Fix RLS policy for JSONB migration

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 20:42:30 +00:00
parent 3d07198454
commit 223e743330
3 changed files with 1013 additions and 0 deletions

View File

@@ -59,6 +59,38 @@ export type Database = {
}
Relationships: []
}
admin_audit_details: {
Row: {
audit_log_id: string
created_at: string | null
detail_key: string
detail_value: string
id: string
}
Insert: {
audit_log_id: string
created_at?: string | null
detail_key: string
detail_value: string
id?: string
}
Update: {
audit_log_id?: string
created_at?: string | null
detail_key?: string
detail_value?: string
id?: string
}
Relationships: [
{
foreignKeyName: "admin_audit_details_audit_log_id_fkey"
columns: ["audit_log_id"]
isOneToOne: false
referencedRelation: "admin_audit_log"
referencedColumns: ["id"]
},
]
}
admin_audit_log: {
Row: {
action: string
@@ -464,6 +496,44 @@ export type Database = {
},
]
}
conflict_detail_fields: {
Row: {
conflict_resolution_id: string
conflicting_value_1: string | null
conflicting_value_2: string | null
created_at: string | null
field_name: string
id: string
resolved_value: string | null
}
Insert: {
conflict_resolution_id: string
conflicting_value_1?: string | null
conflicting_value_2?: string | null
created_at?: string | null
field_name: string
id?: string
resolved_value?: string | null
}
Update: {
conflict_resolution_id?: string
conflicting_value_1?: string | null
conflicting_value_2?: string | null
created_at?: string | null
field_name?: string
id?: string
resolved_value?: string | null
}
Relationships: [
{
foreignKeyName: "conflict_detail_fields_conflict_resolution_id_fkey"
columns: ["conflict_resolution_id"]
isOneToOne: false
referencedRelation: "conflict_resolutions"
referencedColumns: ["id"]
},
]
}
conflict_resolutions: {
Row: {
conflict_details: Json | null
@@ -511,49 +581,64 @@ export type Database = {
}
contact_email_threads: {
Row: {
attachment_count: number | null
body_html: string | null
body_text: string
created_at: string
direction: string
email_provider: string | null
from_email: string
id: string
in_reply_to: string | null
is_auto_reply: boolean | null
message_id: string
metadata: Json | null
reference_chain: string[] | null
sent_by: string | null
smtp_message_id: string | null
spam_score: number | null
subject: string
submission_id: string
to_email: string
}
Insert: {
attachment_count?: number | null
body_html?: string | null
body_text: string
created_at?: string
direction: string
email_provider?: string | null
from_email: string
id?: string
in_reply_to?: string | null
is_auto_reply?: boolean | null
message_id: string
metadata?: Json | null
reference_chain?: string[] | null
sent_by?: string | null
smtp_message_id?: string | null
spam_score?: number | null
subject: string
submission_id: string
to_email: string
}
Update: {
attachment_count?: number | null
body_html?: string | null
body_text?: string
created_at?: string
direction?: string
email_provider?: string | null
from_email?: string
id?: string
in_reply_to?: string | null
is_auto_reply?: boolean | null
message_id?: string
metadata?: Json | null
reference_chain?: string[] | null
sent_by?: string | null
smtp_message_id?: string | null
spam_score?: number | null
subject?: string
submission_id?: string
to_email?: string
@@ -1049,49 +1134,97 @@ export type Database = {
}
historical_parks: {
Row: {
banner_image_id: string | null
banner_image_url: string | null
card_image_id: string | null
card_image_url: string | null
closing_date: string | null
closing_date_precision: string | null
closure_reason: string | null
created_at: string
description: string | null
email: string | null
final_state_data: Json
id: string
location_id: string | null
name: string
opening_date: string | null
opening_date_precision: string | null
operated_from: string | null
operated_from_precision: string | null
operated_until: string | null
operated_until_precision: string | null
operator_id: string | null
original_park_id: string | null
park_type: string | null
phone: string | null
property_owner_id: string | null
slug: string
status: string | null
successor_park_id: string | null
website_url: string | null
}
Insert: {
banner_image_id?: string | null
banner_image_url?: string | null
card_image_id?: string | null
card_image_url?: string | null
closing_date?: string | null
closing_date_precision?: string | null
closure_reason?: string | null
created_at?: string
description?: string | null
email?: string | null
final_state_data: Json
id?: string
location_id?: string | null
name: string
opening_date?: string | null
opening_date_precision?: string | null
operated_from?: string | null
operated_from_precision?: string | null
operated_until?: string | null
operated_until_precision?: string | null
operator_id?: string | null
original_park_id?: string | null
park_type?: string | null
phone?: string | null
property_owner_id?: string | null
slug: string
status?: string | null
successor_park_id?: string | null
website_url?: string | null
}
Update: {
banner_image_id?: string | null
banner_image_url?: string | null
card_image_id?: string | null
card_image_url?: string | null
closing_date?: string | null
closing_date_precision?: string | null
closure_reason?: string | null
created_at?: string
description?: string | null
email?: string | null
final_state_data?: Json
id?: string
location_id?: string | null
name?: string
opening_date?: string | null
opening_date_precision?: string | null
operated_from?: string | null
operated_from_precision?: string | null
operated_until?: string | null
operated_until_precision?: string | null
operator_id?: string | null
original_park_id?: string | null
park_type?: string | null
phone?: string | null
property_owner_id?: string | null
slug?: string
status?: string | null
successor_park_id?: string | null
website_url?: string | null
}
Relationships: [
{
@@ -1101,6 +1234,13 @@ export type Database = {
referencedRelation: "locations"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_parks_operator_id_fkey"
columns: ["operator_id"]
isOneToOne: false
referencedRelation: "companies"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_parks_original_park_id_fkey"
columns: ["original_park_id"]
@@ -1108,6 +1248,13 @@ export type Database = {
referencedRelation: "parks"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_parks_property_owner_id_fkey"
columns: ["property_owner_id"]
isOneToOne: false
referencedRelation: "companies"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_parks_successor_park_id_fkey"
columns: ["successor_park_id"]
@@ -1119,10 +1266,30 @@ export type Database = {
}
historical_rides: {
Row: {
banner_image_id: string | null
banner_image_url: string | null
card_image_id: string | null
card_image_url: string | null
category: string | null
closing_date: string | null
closing_date_precision: string | null
coaster_type: string | null
created_at: string
description: string | null
designer_id: string | null
drop_height_meters: number | null
final_state_data: Json
id: string
intensity_level: string | null
inversions: number | null
length_meters: number | null
manufacturer_id: string | null
max_g_force: number | null
max_height_meters: number | null
max_speed_kmh: number | null
name: string
opening_date: string | null
opening_date_precision: string | null
operated_from: string | null
operated_from_precision: string | null
operated_until: string | null
@@ -1131,14 +1298,37 @@ export type Database = {
park_id: string | null
relocated_to_park_id: string | null
removal_reason: string | null
ride_model_id: string | null
seating_type: string | null
slug: string
status: string | null
successor_ride_id: string | null
}
Insert: {
banner_image_id?: string | null
banner_image_url?: string | null
card_image_id?: string | null
card_image_url?: string | null
category?: string | null
closing_date?: string | null
closing_date_precision?: string | null
coaster_type?: string | null
created_at?: string
description?: string | null
designer_id?: string | null
drop_height_meters?: number | null
final_state_data: Json
id?: string
intensity_level?: string | null
inversions?: number | null
length_meters?: number | null
manufacturer_id?: string | null
max_g_force?: number | null
max_height_meters?: number | null
max_speed_kmh?: number | null
name: string
opening_date?: string | null
opening_date_precision?: string | null
operated_from?: string | null
operated_from_precision?: string | null
operated_until?: string | null
@@ -1147,14 +1337,37 @@ export type Database = {
park_id?: string | null
relocated_to_park_id?: string | null
removal_reason?: string | null
ride_model_id?: string | null
seating_type?: string | null
slug: string
status?: string | null
successor_ride_id?: string | null
}
Update: {
banner_image_id?: string | null
banner_image_url?: string | null
card_image_id?: string | null
card_image_url?: string | null
category?: string | null
closing_date?: string | null
closing_date_precision?: string | null
coaster_type?: string | null
created_at?: string
description?: string | null
designer_id?: string | null
drop_height_meters?: number | null
final_state_data?: Json
id?: string
intensity_level?: string | null
inversions?: number | null
length_meters?: number | null
manufacturer_id?: string | null
max_g_force?: number | null
max_height_meters?: number | null
max_speed_kmh?: number | null
name?: string
opening_date?: string | null
opening_date_precision?: string | null
operated_from?: string | null
operated_from_precision?: string | null
operated_until?: string | null
@@ -1163,10 +1376,27 @@ export type Database = {
park_id?: string | null
relocated_to_park_id?: string | null
removal_reason?: string | null
ride_model_id?: string | null
seating_type?: string | null
slug?: string
status?: string | null
successor_ride_id?: string | null
}
Relationships: [
{
foreignKeyName: "historical_rides_designer_id_fkey"
columns: ["designer_id"]
isOneToOne: false
referencedRelation: "companies"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_rides_manufacturer_id_fkey"
columns: ["manufacturer_id"]
isOneToOne: false
referencedRelation: "companies"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_rides_original_ride_id_fkey"
columns: ["original_ride_id"]
@@ -1188,6 +1418,13 @@ export type Database = {
referencedRelation: "parks"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_rides_ride_model_id_fkey"
columns: ["ride_model_id"]
isOneToOne: false
referencedRelation: "ride_models"
referencedColumns: ["id"]
},
{
foreignKeyName: "historical_rides_successor_ride_id_fkey"
columns: ["successor_ride_id"]
@@ -1197,6 +1434,41 @@ export type Database = {
},
]
}
item_change_fields: {
Row: {
created_at: string | null
edit_history_id: string
field_name: string
id: string
new_value: string | null
old_value: string | null
}
Insert: {
created_at?: string | null
edit_history_id: string
field_name: string
id?: string
new_value?: string | null
old_value?: string | null
}
Update: {
created_at?: string | null
edit_history_id?: string
field_name?: string
id?: string
new_value?: string | null
old_value?: string | null
}
Relationships: [
{
foreignKeyName: "item_change_fields_edit_history_id_fkey"
columns: ["edit_history_id"]
isOneToOne: false
referencedRelation: "item_edit_history"
referencedColumns: ["id"]
},
]
}
item_edit_history: {
Row: {
changes: Json
@@ -1363,6 +1635,38 @@ export type Database = {
},
]
}
moderation_audit_metadata: {
Row: {
audit_log_id: string
created_at: string | null
id: string
metadata_key: string
metadata_value: string
}
Insert: {
audit_log_id: string
created_at?: string | null
id?: string
metadata_key: string
metadata_value: string
}
Update: {
audit_log_id?: string
created_at?: string | null
id?: string
metadata_key?: string
metadata_value?: string
}
Relationships: [
{
foreignKeyName: "moderation_audit_metadata_audit_log_id_fkey"
columns: ["audit_log_id"]
isOneToOne: false
referencedRelation: "moderation_audit_log"
referencedColumns: ["id"]
},
]
}
notification_channels: {
Row: {
channel_type: string
@@ -1423,6 +1727,38 @@ export type Database = {
}
Relationships: []
}
notification_event_data: {
Row: {
created_at: string | null
event_key: string
event_value: string
id: string
notification_log_id: string
}
Insert: {
created_at?: string | null
event_key: string
event_value: string
id?: string
notification_log_id: string
}
Update: {
created_at?: string | null
event_key?: string
event_value?: string
id?: string
notification_log_id?: string
}
Relationships: [
{
foreignKeyName: "notification_event_data_notification_log_id_fkey"
columns: ["notification_log_id"]
isOneToOne: false
referencedRelation: "notification_logs"
referencedColumns: ["id"]
},
]
}
notification_logs: {
Row: {
channel: string
@@ -2188,6 +2524,41 @@ export type Database = {
}
Relationships: []
}
profile_change_fields: {
Row: {
audit_log_id: string
created_at: string | null
field_name: string
id: string
new_value: string | null
old_value: string | null
}
Insert: {
audit_log_id: string
created_at?: string | null
field_name: string
id?: string
new_value?: string | null
old_value?: string | null
}
Update: {
audit_log_id?: string
created_at?: string | null
field_name?: string
id?: string
new_value?: string | null
old_value?: string | null
}
Relationships: [
{
foreignKeyName: "profile_change_fields_audit_log_id_fkey"
columns: ["audit_log_id"]
isOneToOne: false
referencedRelation: "profile_audit_log"
referencedColumns: ["id"]
},
]
}
profiles: {
Row: {
auth0_sub: string | null
@@ -2377,6 +2748,47 @@ export type Database = {
}
Relationships: []
}
request_breadcrumbs: {
Row: {
category: string
created_at: string | null
id: string
level: string | null
message: string
request_id: string
sequence_order: number
timestamp: string
}
Insert: {
category: string
created_at?: string | null
id?: string
level?: string | null
message: string
request_id: string
sequence_order: number
timestamp: string
}
Update: {
category?: string
created_at?: string | null
id?: string
level?: string | null
message?: string
request_id?: string
sequence_order?: number
timestamp?: string
}
Relationships: [
{
foreignKeyName: "request_breadcrumbs_request_id_fkey"
columns: ["request_id"]
isOneToOne: false
referencedRelation: "request_metadata"
referencedColumns: ["request_id"]
},
]
}
request_metadata: {
Row: {
breadcrumbs: Json | null
@@ -2394,7 +2806,12 @@ export type Database = {
method: string
parent_request_id: string | null
request_id: string
request_method: string | null
request_path: string | null
response_status: number | null
response_time_ms: number | null
retry_count: number | null
session_id: string | null
started_at: string
status_code: number | null
trace_id: string | null
@@ -2417,7 +2834,12 @@ export type Database = {
method: string
parent_request_id?: string | null
request_id: string
request_method?: string | null
request_path?: string | null
response_status?: number | null
response_time_ms?: number | null
retry_count?: number | null
session_id?: string | null
started_at?: string
status_code?: number | null
trace_id?: string | null
@@ -2440,7 +2862,12 @@ export type Database = {
method?: string
parent_request_id?: string | null
request_id?: string
request_method?: string | null
request_path?: string | null
response_status?: number | null
response_time_ms?: number | null
retry_count?: number | null
session_id?: string | null
started_at?: string
status_code?: number | null
trace_id?: string | null
@@ -4308,6 +4735,54 @@ export type Database = {
},
]
}
submission_metadata: {
Row: {
created_at: string | null
display_order: number | null
id: string
metadata_key: string
metadata_value: string
submission_id: string
updated_at: string | null
value_type: string | null
}
Insert: {
created_at?: string | null
display_order?: number | null
id?: string
metadata_key: string
metadata_value: string
submission_id: string
updated_at?: string | null
value_type?: string | null
}
Update: {
created_at?: string | null
display_order?: number | null
id?: string
metadata_key?: string
metadata_value?: string
submission_id?: string
updated_at?: string | null
value_type?: string | null
}
Relationships: [
{
foreignKeyName: "submission_metadata_submission_id_fkey"
columns: ["submission_id"]
isOneToOne: false
referencedRelation: "content_submissions"
referencedColumns: ["id"]
},
{
foreignKeyName: "submission_metadata_submission_id_fkey"
columns: ["submission_id"]
isOneToOne: false
referencedRelation: "moderation_queue_with_entities"
referencedColumns: ["id"]
},
]
}
test_data_registry: {
Row: {
created_at: string

193
src/lib/auditHelpers.ts Normal file
View File

@@ -0,0 +1,193 @@
/**
* Helper functions for relational audit logging
* Replaces JSONB storage with proper relational tables
*/
import { supabase } from '@/integrations/supabase/client';
import { logger } from './logger';
/**
* Write admin audit details to relational table
* Replaces JSONB admin_audit_log.details column
*/
export async function writeAdminAuditDetails(
auditLogId: string,
details: Record<string, unknown>
): Promise<void> {
if (!details || Object.keys(details).length === 0) return;
const entries = Object.entries(details).map(([key, value]) => ({
audit_log_id: auditLogId,
detail_key: key,
detail_value: typeof value === 'object' ? JSON.stringify(value) : String(value),
}));
const { error } = await supabase
.from('admin_audit_details')
.insert(entries);
if (error) {
logger.error('Failed to write admin audit details', { error, auditLogId });
throw error;
}
}
/**
* Write moderation audit metadata to relational table
* Replaces JSONB moderation_audit_log.metadata column
*/
export async function writeModerationAuditMetadata(
auditLogId: string,
metadata: Record<string, unknown>
): Promise<void> {
if (!metadata || Object.keys(metadata).length === 0) return;
const entries = Object.entries(metadata).map(([key, value]) => ({
audit_log_id: auditLogId,
metadata_key: key,
metadata_value: typeof value === 'object' ? JSON.stringify(value) : String(value),
}));
const { error } = await supabase
.from('moderation_audit_metadata')
.insert(entries);
if (error) {
logger.error('Failed to write moderation audit metadata', { error, auditLogId });
throw error;
}
}
/**
* Write item change fields to relational table
* Replaces JSONB item_edit_history.changes column
*/
export async function writeItemChangeFields(
editHistoryId: string,
changes: Record<string, { old_value?: unknown; new_value?: unknown }>
): Promise<void> {
if (!changes || Object.keys(changes).length === 0) return;
const entries = Object.entries(changes).map(([fieldName, change]) => ({
edit_history_id: editHistoryId,
field_name: fieldName,
old_value: change.old_value !== undefined
? (typeof change.old_value === 'object' ? JSON.stringify(change.old_value) : String(change.old_value))
: null,
new_value: change.new_value !== undefined
? (typeof change.new_value === 'object' ? JSON.stringify(change.new_value) : String(change.new_value))
: null,
}));
const { error } = await supabase
.from('item_change_fields')
.insert(entries);
if (error) {
logger.error('Failed to write item change fields', { error, editHistoryId });
throw error;
}
}
/**
* Write request breadcrumbs to relational table
* Replaces JSONB request_metadata.breadcrumbs column
*/
export async function writeRequestBreadcrumbs(
requestId: string,
breadcrumbs: Array<{
timestamp: string;
category: string;
message: string;
level?: 'debug' | 'info' | 'warn' | 'error';
}>
): Promise<void> {
if (!breadcrumbs || breadcrumbs.length === 0) return;
const entries = breadcrumbs.map((breadcrumb, index) => ({
request_id: requestId,
timestamp: breadcrumb.timestamp,
category: breadcrumb.category,
message: breadcrumb.message,
level: breadcrumb.level || 'info',
sequence_order: index,
}));
const { error } = await supabase
.from('request_breadcrumbs')
.insert(entries);
if (error) {
logger.error('Failed to write request breadcrumbs', { error, requestId });
throw error;
}
}
/**
* Read admin audit details from relational table
*/
export async function readAdminAuditDetails(
auditLogId: string
): Promise<Record<string, string>> {
const { data, error } = await supabase
.from('admin_audit_details')
.select('detail_key, detail_value')
.eq('audit_log_id', auditLogId);
if (error) {
logger.error('Failed to read admin audit details', { error, auditLogId });
return {};
}
return data.reduce((acc, row) => {
acc[row.detail_key] = row.detail_value;
return acc;
}, {} as Record<string, string>);
}
/**
* Read moderation audit metadata from relational table
*/
export async function readModerationAuditMetadata(
auditLogId: string
): Promise<Record<string, string>> {
const { data, error } = await supabase
.from('moderation_audit_metadata')
.select('metadata_key, metadata_value')
.eq('audit_log_id', auditLogId);
if (error) {
logger.error('Failed to read moderation audit metadata', { error, auditLogId });
return {};
}
return data.reduce((acc, row) => {
acc[row.metadata_key] = row.metadata_value;
return acc;
}, {} as Record<string, string>);
}
/**
* Read item change fields from relational table
*/
export async function readItemChangeFields(
editHistoryId: string
): Promise<Record<string, { old_value: string | null; new_value: string | null }>> {
const { data, error } = await supabase
.from('item_change_fields')
.select('field_name, old_value, new_value')
.eq('edit_history_id', editHistoryId);
if (error) {
logger.error('Failed to read item change fields', { error, editHistoryId });
return {};
}
return data.reduce((acc, row) => {
acc[row.field_name] = {
old_value: row.old_value,
new_value: row.new_value,
};
return acc;
}, {} as Record<string, { old_value: string | null; new_value: string | null }>);
}