diff --git a/serverless.yml b/serverless.yml index 310767b..e32e0d1 100644 --- a/serverless.yml +++ b/serverless.yml @@ -336,6 +336,22 @@ functions: - httpApi: path: /minglaradmin/get-all-invitation-details method: get + + + getAllCoadminAndAMDetails: + handler: src/modules/minglaradmin/handlers/getAllCoadminAndAM.handler + package: + patterns: + - "src/modules/minglaradmin/**" + - "common/**" + - "src/common/**" + - "node_modules/@prisma/client/**" + - "node_modules/.prisma/**" + + events: + - httpApi: + path: /minglaradmin/get-all-coadmin-and-am-details + method: get addCompanyDetails: diff --git a/src/modules/host/handlers/addCompanyDetails.ts b/src/modules/host/handlers/addCompanyDetails.ts index 026bf6d..a5ab281 100644 --- a/src/modules/host/handlers/addCompanyDetails.ts +++ b/src/modules/host/handlers/addCompanyDetails.ts @@ -21,6 +21,27 @@ const s3 = new AWS.S3({ region: config.aws.region, }); +function normalizeJsonField(fields: any, key: string) { + if (!fields[key]) return undefined; + + const val = fields[key]; + + // If frontend sends object + if (typeof val === "object") return val; + + // If Postman or CURL sends stringified JSON + if (typeof val === "string") { + try { + return JSON.parse(val); + } catch (err) { + throw new ApiError(400, `Invalid JSON in field: ${key}`); + } + } + + throw new ApiError(400, `Invalid input: ${key} must be object or JSON string.`); +} + + export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise => { try { // 1) Auth @@ -85,14 +106,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise< if (!fields.documents) throw new ApiError(400, 'Missing documents field.'); // 5) Parse companyDetails - let companyDetailsRaw = fields.companyDetails; - if (typeof companyDetailsRaw === 'string') { - try { - companyDetailsRaw = JSON.parse(companyDetailsRaw); - } catch { - throw new ApiError(400, 'Invalid JSON in companyDetails.'); - } - } + const companyDetailsRaw = normalizeJsonField(fields, "companyDetails"); + if (!companyDetailsRaw) throw new ApiError(400, "companyDetails is required."); + // 6) Zod validation for companyDetails (includes optional parentCompany) const companyValidation = hostCompanyDetailsSchema.safeParse(companyDetailsRaw); @@ -103,14 +119,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise< const parsedCompany = companyValidation.data; // 7) Parse documents metadata - let documentsMetadataRaw = fields.documents; - if (typeof documentsMetadataRaw === 'string') { - try { - documentsMetadataRaw = JSON.parse(documentsMetadataRaw); - } catch { - throw new ApiError(400, 'Invalid JSON in documents.'); - } - } + const documentsMetadataRaw = normalizeJsonField(fields, "documents"); + if (!Array.isArray(documentsMetadataRaw)) + throw new ApiError(400, "documents must be an array."); if (!Array.isArray(documentsMetadataRaw) || documentsMetadataRaw.length === 0) { throw new ApiError(400, 'Documents must be a non-empty array.'); } @@ -157,7 +168,18 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise< const message = parentValidation.error.issues.map((i) => i.message).join(', '); throw new ApiError(400, `Parent company validation failed: ${message}`); } - parsedParentCompany = parentValidation.data; + let parentCompanyRaw = parsedCompany.parentCompany; + + if (typeof parentCompanyRaw === "string") { + try { + parentCompanyRaw = JSON.parse(parentCompanyRaw); + } catch { + throw new ApiError(400, "Invalid JSON in parentCompany."); + } + } + + parsedParentCompany = parentCompanyRaw; + // Ensure required parent docs exist const parentUploadedTypes = parentDocs.map((d) => d.documentTypeXid); diff --git a/src/modules/host/handlers/registration.ts b/src/modules/host/handlers/registration.ts index 5662ede..4f42cdf 100644 --- a/src/modules/host/handlers/registration.ts +++ b/src/modules/host/handlers/registration.ts @@ -59,7 +59,7 @@ export const handler = safeHandler(async ( throw new ApiError(500, 'Failed to send OTP'); } - await sendOtpEmailForHost(newUser?.emailAddress, otpResult.otp); + // await sendOtpEmailForHost(newUser?.emailAddress, otpResult.otp); return { statusCode: 200, diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 8830afe..b68779b 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -210,7 +210,7 @@ export class HostService { data: { userXid: user_xid, companyName: companyData.companyName, - hostRefNumber: companyData.hostRefNumber, + hostRefNumber: await this.generateHostRefNumber(tx), address1: companyData.address1, address2: companyData.address2, cityXid: companyData.cityXid, @@ -294,4 +294,18 @@ export class HostService { }); } + async generateHostRefNumber(tx: any) { + const lastHost = await tx.hostHeader.findFirst({ + orderBy: { + id: 'desc', + }, + select: { + id: true, + } + }); + + const nextId = lastHost ? lastHost.id + 1 : 1; + return `HOSTREFNO-${String(nextId).padStart(6, '0')}`; + } + } diff --git a/src/modules/minglaradmin/handlers/getAllCoadminAndAM.ts b/src/modules/minglaradmin/handlers/getAllCoadminAndAM.ts new file mode 100644 index 0000000..a38cf50 --- /dev/null +++ b/src/modules/minglaradmin/handlers/getAllCoadminAndAM.ts @@ -0,0 +1,39 @@ +import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import ApiError from '../../../common/utils/helper/ApiError'; +import { MinglarService } from '../services/minglar.service'; + +const prismaService = new PrismaService(); +const minglarService = new MinglarService(prismaService); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context +): Promise => { + // Extract token from headers + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] + if(!token) { + throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); + } + + // Authenticate user using the shared authForHost function + await verifyOnlyMinglarAdminToken(token); + + const response = await minglarService.getAllCoadminAndAM(); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Data retrieved successfully', + data: response, + }), + }; +}); + diff --git a/src/modules/minglaradmin/services/minglar.service.ts b/src/modules/minglaradmin/services/minglar.service.ts index 42d92ae..fb38bcc 100644 --- a/src/modules/minglaradmin/services/minglar.service.ts +++ b/src/modules/minglaradmin/services/minglar.service.ts @@ -433,6 +433,53 @@ export class MinglarService { accountManager: host.accountManager || null })); } + async getAllCoadminAndAM() { + return await this.prisma.user.findMany({ + where: { + roleXid: { + in: [ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER] + }, + isActive: true, + + // 🔥 Filter users who have at least ONE accepted invitation + inviteDetails: { + some: { + isMinglarInvitation: true, + is_accepted: true, + invitation_status: MINGLAR_INVITATION_STATUS.ACCEPTED, + isActive: true + } + } + }, + + include: { + role: { + select: { + id: true, + roleName: true, + }, + }, + + // (Optional) to return only the accepted invitations + // inviteDetails: { + // where: { + // isMinglarInvitation: true, + // is_accepted: true, + // invitation_status: MINGLAR_INVITATION_STATUS.ACCEPTED, + // isActive: true + // }, + // select: { + // id: true, + // invitedBy: true, + // invited_on: true, + // invitation_status: true, + // is_accepted: true + // } + // } + } + }); + } + }