20 KiB
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 (US1–US5 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.examplewith 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.ymldefining: postgres, minio, api (depends_on postgres+minio), ui (depends_on api) - T007 [P] Configure
api/pyproject.tomlwith FastAPI, SQLAlchemy 2.x async, asyncpg, Alembic, aiobotocore, pydantic-settings, pytest, pytest-asyncio, ruff - T008 [P] Configure
ui/package.json/angular.jsonwith eslint + prettier; addui/proxy.conf.jsonrouting/api/*tohttp://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/healthreturns 200{"status":"ok"}inapi/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; applyalembic upgrade headon 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
StorageBackendinterface (put, get_presigned_url, delete) inapi/app/storage/backend.py - T019 [P] Implement
S3StorageBackendusing aiobotocore inapi/app/storage/s3_backend.py - T020 [P] Implement
AuthProviderinterface +NoOpAuthProviderinapi/app/auth/provider.pyandapi/app/auth/noop.py - T021 Implement MIME type + file size validation helpers in
api/app/routers/images.py(orapi/app/validation.py) - T022 Write Alembic migration for
imagestable inapi/alembic/versions/ - T023 Implement
ImageSQLAlchemy model inapi/app/models.py - T024 Implement
ImageRepository(create, get_by_id, get_by_hash) inapi/app/repositories/image_repo.py - T025 Wire
AuthProvider,StorageBackend, and DB session into FastAPI dependency injection inapi/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 inapi/tests/integration/test_upload.py - T028 [P] [US1] Integration test: invalid MIME type → 422 with
{"detail":"...","code":"invalid_mime_type"}inapi/tests/integration/test_upload.py - T029 [P] [US1] Integration test: file > MAX_UPLOAD_BYTES → 422
file_too_largeinapi/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: trueresponse → toast shown, navigate to detail inui/src/app/upload/upload.component.spec.ts - T032 [P] [US1] Angular unit test:
duplicate: falseresponse → success toast, navigate to detail inui/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/imagesendpoint (MIME check, size check, SHA-256, duplicate query, storage write, record insert; tags field accepted but ignored) inapi/app/routers/images.py - T035 [US1] Implement
ImageServicewrappingGET /api/v1/imagesandGET /api/v1/images/{id}/fileinui/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 inui/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 inapi/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:
ImageServiceconstructs correct query params from filter state inui/src/app/services/image.service.spec.ts - T044 [P] [US2] Angular unit test:
LibraryComponentrenders image grid from mocked service inui/src/app/library/library.component.spec.ts - T045 [P] [US2] Angular unit test: filter change triggers new API call with updated
tagsparam inui/src/app/library/library.component.spec.ts
Implementation for User Story 2
- T046 [US2] Write Alembic migration for
tagsandimage_tagstables inapi/alembic/versions/ - T047 [US2] Implement
TagandImageTagSQLAlchemy models inapi/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) inapi/app/repositories/tag_repo.py - T050 [US2] Update
POST /api/v1/imagesto process and persist thetagsfield inapi/app/routers/images.py - T051 [US2] Implement
GET /api/v1/imageswithtags(AND-filter),limit,offsetinapi/app/routers/images.py - T052 [US2] Implement
GET /api/v1/images/{id}returning image + tags inapi/app/routers/images.py - T053 [US2] Update
ImageServiceto supporttagsfilter query param inui/src/app/services/image.service.ts - T054 [US2] Implement
LibraryComponent(route/) with image grid, tag chips, debounced filter bar, "Load more" pagination inui/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 withLocationheader pointing to MinIO URL inapi/tests/integration/test_serving.py - T056 [P] [US3] Integration test:
/filefor unknown ID → 404image_not_foundinapi/tests/integration/test_serving.py - T057 [P] [US3] Integration test:
PATCH /api/v1/images/{id}/tagsreplaces tags, old tags unlinked, new tags upserted inapi/tests/integration/test_tags.py - T058 [P] [US3] Integration test: PATCH with invalid tag → 422
invalid_taginapi/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) inapi/app/routers/images.py - T062 [US3] Implement
TagRepository.replace_tags_on_imageinapi/app/repositories/tag_repo.py - T063 [US3] Implement
PATCH /api/v1/images/{id}/tagsinapi/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 inui/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; subsequentGET /{id}returns 404 inapi/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_foundinapi/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) inapi/app/routers/images.py - T071 [US4] Add delete button with confirmation dialog + back-to-Library navigation to
DetailComponentinui/src/app/detail/detail.component.ts - T072 [US4] Implement
NotFoundComponentshown for all unrecognised routes inui/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/tagsreturns all tags alphabetically with correct image_count inapi/tests/integration/test_tags.py - T074 [P] [US5] Integration test:
GET /api/v1/tags?q=careturns only tags prefixed "ca" inapi/tests/integration/test_tags.py - T075 [P] [US5] Angular unit test:
TagServicecallsGET /api/v1/tagswithqparam inui/src/app/services/tag.service.spec.ts
Implementation for User Story 5
- T076 [US5] Implement
GET /api/v1/tagswithqprefix search,limit,offset, image_count inapi/app/routers/tags.py - T077 [US5] Implement
TagServicewrappingGET /api/v1/tagsinui/src/app/services/tag.service.ts - T078 [US5] Wire
TagServiceintoLibraryComponenttag filter bar for tag autocomplete/selection inui/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"}inapi/tests/integration/test_upload.py - T080 [P] Verify all API error responses include both
detailandcodefields (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
NotFoundComponentfor all unrecognised routes inui/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 .inapi/— confirm zero lint errors - T086 [P] Run
npm run lintinui/— 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
- T018–T020 (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)
- Complete Phase 1: Setup
- Complete Phase 2: Foundational — BLOCKS all stories
- Complete Phase 3: US1 Upload
- Complete Phase 4: US2 Browse/Filter
- STOP and VALIDATE: Upload via UI, filter by tag, verify end-to-end
- Can demo at this point: full read+write loop without detail view
Incremental Delivery
- Phase 1 + 2 → Foundation ready
- Phase 3 → US1 Upload works (MVP start)
- Phase 4 → US2 Browse + tag filtering works (MVP complete)
- Phase 5 → US3 Detail + tag editing
- Phase 6 → US4 Delete (completes full CRUD)
- Phase 7 → US5 Tag browser (supplementary)
- 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 upmust keep working after every milestone