mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
+FTS5 Rebuild
This commit is contained in:
@@ -5,12 +5,14 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/exports"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/imports"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||
@@ -20,14 +22,17 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
URL_EXPORTS_ADMIN = "/redaktion/exports/"
|
||||
URL_EXPORTS_RUN = "run/"
|
||||
URL_EXPORTS_LIST = "list/"
|
||||
URL_EXPORTS_DOWNLOAD = "download/"
|
||||
URL_EXPORTS_DELETE = "delete/"
|
||||
TEMPLATE_EXPORTS = "/redaktion/exports/"
|
||||
TEMPLATE_EXPORTS_LIST = "/redaktion/exports/list/"
|
||||
LAYOUT_EXPORTS_FRAGMENT = "fragment"
|
||||
URL_EXPORTS_ADMIN = "/redaktion/exports/"
|
||||
URL_EXPORTS_RUN = "run/"
|
||||
URL_EXPORTS_LIST = "list/"
|
||||
URL_EXPORTS_DOWNLOAD = "download/"
|
||||
URL_EXPORTS_DELETE = "delete/"
|
||||
URL_EXPORTS_SETTINGS = "settings/"
|
||||
URL_EXPORTS_FTS5_REBUILD = "fts5/rebuild/"
|
||||
URL_EXPORTS_FTS5_STATUS = "fts5/status/"
|
||||
TEMPLATE_EXPORTS = "/redaktion/exports/"
|
||||
TEMPLATE_EXPORTS_LIST = "/redaktion/exports/list/"
|
||||
LAYOUT_EXPORTS_FRAGMENT = "fragment"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -56,6 +61,10 @@ func (p *ExportsAdmin) Setup(router *router.Router[*core.RequestEvent], ia pagem
|
||||
rg.POST(URL_EXPORTS_RUN, p.runHandler(appInstance))
|
||||
rg.GET(URL_EXPORTS_DOWNLOAD+"{id}", p.downloadHandler(appInstance))
|
||||
rg.POST(URL_EXPORTS_DELETE+"{id}", p.deleteHandler(appInstance))
|
||||
rg.POST(URL_EXPORTS_SETTINGS+"save/", handleSettingSave(appInstance, URL_EXPORTS_ADMIN))
|
||||
rg.POST(URL_EXPORTS_SETTINGS+"delete/", handleSettingDelete(appInstance, URL_EXPORTS_ADMIN))
|
||||
rg.POST(URL_EXPORTS_FTS5_REBUILD, p.fts5RunHandler(appInstance))
|
||||
rg.GET(URL_EXPORTS_FTS5_STATUS, p.fts5StatusHandler(appInstance))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,6 +91,12 @@ func (p *ExportsAdmin) pageHandler(engine *templating.Engine, app core.App) Hand
|
||||
if err != nil {
|
||||
return engine.Response500(e, err, nil)
|
||||
}
|
||||
if msg := popFlashSuccess(e); msg != "" {
|
||||
data["success"] = msg
|
||||
}
|
||||
if errMsg := strings.TrimSpace(e.Request.URL.Query().Get("error")); errMsg != "" {
|
||||
data["error"] = errMsg
|
||||
}
|
||||
return engine.Response200(e, TEMPLATE_EXPORTS, data, pagemodels.LAYOUT_LOGIN_PAGES)
|
||||
}
|
||||
}
|
||||
@@ -96,6 +111,61 @@ func (p *ExportsAdmin) listHandler(engine *templating.Engine, app core.App) Hand
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExportsAdmin) fts5RunHandler(app core.App) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
req := templating.NewRequest(e)
|
||||
if err := e.Request.ParseForm(); err != nil {
|
||||
return e.JSON(http.StatusBadRequest, map[string]any{"error": "Formulardaten ungueltig."})
|
||||
}
|
||||
if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil {
|
||||
return e.JSON(http.StatusUnauthorized, map[string]any{"error": err.Error()})
|
||||
}
|
||||
|
||||
status, err := imports.StartFTS5Rebuild(app, true)
|
||||
if err != nil {
|
||||
return e.JSON(http.StatusInternalServerError, map[string]any{"error": err.Error()})
|
||||
}
|
||||
|
||||
if status == "running" {
|
||||
return e.JSON(http.StatusConflict, map[string]any{"error": "FTS5-Neuaufbau läuft bereits."})
|
||||
}
|
||||
if status == "restarting" {
|
||||
return e.JSON(http.StatusOK, map[string]any{"success": true, "status": "restarting"})
|
||||
}
|
||||
|
||||
return e.JSON(http.StatusOK, map[string]any{"success": true, "status": "started"})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExportsAdmin) fts5StatusHandler(app core.App) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
status := settingString(app, "fts5_rebuild_status")
|
||||
if status == "" {
|
||||
status = "idle"
|
||||
}
|
||||
message := normalizeGermanMessage(settingString(app, "fts5_rebuild_message"))
|
||||
errMsg := normalizeGermanMessage(settingString(app, "fts5_rebuild_error"))
|
||||
done := 0
|
||||
total := 0
|
||||
if setting, err := dbmodels.Settings_Key(app, "fts5_rebuild_done"); err == nil && setting != nil {
|
||||
done = parseSettingInt(setting.Value())
|
||||
}
|
||||
if setting, err := dbmodels.Settings_Key(app, "fts5_rebuild_total"); err == nil && setting != nil {
|
||||
total = parseSettingInt(setting.Value())
|
||||
}
|
||||
lastRebuild := formatLastRebuild(app)
|
||||
|
||||
return e.JSON(http.StatusOK, map[string]any{
|
||||
"status": status,
|
||||
"message": message,
|
||||
"error": errMsg,
|
||||
"done": done,
|
||||
"total": total,
|
||||
"last_rebuild": lastRebuild,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExportsAdmin) runHandler(app core.App) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
req := templating.NewRequest(e)
|
||||
@@ -234,7 +304,7 @@ func (p *ExportsAdmin) deleteHandler(app core.App) HandleFunc {
|
||||
|
||||
status := record.GetString(dbmodels.EXPORT_STATUS_FIELD)
|
||||
if status == dbmodels.EXPORT_STATUS_RUNNING || status == dbmodels.EXPORT_STATUS_QUEUED {
|
||||
return e.JSON(http.StatusConflict, map[string]any{"error": "Export laeuft noch."})
|
||||
return e.JSON(http.StatusConflict, map[string]any{"error": "Export läuft noch."})
|
||||
}
|
||||
|
||||
exportDir, err := exports.ExportDir(app)
|
||||
@@ -264,6 +334,10 @@ func exportsData(e *core.RequestEvent, app core.App) (map[string]any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := settingsData(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data["exports"] = exportsList
|
||||
data["has_running"] = hasRunning
|
||||
@@ -271,6 +345,9 @@ func exportsData(e *core.RequestEvent, app core.App) (map[string]any, error) {
|
||||
if req.Session() != nil {
|
||||
data["csrf_token"] = req.Session().Token
|
||||
}
|
||||
for key, value := range settings {
|
||||
data[key] = value
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -335,3 +412,73 @@ func formatBytes(size int64) string {
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, units[unitIdx])
|
||||
}
|
||||
|
||||
func settingString(app core.App, key string) string {
|
||||
setting, err := dbmodels.Settings_Key(app, key)
|
||||
if err != nil || setting == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := setting.Value().(string); ok {
|
||||
return normalizeSettingString(value)
|
||||
}
|
||||
return normalizeSettingString(fmt.Sprintf("%v", setting.Value()))
|
||||
}
|
||||
|
||||
func formatLastRebuild(app core.App) string {
|
||||
setting, err := dbmodels.Settings_Key(app, "fts5_last_rebuild")
|
||||
if err != nil || setting == nil {
|
||||
return ""
|
||||
}
|
||||
if formatted, ok := formatSettingGermanDateTime(setting.Value()); ok {
|
||||
return normalizeSettingString(formatted)
|
||||
}
|
||||
if value, ok := parseSettingString(setting.Value()); ok {
|
||||
return normalizeSettingString(value)
|
||||
}
|
||||
return normalizeSettingString(fmt.Sprintf("%v", setting.Value()))
|
||||
}
|
||||
|
||||
func parseSettingInt(value any) int {
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
return int(v)
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
case string:
|
||||
if parsed, err := strconv.Atoi(v); err == nil {
|
||||
return parsed
|
||||
}
|
||||
default:
|
||||
if parsed, err := strconv.Atoi(fmt.Sprintf("%v", value)); err == nil {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func normalizeSettingString(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if len(trimmed) >= 2 && strings.HasPrefix(trimmed, "\"") && strings.HasSuffix(trimmed, "\"") {
|
||||
if unquoted, err := strconv.Unquote(trimmed); err == nil {
|
||||
return unquoted
|
||||
}
|
||||
return strings.Trim(trimmed, "\"")
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func normalizeGermanMessage(value string) string {
|
||||
replacer := strings.NewReplacer(
|
||||
"laeuft", "läuft",
|
||||
"Laeuft", "Läuft",
|
||||
"Loescht", "Löscht",
|
||||
"loescht", "löscht",
|
||||
"fuellt", "füllt",
|
||||
"Fuellt", "Füllt",
|
||||
"Eintraegen", "Einträgen",
|
||||
"eintraegen", "einträgen",
|
||||
)
|
||||
return replacer.Replace(value)
|
||||
}
|
||||
|
||||
255
controllers/settings_admin.go
Normal file
255
controllers/settings_admin.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/imports"
|
||||
"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"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
const (
|
||||
URL_SETTINGS_ADMIN = "/redaktion/settings/"
|
||||
URL_SETTINGS_SAVE = "save/"
|
||||
URL_SETTINGS_DELETE = "delete/"
|
||||
URL_SETTINGS_FTS5_REBUILD = "fts5/rebuild/"
|
||||
TEMPLATE_SETTINGS = "/redaktion/settings/"
|
||||
)
|
||||
|
||||
func init() {
|
||||
app.Register(&SettingsAdmin{})
|
||||
}
|
||||
|
||||
type SettingsAdmin struct{}
|
||||
|
||||
func (p *SettingsAdmin) Up(ia pagemodels.IApp, engine *templating.Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SettingsAdmin) Down(ia pagemodels.IApp, engine *templating.Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SettingsAdmin) Setup(router *router.Router[*core.RequestEvent], ia pagemodels.IApp, engine *templating.Engine) error {
|
||||
appInstance := ia.Core()
|
||||
rg := router.Group(URL_SETTINGS_ADMIN)
|
||||
rg.BindFunc(middleware.Authenticated(appInstance))
|
||||
rg.BindFunc(middleware.IsAdmin())
|
||||
rg.GET("", p.redirectHandler())
|
||||
rg.POST(URL_SETTINGS_SAVE, handleSettingSave(appInstance, URL_SETTINGS_ADMIN))
|
||||
rg.POST(URL_SETTINGS_DELETE, handleSettingDelete(appInstance, URL_SETTINGS_ADMIN))
|
||||
rg.POST(URL_SETTINGS_FTS5_REBUILD, handleFTS5Rebuild(appInstance, URL_SETTINGS_ADMIN))
|
||||
return nil
|
||||
}
|
||||
|
||||
type settingView struct {
|
||||
Key string
|
||||
Value string
|
||||
Updated types.DateTime
|
||||
}
|
||||
|
||||
func (p *SettingsAdmin) redirectHandler() HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
return e.Redirect(http.StatusSeeOther, URL_EXPORTS_ADMIN)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSettingSave(app core.App, redirectBase string) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
req := templating.NewRequest(e)
|
||||
if err := e.Request.ParseForm(); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, "Formulardaten ungueltig.")
|
||||
}
|
||||
if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, err.Error())
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(e.Request.FormValue("key"))
|
||||
if key == "" {
|
||||
return redirectSettingsError(e, redirectBase, "Schluessel darf nicht leer sein.")
|
||||
}
|
||||
|
||||
valueRaw := strings.TrimSpace(e.Request.FormValue("value"))
|
||||
value := parseSettingValue(valueRaw)
|
||||
|
||||
collection, err := app.FindCollectionByNameOrId(dbmodels.SETTINGS_TABLE)
|
||||
if err != nil {
|
||||
return redirectSettingsError(e, redirectBase, "Einstellungen-Tabelle nicht verfuegbar.")
|
||||
}
|
||||
|
||||
var record *core.Record
|
||||
existing, err := dbmodels.Settings_Key(app, key)
|
||||
if err != nil {
|
||||
if !isRecordNotFound(err) {
|
||||
return redirectSettingsError(e, redirectBase, "Konnte Einstellung nicht laden.")
|
||||
}
|
||||
} else if existing != nil {
|
||||
record = existing.ProxyRecord()
|
||||
}
|
||||
|
||||
if record == nil {
|
||||
record = core.NewRecord(collection)
|
||||
}
|
||||
|
||||
record.Set(dbmodels.KEY_FIELD, key)
|
||||
record.Set(dbmodels.VALUE_FIELD, value)
|
||||
if err := app.Save(record); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, "Einstellung konnte nicht gespeichert werden.")
|
||||
}
|
||||
|
||||
setFlashSuccess(e, "Einstellung gespeichert.")
|
||||
return e.Redirect(http.StatusSeeOther, redirectBase)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSettingDelete(app core.App, redirectBase string) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
req := templating.NewRequest(e)
|
||||
if err := e.Request.ParseForm(); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, "Formulardaten ungueltig.")
|
||||
}
|
||||
if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, err.Error())
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(e.Request.FormValue("key"))
|
||||
if key == "" {
|
||||
return redirectSettingsError(e, redirectBase, "Schluessel darf nicht leer sein.")
|
||||
}
|
||||
|
||||
record, err := dbmodels.Settings_Key(app, key)
|
||||
if err != nil {
|
||||
if isRecordNotFound(err) {
|
||||
setFlashSuccess(e, "Einstellung entfernt.")
|
||||
return e.Redirect(http.StatusSeeOther, redirectBase)
|
||||
}
|
||||
return redirectSettingsError(e, redirectBase, "Einstellung konnte nicht geladen werden.")
|
||||
}
|
||||
|
||||
if err := app.Delete(record.ProxyRecord()); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, "Einstellung konnte nicht entfernt werden.")
|
||||
}
|
||||
|
||||
setFlashSuccess(e, "Einstellung entfernt.")
|
||||
return e.Redirect(http.StatusSeeOther, redirectBase)
|
||||
}
|
||||
}
|
||||
|
||||
func handleFTS5Rebuild(app core.App, redirectBase string) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
req := templating.NewRequest(e)
|
||||
if err := e.Request.ParseForm(); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, "Formulardaten ungueltig.")
|
||||
}
|
||||
if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil {
|
||||
return redirectSettingsError(e, redirectBase, err.Error())
|
||||
}
|
||||
|
||||
status, err := imports.StartFTS5Rebuild(app, true)
|
||||
if err != nil {
|
||||
return redirectSettingsError(e, redirectBase, err.Error())
|
||||
}
|
||||
if status == "running" {
|
||||
return redirectSettingsError(e, redirectBase, "FTS5-Neuaufbau läuft bereits.")
|
||||
}
|
||||
if status == "restarting" {
|
||||
setFlashSuccess(e, "FTS5-Neuaufbau wird neu gestartet.")
|
||||
return e.Redirect(http.StatusSeeOther, redirectBase)
|
||||
}
|
||||
|
||||
setFlashSuccess(e, "FTS5-Neuaufbau gestartet.")
|
||||
return e.Redirect(http.StatusSeeOther, redirectBase)
|
||||
}
|
||||
}
|
||||
|
||||
func settingsData(app core.App) (map[string]any, error) {
|
||||
settings, err := dbmodels.Settings_All(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]settingView, 0, len(settings))
|
||||
for _, setting := range settings {
|
||||
if setting == nil {
|
||||
continue
|
||||
}
|
||||
list = append(list, settingView{
|
||||
Key: setting.Key(),
|
||||
Value: formatSettingValue(setting.Value()),
|
||||
Updated: setting.GetDateTime(dbmodels.UPDATED_FIELD),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Key < list[j].Key
|
||||
})
|
||||
|
||||
var lastRebuild string
|
||||
var lastRebuildDT types.DateTime
|
||||
if setting, err := dbmodels.Settings_Key(app, "fts5_last_rebuild"); err == nil && setting != nil {
|
||||
if dt, ok := parseSettingDateTime(setting.Value()); ok {
|
||||
lastRebuildDT = dt
|
||||
lastRebuild = formatSettingValue(dt)
|
||||
} else {
|
||||
lastRebuild = formatSettingValue(setting.Value())
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"settings": list,
|
||||
"fts5_last_rebuild": lastRebuild,
|
||||
"fts5_last_rebuild_dt": lastRebuildDT,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseSettingValue(valueRaw string) any {
|
||||
if valueRaw == "" {
|
||||
return ""
|
||||
}
|
||||
var parsed any
|
||||
if err := json.Unmarshal([]byte(valueRaw), &parsed); err == nil {
|
||||
return parsed
|
||||
}
|
||||
return valueRaw
|
||||
}
|
||||
|
||||
func formatSettingValue(value any) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
if formatted, ok := formatSettingDateTime(value); ok {
|
||||
return formatted
|
||||
}
|
||||
if str, ok := parseSettingString(value); ok && str != "" {
|
||||
return str
|
||||
}
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func isRecordNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
msg := err.Error()
|
||||
return strings.Contains(msg, "no rows in result set") || strings.Contains(msg, "not found")
|
||||
}
|
||||
|
||||
func redirectSettingsError(e *core.RequestEvent, baseURL, message string) error {
|
||||
redirect := fmt.Sprintf("%s?error=%s", baseURL, url.QueryEscape(message))
|
||||
return e.Redirect(http.StatusSeeOther, redirect)
|
||||
}
|
||||
78
controllers/settings_helpers.go
Normal file
78
controllers/settings_helpers.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/functions"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func parseSettingString(value any) (string, bool) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v), true
|
||||
case []byte:
|
||||
if len(v) == 0 {
|
||||
return "", false
|
||||
}
|
||||
var parsed string
|
||||
if err := json.Unmarshal(v, &parsed); err == nil {
|
||||
return strings.TrimSpace(parsed), true
|
||||
}
|
||||
return strings.TrimSpace(string(v)), true
|
||||
case json.RawMessage:
|
||||
if len(v) == 0 {
|
||||
return "", false
|
||||
}
|
||||
var parsed string
|
||||
if err := json.Unmarshal(v, &parsed); err == nil {
|
||||
return strings.TrimSpace(parsed), true
|
||||
}
|
||||
return strings.TrimSpace(string(v)), true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func formatSettingDateTime(value any) (string, bool) {
|
||||
if value == nil {
|
||||
return "", false
|
||||
}
|
||||
if dt, err := types.ParseDateTime(value); err == nil && !dt.IsZero() {
|
||||
return functions.GermanShortDateTime(dt), true
|
||||
}
|
||||
if raw, ok := parseSettingString(value); ok {
|
||||
clean := strings.Trim(raw, "\"")
|
||||
if dt, err := types.ParseDateTime(clean); err == nil && !dt.IsZero() {
|
||||
return functions.GermanShortDateTime(dt), true
|
||||
}
|
||||
if clean != "" {
|
||||
return clean, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func parseSettingDateTime(value any) (types.DateTime, bool) {
|
||||
if value == nil {
|
||||
return types.DateTime{}, false
|
||||
}
|
||||
if dt, err := types.ParseDateTime(value); err == nil && !dt.IsZero() {
|
||||
return dt, true
|
||||
}
|
||||
if raw, ok := parseSettingString(value); ok {
|
||||
clean := strings.Trim(raw, "\"")
|
||||
if dt, err := types.ParseDateTime(clean); err == nil && !dt.IsZero() {
|
||||
return dt, true
|
||||
}
|
||||
}
|
||||
return types.DateTime{}, false
|
||||
}
|
||||
|
||||
func formatSettingGermanDateTime(value any) (string, bool) {
|
||||
if dt, ok := parseSettingDateTime(value); ok {
|
||||
return functions.GermanDate(dt) + " " + functions.GermanTime(dt), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
Reference in New Issue
Block a user