Add new handlers for accepting and rejecting host applications, including email notifications. Updated serverless configuration with new function timeouts and added missing handlers. Refactored profile update logic to improve file upload handling and added user detail retrieval methods in the Minglar service.
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 = `
|
||||
<p>Dear ${amName},</p>
|
||||
@@ -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 = `
|
||||
<p>Dear ${minglarAdminName},</p>
|
||||
|
||||
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';
|
||||
@@ -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<boolean> {
|
||||
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,
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user