mirror of https://github.com/jlelse/GoBlog
parent
63cb57ffaa
commit
9ea6104863
|
@ -55,5 +55,6 @@ Here's an (incomplete) list of features:
|
||||||
|
|
||||||
- [How to install and run GoBlog](./install.md)
|
- [How to install and run GoBlog](./install.md)
|
||||||
- [How to build GoBlog](./build.md)
|
- [How to build GoBlog](./build.md)
|
||||||
|
- [How to use GoBlog](./usage.md)
|
||||||
- [Administration paths](./admin-paths.md)
|
- [Administration paths](./admin-paths.md)
|
||||||
- [GoBlog's storage system](./storage.md)
|
- [GoBlog's storage system](./storage.md)
|
|
@ -0,0 +1,9 @@
|
||||||
|
# How to use GoBlog
|
||||||
|
|
||||||
|
This section of the documentation is a work in progress!
|
||||||
|
|
||||||
|
## Posting
|
||||||
|
|
||||||
|
### Scheduling posts
|
||||||
|
|
||||||
|
To schedule a post, create a post with `status: scheduled` and set the `published` field to the desired date. A scheduler runs in the background and checks every 10 seconds if a scheduled post should be published. If there's a post to publish, the post status is changed to `published`. That will also trigger configured hooks. Scheduled posts are only visible when logged in.
|
|
@ -227,6 +227,8 @@ func (a *goBlog) editorPostDesc(blog string) string {
|
||||||
t := a.ts.GetTemplateStringVariant(bc.Lang, "editorpostdesc")
|
t := a.ts.GetTemplateStringVariant(bc.Lang, "editorpostdesc")
|
||||||
var paramBuilder, statusBuilder strings.Builder
|
var paramBuilder, statusBuilder strings.Builder
|
||||||
for i, param := range []string{
|
for i, param := range []string{
|
||||||
|
"published",
|
||||||
|
"updated",
|
||||||
"summary",
|
"summary",
|
||||||
"translationkey",
|
"translationkey",
|
||||||
"original",
|
"original",
|
||||||
|
@ -252,7 +254,7 @@ func (a *goBlog) editorPostDesc(blog string) string {
|
||||||
paramBuilder.WriteByte('`')
|
paramBuilder.WriteByte('`')
|
||||||
}
|
}
|
||||||
for i, status := range []postStatus{
|
for i, status := range []postStatus{
|
||||||
statusDraft, statusPublished, statusUnlisted, statusPrivate,
|
statusDraft, statusPublished, statusUnlisted, statusScheduled, statusPrivate,
|
||||||
} {
|
} {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
statusBuilder.WriteString(", ")
|
statusBuilder.WriteString(", ")
|
||||||
|
|
2
http.go
2
http.go
|
@ -265,7 +265,7 @@ func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
||||||
case statusPublished, statusUnlisted:
|
case statusPublished, statusUnlisted:
|
||||||
alicePrivate.Append(a.checkActivityStreamsRequest, a.cacheMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
alicePrivate.Append(a.checkActivityStreamsRequest, a.cacheMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case statusDraft, statusPrivate:
|
default: // private, draft, scheduled
|
||||||
alice.New(a.authMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
alice.New(a.authMiddleware).ThenFunc(a.servePost).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
1
main.go
1
main.go
|
@ -175,6 +175,7 @@ func (app *goBlog) initComponents(logging bool) {
|
||||||
app.initBlogStats()
|
app.initBlogStats()
|
||||||
app.initSessions()
|
app.initSessions()
|
||||||
app.initIndieAuth()
|
app.initIndieAuth()
|
||||||
|
app.startPostsScheduler()
|
||||||
// Log finish
|
// Log finish
|
||||||
if logging {
|
if logging {
|
||||||
log.Println("Initialized components")
|
log.Println("Initialized components")
|
||||||
|
|
1
posts.go
1
posts.go
|
@ -42,6 +42,7 @@ const (
|
||||||
statusDraft postStatus = "draft"
|
statusDraft postStatus = "draft"
|
||||||
statusPrivate postStatus = "private"
|
statusPrivate postStatus = "private"
|
||||||
statusUnlisted postStatus = "unlisted"
|
statusUnlisted postStatus = "unlisted"
|
||||||
|
statusScheduled postStatus = "scheduled"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -276,6 +276,7 @@ type postsRequestConfig struct {
|
||||||
parameter string // Ignores parameters
|
parameter string // Ignores parameters
|
||||||
parameterValue string
|
parameterValue string
|
||||||
publishedYear, publishedMonth, publishedDay int
|
publishedYear, publishedMonth, publishedDay int
|
||||||
|
publishedBefore time.Time
|
||||||
randomOrder bool
|
randomOrder bool
|
||||||
priorityOrder bool
|
priorityOrder bool
|
||||||
withoutParameters bool
|
withoutParameters bool
|
||||||
|
@ -360,6 +361,10 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
|
||||||
queryBuilder.WriteString(" and substr(tolocal(published), 9, 2) = @publishedday")
|
queryBuilder.WriteString(" and substr(tolocal(published), 9, 2) = @publishedday")
|
||||||
args = append(args, sql.Named("publishedday", fmt.Sprintf("%02d", c.publishedDay)))
|
args = append(args, sql.Named("publishedday", fmt.Sprintf("%02d", c.publishedDay)))
|
||||||
}
|
}
|
||||||
|
if !c.publishedBefore.IsZero() {
|
||||||
|
queryBuilder.WriteString(" and toutc(published) < @publishedbefore")
|
||||||
|
args = append(args, sql.Named("publishedbefore", c.publishedBefore.UTC().Format(time.RFC3339)))
|
||||||
|
}
|
||||||
// Order
|
// Order
|
||||||
queryBuilder.WriteString(" order by ")
|
queryBuilder.WriteString(" order by ")
|
||||||
if c.randomOrder {
|
if c.randomOrder {
|
||||||
|
|
|
@ -137,7 +137,7 @@ func (a *goBlog) postToMfItem(p *post) *microformatItem {
|
||||||
switch p.Status {
|
switch p.Status {
|
||||||
case statusDraft:
|
case statusDraft:
|
||||||
mfStatus = "draft"
|
mfStatus = "draft"
|
||||||
case statusPublished:
|
case statusPublished, statusScheduled:
|
||||||
mfStatus = "published"
|
mfStatus = "published"
|
||||||
mfVisibility = "public"
|
mfVisibility = "public"
|
||||||
case statusUnlisted:
|
case statusUnlisted:
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *goBlog) startPostsScheduler() {
|
||||||
|
ticker := time.NewTicker(10 * time.Second)
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
a.checkScheduledPosts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
a.shutdown.Add(func() {
|
||||||
|
ticker.Stop()
|
||||||
|
done <- true
|
||||||
|
log.Println("Posts scheduler stopped")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) checkScheduledPosts() {
|
||||||
|
postsToPublish, err := a.getPosts(&postsRequestConfig{
|
||||||
|
status: "scheduled",
|
||||||
|
publishedBefore: time.Now(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error getting scheduled posts:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, post := range postsToPublish {
|
||||||
|
post.Status = "published"
|
||||||
|
err := a.replacePost(post, post.Path, statusScheduled)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error publishing scheduled post:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Println("Published scheduled post:", post.Path)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_postsScheduler(t *testing.T) {
|
||||||
|
|
||||||
|
app := &goBlog{
|
||||||
|
cfg: &config{
|
||||||
|
Db: &configDb{
|
||||||
|
File: filepath.Join(t.TempDir(), "test.db"),
|
||||||
|
},
|
||||||
|
Server: &configServer{
|
||||||
|
PublicAddress: "https://example.com",
|
||||||
|
},
|
||||||
|
DefaultBlog: "en",
|
||||||
|
Blogs: map[string]*configBlog{
|
||||||
|
"en": {
|
||||||
|
Sections: map[string]*configSection{
|
||||||
|
"test": {},
|
||||||
|
},
|
||||||
|
Lang: "en",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Micropub: &configMicropub{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = app.initDatabase(false)
|
||||||
|
app.initComponents(false)
|
||||||
|
|
||||||
|
err := app.db.savePost(&post{
|
||||||
|
Path: "/test/abc",
|
||||||
|
Content: "ABC",
|
||||||
|
Published: toLocalSafe(time.Now().Add(-1 * time.Hour).String()),
|
||||||
|
Blog: "en",
|
||||||
|
Section: "test",
|
||||||
|
Status: statusScheduled,
|
||||||
|
}, &postCreationOptions{new: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
count, err := app.db.countPosts(&postsRequestConfig{status: statusPublished})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
|
||||||
|
app.checkScheduledPosts()
|
||||||
|
|
||||||
|
count, err = app.db.countPosts(&postsRequestConfig{status: statusPublished})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,12 @@ import (
|
||||||
func (a *goBlog) initTelegram() {
|
func (a *goBlog) initTelegram() {
|
||||||
a.pPostHooks = append(a.pPostHooks, func(p *post) {
|
a.pPostHooks = append(a.pPostHooks, func(p *post) {
|
||||||
if tg := a.cfg.Blogs[p.Blog].Telegram; tg.enabled() && p.isPublishedSectionPost() {
|
if tg := a.cfg.Blogs[p.Blog].Telegram; tg.enabled() && p.isPublishedSectionPost() {
|
||||||
|
tgChat := p.firstParameter("telegramchat")
|
||||||
|
tgMsg := p.firstParameter("telegrammsg")
|
||||||
|
if tgChat != "" && tgMsg != "" {
|
||||||
|
// Already posted
|
||||||
|
return
|
||||||
|
}
|
||||||
// Generate HTML
|
// Generate HTML
|
||||||
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
|
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
|
||||||
if html == "" {
|
if html == "" {
|
||||||
|
|
|
@ -7,5 +7,6 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ $short := shorturl .Data }}
|
{{ $short := shorturl .Data }}
|
||||||
{{ if $short }}<div>{{ string .Blog.Lang "shorturl" }} <a href="{{ $short }}" rel="shortlink">{{ $short }}</a></div>{{ end }}
|
{{ if $short }}<div>{{ string .Blog.Lang "shorturl" }} <a href="{{ $short }}" rel="shortlink">{{ $short }}</a></div>{{ end }}
|
||||||
|
{{ if ne .Data.Status "published" }}<div>{{ string .Blog.Lang "status" }}: {{ .Data.Status }}</div>{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
|
@ -48,6 +48,7 @@ send: "Senden (zur Überprüfung)"
|
||||||
share: "Online teilen"
|
share: "Online teilen"
|
||||||
shorturl: "Kurz-Link:"
|
shorturl: "Kurz-Link:"
|
||||||
speak: "Vorlesen"
|
speak: "Vorlesen"
|
||||||
|
status: "Status"
|
||||||
stopspeak: "Vorlesen stoppen"
|
stopspeak: "Vorlesen stoppen"
|
||||||
submit: "Abschicken"
|
submit: "Abschicken"
|
||||||
total: "Gesamt"
|
total: "Gesamt"
|
||||||
|
|
|
@ -60,6 +60,7 @@ send: "Send (to review)"
|
||||||
share: "Share online"
|
share: "Share online"
|
||||||
shorturl: "Short link:"
|
shorturl: "Short link:"
|
||||||
speak: "Read aloud"
|
speak: "Read aloud"
|
||||||
|
status: "Status"
|
||||||
stopspeak: "Stop reading aloud"
|
stopspeak: "Stop reading aloud"
|
||||||
submit: "Submit"
|
submit: "Submit"
|
||||||
total: "Total"
|
total: "Total"
|
||||||
|
|
|
@ -60,6 +60,7 @@ send: "Enviar (para revisão)"
|
||||||
share: "Compartilhar online"
|
share: "Compartilhar online"
|
||||||
shorturl: "Link curto:"
|
shorturl: "Link curto:"
|
||||||
speak: "Leia"
|
speak: "Leia"
|
||||||
|
status: "Status"
|
||||||
stopspeak: "Pare de ler"
|
stopspeak: "Pare de ler"
|
||||||
submit: "Enviar"
|
submit: "Enviar"
|
||||||
total: "Total"
|
total: "Total"
|
||||||
|
|
Loading…
Reference in New Issue