Approve database migration

This commit is contained in:
gpt-engineer-app[bot]
2025-10-15 19:29:06 +00:00
parent 359a156260
commit 444634dc85
9 changed files with 1020 additions and 0 deletions

View File

@@ -134,6 +134,9 @@ export interface CompanyFormData {
card_image_id?: string;
}
// Import timeline types
import type { TimelineEventFormData, TimelineSubmissionData, EntityType } from '@/types/timeline';
/**
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
*
@@ -896,3 +899,185 @@ export async function submitPropertyOwnerUpdate(
return { submitted: true, submissionId: submissionData.id };
}
/**
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
*
* Submits a new timeline event for an entity through the moderation queue.
*
* DO NOT write directly to entity_timeline_events:
* ❌ await supabase.from('entity_timeline_events').insert(data) // BYPASSES MODERATION!
* ✅ await submitTimelineEvent(entityType, entityId, data, userId) // CORRECT
*
* Flow: User Submit → Moderation Queue → Approval → Database Write
*
* @param entityType - Type of entity (park, ride, company)
* @param entityId - ID of the entity
* @param data - The timeline event form data
* @param userId - The ID of the user submitting
* @returns Object containing submitted boolean and submissionId
*/
export async function submitTimelineEvent(
entityType: EntityType,
entityId: string,
data: TimelineEventFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Validate user
if (!userId) {
throw new Error('User ID is required for timeline event submission');
}
// Create submission content (minimal reference data only)
const content: Json = {
action: 'create',
entity_type: entityType,
entity_id: entityId,
};
// Create the main submission record
const { data: submission, error: submissionError } = await supabase
.from('content_submissions')
.insert({
user_id: userId,
submission_type: 'timeline_event',
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
const itemData: Record<string, any> = {
entity_type: entityType,
entity_id: entityId,
event_type: data.event_type,
event_date: data.event_date.toISOString().split('T')[0],
event_date_precision: data.event_date_precision,
title: data.title,
description: data.description,
from_value: data.from_value,
to_value: data.to_value,
from_entity_id: data.from_entity_id,
to_entity_id: data.to_entity_id,
from_location_id: data.from_location_id,
to_location_id: data.to_location_id,
is_public: data.is_public ?? true,
};
const { error: itemError } = await supabase
.from('submission_items')
.insert({
submission_id: submission.id,
item_type: 'timeline_event',
action_type: 'create',
item_data: itemData as unknown as Json,
status: 'pending',
order_index: 0,
});
if (itemError) {
console.error('Failed to create timeline event item:', itemError);
throw new Error('Failed to submit timeline event item for review');
}
return {
submitted: true,
submissionId: submission.id,
};
}
/**
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
*
* Submits an update to an existing timeline event through the moderation queue.
*
* @param eventId - ID of the existing timeline event
* @param data - The updated timeline event data
* @param userId - The ID of the user submitting the update
* @returns Object containing submitted boolean and submissionId
*/
export async function submitTimelineEventUpdate(
eventId: string,
data: TimelineEventFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Fetch original event
const { data: originalEvent, error: fetchError } = await supabase
.from('entity_timeline_events')
.select('*')
.eq('id', eventId)
.single();
if (fetchError || !originalEvent) {
throw new Error('Failed to fetch original timeline event');
}
// Create submission
const content: Json = {
action: 'edit',
event_id: eventId,
entity_type: originalEvent.entity_type,
};
const { data: submission, error: submissionError } = await supabase
.from('content_submissions')
.insert({
user_id: userId,
submission_type: 'timeline_event',
content,
status: 'pending',
approval_mode: 'full',
})
.select()
.single();
if (submissionError || !submission) {
throw new Error('Failed to create timeline event update submission');
}
// Create submission item
const itemData: Record<string, any> = {
entity_type: originalEvent.entity_type,
entity_id: originalEvent.entity_id,
event_type: data.event_type,
event_date: data.event_date.toISOString().split('T')[0],
event_date_precision: data.event_date_precision,
title: data.title,
description: data.description,
from_value: data.from_value,
to_value: data.to_value,
from_entity_id: data.from_entity_id,
to_entity_id: data.to_entity_id,
from_location_id: data.from_location_id,
to_location_id: data.to_location_id,
is_public: data.is_public ?? true,
};
const { error: itemError } = await supabase
.from('submission_items')
.insert({
submission_id: submission.id,
item_type: 'timeline_event',
action_type: 'edit',
item_data: itemData as unknown as Json,
original_data: originalEvent as unknown as Json,
status: 'pending',
order_index: 0,
});
if (itemError) {
throw new Error('Failed to submit timeline event update item');
}
return {
submitted: true,
submissionId: submission.id,
};
}