mirror of https://github.com/jlelse/GoBlog
Drafts
This commit is contained in:
parent
55902a2de9
commit
d1850300e5
9
api.go
9
api.go
|
@ -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)
|
||||
}
|
||||
|
|
17
blogstats.go
17
blogstats.go
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
14
editor.go
14
editor.go
|
@ -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
11
go.mod
|
@ -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
22
go.sum
|
@ -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=
|
||||
|
|
2
hooks.go
2
hooks.go
|
@ -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
19
http.go
|
@ -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 {
|
||||
|
|
27
micropub.go
27
micropub.go
|
@ -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 µformatItem{
|
||||
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
10
posts.go
10
posts.go
|
@ -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
|
||||
|
|
100
postsDb.go
100
postsDb.go
|
@ -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,17 +194,18 @@ func (p *post) createOrReplace(new bool) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if tx.Commit() != nil {
|
||||
finishWritingToDb()
|
||||
return err
|
||||
}
|
||||
finishWritingToDb()
|
||||
rebuildFTSIndex()
|
||||
if !postExists {
|
||||
defer p.postPostHooks()
|
||||
} else {
|
||||
defer p.postUpdateHooks()
|
||||
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, ¶meterName, ¶meterValue)
|
||||
err = rows.Scan(&p.Path, &p.Content, &p.Published, &p.Updated, &p.Blog, &p.Section, &p.Status, ¶meterName, ¶meterValue)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -34,3 +34,4 @@ shorturl: "Short URL:"
|
|||
year: "Year"
|
||||
count: "Count"
|
||||
total: "Total"
|
||||
drafts: "Drafts"
|
|
@ -33,7 +33,9 @@ type mention struct {
|
|||
func initWebmention() error {
|
||||
// Add hooks
|
||||
hookFunc := func(p *post) {
|
||||
p.sendWebmentions()
|
||||
if p.Status == statusPublished {
|
||||
p.sendWebmentions()
|
||||
}
|
||||
}
|
||||
postHooks[postPostHook] = append(postHooks[postPostHook], hookFunc)
|
||||
postHooks[postUpdateHook] = append(postHooks[postUpdateHook], hookFunc)
|
||||
|
|
Loading…
Reference in New Issue