package handler import ( "log/slog" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/denisovdennis/autohero/internal/storage" ) // DailyTaskHandler serves daily/weekly task API endpoints. type DailyTaskHandler struct { taskStore *storage.DailyTaskStore heroStore *storage.HeroStore logger *slog.Logger } // NewDailyTaskHandler creates a new DailyTaskHandler. func NewDailyTaskHandler(taskStore *storage.DailyTaskStore, heroStore *storage.HeroStore, logger *slog.Logger) *DailyTaskHandler { return &DailyTaskHandler{ taskStore: taskStore, heroStore: heroStore, logger: logger, } } // ListHeroTasks returns current daily and weekly tasks with progress. // GET /api/v1/hero/tasks func (h *DailyTaskHandler) ListHeroTasks(w http.ResponseWriter, r *http.Request) { telegramID, ok := resolveTelegramID(r) if !ok { writeJSON(w, http.StatusBadRequest, map[string]string{ "error": "missing telegramId", }) return } hero, err := h.heroStore.GetByTelegramID(r.Context(), telegramID) if err != nil { h.logger.Error("failed to get hero for tasks", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": "failed to load hero", }) return } if hero == nil { writeJSON(w, http.StatusNotFound, map[string]string{ "error": "hero not found", }) return } // Lazily create task rows for the current period. now := time.Now() if err := h.taskStore.EnsureHeroTasks(r.Context(), hero.ID, now); err != nil { h.logger.Error("failed to ensure hero tasks", "hero_id", hero.ID, "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": "failed to initialize tasks", }) return } tasks, err := h.taskStore.ListHeroTasks(r.Context(), hero.ID) if err != nil { h.logger.Error("failed to list hero tasks", "hero_id", hero.ID, "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": "failed to load tasks", }) return } writeJSON(w, http.StatusOK, tasks) } // ClaimTask claims a completed task's reward. // POST /api/v1/hero/tasks/{taskId}/claim func (h *DailyTaskHandler) ClaimTask(w http.ResponseWriter, r *http.Request) { telegramID, ok := resolveTelegramID(r) if !ok { writeJSON(w, http.StatusBadRequest, map[string]string{ "error": "missing telegramId", }) return } taskID := chi.URLParam(r, "taskId") if taskID == "" { writeJSON(w, http.StatusBadRequest, map[string]string{ "error": "taskId is required", }) return } hero, err := h.heroStore.GetByTelegramID(r.Context(), telegramID) if err != nil { h.logger.Error("failed to get hero for task claim", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": "failed to load hero", }) return } if hero == nil { writeJSON(w, http.StatusNotFound, map[string]string{ "error": "hero not found", }) return } reward, err := h.taskStore.ClaimTask(r.Context(), hero.ID, taskID) if err != nil { h.logger.Warn("failed to claim task", "hero_id", hero.ID, "task_id", taskID, "error", err) writeJSON(w, http.StatusBadRequest, map[string]string{ "error": err.Error(), }) return } // Apply reward to hero. switch reward.RewardType { case "gold": hero.Gold += int64(reward.RewardAmount) case "potion": hero.Potions += reward.RewardAmount } if err := h.heroStore.Save(r.Context(), hero); err != nil { h.logger.Error("failed to save hero after task claim", "hero_id", hero.ID, "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": "failed to save hero", }) return } h.logger.Info("task reward claimed", "hero_id", hero.ID, "task_id", taskID, "reward_type", reward.RewardType, "reward_amount", reward.RewardAmount) now := time.Now() hero.RefreshDerivedCombatStats(now) writeJSON(w, http.StatusOK, map[string]any{ "reward": reward, "hero": hero, }) }