# Tasks — Profile picture upload > Red/Green TDD order: each implementation task is preceded by the failing test that > requires it. Task IDs are referenced from `spec.md` → Traceability and from `.specify/rtm.md`. > Check off as work lands; reference the issue in each commit (`Refs #`). ## Backend - [ ] **T-1** Add `ErrorCode.AVATAR_TOO_LARGE` in all four sites at once: `ErrorCode.java`, `frontend/src/lib/shared/errors.ts`, `getErrorMessage()`, `messages/{de,en,es}.json`. *(No new behavior yet — enables REQ-008's error.)* → covers REQ-008 (error plumbing) - [ ] **T-2** `@WebMvcTest` `UserAvatarControllerTest`: write failing slice tests — `unauthenticatedReturns401`, `rejectsNonImage` (400 UNSUPPORTED_FILE_TYPE), `rejectsOversize` (400 AVATAR_TOO_LARGE). Then implement `UserAvatarController` + `@RequirePermission` to green. → REQ-006, REQ-007, REQ-008 - [ ] **T-3** Unit `UserServiceAvatarTest`: failing tests `storesUnderUserKey`, `replaceLeavesNoOrphan`, validation maps to `DomainException`. Then implement `UserService.setAvatar`/`removeAvatar` (mock `FileService`) to green. → REQ-001, REQ-002, REQ-003 - [ ] **T-4** Flyway `V78__add_app_user_avatar_object_key.sql` (verify next free number on disk) adding nullable `avatar_object_key VARCHAR(512)`; add the column + `@Schema` to `AppUser` / `UserProfileView` (`avatarUrl` derived). Test: repository round-trip. → REQ-002 - [ ] **T-5** `deleteMyAvatar` controller test + impl (clears key, deletes object, returns `avatarUrl: null`). → REQ-003 - [ ] **T-6** Admin path: failing tests `adminDeletesOthersAvatar` (200), `nonAdminForbiddenOnOthers` (403). Implement ownership/`ADMIN_USER` check to green. → REQ-005, REQ-009 - [ ] **T-7** Authenticated proxy `getUserAvatar` streaming endpoint + `Content-Type` + `X-Content-Type-Options: nosniff`; test 200 bytes / 404 when no avatar. → REQ-004 (view side) - [ ] **T-A** Run `npm run generate:api` after T-4/T-7 so `avatarUrl` lands in `api.ts`. ## Frontend - [ ] **T-8** i18n keys for the new strings in `messages/{de,en,es}.json` (button labels, validation errors mapped via `getErrorMessage`). → REQ-007, REQ-008 (UX) - [ ] **T-9** Component test `avatar-placeholder.svelte.spec.ts`: failing test asserting initials render when `avatarUrl` is null; implement the placeholder. → REQ-004 - [ ] **T-10** `/profile` upload control: file picker, client-side type/size pre-check, instant preview, confirm/remove. States: idle/preview/uploading/error/done. → REQ-002, REQ-003 - [ ] **T-11** Render avatar where names appear (comments, activity feed, `/users/[id]`), falling back to the placeholder. → REQ-004 - [ ] **T-12** E2E `avatar.spec.ts`: upload → preview → confirm → avatar visible; remove → initials return. → REQ-002, REQ-003, REQ-004 ## Cross-cutting - [ ] **T-13** Set `spring.servlet.multipart.max-file-size` to a 2 MB-matching ceiling so an oversized body is rejected at the container edge (defense in depth for REQ-008). - [ ] **T-14** Update `.specify/rtm.md` Status column to `Done` per REQ as each test goes green.