Compare commits

...

5 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
d00ea2a3ee Fix 406 errors in validation 2025-11-06 04:47:35 +00:00
gpt-engineer-app[bot]
5c24038470 Refactor moderation queue display 2025-11-06 04:42:00 +00:00
gpt-engineer-app[bot]
93e8e98957 Fix: Display temp location data 2025-11-06 04:37:48 +00:00
gpt-engineer-app[bot]
c8a015a15b Fix park type and moderator ID 2025-11-06 04:33:26 +00:00
gpt-engineer-app[bot]
93e48ac457 Fix park type and moderator ID 2025-11-06 04:31:58 +00:00
7 changed files with 165 additions and 21 deletions

View File

@@ -93,14 +93,14 @@ interface ParkFormProps {
}
const parkTypes = [
'Theme Park',
'Amusement Park',
'Water Park',
'Family Entertainment Center',
'Adventure Park',
'Safari Park',
'Carnival',
'Fair'
{ value: 'theme_park', label: 'Theme Park' },
{ value: 'amusement_park', label: 'Amusement Park' },
{ value: 'water_park', label: 'Water Park' },
{ value: 'family_entertainment', label: 'Family Entertainment Center' },
{ value: 'adventure_park', label: 'Adventure Park' },
{ value: 'safari_park', label: 'Safari Park' },
{ value: 'carnival', label: 'Carnival' },
{ value: 'fair', label: 'Fair' }
];
const statusOptions = [
@@ -363,8 +363,8 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
</SelectTrigger>
<SelectContent>
{parkTypes.map((type) => (
<SelectItem key={type} value={type}>
{type}
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>

View File

@@ -177,7 +177,7 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
);
}
// Use rich displays for detailed view
// Use rich displays for detailed view - show BOTH rich display AND field-by-field changes
if (item.item_type === 'park' && entityData) {
return (
<>
@@ -186,6 +186,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as ParkSubmissionData}
actionType={actionType}
/>
<div className="mt-6 pt-6 border-t">
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
All Fields (Detailed View)
</div>
<SubmissionChangesDisplay
item={item}
view="detailed"
showImages={showImages}
submissionId={submissionId}
/>
</div>
</>
);
}
@@ -198,6 +209,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as RideSubmissionData}
actionType={actionType}
/>
<div className="mt-6 pt-6 border-t">
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
All Fields (Detailed View)
</div>
<SubmissionChangesDisplay
item={item}
view="detailed"
showImages={showImages}
submissionId={submissionId}
/>
</div>
</>
);
}
@@ -210,6 +232,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as CompanySubmissionData}
actionType={actionType}
/>
<div className="mt-6 pt-6 border-t">
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
All Fields (Detailed View)
</div>
<SubmissionChangesDisplay
item={item}
view="detailed"
showImages={showImages}
submissionId={submissionId}
/>
</div>
</>
);
}
@@ -222,6 +255,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as RideModelSubmissionData}
actionType={actionType}
/>
<div className="mt-6 pt-6 border-t">
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
All Fields (Detailed View)
</div>
<SubmissionChangesDisplay
item={item}
view="detailed"
showImages={showImages}
submissionId={submissionId}
/>
</div>
</>
);
}

View File

@@ -21,7 +21,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
if (!data) return;
const fetchRelatedData = async () => {
// Fetch location
// Fetch location if location_id exists (for edits)
if (data.location_id) {
const { data: locationData } = await supabase
.from('locations')
@@ -29,6 +29,10 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
.eq('id', data.location_id)
.single();
setLocation(locationData);
}
// Otherwise use temp_location_data (for new submissions)
else if (data.temp_location_data) {
setLocation(data.temp_location_data);
}
// Fetch operator
@@ -53,7 +57,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
};
fetchRelatedData();
}, [data.location_id, data.operator_id, data.property_owner_id]);
}, [data.location_id, data.temp_location_data, data.operator_id, data.property_owner_id]);
const getStatusColor = (status: string | undefined) => {
if (!status) return 'bg-gray-500';

View File

@@ -602,23 +602,39 @@ export async function validateEntityData(
try {
switch (tableName) {
case 'parks': {
const { data } = await supabase.from('parks').select('slug').eq('id', entityId).single();
originalSlug = data?.slug || null;
const { data, error } = await supabase.from('parks').select('slug').eq('id', entityId).maybeSingle();
if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break;
}
case 'rides': {
const { data } = await supabase.from('rides').select('slug').eq('id', entityId).single();
originalSlug = data?.slug || null;
const { data, error } = await supabase.from('rides').select('slug').eq('id', entityId).maybeSingle();
if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break;
}
case 'companies': {
const { data } = await supabase.from('companies').select('slug').eq('id', entityId).single();
originalSlug = data?.slug || null;
const { data, error } = await supabase.from('companies').select('slug').eq('id', entityId).maybeSingle();
if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break;
}
case 'ride_models': {
const { data } = await supabase.from('ride_models').select('slug').eq('id', entityId).single();
originalSlug = data?.slug || null;
const { data, error } = await supabase.from('ride_models').select('slug').eq('id', entityId).maybeSingle();
if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break;
}
}

View File

@@ -3,6 +3,8 @@
* These replace the `any` types in entityTransformers.ts
*/
import type { LocationData } from './location';
export interface ParkSubmissionData {
name: string;
slug: string;
@@ -19,6 +21,7 @@ export interface ParkSubmissionData {
operator_id?: string | null;
property_owner_id?: string | null;
location_id?: string | null;
temp_location_data?: LocationData | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;

View File

@@ -1179,6 +1179,13 @@ serve(withRateLimit(async (req) => {
? 'Submission has unresolved dependencies. Escalation required.'
: undefined;
// Set moderator_id session variable for audit logging
await supabase.rpc('set_config', {
setting: 'app.moderator_id',
value: authenticatedUserId,
is_local: true
});
const { error: updateError } = await supabase
.from('content_submissions')
.update({

View File

@@ -0,0 +1,70 @@
-- Update log_moderation_action to use session variable for moderator_id
-- This allows edge functions using service role to pass the actual moderator ID
CREATE OR REPLACE FUNCTION public.log_moderation_action(
_submission_id uuid,
_action text,
_previous_status text DEFAULT NULL::text,
_new_status text DEFAULT NULL::text,
_notes text DEFAULT NULL::text,
_metadata jsonb DEFAULT '{}'::jsonb
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
_log_id UUID;
_metadata_record record;
_moderator_id UUID;
BEGIN
-- Get moderator ID from session variable (set by edge function) or auth.uid()
BEGIN
_moderator_id := COALESCE(
current_setting('app.moderator_id', true)::uuid,
auth.uid()
);
EXCEPTION WHEN OTHERS THEN
_moderator_id := auth.uid();
END;
-- Insert into moderation_audit_log (without metadata JSONB column)
INSERT INTO public.moderation_audit_log (
submission_id,
moderator_id,
action,
previous_status,
new_status,
notes
) VALUES (
_submission_id,
_moderator_id,
_action,
_previous_status,
_new_status,
_notes
)
RETURNING id INTO _log_id;
-- Write metadata to relational moderation_audit_metadata table
IF _metadata IS NOT NULL AND jsonb_typeof(_metadata) = 'object' THEN
FOR _metadata_record IN
SELECT key, value::text as text_value
FROM jsonb_each_text(_metadata)
LOOP
INSERT INTO public.moderation_audit_metadata (
audit_log_id,
metadata_key,
metadata_value
) VALUES (
_log_id,
_metadata_record.key,
_metadata_record.text_value
);
END LOOP;
END IF;
RETURN _log_id;
END;
$function$;