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:
paritosh18
2025-11-28 15:33:43 +05:30
parent 15c1458f02
commit 82cbaddce1
7 changed files with 2115 additions and 489 deletions

2304
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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 }),
},
}),

View File

@@ -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;