Move hack cover storage to S3

This commit is contained in:
Jared Schoeny
2025-12-03 21:11:42 -10:00
parent 18e83b58c4
commit 6241eeace6
8 changed files with 96 additions and 58 deletions

View File

@@ -12,6 +12,7 @@ import { BsSdCardFill } from "react-icons/bs";
import { CATEGORY_ICONS } from "@/components/Icons/tagCategories";
import { useBaseRoms } from "@/contexts/BaseRomContext";
import { sortOrderedTags, OrderedTag } from "@/utils/format";
import { getCoverSignedUrls } from "@/app/hack/actions";
export default function DiscoverBrowser() {
@@ -66,26 +67,22 @@ export default function DiscoverBrowser() {
.order("position", { ascending: true });
const coversBySlug = new Map<string, string[]>();
if (coverRows && coverRows.length > 0) {
const { data: imagesData } = await supabase.storage
.from('hack-covers')
.createSignedUrls(coverRows.map(c => c.url), 60 * 5);
if (imagesData) {
// Map: storage object url -> signedUrl
const urlToSignedUrl = new Map<string, string>();
imagesData.forEach((d, idx) => {
// If creation fails, d.signedUrl might be undefined; filter those out
if (d.signedUrl) urlToSignedUrl.set(coverRows[idx].url, d.signedUrl);
});
const coverKeys = coverRows.map(c => c.url);
const urls = await getCoverSignedUrls(coverKeys);
// Map: storage object url -> signedUrl
const urlToSignedUrl = new Map<string, string>();
coverKeys.forEach((key, idx) => {
if (urls[idx]) urlToSignedUrl.set(key, urls[idx]);
});
coverRows.forEach((c) => {
const arr = coversBySlug.get(c.hack_slug) || [];
const signed = urlToSignedUrl.get(c.url);
if (signed) {
arr.push(signed);
coversBySlug.set(c.hack_slug, arr);
}
});
}
coverRows.forEach((c) => {
const arr = coversBySlug.get(c.hack_slug) || [];
const signed = urlToSignedUrl.get(c.url);
if (signed) {
arr.push(signed);
coversBySlug.set(c.hack_slug, arr);
}
});
}
const { data: tagRows } = await supabase
.from("hack_tags")

View File

@@ -8,8 +8,7 @@ import TagSelector from "@/components/Submit/TagSelector";
import { baseRoms } from "@/data/baseRoms";
import Image from "next/image";
import { createClient } from "@/utils/supabase/client";
import { updateHack } from "@/app/hack/actions";
import { saveHackCovers } from "@/app/hack/actions";
import { updateHack, saveHackCovers, presignCoverUpload } from "@/app/hack/actions";
import SortableCovers from "@/components/Hack/SortableCovers";
interface HackEditFormProps {
@@ -65,7 +64,7 @@ export default function HackEditForm({ slug, initial }: HackEditFormProps) {
const [coverItems, setCoverItems] = React.useState<CoverItem[]>(() => {
const keys = initial.coverKeys || [];
const urls = initial.signedCoverUrls || [];
return keys.map((k, i) => ({ type: "existing", key: k, url: urls[i] || supabase.storage.from('hack-covers').getPublicUrl(k).data.publicUrl }));
return keys.map((k, i) => ({ type: "existing", key: k, url: urls[i] || '' }));
});
const [coversBaseline, setCoversBaseline] = React.useState<{ keys: string[]; urls: string[] }>(() => ({
keys: initial.coverKeys || [],
@@ -198,8 +197,9 @@ export default function HackEditForm({ slug, initial }: HackEditFormProps) {
} else {
const ext = item.file.name.split('.').pop();
const path = `${slug}/${Date.now()}-${i}.${ext}`;
const { error } = await supabase.storage.from('hack-covers').upload(path, item.file);
if (error) throw error;
const presigned = await presignCoverUpload({ slug, objectKey: path });
if (!presigned.ok) throw new Error(presigned.error || 'Failed to presign cover upload');
await fetch(presigned.presignedUrl, { method: 'PUT', body: item.file, headers: { 'Content-Type': item.file.type || 'image/jpeg' } });
keys.push(path);
}
}
@@ -329,7 +329,7 @@ export default function HackEditForm({ slug, initial }: HackEditFormProps) {
<h2 className="text-xl font-semibold tracking-tight">Screenshots</h2>
<div className="flex items-center gap-2">
{coversChanged && (
<button type="button" onClick={() => setCoverItems(coversBaseline.keys.map((k, i) => ({ type: 'existing' as const, key: k, url: coversBaseline.urls[i] || supabase.storage.from('hack-covers').getPublicUrl(k).data.publicUrl })))} className="inline-flex h-8 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-[12px] cursor-pointer">
<button type="button" onClick={() => setCoverItems(coversBaseline.keys.map((k, i) => ({ type: 'existing' as const, key: k, url: coversBaseline.urls[i] || '' })))} className="inline-flex h-8 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-[12px] cursor-pointer">
Revert
</button>
)}

View File

@@ -6,6 +6,7 @@ import { baseRoms } from "@/data/baseRoms";
import HackCard from "@/components/HackCard";
import { createClient } from "@/utils/supabase/client";
import { prepareSubmission, presignPatchAndSaveCovers, confirmPatchUpload } from "@/app/submit/actions";
import { presignCoverUpload } from "@/app/hack/actions";
import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
@@ -168,8 +169,9 @@ export default function HackSubmitForm({
const file = newCoverFiles[i];
const fileExt = file.name.split('.').pop();
const path = `${slug}/${Date.now()}-${i}.${fileExt}`;
const { error } = await supabase.storage.from('hack-covers').upload(path, file);
if (error) throw error;
const presigned = await presignCoverUpload({ slug, objectKey: path });
if (!presigned.ok) throw new Error(presigned.error || 'Failed to presign cover upload');
await fetch(presigned.presignedUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type || 'image/jpeg' } });
urls.push(path);
}
return urls;