Add database-based settings, settings screen and migrate sections to db and allow to configure them

pull/25/head
Jan-Lukas Else 9 months ago
parent 22fce13246
commit cd9d500a55

@ -5,7 +5,6 @@ import (
"encoding/pem"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -15,15 +14,10 @@ import (
func Test_loadActivityPubPrivateKey(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
cfg: createDefaultTestConfig(t),
}
err := app.initDatabase(false)
err := app.initConfig(false)
require.NoError(t, err)
defer app.db.close()
require.NotNil(t, app.db)
// Generate
@ -55,9 +49,7 @@ func Test_webfinger(t *testing.T) {
cfg: createDefaultTestConfig(t),
}
app.cfg.Server.PublicAddress = "https://example.com"
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.prepareWebfinger()

@ -29,9 +29,7 @@ func Test_authMiddleware(t *testing.T) {
},
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.d = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {

@ -4,7 +4,6 @@ import (
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -17,34 +16,26 @@ func Test_blogroll(t *testing.T) {
app := &goBlog{
httpClient: fc.Client,
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{},
DefaultBlog: "en",
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
Blogroll: &configBlogroll{
Enabled: true,
Path: "/br",
AuthHeader: "Authheader",
AuthValue: "Authtoken",
Opml: "https://example.com/opml",
Categories: []string{"A", "B"},
},
},
},
User: &configUser{},
Cache: &configCache{
Enable: false,
cfg: createDefaultTestConfig(t),
}
app.cfg.Cache.Enable = false
app.cfg.DefaultBlog = "en"
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
Blogroll: &configBlogroll{
Enabled: true,
Path: "/br",
AuthHeader: "Authheader",
AuthValue: "Authtoken",
Opml: "https://example.com/opml",
Categories: []string{"A", "B"},
},
},
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
fc.setFakeResponse(http.StatusOK, `

@ -5,7 +5,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -14,36 +13,26 @@ import (
)
func Test_blogStats(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
BlogStats: &configBlogStats{
Enabled: true,
Path: "/stats",
},
Sections: map[string]*configSection{
"test": {},
},
},
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
BlogStats: &configBlogStats{
Enabled: true,
Path: "/stats",
},
DefaultBlog: "en",
User: &configUser{},
Webmention: &configWebmention{
DisableSending: true,
Sections: map[string]*configSection{
"test": {},
},
},
}
app.cfg.DefaultBlog = "en"
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
// Insert post

@ -23,9 +23,7 @@ func Test_captchaMiddleware(t *testing.T) {
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.d = alice.New(app.checkIsCaptcha, app.captchaMiddleware).ThenFunc(func(rw http.ResponseWriter, r *http.Request) {

@ -5,7 +5,6 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
@ -18,25 +17,20 @@ import (
func Test_comments(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
Comments: &configComments{
Enabled: true,
},
DefaultBlog: "en",
User: &configUser{},
},
}
app.cfg.DefaultBlog = "en"
_ = app.initDatabase(false)
defer app.db.close()
err := app.initConfig(false)
require.NoError(t, err)
app.initComponents(false)
t.Run("Successful comment", func(t *testing.T) {
@ -44,7 +38,7 @@ func Test_comments(t *testing.T) {
// Create comment
data := url.Values{}
data.Add("target", "https://example.com/test")
data.Add("target", "http://localhost:8080/test")
data.Add("comment", "This is just a test")
data.Add("name", "Test name")
data.Add("website", "https://goblog.app")
@ -115,7 +109,7 @@ func Test_comments(t *testing.T) {
// Create comment
data := url.Values{}
data.Add("target", "https://example.com/test")
data.Add("target", "http://localhost:8080/test")
data.Add("comment", "This is just a test")
req := httptest.NewRequest(http.MethodPost, commentPath, strings.NewReader(data.Encode()))
@ -151,7 +145,7 @@ func Test_comments(t *testing.T) {
t.Run("Empty comment", func(t *testing.T) {
data := url.Values{}
data.Add("target", "https://example.com/test")
data.Add("target", "http://localhost:8080/test")
data.Add("comment", "")
req := httptest.NewRequest(http.MethodPost, commentPath, strings.NewReader(data.Encode()))

@ -341,13 +341,17 @@ func (a *goBlog) loadConfigFile(file string) error {
return v.Unmarshal(a.cfg)
}
func (a *goBlog) initConfig() error {
func (a *goBlog) initConfig(logging bool) error {
if a.cfg == nil {
a.cfg = createDefaultConfig()
}
if a.cfg.initialized {
return nil
}
// Init database
if err := a.initDatabase(logging); err != nil {
return err
}
// Check config
// Parse addresses and hostnames
if a.cfg.Server.PublicAddress == "" {
@ -402,12 +406,24 @@ func (a *goBlog) initConfig() error {
b.Comments = &configComments{Enabled: false}
}
}
// Check if sections already migrated to db
const sectionMigrationKey = "sections_migrated"
if val, err := a.getSettingValue(sectionMigrationKey); err != nil {
return err
} else if val == "" {
if err = a.saveAllSections(); err != nil {
return err
}
if err = a.saveSettingValue(sectionMigrationKey, "1"); err != nil {
return err
}
}
// Load db sections
if err = a.loadSections(); err != nil {
return err
}
// Check config for each blog
for _, blog := range a.cfg.Blogs {
// Copy sections key to section name
for k, s := range blog.Sections {
s.Name = k
}
// Check if language is set
if blog.Lang == "" {
blog.Lang = "en"

@ -5,7 +5,6 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
@ -24,35 +23,27 @@ func Test_contact(t *testing.T) {
// Init everything
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
// Config for contact
Contact: &configContact{
Enabled: true,
SMTPPort: port,
SMTPHost: "127.0.0.1",
SMTPUser: "user",
SMTPPassword: "pass",
EmailTo: "to@example.org",
EmailFrom: "from@example.org",
EmailSubject: "Neue Kontaktnachricht",
},
},
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
// Config for contact
Contact: &configContact{
Enabled: true,
SMTPPort: port,
SMTPHost: "127.0.0.1",
SMTPUser: "user",
SMTPPassword: "pass",
EmailTo: "to@example.org",
EmailFrom: "from@example.org",
EmailSubject: "Neue Kontaktnachricht",
},
DefaultBlog: "en",
User: &configUser{},
},
}
_ = app.initDatabase(false)
defer app.db.close()
app.cfg.DefaultBlog = "en"
_ = app.initConfig(false)
app.initComponents(false)
// Make contact form request

@ -31,6 +31,9 @@ type database struct {
}
func (a *goBlog) initDatabase(logging bool) (err error) {
if a.db != nil {
return
}
if logging {
log.Println("Initialize database...")
}

@ -0,0 +1,9 @@
create table sections (
blog text not null,
name text not null,
title text not null default '',
description text not null default '',
pathtemplate text not null default '',
showfull boolean not null default false,
primary key (blog, name)
);

@ -0,0 +1,5 @@
create table settings (
name text not null,
value text not null default '',
primary key (name)
);

@ -15,9 +15,7 @@ func Test_editorPreview(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
h := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {

@ -4,7 +4,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -13,25 +12,10 @@ import (
func Test_errors(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
},
DefaultBlog: "en",
User: &configUser{},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
t.Run("Test 404, no HTML", func(t *testing.T) {

@ -11,14 +11,9 @@ import (
func Test_export(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initMarkdown()
err := app.db.savePost(&post{

@ -15,10 +15,9 @@ func Test_feeds(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.d = app.buildRouter()
handlerClient := newHandlerClient(app.d)

@ -2,7 +2,6 @@ package main
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -11,24 +10,18 @@ import (
func Test_geoTrack(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
"de": {
Lang: "de",
},
},
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
},
"de": {
Lang: "de",
},
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
// First test (just with track)

@ -16,9 +16,8 @@ func Test_geo(t *testing.T) {
httpClient: fc.Client,
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
p := &post{

@ -32,7 +32,7 @@ const (
func (a *goBlog) startServer() (err error) {
log.Println("Start server(s)...")
// Load router
a.d = a.buildRouter()
a.reloadRouter()
// Set basic middlewares
h := alice.New()
h = h.Append(middleware.Heartbeat("/ping"))
@ -43,7 +43,9 @@ func (a *goBlog) startServer() (err error) {
if a.httpsConfigured(false) {
h = h.Append(a.securityHeaders)
}
finalHandler := h.Then(a.d)
finalHandler := h.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
a.d.ServeHTTP(w, r)
})
// Start Onion service
if a.cfg.Server.Tor {
go func() {
@ -116,6 +118,10 @@ const (
feedPath = ".{feed:(rss|json|atom)}"
)
func (a *goBlog) reloadRouter() {
a.d = a.buildRouter()
}
func (a *goBlog) buildRouter() http.Handler {
mapRouter := &maprouter.MapRouter{
Handlers: map[string]http.Handler{},

@ -15,7 +15,7 @@ func Test_httpLogsConfig(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initConfig(false)
assert.Equal(t, false, app.cfg.Server.Logging)
assert.Equal(t, "data/access.log", app.cfg.Server.LogFile)

@ -170,6 +170,9 @@ func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
// Sitemap
r.Group(a.blogSitemapRouter(conf))
// Settings
r.Route(conf.getRelativePath(settingsPath), a.blogSettingsRouter(conf))
}
}
@ -442,3 +445,14 @@ func (a *goBlog) blogSitemapRouter(conf *configBlog) func(r chi.Router) {
r.Get(conf.getRelativePath(sitemapBlogPostsPath), a.serveSitemapBlogPosts)
}
}
// Blog - Settings
func (a *goBlog) blogSettingsRouter(_ *configBlog) func(r chi.Router) {
return func(r chi.Router) {
r.Use(a.authMiddleware)
r.Get("/", a.serveSettings)
r.Post(settingsDeleteSectionPath, a.settingsDeleteSection)
r.Post(settingsCreateSectionPath, a.settingsCreateSection)
r.Post(settingsUpdateSectionPath, a.settingsUpdateSection)
}
}

@ -19,9 +19,8 @@ func Test_indexNow(t *testing.T) {
httpClient: fc.Client,
}
app.cfg.IndexNow = &configIndexNow{Enabled: true}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
// Create http router

@ -5,7 +5,6 @@ import (
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
@ -22,35 +21,25 @@ func Test_indieAuthServer(t *testing.T) {
app := &goBlog{
httpClient: newFakeHttpClient().Client,
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.org",
},
DefaultBlog: "en",
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
},
User: &configUser{
Name: "John Doe",
Nick: "jdoe",
},
Cache: &configCache{
Enable: false,
},
cfg: createDefaultTestConfig(t),
}
app.cfg.Server.PublicAddress = "https://example.org"
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
},
}
app.cfg.User = &configUser{
Name: "John Doe",
Nick: "jdoe",
}
app.cfg.Cache.Enable = false
app.d = app.buildRouter()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.d = app.buildRouter()
app.ias.Client = newHandlerClient(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))

@ -3,7 +3,6 @@ package main
import (
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
@ -15,22 +14,10 @@ func Test_checkIndieAuth(t *testing.T) {
app := &goBlog{
httpClient: newFakeHttpClient().Client,
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{},
DefaultBlog: "en",
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
req := httptest.NewRequest(http.MethodGet, "/", nil)

@ -64,7 +64,7 @@ func main() {
app.logErrAndQuit("Failed to load config file:", err.Error())
return
}
if err = app.initConfig(); err != nil {
if err = app.initConfig(false); err != nil {
app.logErrAndQuit("Failed to init config:", err.Error())
return
}
@ -129,12 +129,6 @@ func main() {
// Execute pre-start hooks
app.preStartHooks()
// Initialize database
if err = app.initDatabase(true); err != nil {
app.logErrAndQuit("Failed to init database:", err.Error())
return
}
// Link check tool after init of markdown
if len(os.Args) >= 2 && os.Args[1] == "check" {
app.initMarkdown()

@ -14,9 +14,7 @@ func Test_micropubQuery(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
// Create a test post with tags

@ -18,9 +18,7 @@ func Test_ntfySending(t *testing.T) {
httpClient: fakeClient.Client,
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
t.Run("Default", func(t *testing.T) {

@ -1,7 +1,6 @@
package main
import (
"path/filepath"
"testing"
"time"
@ -25,9 +24,7 @@ func Test_postsDb(t *testing.T) {
},
},
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
now := toLocalSafe(time.Now().String())
@ -229,14 +226,9 @@ func Test_ftsWithoutTitle(t *testing.T) {
// Added because there was a bug where there were no search results without title
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initMarkdown()
err := app.db.savePost(&post{
@ -261,14 +253,9 @@ func Test_postsPriority(t *testing.T) {
// Added because there was a bug where there were no search results without title
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initMarkdown()
err := app.db.savePost(&post{
@ -312,14 +299,9 @@ func Test_postsPriority(t *testing.T) {
func Test_usesOfMediaFile(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
err := app.db.savePost(&post{
Path: "/test/abc",
@ -368,8 +350,7 @@ func Test_replaceParams(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
err := app.db.savePost(&post{
Path: "/test/abc",
@ -399,9 +380,7 @@ func Test_postDeletesParams(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
err := app.createPost(&post{

@ -11,9 +11,8 @@ func Test_checkDeletedPosts(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
// Create a post

@ -1,7 +1,6 @@
package main
import (
"path/filepath"
"testing"
"time"
@ -12,28 +11,18 @@ import (
func Test_postsScheduler(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
Server: &configServer{
PublicAddress: "https://example.com",
},
DefaultBlog: "en",
Blogs: map[string]*configBlog{
"en": {
Sections: map[string]*configSection{
"test": {},
},
Lang: "en",
},
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Sections: map[string]*configSection{
"test": {},
},
Micropub: &configMicropub{},
Lang: "en",
},
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
err := app.db.savePost(&post{

@ -16,9 +16,7 @@ func Test_serveDate(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.d = app.buildRouter()
@ -106,9 +104,7 @@ func Test_servePost(t *testing.T) {
Username: "test",
Password: "test",
})
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
app.d = app.buildRouter()

@ -3,7 +3,6 @@ package main
import (
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/go-chi/chi/v5/middleware"
@ -16,36 +15,28 @@ func Test_privateMode(t *testing.T) {
// Init
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "db.db"),
},
Server: &configServer{},
PrivateMode: &configPrivateMode{
Enabled: true,
},
User: &configUser{
Name: "Test",
Nick: "test",
Password: "testpw",
AppPasswords: []*configAppPassword{
{
Username: "testapp",
Password: "pw",
},
},
},
DefaultBlog: "en",
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
cfg: createDefaultTestConfig(t),
}
app.cfg.PrivateMode = &configPrivateMode{Enabled: true}
app.cfg.User =
&configUser{
Name: "Test",
Nick: "test",
Password: "testpw",
AppPasswords: []*configAppPassword{
{
Username: "testapp",
Password: "pw",
},
},
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
},
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
handler := alice.New(middleware.WithValue(blogKey, "en"), app.privateModeHandler).ThenFunc(func(rw http.ResponseWriter, r *http.Request) {

@ -2,7 +2,6 @@ package main
import (
"context"
"path/filepath"
"testing"
"time"
@ -12,14 +11,9 @@ import (
func Test_queue(t *testing.T) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(t.TempDir(), "test.db"),
},
},
cfg: createDefaultTestConfig(t),
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
time1 := time.Now()
@ -65,30 +59,3 @@ func Test_queue(t *testing.T) {
require.Equal(t, []byte("1"), qi.content)
}
func Benchmark_queue(b *testing.B) {
app := &goBlog{
cfg: &config{
Db: &configDb{
File: filepath.Join(b.TempDir(), "test.db"),
},
},
}
_ = app.initDatabase(false)
defer app.db.close()
err := app.enqueue("test", []byte("1"), time.Now())
require.NoError(b, err)
b.Run("Peek with item", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = app.peekQueue(context.Background(), "test")
}
})
b.Run("Peek without item", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = app.peekQueue(context.Background(), "abc")
}
})
}

@ -15,9 +15,7 @@ func Test_reactionsLowLevel(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
err := app.saveReaction("🖕", "/testpost")
@ -97,9 +95,7 @@ func Test_reactionsHighLevel(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
// Send unsuccessful reaction

@ -0,0 +1,129 @@
package main
import (
"net/http"
"sort"
"github.com/samber/lo"
)
const settingsPath = "/settings"
func (a *goBlog) serveSettings(w http.ResponseWriter, r *http.Request) {
blog, bc := a.getBlog(r)
sections := lo.Values(bc.Sections)
sort.Slice(sections, func(i, j int) bool { return sections[i].Name < sections[j].Name })
a.render(w, r, a.renderSettings, &renderData{
Data: &settingsRenderData{
blog: blog,
sections: sections,
},
})
}
const settingsDeleteSectionPath = "/deletesection"
func (a *goBlog) settingsDeleteSection(w http.ResponseWriter, r *http.Request) {
blog, bc := a.getBlog(r)
section := r.FormValue("sectionname")
// Check if any post uses this section
count, err := a.db.countPosts(&postsRequestConfig{
blog: blog,
sections: []string{section},
})
if err != nil {
a.serveError(w, r, "Failed to check if section is still used", http.StatusInternalServerError)
return
}
if count > 0 {
a.serveError(w, r, "Section is still used", http.StatusBadRequest)
return
}
// Delete section
err = a.deleteSection(blog, section)
if err != nil {
a.serveError(w, r, "Failed to delete section from the database", http.StatusInternalServerError)
return
}
// Reload sections
err = a.loadSections()
if err != nil {
a.serveError(w, r, "Failed to reload section configuration from the database", http.StatusInternalServerError)
return
}
a.reloadRouter()
a.cache.purge()
http.Redirect(w, r, bc.getRelativePath(settingsPath), http.StatusFound)
}
const settingsCreateSectionPath = "/createsection"
func (a *goBlog) settingsCreateSection(w http.ResponseWriter, r *http.Request) {
blog, bc := a.getBlog(r)
// Read values
sectionName := r.FormValue("sectionname")
sectionTitle := r.FormValue("sectiontitle")
if sectionName == "" || sectionTitle == "" {
a.serveError(w, r, "Missing values for name or title", http.StatusBadRequest)
return
}
// Create section
section := &configSection{
Name: sectionName,
Title: sectionTitle,
}
err := a.addSection(blog, section)
if err != nil {
a.serveError(w, r, "Failed to insert section into database", http.StatusInternalServerError)
return
}
// Reload sections
err = a.loadSections()
if err != nil {
a.serveError(w, r, "Failed to reload section configuration from the database", http.StatusInternalServerError)
return
}
a.reloadRouter()
a.cache.purge()
http.Redirect(w, r, bc.getRelativePath(settingsPath), http.StatusFound)
}
const settingsUpdateSectionPath = "/updatesection"
func (a *goBlog) settingsUpdateSection(w http.ResponseWriter, r *http.Request) {
blog, bc := a.getBlog(r)
// Read values
sectionName := r.FormValue("sectionname")
sectionTitle := r.FormValue("sectiontitle")
if sectionName == "" || sectionTitle == "" {
a.serveError(w, r, "Missing values for name or title", http.StatusBadRequest)
return
}
sectionDescription := r.FormValue("sectiondescription")
sectionPathTemplate := r.FormValue("sectionpathtemplate")
sectionShowFull := r.FormValue("sectionshowfull") == "on"
// Create section
section := &configSection{
Name: sectionName,
Title: sectionTitle,
Description: sectionDescription,
PathTemplate: sectionPathTemplate,
ShowFull: sectionShowFull,
}
err := a.updateSection(blog, sectionName, section)
if err != nil {
a.serveError(w, r, "Failed to update section in database", http.StatusInternalServerError)
return
}
// Reload sections
err = a.loadSections()
if err != nil {
a.serveError(w, r, "Failed to reload section configuration from the database", http.StatusInternalServerError)
return
}
a.reloadRouter()
a.cache.purge()
http.Redirect(w, r, bc.getRelativePath(settingsPath), http.StatusFound)
}

@ -0,0 +1,103 @@
package main
import (
"database/sql"
"errors"
)
func (a *goBlog) getSettingValue(name string) (string, error) {
row, err := a.db.queryRow("select value from settings where name = @name", sql.Named("name", name))
if err != nil {
return "",
err
}
var value string
err = row.Scan(&value)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
} else if err != nil {
return "", err
}
return value, nil
}
func (a *goBlog) saveSettingValue(name, value string) error {
_, err := a.db.exec(
"insert into settings (name, value) values (@name, @value) on conflict (name) do update set value = @value2",
sql.Named("name", name),
sql.Named("value", value),
sql.Named("value2", value),
)
return err
}
func (a *goBlog) loadSections() error {
for blog, bc := range a.cfg.Blogs {
sections, err := a.getSections(blog)
if err != nil {
return err
}
bc.Sections = sections
}
return nil
}
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))
if err != nil {
return nil, err
}
sections := map[string]*configSection{}
for rows.Next() {
section := &configSection{}
err = rows.Scan(&section.Name, &section.Title, &section.Description, &section.PathTemplate, &section.ShowFull)
if err != nil {
return nil, err
}
sections[section.Name] = section
}
return sections, nil
}
func (a *goBlog) saveAllSections() error {
for blog, bc := range a.cfg.Blogs {
for k, s := range bc.Sections {
s.Name = k
if err := a.addSection(blog, s); err != nil {
return err
}
}
}
return nil
}
func (a *goBlog) addSection(blog string, section *configSection) error {
_, err := a.db.exec(
"insert into sections (blog, name, title, description, pathtemplate, showfull) values (@blog, @name, @title, @description, @pathtemplate, @showfull)",
sql.Named("blog", blog),
sql.Named("name", section.Name),
sql.Named("title", section.Title),
sql.Named("description", section.Description),
sql.Named("pathtemplate", section.PathTemplate),
sql.Named("showfull", section.ShowFull),
)
return err
}
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))
return err
}
func (a *goBlog) updateSection(blog string, name string, section *configSection) error {
_, err := a.db.exec(
"update sections set title = @title, description = @description, pathtemplate = @pathtemplate, showfull = @showfull where blog = @blog and name = @name",
sql.Named("title", section.Title),
sql.Named("description", section.Description),
sql.Named("pathtemplate", section.PathTemplate),
sql.Named("showfull", section.ShowFull),
sql.Named("blog", blog),
sql.Named("name", section.Name),
)
return err
}