+Abkürzungen

This commit is contained in:
Simon Martens
2026-01-12 18:57:34 +01:00
parent 696f7fe087
commit 7d7637fe13
21 changed files with 465 additions and 204 deletions

210
controllers/abkuerzungen.go Normal file
View File

@@ -0,0 +1,210 @@
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/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"golang.org/x/text/collate"
"golang.org/x/text/language"
)
const (
URL_ABKUERZUNGEN = "/abkuerzungen/"
TEMPLATE_ABKUERZUNGEN = "/abkuerzungen/"
)
func init() {
ap := &AbkuerzungenPage{
StaticPage: pagemodels.StaticPage{
Name: pagemodels.P_ABKUERZUNGEN_NAME,
URL: URL_ABKUERZUNGEN,
Template: TEMPLATE_ABKUERZUNGEN,
Layout: templating.DEFAULT_LAYOUT_NAME,
},
}
app.Register(ap)
}
type AbkuerzungenPage struct {
pagemodels.StaticPage
}
type AbkEntry struct {
Key string
Value string
}
type AbkuerzungenResult struct {
Entries []AbkEntry
}
func (p *AbkuerzungenPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
router.GET(URL_ABKUERZUNGEN, p.GET(engine, app))
rg := router.Group(URL_ABKUERZUNGEN)
rg.BindFunc(middleware.IsAdminOrEditor())
rg.POST("", p.POST(engine, app))
return nil
}
func (p *AbkuerzungenPage) GET(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
// Read abbreviations from data table
dataRecord, err := dbmodels.Data_Key(app, "abkuerzungen")
if err != nil {
return engine.Response500(e, err, nil)
}
// Parse JSON value into map
abkMap := make(map[string]string)
rawValue := dataRecord.Value()
// Convert via JSON marshal/unmarshal to handle types.JSONRaw
jsonBytes, err := json.Marshal(rawValue)
if err != nil {
app.Logger().Error("Failed to marshal abkürzungen", "error", err)
return engine.Response500(e, err, nil)
}
if err := json.Unmarshal(jsonBytes, &abkMap); err != nil {
app.Logger().Error("Failed to unmarshal abkürzungen", "error", err)
return engine.Response500(e, err, nil)
}
// Convert to sorted array
entries := []AbkEntry{}
for k, v := range abkMap {
entries = append(entries, AbkEntry{Key: k, Value: v})
}
// Sort by key (German collation)
collator := collate.New(language.German)
slices.SortFunc(entries, func(a, b AbkEntry) int {
return collator.CompareString(a.Key, b.Key)
})
data := map[string]any{
"result": &AbkuerzungenResult{Entries: entries},
}
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 engine.Response200(e, p.Template, data, p.Layout)
}
}
type abkFormEntry struct {
Key string `form:"key"`
Value string `form:"value"`
Delete string `form:"_delete"`
}
func (p *AbkuerzungenPage) POST(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
req := templating.NewRequest(e)
// Parse form manually to handle array syntax
if err := e.Request.ParseForm(); err != nil {
return p.redirectError(e, "Formulardaten ungültig.")
}
csrfToken := e.Request.FormValue("csrf_token")
// Validate CSRF
if err := req.CheckCSRF(csrfToken); err != nil {
return p.redirectError(e, err.Error())
}
// Build new map from form data
newMap := make(map[string]string)
// Parse array fields manually
form := e.Request.Form
i := 0
for {
keyField := fmt.Sprintf("abkuerzungen[%d][key]", i)
valueField := fmt.Sprintf("abkuerzungen[%d][value]", i)
deleteField := fmt.Sprintf("abkuerzungen[%d][_delete]", i)
key := form.Get(keyField)
value := form.Get(valueField)
delete := form.Get(deleteField)
// No more entries
if key == "" && value == "" {
break
}
app.Logger().Info("Form entry", "i", i, "key", key, "value", value, "delete", delete)
i++
// Skip deleted or empty entries
if delete == "true" || strings.TrimSpace(key) == "" {
continue
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
// Validate required value
if value == "" {
return p.redirectError(e, fmt.Sprintf("Abkürzung '%s' benötigt eine Beschreibung.", key))
}
// Check for duplicates
if _, exists := newMap[key]; exists {
return p.redirectError(e, fmt.Sprintf("Doppelter Schlüssel: '%s'", key))
}
newMap[key] = value
}
app.Logger().Info("Saving abkürzungen", "count", len(newMap))
// Update data record
dataRecord, err := dbmodels.Data_Key(app, "abkuerzungen")
if err != nil {
app.Logger().Error("Failed to load abkuerzungen record", "error", err)
return p.redirectError(e, "Laden der Daten fehlgeschlagen.")
}
dataRecord.Set(dbmodels.VALUE_FIELD, newMap)
if err := app.Save(dataRecord); err != nil {
app.Logger().Error("Failed to save abkuerzungen", "error", err)
return p.redirectError(e, "Speichern fehlgeschlagen.")
}
// Redirect with success message
redirect := fmt.Sprintf("%s?success=%s", URL_ABKUERZUNGEN, url.QueryEscape("Änderungen gespeichert."))
return e.Redirect(http.StatusSeeOther, redirect)
}
}
func (p *AbkuerzungenPage) redirectError(e *core.RequestEvent, message string) error {
redirect := fmt.Sprintf("%s?error=%s", URL_ABKUERZUNGEN, url.QueryEscape(message))
return e.Redirect(http.StatusSeeOther, redirect)
}