Add design artifacts for view event feature (007)
Spec, research, data model, API contract, implementation plan, and task breakdown for the public event detail page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
225
specs/007-view-event/tasks.md
Normal file
225
specs/007-view-event/tasks.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Tasks: View Event Landing Page
|
||||
|
||||
**Input**: Design documents from `/specs/007-view-event/`
|
||||
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/get-event.yaml
|
||||
|
||||
**Tests**: Included — constitution enforces Test-Driven Methodology (Principle II).
|
||||
|
||||
**Organization**: Tasks grouped by user story. US3 (cancelled event) is deferred until US-18.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US4)
|
||||
- Exact file paths included in descriptions
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup (Cross-Cutting Schema Changes)
|
||||
|
||||
**Purpose**: OpenAPI contract update, database migration, and type generation — prerequisites for all backend and frontend work.
|
||||
|
||||
- [x] T001 Update OpenAPI spec: add `GET /events/{token}` endpoint, `GetEventResponse` schema, and `timezone` field to `CreateEventRequest`/`CreateEventResponse` in `backend/src/main/resources/openapi/api.yaml`
|
||||
- [x] T002 [P] Add Liquibase changeset: `timezone VARCHAR(64) NOT NULL DEFAULT 'UTC'` column on `events` table in `backend/src/main/resources/db/changelog/`
|
||||
- [x] T003 Regenerate frontend TypeScript types from updated OpenAPI spec in `frontend/src/api/schema.d.ts`
|
||||
|
||||
**Checkpoint**: OpenAPI contract finalized, DB schema ready, frontend types available.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Backend — Blocks All User Stories)
|
||||
|
||||
**Purpose**: Domain model update, new GET use case, controller endpoint, and backend tests. All user stories depend on this.
|
||||
|
||||
**CRITICAL**: No frontend user story work can begin until this phase is complete.
|
||||
|
||||
### Backend Tests (TDD — write first, verify they fail)
|
||||
|
||||
- [x] T004 [P] Backend unit tests for `GetEventUseCase`: test getByEventToken returns event, returns empty for unknown token, computes expired flag — in `backend/src/test/java/de/fete/`
|
||||
- [x] T005 [P] Backend controller tests for `GET /events/{token}`: test 200 with full response, 200 with optional fields absent, 404 with ProblemDetail — in `backend/src/test/java/de/fete/`
|
||||
- [x] T006 [P] Backend tests for timezone in Create Event flow: request validation (valid/invalid IANA zone), persistence round-trip — in `backend/src/test/java/de/fete/`
|
||||
|
||||
### Backend Implementation
|
||||
|
||||
- [x] T007 Add `timezone` field (String) to domain model in `backend/src/main/java/de/fete/domain/model/Event.java`
|
||||
- [x] T008 [P] Add `timezone` column to JPA entity and update persistence mapping in `backend/src/main/java/de/fete/adapter/out/persistence/EventJpaEntity.java` and `EventPersistenceAdapter.java`
|
||||
- [x] T009 [P] Update Create Event flow to accept and validate `timezone` (must be valid IANA zone ID via `ZoneId.getAvailableZoneIds()`) in `backend/src/main/java/de/fete/application/service/EventService.java` and `EventController.java`
|
||||
- [x] T010 Create `GetEventUseCase` inbound port with `getByEventToken(UUID): Optional<Event>` in `backend/src/main/java/de/fete/domain/port/in/GetEventUseCase.java`
|
||||
- [x] T011 Implement `GetEventUseCase` in `backend/src/main/java/de/fete/application/service/EventService.java` — delegates to existing `findByEventToken()` repository method
|
||||
- [x] T012 Implement `getEvent()` in `backend/src/main/java/de/fete/adapter/in/web/EventController.java` — maps domain Event to GetEventResponse, computes `expired` (expiryDate vs server clock) and `attendeeCount` (hardcoded 0)
|
||||
|
||||
**Checkpoint**: Backend complete — `GET /api/events/{token}` returns 200 or 404. All backend tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 — View Event Details as Guest (Priority: P1) MVP
|
||||
|
||||
**Goal**: A guest opens a shared event link and sees all event information: title, date/time with IANA timezone, description, location, attendee count. Loading shows skeleton shimmer.
|
||||
|
||||
**Independent Test**: Navigate to a valid event URL, verify all fields display correctly with locale-formatted date/time.
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [x] T013 [P] [US1] Unit tests for EventDetailView loaded state: renders title, date/time (locale-formatted via `Intl.DateTimeFormat`), timezone, description, location, attendee count — in `frontend/src/__tests__/EventDetailView.spec.ts`
|
||||
- [x] T014 [P] [US1] Unit test for EventDetailView loading state: renders skeleton shimmer placeholders — in `frontend/src/__tests__/EventDetailView.spec.ts`
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [x] T015 [P] [US1] Add skeleton shimmer CSS (CSS-only gradient animation, no dependencies) in `frontend/src/assets/main.css`
|
||||
- [x] T016 [US1] Create `EventDetailView.vue` with loading (skeleton shimmer) and loaded states — fetches event via `openapi-fetch` GET `/events/{token}`, formats date/time with `Intl.DateTimeFormat` using browser locale — in `frontend/src/views/EventDetailView.vue`
|
||||
- [x] T017 [US1] Update router to use `EventDetailView` for `/events/:token` route in `frontend/src/router/index.ts`
|
||||
- [x] T018 [P] [US1] Update `EventCreateView.vue` to send `timezone` field (auto-detected via `Intl.DateTimeFormat().resolvedOptions().timeZone`) in `frontend/src/views/EventCreateView.vue`
|
||||
- [x] T019 [US1] E2E test for loaded event: navigate to valid event URL, verify all fields displayed, verify no external resource requests — in `frontend/e2e/event-view.spec.ts`
|
||||
|
||||
**Checkpoint**: US1 complete — guest can view event details. Skeleton shimmer during loading. Date/time locale-formatted with timezone label.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 — View Expired Event (Priority: P2)
|
||||
|
||||
**Goal**: A guest opens a link to an expired event. The page shows event details plus a clear "event has ended" indicator. No RSVP actions shown.
|
||||
|
||||
**Independent Test**: Create an event with past expiry date, navigate to its URL, verify "event has ended" state renders and no RSVP controls are present.
|
||||
|
||||
**Dependencies**: Requires Phase 3 (EventDetailView exists).
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [x] T020 [P] [US2] Unit test for EventDetailView expired state: renders "event has ended" indicator, RSVP controls absent from DOM — in `frontend/src/__tests__/EventDetailView.spec.ts`
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [x] T021 [US2] Add expired state rendering to `EventDetailView.vue`: show event details + "event has ended" banner when `expired === true`, no RSVP actions in DOM — in `frontend/src/views/EventDetailView.vue`
|
||||
- [x] T022 [US2] E2E test for expired event: MSW returns event with `expired: true`, verify banner and absent RSVP controls — in `frontend/e2e/event-view.spec.ts`
|
||||
|
||||
**Checkpoint**: US2 complete — expired events clearly show "ended" state.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 4 — Event Not Found (Priority: P2)
|
||||
|
||||
**Goal**: A guest navigates to an invalid event URL. The page shows a clear "event not found" message — no partial data, no error traces.
|
||||
|
||||
**Independent Test**: Navigate to a URL with an unknown event token, verify "event not found" message renders.
|
||||
|
||||
**Dependencies**: Requires Phase 3 (EventDetailView exists). No dependency on US2.
|
||||
|
||||
### Tests for User Story 4
|
||||
|
||||
- [x] T023 [P] [US4] Unit test for EventDetailView not-found state: renders "event not found" message, no event data in DOM — in `frontend/src/__tests__/EventDetailView.spec.ts`
|
||||
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [x] T024 [US4] Add not-found state rendering to `EventDetailView.vue`: show "event not found" message when API returns 404 — in `frontend/src/views/EventDetailView.vue`
|
||||
- [x] T025 [US4] E2E test for event not found: MSW returns 404 ProblemDetail, verify message and no event data — in `frontend/e2e/event-view.spec.ts`
|
||||
|
||||
**Checkpoint**: US4 complete — invalid tokens show friendly not-found message.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Server error edge case, final validation, and cleanup.
|
||||
|
||||
- [x] T026 Add server error state with manual retry button to `EventDetailView.vue`: friendly error message + "Retry" button that re-fetches — in `frontend/src/views/EventDetailView.vue`
|
||||
- [x] T027 [P] Unit test for server error + retry state in `frontend/src/__tests__/EventDetailView.spec.ts`
|
||||
- [x] T028 [P] E2E test for server error: MSW returns 500, verify error message and retry button functionality — in `frontend/e2e/event-view.spec.ts`
|
||||
- [x] T029 Run quickstart.md validation: verify all key changes listed in quickstart.md are implemented
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: No dependencies — start immediately
|
||||
- **Foundational (Phase 2)**: Depends on T001 (OpenAPI spec) and T002 (migration) from Setup
|
||||
- **US1 (Phase 3)**: Depends on Phase 2 completion (backend endpoint must exist)
|
||||
- **US2 (Phase 4)**: Depends on Phase 3 (EventDetailView exists) — can parallelize with US4
|
||||
- **US4 (Phase 5)**: Depends on Phase 3 (EventDetailView exists) — can parallelize with US2
|
||||
- **Polish (Phase 6)**: Depends on Phase 3 minimum; ideally after US2 + US4
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
```
|
||||
Phase 1 (Setup) ──► Phase 2 (Backend) ──► Phase 3 (US1/MVP)
|
||||
│
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
Phase 4 Phase 5
|
||||
(US2) (US4)
|
||||
└────┬────┘
|
||||
▼
|
||||
Phase 6 (Polish)
|
||||
```
|
||||
|
||||
- **US1 (P1)**: Requires Phase 2 — no dependency on other stories
|
||||
- **US2 (P2)**: Requires US1 (same component) — no dependency on US4
|
||||
- **US4 (P2)**: Requires US1 (same component) — no dependency on US2
|
||||
- **US3 (P2)**: DEFERRED until US-18 (cancel event) is implemented
|
||||
|
||||
### Within Each Phase
|
||||
|
||||
- Tests MUST be written and FAIL before implementation (TDD)
|
||||
- Models/ports before services
|
||||
- Services before controllers
|
||||
- Backend before frontend (for the same endpoint)
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
**Phase 1**: T002 (migration) can run in parallel with T001 (OpenAPI update)
|
||||
**Phase 2**: T004, T005, T006 (tests) can run in parallel. T008, T009 can run in parallel after T007.
|
||||
**Phase 3**: T013, T014 (unit tests) and T015 (CSS) can run in parallel. T018 (create form timezone) is independent.
|
||||
**Phase 4 + 5**: US2 and US4 can be implemented in parallel (different UI states, same file but non-conflicting sections).
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: Phase 2 (Backend)
|
||||
|
||||
```bash
|
||||
# Write all backend tests in parallel (TDD):
|
||||
Task T004: "Unit tests for GetEventUseCase"
|
||||
Task T005: "Controller tests for GET /events/{token}"
|
||||
Task T006: "Tests for timezone in Create Event flow"
|
||||
|
||||
# Then implement in parallel where possible:
|
||||
Task T008: "Add timezone to JPA entity + persistence" # parallel
|
||||
Task T009: "Update Create Event flow for timezone" # parallel
|
||||
# T010-T012 are sequential (port → service → controller)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
|
||||
1. Complete Phase 1: Setup (OpenAPI + migration + types)
|
||||
2. Complete Phase 2: Backend (domain + use case + controller + tests)
|
||||
3. Complete Phase 3: US1 (EventDetailView + router + tests)
|
||||
4. **STOP and VALIDATE**: Guest can view event details via shared link
|
||||
5. Deploy/demo if ready
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. Setup + Backend → Backend ready, API testable via curl
|
||||
2. Add US1 → Guest can view events (MVP!)
|
||||
3. Add US2 → Expired events show "ended" state
|
||||
4. Add US4 → Invalid tokens show "not found"
|
||||
5. Polish → Server error handling, final validation
|
||||
|
||||
### Deferred Work
|
||||
|
||||
- **US3 (Cancelled event)**: Blocked on US-18. No tasks generated. Will require adding `cancelled` + `cancellationMessage` to GetEventResponse and a new UI state.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] tasks = different files, no dependencies on incomplete tasks
|
||||
- [Story] label maps task to specific user story for traceability
|
||||
- `attendeeCount` returns 0 until RSVP feature (US-8+) is implemented (R-3)
|
||||
- `expired` is computed server-side using injected Clock bean (R-4)
|
||||
- Frontend route: `/events/:token` — API path: `/api/events/{token}` (R-5)
|
||||
- Skeleton shimmer is CSS-only, no additional dependencies (R-6)
|
||||
- Date/time formatted via `Intl.DateTimeFormat` with browser locale (spec clarification Q7)
|
||||
Reference in New Issue
Block a user