feat: implement user service for managing personal information, retrieving interests, and ranking activities.
This commit is contained in:
@@ -145,6 +145,153 @@ const attachMediaWithPresignedUrl = async (mediaArr = []) => {
|
|||||||
|
|
||||||
const bucket = config.aws.bucketName;
|
const bucket = config.aws.bucketName;
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
HELPER: RANK & PAGINATE ACTIVITIES
|
||||||
|
===================================================== */
|
||||||
|
async function rankAndPaginateActivities(
|
||||||
|
tx: any,
|
||||||
|
whereClause: any,
|
||||||
|
page: number,
|
||||||
|
limit: number
|
||||||
|
) {
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
// 1️⃣ Fetch Metadata for ALL matching activities for in-memory sorting
|
||||||
|
const allCandidates = await tx.activities.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
sustainabilityScore: true,
|
||||||
|
totalScore: true, // Quality Score
|
||||||
|
ItineraryActivities: {
|
||||||
|
select: {
|
||||||
|
ActivityFeedbacks: {
|
||||||
|
select: { activityStars: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ActivityVenues: {
|
||||||
|
select: {
|
||||||
|
ActivityPrices: {
|
||||||
|
select: { sellPrice: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalCount = allCandidates.length;
|
||||||
|
|
||||||
|
// 2️⃣ Calculate Metrics & Sort
|
||||||
|
const sortedCandidates = allCandidates.map((act: any) => {
|
||||||
|
// Flatten feedbacks
|
||||||
|
const feedbacks = act.ItineraryActivities.flatMap((ia: any) => ia.ActivityFeedbacks);
|
||||||
|
|
||||||
|
// Avg Rating
|
||||||
|
const totalStars = feedbacks.reduce((sum: number, f: any) => sum + f.activityStars, 0);
|
||||||
|
const avgRating = feedbacks.length > 0 ? totalStars / feedbacks.length : 0;
|
||||||
|
|
||||||
|
// Min Price
|
||||||
|
const prices = act.ActivityVenues.flatMap((v: any) => v.ActivityPrices.map((p: any) => p.sellPrice)).filter((p: any) => p !== null) as number[];
|
||||||
|
const minPrice = prices.length > 0 ? Math.min(...prices) : Infinity;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: act.id,
|
||||||
|
avgRating,
|
||||||
|
minPrice,
|
||||||
|
sustainabilityScore: act.sustainabilityScore ?? 0,
|
||||||
|
totalScore: act.totalScore ?? 0,
|
||||||
|
};
|
||||||
|
}).sort((a: any, b: any) => {
|
||||||
|
// 1. Rating (Highest first)
|
||||||
|
if (b.avgRating !== a.avgRating) return b.avgRating - a.avgRating;
|
||||||
|
|
||||||
|
// 2. Price (Lowest first)
|
||||||
|
if (a.minPrice !== b.minPrice) return a.minPrice - b.minPrice;
|
||||||
|
|
||||||
|
// 3. Sustainability Score (Highest first)
|
||||||
|
if (b.sustainabilityScore !== a.sustainabilityScore) return b.sustainabilityScore - a.sustainabilityScore;
|
||||||
|
|
||||||
|
// 4. Quality Score (Highest first)
|
||||||
|
return b.totalScore - a.totalScore;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3️⃣ Paginate IDs
|
||||||
|
const paginatedCandidates = sortedCandidates.slice(skip, skip + limit);
|
||||||
|
const targetIds = paginatedCandidates.map((c: any) => c.id);
|
||||||
|
|
||||||
|
// 4️⃣ Fetch Full Details for the page
|
||||||
|
const activitiesUnsorted = await tx.activities.findMany({
|
||||||
|
where: { id: { in: targetIds } },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
activityTitle: true,
|
||||||
|
activityDurationMins: true,
|
||||||
|
sustainabilityScore: true,
|
||||||
|
activityType: {
|
||||||
|
select: {
|
||||||
|
interestXid: true,
|
||||||
|
energyLevel: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
energyLevelName: true,
|
||||||
|
energyColor: true,
|
||||||
|
energyIcon: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ActivityVenues: {
|
||||||
|
select: {
|
||||||
|
ActivityPrices: {
|
||||||
|
select: { sellPrice: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ActivitiesMedia: {
|
||||||
|
where: { isActive: true },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
mediaFileName: true,
|
||||||
|
mediaType: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-sort to match the calculated order
|
||||||
|
const activities = targetIds
|
||||||
|
.map((id: number) => activitiesUnsorted.find((a: any) => a.id === id))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// 5️⃣ Format Response
|
||||||
|
const formattedActivities = await Promise.all(
|
||||||
|
activities.map(async (activity: any) => {
|
||||||
|
const prices = activity.ActivityVenues.flatMap((v: any) => v.ActivityPrices.map((p: any) => p.sellPrice)).filter((p: any) => p !== null) as number[];
|
||||||
|
const cheapestPrice = prices.length > 0 ? Math.min(...prices) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// interestXid: activity.activityType.interestXid,
|
||||||
|
activityId: activity.id,
|
||||||
|
activityTitle: activity.activityTitle,
|
||||||
|
// activityDurationMins: activity.activityDurationMins,
|
||||||
|
// sustainabilityScore: activity.sustainabilityScore,
|
||||||
|
// cheapestPrice,
|
||||||
|
energyLevel: activity.activityType.energyLevel,
|
||||||
|
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalCount,
|
||||||
|
hasMore: skip + limit < totalCount,
|
||||||
|
activities: formattedActivities,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@@ -494,6 +641,10 @@ export class UserService {
|
|||||||
|
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
1️⃣ FETCH ALL CANDIDATES FOR INTERESTS (SIMPLE SORT)
|
||||||
|
===================================================== */
|
||||||
|
// Reverted to simple ID based sorting for Interest-based activities
|
||||||
const activities = await tx.activities.findMany({
|
const activities = await tx.activities.findMany({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -513,10 +664,9 @@ export class UserService {
|
|||||||
sustainabilityScore: true,
|
sustainabilityScore: true,
|
||||||
checkInLat: true,
|
checkInLat: true,
|
||||||
checkInLong: true,
|
checkInLong: true,
|
||||||
|
|
||||||
activityType: {
|
activityType: {
|
||||||
select: {
|
select: {
|
||||||
interestXid: true, // ✅ VERY IMPORTANT
|
interestXid: true,
|
||||||
energyLevel: {
|
energyLevel: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -527,7 +677,6 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ActivityVenues: {
|
ActivityVenues: {
|
||||||
select: {
|
select: {
|
||||||
ActivityPrices: {
|
ActivityPrices: {
|
||||||
@@ -537,7 +686,6 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ActivitiesMedia: {
|
ActivitiesMedia: {
|
||||||
where: { isActive: true },
|
where: { isActive: true },
|
||||||
select: {
|
select: {
|
||||||
@@ -557,6 +705,9 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
2️⃣ MOST HYPED ACTIVITIES (RANKED)
|
||||||
|
===================================================== */
|
||||||
const mostHypedGrouped = await tx.userBucketInterested.groupBy({
|
const mostHypedGrouped = await tx.userBucketInterested.groupBy({
|
||||||
by: ['activityXid'],
|
by: ['activityXid'],
|
||||||
where: {
|
where: {
|
||||||
@@ -576,9 +727,10 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const totalHypedActivities = mostHypedTotalCount.length;
|
const totalHypedActivities = mostHypedTotalCount.length;
|
||||||
|
|
||||||
const mostHypedActivityIds = mostHypedGrouped.map(a => a.activityXid);
|
const mostHypedActivityIds = mostHypedGrouped.map(a => a.activityXid);
|
||||||
|
|
||||||
|
// Fetch metadata for ranking only for the top hyped ones (optimization: double sorting might be needed if we want to sort BY rating WITHIN the hyped list, but usually Hyped = Count.
|
||||||
|
// 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 },
|
||||||
@@ -589,6 +741,8 @@ export class UserService {
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
activityTitle: true,
|
activityTitle: true,
|
||||||
|
sustainabilityScore: true,
|
||||||
|
totalScore: true,
|
||||||
activityType: {
|
activityType: {
|
||||||
select: {
|
select: {
|
||||||
energyLevel: {
|
energyLevel: {
|
||||||
@@ -609,23 +763,60 @@ export class UserService {
|
|||||||
mediaType: true,
|
mediaType: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Fetch ranking metadata
|
||||||
|
ItineraryActivities: {
|
||||||
|
select: {
|
||||||
|
ActivityFeedbacks: {
|
||||||
|
select: { activityStars: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ActivityVenues: {
|
||||||
|
select: {
|
||||||
|
ActivityPrices: {
|
||||||
|
select: { sellPrice: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const mostHypedActivities = await Promise.all(
|
// Sort Most Hyped by the 4 criteria
|
||||||
mostHypedGrouped.map(async g => {
|
const mostHypedSorted = mostHypedActivitiesRaw.map(act => {
|
||||||
const activity = mostHypedActivitiesRaw.find(a => a.id === g.activityXid);
|
const feedbacks = act.ItineraryActivities.flatMap(ia => ia.ActivityFeedbacks);
|
||||||
if (!activity) return null;
|
const totalStars = feedbacks.reduce((sum, f) => sum + f.activityStars, 0);
|
||||||
|
const avgRating = feedbacks.length > 0 ? totalStars / feedbacks.length : 0;
|
||||||
|
const prices = act.ActivityVenues.flatMap(v => v.ActivityPrices.map(p => p.sellPrice)).filter(p => p !== null) as number[];
|
||||||
|
const minPrice = prices.length > 0 ? Math.min(...prices) : Infinity;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activityId: activity.id,
|
...act, // Keep original fields for final output
|
||||||
activityTitle: activity.activityTitle,
|
avgRating,
|
||||||
hypeCount: g._count.activityXid, // 🔥 VERY IMPORTANT
|
minPrice,
|
||||||
energyLevel: activity.activityType.energyLevel,
|
sustainabilityScore: act.sustainabilityScore ?? 0,
|
||||||
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
|
totalScore: act.totalScore ?? 0,
|
||||||
};
|
hypeCount: mostHypedGrouped.find(g => g.activityXid === act.id)?._count.activityXid ?? 0
|
||||||
})
|
};
|
||||||
).then(arr => arr.filter(Boolean));
|
}).sort((a, b) => {
|
||||||
|
// 1. Rating (Highest first)
|
||||||
|
if (b.avgRating !== a.avgRating) return b.avgRating - a.avgRating;
|
||||||
|
// 2. Price (Lowest first)
|
||||||
|
if (a.minPrice !== b.minPrice) return a.minPrice - b.minPrice;
|
||||||
|
// 3. Sustainability Score
|
||||||
|
if (b.sustainabilityScore !== a.sustainabilityScore) return b.sustainabilityScore - a.sustainabilityScore;
|
||||||
|
// 4. Quality Score
|
||||||
|
return b.totalScore - a.totalScore;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mostHypedActivities = await Promise.all(
|
||||||
|
mostHypedSorted.map(async activity => ({
|
||||||
|
activityId: activity.id,
|
||||||
|
activityTitle: activity.activityTitle,
|
||||||
|
hypeCount: activity.hypeCount,
|
||||||
|
energyLevel: activity.activityType.energyLevel,
|
||||||
|
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const formattedMostHypedActivities = {
|
const formattedMostHypedActivities = {
|
||||||
page,
|
page,
|
||||||
@@ -636,59 +827,21 @@ export class UserService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const newArrivalsCount = await tx.activities.count({
|
/* =====================================================
|
||||||
where: {
|
3️⃣ NEW ARRIVALS (RANKED)
|
||||||
isActive: true,
|
===================================================== */
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
const newArrivalsWhere = {
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
isActive: true,
|
||||||
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) }
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
},
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
});
|
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) }
|
||||||
|
};
|
||||||
|
|
||||||
const newArrivalsActivities = await tx.activities.findMany({
|
const formattedNewArrivalsActivities = await rankAndPaginateActivities(tx, newArrivalsWhere, page, limit);
|
||||||
where: {
|
|
||||||
isActive: true,
|
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
|
||||||
|
|
||||||
// // ✅ Only user's interest types
|
|
||||||
// activityTypeXid: {
|
|
||||||
// in: activitiyTypesOfUserInterests.map(at => at.id),
|
|
||||||
// },
|
|
||||||
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) }
|
|
||||||
},
|
|
||||||
skip,
|
|
||||||
take: limit,
|
|
||||||
orderBy: { id: 'desc' },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
activityTitle: true,
|
|
||||||
activityType: {
|
|
||||||
select: {
|
|
||||||
energyLevel: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
energyLevelName: true,
|
|
||||||
energyColor: true,
|
|
||||||
energyIcon: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: { isActive: true },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
mediaFileName: true,
|
|
||||||
mediaType: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
/* =====================================================
|
/* =====================================================
|
||||||
6️⃣ OTHER STATES ACTIVITIES
|
4️⃣ OTHER STATES ACTIVITIES (RANKED)
|
||||||
===================================================== */
|
===================================================== */
|
||||||
const otherStatesWhere: any = {
|
const otherStatesWhere: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
@@ -702,27 +855,11 @@ export class UserService {
|
|||||||
otherStatesWhere.checkInStateXid = { not: effectiveStateXid };
|
otherStatesWhere.checkInStateXid = { not: effectiveStateXid };
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherStatesTotalCount = await tx.activities.count({
|
const formattedOtherStatesActivities = await rankAndPaginateActivities(tx, otherStatesWhere, page, limit);
|
||||||
where: otherStatesWhere,
|
|
||||||
});
|
|
||||||
|
|
||||||
const otherStatesActivities = await tx.activities.findMany({
|
|
||||||
where: otherStatesWhere,
|
|
||||||
skip,
|
|
||||||
take: limit,
|
|
||||||
orderBy: { id: 'desc' },
|
|
||||||
select: {
|
|
||||||
activityTitle: true,
|
|
||||||
activityType: { select: { energyLevel: true } },
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: { isActive: true },
|
|
||||||
select: { id: true, mediaFileName: true, mediaType: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/* =====================================================
|
/* =====================================================
|
||||||
7️⃣ OVERSEAS ACTIVITIES
|
5️⃣ OVERSEAS ACTIVITIES (RANKED)
|
||||||
===================================================== */
|
===================================================== */
|
||||||
const overseasWhere: any = {
|
const overseasWhere: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -734,24 +871,8 @@ export class UserService {
|
|||||||
overseasWhere.checkInCountryXid = { not: effectiveCountryXid };
|
overseasWhere.checkInCountryXid = { not: effectiveCountryXid };
|
||||||
}
|
}
|
||||||
|
|
||||||
const overSeasTotalCount = await tx.activities.count({
|
const formattedOverSeasActivities = await rankAndPaginateActivities(tx, overseasWhere, page, limit);
|
||||||
where: overseasWhere,
|
|
||||||
});
|
|
||||||
|
|
||||||
const overSeasActivities = await tx.activities.findMany({
|
|
||||||
where: overseasWhere,
|
|
||||||
skip,
|
|
||||||
take: limit,
|
|
||||||
orderBy: { id: 'desc' },
|
|
||||||
select: {
|
|
||||||
activityTitle: true,
|
|
||||||
activityType: { select: { energyLevel: true } },
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: { isActive: true },
|
|
||||||
select: { id: true, mediaFileName: true, mediaType: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const formattedActivities = await Promise.all(
|
const formattedActivities = await Promise.all(
|
||||||
activities.map(async (activity) => {
|
activities.map(async (activity) => {
|
||||||
@@ -774,36 +895,6 @@ export class UserService {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const formattedOtherStatesActivities = {
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
totalCount: otherStatesTotalCount,
|
|
||||||
hasMore: skip + limit < otherStatesTotalCount,
|
|
||||||
activities: await Promise.all(
|
|
||||||
otherStatesActivities.map(async a => ({
|
|
||||||
activityTitle: a.activityTitle,
|
|
||||||
energyLevel: a.activityType.energyLevel,
|
|
||||||
media: await attachMediaWithPresignedUrl(a.ActivitiesMedia),
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const formattedNewArrivalsActivities = {
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
totalCount: newArrivalsCount,
|
|
||||||
hasMore: skip + limit < newArrivalsCount,
|
|
||||||
activities: await Promise.all(
|
|
||||||
newArrivalsActivities.map(async a => ({
|
|
||||||
activityTitle: a.activityTitle,
|
|
||||||
energyLevel: a.activityType.energyLevel,
|
|
||||||
media: await attachMediaWithPresignedUrl(a.ActivitiesMedia),
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const interestsWithActivities = [...userInterests]
|
const interestsWithActivities = [...userInterests]
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
a.interest.interestName.localeCompare(b.interest.interestName)
|
a.interest.interestName.localeCompare(b.interest.interestName)
|
||||||
@@ -835,32 +926,8 @@ export class UserService {
|
|||||||
limit,
|
limit,
|
||||||
},
|
},
|
||||||
interests: interestsWithActivities,
|
interests: interestsWithActivities,
|
||||||
otherStatesActivities: {
|
otherStatesActivities: formattedOtherStatesActivities,
|
||||||
page,
|
overSeasActivities: formattedOverSeasActivities,
|
||||||
limit,
|
|
||||||
totalCount: otherStatesTotalCount,
|
|
||||||
hasMore: skip + limit < otherStatesTotalCount,
|
|
||||||
activities: await Promise.all(
|
|
||||||
otherStatesActivities.map(async a => ({
|
|
||||||
activityTitle: a.activityTitle,
|
|
||||||
energyLevel: a.activityType.energyLevel,
|
|
||||||
media: await attachMediaWithPresignedUrl(a.ActivitiesMedia),
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
},
|
|
||||||
overSeasActivities: {
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
totalCount: overSeasTotalCount,
|
|
||||||
hasMore: skip + limit < overSeasTotalCount,
|
|
||||||
activities: await Promise.all(
|
|
||||||
overSeasActivities.map(async a => ({
|
|
||||||
activityTitle: a.activityTitle,
|
|
||||||
energyLevel: a.activityType.energyLevel,
|
|
||||||
media: await attachMediaWithPresignedUrl(a.ActivitiesMedia),
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
},
|
|
||||||
newArrivalsActivities: formattedNewArrivalsActivities,
|
newArrivalsActivities: formattedNewArrivalsActivities,
|
||||||
mostHypedActivities: formattedMostHypedActivities,
|
mostHypedActivities: formattedMostHypedActivities,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user