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

View File

@@ -50,11 +50,6 @@ func (p *AlmanachPage) GET(engine *templating.Engine, app core.App) HandleFunc {
data["result"] = result
data["filters"] = filters
abbrs, err := pagemodels.GetAbks(app)
if err == nil {
data["abbrs"] = abbrs
}
return engine.Response200(e, p.Template, data)
}
}

View File

@@ -66,11 +66,6 @@ func (p *AlmanachEditPage) GET(engine *templating.Engine, app core.App) HandleFu
data["agent_relations"] = dbmodels.AGENT_RELATIONS
data["series_relations"] = dbmodels.SERIES_RELATIONS
abbrs, err := pagemodels.GetAbks(app)
if err == nil {
data["abbrs"] = abbrs
}
if msg := e.Request.URL.Query().Get("saved_message"); msg != "" {
data["success"] = msg
}
@@ -84,7 +79,7 @@ type AlmanachEditResult struct {
PrevByID *dbmodels.Entry
NextByTitle *dbmodels.Entry
PrevByTitle *dbmodels.Entry
User *dbmodels.User
User *dbmodels.User
AlmanachResult
}

View File

@@ -83,11 +83,6 @@ func (p *AlmanachNewPage) GET(engine *templating.Engine, app core.App) HandleFun
data["series_relations"] = dbmodels.SERIES_RELATIONS
data["is_new"] = true
abbrs, err := pagemodels.GetAbks(app)
if err == nil {
data["abbrs"] = abbrs
}
return engine.Response200(e, p.Template, data, p.Layout)
}
}
@@ -165,7 +160,7 @@ func (p *AlmanachNewPage) POSTSave(engine *templating.Engine, app core.App) Hand
redirect := "/"
if entry != nil {
redirect = "/almanach/" + strconv.Itoa(entry.MusenalmID()) + "/edit?saved_message=" + url.QueryEscape("Änderungen gespeichert.")
redirect = "/almanach/" + strconv.Itoa(entry.MusenalmID()) + "/edit?saved_message=" + url.QueryEscape("Änderungen gespeichert.")
}
return e.JSON(http.StatusOK, map[string]any{

View File

@@ -34,18 +34,13 @@ type BeitragPage struct {
func (p *BeitragPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
router.GET(p.URL, func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
data := make(map[string]interface{})
data := make(map[string]any)
result, err := NewBeitragResult(app, id)
if err != nil {
engine.Response404(e, err, nil)
}
data["result"] = result
abbrs, err := pagemodels.GetAbks(app)
if err == nil {
data["abbrs"] = abbrs
}
return engine.Response200(e, p.Template, data)
})