Compare commits
12 Commits
split-serv
...
bb5da7647b
| Author | SHA1 | Date | |
|---|---|---|---|
| bb5da7647b | |||
| 3f19bb4087 | |||
| be8b9cef7d | |||
|
|
77cef98091 | ||
| 97f9c2b26e | |||
|
|
b93cd6b32c | ||
|
|
51319a69fc | ||
| 5ad46309ef | |||
|
|
781212277a | ||
| 6b0ee461c5 | |||
| cc2fa3eb6b | |||
| fe6bb59cc7 |
@@ -554,20 +554,6 @@ model Frequencies {
|
|||||||
@@schema("mst")
|
@@schema("mst")
|
||||||
}
|
}
|
||||||
|
|
||||||
model NavigationModes {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
navigationModeName String @unique @map("navigation_mode_name") @db.VarChar(30)
|
|
||||||
navigationModeIcon String @map("navigation_mode_icon") @db.VarChar(500)
|
|
||||||
isActive Boolean @default(true) @map("is_active")
|
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
|
||||||
deletedAt DateTime? @map("deleted_at")
|
|
||||||
ActivityNavigationModes ActivityNavigationModes[]
|
|
||||||
|
|
||||||
@@map("navigation_modes")
|
|
||||||
@@schema("mst")
|
|
||||||
}
|
|
||||||
|
|
||||||
model TransportModes {
|
model TransportModes {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
transportModeName String @unique @map("transport_mode_name") @db.VarChar(60)
|
transportModeName String @unique @map("transport_mode_name") @db.VarChar(60)
|
||||||
@@ -1456,8 +1442,7 @@ model ActivityNavigationModes {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
activityXid Int @map("activity_xid")
|
activityXid Int @map("activity_xid")
|
||||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||||
navigationModeXid Int @map("navigation_mode_xid")
|
navigationModeName String @map("navigation_mode_name") @db.VarChar(30)
|
||||||
navigationMode NavigationModes @relation(fields: [navigationModeXid], references: [id], onDelete: Restrict)
|
|
||||||
isInActivityChargeable Boolean @default(false) @map("is_in_activity_chargeable")
|
isInActivityChargeable Boolean @default(false) @map("is_in_activity_chargeable")
|
||||||
navigationModesBasePrice Int @map("navigation_modes_base_price")
|
navigationModesBasePrice Int @map("navigation_modes_base_price")
|
||||||
navigationModesTotalPrice Int @map("navigation_modes_total_price")
|
navigationModesTotalPrice Int @map("navigation_modes_total_price")
|
||||||
@@ -1637,8 +1622,8 @@ model Cancellations {
|
|||||||
scheduleHeaderXid Int @map("schedule_header_xid")
|
scheduleHeaderXid Int @map("schedule_header_xid")
|
||||||
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
|
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
|
||||||
occurenceDate DateTime? @map("occurence_date")
|
occurenceDate DateTime? @map("occurence_date")
|
||||||
startTime String? @map("start_time") @db.VarChar(30)
|
startTime String? @map("start_time") @db.VarChar(30)
|
||||||
endTime String? @map("end_time") @db.VarChar(30)
|
endTime String? @map("end_time") @db.VarChar(30)
|
||||||
cancellationReason String? @map("cancellation_reason")
|
cancellationReason String? @map("cancellation_reason")
|
||||||
isActive Boolean @default(true) @map("is_active")
|
isActive Boolean @default(true) @map("is_active")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|||||||
@@ -268,9 +268,9 @@ async function main() {
|
|||||||
create: { interestName: 'Nightlife & Events', displayOrder: 10, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/NightlifeandEvents.png', interestCode: 'NE' },
|
create: { interestName: 'Nightlife & Events', displayOrder: 10, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/NightlifeandEvents.png', interestCode: 'NE' },
|
||||||
});
|
});
|
||||||
const furfam = await prisma.interests.upsert({
|
const furfam = await prisma.interests.upsert({
|
||||||
where: { interestName: 'Fur Fam' },
|
where: { interestName: 'Pet space' },
|
||||||
update: {},
|
update: {},
|
||||||
create: { interestName: 'Fur Fam', displayOrder: 11, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/petspace.jpg', interestCode: 'PS' },
|
create: { interestName: 'Pet space', displayOrder: 11, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/petspace.jpg', interestCode: 'PS' },
|
||||||
});
|
});
|
||||||
const dogoodfeelgood = await prisma.interests.upsert({
|
const dogoodfeelgood = await prisma.interests.upsert({
|
||||||
where: { interestName: 'Do Good, Feel Good' },
|
where: { interestName: 'Do Good, Feel Good' },
|
||||||
@@ -693,16 +693,6 @@ async function main() {
|
|||||||
skipDuplicates: true,
|
skipDuplicates: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ Navigation Modes
|
|
||||||
await prisma.navigationModes.createMany({
|
|
||||||
data: [
|
|
||||||
{ navigationModeName: 'Elephant Ride', navigationModeIcon: '🚗' },
|
|
||||||
{ navigationModeName: 'Horse Ride', navigationModeIcon: '🏍️' },
|
|
||||||
{ navigationModeName: 'Camel Ride', navigationModeIcon: '🚶' },
|
|
||||||
],
|
|
||||||
skipDuplicates: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Transport Modes
|
// ✅ Transport Modes
|
||||||
await prisma.transportModes.createMany({
|
await prisma.transportModes.createMany({
|
||||||
data: [
|
data: [
|
||||||
|
|||||||
@@ -258,6 +258,22 @@ acceptAggrement:
|
|||||||
path: /host/Host_Admin/onboarding/accept-agreement
|
path: /host/Host_Admin/onboarding/accept-agreement
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
|
getLatestAgreement:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/getLatestAgreement.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Host_Admin/onboarding/getLatestAgreement.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/get-latest-agreement
|
||||||
|
method: get
|
||||||
|
|
||||||
getStepperInfo:
|
getStepperInfo:
|
||||||
handler: src/modules/host/handlers/getStepper.handler
|
handler: src/modules/host/handlers/getStepper.handler
|
||||||
memorySize: 384
|
memorySize: 384
|
||||||
@@ -276,6 +292,22 @@ getStepperInfo:
|
|||||||
path: /stepper
|
path: /stepper
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
|
updateHostProfile:
|
||||||
|
handler: src/modules/host/handlers/updateHostProfile.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/updateHostProfile.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/profile
|
||||||
|
method: patch
|
||||||
|
|
||||||
# Functions with S3/AWS SDK dependencies
|
# Functions with S3/AWS SDK dependencies
|
||||||
submitCompanyDetails:
|
submitCompanyDetails:
|
||||||
handler: src/modules/host/handlers/Host_Admin/onboarding/submitCompanyDetails.handler
|
handler: src/modules/host/handlers/Host_Admin/onboarding/submitCompanyDetails.handler
|
||||||
|
|||||||
@@ -346,4 +346,19 @@ getNearbyActivities:
|
|||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /user/activities/get-nearby-activities
|
path: /user/activities/get-nearby-activities
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
|
addActivityToBucketInterested:
|
||||||
|
handler: src/modules/user/handlers/activities/addToBucketInterested.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/handlers/activities/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /user/activities/add-to-bucket-interested
|
||||||
|
method: post
|
||||||
@@ -54,7 +54,7 @@ export const EquipmentDto = z.object({
|
|||||||
|
|
||||||
/* ================= NAVIGATION MODE ================= */
|
/* ================= NAVIGATION MODE ================= */
|
||||||
export const NavigationModeDto = z.object({
|
export const NavigationModeDto = z.object({
|
||||||
navigationModeXid: z.number().int(),
|
navigationModeName: z.string().optional(),
|
||||||
isChargeable: z.boolean().optional(),
|
isChargeable: z.boolean().optional(),
|
||||||
totalPrice: z.number().int().optional().default(0),
|
totalPrice: z.number().int().optional().default(0),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
|
||||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
import { HostService } from '../../../services/host.service';
|
import { HostService } from '../../../services/host.service';
|
||||||
@@ -25,9 +25,8 @@ export const handler = safeHandler(async (
|
|||||||
// Verify token and get user info
|
// Verify token and get user info
|
||||||
const userInfo = await verifyHostToken(token);
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
// Accept agreement and get dynamic fields and PDF URL
|
||||||
// Add suggestion using service
|
const result = await hostService.acceptMinglarAgreement(userInfo.id);
|
||||||
await hostService.acceptMinglarAgreement(userInfo.id);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
@@ -38,7 +37,10 @@ export const handler = safeHandler(async (
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Application accepted successfully',
|
message: 'Application accepted successfully',
|
||||||
data: null,
|
data: {
|
||||||
|
filePath: result.filePath,
|
||||||
|
dynamicFields: result.dynamicFields,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get latest active agreement for a specific host by hostXid.
|
||||||
|
* Accessible for Minglar Admin / Host Admin using admin-host token.
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate admin/host admin token
|
||||||
|
await verifyMinglarAdminHostToken(token);
|
||||||
|
|
||||||
|
const hostXidParam =
|
||||||
|
event.queryStringParameters?.hostXid ?? event.queryStringParameters?.host_xid;
|
||||||
|
|
||||||
|
const hostXid = Number(hostXidParam);
|
||||||
|
|
||||||
|
if (!hostXidParam) {
|
||||||
|
throw new ApiError(400, 'hostXid is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isNaN(hostXid)) {
|
||||||
|
throw new ApiError(400, 'Invalid hostXid format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const agreement = await hostService.getLatestHostAgreement(hostXid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Latest host agreement retrieved successfully',
|
||||||
|
data: agreement,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ export const handler = safeHandler(async (
|
|||||||
data: {
|
data: {
|
||||||
stepper: host?.host?.stepper || null,
|
stepper: host?.host?.stepper || null,
|
||||||
emailAddress: host.user?.emailAddress || null,
|
emailAddress: host.user?.emailAddress || null,
|
||||||
|
hostId: host.user?.userRefNumber || null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
244
src/modules/host/handlers/updateHostProfile.ts
Normal file
244
src/modules/host/handlers/updateHostProfile.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { prismaClient } from '../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../common/utils/helper/ApiError';
|
||||||
|
import { ROLE } from '../../../common/utils/constants/common.constant';
|
||||||
|
|
||||||
|
const updateHostProfileSchema = z
|
||||||
|
.strictObject({
|
||||||
|
// Personal
|
||||||
|
fullName: z.string().min(1).optional(),
|
||||||
|
firstName: z.string().min(1).optional(),
|
||||||
|
lastName: z.string().min(1).optional(),
|
||||||
|
isdCode: z.string().min(1).max(6).optional(),
|
||||||
|
mobileNumber: z.string().min(5).max(15).optional(),
|
||||||
|
dateOfBirth: z.string().min(1).optional(),
|
||||||
|
|
||||||
|
// Address
|
||||||
|
address1: z.string().min(1).optional(),
|
||||||
|
address2: z.string().min(1).optional(),
|
||||||
|
countryXid: z.number().int().positive().optional(),
|
||||||
|
stateXid: z.number().int().positive().optional(),
|
||||||
|
cityXid: z.number().int().positive().optional(),
|
||||||
|
pinCode: z.string().min(1).optional(),
|
||||||
|
|
||||||
|
// explicitly forbidden
|
||||||
|
emailAddress: z.any().optional(),
|
||||||
|
})
|
||||||
|
.strip();
|
||||||
|
|
||||||
|
function parseDob(dateOfBirth: string): Date {
|
||||||
|
const parsed = dayjs(dateOfBirth, ['YYYY-MM-DD', 'MM/DD/YYYY', 'DD/MM/YYYY'], true);
|
||||||
|
if (!parsed.isValid()) {
|
||||||
|
throw new ApiError(400, 'Invalid dateOfBirth. Use YYYY-MM-DD (recommended) or MM/DD/YYYY.');
|
||||||
|
}
|
||||||
|
return parsed.toDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitFullName(fullName: string): { firstName: string; lastName: string | null } {
|
||||||
|
const parts = fullName.trim().split(/\s+/).filter(Boolean);
|
||||||
|
const firstName = parts[0] || '';
|
||||||
|
const lastName = parts.length > 1 ? parts.slice(1).join(' ') : null;
|
||||||
|
return { firstName, lastName };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthToken(event: APIGatewayProxyEvent): string {
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonBody(event: APIGatewayProxyEvent): any {
|
||||||
|
try {
|
||||||
|
return event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateBody(body: any) {
|
||||||
|
const parsed = updateHostProfileSchema.safeParse(body);
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw new ApiError(400, parsed.error.issues.map((i) => i.message).join(', '));
|
||||||
|
}
|
||||||
|
if (parsed.data.emailAddress !== undefined) {
|
||||||
|
throw new ApiError(400, 'Email address cannot be updated.');
|
||||||
|
}
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeNameFields(data: any): { firstName?: string; lastName?: string | null } {
|
||||||
|
if (data.fullName && !data.firstName && !data.lastName) {
|
||||||
|
const split = splitFullName(data.fullName);
|
||||||
|
return { firstName: split.firstName, lastName: split.lastName };
|
||||||
|
}
|
||||||
|
return { firstName: data.firstName, lastName: data.lastName };
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAddressInput(data: any) {
|
||||||
|
return {
|
||||||
|
address1: data.address1,
|
||||||
|
address2: data.address2,
|
||||||
|
countryXid: data.countryXid,
|
||||||
|
stateXid: data.stateXid,
|
||||||
|
cityXid: data.cityXid,
|
||||||
|
pinCode: data.pinCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasAnyDefined(obj: Record<string, unknown>) {
|
||||||
|
return Object.values(obj).some((v) => v !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureHostUser(tx: any, userId: number) {
|
||||||
|
const user = await tx.user.findUnique({
|
||||||
|
where: { id: userId, isActive: true },
|
||||||
|
select: { id: true, roleXid: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) throw new ApiError(404, 'User not found');
|
||||||
|
if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserIfNeeded(tx: any, userId: number, input: { firstName?: string; lastName?: string | null; isdCode?: string; mobileNumber?: string; dateOfBirth?: string }) {
|
||||||
|
const userUpdateData: any = {};
|
||||||
|
if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null;
|
||||||
|
if (input.lastName !== undefined) userUpdateData.lastName = input.lastName;
|
||||||
|
if (input.isdCode !== undefined) userUpdateData.isdCode = input.isdCode || null;
|
||||||
|
if (input.mobileNumber !== undefined) userUpdateData.mobileNumber = input.mobileNumber || null;
|
||||||
|
if (input.dateOfBirth !== undefined) {
|
||||||
|
userUpdateData.dateOfBirth = input.dateOfBirth ? parseDob(input.dateOfBirth) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAnyDefined(userUpdateData)) return;
|
||||||
|
|
||||||
|
await tx.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
...userUpdateData,
|
||||||
|
isProfileUpdated: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upsertAddressIfNeeded(tx: any, userId: number, addressData: Record<string, any>) {
|
||||||
|
if (!hasAnyDefined(addressData)) return;
|
||||||
|
|
||||||
|
const existingAddress = await tx.userAddressDetails.findFirst({
|
||||||
|
where: { userXid: userId, isActive: true },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addressUpdateData: any = {};
|
||||||
|
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
|
||||||
|
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
|
||||||
|
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
|
||||||
|
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const required = ['address1', 'countryXid', 'stateXid', 'cityXid', 'pinCode'] as const;
|
||||||
|
const missing = required.filter((k) => addressData[k] === undefined);
|
||||||
|
if (missing.length) {
|
||||||
|
throw new ApiError(400, `Missing required address fields: ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.userAddressDetails.create({
|
||||||
|
data: {
|
||||||
|
userXid: userId,
|
||||||
|
...addressUpdateData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProfileSnapshot(tx: any, userId: number) {
|
||||||
|
const updated = await tx.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
emailAddress: true,
|
||||||
|
isdCode: true,
|
||||||
|
mobileNumber: true,
|
||||||
|
dateOfBirth: true,
|
||||||
|
profileImage: true,
|
||||||
|
isProfileUpdated: true,
|
||||||
|
userAddressDetails: {
|
||||||
|
where: { isActive: true },
|
||||||
|
take: 1,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
address1: true,
|
||||||
|
address2: true,
|
||||||
|
countryXid: true,
|
||||||
|
stateXid: true,
|
||||||
|
cityXid: true,
|
||||||
|
pinCode: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: updated,
|
||||||
|
address: updated?.userAddressDetails?.[0] ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = getAuthToken(event);
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
if (!userId || Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user id');
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = parseJsonBody(event);
|
||||||
|
const data = validateBody(body);
|
||||||
|
const name = normalizeNameFields(data);
|
||||||
|
const address = buildAddressInput(data);
|
||||||
|
|
||||||
|
const result = await prismaClient.$transaction(async (tx) => {
|
||||||
|
await ensureHostUser(tx, userId);
|
||||||
|
await updateUserIfNeeded(tx, userId, {
|
||||||
|
firstName: name.firstName,
|
||||||
|
lastName: name.lastName,
|
||||||
|
isdCode: data.isdCode,
|
||||||
|
mobileNumber: data.mobileNumber,
|
||||||
|
dateOfBirth: data.dateOfBirth,
|
||||||
|
});
|
||||||
|
await upsertAddressIfNeeded(tx, userId, address);
|
||||||
|
return getProfileSnapshot(tx, userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Profile updated successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -391,6 +391,22 @@ const s3 = new AWS.S3({
|
|||||||
region: config.aws.region,
|
region: config.aws.region,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type UpdateHostProfileInput = {
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string | null;
|
||||||
|
isdCode?: string;
|
||||||
|
mobileNumber?: string;
|
||||||
|
dateOfBirth?: Date;
|
||||||
|
address?: {
|
||||||
|
address1?: string;
|
||||||
|
address2?: string;
|
||||||
|
countryXid?: number;
|
||||||
|
stateXid?: number;
|
||||||
|
cityXid?: number;
|
||||||
|
pinCode?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HostService {
|
export class HostService {
|
||||||
constructor(private prisma: PrismaClient) { }
|
constructor(private prisma: PrismaClient) { }
|
||||||
@@ -415,8 +431,8 @@ export class HostService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id: user_xid },
|
where: { id: user_xid, isActive: true },
|
||||||
select: { id: true, emailAddress: true },
|
select: { id: true, emailAddress: true, userRefNumber: true },
|
||||||
});
|
});
|
||||||
return { host, user };
|
return { host, user };
|
||||||
}
|
}
|
||||||
@@ -465,6 +481,38 @@ export class HostService {
|
|||||||
profileImage: true,
|
profileImage: true,
|
||||||
userStatus: true,
|
userStatus: true,
|
||||||
userRefNumber: true,
|
userRefNumber: true,
|
||||||
|
userAddressDetails: {
|
||||||
|
where: { isActive: true },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
address1: true,
|
||||||
|
address2: true,
|
||||||
|
locationAddress: true,
|
||||||
|
locationLat: true,
|
||||||
|
locationLong: true,
|
||||||
|
cityXid: true,
|
||||||
|
cities: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
cityName: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stateXid: true,
|
||||||
|
states: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
stateName: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
countryXid: true,
|
||||||
|
country:{
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
countryName: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
companyTypes: {
|
companyTypes: {
|
||||||
@@ -577,6 +625,114 @@ export class HostService {
|
|||||||
return this.prisma.user.delete({ where: { id } });
|
return this.prisma.user.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the logged-in Host's personal profile details.
|
||||||
|
* Email is intentionally NOT editable here.
|
||||||
|
*/
|
||||||
|
async updateHostProfileDetails(userId: number, input: UpdateHostProfileInput) {
|
||||||
|
return this.prisma.$transaction(async (tx) => {
|
||||||
|
const user = await tx.user.findUnique({
|
||||||
|
where: { id: userId, isActive: true },
|
||||||
|
select: { id: true, roleXid: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) throw new ApiError(404, 'User not found');
|
||||||
|
if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.');
|
||||||
|
|
||||||
|
// 1) Update `User` (whitelist only)
|
||||||
|
const userUpdateData: any = {};
|
||||||
|
if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null;
|
||||||
|
if (input.lastName !== undefined) userUpdateData.lastName = input.lastName;
|
||||||
|
if (input.isdCode !== undefined) userUpdateData.isdCode = input.isdCode || null;
|
||||||
|
if (input.mobileNumber !== undefined) userUpdateData.mobileNumber = input.mobileNumber || null;
|
||||||
|
if (input.dateOfBirth !== undefined) userUpdateData.dateOfBirth = input.dateOfBirth;
|
||||||
|
|
||||||
|
if (Object.keys(userUpdateData).length > 0) {
|
||||||
|
await tx.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
...userUpdateData,
|
||||||
|
isProfileUpdated: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Update/Create `UserAddressDetails` (if any address field sent)
|
||||||
|
const addressData = input.address || {};
|
||||||
|
const hasAnyAddressField = Object.values(addressData).some((v) => v !== undefined);
|
||||||
|
|
||||||
|
if (hasAnyAddressField) {
|
||||||
|
const existingAddress = await tx.userAddressDetails.findFirst({
|
||||||
|
where: { userXid: userId, isActive: true },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addressUpdateData: any = {};
|
||||||
|
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
|
||||||
|
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
|
||||||
|
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
|
||||||
|
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
|
||||||
|
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 {
|
||||||
|
const required = ['address1', 'countryXid', 'stateXid', 'cityXid', 'pinCode'] as const;
|
||||||
|
const missing = required.filter((k) => addressData[k] === undefined);
|
||||||
|
|
||||||
|
if (missing.length) {
|
||||||
|
throw new ApiError(400, `Missing required address fields: ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.userAddressDetails.create({
|
||||||
|
data: {
|
||||||
|
userXid: userId,
|
||||||
|
...addressUpdateData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Return updated profile snapshot (including read-only email)
|
||||||
|
const updated = await tx.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
emailAddress: true,
|
||||||
|
isdCode: true,
|
||||||
|
mobileNumber: true,
|
||||||
|
dateOfBirth: true,
|
||||||
|
profileImage: true,
|
||||||
|
isProfileUpdated: true,
|
||||||
|
userAddressDetails: {
|
||||||
|
where: { isActive: true },
|
||||||
|
take: 1,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
address1: true,
|
||||||
|
address2: true,
|
||||||
|
countryXid: true,
|
||||||
|
stateXid: true,
|
||||||
|
cityXid: true,
|
||||||
|
pinCode: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: updated,
|
||||||
|
address: updated?.userAddressDetails?.[0] ?? null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getHostByEmail(email: string): Promise<User> {
|
async getHostByEmail(email: string): Promise<User> {
|
||||||
return this.prisma.user.findUnique({ where: { emailAddress: email } });
|
return this.prisma.user.findUnique({ where: { emailAddress: email } });
|
||||||
}
|
}
|
||||||
@@ -919,55 +1075,120 @@ export class HostService {
|
|||||||
acceptDate,
|
acceptDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
const pdfBuffer = await renderAgreementPdf(agreementVars);
|
let pdfUrl: string | null = null;
|
||||||
|
|
||||||
const existingCount = await this.prisma.hostAgreement.count({
|
try {
|
||||||
where: { hostXid: host.id, isActive: true },
|
const pdfBuffer = await renderAgreementPdf(agreementVars);
|
||||||
});
|
|
||||||
|
|
||||||
const nextVersionNumber = `AG${existingCount + 1}`;
|
const existingCount = await this.prisma.hostAgreement.count({
|
||||||
const baseKey = `Documents/Host/${host.id}/agreements/${nextVersionNumber}`;
|
|
||||||
|
|
||||||
const pdfKey = `${baseKey}.pdf`;
|
|
||||||
|
|
||||||
await s3
|
|
||||||
.upload({
|
|
||||||
Bucket: config.aws.bucketName,
|
|
||||||
Key: pdfKey,
|
|
||||||
Body: pdfBuffer,
|
|
||||||
ContentType: 'application/pdf',
|
|
||||||
ACL: 'private',
|
|
||||||
})
|
|
||||||
.promise();
|
|
||||||
|
|
||||||
const pdfUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${pdfKey}`;
|
|
||||||
|
|
||||||
await this.prisma.$transaction(async (tx) => {
|
|
||||||
// Optional: mark previous agreements inactive
|
|
||||||
await tx.hostAgreement.updateMany({
|
|
||||||
where: { hostXid: host.id, isActive: true },
|
where: { hostXid: host.id, isActive: true },
|
||||||
data: { isActive: false },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.hostAgreement.create({
|
const nextVersionNumber = `AG${existingCount + 1}`;
|
||||||
data: {
|
const baseKey = `Documents/Host/${host.id}/agreements/${nextVersionNumber}`;
|
||||||
hostXid: host.id,
|
|
||||||
filePath: pdfUrl,
|
const pdfKey = `${baseKey}.pdf`;
|
||||||
versionNumber: nextVersionNumber,
|
|
||||||
isActive: true,
|
await s3
|
||||||
},
|
.upload({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: pdfKey,
|
||||||
|
Body: pdfBuffer,
|
||||||
|
ContentType: 'application/pdf',
|
||||||
|
ACL: 'private',
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
|
pdfUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${pdfKey}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating or uploading PDF:', error);
|
||||||
|
// Continue without PDF - will return dynamic fields instead
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingCount = await this.prisma.hostAgreement.count({
|
||||||
|
where: { hostXid: host.id, isActive: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.hostHeader.update({
|
const nextVersionNumber = `AG${existingCount + 1}`;
|
||||||
where: { id: host.id },
|
|
||||||
data: {
|
await this.prisma.$transaction(async (tx) => {
|
||||||
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
// Optional: mark previous agreements inactive
|
||||||
isApproved: true,
|
await tx.hostAgreement.updateMany({
|
||||||
agreementAccepted: true,
|
where: { hostXid: host.id, isActive: true },
|
||||||
agreementStartDate: host.agreementStartDate || new Date(),
|
data: { isActive: false },
|
||||||
},
|
});
|
||||||
|
|
||||||
|
await tx.hostAgreement.create({
|
||||||
|
data: {
|
||||||
|
hostXid: host.id,
|
||||||
|
filePath: pdfUrl,
|
||||||
|
versionNumber: nextVersionNumber,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.hostHeader.update({
|
||||||
|
where: { id: host.id },
|
||||||
|
data: {
|
||||||
|
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
||||||
|
isApproved: true,
|
||||||
|
agreementAccepted: true,
|
||||||
|
agreementStartDate: host.agreementStartDate || new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating host agreement record:', error);
|
||||||
|
// Continue without creating agreement record - will return dynamic fields instead
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return dynamic fields and PDF URL
|
||||||
|
return {
|
||||||
|
filePath: pdfUrl,
|
||||||
|
dynamicFields: agreementVars,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest (active) agreement for a specific host by hostXid.
|
||||||
|
*/
|
||||||
|
async getLatestHostAgreement(hostXid: number) {
|
||||||
|
if (!hostXid || Number.isNaN(hostXid)) {
|
||||||
|
throw new ApiError(400, 'Valid hostXid is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const agreement = await this.prisma.hostAgreement.findFirst({
|
||||||
|
where: { hostXid, isActive: true },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
hostXid: true,
|
||||||
|
filePath: true,
|
||||||
|
versionNumber: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!agreement) {
|
||||||
|
throw new ApiError(404, 'No active agreement found for this host');
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = agreement.filePath;
|
||||||
|
|
||||||
|
// If full URL is saved, extract only S3 key part
|
||||||
|
const key = filePath.startsWith('http')
|
||||||
|
? filePath.split('.com/')[1]
|
||||||
|
: filePath;
|
||||||
|
|
||||||
|
const bucket = config.aws.bucketName;
|
||||||
|
const presignedUrl = await getPresignedUrl(bucket, key);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...agreement,
|
||||||
|
presignedUrl,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPQQQuestionDetail(question_xid: number, activity_xid: number) {
|
async getPQQQuestionDetail(question_xid: number, activity_xid: number) {
|
||||||
@@ -2374,15 +2595,9 @@ export class HostService {
|
|||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
navigationModeName: true,
|
||||||
isInActivityChargeable: true,
|
isInActivityChargeable: true,
|
||||||
navigationModesTotalPrice: true,
|
navigationModesTotalPrice: true,
|
||||||
navigationMode: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
navigationModeName: true,
|
|
||||||
navigationModeIcon: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
equipmentAvailable: true,
|
equipmentAvailable: true,
|
||||||
@@ -3706,7 +3921,7 @@ export class HostService {
|
|||||||
const navMode = await tx.activityNavigationModes.create({
|
const navMode = await tx.activityNavigationModes.create({
|
||||||
data: {
|
data: {
|
||||||
activityXid,
|
activityXid,
|
||||||
navigationModeXid: mode.navigationModeXid,
|
navigationModeName: mode.navigationModeName,
|
||||||
isInActivityChargeable: isChargeable,
|
isInActivityChargeable: isChargeable,
|
||||||
navigationModesBasePrice: basePrice,
|
navigationModesBasePrice: basePrice,
|
||||||
navigationModesTotalPrice: totalPrice,
|
navigationModesTotalPrice: totalPrice,
|
||||||
|
|||||||
@@ -27,15 +27,21 @@ export const handler = safeHandler(async (
|
|||||||
// 2) Authenticate user
|
// 2) Authenticate user
|
||||||
await verifyMinglarAdminHostToken(token);
|
await verifyMinglarAdminHostToken(token);
|
||||||
|
|
||||||
// 3) Get bankXid from query params
|
// 3) Get stateXid and optional search term from query params
|
||||||
const stateXid = Number(event.queryStringParameters?.stateXid);
|
const stateXid = Number(event.queryStringParameters?.stateXid);
|
||||||
|
const search = event.queryStringParameters?.search?.trim();
|
||||||
|
|
||||||
if (!stateXid || isNaN(stateXid)) {
|
if (!stateXid || isNaN(stateXid)) {
|
||||||
throw new ApiError(400, "Valid stateXid is required in query params.");
|
throw new ApiError(400, "Valid stateXid is required in query params.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Fetch branches for the bank
|
// If search is provided, enforce minimum 3 characters
|
||||||
const branches = await prePopulateService.getCityByStateId(stateXid);
|
if (search && search.length < 3) {
|
||||||
|
throw new ApiError(400, "Search term must be at least 3 characters long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Fetch cities for the state (optionally filtered by search)
|
||||||
|
const branches = await prePopulateService.getCityByStateId(stateXid, search);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
@@ -39,12 +39,20 @@ export class PrePopulateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getCityByStateId(stateXid: number) {
|
async getCityByStateId(stateXid: number, search?: string) {
|
||||||
return await this.prisma.cities.findMany({
|
return await this.prisma.cities.findMany({
|
||||||
where: {
|
where: {
|
||||||
stateXid,
|
stateXid,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null
|
deletedAt: null,
|
||||||
|
...(search && search.length >= 3
|
||||||
|
? {
|
||||||
|
cityName: {
|
||||||
|
contains: search,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -153,7 +161,6 @@ export class PrePopulateService {
|
|||||||
foodType,
|
foodType,
|
||||||
cuisineDetails,
|
cuisineDetails,
|
||||||
vehicleType,
|
vehicleType,
|
||||||
navigationMode,
|
|
||||||
taxDetails,
|
taxDetails,
|
||||||
energyLevel,
|
energyLevel,
|
||||||
aminitiesDetails,
|
aminitiesDetails,
|
||||||
@@ -171,9 +178,6 @@ export class PrePopulateService {
|
|||||||
this.prisma.transportModes.findMany({
|
this.prisma.transportModes.findMany({
|
||||||
where: { isActive: true },
|
where: { isActive: true },
|
||||||
}),
|
}),
|
||||||
this.prisma.navigationModes.findMany({
|
|
||||||
where: { isActive: true },
|
|
||||||
}),
|
|
||||||
this.prisma.taxes.findMany({
|
this.prisma.taxes.findMany({
|
||||||
where: { isActive: true },
|
where: { isActive: true },
|
||||||
}),
|
}),
|
||||||
@@ -215,7 +219,6 @@ export class PrePopulateService {
|
|||||||
foodType,
|
foodType,
|
||||||
cuisineDetails,
|
cuisineDetails,
|
||||||
vehicleType,
|
vehicleType,
|
||||||
navigationMode,
|
|
||||||
taxDetails,
|
taxDetails,
|
||||||
energyLevel,
|
energyLevel,
|
||||||
aminitiesDetails,
|
aminitiesDetails,
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { UserService } from '../../services/user.service';
|
||||||
|
|
||||||
|
const userService = new UserService(prismaClient);
|
||||||
|
|
||||||
|
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 verifyUserToken
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = userInfo.id;
|
||||||
|
|
||||||
|
if (Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'User id must be a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await userService.getUserById(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: { activityXid: number; isBucket: boolean; bucketTypeName: string; };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { activityXid, isBucket, bucketTypeName } = body;
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (
|
||||||
|
typeof activityXid !== 'number' ||
|
||||||
|
typeof isBucket !== 'boolean' ||
|
||||||
|
!bucketTypeName
|
||||||
|
) {
|
||||||
|
throw new ApiError(400, 'Required fields missing or invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set the passcode
|
||||||
|
const counts = await userService.addToBucketInterested(userId, isBucket, bucketTypeName, activityXid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: `Activity added to ${isBucket ? 'bucket' : 'interested'} successfully`,
|
||||||
|
data: {
|
||||||
|
bucketCount: counts.bucketCount,
|
||||||
|
interestedCount: counts.interestedCount,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -45,6 +45,8 @@ export const handler = safeHandler(async (
|
|||||||
throw new ApiError(400, 'Invalid schoolCompanyXids');
|
throw new ApiError(400, 'Invalid schoolCompanyXids');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('schoolCompanyXids', schoolCompanyXids);
|
||||||
|
|
||||||
const result = await userService.getAllActivitiesFromConnectionsUserInterests(
|
const result = await userService.getAllActivitiesFromConnectionsUserInterests(
|
||||||
userId,
|
userId,
|
||||||
schoolCompanyXids,
|
schoolCompanyXids,
|
||||||
|
|||||||
@@ -709,6 +709,29 @@ export class UserService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userBucketInterested = await tx.userBucketInterested.findMany({
|
||||||
|
where: {
|
||||||
|
userXid: userId,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
activityXid: true,
|
||||||
|
isBucket: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userBucketActivityIds = userBucketInterested
|
||||||
|
.filter(u => u.isBucket)
|
||||||
|
.map(u => u.activityXid);
|
||||||
|
|
||||||
|
const userInterestedActivityIds = userBucketInterested
|
||||||
|
.filter(u => !u.isBucket)
|
||||||
|
.map(u => u.activityXid);
|
||||||
|
|
||||||
|
const allUserExcludedActivityIds = userBucketInterested.map(
|
||||||
|
u => u.activityXid,
|
||||||
|
);
|
||||||
|
|
||||||
const userConnectionDetails = await tx.connectDetails.findMany({
|
const userConnectionDetails = await tx.connectDetails.findMany({
|
||||||
where: { userXid: userId, isActive: true },
|
where: { userXid: userId, isActive: true },
|
||||||
select: {
|
select: {
|
||||||
@@ -762,6 +785,11 @@ export class UserService {
|
|||||||
activityTypeXid: {
|
activityTypeXid: {
|
||||||
in: activitiyTypesOfUserInterests.map((at) => at.id),
|
in: activitiyTypesOfUserInterests.map((at) => at.id),
|
||||||
},
|
},
|
||||||
|
id: {
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1], // prevent empty notIn issue
|
||||||
|
},
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
@@ -842,7 +870,12 @@ export class UserService {
|
|||||||
// IF user wants the standard 4-step ranking applied TO the most hyped items:
|
// IF user wants the standard 4-step ranking applied TO the most hyped items:
|
||||||
const mostHypedActivitiesRaw = await tx.activities.findMany({
|
const mostHypedActivitiesRaw = await tx.activities.findMany({
|
||||||
where: {
|
where: {
|
||||||
id: { in: mostHypedActivityIds },
|
id: {
|
||||||
|
in: mostHypedActivityIds,
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1],
|
||||||
|
},
|
||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
@@ -966,6 +999,11 @@ export class UserService {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
id: {
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1], // prevent empty notIn issue
|
||||||
|
},
|
||||||
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) },
|
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -984,6 +1022,11 @@ export class UserService {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
id: {
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1], // prevent empty notIn issue
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (effectiveCountryXid) {
|
if (effectiveCountryXid) {
|
||||||
@@ -1010,6 +1053,11 @@ export class UserService {
|
|||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
id: {
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1], // prevent empty notIn issue
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1034,6 +1082,11 @@ export class UserService {
|
|||||||
amInternalStatus:
|
amInternalStatus:
|
||||||
ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
id: {
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1], // prevent empty notIn issue
|
||||||
|
},
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -1076,6 +1129,11 @@ export class UserService {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
id: {
|
||||||
|
notIn: allUserExcludedActivityIds.length
|
||||||
|
? allUserExcludedActivityIds
|
||||||
|
: [-1], // prevent empty notIn issue
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (effectiveCountryXid) {
|
if (effectiveCountryXid) {
|
||||||
@@ -1152,6 +1210,8 @@ export class UserService {
|
|||||||
loggedInNetworkCount: 0,
|
loggedInNetworkCount: 0,
|
||||||
citiesInNetworkCount: 0,
|
citiesInNetworkCount: 0,
|
||||||
rating: 0,
|
rating: 0,
|
||||||
|
interestedCount: userInterestedActivityIds.length,
|
||||||
|
bucketCount: userBucketActivityIds.length,
|
||||||
pagination: {
|
pagination: {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
@@ -1237,6 +1297,32 @@ export class UserService {
|
|||||||
|
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const userBucketInterested = await tx.userBucketInterested.findMany({
|
||||||
|
where: {
|
||||||
|
userXid: userId,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
activityXid: true,
|
||||||
|
isBucket: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const bucketActivityIds = userBucketInterested
|
||||||
|
.filter(u => u.isBucket)
|
||||||
|
.map(u => u.activityXid);
|
||||||
|
|
||||||
|
const interestedActivityIds = userBucketInterested
|
||||||
|
.filter(u => !u.isBucket)
|
||||||
|
.map(u => u.activityXid);
|
||||||
|
|
||||||
|
const excludedActivityIds = userBucketInterested.map(
|
||||||
|
u => u.activityXid,
|
||||||
|
);
|
||||||
|
|
||||||
|
const safeExcludedIds =
|
||||||
|
excludedActivityIds.length > 0 ? excludedActivityIds : [-1];
|
||||||
|
|
||||||
/* =====================================================
|
/* =====================================================
|
||||||
CONNECTION INTEREST MAP
|
CONNECTION INTEREST MAP
|
||||||
===================================================== */
|
===================================================== */
|
||||||
@@ -1270,7 +1356,6 @@ export class UserService {
|
|||||||
where: {
|
where: {
|
||||||
userXid: { in: connectionUserIds },
|
userXid: { in: connectionUserIds },
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isBucket: true,
|
|
||||||
},
|
},
|
||||||
_count: { activityXid: true },
|
_count: { activityXid: true },
|
||||||
});
|
});
|
||||||
@@ -1302,6 +1387,9 @@ export class UserService {
|
|||||||
const otherInterestActivities = await tx.activities.findMany({
|
const otherInterestActivities = await tx.activities.findMany({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
id: { notIn: safeExcludedIds },
|
||||||
...excludeUserInterestCondition,
|
...excludeUserInterestCondition,
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
@@ -1388,7 +1476,16 @@ export class UserService {
|
|||||||
).length;
|
).length;
|
||||||
|
|
||||||
const hypedActivities = await tx.activities.findMany({
|
const hypedActivities = await tx.activities.findMany({
|
||||||
where: { id: { in: mostHypedGrouped.map((h) => h.activityXid) } },
|
where: {
|
||||||
|
id: {
|
||||||
|
in: mostHypedGrouped.map((h) => h.activityXid),
|
||||||
|
notIn: safeExcludedIds,
|
||||||
|
},
|
||||||
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
isActive: true,
|
||||||
|
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
activityTitle: true,
|
activityTitle: true,
|
||||||
@@ -1427,7 +1524,10 @@ export class UserService {
|
|||||||
5️⃣ NEW ARRIVALS
|
5️⃣ NEW ARRIVALS
|
||||||
===================================================== */
|
===================================================== */
|
||||||
const newArrivalsWhere = {
|
const newArrivalsWhere = {
|
||||||
|
id: { notIn: safeExcludedIds },
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) },
|
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) },
|
||||||
...excludeUserInterestCondition,
|
...excludeUserInterestCondition,
|
||||||
};
|
};
|
||||||
@@ -1457,6 +1557,9 @@ export class UserService {
|
|||||||
===================================================== */
|
===================================================== */
|
||||||
const otherStatesWhere: any = {
|
const otherStatesWhere: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
id: { notIn: safeExcludedIds },
|
||||||
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
...excludeUserInterestCondition,
|
...excludeUserInterestCondition,
|
||||||
};
|
};
|
||||||
if (effectiveCountryXid)
|
if (effectiveCountryXid)
|
||||||
@@ -1466,6 +1569,9 @@ export class UserService {
|
|||||||
|
|
||||||
const overseasWhere: any = {
|
const overseasWhere: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
id: { notIn: safeExcludedIds },
|
||||||
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
...excludeUserInterestCondition,
|
...excludeUserInterestCondition,
|
||||||
};
|
};
|
||||||
if (effectiveCountryXid)
|
if (effectiveCountryXid)
|
||||||
@@ -1513,6 +1619,8 @@ export class UserService {
|
|||||||
return {
|
return {
|
||||||
pagination: { page, limit },
|
pagination: { page, limit },
|
||||||
interests: interestsWithActivities,
|
interests: interestsWithActivities,
|
||||||
|
interestedCount: interestedActivityIds.length,
|
||||||
|
bucketCount: bucketActivityIds.length,
|
||||||
|
|
||||||
mostHypedActivities: {
|
mostHypedActivities: {
|
||||||
page,
|
page,
|
||||||
@@ -1743,14 +1851,7 @@ export class UserService {
|
|||||||
where: { isActive: true },
|
where: { isActive: true },
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
navigationModeXid: true,
|
navigationModeName: true,
|
||||||
navigationMode: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
navigationModeName: true,
|
|
||||||
navigationModeIcon: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isInActivityChargeable: true,
|
isInActivityChargeable: true,
|
||||||
navigationModesTotalPrice: true,
|
navigationModesTotalPrice: true,
|
||||||
},
|
},
|
||||||
@@ -1951,13 +2052,6 @@ export class UserService {
|
|||||||
|
|
||||||
const connectionUserIds = connectionUsers.map((u) => u.userXid);
|
const connectionUserIds = connectionUsers.map((u) => u.userXid);
|
||||||
|
|
||||||
const interestedCount = await tx.userBucketInterested.count({
|
|
||||||
where: {
|
|
||||||
activityXid,
|
|
||||||
isBucket: false,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectionInterestedCount = connectionUserIds.length
|
const connectionInterestedCount = connectionUserIds.length
|
||||||
? await tx.userBucketInterested.count({
|
? await tx.userBucketInterested.count({
|
||||||
@@ -1979,6 +2073,50 @@ export class UserService {
|
|||||||
(v) => v.venueCapacity ?? 0,
|
(v) => v.venueCapacity ?? 0,
|
||||||
).reduce((sum, capacity) => sum + capacity, 0);
|
).reduce((sum, capacity) => sum + capacity, 0);
|
||||||
|
|
||||||
|
const interestedCount = await tx.userBucketInterested.count({
|
||||||
|
where: {
|
||||||
|
activityXid,
|
||||||
|
isBucket: false,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const interestedUsers = await tx.userBucketInterested.findMany({
|
||||||
|
where: {
|
||||||
|
activityXid,
|
||||||
|
isBucket: false,
|
||||||
|
isActive: true,
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
profileImage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffledUsers = interestedUsers.sort(() => 0.5 - Math.random());
|
||||||
|
const randomFive = shuffledUsers.slice(0, 5);
|
||||||
|
|
||||||
|
const interestedUserImages: string[] = [];
|
||||||
|
|
||||||
|
for (const item of randomFive) {
|
||||||
|
const profileImage = item.user.profileImage;
|
||||||
|
|
||||||
|
if (profileImage) {
|
||||||
|
const key = profileImage.startsWith('http')
|
||||||
|
? new URL(profileImage).pathname.replace(/^\/+/, '')
|
||||||
|
: profileImage;
|
||||||
|
|
||||||
|
const presignedUrl = await getPresignedUrl(bucket, key);
|
||||||
|
interestedUserImages.push(presignedUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activity,
|
activity,
|
||||||
interestedCount,
|
interestedCount,
|
||||||
@@ -1987,6 +2125,7 @@ export class UserService {
|
|||||||
totalCapacity,
|
totalCapacity,
|
||||||
rating: 0, // ⭐ Placeholder, implement rating logic as needed
|
rating: 0, // ⭐ Placeholder, implement rating logic as needed
|
||||||
distance: 0,
|
distance: 0,
|
||||||
|
interestedUserImages
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2522,6 +2661,8 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('networkUsers', networkUsers);
|
||||||
|
|
||||||
if (!networkUsers.length) {
|
if (!networkUsers.length) {
|
||||||
return {
|
return {
|
||||||
interests: [],
|
interests: [],
|
||||||
@@ -2556,6 +2697,8 @@ export class UserService {
|
|||||||
|
|
||||||
const distinctInterests = networkUserInterests.map(i => i.interestXid);
|
const distinctInterests = networkUserInterests.map(i => i.interestXid);
|
||||||
|
|
||||||
|
console.log('distinctInterests', distinctInterests);
|
||||||
|
|
||||||
if (!distinctInterests.length) {
|
if (!distinctInterests.length) {
|
||||||
return {
|
return {
|
||||||
interests: [],
|
interests: [],
|
||||||
@@ -3500,4 +3643,59 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addToBucketInterested(
|
||||||
|
userXid: number,
|
||||||
|
isBucket: boolean,
|
||||||
|
bucketTypeName: string,
|
||||||
|
activityXid: number
|
||||||
|
) {
|
||||||
|
const activityExists = await this.prisma.activities.findFirst({
|
||||||
|
where: { id: activityXid, isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!activityExists) {
|
||||||
|
throw new ApiError(404, 'Activity not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await this.prisma.userBucketInterested.findFirst({
|
||||||
|
where: { userXid, activityXid },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
throw new ApiError(400, 'Activity already added');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.userBucketInterested.create({
|
||||||
|
data: {
|
||||||
|
userXid,
|
||||||
|
activityXid,
|
||||||
|
isBucket,
|
||||||
|
bucketTypeName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Get updated counts
|
||||||
|
const [bucketCount, interestedCount] = await Promise.all([
|
||||||
|
this.prisma.userBucketInterested.count({
|
||||||
|
where: {
|
||||||
|
userXid,
|
||||||
|
isBucket: true,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
this.prisma.userBucketInterested.count({
|
||||||
|
where: {
|
||||||
|
userXid,
|
||||||
|
isBucket: false,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bucketCount,
|
||||||
|
interestedCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user