mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 07:51:13 -05:00
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.
197 lines
5.1 KiB
TypeScript
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';
|
|
}
|