87 lines
3.9 KiB
Markdown
87 lines
3.9 KiB
Markdown
# F-056 — Implementer evidence
|
|
|
|
## Scope delivered
|
|
|
|
Two distinct defects were addressed in this branch:
|
|
|
|
1. **Uploads 404 / 502 on freshly uploaded files.** Next.js caches the
|
|
`public/` directory listing at server start, so any file written to
|
|
`public/uploads/` after `next start` returns 404 from the static handler and
|
|
`next/image` answers 502 because its upstream fetch fails.
|
|
2. **Product detail image overflows its container** because the wrapper had
|
|
no positioning context for `next/image fill`.
|
|
|
|
## Changes
|
|
|
|
### 1. Dynamic uploads route (per app)
|
|
|
|
Three new route handlers were added — one per Next.js frontend
|
|
(`apps/admin`, `frontend`, `storefront`). Each reads uploads from the local
|
|
`public/uploads/` and the canonical `apps/admin/public/uploads/` directory on
|
|
every request, so a freshly uploaded file resolves without restart or rebuild:
|
|
|
|
- `project/apps/admin/src/app/uploads/[...path]/route.ts`
|
|
- `project/frontend/src/app/uploads/[...path]/route.ts`
|
|
- `project/storefront/src/app/uploads/[...path]/route.ts`
|
|
|
|
Each handler:
|
|
|
|
- Sets `dynamic = 'force-dynamic'` so Next.js does not cache the route at build.
|
|
- Validates the path against `SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/` to block traversal.
|
|
- Maps a known extension to a content type and serves the file with
|
|
`Cache-Control: public, max-age=31536000, immutable`.
|
|
- Generates the `40` and `200` thumbnails on demand via `sharp` and caches them
|
|
on disk, so `/uploads/40/<file>` and `/uploads/200/<file>` resolve without
|
|
requiring `generate-thumbnails.sh`.
|
|
|
|
### 2. Inline thumbnail generation on upload
|
|
|
|
`project/apps/admin/src/app/api/upload/route.ts` now calls `generateThumbnails`
|
|
immediately after `mirrorToPeers`, producing the 40 and 200 px variants in every
|
|
mirror location. Failures are non-fatal: the dynamic handler above regenerates
|
|
the missing thumbnail on the next request.
|
|
|
|
### 3. Product detail image container
|
|
|
|
`project/frontend/src/app/products/[slug]/page.tsx` adds the `relative` class
|
|
to the image wrapper so `next/image fill` is positioned correctly inside the
|
|
`max-h-[500px] overflow-hidden` container. The image stays inside its box on
|
|
all viewports.
|
|
|
|
## Acceptance traceability
|
|
|
|
| Acceptance criterion | How it is met |
|
|
| -------------------- | ------------- |
|
|
| New upload file loads 200 without server restart | Dynamic `/uploads/[...path]/route.ts` reads from disk per request. Verified with curl on a freshly uploaded file. |
|
|
| Thumbs 40 and 200 exist right after upload | `generateThumbnails` runs inline in `/api/upload` after `mirrorToPeers`. |
|
|
| `next/image` no more 502 for fresh uploads | Static 404 (which became 502 upstream) is replaced by the dynamic handler that returns 200 from disk. |
|
|
| Product detail image stays inside its box | `relative` class added so `next/image fill` is contained by `aspect-square max-h-[500px] overflow-hidden`. |
|
|
|
|
## Commands run during build
|
|
|
|
- `pnpm --filter ./project/apps/admin exec tsc --noEmit` — typecheck pass.
|
|
- `pnpm --filter ./project/frontend exec tsc --noEmit` — typecheck pass.
|
|
- `pnpm --filter ./project/storefront exec tsc --noEmit` — typecheck pass.
|
|
- `./scripts/verify.sh` — exit code 0.
|
|
- Manual curl: `/uploads/40/8223b962-642c-4c95-bc2e-37c2d2fad4ac.jpg` →
|
|
HTTP 200, 568 bytes.
|
|
|
|
## Files touched
|
|
|
|
```
|
|
project/apps/admin/src/app/api/upload/route.ts (modified)
|
|
project/apps/admin/src/app/uploads/[...path]/route.ts (new)
|
|
project/frontend/src/app/uploads/[...path]/route.ts (new)
|
|
project/frontend/src/app/products/[slug]/page.tsx (modified)
|
|
project/storefront/src/app/uploads/[...path]/route.ts (new)
|
|
```
|
|
|
|
## Artifacts produced
|
|
|
|
- `project/apps/admin/public/uploads/40/<file>.jpg` — generated inline by upload route.
|
|
- `project/frontend/public/uploads/40/<file>.jpg` — generated inline by upload route.
|
|
- `project/storefront/public/uploads/40/<file>.jpg` — generated inline by upload route.
|
|
|
|
## Status
|
|
|
|
Build completed. Ready for reviewer/security/qa gates. |