Restore the community photo after reload and reject empty, oversized, SVG, and spoofed uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilallo
2026-09-10 15:50:32 -06:00
co-authored by Cursor
parent 6ccc1e8c8e
commit 234f3998ad
18 changed files with 1072 additions and 112 deletions
+15 -2
View File
@@ -22,7 +22,20 @@ function openDb(): Promise<IDBDatabase> {
});
}
function coerceToFile(value: unknown): File | null {
if (value instanceof File) return value;
if (typeof Blob !== "undefined" && value instanceof Blob) {
return new File([value], "community-avatar", {
type: value.type || "application/octet-stream",
});
}
return null;
}
export async function storePendingCommunityAvatarFile(file: File): Promise<void> {
if (typeof indexedDB === "undefined") {
throw new Error("indexedDB is not available");
}
const db = await openDb();
try {
await new Promise<void>((resolve, reject) => {
@@ -38,6 +51,7 @@ export async function storePendingCommunityAvatarFile(file: File): Promise<void>
/** Read staged file without removing it (caller clears after successful upload). */
export async function readPendingCommunityAvatarFile(): Promise<File | null> {
if (typeof indexedDB === "undefined") return null;
const db = await openDb();
try {
return await new Promise<File | null>((resolve, reject) => {
@@ -45,8 +59,7 @@ export async function readPendingCommunityAvatarFile(): Promise<File | null> {
tx.onerror = () => reject(tx.error ?? new Error("indexedDB read failed"));
const getReq = tx.objectStore(STORE).get(KEY);
getReq.onsuccess = () => {
const v = getReq.result;
resolve(v instanceof File ? v : null);
resolve(coerceToFile(getReq.result));
};
getReq.onerror = () => reject(getReq.error);
});