mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 09:11:13 -05:00
Implement submission protections
This commit is contained in:
@@ -93,6 +93,13 @@ export function SubmissionReviewManager({
|
||||
}
|
||||
|
||||
const fetchedItems = await fetchSubmissionItems(submissionId);
|
||||
|
||||
// Protection 2: Detect empty submissions
|
||||
if (!fetchedItems || fetchedItems.length === 0) {
|
||||
setItems([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsWithDeps = buildDependencyTree(fetchedItems);
|
||||
setItems(itemsWithDeps);
|
||||
|
||||
@@ -524,6 +531,49 @@ export function SubmissionReviewManager({
|
||||
);
|
||||
|
||||
function ReviewContent() {
|
||||
// Protection 2: UI detection of empty submissions
|
||||
if (items.length === 0 && !loading) {
|
||||
return (
|
||||
<Alert variant="destructive" className="my-4">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
This submission has no items and appears to be corrupted or incomplete.
|
||||
This usually happens when the submission creation process was interrupted.
|
||||
<div className="mt-2 flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
await supabase
|
||||
.from('content_submissions')
|
||||
.delete()
|
||||
.eq('id', submissionId);
|
||||
|
||||
toast({
|
||||
title: 'Submission Archived',
|
||||
description: 'The corrupted submission has been removed',
|
||||
});
|
||||
onComplete();
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: getErrorMessage(error),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Archive Submission
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<Tabs
|
||||
|
||||
@@ -3590,10 +3590,23 @@ export type Database = {
|
||||
Args: { entity_type: string; keep_versions?: number }
|
||||
Returns: number
|
||||
}
|
||||
cleanup_orphaned_submissions: {
|
||||
Args: Record<PropertyKey, never>
|
||||
Returns: number
|
||||
}
|
||||
cleanup_rate_limits: {
|
||||
Args: Record<PropertyKey, never>
|
||||
Returns: undefined
|
||||
}
|
||||
create_submission_with_items: {
|
||||
Args: {
|
||||
p_content: Json
|
||||
p_items: Json[]
|
||||
p_submission_type: string
|
||||
p_user_id: string
|
||||
}
|
||||
Returns: string
|
||||
}
|
||||
extend_submission_lock: {
|
||||
Args: {
|
||||
extension_duration?: unknown
|
||||
|
||||
@@ -1097,24 +1097,7 @@ export async function submitTimelineEvent(
|
||||
};
|
||||
|
||||
// Create the main submission record
|
||||
const { data: submission, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
submission_type: 'milestone',
|
||||
content,
|
||||
status: 'pending',
|
||||
approval_mode: 'full',
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (submissionError || !submission) {
|
||||
console.error('Failed to create timeline event submission:', submissionError);
|
||||
throw new Error('Failed to submit timeline event for review');
|
||||
}
|
||||
|
||||
// Create submission item with actual data
|
||||
// Use atomic RPC function to create submission + items in transaction
|
||||
const itemData: Record<string, any> = {
|
||||
entity_type: entityType,
|
||||
entity_id: entityId,
|
||||
@@ -1129,28 +1112,32 @@ export async function submitTimelineEvent(
|
||||
to_entity_id: data.to_entity_id,
|
||||
from_location_id: data.from_location_id,
|
||||
to_location_id: data.to_location_id,
|
||||
is_public: true, // All timeline events are public
|
||||
is_public: true,
|
||||
};
|
||||
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submission.id,
|
||||
item_type: 'milestone',
|
||||
action_type: 'create',
|
||||
item_data: itemData as unknown as Json,
|
||||
status: 'pending',
|
||||
order_index: 0,
|
||||
const items = [{
|
||||
item_type: 'milestone',
|
||||
action_type: 'create',
|
||||
item_data: itemData,
|
||||
order_index: 0,
|
||||
}];
|
||||
|
||||
const { data: submissionId, error } = await supabase
|
||||
.rpc('create_submission_with_items', {
|
||||
p_user_id: userId,
|
||||
p_submission_type: 'milestone',
|
||||
p_content: content,
|
||||
p_items: items as any,
|
||||
});
|
||||
|
||||
if (itemError) {
|
||||
console.error('Failed to create timeline event item:', itemError);
|
||||
throw new Error('Failed to submit timeline event item for review');
|
||||
if (error || !submissionId) {
|
||||
console.error('Failed to create timeline event submission:', error);
|
||||
throw new Error('Failed to submit timeline event for review');
|
||||
}
|
||||
|
||||
return {
|
||||
submitted: true,
|
||||
submissionId: submission.id,
|
||||
submissionId: submissionId,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user