feat: add serverless-offline plugin and new showSuggestion function
- Added serverless-offline plugin to package.json and serverless.yml for local development. - Implemented new showSuggestion function in host.yml with appropriate memory size and event configuration. - Removed deprecated getSuggestion function from minglaradmin.yml and updated related handlers. - Enhanced safeHandler to convert Prisma errors to ApiError automatically, improving error handling. - Introduced comprehensive Prisma error handling in ApiError.ts, mapping error codes to user-friendly messages and HTTP status codes.
This commit is contained in:
2304
package-lock.json
generated
2304
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -89,6 +89,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.2.5",
|
||||
"serverless-esbuild": "^1.55.1",
|
||||
"serverless-offline": "^14.4.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.4",
|
||||
"ts-jest": "^29.1.2",
|
||||
|
||||
@@ -81,4 +81,7 @@ package:
|
||||
functions:
|
||||
- ${file(./serverless/functions/host.yml)}
|
||||
- ${file(./serverless/functions/minglaradmin.yml)}
|
||||
- ${file(./serverless/functions/prepopulate.yml)}
|
||||
- ${file(./serverless/functions/prepopulate.yml)}
|
||||
|
||||
plugins:
|
||||
- serverless-offline
|
||||
@@ -177,6 +177,22 @@ getAllActivityType:
|
||||
path: /host/Activity_Hub/OnBoarding/get-activity-type
|
||||
method: get
|
||||
|
||||
showSuggestion:
|
||||
handler: src/modules/host/handlers/Host_Admin/onboarding/showSuggestion.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/Host_Admin/onboarding/showSuggestion.handler.*'
|
||||
- 'src/modules/host/services/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/get-suggestion
|
||||
method: get
|
||||
|
||||
getAllHostActivity:
|
||||
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllHostActivity.handler
|
||||
memorySize: 384
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
# Minglar Admin Module Functions
|
||||
# Admin dashboard and management endpoints
|
||||
|
||||
getSuggestion:
|
||||
handler: src/modules/minglaradmin/handlers/getSuggestion.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/minglaradmin/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/get-suggestion
|
||||
method: get
|
||||
|
||||
|
||||
minglarRegistration:
|
||||
handler: src/modules/minglaradmin/handlers/registration.handler
|
||||
@@ -235,7 +222,7 @@ getAllCoadminAndAMDetails:
|
||||
method: get
|
||||
|
||||
getAllInvitedCoadminAndAMDetails:
|
||||
handler: src/modules/minglaradmin/handlers/handlers/settings/teammates/getAllInvitedCoadminAndAM.handler
|
||||
handler: src/modules/minglaradmin/handlers/settings/teammates/getAllInvitedCoadminAndAM.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
@@ -377,11 +364,11 @@ rejectHostApplicationAM:
|
||||
method: post
|
||||
|
||||
addPQQSuggestion:
|
||||
handler: src/modules/minglar/handlers/hosthub/hosts/addPQQSuggestion.handler
|
||||
handler: src/modules/minglaradmin/handlers/hosthub/hosts/addPQQSuggestion.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/minglar/handlers/hosthub/hosts/**'
|
||||
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||
- 'src/modules/minglaradmin/services/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
|
||||
@@ -44,6 +44,27 @@ export const safeHandler = (
|
||||
} catch (error: any) {
|
||||
console.error('❌ Error occurred:', error);
|
||||
|
||||
// Convert Prisma errors to ApiError automatically
|
||||
if (ApiError.isPrismaError(error)) {
|
||||
const apiError = ApiError.fromPrismaError(error);
|
||||
return {
|
||||
statusCode: apiError.statusCode,
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
message: apiError.message,
|
||||
statusCode: apiError.statusCode,
|
||||
data: null,
|
||||
error: {
|
||||
code: apiError.code || apiError.statusCode,
|
||||
description: apiError.message,
|
||||
statusCode: apiError.statusCode,
|
||||
...(apiError.meta && { meta: apiError.meta }),
|
||||
...(process.env.STAGE !== 'prod' && { debug: apiError.stack }),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof ApiError) {
|
||||
return {
|
||||
statusCode: error.statusCode,
|
||||
@@ -53,9 +74,10 @@ export const safeHandler = (
|
||||
statusCode: error.statusCode,
|
||||
data: null,
|
||||
error: {
|
||||
code: error.statusCode,
|
||||
code: error.code || error.statusCode,
|
||||
description: error.message,
|
||||
statusCode: error.statusCode,
|
||||
...(error.meta && { meta: error.meta }),
|
||||
...(process.env.STAGE !== 'prod' && { debug: error.stack }),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,3 +1,75 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
/**
|
||||
* Prisma error code mappings to user-friendly messages and HTTP status codes
|
||||
*/
|
||||
const PRISMA_ERROR_CODES: Record<string, { statusCode: number; message: string }> = {
|
||||
// Common errors (P1xxx)
|
||||
P1000: { statusCode: 500, message: 'Authentication failed against database server' },
|
||||
P1001: { statusCode: 503, message: 'Database server is not reachable' },
|
||||
P1002: { statusCode: 504, message: 'Database server timed out' },
|
||||
P1003: { statusCode: 500, message: 'Database does not exist' },
|
||||
P1008: { statusCode: 504, message: 'Database operation timed out' },
|
||||
P1009: { statusCode: 409, message: 'Database already exists' },
|
||||
P1010: { statusCode: 403, message: 'User was denied access to the database' },
|
||||
P1011: { statusCode: 500, message: 'Error opening a TLS connection' },
|
||||
P1012: { statusCode: 500, message: 'Schema validation error' },
|
||||
P1013: { statusCode: 400, message: 'Invalid database connection string' },
|
||||
P1014: { statusCode: 500, message: 'Underlying model does not exist' },
|
||||
P1015: { statusCode: 500, message: 'Database schema uses unsupported features' },
|
||||
P1016: { statusCode: 400, message: 'Raw query has incorrect number of parameters' },
|
||||
P1017: { statusCode: 503, message: 'Database server has closed the connection' },
|
||||
|
||||
// Prisma Client Query Engine errors (P2xxx)
|
||||
P2000: { statusCode: 400, message: 'Value too long for column' },
|
||||
P2001: { statusCode: 404, message: 'Record not found' },
|
||||
P2002: { statusCode: 409, message: 'Unique constraint violation' },
|
||||
P2003: { statusCode: 409, message: 'Foreign key constraint violation' },
|
||||
P2004: { statusCode: 400, message: 'Database constraint violation' },
|
||||
P2005: { statusCode: 400, message: 'Invalid value stored in database' },
|
||||
P2006: { statusCode: 400, message: 'Invalid value provided' },
|
||||
P2007: { statusCode: 400, message: 'Data validation error' },
|
||||
P2008: { statusCode: 400, message: 'Failed to parse the query' },
|
||||
P2009: { statusCode: 400, message: 'Failed to validate the query' },
|
||||
P2010: { statusCode: 500, message: 'Raw query failed' },
|
||||
P2011: { statusCode: 400, message: 'Null constraint violation' },
|
||||
P2012: { statusCode: 400, message: 'Missing required value' },
|
||||
P2013: { statusCode: 400, message: 'Missing required argument' },
|
||||
P2014: { statusCode: 409, message: 'Required relation violation' },
|
||||
P2015: { statusCode: 404, message: 'Related record not found' },
|
||||
P2016: { statusCode: 400, message: 'Query interpretation error' },
|
||||
P2017: { statusCode: 400, message: 'Records for relation not connected' },
|
||||
P2018: { statusCode: 404, message: 'Required connected records not found' },
|
||||
P2019: { statusCode: 400, message: 'Input error' },
|
||||
P2020: { statusCode: 400, message: 'Value out of range' },
|
||||
P2021: { statusCode: 500, message: 'Table does not exist' },
|
||||
P2022: { statusCode: 500, message: 'Column does not exist' },
|
||||
P2023: { statusCode: 500, message: 'Inconsistent column data' },
|
||||
P2024: { statusCode: 503, message: 'Connection pool timeout' },
|
||||
P2025: { statusCode: 404, message: 'Record not found' },
|
||||
P2026: { statusCode: 400, message: 'Unsupported database feature used in query' },
|
||||
P2027: { statusCode: 500, message: 'Multiple database errors occurred' },
|
||||
P2028: { statusCode: 500, message: 'Transaction API error' },
|
||||
P2029: { statusCode: 400, message: 'Query parameter limit exceeded' },
|
||||
P2030: { statusCode: 400, message: 'Fulltext index not found' },
|
||||
P2031: { statusCode: 500, message: 'MongoDB requires replica set' },
|
||||
P2033: { statusCode: 400, message: 'Number does not fit in 64 bit signed integer' },
|
||||
P2034: { statusCode: 409, message: 'Transaction failed due to write conflict or deadlock' },
|
||||
P2035: { statusCode: 500, message: 'Database assertion violation' },
|
||||
P2036: { statusCode: 500, message: 'External connector error' },
|
||||
P2037: { statusCode: 503, message: 'Too many database connections opened' },
|
||||
};
|
||||
|
||||
interface PrismaErrorMeta {
|
||||
target?: string[];
|
||||
field_name?: string;
|
||||
model_name?: string;
|
||||
argument_name?: string;
|
||||
constraint?: string;
|
||||
cause?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
class ApiError<T = unknown> extends Error {
|
||||
statusCode: number;
|
||||
data: T | null;
|
||||
@@ -6,13 +78,17 @@ class ApiError<T = unknown> extends Error {
|
||||
errors: Array<Error>;
|
||||
isOperational: boolean;
|
||||
stack?: string;
|
||||
code?: string;
|
||||
meta?: PrismaErrorMeta;
|
||||
|
||||
constructor(
|
||||
statusCode: number,
|
||||
message: string = 'Something went wrong',
|
||||
errors: Array<Error> = [],
|
||||
isOperational: boolean = true,
|
||||
stack?: string
|
||||
stack?: string,
|
||||
code?: string,
|
||||
meta?: PrismaErrorMeta
|
||||
) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
@@ -21,6 +97,8 @@ class ApiError<T = unknown> extends Error {
|
||||
this.success = false;
|
||||
this.errors = errors;
|
||||
this.isOperational = isOperational;
|
||||
this.code = code;
|
||||
this.meta = meta;
|
||||
|
||||
if (stack) {
|
||||
this.stack = stack;
|
||||
@@ -28,6 +106,159 @@ class ApiError<T = unknown> extends Error {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ApiError from a Prisma error
|
||||
* Handles all Prisma error types: PrismaClientKnownRequestError, PrismaClientUnknownRequestError,
|
||||
* PrismaClientRustPanicError, PrismaClientInitializationError, PrismaClientValidationError
|
||||
*/
|
||||
static fromPrismaError(error: unknown): ApiError {
|
||||
// Handle PrismaClientKnownRequestError
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
const errorInfo = PRISMA_ERROR_CODES[error.code] || {
|
||||
statusCode: 500,
|
||||
message: 'Database operation failed',
|
||||
};
|
||||
|
||||
let message = errorInfo.message;
|
||||
const meta = error.meta as PrismaErrorMeta | undefined;
|
||||
|
||||
// Provide more specific messages based on error code and meta
|
||||
switch (error.code) {
|
||||
case 'P2002': {
|
||||
const target = meta?.target;
|
||||
if (target && Array.isArray(target)) {
|
||||
message = `Unique constraint violation on field(s): ${target.join(', ')}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P2003': {
|
||||
const fieldName = meta?.field_name;
|
||||
if (fieldName) {
|
||||
message = `Foreign key constraint failed on field: ${fieldName}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P2025': {
|
||||
const cause = meta?.cause;
|
||||
if (cause) {
|
||||
message = `Record not found: ${cause}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P2011': {
|
||||
const constraint = meta?.constraint;
|
||||
if (constraint) {
|
||||
message = `Null constraint violation on: ${constraint}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P2014': {
|
||||
const modelName = meta?.model_name;
|
||||
if (modelName) {
|
||||
message = `Required relation violation on model: ${modelName}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ApiError(
|
||||
errorInfo.statusCode,
|
||||
message,
|
||||
[error],
|
||||
true,
|
||||
error.stack,
|
||||
error.code,
|
||||
meta
|
||||
);
|
||||
}
|
||||
|
||||
// Handle PrismaClientUnknownRequestError
|
||||
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
|
||||
return new ApiError(
|
||||
500,
|
||||
'An unknown database error occurred',
|
||||
[error],
|
||||
true,
|
||||
error.stack,
|
||||
'UNKNOWN_REQUEST_ERROR'
|
||||
);
|
||||
}
|
||||
|
||||
// Handle PrismaClientRustPanicError
|
||||
if (error instanceof Prisma.PrismaClientRustPanicError) {
|
||||
return new ApiError(
|
||||
500,
|
||||
'A critical database error occurred. Please try again later.',
|
||||
[error],
|
||||
false,
|
||||
error.stack,
|
||||
'RUST_PANIC_ERROR'
|
||||
);
|
||||
}
|
||||
|
||||
// Handle PrismaClientInitializationError
|
||||
if (error instanceof Prisma.PrismaClientInitializationError) {
|
||||
const errorInfo = error.errorCode
|
||||
? PRISMA_ERROR_CODES[error.errorCode] || { statusCode: 500, message: 'Database initialization failed' }
|
||||
: { statusCode: 500, message: 'Database initialization failed' };
|
||||
|
||||
return new ApiError(
|
||||
errorInfo.statusCode,
|
||||
errorInfo.message,
|
||||
[error],
|
||||
false,
|
||||
error.stack,
|
||||
error.errorCode || 'INITIALIZATION_ERROR'
|
||||
);
|
||||
}
|
||||
|
||||
// Handle PrismaClientValidationError
|
||||
if (error instanceof Prisma.PrismaClientValidationError) {
|
||||
return new ApiError(
|
||||
400,
|
||||
'Invalid data provided for database operation',
|
||||
[error],
|
||||
true,
|
||||
error.stack,
|
||||
'VALIDATION_ERROR'
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback for any other error
|
||||
if (error instanceof Error) {
|
||||
return new ApiError(500, error.message, [error], true, error.stack);
|
||||
}
|
||||
|
||||
return new ApiError(500, 'An unexpected error occurred');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is a Prisma error
|
||||
*/
|
||||
static isPrismaError(error: unknown): boolean {
|
||||
return (
|
||||
error instanceof Prisma.PrismaClientKnownRequestError ||
|
||||
error instanceof Prisma.PrismaClientUnknownRequestError ||
|
||||
error instanceof Prisma.PrismaClientRustPanicError ||
|
||||
error instanceof Prisma.PrismaClientInitializationError ||
|
||||
error instanceof Prisma.PrismaClientValidationError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-friendly message for a Prisma error code
|
||||
*/
|
||||
static getPrismaErrorMessage(code: string): string {
|
||||
return PRISMA_ERROR_CODES[code]?.message || 'Database operation failed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP status code for a Prisma error code
|
||||
*/
|
||||
static getPrismaErrorStatusCode(code: string): number {
|
||||
return PRISMA_ERROR_CODES[code]?.statusCode || 500;
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiError;
|
||||
|
||||
Reference in New Issue
Block a user