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:
gpt-engineer-app[bot]
2025-11-06 15:45:12 +00:00
parent 1a4e30674f
commit 5b0ac813e2
8 changed files with 379 additions and 124 deletions

View File

@@ -855,8 +855,7 @@ serve(withRateLimit(async (req) => {
action: 'approval_park_data_debug',
itemId: item.id,
hasLocationId: !!itemData.location_id,
hasTempLocationData: !!itemData.temp_location_data,
tempLocationDataKeys: itemData.temp_location_data ? Object.keys(itemData.temp_location_data) : [],
parkSubmissionId: itemData.id,
parkSubmissionKeys: Object.keys((item as any).park_submission || {}),
requestId: tracking.requestId
});
@@ -1576,47 +1575,55 @@ function normalizeParkTypeValue(data: any): any {
async function createPark(supabase: any, data: any): Promise<string> {
const submitterId = data._submitter_id;
const parkSubmissionId = data.id; // Store the park_submission.id for location lookup
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
.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')
// Create location if park_submission_locations exists and location_id is missing
if (!data.location_id) {
// Try to fetch location from relational table
const { data: locationData, error: locationFetchError } = await supabase
.from('park_submission_locations')
.select('*')
.eq('park_submission_id', parkSubmissionId)
.single();
if (locationError) {
throw new Error(`Failed to create location: ${locationError.message}`);
if (locationData && !locationFetchError) {
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
if (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;
delete data.park_id; // Remove ID from update data
// ✅ FIXED: Handle location updates from temp_location_data
if (data.temp_location_data && !data.location_id) {
edgeLogger.info('Creating location from temp data for update', {
action: 'approval_create_location_update',
locationName: data.temp_location_data.name
});
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')
// ✅ FIXED: Handle location updates from park_submission_locations
if (!data.location_id) {
// Try to fetch location from relational table
const { data: locationData, error: locationFetchError } = await supabase
.from('park_submission_locations')
.select('*')
.eq('park_submission_id', parkSubmissionId)
.single();
if (locationError) {
throw new Error(`Failed to create location: ${locationError.message}`);
if (locationData && !locationFetchError) {
edgeLogger.info('Creating location from relational table for update', {
action: 'approval_create_location_update',
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;
}
data.location_id = newLocation.id;
}
delete data.temp_location_data;
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
const sanitizedData = sanitizeDateFields(normalizedData);

View File

@@ -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.';