Refactor: Implement milestone moderation fixes

This commit is contained in:
gpt-engineer-app[bot]
2025-10-17 17:21:21 +00:00
parent 62c8b7f2c3
commit 026f402057
4 changed files with 103 additions and 44 deletions

View File

@@ -1167,30 +1167,7 @@ export async function submitTimelineEventUpdate(
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: 'milestone',
content,
status: 'pending',
approval_mode: 'full',
})
.select()
.single();
if (submissionError || !submission) {
throw new Error('Failed to create timeline event update submission');
}
// Create submission item
// Prepare item data
const itemData: Record<string, any> = {
entity_type: originalEvent.entity_type,
entity_id: originalEvent.entity_id,
@@ -1205,28 +1182,41 @@ export async function submitTimelineEventUpdate(
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: 'edit',
item_data: itemData as unknown as Json,
original_data: originalEvent as unknown as Json,
status: 'pending',
order_index: 0,
});
// Use atomic RPC function to create submission and item together
const { data: result, error: rpcError } = await supabase.rpc(
'create_submission_with_items',
{
p_user_id: userId,
p_submission_type: 'milestone',
p_content: {
action: 'edit',
event_id: eventId,
entity_type: originalEvent.entity_type,
} as unknown as Json,
p_items: [
{
item_type: 'milestone',
action_type: 'edit',
item_data: itemData,
original_data: originalEvent,
status: 'pending',
order_index: 0,
}
] as unknown as Json[],
}
);
if (itemError) {
throw new Error('Failed to submit timeline event update item');
if (rpcError || !result) {
console.error('Failed to create timeline event update:', rpcError);
throw new Error('Failed to submit timeline event update');
}
return {
submitted: true,
submissionId: submission.id,
submissionId: result,
};
}

View File

@@ -195,18 +195,34 @@ export const milestoneValidationSchema = z.object({
title: z.string().trim().min(1, 'Event title is required').max(200, 'Title must be less than 200 characters'),
description: z.string().trim().max(2000, 'Description must be less than 2000 characters').optional().or(z.literal('')),
event_type: z.string().min(1, 'Event type is required'),
event_date: z.string().min(1, 'Event date is required'),
event_date_precision: z.enum(['day', 'month', 'year']).optional(),
event_date: z.string().min(1, 'Event date is required').refine((val) => {
if (!val) return true;
const date = new Date(val);
const fiveYearsFromNow = new Date();
fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);
return date <= fiveYearsFromNow;
}, 'Event date cannot be more than 5 years in the future'),
event_date_precision: z.enum(['day', 'month', 'year']).optional().default('day'),
entity_type: z.string().min(1, 'Entity type is required'),
entity_id: z.string().uuid('Invalid entity ID'),
is_public: z.boolean().optional(),
display_order: z.number().optional(),
from_value: z.string().optional(),
to_value: z.string().optional(),
from_value: z.string().trim().max(200).optional().or(z.literal('')),
to_value: z.string().trim().max(200).optional().or(z.literal('')),
from_entity_id: z.string().uuid().optional().nullable(),
to_entity_id: z.string().uuid().optional().nullable(),
from_location_id: z.string().uuid().optional().nullable(),
to_location_id: z.string().uuid().optional().nullable(),
}).refine((data) => {
// For change events, require from_value or to_value
const changeEvents = ['name_change', 'operator_change', 'owner_change', 'location_change', 'status_change'];
if (changeEvents.includes(data.event_type)) {
return data.from_value || data.to_value || data.from_entity_id || data.to_entity_id || data.from_location_id || data.to_location_id;
}
return true;
}, {
message: 'Change events must specify what changed (from/to values or entity IDs)',
path: ['from_value'],
});
// ============================================

View File

@@ -212,6 +212,9 @@ export function getSubmissionTypeLabel(submissionType: string): string {
property_owner: 'Property Owner',
ride_model: 'Ride Model',
photo: 'Photo',
photo_delete: 'Photo Deletion',
milestone: 'Timeline Event',
timeline_event: 'Timeline Event',
review: 'Review',
};