mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 03:31:13 -05:00
feat: Implement composite submission system
This commit is contained in:
81
src/components/moderation/DependencyTreeView.tsx
Normal file
81
src/components/moderation/DependencyTreeView.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { CheckCircle2, Clock, XCircle, ChevronRight } from 'lucide-react';
|
||||
import { SubmissionItemWithDeps } from '@/lib/submissionItemsService';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface DependencyTreeViewProps {
|
||||
items: SubmissionItemWithDeps[];
|
||||
}
|
||||
|
||||
export function DependencyTreeView({ items }: DependencyTreeViewProps) {
|
||||
// Build tree structure - root items (no depends_on)
|
||||
const rootItems = items.filter(item => !item.depends_on);
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return <CheckCircle2 className="w-4 h-4 text-green-600" />;
|
||||
case 'rejected':
|
||||
return <XCircle className="w-4 h-4 text-destructive" />;
|
||||
case 'pending':
|
||||
default:
|
||||
return <Clock className="w-4 h-4 text-muted-foreground" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getItemLabel = (item: SubmissionItemWithDeps): string => {
|
||||
const data = item.item_data;
|
||||
const name = data.name || 'Unnamed';
|
||||
const type = item.item_type.replace('_', ' ');
|
||||
return `${name} (${type})`;
|
||||
};
|
||||
|
||||
const renderItem = (item: SubmissionItemWithDeps, level: number = 0) => {
|
||||
const dependents = items.filter(i => i.depends_on === item.id);
|
||||
const hasChildren = dependents.length > 0;
|
||||
|
||||
return (
|
||||
<div key={item.id} className={cn("space-y-2", level > 0 && "ml-6 border-l-2 border-border pl-4")}>
|
||||
<div className="flex items-center gap-2">
|
||||
{level > 0 && <ChevronRight className="w-4 h-4 text-muted-foreground" />}
|
||||
{getStatusIcon(item.status)}
|
||||
<span className={cn(
|
||||
"text-sm",
|
||||
item.status === 'approved' && "text-green-700 dark:text-green-400",
|
||||
item.status === 'rejected' && "text-destructive",
|
||||
item.status === 'pending' && "text-foreground"
|
||||
)}>
|
||||
{getItemLabel(item)}
|
||||
</span>
|
||||
<Badge variant={item.status === 'approved' ? 'default' : item.status === 'rejected' ? 'destructive' : 'secondary'} className="text-xs">
|
||||
{item.status}
|
||||
</Badge>
|
||||
{item.depends_on && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
depends on parent
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{hasChildren && dependents.map(dep => renderItem(dep, level + 1))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (items.length <= 1) {
|
||||
return null; // Don't show tree for single items
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-sm font-semibold">Submission Items ({items.length})</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Composite Submission
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{rootItems.map(item => renderItem(item))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { ArrowDown, AlertCircle } from 'lucide-react';
|
||||
import { type SubmissionItemWithDeps } from '@/lib/submissionItemsService';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { DependencyTreeView } from './DependencyTreeView';
|
||||
|
||||
interface DependencyVisualizerProps {
|
||||
items: SubmissionItemWithDeps[];
|
||||
@@ -45,6 +46,9 @@ export function DependencyVisualizer({ items, selectedIds }: DependencyVisualize
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Compact dependency tree view */}
|
||||
<DependencyTreeView items={items} />
|
||||
|
||||
{hasCircularDependency && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
|
||||
@@ -9,6 +9,26 @@ import { logger } from './logger';
|
||||
import { getErrorMessage } from './errorHandler';
|
||||
import type { TimelineEventFormData, EntityType } from '@/types/timeline';
|
||||
|
||||
// ============================================
|
||||
// COMPOSITE SUBMISSION TYPES
|
||||
// ============================================
|
||||
|
||||
interface CompositeSubmissionDependency {
|
||||
type: 'park' | 'ride' | 'company' | 'ride_model';
|
||||
data: any;
|
||||
tempId: string;
|
||||
companyType?: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
|
||||
parentTempId?: string; // For linking ride_model to manufacturer
|
||||
}
|
||||
|
||||
interface CompositeSubmissionData {
|
||||
primaryEntity: {
|
||||
type: 'park' | 'ride';
|
||||
data: any;
|
||||
};
|
||||
dependencies: CompositeSubmissionDependency[];
|
||||
}
|
||||
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* SUBMISSION PATTERN STANDARD - CRITICAL PROJECT RULE
|
||||
@@ -154,6 +174,172 @@ export interface RideModelFormData {
|
||||
card_image_id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* COMPOSITE SUBMISSION HANDLER
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Creates a single submission containing multiple related entities
|
||||
* (e.g., Park + Operator, Ride + Manufacturer + Model)
|
||||
*
|
||||
* All entities are submitted atomically through the moderation queue,
|
||||
* with dependency tracking to ensure correct approval order.
|
||||
*
|
||||
* @param primaryEntity - The main entity being created (park or ride)
|
||||
* @param dependencies - Array of dependent entities (companies, models)
|
||||
* @param userId - The ID of the user submitting
|
||||
* @returns Object containing submitted boolean and submissionId
|
||||
*/
|
||||
async function submitCompositeCreation(
|
||||
primaryEntity: { type: 'park' | 'ride'; data: any },
|
||||
dependencies: CompositeSubmissionDependency[],
|
||||
userId: string
|
||||
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||
// Check if user is banned
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('banned')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
|
||||
if (profile?.banned) {
|
||||
throw new Error('Account suspended. Contact support for assistance.');
|
||||
}
|
||||
|
||||
// Upload all pending images for all entities
|
||||
const uploadedEntities = await Promise.all([
|
||||
...dependencies.map(async (dep) => {
|
||||
if (dep.data.images?.uploaded && dep.data.images.uploaded.length > 0) {
|
||||
const uploadedImages = await uploadPendingImages(dep.data.images.uploaded);
|
||||
return {
|
||||
...dep,
|
||||
data: {
|
||||
...dep.data,
|
||||
images: { ...dep.data.images, uploaded: uploadedImages }
|
||||
}
|
||||
};
|
||||
}
|
||||
return dep;
|
||||
}),
|
||||
(async () => {
|
||||
if (primaryEntity.data.images?.uploaded && primaryEntity.data.images.uploaded.length > 0) {
|
||||
const uploadedImages = await uploadPendingImages(primaryEntity.data.images.uploaded);
|
||||
return {
|
||||
...primaryEntity,
|
||||
data: {
|
||||
...primaryEntity.data,
|
||||
images: { ...primaryEntity.data.images, uploaded: uploadedImages }
|
||||
}
|
||||
};
|
||||
}
|
||||
return primaryEntity;
|
||||
})()
|
||||
]);
|
||||
|
||||
const uploadedDependencies = uploadedEntities.slice(0, -1) as CompositeSubmissionDependency[];
|
||||
const uploadedPrimary = uploadedEntities[uploadedEntities.length - 1] as typeof primaryEntity;
|
||||
|
||||
// Build submission items array with dependencies first
|
||||
const submissionItems: any[] = [];
|
||||
const tempIdMap = new Map<string, number>(); // Maps tempId to order_index
|
||||
|
||||
// Add dependency items (companies, models) first
|
||||
let orderIndex = 0;
|
||||
for (const dep of uploadedDependencies) {
|
||||
const itemType = dep.type === 'company' ? dep.companyType : dep.type;
|
||||
tempIdMap.set(dep.tempId, orderIndex);
|
||||
|
||||
const itemData: any = {
|
||||
...extractChangedFields(dep.data, {}),
|
||||
images: dep.data.images as unknown as Json
|
||||
};
|
||||
|
||||
// Add company_type for company entities
|
||||
if (dep.type === 'company') {
|
||||
itemData.company_type = dep.companyType;
|
||||
}
|
||||
|
||||
// Add manufacturer dependency for ride models
|
||||
if (dep.type === 'ride_model' && dep.parentTempId) {
|
||||
const parentOrderIndex = tempIdMap.get(dep.parentTempId);
|
||||
if (parentOrderIndex !== undefined) {
|
||||
itemData._temp_manufacturer_ref = parentOrderIndex;
|
||||
}
|
||||
}
|
||||
|
||||
submissionItems.push({
|
||||
item_type: itemType,
|
||||
action_type: 'create' as const,
|
||||
item_data: itemData,
|
||||
status: 'pending' as const,
|
||||
order_index: orderIndex,
|
||||
depends_on: null // Dependencies don't have parents
|
||||
});
|
||||
|
||||
orderIndex++;
|
||||
}
|
||||
|
||||
// Add primary entity last
|
||||
const primaryData: any = {
|
||||
...extractChangedFields(uploadedPrimary.data, {}),
|
||||
images: uploadedPrimary.data.images as unknown as Json
|
||||
};
|
||||
|
||||
// Map temporary IDs to order indices for foreign keys
|
||||
if (uploadedPrimary.type === 'park') {
|
||||
if (uploadedPrimary.data.operator_id?.startsWith('temp-')) {
|
||||
const opIndex = tempIdMap.get('temp-operator');
|
||||
if (opIndex !== undefined) primaryData._temp_operator_ref = opIndex;
|
||||
delete primaryData.operator_id;
|
||||
}
|
||||
if (uploadedPrimary.data.property_owner_id?.startsWith('temp-')) {
|
||||
const ownerIndex = tempIdMap.get('temp-property-owner');
|
||||
if (ownerIndex !== undefined) primaryData._temp_property_owner_ref = ownerIndex;
|
||||
delete primaryData.property_owner_id;
|
||||
}
|
||||
} else if (uploadedPrimary.type === 'ride') {
|
||||
if (uploadedPrimary.data.manufacturer_id?.startsWith('temp-')) {
|
||||
const mfgIndex = tempIdMap.get('temp-manufacturer');
|
||||
if (mfgIndex !== undefined) primaryData._temp_manufacturer_ref = mfgIndex;
|
||||
delete primaryData.manufacturer_id;
|
||||
}
|
||||
if (uploadedPrimary.data.ride_model_id?.startsWith('temp-')) {
|
||||
const modelIndex = tempIdMap.get('temp-ride-model');
|
||||
if (modelIndex !== undefined) primaryData._temp_ride_model_ref = modelIndex;
|
||||
delete primaryData.ride_model_id;
|
||||
}
|
||||
}
|
||||
|
||||
submissionItems.push({
|
||||
item_type: uploadedPrimary.type,
|
||||
action_type: 'create' as const,
|
||||
item_data: primaryData,
|
||||
status: 'pending' as const,
|
||||
order_index: orderIndex,
|
||||
depends_on: null // Will be set by RPC based on refs
|
||||
});
|
||||
|
||||
// Use RPC to create submission with items atomically
|
||||
const { data: result, error } = await supabase.rpc('create_submission_with_items', {
|
||||
p_user_id: userId,
|
||||
p_submission_type: uploadedPrimary.type,
|
||||
p_content: { action: 'create' } as unknown as Json,
|
||||
p_items: submissionItems as unknown as Json[]
|
||||
});
|
||||
|
||||
if (error) {
|
||||
logger.error('Composite submission failed', {
|
||||
action: 'composite_submission',
|
||||
primaryType: uploadedPrimary.type,
|
||||
dependencyCount: dependencies.length,
|
||||
error: getErrorMessage(error)
|
||||
});
|
||||
throw new Error(`Failed to create composite submission: ${getErrorMessage(error)}`);
|
||||
}
|
||||
|
||||
return { submitted: true, submissionId: result };
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
|
||||
*
|
||||
@@ -174,9 +360,41 @@ export interface RideModelFormData {
|
||||
* @returns Object containing submitted boolean and submissionId
|
||||
*/
|
||||
export async function submitParkCreation(
|
||||
data: ParkFormData,
|
||||
data: ParkFormData & { _compositeSubmission?: any },
|
||||
userId: string
|
||||
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||
// Check for composite submission with dependencies
|
||||
if (data._compositeSubmission) {
|
||||
const dependencies: CompositeSubmissionDependency[] = [];
|
||||
|
||||
if (data._compositeSubmission.new_operator) {
|
||||
dependencies.push({
|
||||
type: 'company',
|
||||
data: { ...data._compositeSubmission.new_operator, company_type: 'operator' },
|
||||
tempId: 'temp-operator',
|
||||
companyType: 'operator'
|
||||
});
|
||||
}
|
||||
|
||||
if (data._compositeSubmission.new_property_owner) {
|
||||
dependencies.push({
|
||||
type: 'company',
|
||||
data: { ...data._compositeSubmission.new_property_owner, company_type: 'property_owner' },
|
||||
tempId: 'temp-property-owner',
|
||||
companyType: 'property_owner'
|
||||
});
|
||||
}
|
||||
|
||||
if (dependencies.length > 0) {
|
||||
return submitCompositeCreation(
|
||||
{ type: 'park', data: data._compositeSubmission.park },
|
||||
dependencies,
|
||||
userId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Standard single-entity creation
|
||||
// Check if user is banned
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
@@ -368,9 +586,41 @@ export async function submitParkUpdate(
|
||||
* @returns Object containing submitted boolean and submissionId
|
||||
*/
|
||||
export async function submitRideCreation(
|
||||
data: RideFormData,
|
||||
data: RideFormData & { _tempNewManufacturer?: any; _tempNewRideModel?: any },
|
||||
userId: string
|
||||
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||
// Check for composite submission with dependencies
|
||||
if (data._tempNewManufacturer || data._tempNewRideModel) {
|
||||
const dependencies: CompositeSubmissionDependency[] = [];
|
||||
|
||||
if (data._tempNewManufacturer) {
|
||||
dependencies.push({
|
||||
type: 'company',
|
||||
data: { ...data._tempNewManufacturer, company_type: 'manufacturer' },
|
||||
tempId: 'temp-manufacturer',
|
||||
companyType: 'manufacturer'
|
||||
});
|
||||
}
|
||||
|
||||
if (data._tempNewRideModel) {
|
||||
dependencies.push({
|
||||
type: 'ride_model',
|
||||
data: data._tempNewRideModel,
|
||||
tempId: 'temp-ride-model',
|
||||
parentTempId: data._tempNewManufacturer ? 'temp-manufacturer' : undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (dependencies.length > 0) {
|
||||
return submitCompositeCreation(
|
||||
{ type: 'ride', data },
|
||||
dependencies,
|
||||
userId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Standard single-entity creation
|
||||
// Check if user is banned
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
|
||||
@@ -46,8 +46,24 @@ export const parkValidationSchema = z.object({
|
||||
if (!val || val === '') return true;
|
||||
return z.string().email().safeParse(val).success;
|
||||
}, 'Invalid email format'),
|
||||
operator_id: z.string().uuid().optional().nullable().or(z.literal('')).transform(val => val || undefined),
|
||||
property_owner_id: z.string().uuid().optional().nullable().or(z.literal('')).transform(val => val || undefined),
|
||||
operator_id: z.string()
|
||||
.refine(
|
||||
val => !val || val === '' || val.startsWith('temp-') || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(val),
|
||||
'Must be a valid UUID or temporary placeholder'
|
||||
)
|
||||
.optional()
|
||||
.nullable()
|
||||
.or(z.literal(''))
|
||||
.transform(val => val || undefined),
|
||||
property_owner_id: z.string()
|
||||
.refine(
|
||||
val => !val || val === '' || val.startsWith('temp-') || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(val),
|
||||
'Must be a valid UUID or temporary placeholder'
|
||||
)
|
||||
.optional()
|
||||
.nullable()
|
||||
.or(z.literal(''))
|
||||
.transform(val => val || undefined),
|
||||
banner_image_id: z.string().optional(),
|
||||
banner_image_url: z.string().optional(),
|
||||
card_image_id: z.string().optional(),
|
||||
@@ -83,7 +99,13 @@ export const rideValidationSchema = z.object({
|
||||
ride_sub_type: z.string().trim().max(100, 'Sub type must be less than 100 characters').optional().or(z.literal('')),
|
||||
status: z.enum(['operating', 'closed_permanently', 'closed_temporarily', 'under_construction', 'relocated', 'stored', 'demolished']),
|
||||
park_id: z.string().uuid().optional().nullable(),
|
||||
designer_id: z.string().uuid().optional().nullable(),
|
||||
designer_id: z.string()
|
||||
.refine(
|
||||
val => !val || val === '' || val.startsWith('temp-') || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(val),
|
||||
'Must be a valid UUID or temporary placeholder'
|
||||
)
|
||||
.optional()
|
||||
.nullable(),
|
||||
opening_date: z.string().optional().or(z.literal('')),
|
||||
opening_date_precision: z.enum(['day', 'month', 'year']).optional(),
|
||||
closing_date: z.string().optional().or(z.literal('')),
|
||||
@@ -128,8 +150,20 @@ export const rideValidationSchema = z.object({
|
||||
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
||||
z.number().min(-10, 'G-force must be greater than -10').max(10, 'G-force must be less than 10').optional()
|
||||
),
|
||||
manufacturer_id: z.string().uuid().optional().nullable(),
|
||||
ride_model_id: z.string().uuid().optional().nullable(),
|
||||
manufacturer_id: z.string()
|
||||
.refine(
|
||||
val => !val || val === '' || val.startsWith('temp-') || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(val),
|
||||
'Must be a valid UUID or temporary placeholder'
|
||||
)
|
||||
.optional()
|
||||
.nullable(),
|
||||
ride_model_id: z.string()
|
||||
.refine(
|
||||
val => !val || val === '' || val.startsWith('temp-') || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(val),
|
||||
'Must be a valid UUID or temporary placeholder'
|
||||
)
|
||||
.optional()
|
||||
.nullable(),
|
||||
coaster_type: z.string().optional(),
|
||||
seating_type: z.string().optional(),
|
||||
intensity_level: z.string().optional(),
|
||||
@@ -285,7 +319,12 @@ export const rideModelValidationSchema = z.object({
|
||||
category: z.string().min(1, 'Category is required'),
|
||||
ride_type: z.string().trim().min(1, 'Ride type is required').max(100, 'Ride type must be less than 100 characters'),
|
||||
description: z.string().trim().max(2000, 'Description must be less than 2000 characters').optional().or(z.literal('')),
|
||||
manufacturer_id: z.string().uuid('Invalid manufacturer ID').optional(),
|
||||
manufacturer_id: z.string()
|
||||
.refine(
|
||||
val => !val || val === '' || val.startsWith('temp-') || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(val),
|
||||
'Must be a valid UUID or temporary placeholder'
|
||||
)
|
||||
.optional(),
|
||||
source_url: z.string().trim().optional().or(z.literal('')).refine((val) => {
|
||||
if (!val || val === '') return true;
|
||||
return z.string().url().safeParse(val).success;
|
||||
|
||||
@@ -227,7 +227,7 @@ export async function approveSubmissionItems(
|
||||
);
|
||||
}
|
||||
|
||||
// Add to dependency map for child items
|
||||
// Add to dependency map using item.id as key
|
||||
dependencyMap.set(item.id, entityId);
|
||||
|
||||
} catch (error: unknown) {
|
||||
@@ -898,8 +898,8 @@ async function updateEntityFeaturedImage(
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve dependency references in submission data
|
||||
* Replaces submission item IDs with actual database entity IDs
|
||||
* Resolve dependency references in item_data by looking up approved entity IDs
|
||||
* Replaces temporary references (_temp_*_ref) with actual database entity IDs
|
||||
*/
|
||||
function resolveDependencies(data: any, dependencyMap: Map<string, string>): any {
|
||||
const resolved = { ...data };
|
||||
@@ -915,6 +915,43 @@ function resolveDependencies(data: any, dependencyMap: Map<string, string>): any
|
||||
'location_id',
|
||||
];
|
||||
|
||||
// Resolve temporary references first
|
||||
if (resolved._temp_manufacturer_ref !== undefined) {
|
||||
const refIndex = resolved._temp_manufacturer_ref;
|
||||
const refItemId = Array.from(dependencyMap.keys())[refIndex];
|
||||
if (refItemId && dependencyMap.has(refItemId)) {
|
||||
resolved.manufacturer_id = dependencyMap.get(refItemId);
|
||||
}
|
||||
delete resolved._temp_manufacturer_ref;
|
||||
}
|
||||
|
||||
if (resolved._temp_operator_ref !== undefined) {
|
||||
const refIndex = resolved._temp_operator_ref;
|
||||
const refItemId = Array.from(dependencyMap.keys())[refIndex];
|
||||
if (refItemId && dependencyMap.has(refItemId)) {
|
||||
resolved.operator_id = dependencyMap.get(refItemId);
|
||||
}
|
||||
delete resolved._temp_operator_ref;
|
||||
}
|
||||
|
||||
if (resolved._temp_property_owner_ref !== undefined) {
|
||||
const refIndex = resolved._temp_property_owner_ref;
|
||||
const refItemId = Array.from(dependencyMap.keys())[refIndex];
|
||||
if (refItemId && dependencyMap.has(refItemId)) {
|
||||
resolved.property_owner_id = dependencyMap.get(refItemId);
|
||||
}
|
||||
delete resolved._temp_property_owner_ref;
|
||||
}
|
||||
|
||||
if (resolved._temp_ride_model_ref !== undefined) {
|
||||
const refIndex = resolved._temp_ride_model_ref;
|
||||
const refItemId = Array.from(dependencyMap.keys())[refIndex];
|
||||
if (refItemId && dependencyMap.has(refItemId)) {
|
||||
resolved.ride_model_id = dependencyMap.get(refItemId);
|
||||
}
|
||||
delete resolved._temp_ride_model_ref;
|
||||
}
|
||||
|
||||
// Resolve each foreign key if it's a submission item ID
|
||||
for (const key of foreignKeys) {
|
||||
if (resolved[key] && dependencyMap.has(resolved[key])) {
|
||||
|
||||
Reference in New Issue
Block a user