Files
reactbin/specs/001-reaction-image-board/tasks.md
agatha 8bf6ef443a [Spec Kit] Implementation progress
Implements all 88 tasks for the Reaction Image Board (specs/001-reaction-image-board):

- docker-compose.yml: postgres, minio, minio-init, api, ui services with healthchecks
- api/: FastAPI app with SQLAlchemy 2.x async, Alembic migrations, S3/MinIO storage,
  full integration + unit test suite (pytest + pytest-asyncio)
- ui/: Angular 19 standalone app (Library, Upload, Detail, NotFound components)
- .env.example: all required environment variables
- .gitignore: Python, Node, Docker, IDE, .env patterns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 16:13:23 +00:00

20 KiB
Raw Blame History

description
description
Task list for Reaction Image Board v1

Tasks: Reaction Image Board v1

Input: Design documents from specs/001-reaction-image-board/ Prerequisites: plan.md , spec.md , research.md , data-model.md , contracts/api.md

Tests: Per §5.1 of the constitution, TDD is non-negotiable. Test tasks MUST appear before every implementation task. Write the test, confirm it fails, then implement until it passes.

Organization: Tasks follow the milestone order from plan.md (API-first, serial). Each task is tagged with the user story it serves.

Format: [ID] [P?] [Story?] Description

  • [P]: Can run in parallel (different files, no dependencies on incomplete tasks)
  • [Story]: Which user story this task serves (US1US5 per spec.md)
  • Include exact file paths in descriptions

Path Conventions

api/app/              API source
api/tests/unit/       API unit tests
api/tests/integration/ API integration tests
api/alembic/versions/ Database migrations
ui/src/app/           Angular source
ui/src/app/services/  Angular services (+ .spec.ts colocated)
ui/src/app/<feature>/ Angular component dirs (+ .spec.ts colocated)

Phase 1: Setup (Project Skeleton — M0)

Purpose: Establish the monorepo layout, Docker Compose stack, and linting baseline. No feature logic. All subsequent milestones build on this.

  • T001 Create top-level monorepo layout: api/, ui/, docker-compose.yml, .env.example
  • T002 Write .env.example with all variables from spec §5 (DATABASE_URL, S3_*, API_BASE_URL, MAX_UPLOAD_BYTES)
  • T003 [P] Write api/Dockerfile (Python 3.12 slim, installs pyproject.toml deps, runs uvicorn)
  • T004 [P] Scaffold Angular project with CLI into ui/ (strict mode, standalone components, routing)
  • T005 [P] Write ui/Dockerfile (Node LTS, ng serve --host 0.0.0.0)
  • T006 Write docker-compose.yml defining: postgres, minio, api (depends_on postgres+minio), ui (depends_on api)
  • T007 [P] Configure api/pyproject.toml with FastAPI, SQLAlchemy 2.x async, asyncpg, Alembic, aiobotocore, pydantic-settings, pytest, pytest-asyncio, ruff
  • T008 [P] Configure ui/package.json / angular.json with eslint + prettier; add ui/proxy.conf.json routing /api/* to http://localhost:8000
  • T009 Write API unit test: settings load from env vars without error in api/tests/unit/test_config.py
  • T010 Write API integration test: GET /api/v1/health returns 200 {"status":"ok"} in api/tests/integration/test_health.py
  • T011 Implement api/app/config.py (pydantic-settings reading all env vars)
  • T012 Implement api/app/main.py (FastAPI factory, lifespan connecting to Postgres + MinIO, health route)
  • T013 Configure Alembic in api/alembic/ with async engine; apply alembic upgrade head on startup
  • T014 [P] Add Angular default smoke test in ui/src/app/app.component.spec.ts

Checkpoint: docker compose up starts all four services. Health endpoint returns 200. Both linters pass. All tests pass.


Phase 2: Foundational (Upload API Infrastructure — M1 core)

Purpose: Core interfaces and repositories that MUST exist before any user story endpoint can be implemented. Establishes the StorageBackend, AuthProvider, and ImageRepository abstractions.

⚠️ CRITICAL: No user story work can begin until this phase is complete.

  • T015 Write unit test: SHA-256 hash of known bytes returns expected hex digest in api/tests/unit/test_hashing.py
  • T016 Write unit test: MIME validator accepts jpeg/png/gif/webp and rejects pdf/mp4 in api/tests/unit/test_validation.py
  • T017 Write unit test: file size validator rejects bytes exceeding MAX_UPLOAD_BYTES in api/tests/unit/test_validation.py
  • T018 [P] Implement StorageBackend interface (put, get_presigned_url, delete) in api/app/storage/backend.py
  • T019 [P] Implement S3StorageBackend using aiobotocore in api/app/storage/s3_backend.py
  • T020 [P] Implement AuthProvider interface + NoOpAuthProvider in api/app/auth/provider.py and api/app/auth/noop.py
  • T021 Implement MIME type + file size validation helpers in api/app/routers/images.py (or api/app/validation.py)
  • T022 Write Alembic migration for images table in api/alembic/versions/
  • T023 Implement Image SQLAlchemy model in api/app/models.py
  • T024 Implement ImageRepository (create, get_by_id, get_by_hash) in api/app/repositories/image_repo.py
  • T025 Wire AuthProvider, StorageBackend, and DB session into FastAPI dependency injection in api/app/dependencies.py

Checkpoint: All unit tests pass; foundation ready for user story endpoints.


Phase 3: User Story 1 — Upload an Image (M1 endpoint + M5 UI) 🎯 MVP

Goal: A user can upload an image (with tags, though tag persistence is deferred to Phase 4). Duplicate detection works. Errors shown inline.

Independent Test: Upload a JPEG via the UI, verify it appears in the library grid. Re-upload the same file, verify "Already in your library" toast and no duplicate in DB or MinIO.

Tests for User Story 1 (REQUIRED per §5.1 — TDD) ⚠️

NOTE: Write these tests FIRST, ensure they FAIL before implementation

  • T026 [P] [US1] Integration test: valid JPEG upload → 201, record in DB, object in MinIO in api/tests/integration/test_upload.py
  • T027 [P] [US1] Integration test: same image uploaded twice → 200, duplicate: true, no second MinIO object in api/tests/integration/test_upload.py
  • T028 [P] [US1] Integration test: invalid MIME type → 422 with {"detail":"...","code":"invalid_mime_type"} in api/tests/integration/test_upload.py
  • T029 [P] [US1] Integration test: file > MAX_UPLOAD_BYTES → 422 file_too_large in api/tests/integration/test_upload.py
  • T030 [P] [US1] Angular unit test: tag chip input lowercases and splits on comma/space in ui/src/app/upload/upload.component.spec.ts
  • T031 [P] [US1] Angular unit test: duplicate: true response → toast shown, navigate to detail in ui/src/app/upload/upload.component.spec.ts
  • T032 [P] [US1] Angular unit test: duplicate: false response → success toast, navigate to detail in ui/src/app/upload/upload.component.spec.ts
  • T033 [P] [US1] Angular unit test: error response → inline error shown, no navigation in ui/src/app/upload/upload.component.spec.ts

Implementation for User Story 1

  • T034 [US1] Implement POST /api/v1/images endpoint (MIME check, size check, SHA-256, duplicate query, storage write, record insert; tags field accepted but ignored) in api/app/routers/images.py
  • T035 [US1] Implement ImageService wrapping GET /api/v1/images and GET /api/v1/images/{id}/file in ui/src/app/services/image.service.ts
  • T036 [US1] Implement UploadComponent (route /upload) with drag-and-drop zone, click-to-browse, tag chip input, POST submit, duplicate/success/error handling in ui/src/app/upload/upload.component.ts

Checkpoint: Full upload flow works in browser. Duplicate detection gives correct feedback. API tests and Angular unit tests all pass.


Phase 4: User Story 2 — Browse and Filter the Library (M2 list/search + M4 UI)

Goal: User can view all images in a responsive grid and filter by one or more tags (AND logic). Pagination works. Tags are persisted during upload.

Independent Test: Seed the library via the upload flow. Apply a single tag filter and verify only matching images are shown. Add a second filter, verify both tags must be present. Remove a filter, verify the grid expands.

Tests for User Story 2 (REQUIRED per §5.1 — TDD) ⚠️

NOTE: Write these tests FIRST, ensure they FAIL before implementation

  • T037 [P] [US2] Unit test: tag normalisation — uppercase → lowercase, whitespace stripped in api/tests/unit/test_tags.py
  • T038 [P] [US2] Unit test: tag validation — rejects names > 64 chars, invalid chars in api/tests/unit/test_tags.py
  • T039 [P] [US2] Integration test: upload with tags → tags persisted, returned in response in api/tests/integration/test_tags.py
  • T040 [P] [US2] Integration test: duplicate upload → existing record returned, tags unchanged in api/tests/integration/test_tags.py
  • T041 [P] [US2] Integration test: GET /api/v1/images?tags=cat,funny → only images with both tags in api/tests/integration/test_search.py
  • T042 [P] [US2] Integration test: same query excludes images with only one matching tag in api/tests/integration/test_search.py
  • T043 [P] [US2] Angular unit test: ImageService constructs correct query params from filter state in ui/src/app/services/image.service.spec.ts
  • T044 [P] [US2] Angular unit test: LibraryComponent renders image grid from mocked service in ui/src/app/library/library.component.spec.ts
  • T045 [P] [US2] Angular unit test: filter change triggers new API call with updated tags param in ui/src/app/library/library.component.spec.ts

Implementation for User Story 2

  • T046 [US2] Write Alembic migration for tags and image_tags tables in api/alembic/versions/
  • T047 [US2] Implement Tag and ImageTag SQLAlchemy models in api/app/models.py
  • T048 [US2] Implement tag normalisation + validation helpers in api/app/repositories/tag_repo.py
  • T049 [US2] Implement TagRepository (upsert_by_name, get_by_image_id) in api/app/repositories/tag_repo.py
  • T050 [US2] Update POST /api/v1/images to process and persist the tags field in api/app/routers/images.py
  • T051 [US2] Implement GET /api/v1/images with tags (AND-filter), limit, offset in api/app/routers/images.py
  • T052 [US2] Implement GET /api/v1/images/{id} returning image + tags in api/app/routers/images.py
  • T053 [US2] Update ImageService to support tags filter query param in ui/src/app/services/image.service.ts
  • T054 [US2] Implement LibraryComponent (route /) with image grid, tag chips, debounced filter bar, "Load more" pagination in ui/src/app/library/library.component.ts

Checkpoint: Library view shows real images with tags. Tag filtering (AND logic) and pagination work end-to-end.


Phase 5: User Story 3 — View Image Detail and Edit Tags (M3 serving + M6 detail UI)

Goal: User can view a full-size image and edit its tags inline. Changes saved on blur/Enter.

Independent Test: Open an image detail page, add a new tag, navigate back, filter by that tag, and confirm the image appears.

Tests for User Story 3 (REQUIRED per §5.1 — TDD) ⚠️

NOTE: Write these tests FIRST, ensure they FAIL before implementation

  • T055 [P] [US3] Integration test: GET /api/v1/images/{id}/file → 302 with Location header pointing to MinIO URL in api/tests/integration/test_serving.py
  • T056 [P] [US3] Integration test: /file for unknown ID → 404 image_not_found in api/tests/integration/test_serving.py
  • T057 [P] [US3] Integration test: PATCH /api/v1/images/{id}/tags replaces tags, old tags unlinked, new tags upserted in api/tests/integration/test_tags.py
  • T058 [P] [US3] Integration test: PATCH with invalid tag → 422 invalid_tag in api/tests/integration/test_tags.py
  • T059 [P] [US3] Angular unit test: removing tag chip calls PATCH with updated list (removed tag absent) in ui/src/app/detail/detail.component.spec.ts
  • T060 [P] [US3] Angular unit test: adding tag + Enter calls PATCH with new tag included in ui/src/app/detail/detail.component.spec.ts

Implementation for User Story 3

  • T061 [US3] Implement GET /api/v1/images/{id}/file (generate 1-hour pre-signed URL, return 302) in api/app/routers/images.py
  • T062 [US3] Implement TagRepository.replace_tags_on_image in api/app/repositories/tag_repo.py
  • T063 [US3] Implement PATCH /api/v1/images/{id}/tags in api/app/routers/images.py
  • T064 [US3] Implement DetailComponent (route /images/:id) with full-size image, editable tag chips (add/remove), save on blur/Enter via PATCH, back button in ui/src/app/detail/detail.component.ts

Checkpoint: Full-size image loads in browser via redirect. Tag editing works from detail page. Changes persist across page navigation.


Phase 6: User Story 4 — Delete an Image (M6 detail UI + M2 delete endpoint)

Goal: User can permanently delete an image with confirmation. Returns to library afterwards.

Independent Test: Delete a known image, confirm it no longer appears in the library and that navigating to its former URL shows a not-found screen.

Tests for User Story 4 (REQUIRED per §5.1 — TDD) ⚠️

NOTE: Write these tests FIRST, ensure they FAIL before implementation

  • T065 [P] [US4] Integration test: DELETE /api/v1/images/{id} → 204; subsequent GET /{id} returns 404 in api/tests/integration/test_delete.py
  • T066 [P] [US4] Integration test: DELETE verifies MinIO object is removed in api/tests/integration/test_delete.py
  • T067 [P] [US4] Integration test: DELETE of unknown ID → 404 image_not_found in api/tests/integration/test_delete.py
  • T068 [P] [US4] Angular unit test: delete confirmation → DELETE called → navigation to Library in ui/src/app/detail/detail.component.spec.ts
  • T069 [P] [US4] Angular unit test: cancel confirmation dialog → no DELETE call, stays on detail page in ui/src/app/detail/detail.component.spec.ts

Implementation for User Story 4

  • T070 [US4] Implement DELETE /api/v1/images/{id} (delete image_tags rows, image record, S3 object) in api/app/routers/images.py
  • T071 [US4] Add delete button with confirmation dialog + back-to-Library navigation to DetailComponent in ui/src/app/detail/detail.component.ts
  • T072 [US4] Implement NotFoundComponent shown for all unrecognised routes in ui/src/app/not-found/not-found.component.ts

Checkpoint: Full CRUD loop works: upload → view → re-tag → delete. Deleted images gone from library and storage.


Phase 7: User Story 5 — Browse and Search Tags (M2 tags endpoint + UI)

Goal: User can list all tags alphabetically with image counts and narrow by prefix.

Independent Test: Open the tag browser, verify every tag present in the library appears with a correct image count. Type a prefix and verify only matching tags remain.

Tests for User Story 5 (REQUIRED per §5.1 — TDD) ⚠️

NOTE: Write these tests FIRST, ensure they FAIL before implementation

  • T073 [P] [US5] Integration test: GET /api/v1/tags returns all tags alphabetically with correct image_count in api/tests/integration/test_tags.py
  • T074 [P] [US5] Integration test: GET /api/v1/tags?q=ca returns only tags prefixed "ca" in api/tests/integration/test_tags.py
  • T075 [P] [US5] Angular unit test: TagService calls GET /api/v1/tags with q param in ui/src/app/services/tag.service.spec.ts

Implementation for User Story 5

  • T076 [US5] Implement GET /api/v1/tags with q prefix search, limit, offset, image_count in api/app/routers/tags.py
  • T077 [US5] Implement TagService wrapping GET /api/v1/tags in ui/src/app/services/tag.service.ts
  • T078 [US5] Wire TagService into LibraryComponent tag filter bar for tag autocomplete/selection in ui/src/app/library/library.component.ts

Checkpoint: All user stories independently functional and tested.


Phase 8: Polish & Cross-Cutting Concerns

Purpose: Improvements affecting multiple user stories and final validation.

  • T079 [P] Add GET /api/v1/images/{id} 404 test to verify error envelope shape {"detail":"...","code":"image_not_found"} in api/tests/integration/test_upload.py
  • T080 [P] Verify all API error responses include both detail and code fields (constitution §3.3) — check tests for T028, T029, T056, T058, T067
  • T081 [P] Add empty-state UI for library with zero images in ui/src/app/library/library.component.ts
  • T082 [P] Add empty-state UI for tag filter returning zero results in ui/src/app/library/library.component.ts
  • T083 Configure Angular routing to show NotFoundComponent for all unrecognised routes in ui/src/app/app.routes.ts
  • T084 [P] Run quickstart.md validation: docker compose up, upload an image, filter by tag, edit tag, delete image — full happy path
  • T085 [P] Run ruff check . in api/ — confirm zero lint errors
  • T086 [P] Run npm run lint in ui/ — confirm zero lint errors
  • T087 Run all API tests: docker compose run --rm api pytest — confirm all pass
  • T088 Run all UI tests: docker compose run --rm ui ng test --watch=false — confirm all pass

Dependencies & Execution Order

Phase Dependencies

  • Phase 1 (Setup): No dependencies — start immediately
  • Phase 2 (Foundational): Depends on Phase 1 — BLOCKS all user stories
  • Phase 3 (US1 Upload): Depends on Phase 2 — API endpoint + Angular upload UI
  • Phase 4 (US2 Browse): Depends on Phase 3 (tags API needs upload first)
  • Phase 5 (US3 Detail/Edit): Depends on Phase 4 (detail view needs list view navigation)
  • Phase 6 (US4 Delete): Depends on Phase 5 (delete is in the detail component)
  • Phase 7 (US5 Tags): Can start after Phase 4 (tag endpoint is independent)
  • Phase 8 (Polish): Depends on all prior phases

User Story Dependencies

  • US1 (Upload): Foundational complete → API endpoint first, then Angular component
  • US2 (Browse): US1 must exist to seed data; tag schema (Phase 4) needed
  • US3 (View/Edit): US2 complete (library navigation leads to detail)
  • US4 (Delete): US3 complete (delete is on the detail view)
  • US5 (Tags): Independent after Phase 4 tag endpoint; can start in parallel with US3/US4

Within Each Phase

  • Test tasks MUST be written and confirmed FAILING before implementation
  • Models before repositories before endpoints
  • API endpoint before Angular service
  • Angular service before Angular component
  • Story complete before moving to next phase

Parallel Opportunities

  • All Phase 1 tasks marked [P] can run in parallel after T001/T002
  • T018T020 (interfaces) can run in parallel
  • All integration tests within a phase marked [P] can be written in parallel
  • Angular unit tests within a phase marked [P] can be written in parallel
  • T085/T086/T087/T088 (lint + test runs) can run in parallel

Parallel Example: Phase 3 (User Story 1)

# Write all tests for US1 in parallel (all different files):
T026: api/tests/integration/test_upload.py (new upload)
T027: api/tests/integration/test_upload.py (duplicate)
T028: api/tests/integration/test_upload.py (invalid MIME)
T029: api/tests/integration/test_upload.py (oversized)
T030: ui/src/app/upload/upload.component.spec.ts (tag chips)
T031: ui/src/app/upload/upload.component.spec.ts (duplicate response)

# Then implement sequentially:
T034: POST /api/v1/images endpoint
T035: Angular ImageService
T036: Angular UploadComponent

Implementation Strategy

MVP First (User Stories 1 + 2 only)

  1. Complete Phase 1: Setup
  2. Complete Phase 2: Foundational — BLOCKS all stories
  3. Complete Phase 3: US1 Upload
  4. Complete Phase 4: US2 Browse/Filter
  5. STOP and VALIDATE: Upload via UI, filter by tag, verify end-to-end
  6. Can demo at this point: full read+write loop without detail view

Incremental Delivery

  1. Phase 1 + 2 → Foundation ready
  2. Phase 3 → US1 Upload works (MVP start)
  3. Phase 4 → US2 Browse + tag filtering works (MVP complete)
  4. Phase 5 → US3 Detail + tag editing
  5. Phase 6 → US4 Delete (completes full CRUD)
  6. Phase 7 → US5 Tag browser (supplementary)
  7. Phase 8 → Polish + final validation

Notes

  • [P] = different files, no incomplete dependencies — safe to parallelise
  • [USN] label maps task to user story for traceability
  • TDD is non-negotiable (§5.1): test → fail → implement → pass
  • API tests run against real Postgres + MinIO (no mocks per §5.2)
  • Milestone done-criterion: all tests pass + linter passes
  • docker compose up must keep working after every milestone