Fix migration for admin settings

This commit is contained in:
gpt-engineer-app[bot]
2025-11-05 13:40:25 +00:00
parent ec5181b9e6
commit 80826a83a8
5 changed files with 619 additions and 112 deletions

View File

@@ -16,6 +16,8 @@ import { useAuth } from "@/hooks/useAuth";
import { useToast } from "@/hooks/use-toast";
import { Camera, CheckCircle, AlertCircle, Info } from "lucide-react";
import { UppyPhotoSubmissionUploadProps } from "@/types/submissions";
import { withRetry } from "@/lib/retryHelpers";
import { logger } from "@/lib/logger";
export function UppyPhotoSubmissionUpload({
onSubmissionComplete,
@@ -94,66 +96,92 @@ export function UppyPhotoSubmissionUpload({
setPhotos((prev) => prev.map((p) => (p === photo ? { ...p, uploadStatus: "uploading" as const } : p)));
try {
// Get upload URL from edge function
const { data: uploadData, error: uploadError } = await invokeWithTracking(
"upload-image",
{ metadata: { requireSignedURLs: false }, variant: "public" },
user?.id,
);
// Wrap Cloudflare upload in retry logic
const cloudflareUrl = await withRetry(
async () => {
// Get upload URL from edge function
const { data: uploadData, error: uploadError } = await invokeWithTracking(
"upload-image",
{ metadata: { requireSignedURLs: false }, variant: "public" },
user?.id,
);
if (uploadError) throw uploadError;
if (uploadError) throw uploadError;
const { uploadURL, id: cloudflareId } = uploadData;
const { uploadURL, id: cloudflareId } = uploadData;
// Upload file to Cloudflare
if (!photo.file) {
throw new Error("Photo file is missing");
}
const formData = new FormData();
formData.append("file", photo.file);
// Upload file to Cloudflare
if (!photo.file) {
throw new Error("Photo file is missing");
}
const formData = new FormData();
formData.append("file", photo.file);
const uploadResponse = await fetch(uploadURL, {
method: "POST",
body: formData,
});
const uploadResponse = await fetch(uploadURL, {
method: "POST",
body: formData,
});
if (!uploadResponse.ok) {
throw new Error("Failed to upload to Cloudflare");
}
if (!uploadResponse.ok) {
throw new Error("Failed to upload to Cloudflare");
}
// Poll for processing completion
let attempts = 0;
const maxAttempts = 30;
let cloudflareUrl = "";
// Poll for processing completion
let attempts = 0;
const maxAttempts = 30;
let cloudflareUrl = "";
while (attempts < maxAttempts) {
const {
data: { session },
} = await supabase.auth.getSession();
const supabaseUrl = "https://api.thrillwiki.com";
const statusResponse = await fetch(`${supabaseUrl}/functions/v1/upload-image?id=${cloudflareId}`, {
headers: {
Authorization: `Bearer ${session?.access_token || ""}`,
apikey:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlkdnRtbnJzenlicW5iY3FiZGN5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTgzMjYzNTYsImV4cCI6MjA3MzkwMjM1Nn0.DM3oyapd_omP5ZzIlrT0H9qBsiQBxBRgw2tYuqgXKX4",
},
});
while (attempts < maxAttempts) {
const {
data: { session },
} = await supabase.auth.getSession();
const supabaseUrl = "https://api.thrillwiki.com";
const statusResponse = await fetch(`${supabaseUrl}/functions/v1/upload-image?id=${cloudflareId}`, {
headers: {
Authorization: `Bearer ${session?.access_token || ""}`,
apikey:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlkdnRtbnJzenlicW5iY3FiZGN5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTgzMjYzNTYsImV4cCI6MjA3MzkwMjM1Nn0.DM3oyapd_omP5ZzIlrT0H9qBsiQBxBRgw2tYuqgXKX4",
},
});
if (statusResponse.ok) {
const status = await statusResponse.json();
if (status.uploaded && status.urls) {
cloudflareUrl = status.urls.public;
break;
if (statusResponse.ok) {
const status = await statusResponse.json();
if (status.uploaded && status.urls) {
cloudflareUrl = status.urls.public;
break;
}
}
await new Promise((resolve) => setTimeout(resolve, 1000));
attempts++;
}
if (!cloudflareUrl) {
throw new Error("Upload processing timeout");
}
return cloudflareUrl;
},
{
onRetry: (attempt, error, delay) => {
logger.warn('Retrying photo upload', {
attempt,
delay,
fileName: photo.file?.name
});
// Emit event for UI indicator
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: {
attempt,
maxAttempts: 3,
delay,
type: 'photo upload'
}
}));
}
}
await new Promise((resolve) => setTimeout(resolve, 1000));
attempts++;
}
if (!cloudflareUrl) {
throw new Error("Upload processing timeout");
}
);
// Revoke object URL
URL.revokeObjectURL(photo.url);
@@ -185,59 +213,78 @@ export function UppyPhotoSubmissionUpload({
setUploadProgress(null);
// Create content_submission record first
const { data: submissionData, error: submissionError } = await supabase
.from("content_submissions")
.insert({
user_id: user.id,
submission_type: "photo",
content: {}, // Empty content, all data is in relational tables
})
.select()
.single();
// Create submission records with retry logic
await withRetry(
async () => {
// Create content_submission record first
const { data: submissionData, error: submissionError } = await supabase
.from("content_submissions")
.insert({
user_id: user.id,
submission_type: "photo",
content: {}, // Empty content, all data is in relational tables
})
.select()
.single();
if (submissionError || !submissionData) {
throw submissionError || new Error("Failed to create submission record");
}
if (submissionError || !submissionData) {
throw submissionError || new Error("Failed to create submission record");
}
// Create photo_submission record
const { data: photoSubmissionData, error: photoSubmissionError } = await supabase
.from("photo_submissions")
.insert({
submission_id: submissionData.id,
entity_type: entityType,
entity_id: entityId,
parent_id: parentId || null,
title: title.trim() || null,
})
.select()
.single();
// Create photo_submission record
const { data: photoSubmissionData, error: photoSubmissionError } = await supabase
.from("photo_submissions")
.insert({
submission_id: submissionData.id,
entity_type: entityType,
entity_id: entityId,
parent_id: parentId || null,
title: title.trim() || null,
})
.select()
.single();
if (photoSubmissionError || !photoSubmissionData) {
throw photoSubmissionError || new Error("Failed to create photo submission");
}
if (photoSubmissionError || !photoSubmissionData) {
throw photoSubmissionError || new Error("Failed to create photo submission");
}
// Insert all photo items
const photoItems = photos.map((photo, index) => ({
photo_submission_id: photoSubmissionData.id,
cloudflare_image_id: photo.url.split("/").slice(-2, -1)[0] || "", // Extract ID from URL
cloudflare_image_url:
photo.uploadStatus === "uploaded"
? photo.url
: uploadedPhotos.find((p) => p.order === photo.order)?.url || photo.url,
caption: photo.caption.trim() || null,
title: photo.title?.trim() || null,
filename: photo.file?.name || null,
order_index: index,
file_size: photo.file?.size || null,
mime_type: photo.file?.type || null,
}));
// Insert all photo items
const photoItems = photos.map((photo, index) => ({
photo_submission_id: photoSubmissionData.id,
cloudflare_image_id: photo.url.split("/").slice(-2, -1)[0] || "", // Extract ID from URL
cloudflare_image_url:
photo.uploadStatus === "uploaded"
? photo.url
: uploadedPhotos.find((p) => p.order === photo.order)?.url || photo.url,
caption: photo.caption.trim() || null,
title: photo.title?.trim() || null,
filename: photo.file?.name || null,
order_index: index,
file_size: photo.file?.size || null,
mime_type: photo.file?.type || null,
}));
const { error: itemsError } = await supabase.from("photo_submission_items").insert(photoItems);
const { error: itemsError } = await supabase.from("photo_submission_items").insert(photoItems);
if (itemsError) {
throw itemsError;
}
if (itemsError) {
throw itemsError;
}
},
{
onRetry: (attempt, error, delay) => {
logger.warn('Retrying photo submission creation', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: {
attempt,
maxAttempts: 3,
delay,
type: 'photo submission'
}
}));
}
}
);
toast({
title: "Submission Successful",
@@ -259,7 +306,12 @@ export function UppyPhotoSubmissionUpload({
handleError(error, {
action: 'Submit Photo Submission',
userId: user?.id,
metadata: { entityType, entityId, photoCount: photos.length }
metadata: {
entityType,
entityId,
photoCount: photos.length,
retriesExhausted: true
}
});
toast({