summaryrefslogtreecommitdiff
path: root/cmd/dashboard/main.go
blob: 6e013e8f5877879d35eae71dac0e30a046e08161 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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 planToEatClient api.PlanToEatAPI
	if cfg.HasPlanToEat() {
		planToEatClient = api.NewPlanToEatClient(cfg.PlanToEatAPIKey)
	}

	// Initialize handlers
	h := handlers.New(db, todoistClient, trelloClient, 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/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/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")
}