Improved timehandling (save everything as UTC) and new post status: unlisted and private

This commit is contained in:
Jan-Lukas Else 2021-07-14 15:44:57 +02:00
parent eecc3a7d35
commit caf21a07fd
17 changed files with 123 additions and 72 deletions

View File

@ -63,7 +63,7 @@ with filtered as (
from (
select
path,
coalesce(published, '') as pub,
tolocal(published) as pub,
mdtext(coalesce(content, '')) as content
from posts
where status = @status and blog = @blog

View File

@ -30,7 +30,7 @@ func Test_captchaMiddleware(t *testing.T) {
},
}
app.initDatabase(false)
_ = app.initDatabase(false)
app.initSessions()
_ = app.initTemplateStrings()
_ = app.initRendering()

View File

@ -22,7 +22,7 @@ func (a *goBlog) checkAllExternalLinks() {
log.Println(err.Error())
return
}
a.checkLinks(log.Writer(), posts...)
_ = a.checkLinks(log.Writer(), posts...)
}
func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {

View File

@ -230,18 +230,20 @@ func migrateDb(db *sql.DB, logging bool) error {
return err
},
},
&migrator.Migration{
Name: "00019",
Func: func(tx *sql.Tx) error {
_, err := tx.Exec(`
update posts set published = toutc(published), updated = toutc(updated);
insert into posts_fts(posts_fts) values ('rebuild');
`)
return err
},
},
),
)
if err != nil {
return err
}
err = m.Migrate(db)
if err != nil {
return err
}
// Update times in database to local time
_, err = db.Exec(`
update posts set published = tolocal(published), updated = tolocal(updated);
`)
return err
return m.Migrate(db)
}

View File

@ -17,9 +17,7 @@ func (a *goBlog) serveEditor(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
a.render(w, r, templateEditor, &renderData{
BlogString: blog,
Data: map[string]interface{}{
"Drafts": a.db.getDrafts(blog),
},
Data: map[string]interface{}{},
})
}
@ -32,7 +30,6 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
BlogString: blog,
Data: map[string]interface{}{
"DeleteURL": r.FormValue("url"),
"Drafts": a.db.getDrafts(blog),
},
})
case "loadupdate":
@ -51,7 +48,6 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
Data: map[string]interface{}{
"UpdatePostURL": parsedURL.String(),
"UpdatePostContent": a.postToMfItem(post).Properties.Content[0],
"Drafts": a.db.getDrafts(blog),
},
})
case "updatepost":
@ -77,14 +73,6 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
a.editorMicropubPost(w, req, false)
case "upload":
a.editorMicropubPost(w, r, true)
case "viewdraft":
parsedURL, err := url.Parse(r.FormValue("url"))
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
http.Redirect(w, r, parsedURL.Path, http.StatusFound)
return
default:
a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
}

2
go.mod
View File

@ -32,7 +32,7 @@ require (
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/lestrrat-go/strftime v1.0.5 // indirect
github.com/lib/pq v1.9.0 // indirect
github.com/lopezator/migrator v0.3.0
github.com/mattn/go-sqlite3 v1.14.7

4
go.sum
View File

@ -273,8 +273,8 @@ github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2t
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE=
github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=

40
http.go
View File

@ -198,6 +198,15 @@ func (a *goBlog) buildStaticHandlersRouters() error {
a.editorRouter.Get("/files", a.serveEditorFiles)
a.editorRouter.Post("/files/view", a.serveEditorFilesView)
a.editorRouter.Post("/files/delete", a.serveEditorFilesDelete)
a.editorRouter.Get("/drafts", a.serveDrafts)
a.editorRouter.Get("/drafts"+feedPath, a.serveDrafts)
a.editorRouter.Get("/drafts"+paginationPath, a.serveDrafts)
a.editorRouter.Get("/private", a.servePrivate)
a.editorRouter.Get("/private"+feedPath, a.servePrivate)
a.editorRouter.Get("/private"+paginationPath, a.servePrivate)
a.editorRouter.Get("/unlisted", a.serveUnlisted)
a.editorRouter.Get("/unlisted"+feedPath, a.serveUnlisted)
a.editorRouter.Get("/unlisted"+paginationPath, a.serveUnlisted)
a.commentsRouter = chi.NewRouter()
a.commentsRouter.Use(a.privateModeHandler...)
@ -331,7 +340,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
r.Mount(notificationsPath, a.notificationsRouter)
// Posts
pp, err := a.db.allPostPaths(statusPublished)
pp, err := a.db.getPostPaths(statusPublished)
if err != nil {
return nil, err
}
@ -343,8 +352,33 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
}
})
// Drafts
dp, err := a.db.allPostPaths(statusDraft)
// Unlisted posts
up, err := a.db.getPostPaths(statusUnlisted)
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.checkActivityStreamsRequest, a.cache.cacheMiddleware)
for _, path := range up {
r.Get(path, a.servePost)
}
})
// Private posts
priv, err := a.db.getPostPaths(statusPrivate)
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(a.authMiddleware)
for _, path := range priv {
r.Get(path, a.servePost)
}
})
// Draft posts
dp, err := a.db.getPostPaths(statusDraft)
if err != nil {
return nil, err
}

View File

@ -40,6 +40,8 @@ const (
statusNil postStatus = ""
statusPublished postStatus = "published"
statusDraft postStatus = "draft"
statusPrivate postStatus = "private"
statusUnlisted postStatus = "unlisted"
)
func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
@ -121,6 +123,33 @@ func (a *goBlog) serveHome(w http.ResponseWriter, r *http.Request) {
})))
}
func (a *goBlog) serveDrafts(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
path: a.getRelativePath(blog, "/editor/drafts"),
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "drafts"),
status: statusDraft,
})))
}
func (a *goBlog) servePrivate(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
path: a.getRelativePath(blog, "/editor/private"),
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "privateposts"),
status: statusPrivate,
})))
}
func (a *goBlog) serveUnlisted(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
path: a.getRelativePath(blog, "/editor/unlisted"),
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "unlistedposts"),
status: statusUnlisted,
})))
}
func (a *goBlog) serveDate(w http.ResponseWriter, r *http.Request) {
var year, month, day int
if ys := chi.URLParam(r, "year"); ys != "" && ys != "x" {
@ -176,6 +205,7 @@ type indexConfig struct {
title string
description string
summaryTemplate string
status postStatus
}
const defaultPhotosPath = "/photos"
@ -203,6 +233,10 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
sections = append(sections, sectionKey)
}
}
status := ic.status
if status == statusNil {
status = statusPublished
}
p := paginator.New(&postPaginationAdapter{config: &postsRequestConfig{
blog: blog,
sections: sections,
@ -213,7 +247,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
publishedYear: ic.year,
publishedMonth: ic.month,
publishedDay: ic.day,
status: statusPublished,
status: status,
priorityOrder: true,
}, db: a.db}, a.cfg.Blogs[blog].Pagination)
p.SetPage(pageNo)

View File

@ -132,7 +132,7 @@ func (a *goBlog) createOrReplacePost(p *post, o *postCreationOptions) error {
}
// Trigger hooks
if p.Status == statusPublished {
if o.new || o.oldStatus == statusDraft {
if o.new || o.oldStatus != statusPublished {
defer a.postPostHooks(p)
} else {
defer a.postUpdateHooks(p)
@ -163,7 +163,7 @@ func (db *database) savePost(p *post, o *postCreationOptions) error {
}
// Insert new post
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status, priority) values (?, ?, ?, ?, ?, ?, ?, ?);")
sqlArgs = append(sqlArgs, p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section, p.Status, p.Priority)
sqlArgs = append(sqlArgs, p.Path, p.Content, toUTCSafe(p.Published), toUTCSafe(p.Updated), p.Blog, p.Section, p.Status, p.Priority)
// Insert post parameters
for param, value := range p.Parameters {
for _, value := range value {
@ -278,15 +278,15 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
wheres = append(wheres, ws)
}
if c.publishedYear != 0 {
wheres = append(wheres, "substr(published, 1, 4) = @publishedyear")
wheres = append(wheres, "substr(tolocal(published), 1, 4) = @publishedyear")
args = append(args, sql.Named("publishedyear", fmt.Sprintf("%0004d", c.publishedYear)))
}
if c.publishedMonth != 0 {
wheres = append(wheres, "substr(published, 6, 2) = @publishedmonth")
wheres = append(wheres, "substr(tolocal(published), 6, 2) = @publishedmonth")
args = append(args, sql.Named("publishedmonth", fmt.Sprintf("%02d", c.publishedMonth)))
}
if c.publishedDay != 0 {
wheres = append(wheres, "substr(published, 9, 2) = @publishedday")
wheres = append(wheres, "substr(tolocal(published), 9, 2) = @publishedday")
args = append(args, sql.Named("publishedday", fmt.Sprintf("%02d", c.publishedDay)))
}
if len(wheres) > 0 {
@ -385,11 +385,6 @@ func (d *database) getPost(path string) (*post, error) {
return posts[0], nil
}
func (d *database) getDrafts(blog string) []*post {
ps, _ := d.getPosts(&postsRequestConfig{status: statusDraft, blog: blog})
return ps
}
func (d *database) countPosts(config *postsRequestConfig) (count int, err error) {
query, params := buildPostsQuery(config, "path")
row, err := d.queryRow("select count(distinct path) from ("+query+")", params...)
@ -400,7 +395,7 @@ func (d *database) countPosts(config *postsRequestConfig) (count int, err error)
return
}
func (d *database) allPostPaths(status postStatus) ([]string, error) {
func (d *database) getPostPaths(status postStatus) ([]string, error) {
var postPaths []string
rows, err := d.query("select path from posts where status = @status", sql.Named("status", status))
if err != nil {
@ -456,7 +451,7 @@ type publishedDate struct {
}
func (d *database) allPublishedDates(blog string) (dates []publishedDate, err error) {
rows, err := d.query("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))
rows, err := d.query("select distinct substr(tolocal(published), 1, 4) as year, substr(tolocal(published), 6, 2) as month, substr(tolocal(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

@ -28,7 +28,7 @@ func Test_postsDb(t *testing.T) {
},
},
}
app.initDatabase(false)
_ = app.initDatabase(false)
now := toLocalSafe(time.Now().String())
nowPlus1Hour := toLocalSafe(time.Now().Add(1 * time.Hour).String())
@ -64,18 +64,21 @@ func Test_postsDb(t *testing.T) {
is.Equal([]string{"C", "A", "B"}, p.Parameters["tags"])
// Check number of post paths
pp, err := app.db.allPostPaths(statusDraft)
pp, err := app.db.getPostPaths(statusDraft)
must.NoError(err)
if is.Len(pp, 1) {
is.Equal("/test/abc", pp[0])
}
pp, err = app.db.allPostPaths(statusPublished)
pp, err = app.db.getPostPaths(statusPublished)
must.NoError(err)
is.Len(pp, 0)
// Check drafts
drafts := app.db.getDrafts("en")
drafts, _ := app.db.getPosts(&postsRequestConfig{
blog: "en",
status: statusDraft,
})
is.Len(drafts, 1)
// Check by parameter
@ -222,7 +225,7 @@ func Test_ftsWithoutTitle(t *testing.T) {
},
},
}
app.initDatabase(false)
_ = app.initDatabase(false)
err := app.db.savePost(&post{
Path: "/test/abc",
@ -252,7 +255,7 @@ func Test_postsPriority(t *testing.T) {
},
},
}
app.initDatabase(false)
_ = app.initDatabase(false)
err := app.db.savePost(&post{
Path: "/test/abc",
@ -301,7 +304,7 @@ func Test_usesOfMediaFile(t *testing.T) {
},
},
}
app.initDatabase(false)
_ = app.initDatabase(false)
err := app.db.savePost(&post{
Path: "/test/abc",

View File

@ -133,7 +133,7 @@ func Test_telegram(t *testing.T) {
},
httpClient: fakeClient,
}
app.initDatabase(false)
_ = app.initDatabase(false)
app.initTelegram()
@ -177,7 +177,7 @@ func Test_telegram(t *testing.T) {
},
httpClient: fakeClient,
}
app.initDatabase(false)
_ = app.initDatabase(false)
app.initTelegram()

View File

@ -44,6 +44,10 @@ tags:
<input type="submit" value="{{ string .Blog.Lang "delete" }}">
{{ end }}
</form>
<h2>{{ string .Blog.Lang "posts" }}</h2>
<p><a href="{{ .Blog.RelativePath "/editor/drafts" }}">{{ string .Blog.Lang "drafts" }}</a></p>
<p><a href="{{ .Blog.RelativePath "/editor/private" }}">{{ string .Blog.Lang "privateposts" }}</a></p>
<p><a href="{{ .Blog.RelativePath "/editor/unlisted" }}">{{ string .Blog.Lang "unlistedposts" }}</a></p>
<h2>{{ string .Blog.Lang "upload" }}</h2>
<form class="fw-form p" method="post" enctype="multipart/form-data">
<input type="hidden" name="editoraction" value="upload">
@ -51,18 +55,6 @@ tags:
<input type="submit" value="{{ string .Blog.Lang "upload" }}">
</form>
<p><a href="{{ .Blog.RelativePath "/editor/files" }}">{{ string .Blog.Lang "mediafiles" }}</a></p>
{{ if .Data.Drafts }}
<h2>{{ string .Blog.Lang "drafts" }}</h2>
<form class="fw-form p" method="post">
<input type="hidden" name="editoraction" value="viewdraft">
<select name="url">
{{ range $i, $draft := .Data.Drafts }}
<option value="{{ absolute $draft.Path }}">{{ with ($draft.Title) }}{{ . }}{{ else }}{{ $draft.Path }}{{ end }}</option>
{{ end }}
</select>
<input type="submit" value="{{ string .Blog.Lang "view" }}">
</form>
{{ end }}
<h2>{{ string .Blog.Lang "location" }}</h2>
<form class="fw-form p">
<input id="geobtn" type="button" value="{{ string .Blog.Lang "locationget" }}" data-failed="{{ string .Blog.Lang "locationfailed" }}" data-notsupported="{{ string .Blog.Lang "locationnotsupported" }}">

View File

@ -29,6 +29,7 @@ oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist mögli
pinned: "Angepinnt"
posts: "Posts"
prev: "Zurück"
privateposts: "Private Posts"
publishedon: "Veröffentlicht am"
replyto: "Antwort an"
search: "Suchen"
@ -40,6 +41,7 @@ stopspeak: "Vorlesen stoppen"
total: "Gesamt"
translate: "Übersetzen"
translations: "Übersetzungen"
unlistedposts: "Ungelistete Posts"
update: "Aktualisieren"
updatedon: "Aktualisiert am"
upload: "Hochladen"

View File

@ -41,6 +41,7 @@ password: "Password"
pinned: "Pinned"
posts: "Posts"
prev: "Previous"
privateposts: "Private posts"
publishedon: "Published on"
replyto: "Reply to"
reverify: "Reverify"
@ -56,6 +57,7 @@ total: "Total"
totp: "TOTP"
translate: "Translate"
translations: "Translations"
unlistedposts: "Unlisted posts"
update: "Update"
updatedon: "Updated on"
upload: "Upload"

View File

@ -34,9 +34,7 @@ type mention struct {
func (a *goBlog) initWebmention() {
// Add hooks
hookFunc := func(p *post) {
if p.Status == statusPublished {
_ = a.sendWebmentions(p)
}
_ = a.sendWebmentions(p)
}
a.pPostHooks = append(a.pPostHooks, hookFunc)
a.pUpdateHooks = append(a.pUpdateHooks, hookFunc)

View File

@ -15,6 +15,10 @@ import (
)
func (a *goBlog) sendWebmentions(p *post) error {
if p.Status != statusPublished && p.Status != statusUnlisted {
// Not published or unlisted
return nil
}
if wm := a.cfg.Webmention; wm != nil && wm.DisableSending {
// Just ignore the mentions
return nil
@ -43,10 +47,7 @@ func (a *goBlog) sendWebmentions(p *post) error {
// Private mode, don't send external mentions
continue
}
if wm := a.cfg.Webmention; wm != nil && wm.DisableSending {
// Just ignore the mention
continue
}
// Send webmention
endpoint := a.discoverEndpoint(link)
if endpoint == "" {
continue