mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
390 lines
10 KiB
Go
390 lines
10 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
|
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
|
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
|
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
|
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tools/router"
|
|
"github.com/pocketbase/pocketbase/tools/types"
|
|
)
|
|
|
|
const (
|
|
URL_SEITEN_EDITOR = "/redaktion/seiten/"
|
|
URL_SEITEN_EDITOR_FORM = "form/"
|
|
URL_SEITEN_EDITOR_SAVE = "save/"
|
|
TEMPLATE_SEITEN_EDITOR = "/redaktion/seiten/"
|
|
TEMPLATE_SEITEN_FORM = "/redaktion/seiten/form/"
|
|
LAYOUT_FRAGMENT = "fragment"
|
|
pageHTMLPrefixSeparator = "."
|
|
)
|
|
|
|
func init() {
|
|
p := &SeitenEditPage{
|
|
StaticPage: pagemodels.StaticPage{
|
|
Name: pagemodels.P_SEITEN_EDIT_NAME,
|
|
URL: URL_SEITEN_EDITOR,
|
|
Template: TEMPLATE_SEITEN_EDITOR,
|
|
Layout: pagemodels.LAYOUT_LOGIN_PAGES,
|
|
},
|
|
}
|
|
app.Register(p)
|
|
}
|
|
|
|
type SeitenEditPage struct {
|
|
pagemodels.StaticPage
|
|
}
|
|
|
|
type SeitenEditorPageItem struct {
|
|
Key string
|
|
Title string
|
|
}
|
|
|
|
type SeitenEditorSection struct {
|
|
Key string
|
|
Label string
|
|
HTML string
|
|
}
|
|
|
|
type SeitenEditorSelected struct {
|
|
Key string
|
|
Title string
|
|
URL string
|
|
Description string
|
|
Keywords string
|
|
Sections []SeitenEditorSection
|
|
}
|
|
|
|
func (p *SeitenEditPage) Setup(router *router.Router[*core.RequestEvent], ia pagemodels.IApp, engine *templating.Engine) error {
|
|
app := ia.Core()
|
|
rg := router.Group(URL_SEITEN_EDITOR)
|
|
rg.BindFunc(middleware.IsAdminOrEditor())
|
|
rg.GET("", p.GET(engine, app))
|
|
rg.GET(URL_SEITEN_EDITOR_FORM, p.GETForm(engine, app))
|
|
rg.POST(URL_SEITEN_EDITOR_SAVE, p.POSTSave(engine, ia))
|
|
return nil
|
|
}
|
|
|
|
func (p *SeitenEditPage) GET(engine *templating.Engine, app core.App) HandleFunc {
|
|
return func(e *core.RequestEvent) error {
|
|
data, err := seitenEditorData(e, app)
|
|
if err != nil {
|
|
return engine.Response500(e, err, nil)
|
|
}
|
|
return engine.Response200(e, p.Template, data, p.Layout)
|
|
}
|
|
}
|
|
|
|
func (p *SeitenEditPage) GETForm(engine *templating.Engine, app core.App) HandleFunc {
|
|
return func(e *core.RequestEvent) error {
|
|
data, err := seitenEditorData(e, app)
|
|
if err != nil {
|
|
return engine.Response500(e, err, nil)
|
|
}
|
|
return engine.Response200(e, TEMPLATE_SEITEN_FORM, data, LAYOUT_FRAGMENT)
|
|
}
|
|
}
|
|
|
|
func (p *SeitenEditPage) POSTSave(engine *templating.Engine, ia pagemodels.IApp) HandleFunc {
|
|
return func(e *core.RequestEvent) error {
|
|
app := ia.Core()
|
|
req := templating.NewRequest(e)
|
|
|
|
if err := e.Request.ParseForm(); err != nil {
|
|
return p.redirectError(e, "Formulardaten ungültig.")
|
|
}
|
|
|
|
csrfToken := e.Request.FormValue("csrf_token")
|
|
if err := req.CheckCSRF(csrfToken); err != nil {
|
|
return p.redirectError(e, err.Error())
|
|
}
|
|
|
|
pageKey := strings.TrimSpace(e.Request.FormValue("page_key"))
|
|
if pageKey == "" {
|
|
return p.redirectError(e, "Bitte eine Seite auswählen.")
|
|
}
|
|
|
|
title := strings.TrimSpace(e.Request.FormValue("title"))
|
|
description := strings.TrimSpace(e.Request.FormValue("description"))
|
|
keywords := strings.TrimSpace(e.Request.FormValue("keywords"))
|
|
|
|
htmlUpdates := make(map[string]string)
|
|
for key, values := range e.Request.Form {
|
|
if !strings.HasPrefix(key, "html[") || !strings.HasSuffix(key, "]") {
|
|
continue
|
|
}
|
|
htmlKey := strings.TrimSuffix(strings.TrimPrefix(key, "html["), "]")
|
|
if htmlKey == "" || len(values) == 0 {
|
|
continue
|
|
}
|
|
htmlUpdates[htmlKey] = values[0]
|
|
}
|
|
|
|
pagesCollection, err := app.FindCollectionByNameOrId(dbmodels.PAGES_TABLE)
|
|
if err != nil {
|
|
app.Logger().Error("Failed to load pages collection", "error", err)
|
|
return p.redirectError(e, "Speichern fehlgeschlagen.")
|
|
}
|
|
|
|
pageRecord, err := app.FindFirstRecordByData(pagesCollection.Id, dbmodels.KEY_FIELD, pageKey)
|
|
if err != nil || pageRecord == nil {
|
|
return p.redirectError(e, "Seite nicht gefunden.")
|
|
}
|
|
|
|
data := map[string]any{}
|
|
if existing := pageRecord.Get(dbmodels.DATA_FIELD); existing != nil {
|
|
switch value := existing.(type) {
|
|
case map[string]any:
|
|
data = value
|
|
case types.JSONRaw:
|
|
if err := json.Unmarshal(value, &data); err != nil {
|
|
app.Logger().Error("Failed to unmarshal page data", "error", err)
|
|
}
|
|
}
|
|
}
|
|
data["description"] = description
|
|
data["keywords"] = keywords
|
|
|
|
pageRecord.Set(dbmodels.TITLE_FIELD, title)
|
|
pageRecord.Set(dbmodels.DATA_FIELD, data)
|
|
|
|
htmlCollection, err := app.FindCollectionByNameOrId(dbmodels.HTML_TABLE)
|
|
if err != nil {
|
|
app.Logger().Error("Failed to load html collection", "error", err)
|
|
return p.redirectError(e, "Speichern fehlgeschlagen.")
|
|
}
|
|
|
|
if err := app.RunInTransaction(func(tx core.App) error {
|
|
if err := tx.Save(pageRecord); err != nil {
|
|
return err
|
|
}
|
|
for key, value := range htmlUpdates {
|
|
record, _ := tx.FindFirstRecordByData(htmlCollection.Id, dbmodels.KEY_FIELD, key)
|
|
if record == nil {
|
|
record = core.NewRecord(htmlCollection)
|
|
record.Set(dbmodels.KEY_FIELD, key)
|
|
}
|
|
record.Set(dbmodels.HTML_FIELD, value)
|
|
if err := tx.Save(record); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
app.Logger().Error("Failed to save page data", "page", pageKey, "error", err)
|
|
return p.redirectError(e, "Speichern fehlgeschlagen.")
|
|
}
|
|
|
|
go ia.ResetPagesCache()
|
|
go ia.ResetHtmlCache()
|
|
|
|
redirect := fmt.Sprintf("%s?key=%s&success=%s", URL_SEITEN_EDITOR, url.QueryEscape(pageKey), url.QueryEscape("Änderungen gespeichert."))
|
|
return e.Redirect(http.StatusSeeOther, redirect)
|
|
}
|
|
}
|
|
|
|
func (p *SeitenEditPage) redirectError(e *core.RequestEvent, message string) error {
|
|
redirect := fmt.Sprintf("%s?error=%s", URL_SEITEN_EDITOR, url.QueryEscape(message))
|
|
return e.Redirect(http.StatusSeeOther, redirect)
|
|
}
|
|
|
|
func seitenEditorData(e *core.RequestEvent, app core.App) (map[string]any, error) {
|
|
pages, err := seitenEditorPages(app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
files, err := dbmodels.Files_All(app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
selectedKey := strings.TrimSpace(e.Request.URL.Query().Get("key"))
|
|
if selectedKey == "" && len(pages) > 0 {
|
|
selectedKey = pages[0].Key
|
|
}
|
|
|
|
var selected *SeitenEditorSelected
|
|
if selectedKey != "" {
|
|
selected, err = seitenEditorSelected(app, selectedKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
data := map[string]any{
|
|
"pages": pages,
|
|
"selected": selected,
|
|
"selected_key": selectedKey,
|
|
"files": files,
|
|
}
|
|
|
|
if selectedKey == pagemodels.P_INDEX_NAME {
|
|
images, err := dbmodels.Images_KeyPrefix(app, "page.index.image.")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data["images_index"] = images
|
|
data["images_index_prefix"] = "page.index"
|
|
}
|
|
if selectedKey == pagemodels.P_REIHEN_NAME {
|
|
image, err := dbmodels.Images_Key(app, "page.reihen.image")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data["image_reihen"] = image
|
|
data["image_reihen_key"] = "page.reihen.image"
|
|
}
|
|
|
|
req := templating.NewRequest(e)
|
|
if req.Session() != nil {
|
|
data["csrf_token"] = req.Session().Token
|
|
} else {
|
|
data["csrf_token"] = ""
|
|
}
|
|
|
|
if msg := e.Request.URL.Query().Get("success"); msg != "" {
|
|
data["success"] = msg
|
|
}
|
|
if msg := e.Request.URL.Query().Get("error"); msg != "" {
|
|
data["error"] = msg
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func seitenEditorPages(app core.App) ([]SeitenEditorPageItem, error) {
|
|
pages, err := dbmodels.Pages_All(app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]SeitenEditorPageItem, 0, len(pages))
|
|
for _, page := range pages {
|
|
title := strings.TrimSpace(page.Title())
|
|
if title == "" {
|
|
title = page.Key()
|
|
}
|
|
items = append(items, SeitenEditorPageItem{
|
|
Key: page.Key(),
|
|
Title: title,
|
|
})
|
|
}
|
|
|
|
slices.SortFunc(items, func(a, b SeitenEditorPageItem) int {
|
|
return strings.Compare(strings.ToLower(a.Title), strings.ToLower(b.Title))
|
|
})
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func seitenEditorSelected(app core.App, key string) (*SeitenEditorSelected, error) {
|
|
page, err := dbmodels.TableByField[dbmodels.Page](app, dbmodels.PAGES_TABLE, dbmodels.KEY_FIELD, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
description := ""
|
|
keywords := ""
|
|
if data := (&page).Data(); data != nil {
|
|
if value, ok := data["description"]; ok && value != nil {
|
|
description = fmt.Sprint(value)
|
|
}
|
|
if value, ok := data["keywords"]; ok && value != nil {
|
|
keywords = fmt.Sprint(value)
|
|
}
|
|
}
|
|
|
|
sections, err := seitenEditorSections(app, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageURL := page.URL()
|
|
if pageURL == "" {
|
|
switch key {
|
|
case pagemodels.P_INDEX_NAME:
|
|
pageURL = "/"
|
|
case pagemodels.P_REIHEN_NAME:
|
|
pageURL = "/reihen/"
|
|
case pagemodels.P_DANK_NAME:
|
|
pageURL = "/redaktion/danksagungen/"
|
|
case pagemodels.P_EINFUEHRUNG_NAME:
|
|
pageURL = "/redaktion/einleitung/"
|
|
case pagemodels.P_KONTAKT_NAME:
|
|
pageURL = "/redaktion/kontakt/"
|
|
case pagemodels.P_LIT_NAME:
|
|
pageURL = "/redaktion/literatur/"
|
|
case pagemodels.P_DOK_NAME:
|
|
pageURL = "/redaktion/benutzerhinweise/"
|
|
case pagemodels.P_KABINETT_NAME:
|
|
pageURL = "/redaktion/lesekabinett/"
|
|
}
|
|
}
|
|
|
|
return &SeitenEditorSelected{
|
|
Key: key,
|
|
Title: page.Title(),
|
|
URL: pageURL,
|
|
Description: description,
|
|
Keywords: keywords,
|
|
Sections: sections,
|
|
}, nil
|
|
}
|
|
|
|
func seitenEditorSections(app core.App, key string) ([]SeitenEditorSection, error) {
|
|
prefix := "page." + key
|
|
records := make([]*dbmodels.HTML, 0)
|
|
err := app.RecordQuery(dbmodels.HTML_TABLE).
|
|
Where(dbx.NewExp(dbmodels.KEY_FIELD+" LIKE {:prefix}", dbx.Params{"prefix": prefix + "%"})).
|
|
All(&records)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sections := make([]SeitenEditorSection, 0, len(records))
|
|
for _, record := range records {
|
|
section := strings.TrimPrefix(record.Key(), prefix)
|
|
section = strings.TrimPrefix(section, pageHTMLPrefixSeparator)
|
|
label := "Inhalt"
|
|
if section != "" {
|
|
label = strings.ReplaceAll(section, "_", " ")
|
|
}
|
|
|
|
sections = append(sections, SeitenEditorSection{
|
|
Key: record.Key(),
|
|
Label: label,
|
|
HTML: seitenEditorHTMLValue(record.HTML()),
|
|
})
|
|
}
|
|
|
|
slices.SortFunc(sections, func(a, b SeitenEditorSection) int {
|
|
return strings.Compare(a.Key, b.Key)
|
|
})
|
|
|
|
return sections, nil
|
|
}
|
|
|
|
func seitenEditorHTMLValue(value any) string {
|
|
switch v := value.(type) {
|
|
case string:
|
|
return v
|
|
case []byte:
|
|
return string(v)
|
|
case types.JSONRaw:
|
|
return string(v)
|
|
default:
|
|
return fmt.Sprint(value)
|
|
}
|
|
}
|