mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
Fix park submission locations
Implement Phase 1 of the JSONB violation fix by creating the `park_submission_locations` table. This includes migrating existing data from `park_submissions.temp_location_data` and updating relevant code to read and write to the new relational table. The `temp_location_data` column will be dropped after data migration.
This commit is contained in:
@@ -32,9 +32,14 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
|
|||||||
.single();
|
.single();
|
||||||
setLocation(locationData);
|
setLocation(locationData);
|
||||||
}
|
}
|
||||||
// Otherwise use temp_location_data (for new submissions)
|
// Otherwise fetch from park_submission_locations (for new submissions)
|
||||||
else if (data.temp_location_data) {
|
else if (data.id) {
|
||||||
setLocation(data.temp_location_data);
|
const { data: locationData } = await supabase
|
||||||
|
.from('park_submission_locations')
|
||||||
|
.select('*')
|
||||||
|
.eq('park_submission_id', data.id)
|
||||||
|
.maybeSingle();
|
||||||
|
setLocation(locationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch operator
|
// Fetch operator
|
||||||
@@ -59,7 +64,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchRelatedData();
|
fetchRelatedData();
|
||||||
}, [data.location_id, data.temp_location_data, data.operator_id, data.property_owner_id]);
|
}, [data.location_id, data.id, data.operator_id, data.property_owner_id]);
|
||||||
|
|
||||||
const getStatusColor = (status: string | undefined) => {
|
const getStatusColor = (status: string | undefined) => {
|
||||||
if (!status) return 'bg-gray-500';
|
if (!status) return 'bg-gray-500';
|
||||||
|
|||||||
@@ -162,25 +162,24 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Transform to include item_data
|
// Transform to include item_data
|
||||||
const itemsWithData = fullItems.map(item => {
|
const itemsWithData = await Promise.all(fullItems.map(async item => {
|
||||||
let itemData = {};
|
let itemData = {};
|
||||||
switch (item.item_type) {
|
switch (item.item_type) {
|
||||||
case 'park': {
|
case 'park': {
|
||||||
const parkSub = (item.park_submission as any) || {};
|
const parkSub = (item.park_submission as any) || {};
|
||||||
|
let locationData = null;
|
||||||
|
if (parkSub?.id) {
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('park_submission_locations')
|
||||||
|
.select('*')
|
||||||
|
.eq('park_submission_id', parkSub.id)
|
||||||
|
.maybeSingle();
|
||||||
|
locationData = data;
|
||||||
|
}
|
||||||
itemData = {
|
itemData = {
|
||||||
...parkSub,
|
...parkSub,
|
||||||
// Transform temp_location_data → location for validation
|
location: locationData || undefined
|
||||||
location: parkSub.temp_location_data || undefined,
|
|
||||||
temp_location_data: undefined
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.info('[Submission Flow] Transformed park data for validation', {
|
|
||||||
itemId: item.id,
|
|
||||||
hasLocation: !!parkSub.temp_location_data,
|
|
||||||
locationData: parkSub.temp_location_data,
|
|
||||||
transformedHasLocation: !!(itemData as any).location,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ride':
|
case 'ride':
|
||||||
|
|||||||
@@ -2006,6 +2006,65 @@ export type Database = {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
park_submission_locations: {
|
||||||
|
Row: {
|
||||||
|
city: string | null
|
||||||
|
country: string
|
||||||
|
created_at: string
|
||||||
|
display_name: string | null
|
||||||
|
id: string
|
||||||
|
latitude: number | null
|
||||||
|
longitude: number | null
|
||||||
|
name: string
|
||||||
|
park_submission_id: string
|
||||||
|
postal_code: string | null
|
||||||
|
state_province: string | null
|
||||||
|
street_address: string | null
|
||||||
|
timezone: string | null
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
city?: string | null
|
||||||
|
country: string
|
||||||
|
created_at?: string
|
||||||
|
display_name?: string | null
|
||||||
|
id?: string
|
||||||
|
latitude?: number | null
|
||||||
|
longitude?: number | null
|
||||||
|
name: string
|
||||||
|
park_submission_id: string
|
||||||
|
postal_code?: string | null
|
||||||
|
state_province?: string | null
|
||||||
|
street_address?: string | null
|
||||||
|
timezone?: string | null
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
city?: string | null
|
||||||
|
country?: string
|
||||||
|
created_at?: string
|
||||||
|
display_name?: string | null
|
||||||
|
id?: string
|
||||||
|
latitude?: number | null
|
||||||
|
longitude?: number | null
|
||||||
|
name?: string
|
||||||
|
park_submission_id?: string
|
||||||
|
postal_code?: string | null
|
||||||
|
state_province?: string | null
|
||||||
|
street_address?: string | null
|
||||||
|
timezone?: string | null
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
Relationships: [
|
||||||
|
{
|
||||||
|
foreignKeyName: "park_submission_locations_park_submission_id_fkey"
|
||||||
|
columns: ["park_submission_id"]
|
||||||
|
isOneToOne: false
|
||||||
|
referencedRelation: "park_submissions"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
park_submissions: {
|
park_submissions: {
|
||||||
Row: {
|
Row: {
|
||||||
banner_image_id: string | null
|
banner_image_id: string | null
|
||||||
@@ -2029,7 +2088,6 @@ export type Database = {
|
|||||||
slug: string
|
slug: string
|
||||||
status: string
|
status: string
|
||||||
submission_id: string
|
submission_id: string
|
||||||
temp_location_data: Json | null
|
|
||||||
updated_at: string
|
updated_at: string
|
||||||
website_url: string | null
|
website_url: string | null
|
||||||
}
|
}
|
||||||
@@ -2055,7 +2113,6 @@ export type Database = {
|
|||||||
slug: string
|
slug: string
|
||||||
status?: string
|
status?: string
|
||||||
submission_id: string
|
submission_id: string
|
||||||
temp_location_data?: Json | null
|
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
website_url?: string | null
|
website_url?: string | null
|
||||||
}
|
}
|
||||||
@@ -2081,7 +2138,6 @@ export type Database = {
|
|||||||
slug?: string
|
slug?: string
|
||||||
status?: string
|
status?: string
|
||||||
submission_id?: string
|
submission_id?: string
|
||||||
temp_location_data?: Json | null
|
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
website_url?: string | null
|
website_url?: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,22 +362,11 @@ async function submitCompositeCreation(
|
|||||||
images: primaryImages as unknown as Json
|
images: primaryImages as unknown as Json
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert location object to temp_location_data for parks
|
// Store location reference for park submissions (will be created in relational table)
|
||||||
if (uploadedPrimary.type === 'park' && uploadedPrimary.data.location) {
|
if (uploadedPrimary.type === 'park' && uploadedPrimary.data.location) {
|
||||||
primaryData.temp_location_data = {
|
primaryData._temp_location = uploadedPrimary.data.location;
|
||||||
name: uploadedPrimary.data.location.name,
|
|
||||||
street_address: uploadedPrimary.data.location.street_address || null,
|
|
||||||
city: uploadedPrimary.data.location.city || null,
|
|
||||||
state_province: uploadedPrimary.data.location.state_province || null,
|
|
||||||
country: uploadedPrimary.data.location.country,
|
|
||||||
latitude: uploadedPrimary.data.location.latitude,
|
|
||||||
longitude: uploadedPrimary.data.location.longitude,
|
|
||||||
timezone: uploadedPrimary.data.location.timezone || null,
|
|
||||||
postal_code: uploadedPrimary.data.location.postal_code || null,
|
|
||||||
display_name: uploadedPrimary.data.location.display_name
|
|
||||||
};
|
|
||||||
delete primaryData.location; // Remove the original location object
|
delete primaryData.location; // Remove the original location object
|
||||||
console.log('[submitCompositeCreation] Converted location to temp_location_data:', primaryData.temp_location_data);
|
console.log('[submitCompositeCreation] Stored location for relational insert:', primaryData._temp_location);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map temporary IDs to order indices for foreign keys
|
// Map temporary IDs to order indices for foreign keys
|
||||||
@@ -725,7 +714,7 @@ export async function submitParkCreation(
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
hasLocation: !!data.location,
|
hasLocation: !!data.location,
|
||||||
hasLocationId: !!data.location_id,
|
hasLocationId: !!data.location_id,
|
||||||
temp_location_data: tempLocationData
|
hasLocationData: !!tempLocationData
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: parkSubmission, error: parkSubmissionError } = await supabase
|
const { data: parkSubmission, error: parkSubmissionError } = await supabase
|
||||||
@@ -745,7 +734,6 @@ export async function submitParkCreation(
|
|||||||
operator_id: data.operator_id || null,
|
operator_id: data.operator_id || null,
|
||||||
property_owner_id: data.property_owner_id || null,
|
property_owner_id: data.property_owner_id || null,
|
||||||
location_id: data.location_id || null,
|
location_id: data.location_id || null,
|
||||||
temp_location_data: tempLocationData,
|
|
||||||
banner_image_url: bannerImage?.url || data.banner_image_url || null,
|
banner_image_url: bannerImage?.url || data.banner_image_url || null,
|
||||||
banner_image_id: bannerImage?.cloudflare_id || data.banner_image_id || null,
|
banner_image_id: bannerImage?.cloudflare_id || data.banner_image_id || null,
|
||||||
card_image_url: cardImage?.url || data.card_image_url || null,
|
card_image_url: cardImage?.url || data.card_image_url || null,
|
||||||
@@ -756,6 +744,26 @@ export async function submitParkCreation(
|
|||||||
|
|
||||||
if (parkSubmissionError) throw parkSubmissionError;
|
if (parkSubmissionError) throw parkSubmissionError;
|
||||||
|
|
||||||
|
// Create location in relational table if provided
|
||||||
|
if (tempLocationData) {
|
||||||
|
const { error: locationError } = await supabase
|
||||||
|
.from('park_submission_locations' as any)
|
||||||
|
.insert({
|
||||||
|
park_submission_id: (parkSubmission as any).id,
|
||||||
|
...tempLocationData
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
if (locationError) {
|
||||||
|
console.error('[submitParkCreation] Failed to create location:', locationError);
|
||||||
|
throw new Error(`Failed to save location data: ${locationError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[submitParkCreation] Created park_submission_location', {
|
||||||
|
parkSubmissionId: (parkSubmission as any).id,
|
||||||
|
locationName: tempLocationData.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create submission_items record linking to park_submissions
|
// Create submission_items record linking to park_submissions
|
||||||
const { error: itemError } = await supabase
|
const { error: itemError } = await supabase
|
||||||
.from('submission_items')
|
.from('submission_items')
|
||||||
@@ -934,7 +942,6 @@ export async function submitParkUpdate(
|
|||||||
operator_id: changedFields.operator_id !== undefined ? changedFields.operator_id : existingPark.operator_id,
|
operator_id: changedFields.operator_id !== undefined ? changedFields.operator_id : existingPark.operator_id,
|
||||||
property_owner_id: changedFields.property_owner_id !== undefined ? changedFields.property_owner_id : existingPark.property_owner_id,
|
property_owner_id: changedFields.property_owner_id !== undefined ? changedFields.property_owner_id : existingPark.property_owner_id,
|
||||||
location_id: changedFields.location_id !== undefined ? changedFields.location_id : existingPark.location_id,
|
location_id: changedFields.location_id !== undefined ? changedFields.location_id : existingPark.location_id,
|
||||||
temp_location_data: tempLocationData,
|
|
||||||
banner_image_url: changedFields.banner_image_url !== undefined ? changedFields.banner_image_url : existingPark.banner_image_url,
|
banner_image_url: changedFields.banner_image_url !== undefined ? changedFields.banner_image_url : existingPark.banner_image_url,
|
||||||
banner_image_id: changedFields.banner_image_id !== undefined ? changedFields.banner_image_id : existingPark.banner_image_id,
|
banner_image_id: changedFields.banner_image_id !== undefined ? changedFields.banner_image_id : existingPark.banner_image_id,
|
||||||
card_image_url: changedFields.card_image_url !== undefined ? changedFields.card_image_url : existingPark.card_image_url,
|
card_image_url: changedFields.card_image_url !== undefined ? changedFields.card_image_url : existingPark.card_image_url,
|
||||||
@@ -945,6 +952,26 @@ export async function submitParkUpdate(
|
|||||||
|
|
||||||
if (parkSubmissionError) throw parkSubmissionError;
|
if (parkSubmissionError) throw parkSubmissionError;
|
||||||
|
|
||||||
|
// Create location in relational table if provided
|
||||||
|
if (tempLocationData) {
|
||||||
|
const { error: locationError } = await supabase
|
||||||
|
.from('park_submission_locations' as any)
|
||||||
|
.insert({
|
||||||
|
park_submission_id: (parkSubmission as any).id,
|
||||||
|
...tempLocationData
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
if (locationError) {
|
||||||
|
console.error('[submitParkEdit] Failed to create location:', locationError);
|
||||||
|
throw new Error(`Failed to save location data: ${locationError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[submitParkEdit] Created park_submission_location', {
|
||||||
|
parkSubmissionId: (parkSubmission as any).id,
|
||||||
|
locationName: tempLocationData.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ Create submission_items referencing park_submission (no JSON data)
|
// ✅ Create submission_items referencing park_submission (no JSON data)
|
||||||
const { error: itemError } = await supabase
|
const { error: itemError } = await supabase
|
||||||
.from('submission_items')
|
.from('submission_items')
|
||||||
|
|||||||
@@ -81,12 +81,21 @@ export async function fetchSubmissionItems(submissionId: string): Promise<Submis
|
|||||||
switch (item.item_type) {
|
switch (item.item_type) {
|
||||||
case 'park': {
|
case 'park': {
|
||||||
const parkSub = (item as any).park_submission;
|
const parkSub = (item as any).park_submission;
|
||||||
|
// Fetch location from park_submission_locations if available
|
||||||
|
let locationData = null;
|
||||||
|
if (parkSub?.id) {
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('park_submission_locations')
|
||||||
|
.select('*')
|
||||||
|
.eq('park_submission_id', parkSub.id)
|
||||||
|
.maybeSingle();
|
||||||
|
locationData = data;
|
||||||
|
}
|
||||||
|
|
||||||
item_data = {
|
item_data = {
|
||||||
...parkSub,
|
...parkSub,
|
||||||
// Transform temp_location_data → location for form compatibility
|
// Transform park_submission_location → location for form compatibility
|
||||||
location: parkSub.temp_location_data || undefined,
|
location: locationData || undefined
|
||||||
// Remove temp_location_data to avoid confusion
|
|
||||||
temp_location_data: undefined
|
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -273,8 +282,6 @@ export async function updateSubmissionItem(
|
|||||||
const parkData = item_data as any;
|
const parkData = item_data as any;
|
||||||
const updateData: any = {
|
const updateData: any = {
|
||||||
...parkData,
|
...parkData,
|
||||||
// Transform location → temp_location_data for storage
|
|
||||||
temp_location_data: parkData.location || null,
|
|
||||||
updated_at: new Date().toISOString()
|
updated_at: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -289,34 +296,57 @@ export async function updateSubmissionItem(
|
|||||||
console.info('[Submission Flow] Saving park data', {
|
console.info('[Submission Flow] Saving park data', {
|
||||||
itemId,
|
itemId,
|
||||||
parkSubmissionId: item.park_submission_id,
|
parkSubmissionId: item.park_submission_id,
|
||||||
hasLocation: !!updateData.temp_location_data,
|
hasLocation: !!parkData.location,
|
||||||
locationData: updateData.temp_location_data,
|
|
||||||
fields: Object.keys(updateData),
|
fields: Object.keys(updateData),
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
const { error: updateError } = await supabase
|
// Update park_submissions
|
||||||
.from('park_submissions')
|
const { error: parkError } = await supabase
|
||||||
|
.from('park_submissions' as any)
|
||||||
.update(updateData)
|
.update(updateData)
|
||||||
.eq('id', item.park_submission_id);
|
.eq('id', item.park_submission_id);
|
||||||
|
|
||||||
if (updateError) {
|
if (parkError) {
|
||||||
handleError(updateError, {
|
console.error('[Submission Flow] Park update failed:', parkError);
|
||||||
action: 'Update Park Submission Data',
|
throw parkError;
|
||||||
metadata: {
|
|
||||||
itemId,
|
|
||||||
parkSubmissionId: item.park_submission_id,
|
|
||||||
updateFields: Object.keys(updateData)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
throw updateError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info('[Submission Flow] Park data saved successfully', {
|
// Update or insert location if provided
|
||||||
itemId,
|
if (parkData.location) {
|
||||||
parkSubmissionId: item.park_submission_id,
|
const locationData = {
|
||||||
timestamp: new Date().toISOString()
|
park_submission_id: item.park_submission_id,
|
||||||
});
|
name: parkData.location.name,
|
||||||
|
street_address: parkData.location.street_address || null,
|
||||||
|
city: parkData.location.city || null,
|
||||||
|
state_province: parkData.location.state_province || null,
|
||||||
|
country: parkData.location.country,
|
||||||
|
postal_code: parkData.location.postal_code || null,
|
||||||
|
latitude: parkData.location.latitude,
|
||||||
|
longitude: parkData.location.longitude,
|
||||||
|
timezone: parkData.location.timezone || null,
|
||||||
|
display_name: parkData.location.display_name || null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to update first, if no rows affected, insert
|
||||||
|
const { error: locationError } = await supabase
|
||||||
|
.from('park_submission_locations' as any)
|
||||||
|
.upsert(locationData, {
|
||||||
|
onConflict: 'park_submission_id'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (locationError) {
|
||||||
|
console.error('[Submission Flow] Location upsert failed:', locationError);
|
||||||
|
throw locationError;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[Submission Flow] Location saved', {
|
||||||
|
parkSubmissionId: item.park_submission_id,
|
||||||
|
locationName: locationData.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[Submission Flow] Park data saved successfully');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ride': {
|
case 'ride': {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import type { LocationData } from './location';
|
import type { LocationData } from './location';
|
||||||
|
|
||||||
export interface ParkSubmissionData {
|
export interface ParkSubmissionData {
|
||||||
|
id?: string; // park_submission.id for location lookup
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
|
|||||||
@@ -855,8 +855,7 @@ serve(withRateLimit(async (req) => {
|
|||||||
action: 'approval_park_data_debug',
|
action: 'approval_park_data_debug',
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
hasLocationId: !!itemData.location_id,
|
hasLocationId: !!itemData.location_id,
|
||||||
hasTempLocationData: !!itemData.temp_location_data,
|
parkSubmissionId: itemData.id,
|
||||||
tempLocationDataKeys: itemData.temp_location_data ? Object.keys(itemData.temp_location_data) : [],
|
|
||||||
parkSubmissionKeys: Object.keys((item as any).park_submission || {}),
|
parkSubmissionKeys: Object.keys((item as any).park_submission || {}),
|
||||||
requestId: tracking.requestId
|
requestId: tracking.requestId
|
||||||
});
|
});
|
||||||
@@ -1576,47 +1575,55 @@ function normalizeParkTypeValue(data: any): any {
|
|||||||
|
|
||||||
async function createPark(supabase: any, data: any): Promise<string> {
|
async function createPark(supabase: any, data: any): Promise<string> {
|
||||||
const submitterId = data._submitter_id;
|
const submitterId = data._submitter_id;
|
||||||
|
const parkSubmissionId = data.id; // Store the park_submission.id for location lookup
|
||||||
let uploadedPhotos: any[] = [];
|
let uploadedPhotos: any[] = [];
|
||||||
|
|
||||||
// Create location if temp_location_data exists and location_id is missing
|
|
||||||
if (data.temp_location_data && !data.location_id) {
|
|
||||||
edgeLogger.info('Creating location from temp data', {
|
|
||||||
action: 'approval_create_location',
|
|
||||||
locationName: data.temp_location_data.name
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: newLocation, error: locationError } = await supabase
|
// Create location if park_submission_locations exists and location_id is missing
|
||||||
.from('locations')
|
if (!data.location_id) {
|
||||||
.insert({
|
// Try to fetch location from relational table
|
||||||
name: data.temp_location_data.name,
|
const { data: locationData, error: locationFetchError } = await supabase
|
||||||
street_address: data.temp_location_data.street_address || null,
|
.from('park_submission_locations')
|
||||||
city: data.temp_location_data.city,
|
.select('*')
|
||||||
state_province: data.temp_location_data.state_province,
|
.eq('park_submission_id', parkSubmissionId)
|
||||||
country: data.temp_location_data.country,
|
|
||||||
latitude: data.temp_location_data.latitude,
|
|
||||||
longitude: data.temp_location_data.longitude,
|
|
||||||
timezone: data.temp_location_data.timezone,
|
|
||||||
postal_code: data.temp_location_data.postal_code
|
|
||||||
})
|
|
||||||
.select('id')
|
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (locationError) {
|
if (locationData && !locationFetchError) {
|
||||||
throw new Error(`Failed to create location: ${locationError.message}`);
|
edgeLogger.info('Creating location from relational table', {
|
||||||
|
action: 'approval_create_location',
|
||||||
|
locationName: locationData.name
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: newLocation, error: locationError } = await supabase
|
||||||
|
.from('locations')
|
||||||
|
.insert({
|
||||||
|
name: locationData.name,
|
||||||
|
street_address: locationData.street_address || null,
|
||||||
|
city: locationData.city,
|
||||||
|
state_province: locationData.state_province,
|
||||||
|
country: locationData.country,
|
||||||
|
latitude: locationData.latitude,
|
||||||
|
longitude: locationData.longitude,
|
||||||
|
timezone: locationData.timezone,
|
||||||
|
postal_code: locationData.postal_code
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (locationError) {
|
||||||
|
throw new Error(`Failed to create location: ${locationError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.location_id = newLocation.id;
|
||||||
|
|
||||||
|
edgeLogger.info('Location created successfully', {
|
||||||
|
action: 'approval_location_created',
|
||||||
|
locationId: newLocation.id,
|
||||||
|
locationName: locationData.name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
data.location_id = newLocation.id;
|
|
||||||
|
|
||||||
edgeLogger.info('Location created successfully', {
|
|
||||||
action: 'approval_location_created',
|
|
||||||
locationId: newLocation.id,
|
|
||||||
locationName: data.temp_location_data.name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up temp data
|
|
||||||
delete data.temp_location_data;
|
|
||||||
|
|
||||||
// Transform images object if present
|
// Transform images object if present
|
||||||
if (data.images) {
|
if (data.images) {
|
||||||
const { uploaded, banner_assignment, card_assignment } = data.images;
|
const { uploaded, banner_assignment, card_assignment } = data.images;
|
||||||
@@ -1653,36 +1660,44 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
parkId = data.park_id;
|
parkId = data.park_id;
|
||||||
delete data.park_id; // Remove ID from update data
|
delete data.park_id; // Remove ID from update data
|
||||||
|
|
||||||
// ✅ FIXED: Handle location updates from temp_location_data
|
// ✅ FIXED: Handle location updates from park_submission_locations
|
||||||
if (data.temp_location_data && !data.location_id) {
|
if (!data.location_id) {
|
||||||
edgeLogger.info('Creating location from temp data for update', {
|
// Try to fetch location from relational table
|
||||||
action: 'approval_create_location_update',
|
const { data: locationData, error: locationFetchError } = await supabase
|
||||||
locationName: data.temp_location_data.name
|
.from('park_submission_locations')
|
||||||
});
|
.select('*')
|
||||||
|
.eq('park_submission_id', parkSubmissionId)
|
||||||
const { data: newLocation, error: locationError } = await supabase
|
|
||||||
.from('locations')
|
|
||||||
.insert({
|
|
||||||
name: data.temp_location_data.name,
|
|
||||||
street_address: data.temp_location_data.street_address || null,
|
|
||||||
city: data.temp_location_data.city,
|
|
||||||
state_province: data.temp_location_data.state_province,
|
|
||||||
country: data.temp_location_data.country,
|
|
||||||
latitude: data.temp_location_data.latitude,
|
|
||||||
longitude: data.temp_location_data.longitude,
|
|
||||||
timezone: data.temp_location_data.timezone,
|
|
||||||
postal_code: data.temp_location_data.postal_code
|
|
||||||
})
|
|
||||||
.select('id')
|
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (locationError) {
|
if (locationData && !locationFetchError) {
|
||||||
throw new Error(`Failed to create location: ${locationError.message}`);
|
edgeLogger.info('Creating location from relational table for update', {
|
||||||
}
|
action: 'approval_create_location_update',
|
||||||
|
locationName: locationData.name
|
||||||
|
});
|
||||||
|
|
||||||
data.location_id = newLocation.id;
|
const { data: newLocation, error: locationError } = await supabase
|
||||||
|
.from('locations')
|
||||||
|
.insert({
|
||||||
|
name: locationData.name,
|
||||||
|
street_address: locationData.street_address || null,
|
||||||
|
city: locationData.city,
|
||||||
|
state_province: locationData.state_province,
|
||||||
|
country: locationData.country,
|
||||||
|
latitude: locationData.latitude,
|
||||||
|
longitude: locationData.longitude,
|
||||||
|
timezone: locationData.timezone,
|
||||||
|
postal_code: locationData.postal_code
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (locationError) {
|
||||||
|
throw new Error(`Failed to create location: ${locationError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.location_id = newLocation.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete data.temp_location_data;
|
|
||||||
|
|
||||||
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
|
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
|
||||||
const sanitizedData = sanitizeDateFields(normalizedData);
|
const sanitizedData = sanitizeDateFields(normalizedData);
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
-- Phase 1: Fix park_submissions.temp_location_data JSONB violation
|
||||||
|
-- Create relational table for temporary location data
|
||||||
|
|
||||||
|
-- Create park_submission_locations table
|
||||||
|
CREATE TABLE IF NOT EXISTS public.park_submission_locations (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
park_submission_id UUID NOT NULL REFERENCES public.park_submissions(id) ON DELETE CASCADE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
street_address TEXT,
|
||||||
|
city TEXT,
|
||||||
|
state_province TEXT,
|
||||||
|
country TEXT NOT NULL,
|
||||||
|
postal_code TEXT,
|
||||||
|
latitude NUMERIC(10, 7),
|
||||||
|
longitude NUMERIC(10, 7),
|
||||||
|
timezone TEXT,
|
||||||
|
display_name TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_park_submission_locations_submission
|
||||||
|
ON public.park_submission_locations(park_submission_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_park_submission_locations_country
|
||||||
|
ON public.park_submission_locations(country);
|
||||||
|
|
||||||
|
-- Enable RLS
|
||||||
|
ALTER TABLE public.park_submission_locations ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- RLS Policies (mirror park_submissions policies)
|
||||||
|
CREATE POLICY "Moderators can view all park submission locations"
|
||||||
|
ON public.park_submission_locations
|
||||||
|
FOR SELECT
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
is_moderator(auth.uid())
|
||||||
|
AND ((NOT has_mfa_enabled(auth.uid())) OR has_aal2())
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Users can view their own park submission locations"
|
||||||
|
ON public.park_submission_locations
|
||||||
|
FOR SELECT
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM content_submissions cs
|
||||||
|
INNER JOIN park_submissions ps ON ps.submission_id = cs.id
|
||||||
|
WHERE ps.id = park_submission_locations.park_submission_id
|
||||||
|
AND cs.user_id = auth.uid()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Users can insert park submission locations"
|
||||||
|
ON public.park_submission_locations
|
||||||
|
FOR INSERT
|
||||||
|
TO authenticated
|
||||||
|
WITH CHECK (
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM content_submissions cs
|
||||||
|
INNER JOIN park_submissions ps ON ps.submission_id = cs.id
|
||||||
|
WHERE ps.id = park_submission_locations.park_submission_id
|
||||||
|
AND cs.user_id = auth.uid()
|
||||||
|
)
|
||||||
|
AND NOT is_user_banned(auth.uid())
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Moderators can update park submission locations"
|
||||||
|
ON public.park_submission_locations
|
||||||
|
FOR UPDATE
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
is_moderator(auth.uid())
|
||||||
|
AND ((NOT has_mfa_enabled(auth.uid())) OR has_aal2())
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Moderators can delete park submission locations"
|
||||||
|
ON public.park_submission_locations
|
||||||
|
FOR DELETE
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
is_moderator(auth.uid())
|
||||||
|
AND ((NOT has_mfa_enabled(auth.uid())) OR has_aal2())
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Migrate existing temp_location_data to new table
|
||||||
|
INSERT INTO public.park_submission_locations (
|
||||||
|
park_submission_id,
|
||||||
|
name,
|
||||||
|
street_address,
|
||||||
|
city,
|
||||||
|
state_province,
|
||||||
|
country,
|
||||||
|
postal_code,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
timezone,
|
||||||
|
display_name
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
temp_location_data->>'name',
|
||||||
|
temp_location_data->>'street_address',
|
||||||
|
temp_location_data->>'city',
|
||||||
|
temp_location_data->>'state_province',
|
||||||
|
temp_location_data->>'country',
|
||||||
|
temp_location_data->>'postal_code',
|
||||||
|
(temp_location_data->>'latitude')::numeric,
|
||||||
|
(temp_location_data->>'longitude')::numeric,
|
||||||
|
temp_location_data->>'timezone',
|
||||||
|
temp_location_data->>'display_name'
|
||||||
|
FROM public.park_submissions
|
||||||
|
WHERE temp_location_data IS NOT NULL
|
||||||
|
AND temp_location_data->>'name' IS NOT NULL;
|
||||||
|
|
||||||
|
-- Drop the JSONB column
|
||||||
|
ALTER TABLE public.park_submissions DROP COLUMN IF EXISTS temp_location_data;
|
||||||
|
|
||||||
|
-- Add comment
|
||||||
|
COMMENT ON TABLE public.park_submission_locations IS
|
||||||
|
'Relational storage for park submission location data. Replaces temp_location_data JSONB column for proper queryability and data integrity.';
|
||||||
Reference in New Issue
Block a user