sending the connection interest count and generating the activity ref number

This commit is contained in:
2026-02-27 17:24:39 +05:30
parent 85437ebc2e
commit b98b8cf864
2 changed files with 446 additions and 13 deletions

View File

@@ -61,7 +61,8 @@ interface HostDocumentInput {
export async function generateActivityRefNumber(
tx: any,
hostXid: number,
activityTypeXid: number
activityTypeXid: number,
hostRefNumber: string
) {
// 1⃣ Get ActivityType with Interest
const activityType = await tx.activityTypes.findUnique({
@@ -131,7 +132,7 @@ export async function generateActivityRefNumber(
const nextActivityTypeSequence = activityTypeCount + 1;
return `E-${interestCode}${String(interestSequence).padStart(
return `${hostRefNumber}-E-${interestCode}${String(interestSequence).padStart(
3,
"0"
)}-${String(nextActivityTypeSequence).padStart(2, "0")}`;
@@ -2763,6 +2764,18 @@ export class HostService {
frequenciesXid: number,
) {
return await this.prisma.$transaction(async (tx) => {
const hostUserDetail = await tx.user.findFirst({
where: { id: userId, isActive: true},
select: {
id: true,
userRefNumber: true,
}
})
if(!hostUserDetail) {
throw new ApiError(404, 'User not found');
}
const host = await tx.hostHeader.findFirst({
where: { userXid: userId, isActive: true },
});
@@ -2770,6 +2783,10 @@ export class HostService {
const activityType = await tx.activityTypes.findUnique({
where: { id: activityTypeXid },
include: {
interests: true, // ✅ correct
energyLevel: true, // ✅ this is correct already
},
});
if (!activityType) throw new ApiError(404, 'Activity type not found');
@@ -2780,7 +2797,7 @@ export class HostService {
if (!freq) throw new ApiError(404, 'Frequency not found');
}
const referenceNumber = await generateActivityRefNumber(tx, host.id, activityTypeXid);
const referenceNumber = await generateActivityRefNumber(tx, host.id, activityTypeXid, hostUserDetail.userRefNumber);
const created = await tx.activities.create({
data: {

View File

@@ -193,6 +193,7 @@ async function rankAndPaginateActivities(
whereClause: any,
page: number,
limit: number,
connectionInterestMap
) {
const skip = (page - 1) * limit;
@@ -330,6 +331,8 @@ async function rankAndPaginateActivities(
// activityDurationMins: activity.activityDurationMins,
// sustainabilityScore: activity.sustainabilityScore,
// cheapestPrice,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
@@ -704,6 +707,45 @@ export class UserService {
};
}
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: {
id: true,
schoolCompanyXid: true,
}
})
const otherConnectionUsers = await tx.connectDetails.findMany({
where: { userXid: { notIn: [userId] }, isActive: true, schoolCompanyXid: { in: userConnectionDetails.map((u) => u.schoolCompanyXid) } },
select: {
id: true,
userXid: true,
}
})
const connectionUserIds =
otherConnectionUsers.length > 0
? otherConnectionUsers.map(u => u.userXid)
: [-1]; // impossible user id
const connectionInterestByActivity = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
userXid: { in: connectionUserIds },
isActive: true,
},
_count: {
activityXid: true,
},
});
const connectionInterestMap = new Map(
connectionInterestByActivity.map(item => [
item.activityXid,
item._count.activityXid,
])
);
const skip = (page - 1) * limit;
/* =====================================================
@@ -890,6 +932,8 @@ export class UserService {
mostHypedSorted.map(async (activity) => ({
activityId: activity.id,
activityTitle: activity.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
hypeCount: activity.hypeCount,
energyLevel: activity.activityType.energyLevel
? {
@@ -926,6 +970,7 @@ export class UserService {
newArrivalsWhere,
page,
limit,
connectionInterestMap
);
/* =====================================================
@@ -949,7 +994,76 @@ export class UserService {
otherStatesWhere,
page,
limit,
connectionInterestMap
);
// =====================================================
// 6⃣ RANDOM ACTIVITIES (5 ONLY - SIMPLE)
// =====================================================
const totalActiveCount = await tx.activities.count({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
},
});
let randomActivities: any[] = [];
if (totalActiveCount > 0) {
const takeCount = Math.min(5, totalActiveCount);
const randomOffsets = new Set<number>();
while (randomOffsets.size < takeCount) {
randomOffsets.add(Math.floor(Math.random() * totalActiveCount));
}
const randomFetched = await Promise.all(
Array.from(randomOffsets).map((offset) =>
tx.activities.findFirst({
skip: offset,
where: {
isActive: true,
activityInternalStatus:
ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus:
ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
mediaFileName: true,
},
},
},
}),
),
);
randomActivities = await Promise.all(
randomFetched
.filter(Boolean)
.map(async (activity) => {
const cover = activity!.ActivitiesMedia?.[0];
return {
activityId: activity!.id,
activityTitle: activity!.activityTitle,
coverImage: cover?.mediaFileName ?? null,
coverImagePresignedUrl: cover?.mediaFileName
? await attachPresignedUrl(cover.mediaFileName)
: null,
};
}),
);
}
/* =====================================================
5⃣ OVERSEAS ACTIVITIES (RANKED)
@@ -969,6 +1083,7 @@ export class UserService {
overseasWhere,
page,
limit,
connectionInterestMap
);
const formattedActivities = await Promise.all(
@@ -982,6 +1097,8 @@ export class UserService {
return {
interestXid: activity.activityType.interestXid,
activityId: activity.id,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityTitle: activity.activityTitle,
activityDurationMins: activity.activityDurationMins,
sustainabilityScore: activity.sustainabilityScore,
@@ -1024,14 +1141,16 @@ export class UserService {
return {
userAddressDetails,
experiencesLogged: 25,
citiesDiscovered: 10,
experiencesLogged: 0,
citiesDiscovered: 0,
loggedInNetworkCount: 0,
citiesInNetworkCount: 0,
rating: 0,
pagination: {
page,
limit,
},
randomActivities,
interests: interestsWithActivities,
otherStatesActivities: formattedOtherStatesActivities,
overSeasActivities: formattedOverSeasActivities,
@@ -1112,6 +1231,51 @@ export class UserService {
const skip = (page - 1) * limit;
/* =====================================================
CONNECTION INTEREST MAP
===================================================== */
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: { schoolCompanyXid: true },
});
const otherConnectionUsers = await tx.connectDetails.findMany({
where: {
userXid: { not: userId },
isActive: true,
schoolCompanyXid: {
in: userConnectionDetails.map((u) => u.schoolCompanyXid),
},
},
select: { userXid: true },
});
// Prevent empty IN crash
const connectionUserIds =
otherConnectionUsers.length > 0
? otherConnectionUsers.map((u) => u.userXid)
: [-1];
// Only bucket = true (important!)
const connectionInterestByActivity =
await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
userXid: { in: connectionUserIds },
isActive: true,
isBucket: true,
},
_count: { activityXid: true },
});
const connectionInterestMap = new Map(
connectionInterestByActivity.map((item) => [
item.activityXid,
item._count.activityXid,
]),
);
/* =====================================================
3⃣ OTHER INTERESTS (GROUPED WITH ACTIVITIES)
===================================================== */
@@ -1157,6 +1321,8 @@ export class UserService {
otherInterestActivities.map(async (a) => ({
interestXid: a.activityType.interestXid,
activityId: a.id,
connectionInterestedCount:
connectionInterestMap.get(a.id) ?? 0,
activityTitle: a.activityTitle,
energyLevel: {
...a.activityType.energyLevel,
@@ -1234,6 +1400,8 @@ export class UserService {
activityId: act.id,
activityTitle: act.activityTitle,
hypeCount: g._count.activityXid,
connectionInterestedCount:
connectionInterestMap.get(act.id) ?? 0,
energyLevel: {
...act.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
@@ -1264,6 +1432,7 @@ export class UserService {
take: limit,
orderBy: { id: 'desc' },
select: {
id: true,
activityTitle: true,
activityType: { select: { energyLevel: true } },
ActivitiesMedia: {
@@ -1303,6 +1472,7 @@ export class UserService {
skip,
take: limit,
select: {
id: true,
activityTitle: true,
activityType: { select: { energyLevel: true } },
ActivitiesMedia: {
@@ -1316,6 +1486,7 @@ export class UserService {
skip,
take: limit,
select: {
id: true,
activityTitle: true,
activityType: { select: { energyLevel: true } },
ActivitiesMedia: {
@@ -1349,6 +1520,8 @@ export class UserService {
activities: await Promise.all(
newArrivalsRaw.map(async (a) => ({
activityTitle: a.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(a.id) ?? 0,
energyLevel: {
...a.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
@@ -1368,6 +1541,8 @@ export class UserService {
activities: await Promise.all(
otherStatesRaw.map(async (a) => ({
activityTitle: a.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(a.id) ?? 0,
energyLevel: {
...a.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
@@ -1387,6 +1562,8 @@ export class UserService {
activities: await Promise.all(
overseasRaw.map(async (a) => ({
activityTitle: a.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(a.id) ?? 0,
energyLevel: {
...a.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
@@ -1735,13 +1912,47 @@ export class UserService {
);
}
// 🔹 Get connection users
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: { schoolCompanyXid: true },
});
const schoolCompanyXids = userConnectionDetails.map(
(c) => c.schoolCompanyXid,
);
const connectionUsers = await tx.connectDetails.findMany({
where: {
isActive: true,
schoolCompanyXid: {
in: schoolCompanyXids.length ? schoolCompanyXids : [-1],
},
userXid: { not: userId },
},
select: { userXid: true },
});
const connectionUserIds = connectionUsers.map((u) => u.userXid);
const interestedCount = await tx.userBucketInterested.count({
where: {
activityXid,
isBucket: false,
isActive: true,
},
});
const connectionInterestedCount = connectionUserIds.length
? await tx.userBucketInterested.count({
where: {
activityXid,
userXid: { in: connectionUserIds },
isActive: true,
},
})
: 0;
const prices = activity.ActivityVenues.flatMap((v) =>
v.ActivityPrices.map((p) => p.sellPrice),
).filter((p) => p !== null) as number[];
@@ -1755,6 +1966,7 @@ export class UserService {
return {
activity,
interestedCount,
connectionInterestedCount,
cheapestPrice,
totalCapacity,
rating: 0, // ⭐ Placeholder, implement rating logic as needed
@@ -1898,6 +2110,27 @@ export class UserService {
const skip = (page - 1) * limit;
// 0.5️⃣ Get connection users
const userConnectionDetails = await this.prisma.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: { schoolCompanyXid: true },
});
const schoolCompanyXids = userConnectionDetails.map(
(c) => c.schoolCompanyXid,
);
const connectionUsers = await this.prisma.connectDetails.findMany({
where: {
isActive: true,
schoolCompanyXid: { in: schoolCompanyXids.length ? schoolCompanyXids : [-1] },
userXid: { not: userId },
},
select: { userXid: true },
});
const connectionUserIds = connectionUsers.map((u) => u.userXid);
// 0⃣ Get user's interests and map to activity types
const userInterests = await this.prisma.userInterests.findMany({
where: { userXid: userId, isActive: true },
@@ -2012,6 +2245,31 @@ export class UserService {
.filter((a) => a.distanceKm <= radiusKm)
.sort((a, b) => a.distanceKm - b.distanceKm);
const nearbyActivityIds = withDistance.map((a) => a.id);
let connectionInterestMap = new Map<number, number>();
if (nearbyActivityIds.length && connectionUserIds.length) {
const connectionInterestCounts =
await this.prisma.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
activityXid: { in: nearbyActivityIds },
userXid: { in: connectionUserIds },
isActive: true,
isBucket: true, // ✅ only real interest
},
_count: { activityXid: true },
});
connectionInterestMap = new Map(
connectionInterestCounts.map((item) => [
item.activityXid,
item._count.activityXid,
]),
);
}
const totalCount = withDistance.length;
const paged = withDistance.slice(skip, skip + limit);
@@ -2026,8 +2284,11 @@ export class UserService {
return {
activityId: activity.id,
activityTitle: activity.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityDurationMins: activity.activityDurationMins,
sustainabilityScore: activity.sustainabilityScore,
rating: 0,
distanceKm: activity.distanceKm,
cheapestPrice,
energyLevel: activity.activityType.energyLevel
@@ -2343,6 +2604,42 @@ export class UserService {
const effectiveCountryXid = effectiveLocation?.countryXid ?? null;
const effectiveStateXid = effectiveLocation?.stateXid ?? null;
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: {
id: true,
schoolCompanyXid: true,
}
})
const otherConnectionUsers = await tx.connectDetails.findMany({
where: { userXid: { notIn: [userId] }, isActive: true, schoolCompanyXid: { in: userConnectionDetails.map((u) => u.schoolCompanyXid) } },
select: {
id: true,
userXid: true,
}
})
const connectionUserIds = otherConnectionUsers.map(u => u.userXid);
const connectionInterestByActivity = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
userXid: { in: connectionUserIds },
isActive: true,
},
_count: {
activityXid: true,
},
});
const connectionInterestMap = new Map(
connectionInterestByActivity.map(item => [
item.activityXid,
item._count.activityXid,
])
);
/* =====================================================
1⃣ FETCH ALL CANDIDATES FOR INTERESTS (SIMPLE SORT)
===================================================== */
@@ -2563,7 +2860,7 @@ export class UserService {
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) }
};
const formattedNewArrivalsActivities = await rankAndPaginateActivities(tx, newArrivalsWhere, page, limit);
const formattedNewArrivalsActivities = await rankAndPaginateActivities(tx, newArrivalsWhere, page, limit, connectionInterestMap);
/* =====================================================
4⃣ OTHER STATES ACTIVITIES (RANKED)
@@ -2582,7 +2879,7 @@ export class UserService {
otherStatesWhere.checkInStateXid = { not: effectiveStateXid };
}
const formattedOtherStatesActivities = await rankAndPaginateActivities(tx, otherStatesWhere, page, limit);
const formattedOtherStatesActivities = await rankAndPaginateActivities(tx, otherStatesWhere, page, limit, connectionInterestMap);
/* =====================================================
@@ -2599,7 +2896,7 @@ export class UserService {
overseasWhere.checkInCountryXid = { not: effectiveCountryXid };
}
const formattedOverSeasActivities = await rankAndPaginateActivities(tx, overseasWhere, page, limit);
const formattedOverSeasActivities = await rankAndPaginateActivities(tx, overseasWhere, page, limit, connectionInterestMap);
const formattedActivities = await Promise.all(
@@ -2614,8 +2911,11 @@ export class UserService {
interestXid: activity.activityType.interestXid,
activityId: activity.id,
activityTitle: activity.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityDurationMins: activity.activityDurationMins,
sustainabilityScore: activity.sustainabilityScore,
rating: 0,
cheapestPrice,
energyLevel: activity.activityType.energyLevel
? {
@@ -2841,6 +3141,42 @@ export class UserService {
const effectiveCountryXid = effectiveLocation?.countryXid ?? null;
const effectiveStateXid = effectiveLocation?.stateXid ?? null;
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: {
id: true,
schoolCompanyXid: true,
}
})
const otherConnectionUsers = await tx.connectDetails.findMany({
where: { userXid: { notIn: [userId] }, isActive: true, schoolCompanyXid: { in: userConnectionDetails.map((u) => u.schoolCompanyXid) } },
select: {
id: true,
userXid: true,
}
})
const connectionUserIds = otherConnectionUsers.map(u => u.userXid);
const connectionInterestByActivity = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
userXid: { in: connectionUserIds },
isActive: true,
},
_count: {
activityXid: true,
},
});
const connectionInterestMap = new Map(
connectionInterestByActivity.map(item => [
item.activityXid,
item._count.activityXid,
])
);
/* =======================================================
SWITCH BASED VIEW MORE TYPE
======================================================= */
@@ -2874,7 +3210,7 @@ export class UserService {
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
};
return await rankAndPaginateActivities(tx, where, page, limit);
return await rankAndPaginateActivities(tx, where, page, limit, connectionInterestMap);
}
/* ==========================================
@@ -2891,7 +3227,7 @@ export class UserService {
},
};
return await rankAndPaginateActivities(tx, where, page, limit);
return await rankAndPaginateActivities(tx, where, page, limit, connectionInterestMap);
}
/* ==========================================
@@ -2913,7 +3249,7 @@ export class UserService {
where.checkInStateXid = { not: effectiveStateXid };
}
return await rankAndPaginateActivities(tx, where, page, limit);
return await rankAndPaginateActivities(tx, where, page, limit, connectionInterestMap);
}
/* ==========================================
@@ -2931,7 +3267,7 @@ export class UserService {
where.checkInCountryXid = { not: effectiveCountryXid };
}
return await rankAndPaginateActivities(tx, where, page, limit);
return await rankAndPaginateActivities(tx, where, page, limit, connectionInterestMap);
}
default:
@@ -3021,7 +3357,7 @@ export class UserService {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: {
where: {
isActive: true,
},
select: {
@@ -3063,4 +3399,84 @@ export class UserService {
});
}
async getFiveRandomActivities() {
return await this.prisma.$transaction(async (tx) => {
// Step 1: Count eligible activities
const totalCount = await tx.activities.count({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
},
});
if (totalCount === 0) return [];
// Step 2: Generate 5 unique random offsets
const takeCount = Math.min(5, totalCount);
const randomOffsets = new Set<number>();
while (randomOffsets.size < takeCount) {
randomOffsets.add(Math.floor(Math.random() * totalCount));
}
// Step 3: Fetch activities using skip (efficient for small limit like 5)
const activities = await Promise.all(
Array.from(randomOffsets).map((offset) =>
tx.activities.findFirst({
skip: offset,
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: {
isActive: true,
},
orderBy: {
displayOrder: 'asc',
},
take: 1,
select: {
mediaFileName: true,
},
},
},
})
)
);
// Step 4: Attach presigned URLs
const result = await Promise.all(
activities
.filter(Boolean)
.map(async (activity) => {
const media = activity!.ActivitiesMedia?.[0];
let presignedUrl = null;
if (media?.mediaFileName) {
presignedUrl = await attachPresignedUrl(media.mediaFileName);
}
return {
id: activity!.id,
title: activity!.activityTitle,
coverImage: media?.mediaFileName ?? null,
coverImagePresignedUrl: presignedUrl,
};
})
);
return result;
});
}
}