package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/joho/godotenv" "task-dashboard/internal/api" "task-dashboard/internal/config" "task-dashboard/internal/handlers" "task-dashboard/internal/store" ) func main() { // Load .env file (ignore error if file doesn't exist - env vars might be set directly) _ = godotenv.Load() // Load configuration cfg, err := config.Load() if err != nil { log.Fatalf("Failed to load configuration: %v", err) } // Initialize database db, err := store.New(cfg.DatabasePath) if err != nil { log.Fatalf("Failed to initialize database: %v", err) } defer db.Close() // Initialize API clients todoistClient := api.NewTodoistClient(cfg.TodoistAPIKey) trelloClient := api.NewTrelloClient(cfg.TrelloAPIKey, cfg.TrelloToken) var obsidianClient api.ObsidianAPI if cfg.HasObsidian() { obsidianClient = api.NewObsidianClient(cfg.ObsidianVaultPath) } var planToEatClient api.PlanToEatAPI if cfg.HasPlanToEat() { planToEatClient = api.NewPlanToEatClient(cfg.PlanToEatAPIKey) } // Initialize handlers h := handlers.New(db, todoistClient, trelloClient, obsidianClient, planToEatClient, cfg) tabsHandler := handlers.NewTabsHandler(db, cfg.TemplateDir) // Set up router r := chi.NewRouter() // Middleware r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(middleware.Timeout(60 * time.Second)) // Routes r.Get("/", h.HandleDashboard) r.Post("/api/refresh", h.HandleRefresh) r.Get("/api/tasks", h.HandleGetTasks) r.Get("/api/notes", h.HandleGetNotes) r.Get("/api/meals", h.HandleGetMeals) r.Get("/api/boards", h.HandleGetBoards) // Tab routes for HTMX (using new TabsHandler) r.Get("/tabs/tasks", tabsHandler.HandleTasks) r.Get("/tabs/planning", tabsHandler.HandlePlanning) r.Get("/tabs/notes", tabsHandler.HandleNotes) r.Get("/tabs/meals", tabsHandler.HandleMeals) r.Post("/tabs/refresh", h.HandleRefreshTab) // Trello card operations r.Post("/cards", h.HandleCreateCard) r.Post("/cards/complete", h.HandleCompleteCard) // Todoist task operations r.Post("/tasks", h.HandleCreateTask) r.Post("/tasks/complete", h.HandleCompleteTask) // Unified task completion (for Tasks tab Atoms) r.Post("/complete-atom", h.HandleCompleteAtom) // Unified Quick Add (for Tasks tab) r.Post("/unified-add", h.HandleUnifiedAdd) r.Get("/partials/lists", h.HandleGetListsOptions) // Serve static files fileServer := http.FileServer(http.Dir("web/static")) r.Handle("/static/*", http.StripPrefix("/static/", fileServer)) // Start server addr := ":" + cfg.Port srv := &http.Server{ Addr: addr, Handler: r, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // Graceful shutdown go func() { log.Printf("Starting server on http://localhost%s", addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed: %v", err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") // Graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } log.Println("Server exited") }