fixed the coverimage issue for random activities and addbucket issue also resolved and taking the group count

This commit is contained in:
2026-03-25 13:34:12 +05:30
parent 50ce8e39c5
commit e4a2a04045
4 changed files with 185 additions and 243 deletions

View File

@@ -47,6 +47,10 @@ export const handler = safeHandler(async (
? Number(body.energyLevelXid)
: undefined,
entryTypeXid: Number(body.entryTypeXid),
groupCount:
body.groupCount !== undefined && body.groupCount !== null
? Number(body.groupCount)
: undefined,
page: body.page !== undefined ? Number(body.page) : 1,
limit: body.limit !== undefined ? Number(body.limit) : 20,
};
@@ -61,6 +65,7 @@ export const handler = safeHandler(async (
(payload.energyLevelXid !== undefined &&
Number.isNaN(payload.energyLevelXid)) ||
Number.isNaN(payload.entryTypeXid) ||
(payload.groupCount !== undefined && Number.isNaN(payload.groupCount)) ||
Number.isNaN(payload.page) ||
Number.isNaN(payload.limit)
) {

View File

@@ -36,7 +36,6 @@ export const handler = safeHandler(async (
}
const activities = Array.isArray(body.activities) ? body.activities : [];
const members = Array.isArray(body.members) ? body.members : [];
if (!body.startDate || !body.endDate || !body.startTime || !body.endTime) {
throw new ApiError(
@@ -71,10 +70,6 @@ export const handler = safeHandler(async (
endDate: body.endDate,
startTime: body.startTime,
endTime: body.endTime,
members: members.map((member: any) => ({
memberXid: Number(member.memberXid),
memberRole: member.memberRole,
})),
activities: activities.map((activity: any) => ({
activityXid: Number(activity.activityXid),
venueXid: Number(activity.venueXid),
@@ -113,7 +108,6 @@ export const handler = safeHandler(async (
};
if (
payload.members.some((member) => Number.isNaN(member.memberXid)) ||
payload.activities.some(
(activity) =>
Number.isNaN(activity.activityXid) ||

View File

@@ -447,10 +447,6 @@ export class ItineraryService {
endDate: string;
startTime: string;
endTime: string;
members?: Array<{
memberXid: number;
memberRole?: string;
}>;
activities: Array<{
activityXid: number;
venueXid: number;
@@ -503,64 +499,8 @@ export class ItineraryService {
const itineraryNo = `ITN-${Date.now()}`;
const itineraryTitle = payload.title?.trim() || itineraryNo;
const invitedMembers = Array.from(
new Map(
(payload.members ?? [])
.filter((member) => member.memberXid && member.memberXid !== ownerXid)
.map((member) => [member.memberXid, member]),
).values(),
);
return this.prisma.$transaction(async (tx) => {
if (invitedMembers.length) {
const ownerConnectionDetails = await tx.connectDetails.findMany({
where: {
userXid: ownerXid,
isActive: true,
deletedAt: null,
},
select: {
schoolCompanyXid: true,
},
});
const ownerSchoolCompanyXids = ownerConnectionDetails.map(
(item) => item.schoolCompanyXid,
);
if (!ownerSchoolCompanyXids.length) {
throw new ApiError(
400,
'You can invite members only when you have school/company connection details.',
);
}
const validMembers = await tx.connectDetails.findMany({
where: {
isActive: true,
deletedAt: null,
schoolCompanyXid: { in: ownerSchoolCompanyXids },
userXid: { in: invitedMembers.map((member) => member.memberXid) },
},
distinct: ['userXid'],
select: {
userXid: true,
},
});
const validMemberIds = new Set(validMembers.map((item) => item.userXid));
const invalidMembers = invitedMembers.filter(
(member) => !validMemberIds.has(member.memberXid),
);
if (invalidMembers.length) {
throw new ApiError(
400,
'All invited members must belong to the same school/company network as the owner.',
);
}
}
const itineraryHeader = await tx.itineraryHeader.create({
data: {
itineraryNo,
@@ -586,24 +526,6 @@ export class ItineraryService {
},
});
const createdMembers =
invitedMembers.length > 0
? await Promise.all(
invitedMembers.map((member) =>
tx.itineraryMembers.create({
data: {
itineraryHeaderXid: itineraryHeader.id,
memberXid: member.memberXid,
memberRole: member.memberRole?.trim() || 'MEMBER',
memberStatus: 'pending',
invitedByXid: ownerXid,
isActive: true,
},
}),
),
)
: [];
const createdActivities = await Promise.all(
payload.activities.map(async (activityItem) => {
const scheduleHeader = await tx.scheduleHeader.findFirst({
@@ -840,7 +762,7 @@ export class ItineraryService {
title: itineraryHeader.title,
itineraryStatus: itineraryHeader.itineraryStatus,
ownerMemberXid: ownerMember.id,
membersCount: createdMembers.length + 1,
membersCount: 1,
activitiesCount: createdActivities.length,
members: [
{
@@ -849,12 +771,6 @@ export class ItineraryService {
memberRole: ownerMember.memberRole,
memberStatus: ownerMember.memberStatus,
},
...createdMembers.map((member) => ({
id: member.id,
memberXid: member.memberXid,
memberRole: member.memberRole,
memberStatus: member.memberStatus,
})),
],
activities: createdActivities,
};
@@ -1122,6 +1038,7 @@ export class ItineraryService {
endTime: string;
energyLevelXid?: number;
entryTypeXid: number;
groupCount?: number;
page: number;
limit: number;
},
@@ -1143,8 +1060,39 @@ export class ItineraryService {
);
}
if (payload.groupCount !== undefined) {
if (!Number.isInteger(payload.groupCount) || payload.groupCount <= 0) {
throw new ApiError(400, 'groupCount must be a positive integer.');
}
}
const rangeStartDay = startOfDay(requestedStart);
const rangeEndDay = startOfDay(requestedEnd);
const requestedEntryType = await this.prisma.allowedEntryTypes.findFirst({
where: {
id: payload.entryTypeXid,
isActive: true,
deletedAt: null,
},
select: {
id: true,
allowedEntryTypeName: true,
},
});
if (!requestedEntryType) {
throw new ApiError(404, 'Selected entry type not found.');
}
const isGroupEntryType =
requestedEntryType.allowedEntryTypeName.trim().toLowerCase() === 'group';
if (isGroupEntryType && payload.groupCount === undefined) {
throw new ApiError(
400,
'groupCount is required when entryTypeXid is for group.',
);
}
const activityEntries = await this.prisma.userBucketInterested.findMany({
where: {
@@ -1318,7 +1266,10 @@ export class ItineraryService {
where: {
isActive: true,
deletedAt: null,
maxCapacity: { gt: 0 },
maxCapacity:
isGroupEntryType && payload.groupCount !== undefined
? { gte: payload.groupCount }
: { gt: 0 },
},
select: {
id: true,
@@ -1446,6 +1397,14 @@ export class ItineraryService {
return null;
}
if (
isGroupEntryType &&
payload.groupCount !== undefined &&
slot.maxCapacity < payload.groupCount
) {
return null;
}
return {
scheduleHeaderXid: header.id,
slotId: slot.id,
@@ -1634,6 +1593,7 @@ export class ItineraryService {
endTime: payload.endTime,
energyLevelXid: payload.energyLevelXid,
entryTypeXid: payload.entryTypeXid,
groupCount: payload.groupCount,
page: sanitizedPage,
limit: sanitizedLimit,
interestTypes: distinctInterests,

View File

@@ -1148,7 +1148,9 @@ export class UserService {
// 6⃣ RANDOM ACTIVITIES (5 ONLY - SIMPLE)
// =====================================================
const totalActiveCount = await tx.activities.count({
let randomActivities: any[] = [];
const eligibleRandomActivityIds = await tx.activities.findMany({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
@@ -1157,53 +1159,44 @@ export class UserService {
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1], // prevent empty notIn issue
: [-1],
},
ActivitiesMedia: {
some: {
isActive: true,
isCoverImage: true,
},
},
},
select: {
id: true,
},
});
let randomActivities: any[] = [];
if (eligibleRandomActivityIds.length > 0) {
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
const selectedIds = eligibleRandomActivityIds
.sort(() => Math.random() - 0.5)
.slice(0, takeCount)
.map((activity) => activity.id);
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,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1], // prevent empty notIn issue
},
},
const randomFetched = await tx.activities.findMany({
where: {
id: { in: selectedIds },
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
mediaFileName: true,
},
},
mediaFileName: true,
},
}),
),
);
},
},
});
randomActivities = await Promise.all(
randomFetched
@@ -1817,7 +1810,9 @@ export class UserService {
RANDOM ACTIVITIES (5 COVER IMAGES)
===================================================== */
const totalActiveCount = await tx.activities.count({
let randomActivities: any[] = [];
const eligibleRandomActivityIds = await tx.activities.findMany({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
@@ -1826,50 +1821,43 @@ export class UserService {
id: {
notIn: safeExcludedIds,
},
ActivitiesMedia: {
some: {
isActive: true,
isCoverImage: true,
},
},
...excludeUserInterestCondition,
},
select: {
id: true,
},
});
let randomActivities: any[] = [];
if (eligibleRandomActivityIds.length > 0) {
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
const selectedIds = eligibleRandomActivityIds
.sort(() => Math.random() - 0.5)
.slice(0, takeCount)
.map((activity) => activity.id);
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,
id: {
notIn: safeExcludedIds,
},
...excludeUserInterestCondition,
},
const randomFetched = await tx.activities.findMany({
where: {
id: { in: selectedIds },
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
mediaFileName: true,
},
},
mediaFileName: true,
},
}),
),
);
},
},
});
randomActivities = await Promise.all(
randomFetched
@@ -3634,7 +3622,9 @@ export class UserService {
RANDOM ACTIVITIES FROM CONNECTION USERS (5 COVER IMAGES)
===================================================== */
const totalActiveCount = await tx.activities.count({
let randomActivities: any[] = [];
const eligibleRandomActivityIds = await tx.activities.findMany({
where: {
id: { in: connectionActivityIds },
isActive: true,
@@ -3642,47 +3632,42 @@ export class UserService {
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
activityTypeXid: { in: activityTypeIds },
deletedAt: null,
ActivitiesMedia: {
some: {
isActive: true,
isCoverImage: true,
},
},
},
select: {
id: true,
},
});
let randomActivities: any[] = [];
if (eligibleRandomActivityIds.length > 0) {
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
const selectedIds = eligibleRandomActivityIds
.sort(() => Math.random() - 0.5)
.slice(0, takeCount)
.map((activity) => activity.id);
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: {
id: { in: connectionActivityIds },
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
activityTypeXid: { in: activityTypeIds },
deletedAt: null,
},
const randomFetched = await tx.activities.findMany({
where: {
id: { in: selectedIds },
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: "asc" },
take: 1,
select: {
mediaFileName: true,
},
},
mediaFileName: true,
},
}),
),
);
},
},
});
randomActivities = await Promise.all(
randomFetched
@@ -4151,56 +4136,54 @@ export class UserService {
async getFiveRandomActivities() {
return await this.prisma.$transaction(async (tx) => {
// Step 1: Count eligible activities
const totalCount = await tx.activities.count({
const eligibleRandomActivityIds = await tx.activities.findMany({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
ActivitiesMedia: {
some: {
isActive: true,
isCoverImage: true,
},
},
},
select: {
id: true,
},
});
if (totalCount === 0) return [];
if (eligibleRandomActivityIds.length === 0) return [];
// Step 2: Generate 5 unique random offsets
const takeCount = Math.min(5, totalCount);
const randomOffsets = new Set<number>();
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
const selectedIds = eligibleRandomActivityIds
.sort(() => Math.random() - 0.5)
.slice(0, takeCount)
.map((activity) => activity.id);
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,
const activities = await tx.activities.findMany({
where: {
id: { in: selectedIds },
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
isCoverImage: true,
},
orderBy: {
displayOrder: 'asc',
},
take: 1,
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: {
isActive: true,
},
orderBy: {
displayOrder: 'asc',
},
take: 1,
select: {
mediaFileName: true,
},
},
mediaFileName: true,
},
})
)
);
},
},
});
// Step 4: Attach presigned URLs
const result = await Promise.all(
@@ -4243,7 +4226,7 @@ export class UserService {
}
const existing = await this.prisma.userBucketInterested.findFirst({
where: { userXid, activityXid },
where: { userXid, activityXid, isActive: true },
});
if (existing) {