馃 distributed transcription service thistle.dunkirk.sh
1// Structured error codes for API responses 2 3export enum ErrorCode { 4 // Authentication errors 5 AUTH_REQUIRED = "AUTH_REQUIRED", 6 INVALID_SESSION = "INVALID_SESSION", 7 INVALID_CREDENTIALS = "INVALID_CREDENTIALS", 8 EMAIL_ALREADY_EXISTS = "EMAIL_ALREADY_EXISTS", 9 10 // Validation errors 11 VALIDATION_FAILED = "VALIDATION_FAILED", 12 MISSING_FIELD = "MISSING_FIELD", 13 INVALID_FORMAT = "INVALID_FORMAT", 14 FILE_TOO_LARGE = "FILE_TOO_LARGE", 15 UNSUPPORTED_FILE_TYPE = "UNSUPPORTED_FILE_TYPE", 16 17 // Transcription errors 18 TRANSCRIPTION_NOT_FOUND = "TRANSCRIPTION_NOT_FOUND", 19 TRANSCRIPTION_FAILED = "TRANSCRIPTION_FAILED", 20 WHISPER_SERVICE_UNAVAILABLE = "WHISPER_SERVICE_UNAVAILABLE", 21 WHISPER_SERVICE_ERROR = "WHISPER_SERVICE_ERROR", 22 UPLOAD_FAILED = "UPLOAD_FAILED", 23 24 // Session errors 25 SESSION_NOT_FOUND = "SESSION_NOT_FOUND", 26 SESSION_REVOKE_FAILED = "SESSION_REVOKE_FAILED", 27 28 // User errors 29 USER_UPDATE_FAILED = "USER_UPDATE_FAILED", 30 USER_DELETE_FAILED = "USER_DELETE_FAILED", 31 32 // Generic errors 33 INTERNAL_ERROR = "INTERNAL_ERROR", 34 NOT_FOUND = "NOT_FOUND", 35} 36 37export interface ApiError { 38 error: string; 39 code: ErrorCode; 40 details?: string; 41 field?: string; 42} 43 44export class AppError extends Error { 45 constructor( 46 public code: ErrorCode, 47 message: string, 48 public statusCode: number = 500, 49 public details?: string, 50 public field?: string, 51 ) { 52 super(message); 53 this.name = "AppError"; 54 } 55 56 toJSON(): ApiError { 57 return { 58 error: this.message, 59 code: this.code, 60 details: this.details, 61 field: this.field, 62 }; 63 } 64 65 toResponse(): Response { 66 return Response.json(this.toJSON(), { status: this.statusCode }); 67 } 68} 69 70// Helper functions for common errors 71export const AuthErrors = { 72 required: () => 73 new AppError(ErrorCode.AUTH_REQUIRED, "Authentication required", 401), 74 invalidSession: () => 75 new AppError(ErrorCode.INVALID_SESSION, "Invalid or expired session", 401), 76 invalidCredentials: () => 77 new AppError( 78 ErrorCode.INVALID_CREDENTIALS, 79 "Invalid email or password", 80 401, 81 ), 82 emailExists: () => 83 new AppError( 84 ErrorCode.EMAIL_ALREADY_EXISTS, 85 "Email already registered", 86 400, 87 ), 88}; 89 90export const ValidationErrors = { 91 missingField: (field: string) => 92 new AppError( 93 ErrorCode.MISSING_FIELD, 94 `${field} is required`, 95 400, 96 undefined, 97 field, 98 ), 99 invalidFormat: (field: string, details?: string) => 100 new AppError( 101 ErrorCode.INVALID_FORMAT, 102 `Invalid ${field} format`, 103 400, 104 details, 105 field, 106 ), 107 fileTooLarge: (maxSize: string) => 108 new AppError( 109 ErrorCode.FILE_TOO_LARGE, 110 `File size must be less than ${maxSize}`, 111 400, 112 ), 113 unsupportedFileType: (supportedTypes: string) => 114 new AppError( 115 ErrorCode.UNSUPPORTED_FILE_TYPE, 116 `Unsupported file type. Supported: ${supportedTypes}`, 117 400, 118 ), 119}; 120 121export const TranscriptionErrors = { 122 notFound: () => 123 new AppError( 124 ErrorCode.TRANSCRIPTION_NOT_FOUND, 125 "Transcription not found", 126 404, 127 ), 128 failed: (details?: string) => 129 new AppError( 130 ErrorCode.TRANSCRIPTION_FAILED, 131 "Transcription failed", 132 500, 133 details, 134 ), 135 serviceUnavailable: () => 136 new AppError( 137 ErrorCode.WHISPER_SERVICE_UNAVAILABLE, 138 "Transcription service unavailable", 139 503, 140 "The Whisper transcription service is not responding. Please try again later.", 141 ), 142 serviceError: (details: string) => 143 new AppError( 144 ErrorCode.WHISPER_SERVICE_ERROR, 145 "Transcription service error", 146 502, 147 details, 148 ), 149 uploadFailed: (details?: string) => 150 new AppError(ErrorCode.UPLOAD_FAILED, "Upload failed", 500, details), 151}; 152 153export const SessionErrors = { 154 notFound: () => 155 new AppError(ErrorCode.SESSION_NOT_FOUND, "Session not found", 404), 156 revokeFailed: () => 157 new AppError( 158 ErrorCode.SESSION_REVOKE_FAILED, 159 "Failed to revoke session", 160 500, 161 ), 162}; 163 164export const UserErrors = { 165 updateFailed: (field: string, details?: string) => 166 new AppError( 167 ErrorCode.USER_UPDATE_FAILED, 168 `Failed to update ${field}`, 169 500, 170 details, 171 ), 172 deleteFailed: () => 173 new AppError(ErrorCode.USER_DELETE_FAILED, "Failed to delete user", 500), 174}; 175 176// Generic error handler 177export function handleError(error: unknown): Response { 178 if (error instanceof AppError) { 179 return error.toResponse(); 180 } 181 182 // Handle database unique constraint errors 183 if ( 184 error instanceof Error && 185 error.message?.includes("UNIQUE constraint failed") 186 ) { 187 if (error.message.includes("email")) { 188 return AuthErrors.emailExists().toResponse(); 189 } 190 } 191 192 // Log unexpected errors 193 console.error("Unexpected error:", error); 194 195 // Return generic error 196 return new AppError( 197 ErrorCode.INTERNAL_ERROR, 198 "An unexpected error occurred", 199 500, 200 ).toResponse(); 201}