Statuses
- Migration 006: card_statuses table (project-scoped) and cards.status_id, a
nullable FK with ON DELETE SET NULL. Every new project is seeded with
"To do" / "Doing" / "Done"; GET /api/projects/{id}/statuses lists them.
- New cards have no status -- they sit in an "inbox" until moved.
Project view
- Full-width and tabbed: "All tasks" (a flat list, sorted by name
case-insensitively) and "Kanban" (Inbox plus one column per status).
- Drag a card within or between columns to reorder / restatus; the Inbox
column has its own name + Add form.
Ordering
- Migration 007: `position` is now a dense 0..n-1 rank within a
(project_id, status_id) column, not a project-wide order. New composite
index idx_cards_project_status_position; existing rows re-ranked.
- PUT /api/projects/{id}/cards/order takes { status_id, card_ids } and sets one
column's contents and order, re-parenting moved-in cards and re-packing their
source column in a single transaction. PATCH status_id appends the card to the
end of the destination column.
58 phpunit tests pass; the frontend type-checks and builds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
65 lines
1.8 KiB
PHP
65 lines
1.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Exception\ApiException;
|
|
use App\Repository\CardStatusRepository;
|
|
use App\Repository\ProjectRepository;
|
|
use Psr\Http\Message\ResponseInterface as Response;
|
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
|
|
/**
|
|
* Read-only listing of a project's card statuses. Statuses are seeded when the
|
|
* project is created; there is no create/update/delete yet.
|
|
*/
|
|
final class CardStatusController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly ProjectRepository $projects,
|
|
private readonly CardStatusRepository $statuses,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* GET /api/projects/{projectId}/statuses
|
|
*/
|
|
public function index(Request $request, Response $response, array $args): Response
|
|
{
|
|
$projectId = $this->requireOwnedProjectId($request, $args);
|
|
|
|
return $this->json($response, [
|
|
'statuses' => array_map($this->present(...), $this->statuses->allForProject($projectId)),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $args
|
|
*/
|
|
private function requireOwnedProjectId(Request $request, array $args): int
|
|
{
|
|
$project = $this->projects->findOwnedBy((int) $args['projectId'], $this->user($request)['id']);
|
|
|
|
if ($project === null) {
|
|
throw new ApiException('Project not found.', 404);
|
|
}
|
|
|
|
return $project['id'];
|
|
}
|
|
|
|
/**
|
|
* @param array{id: int, project_id: int, name: string, position: int, created_at: string, updated_at: string} $status
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function present(array $status): array
|
|
{
|
|
return [
|
|
'id' => $status['id'],
|
|
'project_id' => $status['project_id'],
|
|
'name' => $status['name'],
|
|
'position' => $status['position'],
|
|
];
|
|
}
|
|
}
|