diff --git a/serverless.yml b/serverless.yml index 95f9e7c..925be84 100644 --- a/serverless.yml +++ b/serverless.yml @@ -190,6 +190,22 @@ functions: path: /host/getById method: get + getActivityTypes: + handler: src/modules/host/handlers/getActivity.handler + package: + patterns: + - 'src/modules/host/handlers/getActivity.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' + + events: + - httpApi: + path: /host/get-activity + method: get + acceptMinglarAgreement: handler: src/modules/host/handlers/acceptAgreement.handler package: @@ -424,6 +440,21 @@ functions: path: /prepopulate/get-all-pqq-ques-ans method: get + getFrequenciesOfActivity: + handler: src/modules/prepopulate/handlers/getAllFrequencies.handler + package: + patterns: + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' + + events: + - httpApi: + path: /prepopulate/get-all-Frequencies + method: get + assignAMToHost: handler: src/modules/minglaradmin/handlers/assignAM.handler package: diff --git a/src/modules/host/handlers/getActivity.ts b/src/modules/host/handlers/getActivity.ts new file mode 100644 index 0000000..76c9abb --- /dev/null +++ b/src/modules/host/handlers/getActivity.ts @@ -0,0 +1,47 @@ +import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; +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 { HostService } from '../services/host.service'; + +const prismaService = new PrismaService(); +const hostService = new HostService(prismaService); + +/** + * 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 verifyHostToken(token); + + + // Read optional search query (supports ?search= or ?q=) + const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined; + + const data = await hostService.getAllActivityTypesWithInterest(search); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Data retrieved successfully', + data, + }), + }; +}); diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 4b4d69a..287d751 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -550,4 +550,36 @@ export class HostService { }); } + async getAllActivityTypesWithInterest(search?: string) { + const where: any = { + isActive: true, + deletedAt: null, + }; + + if (search && search.trim() !== '') { + const q = search.trim(); + where.OR = [ + { activityTypeName: { contains: q, mode: 'insensitive' } }, + { interests: { interestName: { contains: q, mode: 'insensitive' } } }, + ]; + } + + return await this.prisma.activityTypes.findMany({ + where, + select: { + id: true, + activityTypeName: true, + interestXid: true, + interests: { + select: { + id: true, + interestName: true, + displayOrder: true, + }, + }, + }, + orderBy: { activityTypeName: 'asc' }, + }); + } + } diff --git a/src/modules/minglaradmin/handlers/assignAM.ts b/src/modules/minglaradmin/handlers/assignAM.ts index 5ccea34..9c0be67 100644 --- a/src/modules/minglaradmin/handlers/assignAM.ts +++ b/src/modules/minglaradmin/handlers/assignAM.ts @@ -1,31 +1,41 @@ -import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +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 { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; +import { sendAMEmailForHostAssign } from '../services/AMEmail.service'; const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaService); interface assignAMToHostBody { - host_xid: number; - account_manager_xid: number; - } + host_xid: number; + account_manager_xid: number; +} /** * Get all host applications handler * Returns host details with status, submission date, and account manager info */ -export const handler = safeHandler(async ( +export const handler = safeHandler( + async ( event: APIGatewayProxyEvent, - context?: Context -): Promise => { + context?: Context, + ): Promise => { // Verify authentication token - const token = event.headers['x-auth-token'] || event.headers['X-Auth-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.'); + throw new ApiError( + 401, + 'This is a protected route. Please provide a valid token.', + ); } // Verify token and get user info @@ -33,40 +43,44 @@ export const handler = safeHandler(async ( // Get user details including role const user = await prismaService.user.findUnique({ - where: { id: userInfo.id }, - select: { id: true, roleXid: true } + where: { id: userInfo.id }, + select: { id: true, roleXid: true }, }); if (!user) { - throw new ApiError(404, 'User not found'); + throw new ApiError(404, 'User not found'); } // Parse request body let body: assignAMToHostBody; try { - body = event.body ? JSON.parse(event.body) : {}; + body = event.body ? JSON.parse(event.body) : {}; } catch (error) { - throw new ApiError(400, 'Invalid JSON in request body'); + throw new ApiError(400, 'Invalid JSON in request body'); } - const { - host_xid, - account_manager_xid - } = body; + const { host_xid, account_manager_xid } = body; // Get all host applications from service based on user role await minglarService.assignAMToHost(user.id, host_xid, account_manager_xid); + try { + await minglarService.notifyAMOfAssignment(account_manager_xid); + } catch (err) { + console.error('Failed to notify AM after assignment:', err); + } + return { - statusCode: 200, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - }, - body: JSON.stringify({ - success: true, - message: 'AM assigned to host successfully', - }), + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'AM assigned to host successfully', + }), }; -}); + }, +); diff --git a/src/modules/minglaradmin/services/AMEmail.service.ts b/src/modules/minglaradmin/services/AMEmail.service.ts new file mode 100644 index 0000000..d73de83 --- /dev/null +++ b/src/modules/minglaradmin/services/AMEmail.service.ts @@ -0,0 +1,37 @@ +// ...existing code... +import { brevoService } from '@/common/email/brevoApi'; +import ApiError from '@/common/utils/helper/ApiError'; + +export async function sendAMEmailForHostAssign(emailAddress: string): Promise<{ + sent: boolean; + // messageId: string +}> { + const subject = 'Minglar Admin: Host Assignment Notification'; + + const htmlContent = ` +

Hi,

+ +

You’ve been assigned the Host role by Minglar Admin.

+ +

Best regards,
Minglar Admin 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 invitation via email.'); + } +} +// ...existing code... diff --git a/src/modules/minglaradmin/services/amNotification.service.ts b/src/modules/minglaradmin/services/amNotification.service.ts new file mode 100644 index 0000000..33e4c43 --- /dev/null +++ b/src/modules/minglaradmin/services/amNotification.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { sendAMEmailForHostAssign } from './AMEmail.service'; + +@Injectable() +export class AMNotificationService { + constructor(private prisma: PrismaService) {} + + /** + * Fetch account manager email by id and send assignment email. + * Returns true if email was attempted (sent or attempted), false if AM missing or no email. + */ + async notifyAMOfAssignment(accountManagerXid: number, hostXid?: number): Promise { + if (!accountManagerXid) return false; + + const amUser = await this.prisma.user.findUnique({ + where: { id: accountManagerXid }, + select: { emailAddress: true, firstName: true, lastName: true }, + }); + + if (!amUser || !amUser.emailAddress) { + console.warn(`AM notification skipped: user not found or missing email for id=${accountManagerXid}`); + return false; + } + + try { + await sendAMEmailForHostAssign(amUser.emailAddress); + return true; + } catch (err) { + console.error('Error sending AM assignment email', err); + return false; + } + } +} diff --git a/src/modules/minglaradmin/services/minglar.service.ts b/src/modules/minglaradmin/services/minglar.service.ts index 6d766be..7452df0 100644 --- a/src/modules/minglaradmin/services/minglar.service.ts +++ b/src/modules/minglaradmin/services/minglar.service.ts @@ -7,6 +7,7 @@ import * as bcrypt from 'bcryptjs'; import { PrismaService } from '../../../common/database/prisma.service'; import ApiError from '../../../common/utils/helper/ApiError'; import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto'; +import { sendAMEmailForHostAssign } from './AMEmail.service'; @Injectable() @@ -615,6 +616,32 @@ export class MinglarService { return true; } + /** + * Notify Account Manager by email after assignment. + * Encapsulates lookup + email send so handlers can call a single method. + */ + async notifyAMOfAssignment(accountManagerXid: number): Promise { + if (!accountManagerXid) return false; + + const amUser = await this.prisma.user.findUnique({ + where: { id: accountManagerXid ,isActive:true}, + select: { emailAddress: true}, + }); + + if (!amUser || !amUser.emailAddress) { + console.warn(`AM notification skipped: user not found or missing email for id=${accountManagerXid}`); + return false; + } + + try { + await sendAMEmailForHostAssign(amUser.emailAddress); + return true; + } catch (err) { + console.error('Error sending AM assignment email', err); + return false; + } + } + async addHostSuggestion(hostXid: number, title: string, comments: string, reviewedByXid: number) { // Check if host exists const hostHeader = await this.prisma.hostHeader.findUnique({ diff --git a/src/modules/prepopulate/handlers/getAllFrequencies.ts b/src/modules/prepopulate/handlers/getAllFrequencies.ts new file mode 100644 index 0000000..bcf4796 --- /dev/null +++ b/src/modules/prepopulate/handlers/getAllFrequencies.ts @@ -0,0 +1,39 @@ +import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import ApiError from '../../../common/utils/helper/ApiError'; +import { 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 => { + // 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.getAllFrequencies(); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Data retrieved successfully', + data: result, + }), + }; +}); diff --git a/src/modules/prepopulate/services/prepopulate.service.ts b/src/modules/prepopulate/services/prepopulate.service.ts index 4903490..4e8c2b4 100644 --- a/src/modules/prepopulate/services/prepopulate.service.ts +++ b/src/modules/prepopulate/services/prepopulate.service.ts @@ -1,65 +1,73 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../../common/database/prisma.service'; - @Injectable() export class PrePopulateService { - constructor(private prisma: PrismaService) { } + constructor(private prisma: PrismaService) {} - async getAllBankDetails() { - return await this.prisma.banks.findMany({ - where: { - isActive: true, - deletedAt: null, + async getAllBankDetails() { + return await this.prisma.banks.findMany({ + where: { + isActive: true, + deletedAt: null, + }, + include: { + BankBranches: { + select: { + id: true, + branchAddress: true, + ifscCode: true, + }, + }, + }, + }); + } + + async getAllCurrencyDetails() { + return await this.prisma.currencies.findMany({ + where: { + isActive: true, + deletedAt: null, + }, + }); + } + + async getAllPQQQuesAndAns() { + return await this.prisma.pQQCategories.findMany({ + where: { isActive: true }, + include: { + pqqsubCategories: { + include: { + questions: { + include: { + PQQAnswers: { + orderBy: { + displayOrder: 'asc', + }, + }, + }, + orderBy: { + displayOrder: 'asc', + }, }, - include: { - BankBranches: { - select: { - id: true, - branchAddress: true, - ifscCode: true - } - } - } - }) - } - - async getAllCurrencyDetails() { - return await this.prisma.currencies.findMany({ - where: { - isActive: true, - deletedAt: null, - }, - }) - } - - async getAllPQQQuesAndAns() { - return await this.prisma.pQQCategories.findMany({ - where: { isActive: true }, - include: { - pqqsubCategories: { - include: { - questions: { - include: { - PQQAnswers: { - orderBy: { - displayOrder: 'asc' - } - } - }, - orderBy: { - displayOrder: 'asc' - } - } - }, - orderBy: { displayOrder: 'asc' } - } - }, - orderBy: { displayOrder: 'asc' } - }); - } - - - + }, + orderBy: { displayOrder: 'asc' }, + }, + }, + orderBy: { displayOrder: 'asc' }, + }); + } + async getAllFrequencies() { + return await this.prisma.frequencies.findMany({ + where: { + isActive: true, + deletedAt: null, + }, + select: { + id: true, + frequencyName:true + }, + }); + } }