Refactor moderation data fetching

This commit is contained in:
gpt-engineer-app[bot]
2025-11-04 16:56:49 +00:00
parent f32b8bdfee
commit feee859a50
4 changed files with 110 additions and 117 deletions

View File

@@ -48,125 +48,21 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
} }
setError(null); setError(null);
// Fetch submission items with entity data // Use database function to fetch submission items with entity data in one query
// Note: entity_data is pre-loaded from the view, but we need to fetch from raw table // This eliminates N+1 query problem and properly handles RLS/AAL2 checks
// when this component is used standalone
const { data: itemsData, error: itemsError } = await supabase const { data: itemsData, error: itemsError } = await supabase
.from('submission_items') .rpc('get_submission_items_with_entities', {
.select('*') p_submission_id: submissionId
.eq('submission_id', submissionId) });
.order('order_index');
if (itemsError) throw itemsError; if (itemsError) throw itemsError;
// Fetch entity data for each item based on item_type and typed FK columns // Transform to expected format
const transformedItems = await Promise.all( const transformedItems = (itemsData || []).map((item: any) => ({
(itemsData || []).map(async (item) => { ...item,
// Determine which FK column to use based on item_type item_data: item.entity_data || {},
let itemDataId: string | null = null; entity_data: item.entity_data
switch (item.item_type) { }));
case 'park':
itemDataId = item.park_submission_id;
break;
case 'ride':
itemDataId = item.ride_submission_id;
break;
case 'photo':
case 'photo_edit':
case 'photo_delete':
itemDataId = item.photo_submission_id;
break;
case 'manufacturer':
case 'operator':
case 'designer':
case 'property_owner':
itemDataId = item.company_submission_id;
break;
case 'ride_model':
itemDataId = item.ride_model_submission_id;
break;
case 'milestone':
case 'timeline_event':
itemDataId = item.timeline_event_submission_id;
break;
}
if (!itemDataId) {
return { ...item, item_data: {}, entity_data: null };
}
try {
let entityData = null;
// Fetch from appropriate table based on item_type
switch (item.item_type) {
case 'park': {
const { data } = await supabase
.from('park_submissions')
.select('*')
.eq('id', itemDataId)
.maybeSingle();
entityData = data as any;
break;
}
case 'ride': {
const { data } = await supabase
.from('ride_submissions')
.select('*')
.eq('id', itemDataId)
.maybeSingle();
entityData = data as any;
break;
}
case 'manufacturer':
case 'operator':
case 'designer':
case 'property_owner': {
const { data } = await supabase
.from('company_submissions')
.select('*')
.eq('id', itemDataId)
.maybeSingle();
entityData = data as any;
break;
}
case 'photo': {
const { data } = await supabase
.from('photo_submissions')
.select('*')
.eq('id', itemDataId)
.maybeSingle();
entityData = data as any;
break;
}
case 'ride_model': {
const { data } = await supabase
.from('ride_model_submissions')
.select('*')
.eq('id', itemDataId)
.maybeSingle();
entityData = data as any;
break;
}
default:
entityData = null;
}
return {
...item,
item_data: entityData || {},
entity_data: entityData
};
} catch (err) {
logger.warn('Failed to fetch entity data for item', {
itemId: item.id,
itemType: item.item_type,
error: getErrorMessage(err)
});
return { ...item, item_data: {}, entity_data: null };
}
})
);
// Check for photo submissions (using array query to avoid 406) // Check for photo submissions (using array query to avoid 406)
const { data: photoData, error: photoError } = await supabase const { data: photoData, error: photoError } = await supabase

View File

@@ -10,7 +10,8 @@ interface RichCompanyDisplayProps {
} }
export function RichCompanyDisplay({ data, actionType, showAllFields = true }: RichCompanyDisplayProps) { export function RichCompanyDisplay({ data, actionType, showAllFields = true }: RichCompanyDisplayProps) {
const getCompanyTypeColor = (type: string) => { const getCompanyTypeColor = (type: string | undefined) => {
if (!type) return 'bg-gray-500';
switch (type.toLowerCase()) { switch (type.toLowerCase()) {
case 'manufacturer': return 'bg-blue-500'; case 'manufacturer': return 'bg-blue-500';
case 'operator': return 'bg-green-500'; case 'operator': return 'bg-green-500';
@@ -31,7 +32,7 @@ export function RichCompanyDisplay({ data, actionType, showAllFields = true }: R
<h3 className="text-xl font-bold text-foreground truncate">{data.name}</h3> <h3 className="text-xl font-bold text-foreground truncate">{data.name}</h3>
<div className="flex items-center gap-2 mt-1 flex-wrap"> <div className="flex items-center gap-2 mt-1 flex-wrap">
<Badge className={`${getCompanyTypeColor(data.company_type)} text-white text-xs`}> <Badge className={`${getCompanyTypeColor(data.company_type)} text-white text-xs`}>
{data.company_type?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} {(data.company_type || 'Unknown')?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
</Badge> </Badge>
{data.person_type && ( {data.person_type && (
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="text-xs">

View File

@@ -5612,6 +5612,29 @@ export type Database = {
Args: { p_item_data_id: string; p_item_type: string } Args: { p_item_data_id: string; p_item_type: string }
Returns: Json Returns: Json
} }
get_submission_items_with_entities: {
Args: { p_submission_id: string }
Returns: {
action_type: string
approved_by: string
company_submission_id: string
created_at: string
depends_on: string
entity_data: Json
id: string
item_type: string
order_index: number
park_submission_id: string
photo_submission_id: string
rejected_by: string
ride_model_submission_id: string
ride_submission_id: string
status: string
submission_id: string
timeline_event_submission_id: string
updated_at: string
}[]
}
get_user_management_permissions: { get_user_management_permissions: {
Args: { _user_id: string } Args: { _user_id: string }
Returns: Json Returns: Json

View File

@@ -0,0 +1,73 @@
-- Create function to fetch submission items with entity data
-- This replaces the N individual queries with a single efficient query
CREATE OR REPLACE FUNCTION get_submission_items_with_entities(p_submission_id uuid)
RETURNS TABLE (
id uuid,
submission_id uuid,
item_type text,
action_type text,
status text,
order_index integer,
depends_on uuid,
park_submission_id uuid,
ride_submission_id uuid,
company_submission_id uuid,
photo_submission_id uuid,
ride_model_submission_id uuid,
timeline_event_submission_id uuid,
approved_by uuid,
rejected_by uuid,
created_at timestamptz,
updated_at timestamptz,
entity_data jsonb
)
SECURITY DEFINER
SET search_path = public
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
si.id,
si.submission_id,
si.item_type,
si.action_type,
si.status,
si.order_index,
si.depends_on,
si.park_submission_id,
si.ride_submission_id,
si.company_submission_id,
si.photo_submission_id,
si.ride_model_submission_id,
si.timeline_event_submission_id,
si.approved_by,
si.rejected_by,
si.created_at,
si.updated_at,
-- Join entity data based on item_type
CASE
WHEN si.item_type = 'park' THEN
(SELECT to_jsonb(ps.*) FROM park_submissions ps WHERE ps.id = si.park_submission_id)
WHEN si.item_type = 'ride' THEN
(SELECT to_jsonb(rs.*) FROM ride_submissions rs WHERE rs.id = si.ride_submission_id)
WHEN si.item_type IN ('manufacturer', 'operator', 'designer', 'property_owner') THEN
(SELECT to_jsonb(cs.*) FROM company_submissions cs WHERE cs.id = si.company_submission_id)
WHEN si.item_type IN ('photo', 'photo_edit', 'photo_delete') THEN
(SELECT to_jsonb(phs.*) FROM photo_submissions phs WHERE phs.id = si.photo_submission_id)
WHEN si.item_type = 'ride_model' THEN
(SELECT to_jsonb(rms.*) FROM ride_model_submissions rms WHERE rms.id = si.ride_model_submission_id)
ELSE NULL
END AS entity_data
FROM submission_items si
WHERE si.submission_id = p_submission_id
ORDER BY si.order_index;
END;
$$;
COMMENT ON FUNCTION get_submission_items_with_entities IS
'Fetch submission items with their entity data in a single query. Uses SECURITY DEFINER to access submission tables with proper RLS context from the parent content_submissions access.';
-- Grant execute to authenticated users
GRANT EXECUTE ON FUNCTION get_submission_items_with_entities(uuid) TO authenticated;