mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 13:31:13 -05:00
Add comprehensive edge-function error handling
Enhance error handling and logging across all edge functions: - Introduce a shared edgeFunctionWrapper with standardized error handling, request/response logging, tracing, and validation hooks. - Add runtime type validation utilities (ValidationError, validators, and parse/validate helpers) and integrate into edge flow. - Implement robust validation for incoming requests and known type mismatches, with detailed logs and structured responses. - Add post-RPC and post-database error logging to surface type/mismatch issues early. - Update approval/rejection entry points to leverage new validators and centralized error handling.
This commit is contained in:
196
supabase/functions/_shared/submissionValidation.ts
Normal file
196
supabase/functions/_shared/submissionValidation.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Submission-Specific Validation Utilities
|
||||
*
|
||||
* Validates submission and moderation request structures
|
||||
* Ensures type safety across the submission pipeline
|
||||
*/
|
||||
|
||||
import {
|
||||
validateUUID,
|
||||
validateUUIDArray,
|
||||
validateEntityType,
|
||||
validateActionType,
|
||||
validateObject,
|
||||
validateString,
|
||||
validateArray,
|
||||
type ValidEntityType,
|
||||
type ValidActionType,
|
||||
} from './typeValidation.ts';
|
||||
|
||||
/**
|
||||
* Validated approval request structure
|
||||
*/
|
||||
export interface ValidatedApprovalRequest {
|
||||
submissionId: string;
|
||||
itemIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validated rejection request structure
|
||||
*/
|
||||
export interface ValidatedRejectionRequest {
|
||||
submissionId: string;
|
||||
itemIds: string[];
|
||||
rejectionReason: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validated submission item
|
||||
*/
|
||||
export interface ValidatedSubmissionItem {
|
||||
id: string;
|
||||
item_type: ValidEntityType;
|
||||
action_type: ValidActionType;
|
||||
entity_id?: string | null;
|
||||
item_data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate approval request body
|
||||
*/
|
||||
export function validateApprovalRequest(
|
||||
body: unknown,
|
||||
requestId?: string
|
||||
): ValidatedApprovalRequest {
|
||||
validateObject(body, 'request_body', { requestId });
|
||||
const obj = body as Record<string, unknown>;
|
||||
|
||||
validateUUID(obj.submissionId, 'submissionId', { requestId });
|
||||
validateUUIDArray(obj.itemIds, 'itemIds', 1, { requestId });
|
||||
|
||||
return {
|
||||
submissionId: obj.submissionId as string,
|
||||
itemIds: obj.itemIds as string[],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate rejection request body
|
||||
*/
|
||||
export function validateRejectionRequest(
|
||||
body: unknown,
|
||||
requestId?: string
|
||||
): ValidatedRejectionRequest {
|
||||
validateObject(body, 'request_body', { requestId });
|
||||
const obj = body as Record<string, unknown>;
|
||||
|
||||
validateUUID(obj.submissionId, 'submissionId', { requestId });
|
||||
validateUUIDArray(obj.itemIds, 'itemIds', 1, { requestId });
|
||||
validateString(obj.rejectionReason, 'rejectionReason', { requestId });
|
||||
|
||||
return {
|
||||
submissionId: obj.submissionId as string,
|
||||
itemIds: obj.itemIds as string[],
|
||||
rejectionReason: obj.rejectionReason as string,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate submission item from database
|
||||
*/
|
||||
export function validateSubmissionItemFromDB(
|
||||
item: unknown,
|
||||
context?: Record<string, unknown>
|
||||
): ValidatedSubmissionItem {
|
||||
validateObject(item, 'submission_item', context);
|
||||
const obj = item as Record<string, unknown>;
|
||||
|
||||
// Validate required fields
|
||||
validateUUID(obj.id, 'submission_item.id', context);
|
||||
validateEntityType(obj.item_type, 'submission_item.item_type', {
|
||||
...context,
|
||||
itemId: obj.id,
|
||||
});
|
||||
validateActionType(obj.action_type, 'submission_item.action_type', {
|
||||
...context,
|
||||
itemId: obj.id,
|
||||
});
|
||||
|
||||
return {
|
||||
id: obj.id as string,
|
||||
item_type: obj.item_type as ValidEntityType,
|
||||
action_type: obj.action_type as ValidActionType,
|
||||
entity_id: obj.entity_id as string | null | undefined,
|
||||
item_data: obj.item_data as Record<string, unknown> | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate array of submission items
|
||||
*/
|
||||
export function validateSubmissionItems(
|
||||
items: unknown,
|
||||
context?: Record<string, unknown>
|
||||
): ValidatedSubmissionItem[] {
|
||||
validateArray(items, 'submission_items', 1, context);
|
||||
|
||||
const itemArray = items as unknown[];
|
||||
return itemArray.map((item, index) =>
|
||||
validateSubmissionItemFromDB(item, {
|
||||
...context,
|
||||
itemIndex: index,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that entity type matches the expected submission table
|
||||
* Helps catch data model mismatches early
|
||||
*/
|
||||
export function validateEntityTypeConsistency(
|
||||
item: ValidatedSubmissionItem,
|
||||
expectedTypes: ValidEntityType[],
|
||||
context?: Record<string, unknown>
|
||||
): void {
|
||||
if (!expectedTypes.includes(item.item_type)) {
|
||||
throw new Error(
|
||||
`Entity type mismatch: expected one of [${expectedTypes.join(', ')}] but got '${item.item_type}' ` +
|
||||
`for item ${item.id}. This may indicate a data model inconsistency. ` +
|
||||
`Context: ${JSON.stringify(context)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map entity type to submission table name
|
||||
* Useful for debugging and error messages
|
||||
*/
|
||||
export function getSubmissionTableName(entityType: ValidEntityType): string {
|
||||
const tableMap: Record<ValidEntityType, string> = {
|
||||
park: 'park_submissions',
|
||||
ride: 'ride_submissions',
|
||||
manufacturer: 'company_submissions',
|
||||
operator: 'company_submissions',
|
||||
property_owner: 'company_submissions',
|
||||
designer: 'company_submissions',
|
||||
company: 'company_submissions',
|
||||
ride_model: 'ride_model_submissions',
|
||||
photo: 'photo_submissions',
|
||||
milestone: 'timeline_event_submissions',
|
||||
timeline_event: 'timeline_event_submissions',
|
||||
};
|
||||
|
||||
return tableMap[entityType] || 'unknown_submissions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Map entity type to main table name
|
||||
* Useful for debugging and error messages
|
||||
*/
|
||||
export function getMainTableName(entityType: ValidEntityType): string {
|
||||
const tableMap: Record<ValidEntityType, string> = {
|
||||
park: 'parks',
|
||||
ride: 'rides',
|
||||
manufacturer: 'companies',
|
||||
operator: 'companies',
|
||||
property_owner: 'companies',
|
||||
designer: 'companies',
|
||||
company: 'companies',
|
||||
ride_model: 'ride_models',
|
||||
photo: 'photos',
|
||||
milestone: 'timeline_events',
|
||||
timeline_event: 'timeline_events',
|
||||
};
|
||||
|
||||
return tableMap[entityType] || 'unknown_table';
|
||||
}
|
||||
Reference in New Issue
Block a user