mirror of https://github.com/jlelse/GoBlog
Basic (experimental) plugin support with two plugin types (exec and middleware)
This commit is contained in:
parent
d813e9579c
commit
2158b156c5
|
@ -12,6 +12,7 @@ ADD leaflet/ /app/leaflet/
|
||||||
ADD hlsjs/ /app/hlsjs/
|
ADD hlsjs/ /app/hlsjs/
|
||||||
ADD dbmigrations/ /app/dbmigrations/
|
ADD dbmigrations/ /app/dbmigrations/
|
||||||
ADD strings/ /app/strings/
|
ADD strings/ /app/strings/
|
||||||
|
ADD plugins/ /app/plugins/
|
||||||
|
|
||||||
FROM buildbase as build
|
FROM buildbase as build
|
||||||
|
|
||||||
|
|
|
@ -291,7 +291,7 @@ func (a *goBlog) apGetRemoteActor(iri string) (*asPerson, int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) apGetAllInboxes(blog string) (inboxes []string, err error) {
|
func (db *database) apGetAllInboxes(blog string) (inboxes []string, err error) {
|
||||||
rows, err := db.query("select distinct inbox from activitypub_followers where blog = @blog", sql.Named("blog", blog))
|
rows, err := db.Query("select distinct inbox from activitypub_followers where blog = @blog", sql.Named("blog", blog))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -307,17 +307,17 @@ func (db *database) apGetAllInboxes(blog string) (inboxes []string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) apAddFollower(blog, follower, inbox string) error {
|
func (db *database) apAddFollower(blog, follower, inbox string) error {
|
||||||
_, err := db.exec("insert or replace into activitypub_followers (blog, follower, inbox) values (@blog, @follower, @inbox)", sql.Named("blog", blog), sql.Named("follower", follower), sql.Named("inbox", inbox))
|
_, err := db.Exec("insert or replace into activitypub_followers (blog, follower, inbox) values (@blog, @follower, @inbox)", sql.Named("blog", blog), sql.Named("follower", follower), sql.Named("inbox", inbox))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) apRemoveFollower(blog, follower string) error {
|
func (db *database) apRemoveFollower(blog, follower string) error {
|
||||||
_, err := db.exec("delete from activitypub_followers where blog = @blog and follower = @follower", sql.Named("blog", blog), sql.Named("follower", follower))
|
_, err := db.Exec("delete from activitypub_followers where blog = @blog and follower = @follower", sql.Named("blog", blog), sql.Named("follower", follower))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) apRemoveInbox(inbox string) error {
|
func (db *database) apRemoveInbox(inbox string) error {
|
||||||
_, err := db.exec("delete from activitypub_followers where inbox = @inbox", sql.Named("inbox", inbox))
|
_, err := db.Exec("delete from activitypub_followers where inbox = @inbox", sql.Named("inbox", inbox))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
app.go
3
app.go
|
@ -14,6 +14,7 @@ import (
|
||||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
"go.goblog.app/app/pkgs/minify"
|
"go.goblog.app/app/pkgs/minify"
|
||||||
|
"go.goblog.app/app/pkgs/plugins"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
"tailscale.com/tsnet"
|
"tailscale.com/tsnet"
|
||||||
|
@ -75,6 +76,8 @@ type goBlog struct {
|
||||||
mediaStorage mediaStorage
|
mediaStorage mediaStorage
|
||||||
// Minify
|
// Minify
|
||||||
min minify.Minifier
|
min minify.Minifier
|
||||||
|
// Plugins
|
||||||
|
pluginHost *plugins.PluginHost
|
||||||
// Reactions
|
// Reactions
|
||||||
reactionsInit sync.Once
|
reactionsInit sync.Once
|
||||||
reactionsCache *ristretto.Cache
|
reactionsCache *ristretto.Cache
|
||||||
|
|
|
@ -154,7 +154,7 @@ func (db *database) getBlogStats(blog string) (data *blogStatsData, err error) {
|
||||||
Months: map[string][]blogStatsRow{},
|
Months: map[string][]blogStatsRow{},
|
||||||
}
|
}
|
||||||
// Query and scan
|
// Query and scan
|
||||||
rows, err := db.query(blogStatsSql, sql.Named("status", statusPublished), sql.Named("blog", blog))
|
rows, err := db.Query(blogStatsSql, sql.Named("status", statusPublished), sql.Named("blog", blog))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
10
comments.go
10
comments.go
|
@ -28,7 +28,7 @@ func (a *goBlog) serveComment(w http.ResponseWriter, r *http.Request) {
|
||||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
row, err := a.db.queryRow("select id, target, name, website, comment from comments where id = @id", sql.Named("id", id))
|
row, err := a.db.QueryRow("select id, target, name, website, comment from comments where id = @id", sql.Named("id", id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -63,7 +63,7 @@ func (a *goBlog) createComment(w http.ResponseWriter, r *http.Request) {
|
||||||
name := defaultIfEmpty(cleanHTMLText(r.FormValue("name")), "Anonymous")
|
name := defaultIfEmpty(cleanHTMLText(r.FormValue("name")), "Anonymous")
|
||||||
website := cleanHTMLText(r.FormValue("website"))
|
website := cleanHTMLText(r.FormValue("website"))
|
||||||
// Insert
|
// Insert
|
||||||
result, err := a.db.exec("insert into comments (target, comment, name, website) values (@target, @comment, @name, @website)", sql.Named("target", target), sql.Named("comment", comment), sql.Named("name", name), sql.Named("website", website))
|
result, err := a.db.Exec("insert into comments (target, comment, name, website) values (@target, @comment, @name, @website)", sql.Named("target", target), sql.Named("comment", comment), sql.Named("name", name), sql.Named("website", website))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -116,7 +116,7 @@ func buildCommentsQuery(config *commentsRequestConfig) (query string, args []any
|
||||||
func (db *database) getComments(config *commentsRequestConfig) ([]*comment, error) {
|
func (db *database) getComments(config *commentsRequestConfig) ([]*comment, error) {
|
||||||
comments := []*comment{}
|
comments := []*comment{}
|
||||||
query, args := buildCommentsQuery(config)
|
query, args := buildCommentsQuery(config)
|
||||||
rows, err := db.query(query, args...)
|
rows, err := db.Query(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func (db *database) getComments(config *commentsRequestConfig) ([]*comment, erro
|
||||||
func (db *database) countComments(config *commentsRequestConfig) (count int, err error) {
|
func (db *database) countComments(config *commentsRequestConfig) (count int, err error) {
|
||||||
query, params := buildCommentsQuery(config)
|
query, params := buildCommentsQuery(config)
|
||||||
query = "select count(*) from (" + query + ")"
|
query = "select count(*) from (" + query + ")"
|
||||||
row, err := db.queryRow(query, params...)
|
row, err := db.QueryRow(query, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,6 @@ func (db *database) countComments(config *commentsRequestConfig) (count int, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) deleteComment(id int) error {
|
func (db *database) deleteComment(id int) error {
|
||||||
_, err := db.exec("delete from comments where id = @id", sql.Named("id", id))
|
_, err := db.Exec("delete from comments where id = @id", sql.Named("id", id))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type config struct {
|
||||||
Blogs map[string]*configBlog `mapstructure:"blogs"`
|
Blogs map[string]*configBlog `mapstructure:"blogs"`
|
||||||
User *configUser `mapstructure:"user"`
|
User *configUser `mapstructure:"user"`
|
||||||
Hooks *configHooks `mapstructure:"hooks"`
|
Hooks *configHooks `mapstructure:"hooks"`
|
||||||
|
Plugins []*configPlugin `mapstructure:"plugins"`
|
||||||
Micropub *configMicropub `mapstructure:"micropub"`
|
Micropub *configMicropub `mapstructure:"micropub"`
|
||||||
PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"`
|
PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"`
|
||||||
ActivityPub *configActivityPub `mapstructure:"activityPub"`
|
ActivityPub *configActivityPub `mapstructure:"activityPub"`
|
||||||
|
@ -322,6 +323,13 @@ type configPprof struct {
|
||||||
Address string `mapstructure:"address"`
|
Address string `mapstructure:"address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type configPlugin struct {
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
Type string `mapstructure:"type"`
|
||||||
|
Import string `mapstructure:"import"`
|
||||||
|
Config map[string]any `mapstructure:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
func (a *goBlog) loadConfigFile(file string) error {
|
func (a *goBlog) loadConfigFile(file string) error {
|
||||||
// Use viper to load the config file
|
// Use viper to load the config file
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
|
|
20
database.go
20
database.go
|
@ -211,11 +211,11 @@ func (db *database) prepare(query string, args ...any) (*sql.Stmt, []any, error)
|
||||||
|
|
||||||
const dbNoCache = "nocache"
|
const dbNoCache = "nocache"
|
||||||
|
|
||||||
func (db *database) exec(query string, args ...any) (sql.Result, error) {
|
func (db *database) Exec(query string, args ...any) (sql.Result, error) {
|
||||||
return db.execContext(context.Background(), query, args...)
|
return db.ExecContext(context.Background(), query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) execContext(c context.Context, query string, args ...any) (sql.Result, error) {
|
func (db *database) ExecContext(c context.Context, query string, args ...any) (sql.Result, error) {
|
||||||
if db == nil || db.db == nil {
|
if db == nil || db.db == nil {
|
||||||
return nil, errors.New("database not initialized")
|
return nil, errors.New("database not initialized")
|
||||||
}
|
}
|
||||||
|
@ -234,11 +234,11 @@ func (db *database) execContext(c context.Context, query string, args ...any) (s
|
||||||
return db.db.ExecContext(ctx, query, args...)
|
return db.db.ExecContext(ctx, query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) query(query string, args ...any) (*sql.Rows, error) {
|
func (db *database) Query(query string, args ...any) (*sql.Rows, error) {
|
||||||
return db.queryContext(context.Background(), query, args...)
|
return db.QueryContext(context.Background(), query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) queryContext(c context.Context, query string, args ...any) (rows *sql.Rows, err error) {
|
func (db *database) QueryContext(c context.Context, query string, args ...any) (rows *sql.Rows, err error) {
|
||||||
if db == nil || db.db == nil {
|
if db == nil || db.db == nil {
|
||||||
return nil, errors.New("database not initialized")
|
return nil, errors.New("database not initialized")
|
||||||
}
|
}
|
||||||
|
@ -257,11 +257,11 @@ func (db *database) queryContext(c context.Context, query string, args ...any) (
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) queryRow(query string, args ...any) (*sql.Row, error) {
|
func (db *database) QueryRow(query string, args ...any) (*sql.Row, error) {
|
||||||
return db.queryRowContext(context.Background(), query, args...)
|
return db.QueryRowContext(context.Background(), query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) queryRowContext(c context.Context, query string, args ...any) (row *sql.Row, err error) {
|
func (db *database) QueryRowContext(c context.Context, query string, args ...any) (row *sql.Row, err error) {
|
||||||
if db == nil || db.db == nil {
|
if db == nil || db.db == nil {
|
||||||
return nil, errors.New("database not initialized")
|
return nil, errors.New("database not initialized")
|
||||||
}
|
}
|
||||||
|
@ -283,5 +283,5 @@ func (db *database) queryRowContext(c context.Context, query string, args ...any
|
||||||
// Other things
|
// Other things
|
||||||
|
|
||||||
func (d *database) rebuildFTSIndex() {
|
func (d *database) rebuildFTSIndex() {
|
||||||
_, _ = d.exec("insert into posts_fts(posts_fts) values ('rebuild')")
|
_, _ = d.Exec("insert into posts_fts(posts_fts) values ('rebuild')")
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,17 +15,17 @@ func Test_database(t *testing.T) {
|
||||||
t.Fatalf("Error: %v", err)
|
t.Fatalf("Error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.exec("create table test(test text);")
|
_, err = db.Exec("create table test(test text);")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error: %v", err)
|
t.Fatalf("Error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.exec("insert into test (test) values ('Test')")
|
_, err = db.Exec("insert into test (test) values ('Test')")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error: %v", err)
|
t.Fatalf("Error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
row, err := db.queryRow("select count(test) from test")
|
row, err := db.QueryRow("select count(test) from test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error: %v", err)
|
t.Fatalf("Error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func Test_database(t *testing.T) {
|
||||||
t.Error("Wrong result")
|
t.Error("Wrong result")
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := db.query("select count(test), test from test")
|
rows, err := db.Query("select count(test), test from test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error: %v", err)
|
t.Fatalf("Error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -53,6 +53,7 @@ require (
|
||||||
// master
|
// master
|
||||||
github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec
|
github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||||
|
github.com/traefik/yaegi v0.14.1
|
||||||
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
|
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
|
||||||
github.com/yuin/goldmark v1.4.13
|
github.com/yuin/goldmark v1.4.13
|
||||||
// master
|
// master
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -481,6 +481,8 @@ github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec h1:o5aL1yX+/xzvK4
|
||||||
github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec/go.mod h1:795sjVRFo5wWyN6oOZp0RYienGGBJjpAlgOz2nCngA0=
|
github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec/go.mod h1:795sjVRFo5wWyN6oOZp0RYienGGBJjpAlgOz2nCngA0=
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
|
||||||
|
github.com/traefik/yaegi v0.14.1 h1:t0ssyzeZCWTFGd/JnVuDxH/slMQfYg+2CDD4dLW/rU0=
|
||||||
|
github.com/traefik/yaegi v0.14.1/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0=
|
||||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4=
|
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4=
|
||||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||||
|
|
14
http.go
14
http.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"github.com/klauspost/compress/flate"
|
"github.com/klauspost/compress/flate"
|
||||||
"go.goblog.app/app/pkgs/httpcompress"
|
"go.goblog.app/app/pkgs/httpcompress"
|
||||||
"go.goblog.app/app/pkgs/maprouter"
|
"go.goblog.app/app/pkgs/maprouter"
|
||||||
|
"go.goblog.app/app/pkgs/plugintypes"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +45,16 @@ func (a *goBlog) startServer() (err error) {
|
||||||
if a.httpsConfigured(false) {
|
if a.httpsConfigured(false) {
|
||||||
h = h.Append(a.securityHeaders)
|
h = h.Append(a.securityHeaders)
|
||||||
}
|
}
|
||||||
|
// Add plugin middlewares
|
||||||
|
middlewarePlugins := getPluginsForType[plugintypes.Middleware](a, "middleware")
|
||||||
|
sort.Slice(middlewarePlugins, func(i, j int) bool {
|
||||||
|
// Sort with descending prio
|
||||||
|
return middlewarePlugins[i].Prio() > middlewarePlugins[j].Prio()
|
||||||
|
})
|
||||||
|
for _, plugin := range middlewarePlugins {
|
||||||
|
h = h.Append(plugin.Handler)
|
||||||
|
}
|
||||||
|
// Finally...
|
||||||
finalHandler := h.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
finalHandler := h.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
a.d.ServeHTTP(w, r)
|
a.d.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
@ -245,7 +257,7 @@ func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
// Check if post or alias
|
// Check if post or alias
|
||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
row, err := a.db.queryRow(`
|
row, err := a.db.QueryRow(`
|
||||||
-- normal posts
|
-- normal posts
|
||||||
select 'post', status, 200 from posts where path = @path
|
select 'post', status, 200 from posts where path = @path
|
||||||
union all
|
union all
|
||||||
|
|
|
@ -182,7 +182,7 @@ func (db *database) indieAuthSaveAuthRequest(data *indieauth.AuthenticationReque
|
||||||
// Generate a code to identify the request
|
// Generate a code to identify the request
|
||||||
code := uuid.NewString()
|
code := uuid.NewString()
|
||||||
// Save the request
|
// Save the request
|
||||||
_, err := db.exec(
|
_, err := db.Exec(
|
||||||
"insert into indieauthauth (time, code, client, redirect, scope, challenge, challengemethod) values (?, ?, ?, ?, ?, ?, ?)",
|
"insert into indieauthauth (time, code, client, redirect, scope, challenge, challengemethod) values (?, ?, ?, ?, ?, ?, ?)",
|
||||||
time.Now().UTC().Unix(), code, data.ClientID, data.RedirectURI, strings.Join(data.Scopes, " "), data.CodeChallenge, data.CodeChallengeMethod,
|
time.Now().UTC().Unix(), code, data.ClientID, data.RedirectURI, strings.Join(data.Scopes, " "), data.CodeChallenge, data.CodeChallengeMethod,
|
||||||
)
|
)
|
||||||
|
@ -194,7 +194,7 @@ func (db *database) indieAuthGetAuthRequest(code string) (data *indieauth.Authen
|
||||||
// code valid for 10 minutes
|
// code valid for 10 minutes
|
||||||
maxAge := time.Now().UTC().Add(-10 * time.Minute).Unix()
|
maxAge := time.Now().UTC().Add(-10 * time.Minute).Unix()
|
||||||
// Query the database
|
// Query the database
|
||||||
row, err := db.queryRow("select client, redirect, scope, challenge, challengemethod from indieauthauth where time >= ? and code = ?", maxAge, code)
|
row, err := db.QueryRow("select client, redirect, scope, challenge, challengemethod from indieauthauth where time >= ? and code = ?", maxAge, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ func (db *database) indieAuthGetAuthRequest(code string) (data *indieauth.Authen
|
||||||
data.Scopes = strings.Split(scope, " ")
|
data.Scopes = strings.Split(scope, " ")
|
||||||
}
|
}
|
||||||
// Delete the auth code and expired auth codes
|
// Delete the auth code and expired auth codes
|
||||||
_, _ = db.exec("delete from indieauthauth where code = ? or time < ?", code, maxAge)
|
_, _ = db.Exec("delete from indieauthauth where code = ? or time < ?", code, maxAge)
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ func (a *goBlog) indieAuthTokenVerification(w http.ResponseWriter, r *http.Reque
|
||||||
func (db *database) indieAuthVerifyToken(token string) (data *indieauth.AuthenticationRequest, err error) {
|
func (db *database) indieAuthVerifyToken(token string) (data *indieauth.AuthenticationRequest, err error) {
|
||||||
token = strings.ReplaceAll(token, "Bearer ", "")
|
token = strings.ReplaceAll(token, "Bearer ", "")
|
||||||
data = &indieauth.AuthenticationRequest{Scopes: []string{}}
|
data = &indieauth.AuthenticationRequest{Scopes: []string{}}
|
||||||
row, err := db.queryRow("select client, scope from indieauthtoken where token = @token", sql.Named("token", token))
|
row, err := db.QueryRow("select client, scope from indieauthtoken where token = @token", sql.Named("token", token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -271,13 +271,13 @@ func (db *database) indieAuthVerifyToken(token string) (data *indieauth.Authenti
|
||||||
// Save a new token to the database
|
// Save a new token to the database
|
||||||
func (db *database) indieAuthSaveToken(data *indieauth.AuthenticationRequest) (string, error) {
|
func (db *database) indieAuthSaveToken(data *indieauth.AuthenticationRequest) (string, error) {
|
||||||
token := uuid.NewString()
|
token := uuid.NewString()
|
||||||
_, err := db.exec("insert into indieauthtoken (time, token, client, scope) values (?, ?, ?, ?)", time.Now().UTC().Unix(), token, data.ClientID, strings.Join(data.Scopes, " "))
|
_, err := db.Exec("insert into indieauthtoken (time, token, client, scope) values (?, ?, ?, ?)", time.Now().UTC().Unix(), token, data.ClientID, strings.Join(data.Scopes, " "))
|
||||||
return token, err
|
return token, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revoke and delete the token from the database
|
// Revoke and delete the token from the database
|
||||||
func (db *database) indieAuthRevokeToken(token string) {
|
func (db *database) indieAuthRevokeToken(token string) {
|
||||||
if token != "" {
|
if token != "" {
|
||||||
_, _ = db.exec("delete from indieauthtoken where token=?", token)
|
_, _ = db.Exec("delete from indieauthtoken where token=?", token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
main.go
6
main.go
|
@ -67,6 +67,12 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize plugins
|
||||||
|
if err = app.initPlugins(); err != nil {
|
||||||
|
app.logErrAndQuit("Failed to init plugins:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Healthcheck tool
|
// Healthcheck tool
|
||||||
if len(os.Args) >= 2 && os.Args[1] == "healthcheck" {
|
if len(os.Args) >= 2 && os.Args[1] == "healthcheck" {
|
||||||
// Connect to public address + "/ping" and exit with 0 when successful
|
// Connect to public address + "/ping" and exit with 0 when successful
|
||||||
|
|
|
@ -41,19 +41,19 @@ func (a *goBlog) sendNotification(text string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) saveNotification(n *notification) error {
|
func (db *database) saveNotification(n *notification) error {
|
||||||
if _, err := db.exec("insert into notifications (time, text) values (@time, @text)", sql.Named("time", n.Time), sql.Named("text", n.Text)); err != nil {
|
if _, err := db.Exec("insert into notifications (time, text) values (@time, @text)", sql.Named("time", n.Time), sql.Named("text", n.Text)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) deleteNotification(id int) error {
|
func (db *database) deleteNotification(id int) error {
|
||||||
_, err := db.exec("delete from notifications where id = @id", sql.Named("id", id))
|
_, err := db.Exec("delete from notifications where id = @id", sql.Named("id", id))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) deleteAllNotifications() error {
|
func (db *database) deleteAllNotifications() error {
|
||||||
_, err := db.exec("delete from notifications")
|
_, err := db.Exec("delete from notifications")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ func buildNotificationsQuery(config *notificationsRequestConfig) (query string,
|
||||||
func (db *database) getNotifications(config *notificationsRequestConfig) ([]*notification, error) {
|
func (db *database) getNotifications(config *notificationsRequestConfig) ([]*notification, error) {
|
||||||
notifications := []*notification{}
|
notifications := []*notification{}
|
||||||
query, args := buildNotificationsQuery(config)
|
query, args := buildNotificationsQuery(config)
|
||||||
rows, err := db.query(query, args...)
|
rows, err := db.Query(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ func (db *database) getNotifications(config *notificationsRequestConfig) ([]*not
|
||||||
func (db *database) countNotifications(config *notificationsRequestConfig) (count int, err error) {
|
func (db *database) countNotifications(config *notificationsRequestConfig) (count int, err error) {
|
||||||
query, params := buildNotificationsQuery(config)
|
query, params := buildNotificationsQuery(config)
|
||||||
query = "select count(*) from (" + query + ")"
|
query = "select count(*) from (" + query + ")"
|
||||||
row, err := db.queryRow(query, params...)
|
row, err := db.QueryRow(query, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ func (db *database) cachePersistentlyContext(ctx context.Context, key string, da
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return errors.New("database is nil")
|
return errors.New("database is nil")
|
||||||
}
|
}
|
||||||
_, err := db.execContext(ctx, "insert or replace into persistent_cache(key, data, date) values(@key, @data, @date)", sql.Named("key", key), sql.Named("data", data), sql.Named("date", utcNowString()))
|
_, err := db.ExecContext(ctx, "insert or replace into persistent_cache(key, data, date) values(@key, @data, @date)", sql.Named("key", key), sql.Named("data", data), sql.Named("date", utcNowString()))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func (db *database) retrievePersistentCacheContext(c context.Context, key string
|
||||||
return nil, errors.New("database is nil")
|
return nil, errors.New("database is nil")
|
||||||
}
|
}
|
||||||
d, err, _ := db.pc.Do(key, func() (any, error) {
|
d, err, _ := db.pc.Do(key, func() (any, error) {
|
||||||
if row, err := db.queryRowContext(c, "select data from persistent_cache where key = @key", sql.Named("key", key)); err != nil {
|
if row, err := db.QueryRowContext(c, "select data from persistent_cache where key = @key", sql.Named("key", key)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
err = row.Scan(&data)
|
err = row.Scan(&data)
|
||||||
|
@ -51,6 +51,6 @@ func (db *database) clearPersistentCache(pattern string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) clearPersistentCacheContext(c context.Context, pattern string) error {
|
func (db *database) clearPersistentCacheContext(c context.Context, pattern string) error {
|
||||||
_, err := db.execContext(c, "delete from persistent_cache where key like @pattern", sql.Named("pattern", pattern))
|
_, err := db.ExecContext(c, "delete from persistent_cache where key like @pattern", sql.Named("pattern", pattern))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/traefik/yaegi/interp"
|
||||||
|
"github.com/traefik/yaegi/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type plugin struct {
|
||||||
|
Config *PluginConfig
|
||||||
|
plugin reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginConfig is the configuration of the plugin.
|
||||||
|
type PluginConfig struct {
|
||||||
|
// Path is the storage path of the plugin.
|
||||||
|
Path string
|
||||||
|
// ImportPath is the module path i.e. "github.com/user/module".
|
||||||
|
ImportPath string
|
||||||
|
// PluginType is the type of plugin, this plugin is checked against that type.
|
||||||
|
// The available types are specified by the implementor of this package.
|
||||||
|
PluginType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) initPlugin(host *PluginHost) error {
|
||||||
|
const errText = "initPlugin: %w"
|
||||||
|
|
||||||
|
interpreter := interp.New(interp.Options{
|
||||||
|
GoPath: p.Config.Path,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := interpreter.Use(stdlib.Symbols); err != nil {
|
||||||
|
return fmt.Errorf(errText, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := interpreter.Use(host.Symbols); err != nil {
|
||||||
|
return fmt.Errorf(errText, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := interpreter.Eval(fmt.Sprintf(`import "%s"`, p.Config.ImportPath)); err != nil {
|
||||||
|
return fmt.Errorf(errText, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := interpreter.Eval(filepath.Base(p.Config.ImportPath) + ".GetPlugin")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errText, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := v.Call([]reflect.Value{})
|
||||||
|
if len(result) > 1 {
|
||||||
|
return fmt.Errorf(errText+": function GetPlugin has more than one return value", ErrValidatingPlugin)
|
||||||
|
}
|
||||||
|
p.plugin = result[0]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/traefik/yaegi/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPluginHost initializes a PluginHost.
|
||||||
|
func NewPluginHost(symbols interp.Exports) *PluginHost {
|
||||||
|
return &PluginHost{
|
||||||
|
Plugins: []*plugin{},
|
||||||
|
PluginTypes: map[string]reflect.Type{},
|
||||||
|
Symbols: symbols,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPluginType adds a plugin type to the list.
|
||||||
|
// The interface for the pluginType parameter should be a nil of the plugin type interface:
|
||||||
|
//
|
||||||
|
// (*PluginInterface)(nil)
|
||||||
|
func (h *PluginHost) AddPluginType(name string, pluginType interface{}) {
|
||||||
|
h.PluginTypes[name] = reflect.TypeOf(pluginType).Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPlugin loads a new plugin to the host.
|
||||||
|
func (h *PluginHost) LoadPlugin(config *PluginConfig) (any, error) {
|
||||||
|
p := &plugin{
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
err := p.initPlugin(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = h.validatePlugin(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h.Plugins = append(h.Plugins, p)
|
||||||
|
return p.plugin.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PluginHost) validatePlugin(p *plugin) error {
|
||||||
|
pType := reflect.TypeOf(p.plugin.Interface())
|
||||||
|
|
||||||
|
if _, ok := h.PluginTypes[p.Config.PluginType]; !ok {
|
||||||
|
return fmt.Errorf("validatePlugin: %v: %w", p.Config.PluginType, ErrInvalidType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pType.Implements(h.PluginTypes[p.Config.PluginType]) {
|
||||||
|
return fmt.Errorf("validatePlugin:%v: %w %v", p, ErrValidatingPlugin, p.Config.PluginType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlugins returns a list of all plugins.
|
||||||
|
func (h *PluginHost) GetPlugins() (list []any) {
|
||||||
|
for _, p := range h.Plugins {
|
||||||
|
list = append(list, p.plugin.Interface())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPluginsForType returns all the plugins that are of type pluginType or empty if the pluginType doesn't exist.
|
||||||
|
func GetPluginsForType[T any](h *PluginHost, pluginType string) (list []T) {
|
||||||
|
if _, ok := h.PluginTypes[pluginType]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, p := range h.Plugins {
|
||||||
|
if p.Config.PluginType != pluginType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t, ok := p.plugin.Interface().(T); ok {
|
||||||
|
list = append(list, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/traefik/yaegi/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginHost manages the plugins.
|
||||||
|
type PluginHost struct {
|
||||||
|
// Plugins contains a list of the plugins.
|
||||||
|
Plugins []*plugin
|
||||||
|
// PluginTypes is a list of plugins types that plugins have to use at least one of.
|
||||||
|
PluginTypes map[string]reflect.Type
|
||||||
|
// Symbols is the map of symbols generated by yaegi extract.
|
||||||
|
Symbols interp.Exports
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidType is returned when the plugin type specified by the plugin is invalid.
|
||||||
|
ErrInvalidType = errors.New("invalid plugin type")
|
||||||
|
// ErrValidatingPlugin is returned when the plugin fails to fully implement the interface of the plugin type.
|
||||||
|
ErrValidatingPlugin = errors.New("plugin does not implement type")
|
||||||
|
)
|
|
@ -0,0 +1,51 @@
|
||||||
|
package plugintypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface to GoBlog
|
||||||
|
|
||||||
|
// App is used to access GoBlog's app instance.
|
||||||
|
type App interface {
|
||||||
|
GetDatabase() Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database is used to provide access to GoBlog's database.
|
||||||
|
type Database interface {
|
||||||
|
Exec(string, ...any) (sql.Result, error)
|
||||||
|
ExecContext(context.Context, string, ...any) (sql.Result, error)
|
||||||
|
Query(string, ...any) (*sql.Rows, error)
|
||||||
|
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
|
||||||
|
QueryRow(string, ...any) (*sql.Row, error)
|
||||||
|
QueryRowContext(context.Context, string, ...any) (*sql.Row, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin types
|
||||||
|
|
||||||
|
// SetApp is used in all plugin types to allow
|
||||||
|
// GoBlog set it's app instance to be accessible by the plugin.
|
||||||
|
type SetApp interface {
|
||||||
|
SetApp(App)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfig is used in all plugin types to allow
|
||||||
|
// GoBlog set plugin configuration.
|
||||||
|
type SetConfig interface {
|
||||||
|
SetConfig(map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Exec interface {
|
||||||
|
SetApp
|
||||||
|
SetConfig
|
||||||
|
Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Middleware interface {
|
||||||
|
SetApp
|
||||||
|
SetConfig
|
||||||
|
Handler(http.Handler) http.Handler
|
||||||
|
Prio() int
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Code generated by 'yaegi extract go.goblog.app/app/pkgs/plugintypes'. DO NOT EDIT.
|
||||||
|
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 - 2022 Jan-Lukas Else
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
package yaegiwrappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.goblog.app/app/pkgs/plugintypes"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Symbols["go.goblog.app/app/pkgs/plugintypes/plugintypes"] = map[string]reflect.Value{
|
||||||
|
// type definitions
|
||||||
|
"App": reflect.ValueOf((*plugintypes.App)(nil)),
|
||||||
|
"Database": reflect.ValueOf((*plugintypes.Database)(nil)),
|
||||||
|
"Exec": reflect.ValueOf((*plugintypes.Exec)(nil)),
|
||||||
|
"Middleware": reflect.ValueOf((*plugintypes.Middleware)(nil)),
|
||||||
|
"SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)),
|
||||||
|
"SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)),
|
||||||
|
|
||||||
|
// interface wrapper definitions
|
||||||
|
"_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)),
|
||||||
|
"_Database": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Database)(nil)),
|
||||||
|
"_Exec": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Exec)(nil)),
|
||||||
|
"_Middleware": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Middleware)(nil)),
|
||||||
|
"_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)),
|
||||||
|
"_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// _go_goblog_app_app_pkgs_plugintypes_App is an interface wrapper for App type
|
||||||
|
type _go_goblog_app_app_pkgs_plugintypes_App struct {
|
||||||
|
IValue interface{}
|
||||||
|
WGetDatabase func() plugintypes.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Database {
|
||||||
|
return W.WGetDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// _go_goblog_app_app_pkgs_plugintypes_Database is an interface wrapper for Database type
|
||||||
|
type _go_goblog_app_app_pkgs_plugintypes_Database struct {
|
||||||
|
IValue interface{}
|
||||||
|
WExec func(a0 string, a1 ...any) (sql.Result, error)
|
||||||
|
WExecContext func(a0 context.Context, a1 string, a2 ...any) (sql.Result, error)
|
||||||
|
WQuery func(a0 string, a1 ...any) (*sql.Rows, error)
|
||||||
|
WQueryContext func(a0 context.Context, a1 string, a2 ...any) (*sql.Rows, error)
|
||||||
|
WQueryRow func(a0 string, a1 ...any) (*sql.Row, error)
|
||||||
|
WQueryRowContext func(a0 context.Context, a1 string, a2 ...any) (*sql.Row, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Database) Exec(a0 string, a1 ...any) (sql.Result, error) {
|
||||||
|
return W.WExec(a0, a1...)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Database) ExecContext(a0 context.Context, a1 string, a2 ...any) (sql.Result, error) {
|
||||||
|
return W.WExecContext(a0, a1, a2...)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Database) Query(a0 string, a1 ...any) (*sql.Rows, error) {
|
||||||
|
return W.WQuery(a0, a1...)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Database) QueryContext(a0 context.Context, a1 string, a2 ...any) (*sql.Rows, error) {
|
||||||
|
return W.WQueryContext(a0, a1, a2...)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Database) QueryRow(a0 string, a1 ...any) (*sql.Row, error) {
|
||||||
|
return W.WQueryRow(a0, a1...)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Database) QueryRowContext(a0 context.Context, a1 string, a2 ...any) (*sql.Row, error) {
|
||||||
|
return W.WQueryRowContext(a0, a1, a2...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _go_goblog_app_app_pkgs_plugintypes_Exec is an interface wrapper for Exec type
|
||||||
|
type _go_goblog_app_app_pkgs_plugintypes_Exec struct {
|
||||||
|
IValue interface{}
|
||||||
|
WExec func()
|
||||||
|
WSetApp func(a0 plugintypes.App)
|
||||||
|
WSetConfig func(a0 map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Exec) Exec() {
|
||||||
|
W.WExec()
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Exec) SetApp(a0 plugintypes.App) {
|
||||||
|
W.WSetApp(a0)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Exec) SetConfig(a0 map[string]any) {
|
||||||
|
W.WSetConfig(a0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _go_goblog_app_app_pkgs_plugintypes_Middleware is an interface wrapper for Middleware type
|
||||||
|
type _go_goblog_app_app_pkgs_plugintypes_Middleware struct {
|
||||||
|
IValue interface{}
|
||||||
|
WHandler func(a0 http.Handler) http.Handler
|
||||||
|
WPrio func() int
|
||||||
|
WSetApp func(a0 plugintypes.App)
|
||||||
|
WSetConfig func(a0 map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) Handler(a0 http.Handler) http.Handler {
|
||||||
|
return W.WHandler(a0)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) Prio() int {
|
||||||
|
return W.WPrio()
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) SetApp(a0 plugintypes.App) {
|
||||||
|
W.WSetApp(a0)
|
||||||
|
}
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) SetConfig(a0 map[string]any) {
|
||||||
|
W.WSetConfig(a0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _go_goblog_app_app_pkgs_plugintypes_SetApp is an interface wrapper for SetApp type
|
||||||
|
type _go_goblog_app_app_pkgs_plugintypes_SetApp struct {
|
||||||
|
IValue interface{}
|
||||||
|
WSetApp func(a0 plugintypes.App)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_SetApp) SetApp(a0 plugintypes.App) {
|
||||||
|
W.WSetApp(a0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// _go_goblog_app_app_pkgs_plugintypes_SetConfig is an interface wrapper for SetConfig type
|
||||||
|
type _go_goblog_app_app_pkgs_plugintypes_SetConfig struct {
|
||||||
|
IValue interface{}
|
||||||
|
WSetConfig func(a0 map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (W _go_goblog_app_app_pkgs_plugintypes_SetConfig) SetConfig(a0 map[string]any) {
|
||||||
|
W.WSetConfig(a0)
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package yaegiwrappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Symbols = make(map[string]map[string]reflect.Value)
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate yaegi extract -license ../../LICENSE -name yaegiwrappers go.goblog.app/app/pkgs/plugintypes
|
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.goblog.app/app/pkgs/plugins"
|
||||||
|
"go.goblog.app/app/pkgs/plugintypes"
|
||||||
|
"go.goblog.app/app/pkgs/yaegiwrappers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *goBlog) initPlugins() error {
|
||||||
|
a.pluginHost = plugins.NewPluginHost(yaegiwrappers.Symbols)
|
||||||
|
|
||||||
|
a.pluginHost.AddPluginType("exec", (*plugintypes.Exec)(nil))
|
||||||
|
a.pluginHost.AddPluginType("middleware", (*plugintypes.Middleware)(nil))
|
||||||
|
|
||||||
|
for _, pc := range a.cfg.Plugins {
|
||||||
|
if pluginInterface, err := a.pluginHost.LoadPlugin(&plugins.PluginConfig{
|
||||||
|
Path: pc.Path,
|
||||||
|
ImportPath: pc.Import,
|
||||||
|
PluginType: pc.Type,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
} else if pluginInterface != nil {
|
||||||
|
if setAppPlugin, ok := pluginInterface.(plugintypes.SetApp); ok {
|
||||||
|
setAppPlugin.SetApp(a)
|
||||||
|
}
|
||||||
|
if setConfigPlugin, ok := pluginInterface.(plugintypes.SetConfig); ok {
|
||||||
|
setConfigPlugin.SetConfig(pc.Config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execs := getPluginsForType[plugintypes.Exec](a, "exec")
|
||||||
|
for _, p := range execs {
|
||||||
|
go p.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPluginsForType[T any](a *goBlog, pluginType string) (list []T) {
|
||||||
|
return plugins.GetPluginsForType[T](a.pluginHost, pluginType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement all needed interfaces for goblog
|
||||||
|
|
||||||
|
var _ plugintypes.App = &goBlog{}
|
||||||
|
|
||||||
|
func (a *goBlog) GetDatabase() plugintypes.Database {
|
||||||
|
return a.db
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ plugintypes.Database = &database{}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package demoexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.goblog.app/app/pkgs/plugintypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPlugin() plugintypes.Exec {
|
||||||
|
return &plugin{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type plugin struct {
|
||||||
|
app plugintypes.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) SetApp(app plugintypes.App) {
|
||||||
|
p.app = app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*plugin) SetConfig(_ map[string]any) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) Exec() {
|
||||||
|
fmt.Println("Hello World from the demo plugin!")
|
||||||
|
|
||||||
|
row, _ := p.app.GetDatabase().QueryRow("select count (*) from posts")
|
||||||
|
var count int
|
||||||
|
if err := row.Scan(&count); err != nil {
|
||||||
|
fmt.Println(fmt.Errorf("failed to count posts: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Number of posts in database: %d", count)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package demomiddleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.goblog.app/app/pkgs/plugintypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPlugin() plugintypes.Middleware {
|
||||||
|
return &plugin{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type plugin struct {
|
||||||
|
app plugintypes.App
|
||||||
|
config map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) SetApp(app plugintypes.App) {
|
||||||
|
p.app = app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) SetConfig(config map[string]any) {
|
||||||
|
p.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) Prio() int {
|
||||||
|
if prioAny, ok := p.config["prio"]; ok {
|
||||||
|
if prio, ok := prioAny.(int); ok {
|
||||||
|
return prio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) Handler(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("X-Demo", fmt.Sprintf("This is from the demo middleware with prio %d", p.Prio()))
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.goblog.app/app/pkgs/plugintypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExecPlugin(t *testing.T) {
|
||||||
|
app := &goBlog{
|
||||||
|
cfg: createDefaultTestConfig(t),
|
||||||
|
}
|
||||||
|
app.cfg.Plugins = []*configPlugin{
|
||||||
|
{
|
||||||
|
Path: "./plugins/demo",
|
||||||
|
Type: "exec",
|
||||||
|
Import: "demoexec",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.initConfig(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = app.initPlugins()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewarePlugin(t *testing.T) {
|
||||||
|
app := &goBlog{
|
||||||
|
cfg: createDefaultTestConfig(t),
|
||||||
|
}
|
||||||
|
app.cfg.Plugins = []*configPlugin{
|
||||||
|
{
|
||||||
|
Path: "./plugins/demo",
|
||||||
|
Type: "middleware",
|
||||||
|
Import: "demomiddleware",
|
||||||
|
Config: map[string]any{
|
||||||
|
"prio": 99,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.initConfig(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = app.initPlugins()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
middlewarePlugins := getPluginsForType[plugintypes.Middleware](app, "middleware")
|
||||||
|
if assert.Len(t, middlewarePlugins, 1) {
|
||||||
|
mdw := middlewarePlugins[0]
|
||||||
|
assert.Equal(t, 99, mdw.Prio())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
postsDb.go
22
postsDb.go
|
@ -203,7 +203,7 @@ func (db *database) savePost(p *post, o *postCreationOptions) error {
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
sqlBuilder.WriteString("commit;")
|
sqlBuilder.WriteString("commit;")
|
||||||
// Execute
|
// Execute
|
||||||
if _, err := db.exec(sqlBuilder.String(), sqlArgs...); err != nil {
|
if _, err := db.Exec(sqlBuilder.String(), sqlArgs...); err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE constraint failed: posts.path") {
|
if strings.Contains(err.Error(), "UNIQUE constraint failed: posts.path") {
|
||||||
return errors.New("post already exists at given path")
|
return errors.New("post already exists at given path")
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ func (a *goBlog) deletePost(path string) error {
|
||||||
// Post exists, check if it's already marked as deleted
|
// Post exists, check if it's already marked as deleted
|
||||||
if strings.HasSuffix(string(p.Status), statusDeletedSuffix) {
|
if strings.HasSuffix(string(p.Status), statusDeletedSuffix) {
|
||||||
// Post is already marked as deleted, delete it from database
|
// Post is already marked as deleted, delete it from database
|
||||||
if _, err = a.db.exec(
|
if _, err = a.db.Exec(
|
||||||
`begin; delete from posts where path = ?; insert or ignore into deleted (path) values (?); commit;`,
|
`begin; delete from posts where path = ?; insert or ignore into deleted (path) values (?); commit;`,
|
||||||
dbNoCache, p.Path, p.Path, p.Path,
|
dbNoCache, p.Path, p.Path, p.Path,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -250,7 +250,7 @@ func (a *goBlog) deletePost(path string) error {
|
||||||
}
|
}
|
||||||
p.Parameters["deleted"] = []string{deletedTime}
|
p.Parameters["deleted"] = []string{deletedTime}
|
||||||
// Mark post as deleted
|
// Mark post as deleted
|
||||||
if _, err = a.db.exec(
|
if _, err = a.db.Exec(
|
||||||
`begin; update posts set status = ? where path = ?; delete from post_parameters where path = ? and parameter = 'deleted'; insert into post_parameters (path, parameter, value) values (?, 'deleted', ?); commit;`,
|
`begin; update posts set status = ? where path = ?; delete from post_parameters where path = ? and parameter = 'deleted'; insert into post_parameters (path, parameter, value) values (?, 'deleted', ?); commit;`,
|
||||||
dbNoCache, p.Status, p.Path, p.Path, p.Path, deletedTime,
|
dbNoCache, p.Status, p.Path, p.Path, p.Path, deletedTime,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -283,7 +283,7 @@ func (a *goBlog) undeletePost(path string) error {
|
||||||
// Remove parameter
|
// Remove parameter
|
||||||
p.Parameters["deleted"] = nil
|
p.Parameters["deleted"] = nil
|
||||||
// Update database
|
// Update database
|
||||||
if _, err = a.db.exec(
|
if _, err = a.db.Exec(
|
||||||
`begin; update posts set status = ? where path = ?; delete from post_parameters where path = ? and parameter = 'deleted'; commit;`,
|
`begin; update posts set status = ? where path = ?; delete from post_parameters where path = ? and parameter = 'deleted'; commit;`,
|
||||||
dbNoCache, p.Status, p.Path, p.Path,
|
dbNoCache, p.Status, p.Path, p.Path,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -320,7 +320,7 @@ func (db *database) replacePostParam(path, param string, values []string) error
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
sqlBuilder.WriteString("commit;")
|
sqlBuilder.WriteString("commit;")
|
||||||
// Execute
|
// Execute
|
||||||
_, err := db.exec(sqlBuilder.String(), sqlArgs...)
|
_, err := db.Exec(sqlBuilder.String(), sqlArgs...)
|
||||||
bufferpool.Put(sqlBuilder)
|
bufferpool.Put(sqlBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -514,7 +514,7 @@ func (d *database) loadPostParameters(posts []*post, parameters ...string) (err
|
||||||
// Order
|
// Order
|
||||||
queryBuilder.WriteString(" order by id")
|
queryBuilder.WriteString(" order by id")
|
||||||
// Query
|
// Query
|
||||||
rows, err := d.query(queryBuilder.String(), sqlArgs...)
|
rows, err := d.Query(queryBuilder.String(), sqlArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -542,7 +542,7 @@ func (d *database) loadPostParameters(posts []*post, parameters ...string) (err
|
||||||
func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error) {
|
func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error) {
|
||||||
// Query posts
|
// Query posts
|
||||||
query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, priority")
|
query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, priority")
|
||||||
rows, err := a.db.query(query, queryParams...)
|
rows, err := a.db.Query(query, queryParams...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -595,7 +595,7 @@ func (a *goBlog) getPost(path string) (*post, error) {
|
||||||
|
|
||||||
func (d *database) countPosts(config *postsRequestConfig) (count int, err error) {
|
func (d *database) countPosts(config *postsRequestConfig) (count int, err error) {
|
||||||
query, params := buildPostsQuery(config, "path")
|
query, params := buildPostsQuery(config, "path")
|
||||||
row, err := d.queryRow("select count(distinct path) from ("+query+")", params...)
|
row, err := d.QueryRow("select count(distinct path) from ("+query+")", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -606,7 +606,7 @@ func (d *database) countPosts(config *postsRequestConfig) (count int, err error)
|
||||||
func (a *goBlog) getRandomPostPath(blog string) (path string, err error) {
|
func (a *goBlog) getRandomPostPath(blog string) (path string, err error) {
|
||||||
sections := lo.Keys(a.cfg.Blogs[blog].Sections)
|
sections := lo.Keys(a.cfg.Blogs[blog].Sections)
|
||||||
query, params := buildPostsQuery(&postsRequestConfig{randomOrder: true, limit: 1, blog: blog, sections: sections}, "path")
|
query, params := buildPostsQuery(&postsRequestConfig{randomOrder: true, limit: 1, blog: blog, sections: sections}, "path")
|
||||||
row, err := a.db.queryRow(query, params...)
|
row, err := a.db.QueryRow(query, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -621,7 +621,7 @@ func (a *goBlog) getRandomPostPath(blog string) (path string, err error) {
|
||||||
|
|
||||||
func (d *database) allTaxonomyValues(blog string, taxonomy string) ([]string, error) {
|
func (d *database) allTaxonomyValues(blog string, taxonomy string) ([]string, error) {
|
||||||
// TODO: Query posts the normal way
|
// TODO: Query posts the normal way
|
||||||
rows, err := d.query("select distinct value from post_parameters where parameter = @tax and length(coalesce(value, '')) > 0 and path in (select path from posts where blog = @blog and status = @status) order by value", sql.Named("tax", taxonomy), sql.Named("blog", blog), sql.Named("status", statusPublished))
|
rows, err := d.Query("select distinct value from post_parameters where parameter = @tax and length(coalesce(value, '')) > 0 and path in (select path from posts where blog = @blog and status = @status) order by value", sql.Named("tax", taxonomy), sql.Named("blog", blog), sql.Named("status", statusPublished))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -663,7 +663,7 @@ func (db *database) usesOfMediaFile(names ...string) (counts []int, err error) {
|
||||||
nameValues.WriteByte(')')
|
nameValues.WriteByte(')')
|
||||||
sqlArgs = append(sqlArgs, sql.Named(named, n))
|
sqlArgs = append(sqlArgs, sql.Named(named, n))
|
||||||
}
|
}
|
||||||
rows, err := db.query(fmt.Sprintf(mediaUseSql, nameValues.String()), sqlArgs...)
|
rows, err := db.Query(fmt.Sprintf(mediaUseSql, nameValues.String()), sqlArgs...)
|
||||||
bufferpool.Put(nameValues)
|
bufferpool.Put(nameValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -402,7 +402,7 @@ func Test_postDeletesParams(t *testing.T) {
|
||||||
err = app.deletePost("/test/abc")
|
err = app.deletePost("/test/abc")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
row, err := app.db.queryRow("select count(*) from post_parameters where path = ? and parameter = ?", "/test/abc", "test")
|
row, err := app.db.QueryRow("select count(*) from post_parameters where path = ? and parameter = ?", "/test/abc", "test")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
|
@ -415,7 +415,7 @@ func Test_postDeletesParams(t *testing.T) {
|
||||||
err = app.deletePost("/test/abc")
|
err = app.deletePost("/test/abc")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
row, err = app.db.queryRow("select count(*) from post_parameters where path = ? and parameter = ?", "/test/abc", "test")
|
row, err = app.db.QueryRow("select count(*) from post_parameters where path = ? and parameter = ?", "/test/abc", "test")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = row.Scan(&count)
|
err = row.Scan(&count)
|
||||||
|
|
8
queue.go
8
queue.go
|
@ -22,7 +22,7 @@ func (a *goBlog) enqueue(name string, content []byte, schedule time.Time) error
|
||||||
if len(content) == 0 {
|
if len(content) == 0 {
|
||||||
return errors.New("empty content")
|
return errors.New("empty content")
|
||||||
}
|
}
|
||||||
_, err := a.db.exec(
|
_, err := a.db.Exec(
|
||||||
"insert into queue (name, content, schedule) values (@name, @content, @schedule)",
|
"insert into queue (name, content, schedule) values (@name, @content, @schedule)",
|
||||||
sql.Named("name", name),
|
sql.Named("name", name),
|
||||||
sql.Named("content", content),
|
sql.Named("content", content),
|
||||||
|
@ -35,7 +35,7 @@ func (a *goBlog) enqueue(name string, content []byte, schedule time.Time) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) reschedule(qi *queueItem, dur time.Duration) error {
|
func (a *goBlog) reschedule(qi *queueItem, dur time.Duration) error {
|
||||||
_, err := a.db.exec(
|
_, err := a.db.Exec(
|
||||||
"update queue set schedule = @schedule, content = @content where id = @id",
|
"update queue set schedule = @schedule, content = @content where id = @id",
|
||||||
sql.Named("schedule", qi.schedule.Add(dur).UTC().Format(time.RFC3339Nano)),
|
sql.Named("schedule", qi.schedule.Add(dur).UTC().Format(time.RFC3339Nano)),
|
||||||
sql.Named("content", qi.content),
|
sql.Named("content", qi.content),
|
||||||
|
@ -45,12 +45,12 @@ func (a *goBlog) reschedule(qi *queueItem, dur time.Duration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) dequeue(qi *queueItem) error {
|
func (a *goBlog) dequeue(qi *queueItem) error {
|
||||||
_, err := a.db.exec("delete from queue where id = @id", sql.Named("id", qi.id))
|
_, err := a.db.Exec("delete from queue where id = @id", sql.Named("id", qi.id))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) peekQueue(ctx context.Context, name string) (*queueItem, error) {
|
func (a *goBlog) peekQueue(ctx context.Context, name string) (*queueItem, error) {
|
||||||
row, err := a.db.queryRowContext(
|
row, err := a.db.QueryRowContext(
|
||||||
ctx,
|
ctx,
|
||||||
"select id, name, content, schedule from queue where schedule <= @schedule and name = @name order by schedule asc limit 1",
|
"select id, name, content, schedule from queue where schedule <= @schedule and name = @name order by schedule asc limit 1",
|
||||||
sql.Named("name", name),
|
sql.Named("name", name),
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (a *goBlog) saveReaction(reaction, path string) error {
|
||||||
defer a.reactionsSfg.Forget(path)
|
defer a.reactionsSfg.Forget(path)
|
||||||
defer a.reactionsCache.Del(path)
|
defer a.reactionsCache.Del(path)
|
||||||
// Insert reaction
|
// Insert reaction
|
||||||
_, err := a.db.exec("insert into reactions (path, reaction, count) values (?, ?, 1) on conflict (path, reaction) do update set count=count+1", path, reaction)
|
_, err := a.db.Exec("insert into reactions (path, reaction, count) values (?, ?, 1) on conflict (path, reaction) do update set count=count+1", path, reaction)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ func (a *goBlog) getReactionsFromDatabase(path string) (map[string]int, error) {
|
||||||
sqlBuf.WriteString(") and path not in (select path from post_parameters where parameter=? and value=?)")
|
sqlBuf.WriteString(") and path not in (select path from post_parameters where parameter=? and value=?)")
|
||||||
sqlArgs = append(sqlArgs, reactionsPostParam, "false")
|
sqlArgs = append(sqlArgs, reactionsPostParam, "false")
|
||||||
// Execute query
|
// Execute query
|
||||||
rows, err := a.db.query(sqlBuf.String(), sqlArgs...)
|
rows, err := a.db.Query(sqlBuf.String(), sqlArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
10
sessions.go
10
sessions.go
|
@ -23,7 +23,7 @@ const (
|
||||||
|
|
||||||
func (a *goBlog) initSessions() {
|
func (a *goBlog) initSessions() {
|
||||||
deleteExpiredSessions := func() {
|
deleteExpiredSessions := func() {
|
||||||
if _, err := a.db.exec(
|
if _, err := a.db.Exec(
|
||||||
"delete from sessions where expires < @now",
|
"delete from sessions where expires < @now",
|
||||||
sql.Named("now", utcNowString()),
|
sql.Named("now", utcNowString()),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -103,14 +103,14 @@ func (s *dbSessionStore) Delete(_ *http.Request, w http.ResponseWriter, session
|
||||||
for k := range session.Values {
|
for k := range session.Values {
|
||||||
delete(session.Values, k)
|
delete(session.Values, k)
|
||||||
}
|
}
|
||||||
if _, err := s.db.exec("delete from sessions where id = @id", sql.Named("id", session.ID)); err != nil {
|
if _, err := s.db.Exec("delete from sessions where id = @id", sql.Named("id", session.ID)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbSessionStore) load(session *sessions.Session) (err error) {
|
func (s *dbSessionStore) load(session *sessions.Session) (err error) {
|
||||||
row, err := s.db.queryRow(
|
row, err := s.db.QueryRow(
|
||||||
"select data, created, modified, expires from sessions where id = @id and expires > @now",
|
"select data, created, modified, expires from sessions where id = @id and expires > @now",
|
||||||
sql.Named("id", session.ID),
|
sql.Named("id", session.ID),
|
||||||
sql.Named("now", utcNowString()),
|
sql.Named("now", utcNowString()),
|
||||||
|
@ -142,7 +142,7 @@ func (s *dbSessionStore) insert(session *sessions.Session) (err error) {
|
||||||
session.ID = session.Name() + "-" + uuid.NewString()
|
session.ID = session.Name() + "-" + uuid.NewString()
|
||||||
created, modified := utcNowString(), utcNowString()
|
created, modified := utcNowString(), utcNowString()
|
||||||
expires := time.Now().UTC().Add(time.Second * time.Duration(session.Options.MaxAge)).Format(time.RFC3339)
|
expires := time.Now().UTC().Add(time.Second * time.Duration(session.Options.MaxAge)).Format(time.RFC3339)
|
||||||
_, err = s.db.exec(
|
_, err = s.db.Exec(
|
||||||
"insert or replace into sessions(id, data, created, modified, expires) values(@id, @data, @created, @modified, @expires)",
|
"insert or replace into sessions(id, data, created, modified, expires) values(@id, @data, @created, @modified, @expires)",
|
||||||
sql.Named("id", session.ID),
|
sql.Named("id", session.ID),
|
||||||
sql.Named("data", encoded.Bytes()),
|
sql.Named("data", encoded.Bytes()),
|
||||||
|
@ -163,7 +163,7 @@ func (s *dbSessionStore) save(session *sessions.Session) (err error) {
|
||||||
if err = gob.NewEncoder(encoded).Encode(session.Values); err != nil {
|
if err = gob.NewEncoder(encoded).Encode(session.Values); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = s.db.exec(
|
_, err = s.db.Exec(
|
||||||
"update sessions set data = @data, modified = @modified where id = @id",
|
"update sessions set data = @data, modified = @modified where id = @id",
|
||||||
sql.Named("data", encoded.Bytes()),
|
sql.Named("data", encoded.Bytes()),
|
||||||
sql.Named("modified", utcNowString()),
|
sql.Named("modified", utcNowString()),
|
||||||
|
|
|
@ -18,7 +18,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) getSettingValue(name string) (string, error) {
|
func (a *goBlog) getSettingValue(name string) (string, error) {
|
||||||
row, err := a.db.queryRow("select value from settings where name = @name", sql.Named("name", name))
|
row, err := a.db.QueryRow("select value from settings where name = @name", sql.Named("name", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "",
|
return "",
|
||||||
err
|
err
|
||||||
|
@ -45,7 +45,7 @@ func (a *goBlog) getBooleanSettingValue(name string, defaultValue bool) (bool, e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) saveSettingValue(name, value string) error {
|
func (a *goBlog) saveSettingValue(name, value string) error {
|
||||||
_, err := a.db.exec(
|
_, err := a.db.Exec(
|
||||||
"insert into settings (name, value) values (@name, @value) on conflict (name) do update set value = @value2",
|
"insert into settings (name, value) values (@name, @value) on conflict (name) do update set value = @value2",
|
||||||
sql.Named("name", name),
|
sql.Named("name", name),
|
||||||
sql.Named("value", value),
|
sql.Named("value", value),
|
||||||
|
@ -70,7 +70,7 @@ func (a *goBlog) loadSections() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) getSections(blog string) (map[string]*configSection, error) {
|
func (a *goBlog) getSections(blog string) (map[string]*configSection, error) {
|
||||||
rows, err := a.db.query("select name, title, description, pathtemplate, showfull from sections where blog = @blog", sql.Named("blog", blog))
|
rows, err := a.db.Query("select name, title, description, pathtemplate, showfull from sections where blog = @blog", sql.Named("blog", blog))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ func (a *goBlog) saveAllSections() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) saveSection(blog string, section *configSection) error {
|
func (a *goBlog) saveSection(blog string, section *configSection) error {
|
||||||
_, err := a.db.exec(
|
_, err := a.db.Exec(
|
||||||
`
|
`
|
||||||
insert into sections (blog, name, title, description, pathtemplate, showfull) values (@blog, @name, @title, @description, @pathtemplate, @showfull)
|
insert into sections (blog, name, title, description, pathtemplate, showfull) values (@blog, @name, @title, @description, @pathtemplate, @showfull)
|
||||||
on conflict (blog, name) do update set title = @title2, description = @description2, pathtemplate = @pathtemplate2, showfull = @showfull2
|
on conflict (blog, name) do update set title = @title2, description = @description2, pathtemplate = @pathtemplate2, showfull = @showfull2
|
||||||
|
@ -119,6 +119,6 @@ func (a *goBlog) saveSection(blog string, section *configSection) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) deleteSection(blog string, name string) error {
|
func (a *goBlog) deleteSection(blog string, name string) error {
|
||||||
_, err := a.db.exec("delete from sections where blog = @blog and name = @name", sql.Named("blog", blog), sql.Named("name", name))
|
_, err := a.db.Exec("delete from sections where blog = @blog and name = @name", sql.Named("blog", blog), sql.Named("name", name))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (db *database) shortenPath(p string) (string, error) {
|
||||||
return spi.(string), nil
|
return spi.(string), nil
|
||||||
}
|
}
|
||||||
// Insert in case it isn't shortened yet
|
// Insert in case it isn't shortened yet
|
||||||
_, err := db.exec(`
|
_, err := db.Exec(`
|
||||||
insert or rollback into shortpath (id, path)
|
insert or rollback into shortpath (id, path)
|
||||||
values (
|
values (
|
||||||
-- next available id (reuse skipped ids due to bug)
|
-- next available id (reuse skipped ids due to bug)
|
||||||
|
@ -31,7 +31,7 @@ func (db *database) shortenPath(p string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Query short path
|
// Query short path
|
||||||
row, err := db.queryRow("select printf('/s/%x', id) from shortpath where path = @path", sql.Named("path", p))
|
row, err := db.QueryRow("select printf('/s/%x', id) from shortpath where path = @path", sql.Named("path", p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func Test_shortenPath(t *testing.T) {
|
||||||
assert.Equal(t, "/s/1", res4)
|
assert.Equal(t, "/s/1", res4)
|
||||||
|
|
||||||
db.spc.Del("/a")
|
db.spc.Del("/a")
|
||||||
_, _ = db.exec("delete from shortpath where id = 1")
|
_, _ = db.Exec("delete from shortpath where id = 1")
|
||||||
|
|
||||||
res5, err := db.shortenPath("/c")
|
res5, err := db.shortenPath("/c")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -213,7 +213,7 @@ select distinct '/x/x/' || day from alldates;
|
||||||
`
|
`
|
||||||
|
|
||||||
func (a *goBlog) sitemapDatePaths(blog string) (paths []string, err error) {
|
func (a *goBlog) sitemapDatePaths(blog string) (paths []string, err error) {
|
||||||
rows, err := a.db.query(sitemapDatePathsSql, sql.Named("blog", blog), sql.Named("status", statusPublished))
|
rows, err := a.db.Query(sitemapDatePathsSql, sql.Named("blog", blog), sql.Named("status", statusPublished))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (a *goBlog) serveTaxonomyValue(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get value from DB
|
// Get value from DB
|
||||||
row, err := a.db.queryRow(
|
row, err := a.db.QueryRow(
|
||||||
"select value from post_parameters where parameter = @tax and urlize(value) = @taxValue limit 1",
|
"select value from post_parameters where parameter = @tax and urlize(value) = @taxValue limit 1",
|
||||||
sql.Named("tax", tax.Name), sql.Named("taxValue", taxValueParam),
|
sql.Named("tax", tax.Name), sql.Named("taxValue", taxValueParam),
|
||||||
)
|
)
|
||||||
|
|
|
@ -103,7 +103,7 @@ func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
|
||||||
|
|
||||||
func (db *database) webmentionExists(m *mention) bool {
|
func (db *database) webmentionExists(m *mention) bool {
|
||||||
result := 0
|
result := 0
|
||||||
row, err := db.queryRow(
|
row, err := db.QueryRow(
|
||||||
`
|
`
|
||||||
select exists(
|
select exists(
|
||||||
select 1
|
select 1
|
||||||
|
@ -134,7 +134,7 @@ func (a *goBlog) createWebmention(source, target string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) insertWebmention(m *mention, status webmentionStatus) error {
|
func (db *database) insertWebmention(m *mention, status webmentionStatus) error {
|
||||||
_, err := db.exec(
|
_, err := db.Exec(
|
||||||
`
|
`
|
||||||
insert into webmentions (source, target, url, created, status, title, content, author)
|
insert into webmentions (source, target, url, created, status, title, content, author)
|
||||||
values (@source, lowerunescaped(@target), @url, @created, @status, @title, @content, @author)
|
values (@source, lowerunescaped(@target), @url, @created, @status, @title, @content, @author)
|
||||||
|
@ -152,7 +152,7 @@ func (db *database) insertWebmention(m *mention, status webmentionStatus) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) updateWebmention(m *mention, newStatus webmentionStatus) error {
|
func (db *database) updateWebmention(m *mention, newStatus webmentionStatus) error {
|
||||||
_, err := db.exec(`
|
_, err := db.Exec(`
|
||||||
update webmentions
|
update webmentions
|
||||||
set
|
set
|
||||||
source = @newsource,
|
source = @newsource,
|
||||||
|
@ -182,12 +182,12 @@ func (db *database) updateWebmention(m *mention, newStatus webmentionStatus) err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) deleteWebmentionId(id int) error {
|
func (db *database) deleteWebmentionId(id int) error {
|
||||||
_, err := db.exec("delete from webmentions where id = @id", sql.Named("id", id))
|
_, err := db.Exec("delete from webmentions where id = @id", sql.Named("id", id))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) deleteWebmention(m *mention) error {
|
func (db *database) deleteWebmention(m *mention) error {
|
||||||
_, err := db.exec(
|
_, err := db.Exec(
|
||||||
"delete from webmentions where lowerunescaped(source) in (lowerunescaped(@source), lowerunescaped(@newsource)) and lowerunescaped(target) in (lowerunescaped(@target), lowerunescaped(@newtarget))",
|
"delete from webmentions where lowerunescaped(source) in (lowerunescaped(@source), lowerunescaped(@newsource)) and lowerunescaped(target) in (lowerunescaped(@target), lowerunescaped(@newtarget))",
|
||||||
sql.Named("source", m.Source),
|
sql.Named("source", m.Source),
|
||||||
sql.Named("newsource", defaultIfEmpty(m.NewSource, m.Source)),
|
sql.Named("newsource", defaultIfEmpty(m.NewSource, m.Source)),
|
||||||
|
@ -198,7 +198,7 @@ func (db *database) deleteWebmention(m *mention) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *database) approveWebmentionId(id int) error {
|
func (db *database) approveWebmentionId(id int) error {
|
||||||
_, err := db.exec("update webmentions set status = ? where id = ?", webmentionStatusApproved, id)
|
_, err := db.Exec("update webmentions set status = ? where id = ?", webmentionStatusApproved, id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ func buildWebmentionsQuery(config *webmentionsRequestConfig) (query string, args
|
||||||
func (db *database) getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
|
func (db *database) getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
|
||||||
mentions := []*mention{}
|
mentions := []*mention{}
|
||||||
query, args := buildWebmentionsQuery(config)
|
query, args := buildWebmentionsQuery(config)
|
||||||
rows, err := db.query(query, args...)
|
rows, err := db.Query(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -310,7 +310,7 @@ func (db *database) getWebmentionsByAddress(address string) []*mention {
|
||||||
func (db *database) countWebmentions(config *webmentionsRequestConfig) (count int, err error) {
|
func (db *database) countWebmentions(config *webmentionsRequestConfig) (count int, err error) {
|
||||||
query, params := buildWebmentionsQuery(config)
|
query, params := buildWebmentionsQuery(config)
|
||||||
query = "select count(*) from (" + query + ")"
|
query = "select count(*) from (" + query + ")"
|
||||||
row, err := db.queryRow(query, params...)
|
row, err := db.QueryRow(query, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue