Merge branch 'mayank' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh
This commit is contained in:
@@ -3,18 +3,18 @@ import config from '@/config/config';
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||
import AWS from 'aws-sdk';
|
||||
import Busboy from 'busboy';
|
||||
import crypto from 'crypto';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import {
|
||||
hostCompanyDetailsSchema,
|
||||
hostDocumentsSchema,
|
||||
parentCompanySchema,
|
||||
REQUIRED_DOC_TYPES,
|
||||
hostDocumentsSchema
|
||||
REQUIRED_DOC_TYPES
|
||||
} from '../../../common/utils/validation/host/hostCompanyDetails.validation';
|
||||
import { HostService } from '../../host/services/host.service';
|
||||
import { sendEmailToAM, sendEmailToMinglarAdmin } from '../services/sendHostResubmitEmailToAM.service';
|
||||
|
||||
const prisma = new PrismaService();
|
||||
const hostService = new HostService(prisma);
|
||||
@@ -50,16 +50,28 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
|
||||
// 2) multipart check
|
||||
const contentType = event.headers['content-type'] || event.headers['Content-Type'];
|
||||
if (!contentType?.startsWith('multipart/form-data')) throw new ApiError(400, 'Content-Type must be multipart/form-data.');
|
||||
if (!event.isBase64Encoded) throw new ApiError(400, 'Event body must be base64 encoded for multipart uploads.');
|
||||
if (!contentType?.includes('multipart/form-data')) {
|
||||
throw new ApiError(400, 'Content-Type must be multipart/form-data.');
|
||||
}
|
||||
|
||||
// Handle both base64 and non-base64 encoded bodies
|
||||
let bodyBuffer: Buffer;
|
||||
if (event.isBase64Encoded) {
|
||||
bodyBuffer = Buffer.from(event.body as string, 'base64');
|
||||
} else {
|
||||
bodyBuffer = Buffer.from(event.body as string, 'binary');
|
||||
}
|
||||
|
||||
const bodyBuffer = Buffer.from(event.body as string, 'base64');
|
||||
const fields: Record<string, any> = {};
|
||||
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
|
||||
|
||||
// 3) parse with Busboy
|
||||
// 3) parse with Busboy - FIXED VERSION
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const bb = Busboy({ headers: { 'content-type': contentType } });
|
||||
const bb = Busboy({
|
||||
headers: {
|
||||
'content-type': contentType
|
||||
}
|
||||
});
|
||||
|
||||
bb.on('file', (fieldname, file, info) => {
|
||||
const { filename, mimeType } = info;
|
||||
@@ -70,36 +82,64 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
file.on('data', (chunk) => {
|
||||
totalSize += chunk.length;
|
||||
if (totalSize > MAX_SIZE) {
|
||||
file.resume();
|
||||
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit.`));
|
||||
file.destroy(new Error(`File ${filename} exceeds 5MB limit.`));
|
||||
return;
|
||||
}
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
file.on('end', () => {
|
||||
files.push({
|
||||
buffer: Buffer.concat(chunks),
|
||||
mimeType,
|
||||
fileName: filename,
|
||||
fieldName: fieldname,
|
||||
});
|
||||
if (chunks.length > 0) {
|
||||
files.push({
|
||||
buffer: Buffer.concat(chunks),
|
||||
mimeType: mimeType || 'application/octet-stream',
|
||||
fileName: filename || 'unknown',
|
||||
fieldName: fieldname,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
file.on('error', (error) => {
|
||||
reject(new ApiError(400, `File upload error: ${error.message}`));
|
||||
});
|
||||
});
|
||||
|
||||
bb.on('field', (fieldname, val) => {
|
||||
// Keep raw string for JSON parse later; try parse for convenience
|
||||
try {
|
||||
fields[fieldname] = JSON.parse(val);
|
||||
} catch {
|
||||
fields[fieldname] = val;
|
||||
}
|
||||
// Store as string initially, parse later in normalizeJsonField
|
||||
fields[fieldname] = val;
|
||||
});
|
||||
|
||||
bb.on('close', resolve);
|
||||
bb.on('error', reject);
|
||||
bb.end(bodyBuffer);
|
||||
bb.on('close', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
bb.on('error', (error) => {
|
||||
reject(new ApiError(400, `Multipart parsing error: ${error.message}`));
|
||||
});
|
||||
|
||||
bb.write(bodyBuffer);
|
||||
bb.end();
|
||||
});
|
||||
|
||||
if (fields.userProfile) {
|
||||
const userProfileRaw = normalizeJsonField(fields, "userProfile");
|
||||
if (userProfileRaw) {
|
||||
const { firstName, lastName, mobileNumber } = userProfileRaw;
|
||||
|
||||
// Update user profile if provided
|
||||
if (firstName || lastName || mobileNumber) {
|
||||
await prisma.user.update({
|
||||
where: { id: userInfo.id },
|
||||
data: {
|
||||
...(firstName && { firstName }),
|
||||
...(lastName && { lastName }),
|
||||
...(mobileNumber && { mobileNumber }),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Validate required root fields
|
||||
if (!fields.companyDetails) throw new ApiError(400, 'Missing companyDetails field.');
|
||||
if (!fields.documents) throw new ApiError(400, 'Missing documents field.');
|
||||
@@ -108,6 +148,83 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
const companyDetailsRaw = normalizeJsonField(fields, "companyDetails");
|
||||
if (!companyDetailsRaw) throw new ApiError(400, "companyDetails is required.");
|
||||
|
||||
// Get existing host to determine host ID for folder structure
|
||||
const existingHost = await prisma.hostHeader.findFirst({
|
||||
where: { userXid: userInfo.id },
|
||||
});
|
||||
|
||||
let hostId: number;
|
||||
if (existingHost) {
|
||||
hostId = existingHost.id;
|
||||
} else {
|
||||
// For new hosts, we'll use user ID temporarily and update after host creation
|
||||
hostId = userInfo.id;
|
||||
}
|
||||
|
||||
// Define uploadToS3 function with proper folder structure using fieldName for filenames
|
||||
// Define uploadToS3 function with proper folder structure using fieldName for filenames
|
||||
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, folderType: 'logo' | 'documents' | 'parent_company', documentTypeXid?: number, fieldName?: string) {
|
||||
let s3Key: string;
|
||||
|
||||
// Sanitize file name: remove special characters and spaces
|
||||
const sanitizeFileName = (name: string) => {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9.]/g, '_') // Replace special characters with underscore
|
||||
.replace(/_+/g, '_') // Replace multiple underscores with single
|
||||
.replace(/^_+|_+$/g, ''); // Remove leading/trailing underscores
|
||||
};
|
||||
|
||||
// Get file extension from original file name
|
||||
const fileExtension = originalName.split('.').pop() || 'pdf';
|
||||
|
||||
// Determine folder structure based on type
|
||||
if (folderType === 'logo') {
|
||||
// Logo: Documents/Host/logo/{HostID}/{sanitized_filename}
|
||||
const sanitizedFileName = sanitizeFileName(originalName);
|
||||
s3Key = `Documents/Host/logo/${hostId}/${sanitizedFileName}`;
|
||||
} else if (folderType === 'documents' && documentTypeXid && fieldName) {
|
||||
// Host Documents: Documents/Host/documents/{HostID}/{documentTypeXid}_{fieldName}.{extension}
|
||||
const fileName = `${documentTypeXid}_${fieldName}.${fileExtension}`;
|
||||
const sanitizedFileName = sanitizeFileName(fileName);
|
||||
s3Key = `Documents/Host/documents/${hostId}/${sanitizedFileName}`;
|
||||
} else if (folderType === 'parent_company' && documentTypeXid && fieldName) {
|
||||
// Parent Documents: Documents/Host/parent_company/{HostID}/{documentTypeXid}_{fieldName}.{extension}
|
||||
const fileName = `${documentTypeXid}_${fieldName}.${fileExtension}`;
|
||||
const sanitizedFileName = sanitizeFileName(fileName);
|
||||
s3Key = `Documents/Host/parent_company/${hostId}/${sanitizedFileName}`;
|
||||
} else {
|
||||
throw new ApiError(400, 'Invalid folder type or missing documentTypeXid/fieldName');
|
||||
}
|
||||
|
||||
// Upload new file (S3 will automatically replace if same key exists)
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: buffer,
|
||||
ContentType: mimeType,
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
console.log(`File uploaded successfully: ${s3Key}`);
|
||||
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
}
|
||||
|
||||
// 5.5) Handle company logo upload
|
||||
const logoFile = files.find((f) => f.fieldName === 'companyLogo');
|
||||
if (logoFile) {
|
||||
const logoPath = await uploadToS3(
|
||||
logoFile.buffer,
|
||||
logoFile.mimeType,
|
||||
logoFile.fileName,
|
||||
'logo'
|
||||
);
|
||||
companyDetailsRaw.logoPath = logoPath;
|
||||
console.log('Company logo uploaded:', logoPath);
|
||||
}
|
||||
|
||||
// 6) Zod validation for companyDetails (includes optional parentCompany)
|
||||
const companyValidation = hostCompanyDetailsSchema.safeParse(companyDetailsRaw);
|
||||
if (!companyValidation.success) {
|
||||
@@ -129,21 +246,21 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
}
|
||||
const documentsMetadata = documentsMetadataRaw.map((d: any) => ({
|
||||
...d,
|
||||
owner: d.owner === 'parent' ? 'parent' : 'host', // default host
|
||||
owner: d.owner || 'host', // default to host
|
||||
}));
|
||||
|
||||
// 9) Map uploaded files to metadata (one entry per file - Q2 = A)
|
||||
// 8) Map uploaded files to metadata
|
||||
const documentMetadata = documentsMetadata.map((doc: any) => {
|
||||
const file = files.find((f) => f.fieldName === doc.fieldName);
|
||||
if (!file) throw new ApiError(400, `File not found for field: ${doc.fieldName}`);
|
||||
return { ...doc, file };
|
||||
});
|
||||
|
||||
// 10) Split host vs parent docs
|
||||
// 9) Split host vs parent docs
|
||||
const hostDocs = documentMetadata.filter((d) => d.owner === 'host');
|
||||
const parentDocs = documentMetadata.filter((d) => d.owner === 'parent');
|
||||
|
||||
// 11) Ensure required docs for host exist (IDs 1,2,3,4)
|
||||
// 10) Ensure required docs for host exist (IDs 1,2,3,4)
|
||||
const hostUploadedTypes = hostDocs.map((d) => d.documentTypeXid);
|
||||
const requiredHostTypes = Object.values(REQUIRED_DOC_TYPES);
|
||||
const missingHostDocs = requiredHostTypes.filter((typeId) => !hostUploadedTypes.includes(typeId));
|
||||
@@ -151,7 +268,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
throw new ApiError(400, `Missing mandatory documents for host: ${missingHostDocs.join(', ')}`);
|
||||
}
|
||||
|
||||
// 12) If isSubsidairy === true and parentCompany provided -> validate parent company & docs
|
||||
// 11) If isSubsidairy === true and parentCompany provided -> validate parent company & docs
|
||||
let parsedParentCompany: any = null;
|
||||
if (parsedCompany.isSubsidairy) {
|
||||
if (!parsedCompany.parentCompany) {
|
||||
@@ -163,16 +280,7 @@ 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}`);
|
||||
}
|
||||
|
||||
let parentCompanyRaw = parsedCompany.parentCompany;
|
||||
if (typeof parentCompanyRaw === "string") {
|
||||
try {
|
||||
parentCompanyRaw = JSON.parse(parentCompanyRaw);
|
||||
} catch {
|
||||
throw new ApiError(400, "Invalid JSON in parentCompany.");
|
||||
}
|
||||
}
|
||||
parsedParentCompany = parentCompanyRaw;
|
||||
parsedParentCompany = parsedCompany.parentCompany;
|
||||
|
||||
const parentUploadedTypes = parentDocs.map((d) => d.documentTypeXid);
|
||||
const requiredParentTypes = Object.values(REQUIRED_DOC_TYPES);
|
||||
@@ -182,49 +290,47 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
// 13) Upload files to S3 (host docs under Documents/Host/, parent docs under Documents/Host/parent_company/)
|
||||
// 12) Upload files to S3 with proper folder structure using fieldName for filenames
|
||||
const uploadedHostDocs: Array<{ documentTypeXid: number; documentName: string; filePath: string }> = [];
|
||||
const uploadedParentDocs: Array<{ documentTypeXid: number; documentName: string; filePath: string }> = [];
|
||||
|
||||
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string) {
|
||||
const uniqueKey = `${userInfo.id}_${crypto.randomUUID()}_${originalName}`;
|
||||
const s3Key = `${prefix}/${uniqueKey}`;
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: buffer,
|
||||
ContentType: mimeType,
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
}
|
||||
|
||||
// upload host files
|
||||
// Upload host documents with proper folder structure using fieldName
|
||||
for (const doc of hostDocs) {
|
||||
const filePath = await uploadToS3(doc.file.buffer, doc.file.mimeType, doc.file.fileName, 'Documents/Host');
|
||||
const filePath = await uploadToS3(
|
||||
doc.file.buffer,
|
||||
doc.file.mimeType,
|
||||
doc.file.fileName, // Use original file name for extension
|
||||
'documents',
|
||||
doc.documentTypeXid,
|
||||
doc.fieldName // Use fieldName for the filename
|
||||
);
|
||||
uploadedHostDocs.push({
|
||||
documentTypeXid: doc.documentTypeXid,
|
||||
documentName: doc.documentName,
|
||||
documentName: doc.fieldName, // Keep documentName for database
|
||||
filePath,
|
||||
});
|
||||
}
|
||||
|
||||
// upload parent files (if any)
|
||||
// Upload parent company documents with proper folder structure using fieldName
|
||||
if (parentDocs.length > 0) {
|
||||
for (const doc of parentDocs) {
|
||||
const filePath = await uploadToS3(doc.file.buffer, doc.file.mimeType, doc.file.fileName, 'Documents/Host/parent_company');
|
||||
const filePath = await uploadToS3(
|
||||
doc.file.buffer,
|
||||
doc.file.mimeType,
|
||||
doc.file.fileName, // Use original file name for extension
|
||||
'parent_company',
|
||||
doc.documentTypeXid,
|
||||
doc.fieldName // Use fieldName for the filename
|
||||
);
|
||||
uploadedParentDocs.push({
|
||||
documentTypeXid: doc.documentTypeXid,
|
||||
documentName: doc.documentName,
|
||||
documentName: doc.documentName, // Keep documentName for database
|
||||
filePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 14) Persist using hostService
|
||||
// 13) Persist using hostService
|
||||
const createdOrUpdated = await hostService.addOrUpdateCompanyDetails(
|
||||
userInfo.id,
|
||||
parsedCompany,
|
||||
@@ -235,7 +341,31 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
|
||||
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
||||
|
||||
// 15) Success
|
||||
// Update hostId if it was a new creation
|
||||
if (!existingHost) {
|
||||
hostId = createdOrUpdated.id;
|
||||
console.log(`Host created with ID: ${hostId}`);
|
||||
}
|
||||
|
||||
const getSuggestionDetails = await hostService.getSuggestionDetails(userInfo.id)
|
||||
|
||||
if (getSuggestionDetails.hostDetails.accountManagerXid !== null) {
|
||||
await sendEmailToAM(
|
||||
getSuggestionDetails.hostDetails.accountManager.emailAddress,
|
||||
getSuggestionDetails.hostDetails.accountManager.firstName,
|
||||
getSuggestionDetails.hostDetails.companyName,
|
||||
getSuggestionDetails.hostDetails.hostRefNumber
|
||||
);
|
||||
} else {
|
||||
await sendEmailToMinglarAdmin(
|
||||
config.MinglarAdminEmail,
|
||||
config.MinglarAdminName,
|
||||
getSuggestionDetails.hostDetails.companyName,
|
||||
getSuggestionDetails.hostDetails.hostRefNumber
|
||||
)
|
||||
}
|
||||
|
||||
// 14) Success
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
@@ -252,4 +382,4 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
console.error('❌ Error in addCompanyDetails:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -228,7 +228,9 @@ export class HostService {
|
||||
await this.prisma.hostHeader.update({
|
||||
where: { id: hostDetails.id },
|
||||
data: {
|
||||
stepper: STEPPER.AGREEMENT_ACCEPTED
|
||||
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
||||
agreementAccepted: true,
|
||||
isApproved: true
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -480,6 +482,50 @@ export class HostService {
|
||||
});
|
||||
}
|
||||
|
||||
async getSuggestionDetails(user_xid: number) {
|
||||
const hostDetails = await this.prisma.hostHeader.findFirst({
|
||||
where: { userXid: user_xid, isActive: true },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
firstName: true,
|
||||
}
|
||||
},
|
||||
accountManager: {
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
firstName: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!hostDetails) {
|
||||
return { hostSuggestionDetails: [], hostDetails: null };
|
||||
}
|
||||
|
||||
const hostSuggestionDetails = await this.prisma.hostSuggestion.findMany({
|
||||
where: { hostXid: hostDetails.id, isActive: true, isreviewed: false }
|
||||
});
|
||||
|
||||
if (hostSuggestionDetails) {
|
||||
await this.prisma.hostSuggestion.updateMany({
|
||||
where: { hostXid: hostDetails.id, isActive: true, isreviewed: false },
|
||||
data: {
|
||||
isreviewed: true,
|
||||
reviewedByXid: hostDetails.id,
|
||||
reviewOn: new Date(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { hostSuggestionDetails, hostDetails };
|
||||
}
|
||||
|
||||
|
||||
|
||||
async generateHostRefNumber(tx: any) {
|
||||
const lastHost = await tx.hostHeader.findFirst({
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { brevoService } from "@/common/email/brevoApi";
|
||||
import ApiError from "@/common/utils/helper/ApiError";
|
||||
|
||||
export async function sendEmailToAM(
|
||||
emailAddress: string,
|
||||
amName: string,
|
||||
hostCompanyName: string,
|
||||
hostRefNumber: string
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = `Host Application Re-Submited : ${hostCompanyName}`;
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear ${amName},</p>
|
||||
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has re-submited the application with implimented suggestions.</p>
|
||||
<p>Please review their appliaction and take the necessary action.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
// console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendEmailToMinglarAdmin(
|
||||
emailAddress: string,
|
||||
minglarAdminName: string,
|
||||
hostCompanyName: string,
|
||||
hostRefNumber: string
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = `New Host Application Recieved : ${hostCompanyName}`;
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear ${minglarAdminName},</p>
|
||||
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has submited their application.</p>
|
||||
<p>Please review their appliaction and take the necessary action.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
// console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||
}
|
||||
}
|
||||
65
src/modules/minglaradmin/handlers/acceptHostAppMinglar.ts
Normal file
65
src/modules/minglaradmin/handlers/acceptHostAppMinglar.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
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 { sendEmailToHostForMinglarApproval } from '../services/approvalMailtoHost.service';
|
||||
import { MinglarService } from '../services/minglar.service';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
|
||||
interface AddSuggestionBody {
|
||||
hostXid: number;
|
||||
title: string;
|
||||
comments: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add suggestion handler for host applications
|
||||
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||
*/
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Verify authentication token
|
||||
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||
if (!token) {
|
||||
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||
}
|
||||
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||
|
||||
// Parse request body
|
||||
let body: AddSuggestionBody;
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { hostXid } = body;
|
||||
|
||||
|
||||
// Add suggestion using service
|
||||
await minglarService.acceptHostApplicationMinglarAdmin(hostXid, userInfo.id);
|
||||
const hostDetails = await minglarService.getUserDetails(userInfo.id)
|
||||
await sendEmailToHostForMinglarApproval(hostDetails.emailAddress)
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Application accepted successfully',
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authFor
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { MinglarService } from '../services/minglar.service';
|
||||
import { sendEmailToHostForApprovedApplication } from '../services/approvalMailtoHost.service'
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
@@ -46,6 +47,8 @@ export const handler = safeHandler(async (
|
||||
|
||||
// Add suggestion using service
|
||||
await minglarService.acceptHostApplication(hostXid, userInfo.id);
|
||||
const hostDetails = await minglarService.getUserDetails(userInfo.id)
|
||||
await sendEmailToHostForApprovedApplication(hostDetails.emailAddress)
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
import { sendEmailToHostForRejectedApplication } from '../services/rejectionMailtoHost.service';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
@@ -46,6 +47,8 @@ export const handler = safeHandler(async (
|
||||
|
||||
// Add suggestion using service
|
||||
await minglarService.rejectHostApplication(hostXid, userInfo.id);
|
||||
const hostDetails = await minglarService.getUserDetails(userInfo.id)
|
||||
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress)
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
65
src/modules/minglaradmin/handlers/rejectHostApplicationAM.ts
Normal file
65
src/modules/minglaradmin/handlers/rejectHostApplicationAM.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
|
||||
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';
|
||||
import { sendAMRejectionMailtoHost } from '../services/rejectionMailtoHost.service';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
|
||||
interface AddSuggestionBody {
|
||||
hostXid: number;
|
||||
title: string;
|
||||
comments: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add suggestion handler for host applications
|
||||
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||
*/
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Verify authentication token
|
||||
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||
if (!token) {
|
||||
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||
}
|
||||
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyMinglarAdminToken(token);
|
||||
|
||||
// Parse request body
|
||||
let body: AddSuggestionBody;
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { hostXid } = body;
|
||||
|
||||
|
||||
// Add suggestion using service
|
||||
await minglarService.rejectHostApplicationAM(hostXid, userInfo.id);
|
||||
const hostDetails = await minglarService.getUserDetails(userInfo.id)
|
||||
await sendAMRejectionMailtoHost(hostDetails.emailAddress)
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Application rejected successfully',
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -1,13 +1,13 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { MinglarService } from '../services/minglar.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||
import { parseMultipartFormData, parseJsonField } from '../../../common/utils/helper/parseMultipartFormData';
|
||||
import AWS from 'aws-sdk';
|
||||
import crypto from 'crypto';
|
||||
// modules/minglar/handlers/updateProfile.ts
|
||||
import config from '@/config/config';
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import AWS from 'aws-sdk';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { parseJsonField, parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData';
|
||||
import { MinglarService } from '../services/minglar.service';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
@@ -16,136 +16,184 @@ const s3 = new AWS.S3({
|
||||
region: config.aws.region,
|
||||
});
|
||||
|
||||
// Define uploadToS3 function with proper folder structure and file replacement
|
||||
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, folderType: 'profile' | 'documents', userId: number, documentType?: string) {
|
||||
let s3Key: string;
|
||||
|
||||
// Sanitize file name: remove special characters and spaces
|
||||
const sanitizeFileName = (name: string) => {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9.]/g, '_') // Replace special characters with underscore
|
||||
.replace(/_+/g, '_') // Replace multiple underscores with single
|
||||
.replace(/^_+|_+$/g, ''); // Remove leading/trailing underscores
|
||||
};
|
||||
|
||||
// Get file extension from original file name
|
||||
const fileExtension = originalName.split('.').pop() || 'jpg';
|
||||
|
||||
// Determine folder structure based on type
|
||||
if (folderType === 'profile') {
|
||||
// Profile Images: MinglarAdmin/ProfileImages/{UserID}/profile_image.{extension}
|
||||
const fileName = `profile_image.${fileExtension}`;
|
||||
const sanitizedFileName = sanitizeFileName(fileName);
|
||||
s3Key = `MinglarAdmin/ProfileImages/${userId}/${sanitizedFileName}`;
|
||||
} else if (folderType === 'documents' && documentType) {
|
||||
// Documents: MinglarAdmin/Documents/{UserID}/{documentType}.{extension}
|
||||
const fileName = `${documentType}.${fileExtension}`;
|
||||
const sanitizedFileName = sanitizeFileName(fileName);
|
||||
s3Key = `MinglarAdmin/Documents/${userId}/${sanitizedFileName}`;
|
||||
} else {
|
||||
throw new ApiError(400, 'Invalid folder type or missing documentType');
|
||||
}
|
||||
|
||||
// Upload new file (S3 will automatically replace if same key exists)
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: buffer,
|
||||
ContentType: mimeType,
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
console.log(`File uploaded successfully: ${s3Key}`);
|
||||
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
}
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// 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.');
|
||||
}
|
||||
try {
|
||||
// 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.');
|
||||
}
|
||||
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyMinglarAdminToken(token);
|
||||
const userId = Number(userInfo.id);
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyMinglarAdminToken(token);
|
||||
const userId = Number(userInfo.id);
|
||||
|
||||
if (!userId || isNaN(userId)) {
|
||||
throw new ApiError(400, 'Invalid user ID');
|
||||
}
|
||||
if (!userId || isNaN(userId)) {
|
||||
throw new ApiError(400, 'Invalid user ID');
|
||||
}
|
||||
|
||||
// Parse multipart form data
|
||||
const contentType = event.headers['Content-Type'] || event.headers['content-type'];
|
||||
const isBase64Encoded = event.isBase64Encoded || false;
|
||||
|
||||
const { fields, files } = parseMultipartFormData(
|
||||
event.body,
|
||||
contentType,
|
||||
isBase64Encoded
|
||||
);
|
||||
|
||||
// Parse JSON fields
|
||||
const userData = parseJsonField(fields, 'userData') || {};
|
||||
const addressData = parseJsonField(fields, 'addressData') || {};
|
||||
|
||||
// Extract user fields
|
||||
const { firstName, lastName, mobileNumber, dateOfBirth, profileImage } = userData;
|
||||
|
||||
// Extract address fields
|
||||
const { address1, address2, stateXid, countryXid, cityXid, pinCode } = addressData;
|
||||
|
||||
// Handle file uploads (profileImage, aadharCard, panCard)
|
||||
const uploadedFiles: Array<{ fileName: string; filePath: string; documentType?: string }> = [];
|
||||
let profileImagePath: string | undefined = profileImage;
|
||||
|
||||
// Upload profile image if provided as file
|
||||
const profileImageFile = files.find(f => f.fieldName === 'profileImage');
|
||||
if (profileImageFile) {
|
||||
const uniqueKey = `${userId}_${crypto.randomUUID()}_${profileImageFile.fileName}`;
|
||||
const s3Key = `MinglarAdmin/ProfileImages/${uniqueKey}`;
|
||||
// Parse multipart form data
|
||||
const contentType = event.headers['Content-Type'] || event.headers['content-type'];
|
||||
const isBase64Encoded = event.isBase64Encoded || false;
|
||||
|
||||
await s3.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: profileImageFile.data,
|
||||
ContentType: profileImageFile.contentType,
|
||||
ACL: 'private',
|
||||
}).promise();
|
||||
const { fields, files } = parseMultipartFormData(
|
||||
event.body,
|
||||
contentType,
|
||||
isBase64Encoded
|
||||
);
|
||||
|
||||
profileImagePath = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
// Parse JSON fields
|
||||
const userData = parseJsonField(fields, 'userData') || {};
|
||||
const addressData = parseJsonField(fields, 'addressData') || {};
|
||||
|
||||
// Extract user fields
|
||||
const { firstName, lastName, mobileNumber, dateOfBirth, profileImage } = userData;
|
||||
|
||||
// Extract address fields
|
||||
const { address1, address2, stateXid, countryXid, cityXid, pinCode } = addressData;
|
||||
|
||||
// Handle file uploads with proper folder structure and replacement
|
||||
const uploadedFiles: Array<{ fileName: string; filePath: string; documentType?: string }> = [];
|
||||
let profileImagePath: string | undefined = profileImage;
|
||||
|
||||
// Upload profile image if provided as file
|
||||
const profileImageFile = files.find(f => f.fieldName === 'profileImage');
|
||||
if (profileImageFile) {
|
||||
profileImagePath = await uploadToS3(
|
||||
profileImageFile.data,
|
||||
profileImageFile.contentType,
|
||||
profileImageFile.fileName,
|
||||
'profile',
|
||||
userId
|
||||
);
|
||||
console.log('Profile image uploaded:', profileImagePath);
|
||||
}
|
||||
|
||||
// Upload documents (aadharCard, panCard) with proper naming and replacement
|
||||
const aadharFile = files.find(f => f.fieldName === 'aadharCard');
|
||||
const panFile = files.find(f => f.fieldName === 'panCard');
|
||||
|
||||
if (aadharFile) {
|
||||
const filePath = await uploadToS3(
|
||||
aadharFile.data,
|
||||
aadharFile.contentType,
|
||||
aadharFile.fileName,
|
||||
'documents',
|
||||
userId,
|
||||
'aadhar'
|
||||
);
|
||||
uploadedFiles.push({
|
||||
fileName: aadharFile.fileName,
|
||||
filePath,
|
||||
documentType: 'aadhar'
|
||||
});
|
||||
console.log('Aadhar document uploaded:', filePath);
|
||||
}
|
||||
|
||||
if (panFile) {
|
||||
const filePath = await uploadToS3(
|
||||
panFile.data,
|
||||
panFile.contentType,
|
||||
panFile.fileName,
|
||||
'documents',
|
||||
userId,
|
||||
'pan'
|
||||
);
|
||||
uploadedFiles.push({
|
||||
fileName: panFile.fileName,
|
||||
filePath,
|
||||
documentType: 'pan'
|
||||
});
|
||||
console.log('PAN document uploaded:', filePath);
|
||||
}
|
||||
|
||||
// Update profile using service
|
||||
const result = await minglarService.updateProfile(
|
||||
userId,
|
||||
{
|
||||
firstName,
|
||||
lastName,
|
||||
mobileNumber,
|
||||
dateOfBirth,
|
||||
profileImage: profileImagePath,
|
||||
},
|
||||
{
|
||||
address1,
|
||||
address2,
|
||||
stateXid,
|
||||
countryXid,
|
||||
cityXid,
|
||||
pinCode,
|
||||
},
|
||||
uploadedFiles.filter(f => f.documentType).map(f => ({
|
||||
fileName: f.fileName,
|
||||
filePath: f.filePath,
|
||||
}))
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Profile updated successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('❌ Error in updateProfile:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Upload documents (aadharCard, panCard)
|
||||
const aadharFile = files.find(f => f.fieldName === 'aadharCard');
|
||||
const panFile = files.find(f => f.fieldName === 'panCard');
|
||||
|
||||
if (aadharFile) {
|
||||
const uniqueKey = `${userId}_${crypto.randomUUID()}_${aadharFile.fileName}`;
|
||||
const s3Key = `MinglarAdmin/Documents/${uniqueKey}`;
|
||||
|
||||
await s3.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: aadharFile.data,
|
||||
ContentType: aadharFile.contentType,
|
||||
ACL: 'private',
|
||||
}).promise();
|
||||
|
||||
const filePath = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
uploadedFiles.push({ fileName: aadharFile.fileName, filePath, documentType: 'aadhar' });
|
||||
}
|
||||
|
||||
if (panFile) {
|
||||
const uniqueKey = `${userId}_${crypto.randomUUID()}_${panFile.fileName}`;
|
||||
const s3Key = `MinglarAdmin/${userId}/documents/pan_${uniqueKey}`;
|
||||
|
||||
await s3.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: panFile.data,
|
||||
ContentType: panFile.contentType,
|
||||
ACL: 'private',
|
||||
}).promise();
|
||||
|
||||
const filePath = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
uploadedFiles.push({ fileName: panFile.fileName, filePath, documentType: 'pan' });
|
||||
}
|
||||
|
||||
// Update profile using service
|
||||
const result = await minglarService.updateProfile(
|
||||
userId,
|
||||
{
|
||||
firstName,
|
||||
lastName,
|
||||
mobileNumber,
|
||||
dateOfBirth,
|
||||
profileImage: profileImagePath,
|
||||
},
|
||||
{
|
||||
address1,
|
||||
address2,
|
||||
stateXid,
|
||||
countryXid,
|
||||
cityXid,
|
||||
pinCode,
|
||||
},
|
||||
uploadedFiles.filter(f => f.documentType).map(f => ({
|
||||
fileName: f.fileName,
|
||||
filePath: f.filePath,
|
||||
}))
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Profile updated successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { brevoService } from "@/common/email/brevoApi";
|
||||
import ApiError from "@/common/utils/helper/ApiError";
|
||||
|
||||
export async function sendEmailToHostForApprovedApplication(
|
||||
emailAddress: string,
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Approval for your application";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear Host,</p>
|
||||
<p>Congratulations, Your application to minglar admin has been approved.</p>
|
||||
<p>You can start onboarding your activities through the host panel.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendEmailToHostForMinglarApproval(
|
||||
emailAddress: string,
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Approval for your application";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear Host,</p>
|
||||
<p>Congratulations, Your application to minglar admin has been approved by minglar admin.</p>
|
||||
<p>Minglar admin will assign account manager to your application.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ROLE, USER_STATUS } from '@/common/utils/constants/common.constant';
|
||||
import { HOST_STATUS_DISPLAY, HOST_STATUS_INTERNAL } from '@/common/utils/constants/host.constant';
|
||||
import { HOST_STATUS_DISPLAY, HOST_STATUS_INTERNAL, STEPPER } from '@/common/utils/constants/host.constant';
|
||||
import { MINGLAR_INVITATION_STATUS, MINGLAR_STATUS_DISPLAY, MINGLAR_STATUS_INTERNAL } from '@/common/utils/constants/minglar.constant';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { User } from '@prisma/client';
|
||||
@@ -66,7 +66,7 @@ export class MinglarService {
|
||||
}
|
||||
|
||||
async getAllHosts() {
|
||||
return this.prisma.user.findMany({ where: { roleXid: 3 } });
|
||||
return this.prisma.user.findMany({ where: { roleXid: ROLE.HOST } });
|
||||
}
|
||||
|
||||
async updateHost(id: number, data: UpdateMinglarDto) {
|
||||
@@ -84,6 +84,12 @@ export class MinglarService {
|
||||
return this.prisma.user.findUnique({ where: { emailAddress: email } });
|
||||
}
|
||||
|
||||
async getUserDetails(id: number) {
|
||||
return await this.prisma.user.findUnique({
|
||||
where: { id: id }
|
||||
})
|
||||
}
|
||||
|
||||
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { emailAddress: email },
|
||||
@@ -270,154 +276,186 @@ export class MinglarService {
|
||||
},
|
||||
documents: Array<{ fileName: string; filePath: string }>
|
||||
) {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
// 1. Update User table
|
||||
const userUpdateData: any = {};
|
||||
if (userData.firstName !== undefined) userUpdateData.firstName = userData.firstName;
|
||||
if (userData.lastName !== undefined) userUpdateData.lastName = userData.lastName;
|
||||
if (userData.mobileNumber !== undefined) userUpdateData.mobileNumber = userData.mobileNumber;
|
||||
if (userData.dateOfBirth !== undefined) userUpdateData.dateOfBirth = new Date(userData.dateOfBirth);
|
||||
if (userData.profileImage !== undefined) userUpdateData.profileImage = userData.profileImage;
|
||||
try {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
console.log('Starting transaction for user:', userId);
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) {
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: userUpdateData,
|
||||
});
|
||||
}
|
||||
// 1. Update User table (optimized)
|
||||
const userUpdateData: any = {};
|
||||
const userFields = ['firstName', 'lastName', 'mobileNumber', 'dateOfBirth', 'profileImage'];
|
||||
|
||||
// 2. Update or create UserAddressDetails
|
||||
if (Object.keys(addressData).length > 0) {
|
||||
const existingAddress = await tx.userAddressDetails.findFirst({
|
||||
where: { userXid: userId, isActive: true },
|
||||
});
|
||||
|
||||
const addressUpdateData: any = {};
|
||||
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
|
||||
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
|
||||
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
|
||||
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
|
||||
if (addressData.cityXid !== undefined) addressUpdateData.cityXid = addressData.cityXid;
|
||||
if (addressData.pinCode !== undefined) addressUpdateData.pinCode = addressData.pinCode;
|
||||
|
||||
if (existingAddress) {
|
||||
await tx.userAddressDetails.update({
|
||||
where: { id: existingAddress.id },
|
||||
data: addressUpdateData,
|
||||
});
|
||||
} else {
|
||||
if (!addressData.address1 || !addressData.stateXid || !addressData.countryXid || !addressData.cityXid || !addressData.pinCode) {
|
||||
throw new ApiError(400, 'All address fields are required for new address');
|
||||
userFields.forEach(field => {
|
||||
if (userData[field as keyof typeof userData] !== undefined) {
|
||||
if (field === 'dateOfBirth' && userData.dateOfBirth) {
|
||||
userUpdateData[field] = new Date(userData.dateOfBirth);
|
||||
} else {
|
||||
userUpdateData[field] = userData[field as keyof typeof userData];
|
||||
}
|
||||
}
|
||||
await tx.userAddressDetails.create({
|
||||
data: {
|
||||
userXid: userId,
|
||||
...addressUpdateData,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) {
|
||||
console.log('Updating user data:', userUpdateData);
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: userUpdateData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update or create UserDocuments (store S3 URL in fileName field)
|
||||
if (documents && documents.length > 0) {
|
||||
const existingDocs = await tx.userDocuments.findMany({
|
||||
where: { userXid: userId, isActive: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
// 2. Update or create UserAddressDetails
|
||||
if (Object.keys(addressData).length > 0) {
|
||||
console.log('Processing address data:', addressData);
|
||||
|
||||
// Update existing documents or create new ones
|
||||
for (let i = 0; i < documents.length; i++) {
|
||||
const doc = documents[i];
|
||||
if (existingDocs[i]) {
|
||||
// Update existing document
|
||||
await tx.userDocuments.update({
|
||||
where: { id: existingDocs[i].id },
|
||||
data: { fileName: doc.filePath }, // Store S3 URL in fileName
|
||||
const existingAddress = await tx.userAddressDetails.findFirst({
|
||||
where: { userXid: userId, isActive: true },
|
||||
select: { id: true } // Only select needed field
|
||||
});
|
||||
|
||||
const addressUpdateData: any = {};
|
||||
const addressFields = ['address1', 'address2', 'stateXid', 'countryXid', 'cityXid', 'pinCode'];
|
||||
|
||||
addressFields.forEach(field => {
|
||||
if (addressData[field as keyof typeof addressData] !== undefined) {
|
||||
addressUpdateData[field] = addressData[field as keyof typeof addressData];
|
||||
}
|
||||
});
|
||||
|
||||
if (existingAddress) {
|
||||
await tx.userAddressDetails.update({
|
||||
where: { id: existingAddress.id },
|
||||
data: addressUpdateData,
|
||||
});
|
||||
} else {
|
||||
// Create new document
|
||||
await tx.userDocuments.create({
|
||||
// Validate required fields
|
||||
const requiredFields = ['address1', 'stateXid', 'countryXid', 'cityXid', 'pinCode'];
|
||||
const missingFields = requiredFields.filter(field => !addressData[field as keyof typeof addressData]);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
throw new ApiError(400, `Missing required address fields: ${missingFields.join(', ')}`);
|
||||
}
|
||||
|
||||
await tx.userAddressDetails.create({
|
||||
data: {
|
||||
userXid: userId,
|
||||
fileName: doc.filePath, // Store S3 URL in fileName
|
||||
...addressUpdateData,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Fetch updated user data to calculate percentage
|
||||
const updatedUser = await tx.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
userAddressDetails: {
|
||||
where: { isActive: true },
|
||||
take: 1,
|
||||
},
|
||||
userDocuments: {
|
||||
where: { isActive: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
// 3. Handle documents more efficiently
|
||||
if (documents && documents.length > 0) {
|
||||
console.log('Processing documents:', documents.length);
|
||||
|
||||
if (!updatedUser) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
// Use deleteMany and createMany for better performance
|
||||
await tx.userDocuments.deleteMany({
|
||||
where: { userXid: userId, isActive: true },
|
||||
});
|
||||
|
||||
// 5. Calculate profile completion percentage
|
||||
let percentage = 0;
|
||||
|
||||
// Profile Image: 15%
|
||||
if (updatedUser.profileImage) {
|
||||
percentage += 15;
|
||||
}
|
||||
|
||||
// Name and Phone Number: 15%
|
||||
if (updatedUser.firstName && updatedUser.lastName && updatedUser.mobileNumber) {
|
||||
percentage += 15;
|
||||
}
|
||||
|
||||
// Location Info: 25%
|
||||
if (updatedUser.userAddressDetails && updatedUser.userAddressDetails.length > 0) {
|
||||
const address = updatedUser.userAddressDetails[0];
|
||||
if (address.address1 && address.stateXid && address.countryXid && address.cityXid && address.pinCode) {
|
||||
percentage += 25;
|
||||
}
|
||||
}
|
||||
|
||||
// Documents (Aadhar and PAN): 45%
|
||||
if (updatedUser.userDocuments && updatedUser.userDocuments.length >= 2) {
|
||||
percentage += 45;
|
||||
} else if (updatedUser.userDocuments && updatedUser.userDocuments.length === 1) {
|
||||
percentage += 22.5; // Half if only one document
|
||||
}
|
||||
|
||||
const profilePercentage = Math.min(percentage, 100)
|
||||
if (profilePercentage > 80) {
|
||||
await this.prisma.user.update({
|
||||
where: {
|
||||
id: userId
|
||||
},
|
||||
data: {
|
||||
isProfileUpdated: true
|
||||
if (documents.length > 0) {
|
||||
await tx.userDocuments.createMany({
|
||||
data: documents.map(doc => ({
|
||||
userXid: userId,
|
||||
fileName: doc.filePath,
|
||||
isActive: true,
|
||||
})),
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: updatedUser.id,
|
||||
firstName: updatedUser.firstName,
|
||||
lastName: updatedUser.lastName,
|
||||
mobileNumber: updatedUser.mobileNumber,
|
||||
dateOfBirth: updatedUser.dateOfBirth,
|
||||
profileImage: updatedUser.profileImage,
|
||||
},
|
||||
address: updatedUser.userAddressDetails[0] || null,
|
||||
documents: updatedUser.userDocuments,
|
||||
profileCompletionPercentage: Math.min(percentage, 100),
|
||||
};
|
||||
});
|
||||
// 4. Fetch updated user data efficiently
|
||||
const updatedUser = await tx.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
mobileNumber: true,
|
||||
dateOfBirth: true,
|
||||
profileImage: true,
|
||||
userAddressDetails: {
|
||||
where: { isActive: true },
|
||||
take: 1,
|
||||
select: {
|
||||
id: true,
|
||||
address1: true,
|
||||
address2: true,
|
||||
stateXid: true,
|
||||
countryXid: true,
|
||||
cityXid: true,
|
||||
pinCode: true,
|
||||
}
|
||||
},
|
||||
userDocuments: {
|
||||
where: { isActive: true },
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!updatedUser) {
|
||||
throw new ApiError(404, 'User not found after update');
|
||||
}
|
||||
|
||||
// 5. Calculate profile completion percentage
|
||||
let percentage = 0;
|
||||
|
||||
// Profile Image: 15%
|
||||
if (updatedUser.profileImage) percentage += 15;
|
||||
|
||||
// Name and Phone Number: 15%
|
||||
if (updatedUser.firstName && updatedUser.lastName && updatedUser.mobileNumber) {
|
||||
percentage += 15;
|
||||
}
|
||||
|
||||
// Location Info: 25%
|
||||
if (updatedUser.userAddressDetails.length > 0) {
|
||||
const address = updatedUser.userAddressDetails[0];
|
||||
if (address.address1 && address.stateXid && address.countryXid && address.cityXid && address.pinCode) {
|
||||
percentage += 25;
|
||||
}
|
||||
}
|
||||
|
||||
// Documents: 45%
|
||||
if (updatedUser.userDocuments.length >= 2) {
|
||||
percentage += 45;
|
||||
} else if (updatedUser.userDocuments.length === 1) {
|
||||
percentage += 22.5;
|
||||
}
|
||||
|
||||
const profilePercentage = Math.min(percentage, 100);
|
||||
|
||||
// Update profile completion status
|
||||
if (profilePercentage > 80) {
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: { isProfileUpdated: true }
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Transaction completed successfully');
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: updatedUser.id,
|
||||
firstName: updatedUser.firstName,
|
||||
lastName: updatedUser.lastName,
|
||||
mobileNumber: updatedUser.mobileNumber,
|
||||
dateOfBirth: updatedUser.dateOfBirth,
|
||||
profileImage: updatedUser.profileImage,
|
||||
},
|
||||
address: updatedUser.userAddressDetails[0] || null,
|
||||
documents: updatedUser.userDocuments,
|
||||
profileCompletionPercentage: profilePercentage,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in updateProfile transaction:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getAllInvitationDetails() {
|
||||
@@ -530,58 +568,58 @@ export class MinglarService {
|
||||
}
|
||||
|
||||
async getAllCoadminAndAM() {
|
||||
// 1. Fetch all required users (Admin, Co-Admin, AM)
|
||||
const users = await this.prisma.user.findMany({
|
||||
where: {
|
||||
roleXid: {
|
||||
in: [
|
||||
ROLE.MINGLAR_ADMIN, // Admin
|
||||
ROLE.CO_ADMIN, // Co-Admin
|
||||
ROLE.ACCOUNT_MANAGER // AM
|
||||
]
|
||||
// 1. Fetch all required users (Admin, Co-Admin, AM)
|
||||
const users = await this.prisma.user.findMany({
|
||||
where: {
|
||||
roleXid: {
|
||||
in: [
|
||||
ROLE.MINGLAR_ADMIN, // Admin
|
||||
ROLE.CO_ADMIN, // Co-Admin
|
||||
ROLE.ACCOUNT_MANAGER // AM
|
||||
]
|
||||
},
|
||||
isActive: true,
|
||||
userStatus: USER_STATUS.ACTIVE,
|
||||
},
|
||||
isActive: true,
|
||||
userStatus: USER_STATUS.ACTIVE,
|
||||
},
|
||||
include: {
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
roleName: true,
|
||||
include: {
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
roleName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!users.length) return [];
|
||||
if (!users.length) return [];
|
||||
|
||||
const userIds = users.map((u) => u.id);
|
||||
const userIds = users.map((u) => u.id);
|
||||
|
||||
// 2. Count assigned hosts for ANY user (Admin / Co-Admin / AM)
|
||||
const groupedHosts = await this.prisma.hostHeader.groupBy({
|
||||
by: ["accountManagerXid"],
|
||||
where: {
|
||||
accountManagerXid: { in: userIds }, // assigned user
|
||||
isActive: true,
|
||||
},
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
// 2. Count assigned hosts for ANY user (Admin / Co-Admin / AM)
|
||||
const groupedHosts = await this.prisma.hostHeader.groupBy({
|
||||
by: ["accountManagerXid"],
|
||||
where: {
|
||||
accountManagerXid: { in: userIds }, // assigned user
|
||||
isActive: true,
|
||||
},
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Build quick lookup map: userId -> hostCount
|
||||
const hostCountMap: Record<number, number> = {};
|
||||
groupedHosts.forEach((g) => {
|
||||
const uid = Number(g.accountManagerXid);
|
||||
hostCountMap[uid] = g._count.id;
|
||||
});
|
||||
// 3. Build quick lookup map: userId -> hostCount
|
||||
const hostCountMap: Record<number, number> = {};
|
||||
groupedHosts.forEach((g) => {
|
||||
const uid = Number(g.accountManagerXid);
|
||||
hostCountMap[uid] = g._count.id;
|
||||
});
|
||||
|
||||
// 4. Attach host counts to each user
|
||||
return users.map((user) => ({
|
||||
...user,
|
||||
assignedHostCount: hostCountMap[user.id] ?? 0,
|
||||
}));
|
||||
}
|
||||
// 4. Attach host counts to each user
|
||||
return users.map((user) => ({
|
||||
...user,
|
||||
assignedHostCount: hostCountMap[user.id] ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
async assignAMToHost(userId: number, hostXid: number, accountManagerXid: number) {
|
||||
@@ -731,11 +769,31 @@ export class MinglarService {
|
||||
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW
|
||||
},
|
||||
data: {
|
||||
isApproved: true,
|
||||
hostStatusInternal: HOST_STATUS_INTERNAL.APPROVED,
|
||||
hostStatusDisplay: HOST_STATUS_DISPLAY.APPROVED,
|
||||
adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_APPROVED,
|
||||
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.APPROVED
|
||||
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.APPROVED,
|
||||
stepper: STEPPER.COMPANY_DETAILS_APPROVED
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async acceptHostApplicationMinglarAdmin(host_xid: number, user_xid: number) {
|
||||
return await this.prisma.hostHeader.update({
|
||||
where: {
|
||||
id: host_xid,
|
||||
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
|
||||
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
|
||||
adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW,
|
||||
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.NEW
|
||||
},
|
||||
data: {
|
||||
isApproved: true,
|
||||
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
|
||||
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
|
||||
adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED,
|
||||
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -774,5 +832,30 @@ export class MinglarService {
|
||||
}
|
||||
|
||||
|
||||
async rejectHostApplicationAM(host_xid: number, user_xid: number) {
|
||||
const hostDetails = await this.prisma.hostHeader.findFirst({
|
||||
where: { id: host_xid },
|
||||
select: { id: true, userXid: true }
|
||||
})
|
||||
if (!hostDetails) {
|
||||
throw new Error("Host not found");
|
||||
}
|
||||
await this.prisma.hostHeader.update({
|
||||
where: {
|
||||
id: host_xid,
|
||||
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
|
||||
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW
|
||||
},
|
||||
data: {
|
||||
hostStatusInternal: HOST_STATUS_INTERNAL.REJECTED,
|
||||
hostStatusDisplay: HOST_STATUS_DISPLAY.REJECTED,
|
||||
adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_REJECTED,
|
||||
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.REJECTED,
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { brevoService } from "@/common/email/brevoApi";
|
||||
import ApiError from "@/common/utils/helper/ApiError";
|
||||
|
||||
export async function sendEmailToHostForRejectedApplication(
|
||||
emailAddress: string,
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Rejection for your application";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear Host,</p>
|
||||
<p>Sorry to say that, But your application to minglar admin has been rejected.</p>
|
||||
<p>If you have any questions please contact to minglar admin.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendAMRejectionMailtoHost(
|
||||
emailAddress: string,
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Improvement of your application";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear Host,</p>
|
||||
<p>Your account manager has made some suggestions on your application.<br/>
|
||||
Please improve it and re-submit the application to onboard on minglar.</p>
|
||||
<p>If you have any questions please contact to minglar admin.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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 { PrePopulateService } from '../services/prepopulate.service';
|
||||
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const prePopulateService = new PrePopulateService(prismaService);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// 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 verifyHostToken(token);
|
||||
|
||||
const result = await prePopulateService.getAllDocumentTypeWithCountryStateCity();
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Data retrieved successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -58,6 +58,27 @@ export class PrePopulateService {
|
||||
});
|
||||
}
|
||||
|
||||
async getAllDocumentTypeWithCountryStateCity() {
|
||||
const [documentDetails, countryDetails, stateDetails, cityDetails] =
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.documentType.findMany({
|
||||
where: { isActive: true, isVisible: true },
|
||||
orderBy: { displayOrder: 'asc' },
|
||||
}),
|
||||
this.prisma.countries.findMany({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
this.prisma.states.findMany({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
this.prisma.cities.findMany({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
return { documentDetails, countryDetails, stateDetails, cityDetails };
|
||||
}
|
||||
|
||||
async getAllFrequencies() {
|
||||
return await this.prisma.frequencies.findMany({
|
||||
where: {
|
||||
@@ -66,7 +87,7 @@ export class PrePopulateService {
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
frequencyName:true
|
||||
frequencyName: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user