- sogoctl: supervisor avec health checks et restart auto - sogoway: gateway HTTP, auth JWT, routing par hostname - sogoms-db: microservice MariaDB avec pool par application - Protocol IPC Unix socket JSON length-prefixed - Config YAML multi-application (prokov) - Deploy script pour container Alpine gw3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
400 lines
12 KiB
PHP
400 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Contrôleur des tâches
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
class TaskController extends Controller
|
|
{
|
|
/**
|
|
* GET /tasks
|
|
* Liste les tâches avec filtres optionnels
|
|
* ?project_id=X - filtrer par projet
|
|
* ?status_id=X - filtrer par statut
|
|
* ?tag_id=X - filtrer par tag
|
|
* ?date_start=YYYY-MM-DD - tâches commençant après
|
|
* ?date_end=YYYY-MM-DD - tâches finissant avant
|
|
*/
|
|
public function index(): void
|
|
{
|
|
$this->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(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|