Spaces:
Running
Running
Optimize profile image caching
Browse files- README.md +1 -1
- src/profile-images.ts +3 -2
README.md
CHANGED
|
@@ -54,7 +54,7 @@ Node health: `GET /health`. AI health is internal at `http://127.0.0.1:8000/heal
|
|
| 54 |
|
| 55 |
Run one container with one Python worker: analysis jobs and retry payloads are in memory and are lost on restart. Temporary local files are ephemeral. Completed videos and `results.json` are stored in the private HF dataset; Node stores session metadata and frame indices in Supabase. Model constants are in `app.py`.
|
| 56 |
|
| 57 |
-
The production database must have the analysis functions and action-review structures used by secret-protected `/internal/analysis` routes. The database also needs the profile columns shown in `../Database/schema.sql` and a private `profile-images` Supabase Storage bucket for JPG, PNG, and WEBP files up to 4 MB. The backend reads and writes images through its service-role client; browsers access them through authenticated API routes.
|
| 58 |
|
| 59 |
Before deploying access-control or deletion changes, run [`../Database/updates/access-permissions.sql`](../Database/updates/access-permissions.sql) in the Supabase SQL Editor. This update adds the admin-origin flag, nullable owner references for retained records, cascading action deletion, and the service-role RPCs used for quota refunds and analysis completion. All service-role reads and writes must remain behind the ownership and admin checks in the API. The full policy is documented in [`../Database/access-permissions.md`](../Database/access-permissions.md).
|
| 60 |
|
|
|
|
| 54 |
|
| 55 |
Run one container with one Python worker: analysis jobs and retry payloads are in memory and are lost on restart. Temporary local files are ephemeral. Completed videos and `results.json` are stored in the private HF dataset; Node stores session metadata and frame indices in Supabase. Model constants are in `app.py`.
|
| 56 |
|
| 57 |
+
The production database must have the analysis functions and action-review structures used by secret-protected `/internal/analysis` routes. The database also needs the profile columns shown in `../Database/schema.sql` and a private `profile-images` Supabase Storage bucket for JPG, PNG, and WEBP files up to 4 MB. The frontend resizes profile photos to at most 1280 pixels and encodes them as WebP before upload. The backend reads and writes images through its service-role client; browsers access them through authenticated API routes with private, versioned browser caching.
|
| 58 |
|
| 59 |
Before deploying access-control or deletion changes, run [`../Database/updates/access-permissions.sql`](../Database/updates/access-permissions.sql) in the Supabase SQL Editor. This update adds the admin-origin flag, nullable owner references for retained records, cascading action deletion, and the service-role RPCs used for quota refunds and analysis completion. All service-role reads and writes must remain behind the ownership and admin checks in the API. The full policy is documented in [`../Database/access-permissions.md`](../Database/access-permissions.md).
|
| 60 |
|
src/profile-images.ts
CHANGED
|
@@ -48,7 +48,8 @@ async function sendImage(res: Response, path: string | null | undefined) {
|
|
| 48 |
const type = path.endsWith(".png") ? "image/png" : path.endsWith(".webp") ? "image/webp" : "image/jpeg";
|
| 49 |
res.set({
|
| 50 |
"Content-Type": type,
|
| 51 |
-
|
|
|
|
| 52 |
"Cross-Origin-Resource-Policy": "cross-origin",
|
| 53 |
"X-Content-Type-Options": "nosniff",
|
| 54 |
});
|
|
@@ -65,7 +66,7 @@ async function uploadImage(req: Request, res: Response, owner: Owner, id: string
|
|
| 65 |
const path = `${owner}/${id}/${randomUUID()}.${type.extension}`;
|
| 66 |
const { error: uploadError } = await supabaseAdmin.storage.from(BUCKET).upload(path, bytes, {
|
| 67 |
contentType: type.mime,
|
| 68 |
-
cacheControl: "
|
| 69 |
upsert: false,
|
| 70 |
});
|
| 71 |
if (uploadError) throw new Error(`Unable to upload profile image: ${uploadError.message}`);
|
|
|
|
| 48 |
const type = path.endsWith(".png") ? "image/png" : path.endsWith(".webp") ? "image/webp" : "image/jpeg";
|
| 49 |
res.set({
|
| 50 |
"Content-Type": type,
|
| 51 |
+
// Each upload gets a new UUID path, so this response never needs revalidation.
|
| 52 |
+
"Cache-Control": "private, max-age=31536000, immutable",
|
| 53 |
"Cross-Origin-Resource-Policy": "cross-origin",
|
| 54 |
"X-Content-Type-Options": "nosniff",
|
| 55 |
});
|
|
|
|
| 66 |
const path = `${owner}/${id}/${randomUUID()}.${type.extension}`;
|
| 67 |
const { error: uploadError } = await supabaseAdmin.storage.from(BUCKET).upload(path, bytes, {
|
| 68 |
contentType: type.mime,
|
| 69 |
+
cacheControl: "31536000",
|
| 70 |
upsert: false,
|
| 71 |
});
|
| 72 |
if (uploadError) throw new Error(`Unable to upload profile image: ${uploadError.message}`);
|