Drop the unused project description field entirely

It never got a UI home on the frontend -- removed from ProjectView a
few sessions back and never restored anywhere -- so it was a fully
live field (validated, stored, returned by the API, covered by tests)
with no consumer.

- Migration 011: ALTER TABLE projects DROP COLUMN description.
- ProjectRepository: description dropped from ProjectRow, SELECT,
  create() and update() -- update() is now just a rename (string, not
  a $fields array; there was never more than one editable field once
  this left).
- ProjectController: store()/update() no longer accept or validate
  it; update() drops the "provide at least one of title, description"
  branch, since title is unconditionally the only field now.
- Frontend Project type, root README's API docs and curl example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 01:25:53 +01:00
co-authored by Claude Sonnet 5
parent 367a98308a
commit fb59a54938
7 changed files with 22 additions and 50 deletions
+4 -16
View File
@@ -18,7 +18,6 @@ use Psr\Http\Message\ServerRequestInterface as Request;
final class ProjectController extends ProjectScopedController
{
private const TITLE_MAX = 255;
private const DESCRIPTION_MAX = 2000;
private const MAX_PROJECTS_PER_OWNER = 100;
public function __construct(
@@ -47,7 +46,6 @@ final class ProjectController extends ProjectScopedController
$validator = new Validator($this->body($request));
$title = $validator->requiredString('title', self::TITLE_MAX);
$description = $validator->optionalString('description', self::DESCRIPTION_MAX) ?? '';
$validator->assert();
if ($this->projects->countForOwner($ownerId) >= self::MAX_PROJECTS_PER_OWNER) {
@@ -57,7 +55,7 @@ final class ProjectController extends ProjectScopedController
);
}
$project = $this->projects->create($ownerId, $title, $description);
$project = $this->projects->create($ownerId, $title);
$this->statuses->seedDefaults($project['id']);
return $this->json($response, ['project' => $this->present($project)], 201);
@@ -79,19 +77,10 @@ final class ProjectController extends ProjectScopedController
$project = $this->requireOwnedProject($request, $args);
$validator = new Validator($this->body($request));
$fields = [];
if ($validator->has('title')) {
$fields['title'] = $validator->requiredString('title', self::TITLE_MAX);
}
if ($validator->has('description')) {
$fields['description'] = $validator->optionalString('description', self::DESCRIPTION_MAX) ?? '';
}
if ($fields === [] && !$validator->failed()) {
$validator->add('title', 'Provide at least one of: title, description.');
}
$title = $validator->requiredString('title', self::TITLE_MAX);
$validator->assert();
$updated = $this->projects->update($project['id'], $project['owner_id'], $fields);
$updated = $this->projects->update($project['id'], $project['owner_id'], $title);
return $this->json($response, ['project' => $this->present($updated)]);
}
@@ -107,7 +96,7 @@ final class ProjectController extends ProjectScopedController
}
/**
* @param array{id: int, owner_id: int, title: string, description: string, card_count: int, completed_count: int, created_at: string, updated_at: string} $project
* @param array{id: int, owner_id: int, title: string, card_count: int, completed_count: int, created_at: string, updated_at: string} $project
* @return array<string, mixed>
*/
private function present(array $project): array
@@ -115,7 +104,6 @@ final class ProjectController extends ProjectScopedController
return [
'id' => $project['id'],
'title' => $project['title'],
'description' => $project['description'],
'owner_id' => $project['owner_id'],
'card_count' => $project['card_count'],
'completed_count' => $project['completed_count'],
@@ -24,7 +24,7 @@ abstract class ProjectScopedController extends Controller
* by the authenticated user.
*
* @param array<string, string> $args
* @return array{id: int, owner_id: int, title: string, description: string, card_count: int, completed_count: int, created_at: string, updated_at: string}
* @return array{id: int, owner_id: int, title: string, card_count: int, completed_count: int, created_at: string, updated_at: string}
*/
protected function requireOwnedProject(Request $request, array $args): array
{
+9 -21
View File
@@ -11,14 +11,14 @@ use PDO;
* Data access for the `projects` table.
*
* @phpstan-type ProjectRow array{
* id: int, owner_id: int, title: string, description: string,
* id: int, owner_id: int, title: string,
* card_count: int, completed_count: int, created_at: string, updated_at: string
* }
*/
final class ProjectRepository
{
private const SELECT = <<<'SQL'
SELECT p.id, p.owner_id, p.title, p.description, p.created_at, p.updated_at,
SELECT p.id, p.owner_id, p.title, p.created_at, p.updated_at,
(SELECT COUNT(*) FROM cards c WHERE c.project_id = p.id) AS card_count,
(SELECT COUNT(*) FROM cards c WHERE c.project_id = p.id AND c.complete = 1) AS completed_count
FROM projects p
@@ -68,15 +68,12 @@ final class ProjectRepository
/**
* @return ProjectRow
*/
public function create(int $ownerId, string $title, string $description): array
public function create(int $ownerId, string $title): array
{
$stmt = $this->pdo->prepare(
'INSERT INTO projects (owner_id, title, description) VALUES (:owner, :title, :description)'
);
$stmt = $this->pdo->prepare('INSERT INTO projects (owner_id, title) VALUES (:owner, :title)');
$stmt->execute([
'owner' => $ownerId,
'title' => $title,
'description' => $description,
]);
/** @var ProjectRow $project */
@@ -86,23 +83,14 @@ final class ProjectRepository
}
/**
* @param array{title?: string, description?: string} $fields
* @return ProjectRow
*/
public function update(int $id, int $ownerId, array $fields): array
public function update(int $id, int $ownerId, string $title): array
{
$sets = ['updated_at = ' . Database::nowExpr()];
$params = ['id' => $id];
foreach (['title', 'description'] as $column) {
if (array_key_exists($column, $fields)) {
$sets[] = "{$column} = :{$column}";
$params[$column] = $fields[$column];
}
}
$stmt = $this->pdo->prepare('UPDATE projects SET ' . implode(', ', $sets) . ' WHERE id = :id');
$stmt->execute($params);
$stmt = $this->pdo->prepare(
'UPDATE projects SET title = :title, updated_at = ' . Database::nowExpr() . ' WHERE id = :id'
);
$stmt->execute(['id' => $id, 'title' => $title]);
/** @var ProjectRow $project */
$project = $this->findOwnedBy($id, $ownerId);