mirror of https://github.com/jlelse/GoBlog
Use slog
This commit is contained in:
parent
5220c497cf
commit
ad7536034a
|
@ -13,7 +13,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -157,7 +156,7 @@ func (a *goBlog) apCheckMentions(p *post) {
|
||||||
links, err := allLinksFromHTML(pr, a.fullPostURL(p))
|
links, err := allLinksFromHTML(pr, a.fullPostURL(p))
|
||||||
_ = pr.CloseWithError(err)
|
_ = pr.CloseWithError(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to extract links from post: " + err.Error())
|
a.error("ActivityPub: Failed to extract links from post", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apc := a.apHttpClients[p.Blog]
|
apc := a.apHttpClients[p.Blog]
|
||||||
|
@ -486,12 +485,12 @@ func (a *goBlog) apUndelete(p *post) {
|
||||||
|
|
||||||
func (a *goBlog) apAccept(blogName string, blog *configBlog, follow *ap.Activity) {
|
func (a *goBlog) apAccept(blogName string, blog *configBlog, follow *ap.Activity) {
|
||||||
newFollower := follow.Actor.GetLink()
|
newFollower := follow.Actor.GetLink()
|
||||||
log.Println("New follow request from follower id:", newFollower.String())
|
a.info("AcitivyPub: New follow request from follower", "id", newFollower.String())
|
||||||
// Get remote actor
|
// Get remote actor
|
||||||
follower, err := a.apGetRemoteActor(newFollower, blogName)
|
follower, err := a.apGetRemoteActor(newFollower, blogName)
|
||||||
if err != nil || follower == nil {
|
if err != nil || follower == nil {
|
||||||
// Couldn't retrieve remote actor info
|
// Couldn't retrieve remote actor info
|
||||||
log.Println("Failed to retrieve remote actor info:", newFollower)
|
a.error("ActivityPub: Failed to retrieve remote actor info", "actor", newFollower)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Add or update follower
|
// Add or update follower
|
||||||
|
@ -529,7 +528,7 @@ func (a *goBlog) apSendProfileUpdates() {
|
||||||
func (a *goBlog) apSendToAllFollowers(blog string, activity *ap.Activity, mentions ...string) {
|
func (a *goBlog) apSendToAllFollowers(blog string, activity *ap.Activity, mentions ...string) {
|
||||||
inboxes, err := a.db.apGetAllInboxes(blog)
|
inboxes, err := a.db.apGetAllInboxes(blog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to retrieve follower inboxes:", err.Error())
|
a.error("ActivityPub: Failed to retrieve follower inboxes", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, m := range mentions {
|
for _, m := range mentions {
|
||||||
|
@ -583,7 +582,7 @@ func (a *goBlog) loadActivityPubPrivateKey() error {
|
||||||
if keyData, err := a.db.retrievePersistentCache("activitypub_key"); err == nil && keyData != nil {
|
if keyData, err := a.db.retrievePersistentCache("activitypub_key"); err == nil && keyData != nil {
|
||||||
privateKeyDecoded, _ := pem.Decode(keyData)
|
privateKeyDecoded, _ := pem.Decode(keyData)
|
||||||
if privateKeyDecoded == nil {
|
if privateKeyDecoded == nil {
|
||||||
log.Println("failed to decode cached private key")
|
a.error("ActivityPub: failed to decode cached private key")
|
||||||
// continue
|
// continue
|
||||||
} else {
|
} else {
|
||||||
key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)
|
key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ func (a *goBlog) initAPSendQueue() {
|
||||||
a.listenOnQueue("ap", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
|
a.listenOnQueue("ap", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
|
||||||
var r apRequest
|
var r apRequest
|
||||||
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&r); err != nil {
|
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&r); err != nil {
|
||||||
log.Println("activitypub queue:", err.Error())
|
a.error("Activitypub queue", "err", err)
|
||||||
dequeue()
|
dequeue()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,7 +39,7 @@ func (a *goBlog) initAPSendQueue() {
|
||||||
bufferpool.Put(buf)
|
bufferpool.Put(buf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("AP request failed for the 20th time:", r.To)
|
a.info("AP request failed for the 20th time", "to", r.To)
|
||||||
_ = a.db.apRemoveInbox(r.To)
|
_ = a.db.apRemoveInbox(r.To)
|
||||||
}
|
}
|
||||||
dequeue()
|
dequeue()
|
||||||
|
|
6
app.go
6
app.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -66,8 +67,11 @@ type goBlog struct {
|
||||||
inLoad sync.Once
|
inLoad sync.Once
|
||||||
// IndieAuth
|
// IndieAuth
|
||||||
ias *indieauth.Server
|
ias *indieauth.Server
|
||||||
// Logs
|
// Logs (HTTP)
|
||||||
logf *rotatelogs.RotateLogs
|
logf *rotatelogs.RotateLogs
|
||||||
|
// Logs (Program)
|
||||||
|
logger *slog.Logger
|
||||||
|
logLevel *slog.LevelVar
|
||||||
// Markdown
|
// Markdown
|
||||||
md, absoluteMd, titleMd goldmark.Markdown
|
md, absoluteMd, titleMd goldmark.Markdown
|
||||||
// Media
|
// Media
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -25,7 +24,7 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||||
return a.getBlogrollOutlines(blog)
|
return a.getBlogrollOutlines(blog)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to get outlines: %v", err)
|
a.error("Blogroll: Failed to get outlines", "err", err)
|
||||||
a.serveError(w, r, "", http.StatusInternalServerError)
|
a.serveError(w, r, "", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -48,7 +47,7 @@ func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
|
||||||
return a.getBlogrollOutlines(blog)
|
return a.getBlogrollOutlines(blog)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to get outlines: %v", err)
|
a.error("Blogroll: Failed to get outlines", "err", err)
|
||||||
a.serveError(w, r, "", http.StatusInternalServerError)
|
a.serveError(w, r, "", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
3
cache.go
3
cache.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -41,7 +40,7 @@ func (a *goBlog) initCache() (err error) {
|
||||||
ticker := time.NewTicker(15 * time.Minute)
|
ticker := time.NewTicker(15 * time.Minute)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
met := a.cache.c.Metrics
|
met := a.cache.c.Metrics
|
||||||
log.Println("\nCache:", met.String())
|
a.info("Cache metrics", "metrics", met.String())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return
|
return
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -104,8 +103,6 @@ func Test_captchaMiddleware(t *testing.T) {
|
||||||
_, captchaSolved := session.Values["captcha"].(bool)
|
_, captchaSolved := session.Values["captcha"].(bool)
|
||||||
assert.False(t, captchaSolved)
|
assert.False(t, captchaSolved)
|
||||||
|
|
||||||
log.Println("Captcha ID:", captchaId)
|
|
||||||
|
|
||||||
// Check form values
|
// Check form values
|
||||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
@ -157,8 +154,6 @@ func Test_captchaMiddleware(t *testing.T) {
|
||||||
_, captchaSolved = session.Values["captcha"].(bool)
|
_, captchaSolved = session.Values["captcha"].(bool)
|
||||||
assert.False(t, captchaSolved)
|
assert.False(t, captchaSolved)
|
||||||
|
|
||||||
log.Println("Captcha ID:", captchaId)
|
|
||||||
|
|
||||||
// Check form values
|
// Check form values
|
||||||
doc, err = goquery.NewDocumentFromReader(res.Body)
|
doc, err = goquery.NewDocumentFromReader(res.Body)
|
||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
|
14
check.go
14
check.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -27,24 +26,24 @@ func (a *goBlog) checkAllExternalLinks() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return a.checkLinks(log.Writer(), posts...)
|
return a.checkLinks(posts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {
|
func (a *goBlog) checkLinks(posts ...*post) error {
|
||||||
// Get all links
|
// Get all links
|
||||||
allLinks, err := a.allLinksToCheck(posts...)
|
allLinks, err := a.allLinksToCheck(posts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Print some info
|
// Print some info
|
||||||
fmt.Fprintln(w, "Checking", len(allLinks), "links")
|
fmt.Println("Checking", len(allLinks), "links")
|
||||||
// Cancel context
|
// Cancel context
|
||||||
cancelContext, cancelFunc := context.WithCancel(context.Background())
|
cancelContext, cancelFunc := context.WithCancel(context.Background())
|
||||||
var done atomic.Bool
|
var done atomic.Bool
|
||||||
a.shutdown.Add(func() {
|
a.shutdown.Add(func() {
|
||||||
done.Store(true)
|
done.Store(true)
|
||||||
cancelFunc()
|
cancelFunc()
|
||||||
fmt.Fprintln(w, "Cancelled link check")
|
fmt.Println("Cancelled link check")
|
||||||
})
|
})
|
||||||
// Create HTTP cache
|
// Create HTTP cache
|
||||||
cache, err := ristretto.NewCache(&ristretto.Config{
|
cache, err := ristretto.NewCache(&ristretto.Config{
|
||||||
|
@ -106,11 +105,12 @@ func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
fmt.Fprintf(w, "%s in %s: %s\n", r.link, r.in, r.err.Error())
|
fmt.Printf("%s in %s: %s\n", r.link, r.in, r.err.Error())
|
||||||
} else if !successStatus(r.status) {
|
} else if !successStatus(r.status) {
|
||||||
fmt.Fprintf(w, "%s in %s: %d (%s)\n", r.link, r.in, r.status, http.StatusText(r.status))
|
fmt.Printf("%s in %s: %d (%s)\n", r.link, r.in, r.status, http.StatusText(r.status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fmt.Println("Finished link check")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -386,6 +385,8 @@ func (a *goBlog) initConfig(logging bool) error {
|
||||||
if a.cfg.initialized {
|
if a.cfg.initialized {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Update log level
|
||||||
|
a.updateLogLevel()
|
||||||
// Init database
|
// Init database
|
||||||
if err := a.initDatabase(logging); err != nil {
|
if err := a.initDatabase(logging); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -561,7 +562,7 @@ func (a *goBlog) initConfig(logging bool) error {
|
||||||
}
|
}
|
||||||
// Log success
|
// Log success
|
||||||
a.cfg.initialized = true
|
a.cfg.initialized = true
|
||||||
log.Println("Initialized configuration")
|
a.info("Initialized configuration")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
|
||||||
// Send submission
|
// Send submission
|
||||||
go func() {
|
go func() {
|
||||||
if err := a.sendContactEmail(bc.Contact, message.String(), formEmail); err != nil {
|
if err := a.sendContactEmail(bc.Contact, message.String(), formEmail); err != nil {
|
||||||
log.Println(err.Error())
|
a.error("Failed to send contact email", "err", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Send notification
|
// Send notification
|
||||||
|
|
19
database.go
19
database.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -17,6 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type database struct {
|
type database struct {
|
||||||
|
a *goBlog
|
||||||
// Basic things
|
// Basic things
|
||||||
db *sql.DB // database
|
db *sql.DB // database
|
||||||
em sync.Mutex // command execution (insert, update, delete ...)
|
em sync.Mutex // command execution (insert, update, delete ...)
|
||||||
|
@ -35,7 +35,7 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if logging {
|
if logging {
|
||||||
log.Println("Initialize database...")
|
a.info("Initialize database")
|
||||||
}
|
}
|
||||||
// Setup db
|
// Setup db
|
||||||
db, err := a.openDatabase(a.cfg.Db.File, logging)
|
db, err := a.openDatabase(a.cfg.Db.File, logging)
|
||||||
|
@ -46,9 +46,9 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
||||||
a.db = db
|
a.db = db
|
||||||
a.shutdown.Add(func() {
|
a.shutdown.Add(func() {
|
||||||
if err := db.close(); err != nil {
|
if err := db.close(); err != nil {
|
||||||
log.Printf("Failed to close database: %v", err)
|
a.error("Failed to close database", "err", err)
|
||||||
} else {
|
} else {
|
||||||
log.Println("Closed database")
|
a.info("Closed database")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if a.cfg.Db.DumpFile != "" {
|
if a.cfg.Db.DumpFile != "" {
|
||||||
|
@ -58,7 +58,7 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
||||||
db.dump(a.cfg.Db.DumpFile)
|
db.dump(a.cfg.Db.DumpFile)
|
||||||
}
|
}
|
||||||
if logging {
|
if logging {
|
||||||
log.Println("Initialized database")
|
a.info("Initialized database")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
||||||
return nil, errors.New("sqlite not compiled with FTS5")
|
return nil, errors.New("sqlite not compiled with FTS5")
|
||||||
}
|
}
|
||||||
// Migrate DB
|
// Migrate DB
|
||||||
err = migrateDb(db, logging)
|
err = a.migrateDb(db, logging)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &database{
|
return &database{
|
||||||
|
a: a,
|
||||||
db: db,
|
db: db,
|
||||||
debug: debug,
|
debug: debug,
|
||||||
psc: psc,
|
psc: psc,
|
||||||
|
@ -163,11 +164,11 @@ func (db *database) dump(file string) {
|
||||||
// Dump database
|
// Dump database
|
||||||
f, err := os.Create(file)
|
f, err := os.Create(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error while dump db:", err.Error())
|
db.a.error("Error while dump db", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = sqlite3dump.DumpDB(db.db, f, sqlite3dump.WithTransaction(true)); err != nil {
|
if err = sqlite3dump.DumpDB(db.db, f, sqlite3dump.WithTransaction(true)); err != nil {
|
||||||
log.Println("Error while dump db:", err.Error())
|
db.a.error("Error while dump db", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +203,7 @@ func (db *database) prepare(query string, args ...any) (*sql.Stmt, []any, error)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if db.debug {
|
if db.debug {
|
||||||
log.Printf(`Failed to prepare query "%s": %s`, query, err.Error())
|
db.a.error("Failed to prepare query", "query", query, "err", err)
|
||||||
}
|
}
|
||||||
return nil, args, err
|
return nil, args, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
@ -25,34 +24,27 @@ func (db *database) dbAfter(ctx context.Context, query string, args ...any) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dur := time.Since(ctx.Value(dbHooksBegin).(time.Time))
|
dur := time.Since(ctx.Value(dbHooksBegin).(time.Time))
|
||||||
logBuilder := builderpool.Get()
|
argsBuilder := builderpool.Get()
|
||||||
logBuilder.WriteString("\nQuery: ")
|
|
||||||
logBuilder.WriteString(`"`)
|
|
||||||
logBuilder.WriteString(query)
|
|
||||||
logBuilder.WriteString(`"`)
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
logBuilder.WriteString("\nArgs: ")
|
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
logBuilder.WriteString(", ")
|
argsBuilder.WriteString(", ")
|
||||||
}
|
}
|
||||||
if named, ok := arg.(sql.NamedArg); ok && named.Name != "" {
|
if named, ok := arg.(sql.NamedArg); ok && named.Name != "" {
|
||||||
logBuilder.WriteString("(")
|
argsBuilder.WriteString("(")
|
||||||
logBuilder.WriteString(named.Name)
|
argsBuilder.WriteString(named.Name)
|
||||||
logBuilder.WriteString(`) "`)
|
argsBuilder.WriteString(`) '`)
|
||||||
logBuilder.WriteString(argToString(named.Value))
|
argsBuilder.WriteString(argToString(named.Value))
|
||||||
logBuilder.WriteString(`"`)
|
argsBuilder.WriteString(`'`)
|
||||||
} else {
|
} else {
|
||||||
logBuilder.WriteString(`"`)
|
argsBuilder.WriteString(`'`)
|
||||||
logBuilder.WriteString(argToString(arg))
|
argsBuilder.WriteString(argToString(arg))
|
||||||
logBuilder.WriteString(`"`)
|
argsBuilder.WriteString(`'`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logBuilder.WriteString("\nDuration: ")
|
db.a.debug("Database query", "query", query, "args", argsBuilder.String(), "duration", dur.String())
|
||||||
logBuilder.WriteString(dur.String())
|
builderpool.Put(argsBuilder)
|
||||||
log.Println(logBuilder.String())
|
|
||||||
builderpool.Put(logBuilder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func argToString(arg any) string {
|
func argToString(arg any) string {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/lopezator/migrator"
|
"github.com/lopezator/migrator"
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
//go:embed dbmigrations/*
|
//go:embed dbmigrations/*
|
||||||
var dbMigrations embed.FS
|
var dbMigrations embed.FS
|
||||||
|
|
||||||
func migrateDb(db *sql.DB, logging bool) error {
|
func (a *goBlog) migrateDb(db *sql.DB, logging bool) error {
|
||||||
var sqlMigrations []any
|
var sqlMigrations []any
|
||||||
err := fs.WalkDir(dbMigrations, "dbmigrations", func(path string, d fs.DirEntry, err error) error {
|
err := fs.WalkDir(dbMigrations, "dbmigrations", func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil || d.Type().IsDir() {
|
if err != nil || d.Type().IsDir() {
|
||||||
|
@ -41,7 +41,7 @@ func migrateDb(db *sql.DB, logging bool) error {
|
||||||
m, err := migrator.New(
|
m, err := migrator.New(
|
||||||
migrator.WithLogger(migrator.LoggerFunc(func(s string, i ...any) {
|
migrator.WithLogger(migrator.LoggerFunc(func(s string, i ...any) {
|
||||||
if logging {
|
if logging {
|
||||||
log.Printf(s, i)
|
a.info(fmt.Sprintf(s, i...))
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
migrator.Migrations(sqlMigrations...),
|
migrator.Migrations(sqlMigrations...),
|
||||||
|
|
9
debug.go
9
debug.go
|
@ -1,9 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
func (a *goBlog) debug(msg ...any) {
|
|
||||||
if a.cfg.Debug {
|
|
||||||
log.Println(append([]any{"Debug:"}, msg...)...)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/tkrajina/gpxgo/gpx"
|
"github.com/tkrajina/gpxgo/gpx"
|
||||||
|
@ -51,7 +50,7 @@ func (a *goBlog) getTrack(p *post, withMapFeatures bool) (result *trackResult, e
|
||||||
parseResult, err := trackParseGPX(gpxString)
|
parseResult, err := trackParseGPX(gpxString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Failed to parse, but just log error
|
// Failed to parse, but just log error
|
||||||
log.Printf("failed to parse GPX: %v", err)
|
a.error("failed to parse GPX", "err", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
34
hooks.go
34
hooks.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ func (a *goBlog) preStartHooks() {
|
||||||
cfg := a.cfg.Hooks
|
cfg := a.cfg.Hooks
|
||||||
for _, cmd := range cfg.PreStart {
|
for _, cmd := range cfg.PreStart {
|
||||||
func(cmd string) {
|
func(cmd string) {
|
||||||
executeHookCommand("pre-start", cfg.Shell, cmd)
|
a.executeHookCommand("pre-start", cfg.Shell, cmd)
|
||||||
}(cmd)
|
}(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +25,7 @@ func (a *goBlog) postPostHooks(p *post) {
|
||||||
if hc := a.cfg.Hooks; hc != nil {
|
if hc := a.cfg.Hooks; hc != nil {
|
||||||
for _, cmdTmplString := range hc.PostPost {
|
for _, cmdTmplString := range hc.PostPost {
|
||||||
go func(p *post, cmdTmplString string) {
|
go func(p *post, cmdTmplString string) {
|
||||||
a.cfg.Hooks.executeTemplateCommand("post-post", cmdTmplString, map[string]any{
|
a.executeHookTemplateCommand("post-post", cmdTmplString, map[string]any{
|
||||||
"URL": a.fullPostURL(p),
|
"URL": a.fullPostURL(p),
|
||||||
"Post": p,
|
"Post": p,
|
||||||
})
|
})
|
||||||
|
@ -46,7 +45,7 @@ func (a *goBlog) postUpdateHooks(p *post) {
|
||||||
if hc := a.cfg.Hooks; hc != nil {
|
if hc := a.cfg.Hooks; hc != nil {
|
||||||
for _, cmdTmplString := range hc.PostUpdate {
|
for _, cmdTmplString := range hc.PostUpdate {
|
||||||
go func(p *post, cmdTmplString string) {
|
go func(p *post, cmdTmplString string) {
|
||||||
a.cfg.Hooks.executeTemplateCommand("post-update", cmdTmplString, map[string]any{
|
a.executeHookTemplateCommand("post-update", cmdTmplString, map[string]any{
|
||||||
"URL": a.fullPostURL(p),
|
"URL": a.fullPostURL(p),
|
||||||
"Post": p,
|
"Post": p,
|
||||||
})
|
})
|
||||||
|
@ -65,7 +64,7 @@ func (a *goBlog) postDeleteHooks(p *post) {
|
||||||
if hc := a.cfg.Hooks; hc != nil {
|
if hc := a.cfg.Hooks; hc != nil {
|
||||||
for _, cmdTmplString := range hc.PostDelete {
|
for _, cmdTmplString := range hc.PostDelete {
|
||||||
go func(p *post, cmdTmplString string) {
|
go func(p *post, cmdTmplString string) {
|
||||||
a.cfg.Hooks.executeTemplateCommand("post-delete", cmdTmplString, map[string]any{
|
a.executeHookTemplateCommand("post-delete", cmdTmplString, map[string]any{
|
||||||
"URL": a.fullPostURL(p),
|
"URL": a.fullPostURL(p),
|
||||||
"Post": p,
|
"Post": p,
|
||||||
})
|
})
|
||||||
|
@ -84,7 +83,7 @@ func (a *goBlog) postUndeleteHooks(p *post) {
|
||||||
if hc := a.cfg.Hooks; hc != nil {
|
if hc := a.cfg.Hooks; hc != nil {
|
||||||
for _, cmdTmplString := range hc.PostUndelete {
|
for _, cmdTmplString := range hc.PostUndelete {
|
||||||
go func(p *post, cmdTmplString string) {
|
go func(p *post, cmdTmplString string) {
|
||||||
a.cfg.Hooks.executeTemplateCommand("post-undelete", cmdTmplString, map[string]any{
|
a.executeHookTemplateCommand("post-undelete", cmdTmplString, map[string]any{
|
||||||
"URL": a.fullPostURL(p),
|
"URL": a.fullPostURL(p),
|
||||||
"Post": p,
|
"Post": p,
|
||||||
})
|
})
|
||||||
|
@ -96,19 +95,20 @@ func (a *goBlog) postUndeleteHooks(p *post) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *configHooks) executeTemplateCommand(hookType string, tmpl string, data map[string]any) {
|
func (a *goBlog) executeHookTemplateCommand(hookType string, tmpl string, data map[string]any) {
|
||||||
|
cfg := a.cfg.Hooks
|
||||||
cmdTmpl, err := template.New("cmd").Parse(tmpl)
|
cmdTmpl, err := template.New("cmd").Parse(tmpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to parse cmd template:", err.Error())
|
a.error("Failed to parse cmd template", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmdBuf := bufferpool.Get()
|
cmdBuf := bufferpool.Get()
|
||||||
defer bufferpool.Put(cmdBuf)
|
defer bufferpool.Put(cmdBuf)
|
||||||
if err = cmdTmpl.Execute(cmdBuf, data); err != nil {
|
if err = cmdTmpl.Execute(cmdBuf, data); err != nil {
|
||||||
log.Println("Failed to execute cmd template:", err.Error())
|
a.error("Failed to execute cmd template", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
executeHookCommand(hookType, cfg.Shell, cmdBuf.String())
|
a.executeHookCommand(hookType, cfg.Shell, cmdBuf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
type hourlyHookFunc func()
|
type hourlyHookFunc func()
|
||||||
|
@ -119,7 +119,7 @@ func (a *goBlog) startHourlyHooks() {
|
||||||
for _, cmd := range cfg.Hourly {
|
for _, cmd := range cfg.Hourly {
|
||||||
c := cmd
|
c := cmd
|
||||||
f := func() {
|
f := func() {
|
||||||
executeHookCommand("hourly", cfg.Shell, c)
|
a.executeHookCommand("hourly", cfg.Shell, c)
|
||||||
}
|
}
|
||||||
a.hourlyHooks = append(a.hourlyHooks, f)
|
a.hourlyHooks = append(a.hourlyHooks, f)
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ func (a *goBlog) startHourlyHooks() {
|
||||||
ticker := time.NewTicker(1 * time.Hour)
|
ticker := time.NewTicker(1 * time.Hour)
|
||||||
a.shutdown.Add(func() {
|
a.shutdown.Add(func() {
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
log.Println("Stopped hourly hooks")
|
a.info("Stopped hourly hooks")
|
||||||
})
|
})
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
for _, f := range a.hourlyHooks {
|
for _, f := range a.hourlyHooks {
|
||||||
|
@ -145,19 +145,19 @@ func (a *goBlog) startHourlyHooks() {
|
||||||
})
|
})
|
||||||
a.shutdown.Add(func() {
|
a.shutdown.Add(func() {
|
||||||
if tr.Stop() {
|
if tr.Stop() {
|
||||||
log.Println("Canceled hourly hooks")
|
a.info("Canceled hourly hooks")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeHookCommand(hookType, shell, cmd string) {
|
func (a *goBlog) executeHookCommand(hookType, shell, cmd string) {
|
||||||
log.Printf("Executing %v hook: %v", hookType, cmd)
|
a.info("Executing hook", "type", hookType, "cmd", cmd)
|
||||||
out, err := exec.Command(shell, "-c", cmd).CombinedOutput()
|
out, err := exec.Command(shell, "-c", cmd).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to execute command:", err.Error())
|
a.error("Failed to execute command", "err", err, "cmd", cmd)
|
||||||
}
|
}
|
||||||
if len(out) > 0 {
|
if len(out) > 0 {
|
||||||
log.Printf("Output:\n%v", string(out))
|
a.info("Hook output", "out", string(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
http.go
17
http.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -33,7 +32,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) startServer() (err error) {
|
func (a *goBlog) startServer() (err error) {
|
||||||
log.Println("Start server(s)...")
|
a.info("Start server(s)...")
|
||||||
// Load router
|
// Load router
|
||||||
a.reloadRouter()
|
a.reloadRouter()
|
||||||
// Set basic middlewares
|
// Set basic middlewares
|
||||||
|
@ -63,7 +62,7 @@ func (a *goBlog) startServer() (err error) {
|
||||||
if a.cfg.Server.Tor {
|
if a.cfg.Server.Tor {
|
||||||
go func() {
|
go func() {
|
||||||
if err := a.startOnionService(finalHandler); err != nil {
|
if err := a.startOnionService(finalHandler); err != nil {
|
||||||
log.Println("Tor failed:", err.Error())
|
a.error("Tor failed", "err", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -82,9 +81,9 @@ func (a *goBlog) startServer() (err error) {
|
||||||
ReadTimeout: 5 * time.Minute,
|
ReadTimeout: 5 * time.Minute,
|
||||||
WriteTimeout: 5 * time.Minute,
|
WriteTimeout: 5 * time.Minute,
|
||||||
}
|
}
|
||||||
a.shutdown.Add(shutdownServer(httpServer, "http server"))
|
a.shutdown.Add(a.shutdownServer(httpServer, "http server"))
|
||||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Println("Failed to start HTTP server:", err.Error())
|
a.error("Failed to start HTTP server", "err", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -94,7 +93,7 @@ func (a *goBlog) startServer() (err error) {
|
||||||
ReadTimeout: 5 * time.Minute,
|
ReadTimeout: 5 * time.Minute,
|
||||||
WriteTimeout: 5 * time.Minute,
|
WriteTimeout: 5 * time.Minute,
|
||||||
}
|
}
|
||||||
a.shutdown.Add(shutdownServer(s, "main server"))
|
a.shutdown.Add(a.shutdownServer(s, "main server"))
|
||||||
s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port)
|
s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port)
|
||||||
if a.cfg.Server.PublicHTTPS {
|
if a.cfg.Server.PublicHTTPS {
|
||||||
s.TLSConfig = a.getAutocertManager().TLSConfig()
|
s.TLSConfig = a.getAutocertManager().TLSConfig()
|
||||||
|
@ -110,14 +109,14 @@ func (a *goBlog) startServer() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func shutdownServer(s *http.Server, name string) func() {
|
func (a *goBlog) shutdownServer(s *http.Server, name string) func() {
|
||||||
return func() {
|
return func() {
|
||||||
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
|
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer c()
|
defer c()
|
||||||
if err := s.Shutdown(toc); err != nil {
|
if err := s.Shutdown(toc); err != nil {
|
||||||
log.Printf("Error on server shutdown (%v): %v", name, err)
|
a.error("Error on server shutdown (%v): %v", name, err)
|
||||||
}
|
}
|
||||||
log.Println("Stopped server:", name)
|
a.info("Stopped server", "name", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
indexnow.go
11
indexnow.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/carlmjohnson/requests"
|
"github.com/carlmjohnson/requests"
|
||||||
|
@ -50,7 +49,7 @@ func (a *goBlog) indexNow(url string) {
|
||||||
}
|
}
|
||||||
key := a.indexNowKey()
|
key := a.indexNowKey()
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
log.Println("Skipping IndexNow")
|
a.info("Skipping IndexNow")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := requests.URL("https://api.indexnow.org/indexnow").
|
err := requests.URL("https://api.indexnow.org/indexnow").
|
||||||
|
@ -59,10 +58,10 @@ func (a *goBlog) indexNow(url string) {
|
||||||
Param("key", string(key)).
|
Param("key", string(key)).
|
||||||
Fetch(context.Background())
|
Fetch(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Sending IndexNow request failed:", err.Error())
|
a.error("Sending IndexNow request failed", "err", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
log.Println("IndexNow request sent for", url)
|
a.info("IndexNow request sent", "url", url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ func (a *goBlog) indexNowKey() []byte {
|
||||||
// Try to load key from database
|
// Try to load key from database
|
||||||
keyBytes, err := a.db.retrievePersistentCache("indexnowkey")
|
keyBytes, err := a.db.retrievePersistentCache("indexnowkey")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to retrieve cached IndexNow key:", err.Error())
|
a.error("Failed to retrieve cached IndexNow key", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if keyBytes == nil {
|
if keyBytes == nil {
|
||||||
|
@ -80,7 +79,7 @@ func (a *goBlog) indexNowKey() []byte {
|
||||||
// Store key in database
|
// Store key in database
|
||||||
err = a.db.cachePersistently("indexnowkey", keyBytes)
|
err = a.db.cachePersistently("indexnowkey", keyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to cache IndexNow key:", err.Error())
|
a.error("Failed to cache IndexNow key", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *goBlog) initLog() {
|
||||||
|
a.logLevel = new(slog.LevelVar)
|
||||||
|
a.logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
Level: a.logLevel,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) updateLogLevel() {
|
||||||
|
if a.logLevel == nil {
|
||||||
|
a.initLog()
|
||||||
|
}
|
||||||
|
if a.cfg.Debug {
|
||||||
|
a.logLevel.Set(slog.LevelDebug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) debug(msg string, args ...any) {
|
||||||
|
a.logger.Debug(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) info(msg string, args ...any) {
|
||||||
|
a.logger.Info(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) error(msg string, args ...any) {
|
||||||
|
a.logger.Error(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) fatal(msg string, args ...any) {
|
||||||
|
a.error(msg, args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
64
main.go
64
main.go
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
netpprof "net/http/pprof"
|
netpprof "net/http/pprof"
|
||||||
|
@ -22,17 +22,23 @@ func main() {
|
||||||
memprofile := flag.String("memprofile", "", "write memory profile to `file`")
|
memprofile := flag.String("memprofile", "", "write memory profile to `file`")
|
||||||
configfile := flag.String("config", "", "use a specific config file")
|
configfile := flag.String("config", "", "use a specific config file")
|
||||||
|
|
||||||
|
// Init app and logger
|
||||||
|
app := &goBlog{
|
||||||
|
httpClient: newHttpClient(),
|
||||||
|
}
|
||||||
|
app.initLog()
|
||||||
|
|
||||||
// Init CPU and memory profiling
|
// Init CPU and memory profiling
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *cpuprofile != "" {
|
if *cpuprofile != "" {
|
||||||
f, err := os.Create(*cpuprofile)
|
f, err := os.Create(*cpuprofile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("could not create CPU profile: ", err)
|
app.fatal("could not create CPU profile", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
log.Fatalln("could not start CPU profile: ", err)
|
app.fatal("could not start CPU profile", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
|
@ -41,29 +47,25 @@ func main() {
|
||||||
defer func() {
|
defer func() {
|
||||||
f, err := os.Create(*memprofile)
|
f, err := os.Create(*memprofile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("could not create memory profile: ", err.Error())
|
app.fatal("could not create memory profile", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
log.Fatalln("could not write memory profile: ", err.Error())
|
app.fatal("could not write memory profile", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &goBlog{
|
|
||||||
httpClient: newHttpClient(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize config
|
// Initialize config
|
||||||
if err = app.loadConfigFile(*configfile); err != nil {
|
if err = app.loadConfigFile(*configfile); err != nil {
|
||||||
app.logErrAndQuit("Failed to load config file:", err.Error())
|
app.logErrAndQuit("Failed to load config file", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = app.initConfig(false); err != nil {
|
if err = app.initConfig(false); err != nil {
|
||||||
app.logErrAndQuit("Failed to init config:", err.Error())
|
app.logErrAndQuit("Failed to init config", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,17 +85,17 @@ func main() {
|
||||||
AccountName: app.cfg.User.Nick,
|
AccountName: app.cfg.User.Nick,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.logErrAndQuit(err.Error())
|
app.logErrAndQuit("Failed to generate TOTP secret", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("TOTP-Secret:", key.Secret())
|
fmt.Println("TOTP-Secret:", key.Secret())
|
||||||
app.shutdown.ShutdownAndWait()
|
app.shutdown.ShutdownAndWait()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize plugins
|
// Initialize plugins
|
||||||
if err = app.initPlugins(); err != nil {
|
if err = app.initPlugins(); err != nil {
|
||||||
app.logErrAndQuit("Failed to init plugins:", err.Error())
|
app.logErrAndQuit("Failed to init plugins", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,13 +121,13 @@ func main() {
|
||||||
}
|
}
|
||||||
listener, err := net.Listen("tcp", pprofServer.Addr)
|
listener, err := net.Listen("tcp", pprofServer.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to start pprof server:", err.Error())
|
app.fatal("Failed to start pprof server", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("Pprof server listening on", listener.Addr().String())
|
app.info("Pprof server listening", "addr", listener.Addr().String())
|
||||||
// Start server
|
// Start server
|
||||||
if err := pprofServer.Serve(listener); err != nil {
|
if err := pprofServer.Serve(listener); err != nil {
|
||||||
log.Fatalln("Failed to start pprof server:", err.Error())
|
app.fatal("Failed to start pprof server", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -139,11 +141,11 @@ func main() {
|
||||||
app.initMarkdown()
|
app.initMarkdown()
|
||||||
err = app.initTemplateStrings()
|
err = app.initTemplateStrings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.logErrAndQuit("Failed to start check:", err.Error())
|
app.logErrAndQuit("Failed to start check", "err", err)
|
||||||
}
|
}
|
||||||
err = app.checkAllExternalLinks()
|
err = app.checkAllExternalLinks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.logErrAndQuit("Failed to start check:", err.Error())
|
app.logErrAndQuit("Failed to start check", "err", err)
|
||||||
}
|
}
|
||||||
app.shutdown.ShutdownAndWait()
|
app.shutdown.ShutdownAndWait()
|
||||||
return
|
return
|
||||||
|
@ -157,7 +159,7 @@ func main() {
|
||||||
}
|
}
|
||||||
err = app.exportMarkdownFiles(dir)
|
err = app.exportMarkdownFiles(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.logErrAndQuit("Failed to export markdown files:", err.Error())
|
app.logErrAndQuit("Failed to export markdown files", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.shutdown.ShutdownAndWait()
|
app.shutdown.ShutdownAndWait()
|
||||||
|
@ -173,7 +175,7 @@ func main() {
|
||||||
// Start the server
|
// Start the server
|
||||||
err = app.startServer()
|
err = app.startServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.logErrAndQuit("Failed to start server(s):", err.Error())
|
app.logErrAndQuit("Failed to start server(s)", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,31 +186,31 @@ func main() {
|
||||||
func (app *goBlog) initComponents() {
|
func (app *goBlog) initComponents() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
log.Println("Initialize components...")
|
app.info("Initialize components...")
|
||||||
|
|
||||||
app.initMarkdown()
|
app.initMarkdown()
|
||||||
if err = app.initTemplateAssets(); err != nil { // Needs minify
|
if err = app.initTemplateAssets(); err != nil { // Needs minify
|
||||||
app.logErrAndQuit("Failed to init template assets:", err.Error())
|
app.logErrAndQuit("Failed to init template assets", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = app.initTemplateStrings(); err != nil {
|
if err = app.initTemplateStrings(); err != nil {
|
||||||
app.logErrAndQuit("Failed to init template translations:", err.Error())
|
app.logErrAndQuit("Failed to init template translations", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = app.initCache(); err != nil {
|
if err = app.initCache(); err != nil {
|
||||||
app.logErrAndQuit("Failed to init HTTP cache:", err.Error())
|
app.logErrAndQuit("Failed to init HTTP cache", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = app.initRegexRedirects(); err != nil {
|
if err = app.initRegexRedirects(); err != nil {
|
||||||
app.logErrAndQuit("Failed to init redirects:", err.Error())
|
app.logErrAndQuit("Failed to init redirects", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = app.initHTTPLog(); err != nil {
|
if err = app.initHTTPLog(); err != nil {
|
||||||
app.logErrAndQuit("Failed to init HTTP logging:", err.Error())
|
app.logErrAndQuit("Failed to init HTTP logging", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = app.initActivityPub(); err != nil {
|
if err = app.initActivityPub(); err != nil {
|
||||||
app.logErrAndQuit("Failed to init ActivityPub:", err.Error())
|
app.logErrAndQuit("Failed to init ActivityPub", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.initWebmention()
|
app.initWebmention()
|
||||||
|
@ -221,11 +223,11 @@ func (app *goBlog) initComponents() {
|
||||||
app.initPostsDeleter()
|
app.initPostsDeleter()
|
||||||
app.initIndexNow()
|
app.initIndexNow()
|
||||||
|
|
||||||
log.Println("Initialized components")
|
app.info("Initialized components")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) logErrAndQuit(v ...any) {
|
func (a *goBlog) logErrAndQuit(msg string, args ...any) {
|
||||||
log.Println(v...)
|
a.error(msg, args...)
|
||||||
a.shutdown.ShutdownAndWait()
|
a.shutdown.ShutdownAndWait()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/carlmjohnson/requests"
|
"github.com/carlmjohnson/requests"
|
||||||
|
@ -42,17 +41,18 @@ func (a *goBlog) initMediaCompressors() {
|
||||||
}
|
}
|
||||||
config := a.cfg.Micropub.MediaStorage
|
config := a.cfg.Micropub.MediaStorage
|
||||||
if key := config.TinifyKey; key != "" {
|
if key := config.TinifyKey; key != "" {
|
||||||
a.compressors = append(a.compressors, &tinify{key})
|
a.compressors = append(a.compressors, &tinify{a: a, key: key})
|
||||||
}
|
}
|
||||||
if config.CloudflareCompressionEnabled {
|
if config.CloudflareCompressionEnabled {
|
||||||
a.compressors = append(a.compressors, &cloudflare{})
|
a.compressors = append(a.compressors, &cloudflare{})
|
||||||
}
|
}
|
||||||
if config.LocalCompressionEnabled {
|
if config.LocalCompressionEnabled {
|
||||||
a.compressors = append(a.compressors, &localMediaCompressor{})
|
a.compressors = append(a.compressors, &localMediaCompressor{a: a})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type tinify struct {
|
type tinify struct {
|
||||||
|
a *goBlog
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,12 +78,12 @@ func (tf *tinify) compress(url string, upload mediaStorageSaveFunc, hc *http.Cli
|
||||||
ToHeaders(headers).
|
ToHeaders(headers).
|
||||||
Fetch(context.Background())
|
Fetch(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Tinify error:", err.Error())
|
tf.a.error("Tinify error", "err", err)
|
||||||
return "", tinifyErr
|
return "", tinifyErr
|
||||||
}
|
}
|
||||||
compressedLocation := headers.Get("Location")
|
compressedLocation := headers.Get("Location")
|
||||||
if compressedLocation == "" {
|
if compressedLocation == "" {
|
||||||
log.Println("Tinify error: location header missing")
|
tf.a.error("Tinify error: location header missing")
|
||||||
return "", tinifyErr
|
return "", tinifyErr
|
||||||
}
|
}
|
||||||
// Resize and download image
|
// Resize and download image
|
||||||
|
@ -134,9 +134,11 @@ func (*cloudflare) compress(url string, upload mediaStorageSaveFunc, hc *http.Cl
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type localMediaCompressor struct{}
|
type localMediaCompressor struct {
|
||||||
|
a *goBlog
|
||||||
|
}
|
||||||
|
|
||||||
func (*localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
|
func (lc *localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
|
||||||
// Check url
|
// Check url
|
||||||
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
||||||
if !allowed {
|
if !allowed {
|
||||||
|
@ -150,7 +152,7 @@ func (*localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, h
|
||||||
img, err := imaging.Decode(pr, imaging.AutoOrientation(true))
|
img, err := imaging.Decode(pr, imaging.AutoOrientation(true))
|
||||||
_ = pr.CloseWithError(err)
|
_ = pr.CloseWithError(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Local compressor error:", err.Error())
|
lc.a.error("Local compressor error", "err", err)
|
||||||
return "", errors.New("failed to compress image using local compressor")
|
return "", errors.New("failed to compress image using local compressor")
|
||||||
}
|
}
|
||||||
// Resize image
|
// Resize image
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -29,7 +28,7 @@ func (a *goBlog) sendNotification(text string) {
|
||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
if err := a.db.saveNotification(n); err != nil {
|
if err := a.db.saveNotification(n); err != nil {
|
||||||
log.Println("Failed to save notification:", err.Error())
|
a.error("Failed to save notification", "err", err)
|
||||||
}
|
}
|
||||||
if cfg := a.cfg.Notifications; cfg != nil {
|
if cfg := a.cfg.Notifications; cfg != nil {
|
||||||
p := pool.New().WithErrors()
|
p := pool.New().WithErrors()
|
||||||
|
@ -45,7 +44,7 @@ func (a *goBlog) sendNotification(text string) {
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err := p.Wait(); err != nil {
|
if err := p.Wait(); err != nil {
|
||||||
log.Println("Failed to send notification:", err.Error())
|
a.error("Failed to send notification", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/araddon/dateparse"
|
"github.com/araddon/dateparse"
|
||||||
|
@ -13,21 +12,24 @@ func (a *goBlog) initPostsDeleter() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletedPostParam = "deleted"
|
||||||
|
|
||||||
func (a *goBlog) checkDeletedPosts() {
|
func (a *goBlog) checkDeletedPosts() {
|
||||||
// Get all posts with `deleted` parameter and a deleted status
|
// Get all posts with `deleted` parameter and a deleted status
|
||||||
postsToDelete, err := a.getPosts(&postsRequestConfig{
|
postsToDelete, err := a.getPosts(&postsRequestConfig{
|
||||||
status: []postStatus{statusPublishedDeleted, statusDraftDeleted, statusScheduledDeleted},
|
status: []postStatus{statusPublishedDeleted, statusDraftDeleted, statusScheduledDeleted},
|
||||||
parameter: "deleted",
|
parameter: deletedPostParam,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error getting deleted posts:", err)
|
a.error("Error getting deleted posts", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, post := range postsToDelete {
|
for _, post := range postsToDelete {
|
||||||
// Check if post is deleted for more than 7 days
|
// Check if post is deleted for more than 7 days
|
||||||
if deleted, err := dateparse.ParseLocal(post.firstParameter("deleted")); err == nil && deleted.Add(time.Hour*24*7).Before(time.Now()) {
|
if deleted, err := dateparse.ParseLocal(post.firstParameter(deletedPostParam)); err == nil &&
|
||||||
|
deleted.Add(time.Hour*24*7).Before(time.Now()) {
|
||||||
if err := a.deletePost(post.Path); err != nil {
|
if err := a.deletePost(post.Path); err != nil {
|
||||||
log.Println("Error deleting post:", err)
|
a.error("Error deleting post", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ func (a *goBlog) startPostsScheduler() {
|
||||||
a.shutdown.Add(func() {
|
a.shutdown.Add(func() {
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
log.Println("Posts scheduler stopped")
|
a.info("Posts scheduler stopped")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,16 +30,16 @@ func (a *goBlog) checkScheduledPosts() {
|
||||||
publishedBefore: time.Now(),
|
publishedBefore: time.Now(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error getting scheduled posts:", err)
|
a.error("Error getting scheduled posts", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, post := range postsToPublish {
|
for _, post := range postsToPublish {
|
||||||
post.Status = statusPublished
|
post.Status = statusPublished
|
||||||
err := a.replacePost(post, post.Path, statusScheduled, post.Visibility)
|
err := a.replacePost(post, post.Path, statusScheduled, post.Visibility)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error publishing scheduled post:", err)
|
a.error("Error publishing scheduled post", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Println("Published scheduled post:", post.Path)
|
a.info("Published scheduled post", "path", post.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
queue.go
9
queue.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -101,7 +100,7 @@ func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process que
|
||||||
}
|
}
|
||||||
qi, err := a.peekQueue(queueContext, queueName)
|
qi, err := a.peekQueue(queueContext, queueName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("queue peek error:", err.Error())
|
a.error("queue peek error", "err", err)
|
||||||
continue queueLoop
|
continue queueLoop
|
||||||
}
|
}
|
||||||
if qi == nil {
|
if qi == nil {
|
||||||
|
@ -117,17 +116,17 @@ func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process que
|
||||||
qi,
|
qi,
|
||||||
func() {
|
func() {
|
||||||
if err := a.dequeue(qi); err != nil {
|
if err := a.dequeue(qi); err != nil {
|
||||||
log.Println("queue dequeue error:", err.Error())
|
a.error("queue dequeue error", "err", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
func(dur time.Duration) {
|
func(dur time.Duration) {
|
||||||
if err := a.reschedule(qi, dur); err != nil {
|
if err := a.reschedule(qi, dur); err != nil {
|
||||||
log.Println("queue reschedule error:", err.Error())
|
a.error("queue reschedule error", "err", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
log.Println("stopped queue:", queueName)
|
a.info("stopped queue", "name", queueName)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -27,7 +26,7 @@ func (a *goBlog) initSessions() {
|
||||||
"delete from sessions where expires < @now",
|
"delete from sessions where expires < @now",
|
||||||
sql.Named("now", utcNowString()),
|
sql.Named("now", utcNowString()),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Println("Failed to delete expired sessions:", err.Error())
|
a.error("Failed to delete expired sessions", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleteExpiredSessions()
|
deleteExpiredSessions()
|
||||||
|
|
85
telegram.go
85
telegram.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -11,10 +10,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) initTelegram() {
|
func (a *goBlog) initTelegram() {
|
||||||
a.pPostHooks = append(a.pPostHooks, a.tgPost(false))
|
a.pPostHooks = append(a.pPostHooks, func(p *post) { a.tgPost(p, false) })
|
||||||
a.pUpdateHooks = append(a.pUpdateHooks, a.tgUpdate)
|
a.pUpdateHooks = append(a.pUpdateHooks, a.tgUpdate)
|
||||||
a.pDeleteHooks = append(a.pDeleteHooks, a.tgDelete)
|
a.pDeleteHooks = append(a.pDeleteHooks, a.tgDelete)
|
||||||
a.pUndeleteHooks = append(a.pUndeleteHooks, a.tgPost(true))
|
a.pUndeleteHooks = append(a.pUndeleteHooks, func(p *post) { a.tgPost(p, true) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tg *configTelegram) enabled() bool {
|
func (tg *configTelegram) enabled() bool {
|
||||||
|
@ -24,39 +23,37 @@ func (tg *configTelegram) enabled() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) tgPost(silent bool) func(*post) {
|
func (a *goBlog) tgPost(p *post, silent bool) {
|
||||||
return func(p *post) {
|
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() {
|
||||||
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() {
|
tgChat := p.firstParameter("telegramchat")
|
||||||
tgChat := p.firstParameter("telegramchat")
|
tgMsg := p.firstParameter("telegrammsg")
|
||||||
tgMsg := p.firstParameter("telegrammsg")
|
if tgChat != "" && tgMsg != "" {
|
||||||
if tgChat != "" && tgMsg != "" {
|
// Already posted
|
||||||
// Already posted
|
return
|
||||||
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 == "" {
|
return
|
||||||
return
|
}
|
||||||
}
|
// Send message
|
||||||
// Send message
|
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent)
|
||||||
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent)
|
if err != nil {
|
||||||
if err != nil {
|
a.error("Failed to send post to Telegram", "err", err)
|
||||||
log.Printf("Failed to send post to Telegram: %v", err)
|
return
|
||||||
return
|
}
|
||||||
}
|
if chatId == 0 || msgId == 0 {
|
||||||
if chatId == 0 || msgId == 0 {
|
// Not sent
|
||||||
// Not sent
|
return
|
||||||
return
|
}
|
||||||
}
|
// Save chat and message id to post
|
||||||
// Save chat and message id to post
|
err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)})
|
||||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)})
|
if err != nil {
|
||||||
if err != nil {
|
a.error("Failed to save Telegram chat id", "err", err)
|
||||||
log.Printf("Failed to save Telegram chat id: %v", err)
|
}
|
||||||
}
|
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)})
|
||||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)})
|
if err != nil {
|
||||||
if err != nil {
|
a.error("Failed to save Telegram message id", "err", err)
|
||||||
log.Printf("Failed to save Telegram message id: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,13 +69,13 @@ func (a *goBlog) tgUpdate(p *post) {
|
||||||
// Parse tgChat to int64
|
// Parse tgChat to int64
|
||||||
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to parse Telegram chat ID: %v", err)
|
a.error("Failed to parse Telegram chat ID", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Parse tgMsg to int
|
// Parse tgMsg to int
|
||||||
messageId, err := strconv.Atoi(tgMsg)
|
messageId, err := strconv.Atoi(tgMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to parse Telegram message ID: %v", err)
|
a.error("Failed to parse Telegram message ID", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Generate HTML
|
// Generate HTML
|
||||||
|
@ -89,7 +86,7 @@ func (a *goBlog) tgUpdate(p *post) {
|
||||||
// Send update
|
// Send update
|
||||||
err = a.updateTelegram(tg, chatId, messageId, html, "HTML")
|
err = a.updateTelegram(tg, chatId, messageId, html, "HTML")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to send update to Telegram: %v", err)
|
a.error("Failed to send update to Telegram", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,28 +102,28 @@ func (a *goBlog) tgDelete(p *post) {
|
||||||
// Parse tgChat to int64
|
// Parse tgChat to int64
|
||||||
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to parse Telegram chat ID: %v", err)
|
a.error("Failed to parse Telegram chat ID", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Parse tgMsg to int
|
// Parse tgMsg to int
|
||||||
messageId, err := strconv.Atoi(tgMsg)
|
messageId, err := strconv.Atoi(tgMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to parse Telegram message ID: %v", err)
|
a.error("Failed to parse Telegram message ID", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Delete message
|
// Delete message
|
||||||
err = a.deleteTelegram(tg, chatId, messageId)
|
err = a.deleteTelegram(tg, chatId, messageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to delete Telegram message: %v", err)
|
a.error("Failed to delete Telegram message", "err", err)
|
||||||
}
|
}
|
||||||
// Delete chat and message id from post
|
// Delete chat and message id from post
|
||||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{})
|
err = a.db.replacePostParam(p.Path, "telegramchat", []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to remove Telegram chat id: %v", err)
|
a.error("Failed to remove Telegram chat id", "err", err)
|
||||||
}
|
}
|
||||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{})
|
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to remove Telegram message id: %v", err)
|
a.error("Failed to remove Telegram message id", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
tor.go
7
tor.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -34,7 +33,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Start tor
|
// Start tor
|
||||||
log.Println("Starting and registering onion service, please wait a couple of minutes...")
|
a.info("Starting and registering onion service")
|
||||||
t, err := tor.Start(context.Background(), &tor.StartConf{
|
t, err := tor.Start(context.Background(), &tor.StartConf{
|
||||||
TempDataDirBase: os.TempDir(),
|
TempDataDirBase: os.TempDir(),
|
||||||
NoAutoSocksPort: true,
|
NoAutoSocksPort: true,
|
||||||
|
@ -64,7 +63,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
||||||
a.torAddress = "http://" + onion.String()
|
a.torAddress = "http://" + onion.String()
|
||||||
torUrl, _ := url.Parse(a.torAddress)
|
torUrl, _ := url.Parse(a.torAddress)
|
||||||
a.torHostname = torUrl.Hostname()
|
a.torHostname = torUrl.Hostname()
|
||||||
log.Println("Onion service published on " + a.torAddress)
|
a.info("Onion service published", "address", a.torAddress)
|
||||||
// Clear cache
|
// Clear cache
|
||||||
a.cache.purge()
|
a.cache.purge()
|
||||||
// Serve handler
|
// Serve handler
|
||||||
|
@ -74,7 +73,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
||||||
ReadTimeout: 5 * time.Minute,
|
ReadTimeout: 5 * time.Minute,
|
||||||
WriteTimeout: 5 * time.Minute,
|
WriteTimeout: 5 * time.Minute,
|
||||||
}
|
}
|
||||||
a.shutdown.Add(shutdownServer(s, "tor"))
|
a.shutdown.Add(a.shutdownServer(s, "tor"))
|
||||||
if err = s.Serve(onion); err != nil && err != http.ErrServerClosed {
|
if err = s.Serve(onion); err != nil && err != http.ErrServerClosed {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
11
tts.go
11
tts.go
|
@ -8,7 +8,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -38,7 +37,7 @@ func (a *goBlog) initTTS() {
|
||||||
// Create TTS audio
|
// Create TTS audio
|
||||||
err := a.createPostTTSAudio(p)
|
err := a.createPostTTSAudio(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create post audio for %s failed: %v", p.Path, err)
|
a.error("create post audio failed", "path", p.Path, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.pPostHooks = append(a.pPostHooks, createOrUpdate)
|
a.pPostHooks = append(a.pPostHooks, createOrUpdate)
|
||||||
|
@ -47,7 +46,7 @@ func (a *goBlog) initTTS() {
|
||||||
a.pDeleteHooks = append(a.pDeleteHooks, func(p *post) {
|
a.pDeleteHooks = append(a.pDeleteHooks, func(p *post) {
|
||||||
// Try to delete the audio file
|
// Try to delete the audio file
|
||||||
if a.deletePostTTSAudio(p) {
|
if a.deletePostTTSAudio(p) {
|
||||||
log.Println("deleted tts audio for", p.Path)
|
a.info("deleted tts audio", "path", p.Path)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -131,7 +130,7 @@ func (a *goBlog) createPostTTSAudio(p *post) error {
|
||||||
// Already has tts audio, but with different location
|
// Already has tts audio, but with different location
|
||||||
// Try to delete the old audio file
|
// Try to delete the old audio file
|
||||||
if a.deletePostTTSAudio(p) {
|
if a.deletePostTTSAudio(p) {
|
||||||
log.Println("deleted old tts audio for", p.Path)
|
a.info("deleted old tts audio", "path", p.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +157,7 @@ func (a *goBlog) deletePostTTSAudio(p *post) bool {
|
||||||
fileUrl, err := url.Parse(audio)
|
fileUrl, err := url.Parse(audio)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Failed to parse audio url
|
// Failed to parse audio url
|
||||||
log.Println("failed to parse audio url:", err)
|
a.error("failed to parse audio url", "err", err, "audio", audio)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
fileName := path.Base(fileUrl.Path)
|
fileName := path.Base(fileUrl.Path)
|
||||||
|
@ -169,7 +168,7 @@ func (a *goBlog) deletePostTTSAudio(p *post) bool {
|
||||||
// Try to delete the audio file
|
// Try to delete the audio file
|
||||||
err = a.deleteMediaFile(fileName)
|
err = a.deleteMediaFile(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("failed to delete audio file:", err)
|
a.error("failed to delete audio file", "err", err, "file", fileName)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -52,35 +52,35 @@ func (a *goBlog) initWebmention() {
|
||||||
func (a *goBlog) handleWebmention(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) handleWebmention(w http.ResponseWriter, r *http.Request) {
|
||||||
m, err := a.extractMention(r)
|
m, err := a.extractMention(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.debug("Error extracting webmention:", err.Error())
|
a.debug("Error extracting webmention", "err", err)
|
||||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hasShortPrefix := a.cfg.Server.ShortPublicAddress != "" && strings.HasPrefix(m.Target, a.cfg.Server.ShortPublicAddress)
|
hasShortPrefix := a.cfg.Server.ShortPublicAddress != "" && strings.HasPrefix(m.Target, a.cfg.Server.ShortPublicAddress)
|
||||||
hasLongPrefix := strings.HasPrefix(m.Target, a.cfg.Server.PublicAddress)
|
hasLongPrefix := strings.HasPrefix(m.Target, a.cfg.Server.PublicAddress)
|
||||||
if !hasShortPrefix && !hasLongPrefix {
|
if !hasShortPrefix && !hasLongPrefix {
|
||||||
a.debug("Webmention target not allowed:", m.Target)
|
a.debug("Webmention target not allowed", "target", m.Target)
|
||||||
a.serveError(w, r, "target not allowed", http.StatusBadRequest)
|
a.serveError(w, r, "target not allowed", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if m.Target == m.Source {
|
if m.Target == m.Source {
|
||||||
a.debug("Webmention target and source are the same:", m.Target)
|
a.debug("Webmention target and source are the same", "target", m.Target)
|
||||||
a.serveError(w, r, "target and source are the same", http.StatusBadRequest)
|
a.serveError(w, r, "target and source are the same", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = a.queueMention(m); err != nil {
|
if err = a.queueMention(m); err != nil {
|
||||||
a.debug("Failed to queue webmention", err.Error())
|
a.debug("Failed to queue webmention", "err", err)
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
_, _ = fmt.Fprint(w, "Webmention accepted")
|
_, _ = fmt.Fprint(w, "Webmention accepted")
|
||||||
a.debug("Accepted webmention:", m.Source, m.Target)
|
a.debug("Accepted webmention", "source", m.Source, "target", m.Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
|
func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
|
||||||
if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.WWWForm) {
|
if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.WWWForm) {
|
||||||
a.debug("New webmention request with wrong content type:", ct)
|
a.debug("New webmention request with wrong content type", "ct", ct)
|
||||||
return nil, errors.New("unsupported Content-Type")
|
return nil, errors.New("unsupported Content-Type")
|
||||||
}
|
}
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
|
@ -90,7 +90,7 @@ func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
|
||||||
source := r.Form.Get("source")
|
source := r.Form.Get("source")
|
||||||
target := r.Form.Get("target")
|
target := r.Form.Get("target")
|
||||||
if source == "" || target == "" || !isAbsoluteURL(source) || !isAbsoluteURL(target) {
|
if source == "" || target == "" || !isAbsoluteURL(source) || !isAbsoluteURL(target) {
|
||||||
a.debug("Invalid webmention request, source:", source, "target:", target)
|
a.debug("Invalid webmention request", "source", source, "target", target)
|
||||||
return nil, errors.New("invalid request")
|
return nil, errors.New("invalid request")
|
||||||
}
|
}
|
||||||
return &mention{
|
return &mention{
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -49,7 +48,7 @@ func (a *goBlog) sendWebmentions(p *post) error {
|
||||||
if strings.HasPrefix(link, a.cfg.Server.PublicAddress) {
|
if strings.HasPrefix(link, a.cfg.Server.PublicAddress) {
|
||||||
// Save mention directly
|
// Save mention directly
|
||||||
if err := a.createWebmention(a.fullPostURL(p), link); err != nil {
|
if err := a.createWebmention(a.fullPostURL(p), link); err != nil {
|
||||||
log.Println("Failed to create webmention:", err.Error())
|
a.error("Failed to create webmention", "err", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -64,10 +63,10 @@ func (a *goBlog) sendWebmentions(p *post) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = a.sendWebmention(endpoint, a.fullPostURL(p), link); err != nil {
|
if err = a.sendWebmention(endpoint, a.fullPostURL(p), link); err != nil {
|
||||||
log.Println("Sending webmention to " + link + " failed")
|
a.error("Sending webmention failed", "link", link)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Println("Sent webmention to " + link)
|
a.info("Sent webmention", "link", link)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -21,12 +20,12 @@ func (a *goBlog) initWebmentionQueue() {
|
||||||
a.listenOnQueue("wm", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
|
a.listenOnQueue("wm", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
|
||||||
var m mention
|
var m mention
|
||||||
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&m); err != nil {
|
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&m); err != nil {
|
||||||
log.Println("webmention queue:", err.Error())
|
a.error("webmention queue error", "err", err)
|
||||||
dequeue()
|
dequeue()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := a.verifyMention(&m); err != nil {
|
if err := a.verifyMention(&m); err != nil {
|
||||||
log.Printf("Failed to verify webmention from %s to %s: %s", m.Source, m.Target, err.Error())
|
a.error("Failed to verify webmention", "source", m.Source, "target", m.Target, "err", err)
|
||||||
}
|
}
|
||||||
dequeue()
|
dequeue()
|
||||||
})
|
})
|
||||||
|
@ -59,9 +58,7 @@ func (a *goBlog) verifyMention(m *mention) error {
|
||||||
_ = targetResp.Body.Close()
|
_ = targetResp.Body.Close()
|
||||||
// Check if target has a valid status code
|
// Check if target has a valid status code
|
||||||
if targetResp.StatusCode != http.StatusOK {
|
if targetResp.StatusCode != http.StatusOK {
|
||||||
if a.cfg.Debug {
|
a.debug("Webmention for unknown path", "target", m.Target)
|
||||||
a.debug(fmt.Sprintf("Webmention for unknown path: %s", m.Target))
|
|
||||||
}
|
|
||||||
return a.db.deleteWebmention(m)
|
return a.db.deleteWebmention(m)
|
||||||
}
|
}
|
||||||
// Check if target has a redirect
|
// Check if target has a redirect
|
||||||
|
@ -94,9 +91,7 @@ func (a *goBlog) verifyMention(m *mention) error {
|
||||||
}
|
}
|
||||||
// Check if source has a valid status code
|
// Check if source has a valid status code
|
||||||
if sourceResp.StatusCode != http.StatusOK {
|
if sourceResp.StatusCode != http.StatusOK {
|
||||||
if a.cfg.Debug {
|
a.debug("Delete webmention because source doesn't have valid status code", "source", m.Source)
|
||||||
a.debug(fmt.Sprintf("Delete webmention because source doesn't have valid status code: %s", m.Source))
|
|
||||||
}
|
|
||||||
return a.db.deleteWebmention(m)
|
return a.db.deleteWebmention(m)
|
||||||
}
|
}
|
||||||
// Check if source has a redirect
|
// Check if source has a redirect
|
||||||
|
@ -108,17 +103,13 @@ func (a *goBlog) verifyMention(m *mention) error {
|
||||||
// Parse response body
|
// Parse response body
|
||||||
err = a.verifyReader(m, sourceResp.Body)
|
err = a.verifyReader(m, sourceResp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if a.cfg.Debug {
|
a.debug("Delete webmention because verifying source threw error", "source", m.Source, "err", err)
|
||||||
a.debug(fmt.Sprintf("Delete webmention because verifying %s threw error: %s", m.Source, err.Error()))
|
|
||||||
}
|
|
||||||
return a.db.deleteWebmention(m)
|
return a.db.deleteWebmention(m)
|
||||||
}
|
}
|
||||||
newStatus := webmentionStatusVerified
|
newStatus := webmentionStatusVerified
|
||||||
// Update or insert webmention
|
// Update or insert webmention
|
||||||
if a.db.webmentionExists(m) {
|
if a.db.webmentionExists(m) {
|
||||||
if a.cfg.Debug {
|
a.debug("Update webmention", "source", m.Source, "target", m.Target)
|
||||||
a.debug(fmt.Sprintf("Update webmention: %s => %s", m.Source, m.Target))
|
|
||||||
}
|
|
||||||
// Update webmention
|
// Update webmention
|
||||||
err = a.db.updateWebmention(m, newStatus)
|
err = a.db.updateWebmention(m, newStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue