Files
thrilltrack-explorer/supabase/functions/_shared/submissionValidation.ts
gpt-engineer-app[bot] 7181fdbcac 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.
2025-11-11 02:54:50 +00:00

197 lines
5.1 KiB
TypeScript

/**
* 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';
}