requireAuth(); $db = Database::getInstance(); $where = ['t.user_id = :user_id']; $params = ['user_id' => $this->getUserId()]; // Filtre par projet $projectId = $this->request->get('project_id'); if ($projectId !== null) { $where[] = 't.project_id = :project_id'; $params['project_id'] = (int) $projectId; } // Filtre par statut $statusId = $this->request->get('status_id'); if ($statusId !== null) { $where[] = 't.status_id = :status_id'; $params['status_id'] = (int) $statusId; } // Filtre par date de début $dateStart = $this->request->get('date_start'); if ($dateStart !== null) { $where[] = 't.date_start >= :date_start'; $params['date_start'] = $dateStart; } // Filtre par date de fin $dateEnd = $this->request->get('date_end'); if ($dateEnd !== null) { $where[] = 't.date_end <= :date_end'; $params['date_end'] = $dateEnd; } $sql = ' SELECT t.*, p.name as project_name, s.name as status_name, s.color as status_color, GROUP_CONCAT(tg.id) as tag_ids, GROUP_CONCAT(tg.name) as tag_names, GROUP_CONCAT(tg.color) as tag_colors FROM tasks t LEFT JOIN projects p ON t.project_id = p.id LEFT JOIN statuses s ON t.status_id = s.id LEFT JOIN task_tags tt ON t.id = tt.task_id LEFT JOIN tags tg ON tt.tag_id = tg.id WHERE ' . implode(' AND ', $where) . ' GROUP BY t.id ORDER BY t.position ASC, t.priority DESC, t.created_at DESC '; $stmt = $db->prepare($sql); $stmt->execute($params); $tasks = $stmt->fetchAll(); // Filtre par tag (après GROUP BY) $tagId = $this->request->get('tag_id'); // Parser les tags foreach ($tasks as &$task) { $task['tags'] = $this->parseTags($task); unset($task['tag_ids'], $task['tag_names'], $task['tag_colors']); } // Appliquer filtre tag si nécessaire if ($tagId !== null) { $tagId = (int) $tagId; $tasks = array_filter($tasks, function ($task) use ($tagId) { foreach ($task['tags'] as $tag) { if ($tag['id'] === $tagId) { return true; } } return false; }); $tasks = array_values($tasks); } Response::success($tasks); } /** * GET /tasks/{id} */ public function show(): void { $this->requireAuth(); $id = (int) $this->request->getParam('id'); $task = $this->findOrFail($id); $task['tags'] = $this->getTaskTags($id); Response::success($task); } /** * POST /tasks */ public function store(): void { $this->requireAuth(); $data = $this->validate([ 'project_id' => 'required|int', 'status_id' => 'required|int', 'title' => 'required|min:1|max:255', 'description' => 'max:65535', 'priority' => 'int', 'date_start' => 'max:10', 'date_end' => 'max:10', 'time_estimated' => 'int', 'time_spent' => 'int', 'billing' => 'numeric', 'position' => 'int', ]); // Vérifier que le projet appartient à l'utilisateur $this->verifyProject((int) $data['project_id']); // Vérifier que le statut appartient à l'utilisateur $this->verifyStatus((int) $data['status_id']); $db = Database::getInstance(); $stmt = $db->prepare(' INSERT INTO tasks (user_id, project_id, status_id, title, description, priority, date_start, date_end, time_estimated, time_spent, billing, position) VALUES (:user_id, :project_id, :status_id, :title, :description, :priority, :date_start, :date_end, :time_estimated, :time_spent, :billing, :position) '); $stmt->execute([ 'user_id' => $this->getUserId(), 'project_id' => $data['project_id'], 'status_id' => $data['status_id'], 'title' => $data['title'], 'description' => $data['description'] ?? null, 'priority' => $data['priority'] ?? 5, 'date_start' => $data['date_start'] ?: null, 'date_end' => $data['date_end'] ?: null, 'time_estimated' => $data['time_estimated'] ?? 0, 'time_spent' => $data['time_spent'] ?? 0, 'billing' => $data['billing'] ?? 0, 'position' => $data['position'] ?? 0, ]); $taskId = (int) $db->lastInsertId(); // Gérer les tags si fournis $tags = $this->request->get('tags'); if (is_array($tags)) { $this->syncTags($taskId, $tags); } $task = $this->findOrFail($taskId); $task['tags'] = $this->getTaskTags($taskId); Response::success($task, 'Tâche créée', 201); } /** * PUT /tasks/{id} */ public function update(): void { $this->requireAuth(); $id = (int) $this->request->getParam('id'); $this->findOrFail($id); $data = $this->validate([ 'project_id' => 'int', 'status_id' => 'int', 'title' => 'min:1|max:255', 'description' => 'max:65535', 'priority' => 'int', 'date_start' => 'max:10', 'date_end' => 'max:10', 'time_estimated' => 'int', 'time_spent' => 'int', 'billing' => 'numeric', 'position' => 'int', ]); if (!empty($data['project_id'])) { $this->verifyProject((int) $data['project_id']); } if (!empty($data['status_id'])) { $this->verifyStatus((int) $data['status_id']); } $db = Database::getInstance(); $fields = []; $params = ['id' => $id]; $allowedFields = [ 'project_id', 'status_id', 'title', 'description', 'priority', 'date_start', 'date_end', 'time_estimated', 'time_spent', 'billing', 'position' ]; foreach ($allowedFields as $field) { if (array_key_exists($field, $data)) { $fields[] = "{$field} = :{$field}"; $value = $data[$field]; // Convertir les chaînes vides en null pour les dates if (in_array($field, ['date_start', 'date_end']) && $value === '') { $value = null; } $params[$field] = $value; } } if (!empty($fields)) { $sql = 'UPDATE tasks SET ' . implode(', ', $fields) . ' WHERE id = :id'; $stmt = $db->prepare($sql); $stmt->execute($params); } // Gérer les tags si fournis $tags = $this->request->get('tags'); if (is_array($tags)) { $this->syncTags($id, $tags); } $task = $this->findOrFail($id); $task['tags'] = $this->getTaskTags($id); Response::success($task, 'Tâche mise à jour'); } /** * DELETE /tasks/{id} */ public function destroy(): void { $this->requireAuth(); $id = (int) $this->request->getParam('id'); $this->findOrFail($id); $db = Database::getInstance(); $stmt = $db->prepare('DELETE FROM tasks WHERE id = :id'); $stmt->execute(['id' => $id]); Response::success(null, 'Tâche supprimée'); } /** * Trouver une tâche ou retourner 404 */ private function findOrFail(int $id): array { $db = Database::getInstance(); $stmt = $db->prepare(' SELECT t.*, p.name as project_name, s.name as status_name, s.color as status_color FROM tasks t LEFT JOIN projects p ON t.project_id = p.id LEFT JOIN statuses s ON t.status_id = s.id WHERE t.id = :id AND t.user_id = :user_id '); $stmt->execute([ 'id' => $id, 'user_id' => $this->getUserId(), ]); $task = $stmt->fetch(); if (!$task) { Response::notFound('Tâche non trouvée'); } return $task; } /** * Vérifier qu'un projet appartient à l'utilisateur */ private function verifyProject(int $projectId): void { $db = Database::getInstance(); $stmt = $db->prepare('SELECT id FROM projects WHERE id = :id AND user_id = :user_id'); $stmt->execute(['id' => $projectId, 'user_id' => $this->getUserId()]); if (!$stmt->fetch()) { Response::error('Projet invalide', 422); } } /** * Vérifier qu'un statut appartient à l'utilisateur */ private function verifyStatus(int $statusId): void { $db = Database::getInstance(); $stmt = $db->prepare('SELECT id FROM statuses WHERE id = :id AND user_id = :user_id'); $stmt->execute(['id' => $statusId, 'user_id' => $this->getUserId()]); if (!$stmt->fetch()) { Response::error('Statut invalide', 422); } } /** * Parser les tags depuis le GROUP_CONCAT */ private function parseTags(array $task): array { $tags = []; if (!empty($task['tag_ids'])) { $ids = explode(',', $task['tag_ids']); $names = explode(',', $task['tag_names']); $colors = explode(',', $task['tag_colors']); foreach ($ids as $i => $tagId) { $tags[] = [ 'id' => (int) $tagId, 'name' => $names[$i] ?? '', 'color' => $colors[$i] ?? '#3B82F6', ]; } } return $tags; } /** * Récupérer les tags d'une tâche */ private function getTaskTags(int $taskId): array { $db = Database::getInstance(); $stmt = $db->prepare(' SELECT t.id, t.name, t.color FROM tags t JOIN task_tags tt ON t.id = tt.tag_id WHERE tt.task_id = :task_id '); $stmt->execute(['task_id' => $taskId]); return $stmt->fetchAll(); } /** * Synchroniser les tags d'une tâche */ private function syncTags(int $taskId, array $tagIds): void { $db = Database::getInstance(); $stmt = $db->prepare('DELETE FROM task_tags WHERE task_id = :task_id'); $stmt->execute(['task_id' => $taskId]); if (!empty($tagIds)) { $stmt = $db->prepare(' INSERT INTO task_tags (task_id, tag_id) SELECT :task_id, id FROM tags WHERE id = :tag_id AND user_id = :user_id '); foreach ($tagIds as $tagId) { $stmt->execute([ 'task_id' => $taskId, 'tag_id' => (int) $tagId, 'user_id' => $this->getUserId(), ]); } } } }