diff --git a/serverless.yml b/serverless.yml
index dfb53f6..b37fc6d 100644
--- a/serverless.yml
+++ b/serverless.yml
@@ -287,6 +287,7 @@ functions:
updateMinglarProfile:
handler: src/modules/minglaradmin/handlers/updateProfile.handler
+ timeout: 30
package:
patterns:
- 'src/modules/host/handlers/updateProfile.*'
@@ -299,7 +300,6 @@ functions:
- 'node_modules/@smithy/**'
- 'node_modules/tslib/**'
- 'node_modules/fast-xml-parser/**'
-
events:
- httpApi:
@@ -410,8 +410,7 @@ functions:
- httpApi:
path: /prepopulate/get-all-bank-currency-details
method: get
-
-
+
getAllDocumentCountryStateCityDetails:
handler: src/modules/prepopulate/handlers/getAllDocTypeWithCountryState.handler
package:
@@ -487,6 +486,21 @@ functions:
path: /minglaradmin/accept-host-application
method: patch
+ acceptHostApplicationMinglar:
+ handler: src/modules/minglaradmin/handlers/acceptHostAppMinglar.handler
+ package:
+ patterns:
+ - 'src/modules/minglaradmin/**'
+ - 'common/**'
+ - 'src/common/**'
+ - 'node_modules/@prisma/client/**'
+ - 'node_modules/.prisma/**'
+
+ events:
+ - httpApi:
+ path: /minglaradmin/accept-host-application-minglar
+ method: patch
+
rejectHostApplication:
handler: src/modules/minglaradmin/handlers/rejectHostApplication.handler
package:
@@ -502,6 +516,21 @@ functions:
path: /minglaradmin/reject-host-application
method: patch
+ rejectHostApplicationAM:
+ handler: src/modules/minglaradmin/handlers/rejectHostApplicationAM.handler
+ package:
+ patterns:
+ - 'src/modules/minglaradmin/**'
+ - 'common/**'
+ - 'src/common/**'
+ - 'node_modules/@prisma/client/**'
+ - 'node_modules/.prisma/**'
+
+ events:
+ - httpApi:
+ path: /minglaradmin/reject-host-application-am
+ method: patch
+
addCompanyDetails:
handler: src/modules/host/handlers/addCompanyDetails.handler
package:
diff --git a/src/modules/host/handlers/addCompanyDetails.ts b/src/modules/host/handlers/addCompanyDetails.ts
index b25ea1e..1999f67 100644
--- a/src/modules/host/handlers/addCompanyDetails.ts
+++ b/src/modules/host/handlers/addCompanyDetails.ts
@@ -3,16 +3,15 @@ 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';
@@ -350,7 +349,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const getSuggestionDetails = await hostService.getSuggestionDetails(userInfo.id)
- if (getSuggestionDetails && getSuggestionDetails.hostDetails.accountManagerXid !== null) {
+ if (getSuggestionDetails.hostDetails.accountManagerXid !== null) {
await sendEmailToAM(
getSuggestionDetails.hostDetails.accountManager.emailAddress,
getSuggestionDetails.hostDetails.accountManager.firstName,
diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts
index 3fb1693..3456ccd 100644
--- a/src/modules/host/services/host.service.ts
+++ b/src/modules/host/services/host.service.ts
@@ -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
}
})
}
diff --git a/src/modules/host/services/sendHostResubmitEmailToAM.service.ts b/src/modules/host/services/sendHostResubmitEmailToAM.service.ts
index 8565bb8..87c1bd9 100644
--- a/src/modules/host/services/sendHostResubmitEmailToAM.service.ts
+++ b/src/modules/host/services/sendHostResubmitEmailToAM.service.ts
@@ -11,7 +11,7 @@ export async function sendEmailToAM(
// messageId: string
}> {
- const subject = "Host Application Re-Submited";
+ const subject = `Host Application Re-Submited : ${hostCompanyName}`;
const htmlContent = `
Dear ${amName},
@@ -49,7 +49,7 @@ export async function sendEmailToMinglarAdmin(
// messageId: string
}> {
- const subject = "New Host Application Recieved";
+ const subject = `New Host Application Recieved : ${hostCompanyName}`;
const htmlContent = `
Dear ${minglarAdminName},
diff --git a/src/modules/minglaradmin/handlers/acceptHostAppMinglar.ts b/src/modules/minglaradmin/handlers/acceptHostAppMinglar.ts
new file mode 100644
index 0000000..00b6fd4
--- /dev/null
+++ b/src/modules/minglaradmin/handlers/acceptHostAppMinglar.ts
@@ -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 => {
+ // 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,
+ }),
+ };
+});
diff --git a/src/modules/minglaradmin/handlers/acceptHostApplication.ts b/src/modules/minglaradmin/handlers/acceptHostApplication.ts
index a903f16..a71cc7d 100644
--- a/src/modules/minglaradmin/handlers/acceptHostApplication.ts
+++ b/src/modules/minglaradmin/handlers/acceptHostApplication.ts
@@ -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,
diff --git a/src/modules/minglaradmin/handlers/rejectHostApplication.ts b/src/modules/minglaradmin/handlers/rejectHostApplication.ts
index 075a62e..780a0b6 100644
--- a/src/modules/minglaradmin/handlers/rejectHostApplication.ts
+++ b/src/modules/minglaradmin/handlers/rejectHostApplication.ts
@@ -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,
diff --git a/src/modules/minglaradmin/handlers/rejectHostApplicationAM.ts b/src/modules/minglaradmin/handlers/rejectHostApplicationAM.ts
new file mode 100644
index 0000000..9f02400
--- /dev/null
+++ b/src/modules/minglaradmin/handlers/rejectHostApplicationAM.ts
@@ -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 => {
+ // 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,
+ }),
+ };
+});
diff --git a/src/modules/minglaradmin/handlers/updateProfile.ts b/src/modules/minglaradmin/handlers/updateProfile.ts
index 4708dca..d9d33eb 100644
--- a/src/modules/minglaradmin/handlers/updateProfile.ts
+++ b/src/modules/minglaradmin/handlers/updateProfile.ts
@@ -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 => {
- // 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,
- }),
- };
-});
-
+});
\ No newline at end of file
diff --git a/src/modules/minglaradmin/services/approvalMailtoHost.service.ts b/src/modules/minglaradmin/services/approvalMailtoHost.service.ts
new file mode 100644
index 0000000..f39fbf4
--- /dev/null
+++ b/src/modules/minglaradmin/services/approvalMailtoHost.service.ts
@@ -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 = `
+ Dear Host,
+ Congratulations, Your application to minglar admin has been approved.
+ You can start onboarding your activities through the host panel.
+ Best regards,
Minglar Team
+ `;
+
+ 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 = `
+ Dear Host,
+ Congratulations, Your application to minglar admin has been approved by minglar admin.
+ Minglar admin will assign account manager to your application.
+ Best regards,
Minglar Team
+ `;
+
+ 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.");
+ }
+}
diff --git a/src/modules/minglaradmin/services/minglar.service.ts b/src/modules/minglaradmin/services/minglar.service.ts
index 71a0d99..f5fcdfd 100644
--- a/src/modules/minglaradmin/services/minglar.service.ts
+++ b/src/modules/minglaradmin/services/minglar.service.ts
@@ -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';
@@ -65,7 +65,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) {
@@ -83,6 +83,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 {
const user = await this.prisma.user.findUnique({
where: { emailAddress: email },
@@ -269,154 +275,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() {
@@ -704,11 +742,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,
}
})
}
@@ -747,5 +805,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,
+
+ }
+ })
+ }
+
+
}
diff --git a/src/modules/minglaradmin/services/rejectionMailtoHost.service.ts b/src/modules/minglaradmin/services/rejectionMailtoHost.service.ts
new file mode 100644
index 0000000..48b6e2d
--- /dev/null
+++ b/src/modules/minglaradmin/services/rejectionMailtoHost.service.ts
@@ -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 = `
+ Dear Host,
+ Sorry to say that, But your application to minglar admin has been rejected.
+ If you have any questions please contact to minglar admin.
+ Best regards,
Minglar Team
+ `;
+
+ 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 = `
+ Dear Host,
+ Your account manager has made some suggestions on your application.
+ Please improve it and re-submit the application to onboard on minglar.
+ If you have any questions please contact to minglar admin.
+ Best regards,
Minglar Team
+ `;
+
+ 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.");
+ }
+}