package api import ( "context" "encoding/json" "io/fs" "log" "net/http" "strconv" "strings" "github.com/jackc/pgx/v5/pgxpool" "energy-frontend/internal/config" ) func NewRouter(cfg config.Config, frontendFS fs.FS) http.Handler { pool, err := pgxpool.New(context.Background(), cfg.DB.DSN) if err != nil { log.Fatalf("db pool: %v", err) } mux := http.NewServeMux() auth := newAuthHandler(cfg.Auth) mux.HandleFunc("POST /api/login", auth.loginHandler) mux.HandleFunc("POST /api/logout", auth.logoutHandler) protected := http.NewServeMux() protected.HandleFunc("GET /api/auth/check", auth.checkHandler) protected.HandleFunc("GET /api/bars", func(w http.ResponseWriter, r *http.Request) { period := r.URL.Query().Get("period") if period == "" { period = "day" } count, err := strconv.Atoi(r.URL.Query().Get("count")) if err != nil || count <= 0 { count = periodDefaults[period] } bars, err := getBars(r.Context(), pool, period, count) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]interface{}{"bars": bars}) }) protected.HandleFunc("GET /api/power", func(w http.ResponseWriter, r *http.Request) { tr := r.URL.Query().Get("range") if tr == "" { tr = "24h" } pts, err := getPower(r.Context(), pool, tr) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]interface{}{"points": pts}) }) protected.HandleFunc("GET /api/battery", func(w http.ResponseWriter, r *http.Request) { tr := r.URL.Query().Get("range") if tr == "" { tr = "24h" } pts, err := getBattery(r.Context(), pool, tr) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]interface{}{"points": pts}) }) mux.Handle("/api/", auth.middleware(protected)) if frontendFS != nil { mux.Handle("/", spaHandler(frontendFS)) } return corsMiddleware(mux) } func spaHandler(frontendFS fs.FS) http.Handler { fileServer := http.FileServer(http.FS(frontendFS)) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/") if path == "" { path = "index.html" } if _, err := fs.Stat(frontendFS, path); err == nil { fileServer.ServeHTTP(w, r) return } r.URL.Path = "/" fileServer.ServeHTTP(w, r) }) } func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.Header().Set("Access-Control-Allow-Credentials", "true") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v) }