| 139 | } |
| 140 | |
| 141 | func handleTaskByID(dp *DataProvider) http.HandlerFunc { |
| 142 | return func(w http.ResponseWriter, r *http.Request) { |
| 143 | dp := effectiveDP(r, dp) |
| 144 | taskID := r.PathValue("id") |
| 145 | if taskID == "" { |
| 146 | http.Error(w, "task ID is required", http.StatusBadRequest) |
| 147 | return |
| 148 | } |
| 149 | |
| 150 | tasks, err := dp.GetTasks() |
| 151 | if err != nil { |
| 152 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 153 | return |
| 154 | } |
| 155 | |
| 156 | // Find task by ID |
| 157 | var foundTask *model.Task |
| 158 | for _, task := range tasks { |
| 159 | if task.ID == taskID { |
| 160 | foundTask = task |
| 161 | break |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | if foundTask == nil { |
| 166 | http.Error(w, "task not found", http.StatusNotFound) |
| 167 | return |
| 168 | } |
| 169 | |
| 170 | // Return task with body and worklog metadata |
| 171 | detail := TaskDetail{ |
| 172 | Task: foundTask, |
| 173 | Body: foundTask.Body, |
| 174 | } |
| 175 | |
| 176 | wlPath := worklog.WorklogPath(foundTask.FilePath, taskID) |
| 177 | if worklog.Exists(wlPath) { |
| 178 | if wl, err := worklog.ParseWorklog(wlPath); err == nil && len(wl.Entries) > 0 { |
| 179 | detail.WorklogEntries = len(wl.Entries) |
| 180 | last := wl.Entries[len(wl.Entries)-1] |
| 181 | detail.WorklogUpdated = last.Timestamp.Format("2006-01-02T15:04:05Z07:00") |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | writeJSON(w, detail) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | func handleBoard(dp *DataProvider, phases []PhaseInfo) http.HandlerFunc { |
| 190 | return func(w http.ResponseWriter, r *http.Request) { |