diff --git a/CLIENT_MIGRATION.md b/CLIENT_MIGRATION.md new file mode 100644 index 0000000..3e85ace --- /dev/null +++ b/CLIENT_MIGRATION.md @@ -0,0 +1,121 @@ +# Client migration — `UploadDto` no longer returns raw storage keys + +**Affects:** `C:\Code\utmmsa-pwa` (and any other consumer of the generated client) +**Breaking:** yes — the *values* of `fileUrl` and `thumbnailUrl` change meaning, and `uploadedBy` / +`instagramHandle` can now be `null` where they previously were not. + +## Endpoints affected + +Every endpoint returning `UploadDto` or `Page`: + +| Method | Path | Handler | +| --- | --- | --- | +| GET | `/api/user/uploads` | `UserController.getUserUploads` | +| GET | `/api/upload/{uploadId}` | `FileUploadController.getUploadById` | +| GET | `/api/upload/event/{eventId}` | `FileUploadController.getUploadsByEvent` | + +`AdminUploadDto` (the `/api/admin/uploads/**` family) is **unchanged** — it already returned presigned +URLs in `secureUrl` / `thumbnailUrl`. + +## What changed + +### 1. `fileUrl` and `thumbnailUrl` are now absolute presigned URLs + +Before — the verbatim R2 object key: + +```json +{ "fileUrl": "images/e5c6f37c-845d-4252-9a2f-16e5e68eee96", + "thumbnailUrl": "thumbnails/e5c6f37c-845d-4252-9a2f-16e5e68eee96" } +``` + +After — a scoped, signed, expiring URL on the media domain: + +```json +{ "fileUrl": "https://media.lensbridge.tech/images/e5c6f37c-…?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=…", + "thumbnailUrl": "https://media.lensbridge.tech/thumbnails/e5c6f37c-…?…&X-Amz-Signature=…", + "urlExpiresAt": "2026-08-25T18:45:00Z" } +``` + +**Why:** `media.lensbridge.tech` is a public custom domain in front of the bucket (see +`PosterService.java:100,181`, which serves `publicUrl + "/" + objectKey` unsigned). Returning the key +was therefore equivalent to returning the media itself, to anyone who obtained the JSON, forever, with +no approval check. The presign step is also where the approval gate lives — +`R2StorageService.getSecureUrl` refuses unapproved content to unprivileged callers. + +**Frontend change required:** stop concatenating. Anywhere the PWA does + +```ts +const src = `${MEDIA_BASE_URL}/${upload.fileUrl}`; // ← delete this +``` + +use the value directly: + +```ts +const src = upload.fileUrl; // already absolute +``` + +Grep the PWA for the media base-URL constant (`VITE_MEDIA_URL` / `media.lensbridge.tech` / +any `${...}/${upload.fileUrl}` template) and remove the join at every call site, including +``, `