Add new endpoints for activity types and frequencies, and implement email notifications for AM assignments

This commit is contained in:
paritosh18
2025-11-22 19:25:07 +05:30
parent 15c85686c6
commit d0b2de3f18
9 changed files with 353 additions and 84 deletions

View File

@@ -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:

View File

@@ -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<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 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,
}),
};
});

View File

@@ -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' },
});
}
}

View File

@@ -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<APIGatewayProxyResult> => {
context?: Context,
): Promise<APIGatewayProxyResult> => {
// 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',
}),
};
});
},
);

View File

@@ -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 = `
<p>Hi,</p>
<p>Youve been assigned the <strong>Host</strong> role by Minglar Admin.</p>
<p>Best regards,<br/>Minglar Admin 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 invitation via email.');
}
}
// ...existing code...

View File

@@ -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<boolean> {
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;
}
}
}

View File

@@ -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<boolean> {
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({

View File

@@ -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<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using the shared authForHost function
await verifyHostToken(token);
const result = await prePopulateService.getAllFrequencies();
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -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
},
});
}
}