The Go server now runs sync -> infer -> flush-cache by itself at the times in SYNC_SCHEDULE (comma-separated HH:MM, default 04:00, "off" disables), read in SYNC_TZ (default Europe/Prague). Nothing runs at startup; an invalid value stops the server with an error. - internal/scheduler: time-of-day parsing, Next, and the run loop - banksync.Runner: one lock shared by /sync-bank and the scheduler so runs never overlap, plus the status of the last run - footer shows the last sync since start and the next scheduled run - binary embeds time/tzdata because the Alpine image has no zone data Deliberately not implemented in the legacy Python app, which is being phased out; recorded in CLAUDE.md under "Deliberately Go-only features". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.6 KiB
Go
83 lines
2.6 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"fuj-management/go/internal/config"
|
|
"fuj-management/go/internal/services/membership"
|
|
"fuj-management/go/internal/web/api"
|
|
"fuj-management/go/internal/web/middleware"
|
|
"io"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
)
|
|
|
|
// BuildInfo carries the linker-injected build metadata.
|
|
type BuildInfo struct {
|
|
Version string
|
|
Commit string
|
|
BuildDate string
|
|
}
|
|
|
|
// ActionHandlers holds function closures for side-effectful operations that
|
|
// require dependencies (fio, sheets) not present on the core API handler.
|
|
type ActionHandlers struct {
|
|
// BankSync runs sync+infer and writes a human-readable log to out.
|
|
// nil disables the /sync-bank action (renders an error instead).
|
|
BankSync func(ctx context.Context, out io.Writer) error
|
|
// SyncInfo returns a one-line summary of the last and next bank sync for
|
|
// the page footer. nil or an empty result shows nothing.
|
|
SyncInfo func() string
|
|
}
|
|
|
|
// Run registers routes and starts the HTTP server on addr.
|
|
func Run(logger *slog.Logger, addr string, build BuildInfo, sources membership.Sources, cfg config.Config, actions ActionHandlers) error {
|
|
renderer, err := NewRenderer()
|
|
if err != nil {
|
|
return fmt.Errorf("init templates: %w", err)
|
|
}
|
|
|
|
ah := &api.Handler{
|
|
BuildVersion: build.Version,
|
|
BuildCommit: build.Commit,
|
|
BuildDate: build.BuildDate,
|
|
Sources: sources,
|
|
Config: cfg,
|
|
Logger: logger,
|
|
}
|
|
hh := NewHTMLHandler(renderer, build, ah, actions)
|
|
|
|
staticSubFS, err := fs.Sub(staticFS, "static")
|
|
if err != nil {
|
|
return fmt.Errorf("static subfs: %w", err)
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
// HTML routes
|
|
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, "/adults", http.StatusFound)
|
|
})
|
|
mux.HandleFunc("GET /adults", hh.ServeAdults)
|
|
mux.HandleFunc("GET /juniors", hh.ServeJuniors)
|
|
mux.HandleFunc("GET /payments", hh.ServePayments)
|
|
mux.HandleFunc("GET /sync-bank", hh.ServeSync)
|
|
mux.HandleFunc("GET /flush-cache", hh.ServeFlushCacheGET)
|
|
mux.HandleFunc("POST /flush-cache", hh.ServeFlushCachePOST)
|
|
mux.HandleFunc("GET /qr", hh.ServeQR)
|
|
|
|
// Static files
|
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServerFS(staticSubFS)))
|
|
|
|
// JSON API routes
|
|
mux.HandleFunc("GET /api/version", ah.ServeVersion)
|
|
mux.HandleFunc("GET /version", ah.ServeVersion)
|
|
mux.HandleFunc("GET /api/adults", ah.ServeAdults)
|
|
mux.HandleFunc("GET /api/juniors", ah.ServeJuniors)
|
|
mux.HandleFunc("GET /api/payments", ah.ServePayments)
|
|
|
|
logger.Info("starting server", "addr", addr)
|
|
return http.ListenAndServe(addr, middleware.RequestTimer(logger, mux))
|
|
}
|