package main import ( "context" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "Engine/internal/database" "Engine/internal/handler" "Engine/internal/service" ) func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) dbPath := os.Getenv("DB_PATH") if dbPath == "" { dbPath = "fpa.db" } db, err := database.Connect(context.Background(), dbPath) if err != nil { logger.Error("failed to open database", "error", err) os.Exit(1) } defer db.Close() if err := database.Migrate(db); err != nil { logger.Error("migration failed", "error", err) os.Exit(1) } budgetRepo := database.NewBudgetRepo(db) actualsRepo := database.NewActualsRepo(db) budgetSvc := service.NewBudgetService(budgetRepo) varianceSvc := service.NewVarianceService(budgetRepo, actualsRepo) budgetH := handler.NewBudgetHandler(budgetSvc) actualsH := handler.NewActualsHandler(actualsRepo) varianceH := handler.NewVarianceHandler(varianceSvc) mux := http.NewServeMux() mux.HandleFunc("POST /api/v1/budgets", budgetH.Create) mux.HandleFunc("PUT /api/v1/budgets/{id}", budgetH.Update) mux.HandleFunc("DELETE /api/v1/budgets/{id}", budgetH.Delete) mux.HandleFunc("POST /api/v1/actuals/ingest", actualsH.Ingest) mux.HandleFunc("GET /api/v1/variance", varianceH.Report) mux.HandleFunc("GET /api/v1/variance/alerts", varianceH.Alerts) mux.HandleFunc("GET /api/v1/health", func(w http.ResponseWriter, r *http.Request) { if err := db.PingContext(r.Context()); err != nil { http.Error(w, `{"error":"db unhealthy"}`, http.StatusServiceUnavailable) return } w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) }) port := os.Getenv("PORT") if port == "" { port = "8080" } srv := &http.Server{ Addr: ":" + port, Handler: mux, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) go func() { logger.Info("server starting", "port", port, "db", dbPath) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Error("server error", "error", err) os.Exit(1) } }() <-quit logger.Info("shutting down gracefully") ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() srv.Shutdown(ctx) }