initial commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user