resolved Merge

This commit is contained in:
paritosh18
2025-11-24 23:25:20 +05:30
parent 7056f32e24
commit d65f7f5368
3 changed files with 313 additions and 106 deletions

View File

@@ -0,0 +1,91 @@
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 { HOST_SUGGESTION_TITLES } from '../../../common/utils/constants/minglar.constant';
const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody {
hostXid: number;
title: string;
comments: string;
activity_pqq_header_xid:number
}
/**
* 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);
// Get user details
const user = await prismaService.user.findUnique({
where: { id: userInfo.id },
select: { id: true, roleXid: true }
});
if (!user) {
throw new ApiError(404, 'User not found');
}
// 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 { title, comments , activity_pqq_header_xid} = body;
if (!title) {
throw new ApiError(400, 'Title is required');
}
if (!comments) {
throw new ApiError(400, 'Comments are required');
}
if(!activity_pqq_header_xid){
throw new ApiError(400 , "Activity Pqq HeaderXid Required");
}
// Validate title is one of the allowed types
const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
if (!allowedTitles.includes(title)) {
throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
}
// Add suggestion using service
await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Suggestion added successfully',
data: null,
}),
};
});

View File

@@ -1,6 +1,14 @@
import { ROLE, USER_STATUS } from '@/common/utils/constants/common.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 {
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';
import * as bcrypt from 'bcryptjs';
@@ -9,10 +17,9 @@ import ApiError from '../../../common/utils/helper/ApiError';
import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto';
import { sendAMEmailForHostAssign } from './AMEmail.service';
@Injectable()
export class MinglarService {
constructor(private prisma: PrismaService) { }
constructor(private prisma: PrismaService) {}
async createPassword(user_xid: number, password: string): Promise<boolean> {
// Find user by id
@@ -35,8 +42,8 @@ export class MinglarService {
invitation_status: MINGLAR_INVITATION_STATUS.ACCEPTED,
accepted_on: new Date(),
is_accepted: true,
}
})
},
});
}
if (!user) {
@@ -45,7 +52,10 @@ export class MinglarService {
// Check if password already exists
if (user.userPassword) {
throw new ApiError(400, 'Password already exists. Use update password instead.');
throw new ApiError(
400,
'Password already exists. Use update password instead.',
);
}
// Hash the password
@@ -55,7 +65,11 @@ export class MinglarService {
// Update user with hashed password
await this.prisma.user.update({
where: { id: user.id },
data: { userPassword: hashedPassword, userStatus: USER_STATUS.ACTIVE, isEmailVerfied: true },
data: {
userPassword: hashedPassword,
userStatus: USER_STATUS.ACTIVE,
isEmailVerfied: true,
},
});
return true;
@@ -86,8 +100,8 @@ export class MinglarService {
async getUserDetails(id: number) {
return await this.prisma.user.findUnique({
where: { id: id }
})
where: { id: id },
});
}
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
@@ -138,18 +152,29 @@ export class MinglarService {
async loginForMinglar(emailAddress: string, userPassword: string) {
const existingUser = await this.prisma.user.findUnique({
where: { emailAddress: emailAddress, isActive: true, userStatus: USER_STATUS.ACTIVE }
where: {
emailAddress: emailAddress,
isActive: true,
userStatus: USER_STATUS.ACTIVE,
},
});
if (!existingUser) {
throw new ApiError(404, 'User not found');
}
if (existingUser.roleXid !== ROLE.MINGLAR_ADMIN && existingUser.roleXid !== ROLE.CO_ADMIN && existingUser.roleXid !== ROLE.ACCOUNT_MANAGER) {
if (
existingUser.roleXid !== ROLE.MINGLAR_ADMIN &&
existingUser.roleXid !== ROLE.CO_ADMIN &&
existingUser.roleXid !== ROLE.ACCOUNT_MANAGER
) {
throw new ApiError(403, 'Access denied.');
}
const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword);
const matchPassword = await bcrypt.compare(
userPassword,
existingUser.userPassword,
);
if (!matchPassword) {
throw new ApiError(401, 'Invalid credentials');
}
@@ -159,7 +184,7 @@ export class MinglarService {
async checkUserExists(emailAddress: string) {
return await this.prisma.user.findUnique({
where: { emailAddress: emailAddress, isActive: true }
where: { emailAddress: emailAddress, isActive: true },
});
}
@@ -168,23 +193,31 @@ export class MinglarService {
data: {
emailAddress: emailAddress,
roleXid: roleXid,
userStatus: USER_STATUS.INVITED
}
userStatus: USER_STATUS.INVITED,
},
});
}
async createUserRevenue(userXid: number, isFixedSalary: boolean, perValue: number) {
async createUserRevenue(
userXid: number,
isFixedSalary: boolean,
perValue: number,
) {
return await this.prisma.userRevenue.create({
data: {
userXid: userXid,
is_fixed_salary: isFixedSalary,
per_value: perValue || 0,
isActive: true
}
isActive: true,
},
});
}
async createInviteDetails(userXid: number, invitedBy: number, invitationStatus: string) {
async createInviteDetails(
userXid: number,
invitedBy: number,
invitationStatus: string,
) {
return await this.prisma.inviteDetails.create({
data: {
userXid: userXid,
@@ -195,7 +228,7 @@ export class MinglarService {
invitation_status: invitationStatus,
isActive: true,
isMinglarInvitation: true,
}
},
});
}
@@ -208,7 +241,7 @@ export class MinglarService {
roleXid: number,
isFixedSalary: boolean,
perValue: number,
invitedBy: number
invitedBy: number,
) {
return await this.prisma.$transaction(async (tx) => {
// Check existing user
@@ -274,7 +307,7 @@ export class MinglarService {
cityXid?: number;
pinCode?: string;
},
documents: Array<{ fileName: string; filePath: string }>
documents: Array<{ fileName: string; filePath: string }>,
) {
try {
return await this.prisma.$transaction(async (tx) => {
@@ -282,9 +315,15 @@ export class MinglarService {
// 1. Update User table (optimized)
const userUpdateData: any = {};
const userFields = ['firstName', 'lastName', 'mobileNumber', 'dateOfBirth', 'profileImage'];
const userFields = [
'firstName',
'lastName',
'mobileNumber',
'dateOfBirth',
'profileImage',
];
userFields.forEach(field => {
userFields.forEach((field) => {
if (userData[field as keyof typeof userData] !== undefined) {
if (field === 'dateOfBirth' && userData.dateOfBirth) {
userUpdateData[field] = new Date(userData.dateOfBirth);
@@ -308,15 +347,23 @@ export class MinglarService {
const existingAddress = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: { id: true } // Only select needed field
select: { id: true }, // Only select needed field
});
const addressUpdateData: any = {};
const addressFields = ['address1', 'address2', 'stateXid', 'countryXid', 'cityXid', 'pinCode'];
const addressFields = [
'address1',
'address2',
'stateXid',
'countryXid',
'cityXid',
'pinCode',
];
addressFields.forEach(field => {
addressFields.forEach((field) => {
if (addressData[field as keyof typeof addressData] !== undefined) {
addressUpdateData[field] = addressData[field as keyof typeof addressData];
addressUpdateData[field] =
addressData[field as keyof typeof addressData];
}
});
@@ -327,11 +374,22 @@ export class MinglarService {
});
} else {
// Validate required fields
const requiredFields = ['address1', 'stateXid', 'countryXid', 'cityXid', 'pinCode'];
const missingFields = requiredFields.filter(field => !addressData[field as keyof typeof addressData]);
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(', ')}`);
throw new ApiError(
400,
`Missing required address fields: ${missingFields.join(', ')}`,
);
}
await tx.userAddressDetails.create({
@@ -354,7 +412,7 @@ export class MinglarService {
if (documents.length > 0) {
await tx.userDocuments.createMany({
data: documents.map(doc => ({
data: documents.map((doc) => ({
userXid: userId,
fileName: doc.filePath,
isActive: true,
@@ -384,14 +442,14 @@ export class MinglarService {
countryXid: true,
cityXid: true,
pinCode: true,
}
},
},
userDocuments: {
where: { isActive: true },
select: {
id: true,
fileName: true,
}
},
},
},
});
@@ -407,14 +465,24 @@ export class MinglarService {
if (updatedUser.profileImage) percentage += 15;
// Name and Phone Number: 15%
if (updatedUser.firstName && updatedUser.lastName && updatedUser.mobileNumber) {
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) {
if (
address.address1 &&
address.stateXid &&
address.countryXid &&
address.cityXid &&
address.pinCode
) {
percentage += 25;
}
}
@@ -432,7 +500,7 @@ export class MinglarService {
if (profilePercentage > 80) {
await tx.user.update({
where: { id: userId },
data: { isProfileUpdated: true }
data: { isProfileUpdated: true },
});
}
@@ -477,12 +545,12 @@ export class MinglarService {
select: {
id: true,
roleName: true,
}
}
}
}
}
})
},
},
},
},
},
});
}
async getAllHostApplications(
@@ -609,10 +677,10 @@ export class MinglarService {
where: {
roleXid: {
in: [
ROLE.MINGLAR_ADMIN, // Admin
ROLE.CO_ADMIN, // Co-Admin
ROLE.ACCOUNT_MANAGER // AM
]
ROLE.MINGLAR_ADMIN, // Admin
ROLE.CO_ADMIN, // Co-Admin
ROLE.ACCOUNT_MANAGER, // AM
],
},
isActive: true,
userStatus: USER_STATUS.ACTIVE,
@@ -633,9 +701,9 @@ export class MinglarService {
// 2. Count assigned hosts for ANY user (Admin / Co-Admin / AM)
const groupedHosts = await this.prisma.hostHeader.groupBy({
by: ["accountManagerXid"],
by: ['accountManagerXid'],
where: {
accountManagerXid: { in: userIds }, // assigned user
accountManagerXid: { in: userIds }, // assigned user
isActive: true,
},
_count: {
@@ -662,14 +730,14 @@ export class MinglarService {
where: {
roleXid: {
in: [
ROLE.MINGLAR_ADMIN, // Admin
ROLE.CO_ADMIN, // Co-Admin
ROLE.ACCOUNT_MANAGER // AM
]
ROLE.MINGLAR_ADMIN, // Admin
ROLE.CO_ADMIN, // Co-Admin
ROLE.ACCOUNT_MANAGER, // AM
],
},
isActive: true,
userStatus: {
not: USER_STATUS.DE_ACTIVATED // Exclude DE_ACTIVATED status
not: USER_STATUS.DE_ACTIVATED, // Exclude DE_ACTIVATED status
},
},
include: {
@@ -683,11 +751,14 @@ export class MinglarService {
});
}
async assignAMToHost(userId: number, hostXid: number, accountManagerXid: number) {
async assignAMToHost(
userId: number,
hostXid: number,
accountManagerXid: number,
) {
const hostDetails = await this.prisma.hostHeader.findFirst({
where: { id: hostXid },
})
});
if (!hostDetails) {
throw new ApiError(404, 'Host not found');
@@ -697,8 +768,11 @@ export class MinglarService {
throw new ApiError(400, 'AM already assigned to this host');
}
if (hostDetails.adminStatusInternal !== MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED &&
hostDetails.adminStatusDisplay !== MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED) {
if (
hostDetails.adminStatusInternal !==
MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED &&
hostDetails.adminStatusDisplay !== MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED
) {
throw new ApiError(400, 'Invalid host status');
}
@@ -711,7 +785,7 @@ export class MinglarService {
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
}
},
});
return true;
}
@@ -729,7 +803,9 @@ export class MinglarService {
});
if (!amUser || !amUser.emailAddress) {
console.warn(`AM notification skipped: user not found or missing email for id=${accountManagerXid}`);
console.warn(
`AM notification skipped: user not found or missing email for id=${accountManagerXid}`,
);
return false;
}
@@ -742,13 +818,18 @@ export class MinglarService {
}
}
async addHostSuggestion(hostXid: number, title: string, comments: string, reviewedByXid: number) {
async addHostSuggestion(
hostXid: number,
title: string,
comments: string,
reviewedByXid: number,
) {
// Check if host exists
const hostHeader = await this.prisma.hostHeader.findUnique({
where: { id: hostXid },
select: { id: true }
select: { id: true },
});
console.log(hostHeader)
console.log(hostHeader);
if (!hostHeader) {
throw new ApiError(404, 'Host not found');
@@ -764,18 +845,46 @@ export class MinglarService {
isreviewed: false,
reviewedByXid: reviewedByXid,
reviewOn: null,
isActive: true
}
isActive: true,
},
});
return true;
}
async addPqqSuggestion(
title: string,
comments: string,
activity_pqq_header_xid: number,
) {
// Check if host exists
const ActivityHeader = await this.prisma.activityPQQheader.findUnique({
where: { id: activity_pqq_header_xid },
select: { id: true },
});
if (!ActivityHeader) {
throw new ApiError(404, 'Host not found');
}
await this.prisma.activityPQQSuggestions.create({
data: {
title: title,
comments: comments,
isReviewed: false,
reviewedOn: null,
isActive: true,
activityPqqHeaderXid: activity_pqq_header_xid,
reviewedByXid: null,
},
});
return true;
}
async getHostSuggestions(userId: number) {
const hostDetail = await this.prisma.hostHeader.findFirst({
where: { userXid: userId, isActive: true }
})
where: { userXid: userId, isActive: true },
});
const suggestions = await this.prisma.hostSuggestion.findMany({
where: { hostXid: hostDetail.id, isreviewed: false, isActive: true },
@@ -788,8 +897,8 @@ export class MinglarService {
reviewOn: true,
},
orderBy: {
id: 'asc'
}
id: 'asc',
},
});
return suggestions;
@@ -804,7 +913,7 @@ export class MinglarService {
amountPerBooking: number,
durationFrequency: string,
payoutDurationNum: number,
payoutDurationFrequency: string
payoutDurationFrequency: string,
) {
return await this.prisma.hostHeader.update({
where: { id: host_xid },
@@ -816,9 +925,9 @@ export class MinglarService {
commisionPer: commisionPer ? Number(commisionPer) : null, // Convert to number if exists
amountPerBooking: amountPerBooking ? Number(amountPerBooking) : null, // Convert to number if exists
payoutDurationNum: Number(payoutDurationNum), // Convert to number
payoutDurationFrequency: payoutDurationFrequency
}
})
payoutDurationFrequency: payoutDurationFrequency,
},
});
}
async acceptHostApplication(host_xid: number, user_xid: number) {
@@ -828,19 +937,18 @@ export class MinglarService {
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
},
data: {
hostStatusInternal: HOST_STATUS_INTERNAL.APPROVED,
hostStatusDisplay: HOST_STATUS_DISPLAY.APPROVED,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_APPROVED,
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.APPROVED,
stepper: STEPPER.COMPANY_DETAILS_APPROVED
}
})
stepper: STEPPER.COMPANY_DETAILS_APPROVED,
},
});
}
async acceptHostApplicationMinglarAdmin(host_xid: number, user_xid: number) {
return await this.prisma.hostHeader.update({
where: {
@@ -848,7 +956,7 @@ export class MinglarService {
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW,
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.NEW
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.NEW,
},
data: {
isApproved: true,
@@ -856,68 +964,62 @@ export class MinglarService {
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.AM_NOT_ASSIGNED,
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.AM_NOT_ASSIGNED,
}
})
},
});
}
async rejectHostApplication(host_xid: number, user_xid: number) {
await this.prisma.$transaction(async (tx) => {
const hostDetails = await tx.hostHeader.findFirst({
where: { id: host_xid },
select: { id: true, userXid: true }
})
select: { id: true, userXid: true },
});
if (!hostDetails) {
throw new Error("Host not found");
throw new Error('Host not found');
}
await tx.hostHeader.update({
where: {
id: host_xid,
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW
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,
}
})
},
});
await tx.user.update({
where: { id: hostDetails.userXid },
data: {
userStatus: USER_STATUS.REJECTED
}
})
})
userStatus: USER_STATUS.REJECTED,
},
});
});
}
async rejectHostApplicationAM(host_xid: number, user_xid: number) {
const hostDetails = await this.prisma.hostHeader.findFirst({
where: { id: host_xid },
select: { id: true, userXid: true }
})
select: { id: true, userXid: true },
});
if (!hostDetails) {
throw new Error("Host not found");
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
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,
}
})
},
});
}
}