Files
zeitkort-web-golang/main.go
T
2026-06-18 21:41:47 +01:00

153 lines
4.4 KiB
Go

package main
import (
"flag"
"log"
"net/http"
"os"
"sync"
"time"
"zeitkort-web/repository"
)
// Version is set at build time via -ldflags.
var Version = "dev"
const resetInterval = 10 * time.Minute
// Server holds shared dependencies.
type Server struct {
mu sync.Mutex // guards Cfg/resetAt when Settings are saved/reset
Cfg Config
initialCfg Config
resetAt time.Time
repo repository.Repository
info *log.Logger
errl *log.Logger
}
func main() {
cfgPath := flag.String("config", "config.json", "path to config.json")
flag.Parse()
info := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
errl := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
cfg, err := loadConfig(*cfgPath)
if err != nil {
errl.Fatalf("config: %v", err)
}
repo := repository.NewMemoryRepository()
if err := repo.Migrate(); err != nil {
errl.Fatalf("repository: %v", err)
}
s := &Server{
Cfg: cfg,
initialCfg: cfg,
resetAt: time.Now().Add(resetInterval),
repo: repo,
info: info,
errl: errl,
}
if err := parseTemplates(); err != nil {
errl.Fatalf("templates: %v", err)
}
go s.resetLoop(resetInterval)
info.Printf("zeitkort-web %s listening on %s (web-only POC; in-memory state resets every %s)", Version, cfg.ListenAddr, resetInterval)
if err := http.ListenAndServe(cfg.ListenAddr, s.routes()); err != nil {
errl.Fatal(err)
}
}
func (s *Server) nextResetAt() time.Time {
s.mu.Lock()
defer s.mu.Unlock()
return s.resetAt
}
func (s *Server) resetLoop(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
if resetter, ok := s.repo.(interface{ Reset() }); ok {
resetter.Reset()
}
s.mu.Lock()
s.Cfg = s.initialCfg
s.resetAt = time.Now().Add(interval)
s.mu.Unlock()
s.info.Printf("in-memory proof-of-concept state reset after %s", interval)
}
}
func (s *Server) routes() http.Handler {
mux := http.NewServeMux()
// Static assets (css + the one small timer script), embedded in the binary.
mux.Handle("GET /static/", staticHandler())
// Main working area.
mux.HandleFunc("GET /", s.handleIndex)
mux.HandleFunc("GET /main", s.handleMain)
// Companies.
mux.HandleFunc("GET /companies/new", s.handleCompanyForm)
mux.HandleFunc("GET /companies/{id}/edit", s.handleCompanyForm)
mux.HandleFunc("POST /companies", s.handleCompanySave)
mux.HandleFunc("POST /companies/{id}", s.handleCompanySave)
mux.HandleFunc("POST /companies/{id}/delete", s.handleCompanyDelete)
// Assignments.
mux.HandleFunc("GET /assignments/new", s.handleAssignmentForm)
mux.HandleFunc("GET /assignments/{id}/edit", s.handleAssignmentForm)
mux.HandleFunc("POST /assignments", s.handleAssignmentSave)
mux.HandleFunc("POST /assignments/{id}", s.handleAssignmentSave)
mux.HandleFunc("POST /assignments/{id}/delete", s.handleAssignmentDelete)
// Tasks.
mux.HandleFunc("GET /tasks/new", s.handleTaskNewForm)
mux.HandleFunc("GET /tasks/{id}/edit", s.handleTaskEditForm)
mux.HandleFunc("POST /tasks", s.handleTaskCreate)
mux.HandleFunc("POST /tasks/{id}", s.handleTaskUpdate)
mux.HandleFunc("POST /tasks/{id}/delete", s.handleTaskDelete)
// Work sessions.
mux.HandleFunc("POST /sessions/start", s.handleSessionStart)
mux.HandleFunc("POST /sessions/{id}/stop", s.handleSessionStop)
mux.HandleFunc("GET /sessions/new", s.handleSessionForm)
mux.HandleFunc("GET /sessions/{id}/edit", s.handleSessionForm)
mux.HandleFunc("POST /sessions/manual", s.handleSessionSave)
mux.HandleFunc("POST /sessions/{id}", s.handleSessionSave)
mux.HandleFunc("POST /sessions/{id}/delete", s.handleSessionDelete)
// Invoice.
mux.HandleFunc("GET /invoice", s.handleInvoiceForm)
mux.HandleFunc("GET /invoice/line", s.handleInvoiceLine)
mux.HandleFunc("POST /invoice", s.handleInvoiceGenerate)
// Settings and POC information.
mux.HandleFunc("GET /settings", s.handleSettings)
mux.HandleFunc("POST /settings", s.handleSettingsSave)
mux.HandleFunc("GET /about", s.handleAbout)
// Used by htmx to remove a row from the page (returns empty body).
mux.HandleFunc("GET /void", func(w http.ResponseWriter, r *http.Request) {})
return s.logging(mux)
}
func (s *Server) logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
s.info.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond))
})
}