1
mirror of https://github.com/jlelse/GoBlog synced 2024-07-15 12:22:58 +00:00

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

This commit is contained in:
Jan-Lukas Else 2022-07-16 21:09:43 +02:00
parent 22fce13246
commit cd9d500a55
44 changed files with 589 additions and 429 deletions

View File

@ -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()

View File

@ -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) {

View File

@ -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, `

View File

@ -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"),
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
BlogStats: &configBlogStats{
Enabled: true,
Path: "/stats",
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
BlogStats: &configBlogStats{
Enabled: true,
Path: "/stats",
},
Sections: map[string]*configSection{
"test": {},
},
},
},
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

View File

@ -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) {

View File

@ -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"),
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Lang: "en",
Comments: &configComments{
Enabled: true,
},
Server: &configServer{
PublicAddress: "https://example.com",
},
Blogs: map[string]*configBlog{
"en": {
Lang: "en",
},
},
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()))

View File

@ -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"

View File

@ -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"),
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",
},
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",
},
},
},
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

View File

@ -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...")
}

9
dbmigrations/00029.sql Normal file
View File

@ -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)
);

5
dbmigrations/00030.sql Normal file
View File

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

View File

@ -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) {

View File

@ -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) {

View File

@ -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{

View File

@ -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)

View File

@ -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)

View File

@ -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{

10
http.go
View File

@ -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{},

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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.initConfig(false)
app.initComponents(false)
app.d = app.buildRouter()
_ = app.initDatabase(false)
defer app.db.close()
app.initComponents(false)
app.ias.Client = newHandlerClient(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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) {

View File

@ -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{

View File

@ -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

View File

@ -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"),
cfg: createDefaultTestConfig(t),
}
app.cfg.Blogs = map[string]*configBlog{
"en": {
Sections: map[string]*configSection{
"test": {},
},
Server: &configServer{
PublicAddress: "https://example.com",
},
DefaultBlog: "en",
Blogs: map[string]*configBlog{
"en": {
Sections: map[string]*configSection{
"test": {},
},
Lang: "en",
},
},
Micropub: &configMicropub{},
Lang: "en",
},
}
_ = app.initDatabase(false)
defer app.db.close()
_ = app.initConfig(false)
app.initComponents(false)
err := app.db.savePost(&post{

View File

@ -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()

View File

@ -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) {

View File

@ -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")
}
})
}

View File

@ -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

129
settings.go Normal file
View File

@ -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)
}

103
settingsDb.go Normal file
View File

@ -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 {
retur