This commit is contained in:
Jan-Lukas Else 2021-01-15 21:56:46 +01:00
parent 55902a2de9
commit d1850300e5
19 changed files with 201 additions and 82 deletions

9
api.go
View File

@ -6,6 +6,8 @@ import (
"strings"
)
// Not tested anymore
func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
blog := r.URL.Query().Get("blog")
path := r.URL.Query().Get("path")
@ -29,7 +31,7 @@ func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
p.Path = path
p.Section = section
p.Slug = slug
err = p.replace()
err = p.create()
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
@ -49,12 +51,11 @@ func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
}
if len(aliases) > 0 {
p.Parameters["aliases"] = aliases
err = p.replace()
err = p.replace(p.Path, p.Status)
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
}
w.Header().Set("Location", p.fullURL())
w.WriteHeader(http.StatusCreated)
http.Redirect(w, r, p.fullURL(), http.StatusCreated)
}

View File

@ -1,29 +1,36 @@
package main
import (
"database/sql"
"net/http"
)
func serveBlogStats(blog, statsPath string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var totalCount int
row, err := appDbQueryRow("select count(path) as count from posts where blog = @blog", sql.Named("blog", blog))
// Build query
query, params := buildQuery(&postsRequestConfig{
blog: blog,
status: statusPublished,
})
// Count total posts
row, err := appDbQueryRow("select count(distinct path) from ("+query+")", params...)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
var totalCount int
err = row.Scan(&totalCount)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
var years, counts []int
rows, err := appDbQuery("select substr(published, 1, 4) as year, count(path) as count from posts where blog = @blog and coalesce(published, '') != '' group by year order by year desc", sql.Named("blog", blog))
// Count posts per year
rows, err := appDbQuery(`select substr(published, 1, 4) as year, count(distinct path) as count from (`+query+`)
where published != '' group by year order by year desc`, params...)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
var years, counts []int
for rows.Next() {
var year, count int
rows.Scan(&year, &count)

View File

@ -106,6 +106,21 @@ func migrateDb() error {
return err
},
},
&migrator.Migration{
Name: "00009",
Func: func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table posts add status text;
update posts set status = 'published';
drop view view_posts_with_title;
drop table posts_fts;
create view view_posts_with_title as select id, path, title, content, published, updated, blog, section, status from (select p.rowid as id, p.path as path, pp.value as title, content, published, updated, blog, section, status from posts p left outer join post_parameters pp on p.path = pp.path where pp.parameter = 'title');
create virtual table posts_fts using fts5(path unindexed, title, content, published unindexed, updated unindexed, blog unindexed, section unindexed, status unindexed, content=view_posts_with_title, content_rowid=id);
insert into posts_fts(posts_fts) values ('rebuild');
`)
return err
},
},
),
)
if err != nil {

View File

@ -11,7 +11,11 @@ import (
const editorPath = "/editor"
func serveEditor(w http.ResponseWriter, _ *http.Request) {
render(w, templateEditor, &renderData{})
render(w, templateEditor, &renderData{
Data: map[string]interface{}{
"Drafts": loadDrafts(),
},
})
}
func serveEditorPost(w http.ResponseWriter, r *http.Request) {
@ -33,6 +37,7 @@ func serveEditorPost(w http.ResponseWriter, r *http.Request) {
Data: map[string]interface{}{
"UpdatePostURL": parsedURL.String(),
"UpdatePostContent": mf.Properties.Content[0],
"Drafts": loadDrafts(),
},
})
case "updatepost":
@ -69,6 +74,11 @@ func serveEditorPost(w http.ResponseWriter, r *http.Request) {
editorMicropubPost(w, r, false)
}
func loadDrafts() []*post {
ps, _ := getPosts(&postsRequestConfig{status: statusDraft})
return ps
}
func editorMicropubPost(w http.ResponseWriter, r *http.Request, media bool) {
recorder := httptest.NewRecorder()
if media {
@ -78,7 +88,7 @@ func editorMicropubPost(w http.ResponseWriter, r *http.Request, media bool) {
}
result := recorder.Result()
if location := result.Header.Get("Location"); location != "" {
http.Redirect(w, r, result.Header.Get("Location"), http.StatusFound)
http.Redirect(w, r, location, http.StatusFound)
return
}
if result.StatusCode >= 200 && result.StatusCode <= 400 {

11
go.mod
View File

@ -4,7 +4,7 @@ go 1.15
require (
codeberg.org/jlelse/tinify v0.0.0-20200123222407-7fc9c21822b0
github.com/PuerkitoBio/goquery v1.6.0
github.com/PuerkitoBio/goquery v1.6.1
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4
github.com/caddyserver/certmagic v0.12.0
@ -35,7 +35,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.6
github.com/mholt/acmez v0.1.2 // indirect
github.com/miekg/dns v1.1.35 // indirect
github.com/mitchellh/mapstructure v1.4.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/snabb/sitemap v1.0.0
@ -43,6 +43,7 @@ require (
github.com/spf13/cast v1.3.1
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.7.0 // indirect
github.com/tdewolff/minify/v2 v2.9.10
github.com/tdewolff/parse/v2 v2.5.7 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
@ -55,13 +56,13 @@ require (
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/mod v0.4.0 // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 // indirect
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e // indirect
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

22
go.sum
View File

@ -18,8 +18,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94=
github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk=
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
@ -232,8 +232,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -309,6 +309,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdewolff/minify/v2 v2.9.10 h1:p+ifTTl+JMFFLDYNAm7nxQ9XuCG10HTW00wlPAZ7aoE=
@ -393,8 +395,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -454,8 +456,8 @@ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 h1:DQmQoKxQWtyybCtX/3dIuDBcAhFszqq8YiNeS6sNu1c=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -495,8 +497,8 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnf
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e h1:Z2uDrs8MyXUWJbwGc4V+nGjV4Ygo+oubBbWSVQw21/I=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=

View File

@ -28,6 +28,7 @@ const (
var postHooks = map[postHookType][]func(*post){}
func (p *post) postPostHooks() {
// Hooks after post published
for _, cmdTmplString := range appConfig.Hooks.PostPost {
go func(p *post, cmdTmplString string) {
executeTemplateCommand("post-post", cmdTmplString, map[string]interface{}{
@ -42,6 +43,7 @@ func (p *post) postPostHooks() {
}
func (p *post) postUpdateHooks() {
// Hooks after post updated
for _, cmdTmplString := range appConfig.Hooks.PostUpdate {
go func(p *post, cmdTmplString string) {
executeTemplateCommand("post-update", cmdTmplString, map[string]interface{}{

19
http.go
View File

@ -108,7 +108,7 @@ func buildHandler() (http.Handler, error) {
// Editor
r.Route("/editor", func(mpRouter chi.Router) {
mpRouter.Use(authMiddleware, middleware.NoCache, minifier.Middleware)
mpRouter.Use(middleware.NoCache, minifier.Middleware, authMiddleware)
mpRouter.Get("/", serveEditor)
mpRouter.Post("/", serveEditorPost)
})
@ -137,13 +137,13 @@ func buildHandler() (http.Handler, error) {
r.Route("/webmention", func(webmentionRouter chi.Router) {
webmentionRouter.Use(middleware.NoCache)
webmentionRouter.Post("/", handleWebmention)
webmentionRouter.With(authMiddleware, minifier.Middleware).Get("/", webmentionAdmin)
webmentionRouter.With(minifier.Middleware, authMiddleware).Get("/", webmentionAdmin)
webmentionRouter.With(authMiddleware).Post("/delete", webmentionAdminDelete)
webmentionRouter.With(authMiddleware).Post("/approve", webmentionAdminApprove)
})
// Posts
allPostPaths, err := allPostPaths()
pp, err := allPostPaths(statusPublished)
if err != nil {
return nil, err
}
@ -153,12 +153,23 @@ func buildHandler() (http.Handler, error) {
} else {
postMW = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
}
for _, path := range allPostPaths {
for _, path := range pp {
if path != "" {
r.With(postMW...).Get(path, servePost)
}
}
// Drafts
dp, err := allPostPaths(statusDraft)
if err != nil {
return nil, err
}
for _, path := range dp {
if path != "" {
r.With(cacheMiddleware, minifier.Middleware, authMiddleware).Get(path, servePost)
}
}
// Post aliases
allPostAliases, err := allPostAliases()
if err != nil {

View File

@ -90,6 +90,7 @@ func (p *post) toMfItem() *microformatItem {
params["blog"] = []string{p.Blog}
params["published"] = []string{p.Published}
params["updated"] = []string{p.Updated}
params["status"] = []string{string(p.Status)}
pb, _ := yaml.Marshal(p.Parameters)
content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
return &microformatItem{
@ -98,6 +99,7 @@ func (p *post) toMfItem() *microformatItem {
Name: p.Parameters["title"],
Published: []string{p.Published},
Updated: []string{p.Updated},
PostStatus: []string{string(p.Status)},
Category: p.Parameters[appConfig.Micropub.CategoryParam],
Content: []string{content},
URL: []string{p.fullURL()},
@ -205,6 +207,9 @@ func convertMPValueMapToPost(values map[string][]string) (*post, error) {
if updated, ok := values["updated"]; ok {
entry.Updated = updated[0]
}
if status, ok := values["post-status"]; ok {
entry.Status = postStatus(status[0])
}
// Parameter
if name, ok := values["name"]; ok {
entry.Parameters["title"] = name
@ -269,6 +274,7 @@ type microformatProperties struct {
Name []string `json:"name,omitempty"`
Published []string `json:"published,omitempty"`
Updated []string `json:"updated,omitempty"`
PostStatus []string `json:"post-status,omitempty"`
Category []string `json:"category,omitempty"`
Content []string `json:"content,omitempty"`
URL []string `json:"url,omitempty"`
@ -297,6 +303,9 @@ func convertMPMfToPost(mf *microformatItem) (*post, error) {
if len(mf.Properties.Updated) == 1 {
entry.Updated = mf.Properties.Updated[0]
}
if len(mf.Properties.PostStatus) == 1 {
entry.Status = postStatus(mf.Properties.PostStatus[0])
}
// Parameter
if len(mf.Properties.Name) == 1 {
entry.Parameters["title"] = mf.Properties.Name
@ -370,26 +379,30 @@ func (p *post) computeExtraPostParameters() error {
} else {
p.Blog = appConfig.DefaultBlog
}
if path := p.Parameters["path"]; len(path) == 1 && path[0] != "" {
if path := p.Parameters["path"]; len(path) == 1 {
p.Path = path[0]
delete(p.Parameters, "path")
}
if section := p.Parameters["section"]; len(section) == 1 && section[0] != "" {
if section := p.Parameters["section"]; len(section) == 1 {
p.Section = section[0]
delete(p.Parameters, "section")
}
if slug := p.Parameters["slug"]; len(slug) == 1 && slug[0] != "" {
if slug := p.Parameters["slug"]; len(slug) == 1 {
p.Slug = slug[0]
delete(p.Parameters, "slug")
}
if published := p.Parameters["published"]; len(published) == 1 && published[0] != "" {
if published := p.Parameters["published"]; len(published) == 1 {
p.Published = published[0]
delete(p.Parameters, "published")
}
if updated := p.Parameters["updated"]; len(updated) == 1 && updated[0] != "" {
if updated := p.Parameters["updated"]; len(updated) == 1 {
p.Updated = updated[0]
delete(p.Parameters, "updated")
}
if status := p.Parameters["status"]; len(status) == 1 {
p.Status = postStatus(status[0])
delete(p.Parameters, "status")
}
if p.Path == "" && p.Section == "" {
// Has no path or section -> default section
p.Section = appConfig.Blogs[p.Blog].DefaultSection
@ -437,6 +450,8 @@ func micropubUpdate(w http.ResponseWriter, r *http.Request, u *url.URL, mf *micr
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
oldPath := p.Path
oldStatus := p.Status
if mf.Replace != nil {
for key, value := range mf.Replace {
switch key {
@ -551,7 +566,7 @@ func micropubUpdate(w http.ResponseWriter, r *http.Request, u *url.URL, mf *micr
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
err = p.replace()
err = p.replace(oldPath, oldStatus)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return

View File

@ -44,7 +44,9 @@ func (r *nodeInfoResolver) IsOpenRegistration() (bool, error) {
}
func (r *nodeInfoResolver) Usage() (nodeinfo.Usage, error) {
postCount, _ := countPosts(&postsRequestConfig{})
postCount, _ := countPosts(&postsRequestConfig{
status: statusPublished,
})
u := nodeinfo.Usage{
Users: nodeinfo.UsageUsers{
Total: len(appConfig.Blogs),

View File

@ -25,12 +25,21 @@ type post struct {
Parameters map[string][]string `json:"parameters"`
Blog string `json:"blog"`
Section string `json:"section"`
Status postStatus `json:"status"`
// Not persisted
Slug string `json:"slug"`
rendered template.HTML
absoluteRendered template.HTML
}
type postStatus string
const (
statusNil postStatus = ""
statusPublished postStatus = "published"
statusDraft postStatus = "draft"
)
func servePost(w http.ResponseWriter, r *http.Request) {
as := strings.HasSuffix(r.URL.Path, ".as")
if as {
@ -213,6 +222,7 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
publishedYear: ic.year,
publishedMonth: ic.month,
publishedDay: ic.day,
status: statusPublished,
}}, appConfig.Blogs[ic.blog].Pagination)
p.SetPage(pageNo)
var posts []*post

View File

@ -32,6 +32,10 @@ func (p *post) checkPost() (err error) {
return err
}
}
// Check status
if p.Status == "" {
p.Status = statusPublished
}
// Cleanup params
for key, value := range p.Parameters {
if value == nil {
@ -103,45 +107,75 @@ func (p *post) checkPost() (err error) {
}
func (p *post) create() error {
return p.createOrReplace(true)
return p.createOrReplace(&postCreationOptions{new: true})
}
func (p *post) replace() error {
return p.createOrReplace(false)
func (p *post) replace(oldPath string, oldStatus postStatus) error {
return p.createOrReplace(&postCreationOptions{new: false, oldPath: oldPath, oldStatus: oldStatus})
}
func (p *post) createOrReplace(new bool) error {
type postCreationOptions struct {
new bool
oldPath string
oldStatus postStatus
}
func (p *post) createOrReplace(o *postCreationOptions) error {
err := p.checkPost()
if err != nil {
return err
}
startWritingToDb()
postExists := postExists(p.Path)
if postExists && new {
finishWritingToDb()
return errors.New("post already exists at given path")
}
// Create transaction
tx, err := appDb.Begin()
if err != nil {
finishWritingToDb()
return err
}
if postExists {
_, err := tx.Exec("delete from posts where path = @path", sql.Named("path", p.Path))
if !o.new {
// Remove old post
path := p.Path
if o.oldPath != "" {
path = o.oldPath
}
_, err := tx.Exec("delete from posts where path = @path", sql.Named("path", path))
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err
}
}
// Check if new path exists
postExists := func(path string) (bool, error) {
result := 0
row := tx.QueryRow("select exists(select 1 from posts where path = @path)", sql.Named("path", path))
if err = row.Scan(&result); err != nil {
return false, err
}
return result == 1, nil
}
if exists, err := postExists(p.Path); err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err
} else if exists {
_ = tx.Rollback()
finishWritingToDb()
return errors.New("post already exists at given path")
}
// Create new post
_, err = tx.Exec(
"insert into posts (path, content, published, updated, blog, section) values (@path, @content, @published, @updated, @blog, @section)",
sql.Named("path", p.Path), sql.Named("content", p.Content), sql.Named("published", p.Published), sql.Named("updated", p.Updated), sql.Named("blog", p.Blog), sql.Named("section", p.Section))
`insert into posts (path, content, published, updated, blog, section, status)
values (@path, @content, @published, @updated, @blog, @section, @status)`,
sql.Named("path", p.Path), sql.Named("content", p.Content), sql.Named("published", p.Published),
sql.Named("updated", p.Updated), sql.Named("blog", p.Blog), sql.Named("section", p.Section),
sql.Named("status", p.Status))
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err
}
// Create parameters
ppStmt, err := tx.Prepare("insert into post_parameters (path, parameter, value) values (@path, @parameter, @value)")
if err != nil {
_ = tx.Rollback()
@ -160,18 +194,19 @@ func (p *post) createOrReplace(new bool) error {
}
}
}
err = tx.Commit()
if err != nil {
if tx.Commit() != nil {
finishWritingToDb()
return err
}
finishWritingToDb()
rebuildFTSIndex()
if !postExists {
if p.Status == statusPublished {
if o.new || o.oldStatus == statusDraft {
defer p.postPostHooks()
} else {
defer p.postUpdateHooks()
}
}
return reloadRouter()
}
@ -197,18 +232,6 @@ func rebuildFTSIndex() {
return
}
func postExists(path string) bool {
result := 0
row, err := appDbQueryRow("select exists(select 1 from posts where path = @path)", sql.Named("path", path))
if err != nil {
return false
}
if err = row.Scan(&result); err != nil {
return false
}
return result == 1
}
func getPost(path string) (*post, error) {
posts, err := getPosts(&postsRequestConfig{path: path})
if err != nil {
@ -226,6 +249,7 @@ type postsRequestConfig struct {
limit int
offset int
sections []string
status postStatus
taxonomy *taxonomy
taxonomyValue string
parameter string
@ -235,12 +259,16 @@ type postsRequestConfig struct {
func buildQuery(config *postsRequestConfig) (query string, args []interface{}) {
args = []interface{}{}
defaultSelection := "select p.path as path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), coalesce(blog, ''), coalesce(section, ''), coalesce(parameter, ''), coalesce(value, '') "
defaultSelection := "select p.path as path, coalesce(content, '') as content, coalesce(published, '') as published, coalesce(updated, '') as updated, coalesce(blog, '') as blog, coalesce(section, '') as section, coalesce(status, '') as status, coalesce(parameter, '') as parameter, coalesce(value, '') as value "
postsTable := "posts"
if config.search != "" {
postsTable = "posts_fts(@search)"
args = append(args, sql.Named("search", config.search))
}
if config.status != "" && config.status != statusNil {
postsTable = "(select * from " + postsTable + " where status = @status)"
args = append(args, sql.Named("status", config.status))
}
if config.blog != "" {
postsTable = "(select * from " + postsTable + " where blog = @blog)"
args = append(args, sql.Named("blog", config.blog))
@ -310,7 +338,7 @@ func getPosts(config *postsRequestConfig) (posts []*post, err error) {
for rows.Next() {
p := &post{}
var parameterName, parameterValue string
err = rows.Scan(&p.Path, &p.Content, &p.Published, &p.Updated, &p.Blog, &p.Section, &parameterName, &parameterValue)
err = rows.Scan(&p.Path, &p.Content, &p.Published, &p.Updated, &p.Blog, &p.Section, &p.Status, &parameterName, &parameterValue)
if err != nil {
return nil, err
}
@ -342,9 +370,9 @@ func countPosts(config *postsRequestConfig) (count int, err error) {
return
}
func allPostPaths() ([]string, error) {
func allPostPaths(status postStatus) ([]string, error) {
var postPaths []string
rows, err := appDbQuery("select path from posts")
rows, err := appDbQuery("select path from posts where status = @status", sql.Named("status", status))
if err != nil {
return nil, err
}
@ -375,7 +403,7 @@ type publishedDate struct {
}
func allPublishedDates(blog string) (dates []publishedDate, err error) {
rows, err := appDbQuery("select distinct substr(published, 1, 4) as year, substr(published, 6, 2) as month, substr(published, 9, 2) as day from posts where blog = @blog and year != '' and month != '' and day != ''", sql.Named("blog", blog))
rows, err := appDbQuery("select distinct substr(published, 1, 4) as year, substr(published, 6, 2) as month, substr(published, 9, 2) as day from posts where blog = @blog and status = @status and year != '' and month != '' and day != ''", sql.Named("blog", blog), sql.Named("status", statusPublished))
if err != nil {
return nil, err
}

View File

@ -103,5 +103,5 @@ func (p *post) translations() []*post {
}
func (p *post) isPublishedSectionPost() bool {
return p.Published != "" && p.Section != ""
return p.Published != "" && p.Section != "" && p.Status == statusPublished
}

View File

@ -11,7 +11,9 @@ import (
const sitemapPath = "/sitemap.xml"
func serveSitemap(w http.ResponseWriter, r *http.Request) {
posts, err := getPosts(&postsRequestConfig{})
posts, err := getPosts(&postsRequestConfig{
status: statusPublished,
})
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return

View File

@ -1,4 +1,4 @@
.sans-serif, input, textarea, button, .button, body {
.sans-serif, input, textarea, select, button, .button, body {
font-family: sans-serif;
}
@ -52,7 +52,7 @@ img {
width: 100%;
}
input, textarea, button, .button {
input, textarea, select, button, .button {
border: 1px solid #000;
border: 1px solid var(--primary, #000);
background: #fff;
@ -66,7 +66,7 @@ input, textarea, button, .button {
font-size: 1rem;
}
form input, form textarea {
form input, form textarea, form select {
margin-bottom: 5px;
}

View File

@ -77,7 +77,7 @@ img {
width: 100%;
}
input, textarea, button, .button {
input, textarea, select, button, .button {
@include color-border(border, 1px, solid, primary);
@include color(background, background);
@include color(color, primary);
@ -89,7 +89,7 @@ input, textarea, button, .button {
font-size: 1rem;
}
form input, form textarea {
form input, form textarea, form select {
margin-bottom: 5px;
}

View File

@ -41,6 +41,16 @@ blog:
<input class="fw" type="file" name="file">
<input class="fw" type="submit" value="{{ string .Blog.Lang "upload" }}">
</form>
<h2>{{ string .Blog.Lang "drafts" }}</h2>
<form class="fw-form p" method="post">
<input type="hidden" name="editoraction" value="loadupdate">
<select name="url" class="fw">
{{ range $i, $draft := .Data.Drafts }}
<option value="{{ absolute $draft.Path }}">{{ (title $draft) | default $draft.Path }}</option>
{{ end }}
</select>
<input class="fw" type="submit" value="{{ string .Blog.Lang "update" }}">
</form>
</main>
{{ end }}

View File

@ -34,3 +34,4 @@ shorturl: "Short URL:"
year: "Year"
count: "Count"
total: "Total"
drafts: "Drafts"

View File

@ -33,8 +33,10 @@ type mention struct {
func initWebmention() error {
// Add hooks
hookFunc := func(p *post) {
if p.Status == statusPublished {
p.sendWebmentions()
}
}
postHooks[postPostHook] = append(postHooks[postPostHook], hookFunc)
postHooks[postUpdateHook] = append(postHooks[postUpdateHook], hookFunc)
postHooks[postDeleteHook] = append(postHooks[postDeleteHook], hookFunc)