153 lines
4.4 KiB
Go
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))
|
|
})
|
|
}
|