From ca16b8e3bbba75517fb14132487672733552a075 Mon Sep 17 00:00:00 2001 From: sjschlapbach Date: Fri, 29 Sep 2023 16:20:36 +0200 Subject: [PATCH 1/2] feat: automatically award live session achievements for first, second and third rank --- packages/graphql/src/services/sessions.ts | 146 ++++++++++++++++++++-- 1 file changed, 137 insertions(+), 9 deletions(-) diff --git a/packages/graphql/src/services/sessions.ts b/packages/graphql/src/services/sessions.ts index 3392613c78..1d956eea0a 100644 --- a/packages/graphql/src/services/sessions.ts +++ b/packages/graphql/src/services/sessions.ts @@ -437,6 +437,33 @@ interface EndSessionArgs { } export async function endSession({ id }: EndSessionArgs, ctx: ContextWithUser) { + // TODO: update achievement IDs after seeding, points and XP as required + const FIRST_ACHIEVEMENT_ID = 11 + const SECOND_ACHIEVEMENT_ID = 12 + const THIRD_ACHIEVEMENT_ID = 13 + const FIRST_RANK_POINTS = 100 + const SECOND_RANK_POINTS = 50 + const THIRD_RANK_POINTS = 30 + const FIRST_RANK_XP = 300 + const SECOND_RANK_XP = 200 + const THIRD_RANK_XP = 100 + + const achievements = { + 1: FIRST_ACHIEVEMENT_ID, + 2: SECOND_ACHIEVEMENT_ID, + 3: THIRD_ACHIEVEMENT_ID, + } + const points = { + 1: FIRST_RANK_POINTS, + 2: SECOND_RANK_POINTS, + 3: THIRD_RANK_POINTS, + } + const xp = { + 1: FIRST_RANK_XP, + 2: SECOND_RANK_XP, + 3: THIRD_RANK_XP, + } + const session = await ctx.prisma.liveSession.findFirst({ where: { id, @@ -468,7 +495,7 @@ export async function endSession({ id }: EndSessionArgs, ctx: ContextWithUser) { try { // if the session is part of a course, update the course leaderboard with the accumulated points - if (session.courseId) { + if (session.courseId && session.isGamificationEnabled) { const sessionLB = await ctx.redisExec.hgetall(`s:${id}:lb`) if (sessionLB) { @@ -492,16 +519,117 @@ export async function endSession({ id }: EndSessionArgs, ctx: ContextWithUser) { ) return null - return [id, score] + return { participantId: id, score: parseInt(score) } }) ) - ).flatMap((result) => { - if (result.status !== 'fulfilled' || !result.value) return [] - return [result.value] - }) + ) + .flatMap((result) => { + if (result.status !== 'fulfilled' || !result.value) return [] + return [result.value] + }) + .sort((a, b) => b.score - a.score) + + const newAchievements = [] + + if (existingParticipantsLB[0]) { + const firstRank = existingParticipantsLB[0] + newAchievements.push({ + participantId: firstRank.participantId, + achievementId: achievements[1], + points: points[1], + xp: xp[1], + }) + + if (existingParticipantsLB[1]) { + const secondRank = existingParticipantsLB[1] + const rank2 = secondRank.score < firstRank.score ? 2 : 1 + + newAchievements.push({ + participantId: secondRank.participantId, + achievementId: achievements[rank2], + points: points[rank2], + xp: xp[rank2], + }) + + if (existingParticipantsLB[2]) { + const thirdRank = existingParticipantsLB[2] + const rank3 = + thirdRank.score < secondRank.score + ? 3 + : thirdRank.score < firstRank.score + ? 2 + : 1 + + newAchievements.push({ + participantId: thirdRank.participantId, + achievementId: achievements[rank3], + points: points[rank3], + xp: xp[rank3], + }) + } + } + } + + // map over newAchievements and update participants in a prisma transaction + await ctx.prisma.$transaction( + newAchievements.map(({ participantId, achievementId, points, xp }) => + ctx.prisma.participant.update({ + where: { + id: participantId, + }, + data: { + // increment xp + xp: { + increment: xp, + }, + // increment points on course leaderboard, if session is assigned to course + leaderboards: { + update: { + where: { + type_participantId_courseId: { + type: 'COURSE', + courseId: session.courseId!, + participantId, + }, + }, + data: { + score: { + increment: points, + }, + }, + }, + }, + // create achievement or increment achievement count + achievements: { + upsert: { + where: { + participantId_achievementId: { + participantId, + achievementId, + }, + }, + create: { + achievement: { + connect: { + id: achievementId, + }, + }, + achievedAt: new Date(), + }, + update: { + achievedCount: { + increment: 1, + }, + }, + }, + }, + }, + }) + ) + ) await ctx.prisma.$transaction( - existingParticipantsLB.map(([participantId, score]) => + existingParticipantsLB.map(({ participantId, score }) => ctx.prisma.leaderboardEntry.upsert({ where: { type_participantId_courseId: { @@ -548,11 +676,11 @@ export async function endSession({ id }: EndSessionArgs, ctx: ContextWithUser) { }, }, }, - score: parseInt(score), + score: score, }, update: { score: { - increment: parseInt(score), + increment: score, }, }, }) From 3a4fd3fa90501331626f4db6ad850f7db3859732 Mon Sep 17 00:00:00 2001 From: sjschlapbach Date: Fri, 29 Sep 2023 16:27:20 +0200 Subject: [PATCH 2/2] fix: ensure that rewarded points and xp are directly fetched from the database through the corresponding achievement information --- packages/graphql/src/services/sessions.ts | 36 +++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/graphql/src/services/sessions.ts b/packages/graphql/src/services/sessions.ts index 1d956eea0a..42a830c4ad 100644 --- a/packages/graphql/src/services/sessions.ts +++ b/packages/graphql/src/services/sessions.ts @@ -437,16 +437,26 @@ interface EndSessionArgs { } export async function endSession({ id }: EndSessionArgs, ctx: ContextWithUser) { - // TODO: update achievement IDs after seeding, points and XP as required + // TODO: update achievement IDs after seeding const FIRST_ACHIEVEMENT_ID = 11 const SECOND_ACHIEVEMENT_ID = 12 const THIRD_ACHIEVEMENT_ID = 13 - const FIRST_RANK_POINTS = 100 - const SECOND_RANK_POINTS = 50 - const THIRD_RANK_POINTS = 30 - const FIRST_RANK_XP = 300 - const SECOND_RANK_XP = 200 - const THIRD_RANK_XP = 100 + + const firstRankAchievement = await ctx.prisma.achievement.findUnique({ + where: { + id: FIRST_ACHIEVEMENT_ID, + }, + }) + const secondRankAchievement = await ctx.prisma.achievement.findUnique({ + where: { + id: SECOND_ACHIEVEMENT_ID, + }, + }) + const thirdRankAchievement = await ctx.prisma.achievement.findUnique({ + where: { + id: THIRD_ACHIEVEMENT_ID, + }, + }) const achievements = { 1: FIRST_ACHIEVEMENT_ID, @@ -454,14 +464,14 @@ export async function endSession({ id }: EndSessionArgs, ctx: ContextWithUser) { 3: THIRD_ACHIEVEMENT_ID, } const points = { - 1: FIRST_RANK_POINTS, - 2: SECOND_RANK_POINTS, - 3: THIRD_RANK_POINTS, + 1: firstRankAchievement?.rewardedPoints || 0, + 2: secondRankAchievement?.rewardedPoints || 0, + 3: thirdRankAchievement?.rewardedPoints || 0, } const xp = { - 1: FIRST_RANK_XP, - 2: SECOND_RANK_XP, - 3: THIRD_RANK_XP, + 1: firstRankAchievement?.rewardedXP || 0, + 2: secondRankAchievement?.rewardedXP || 0, + 3: thirdRankAchievement?.rewardedXP || 0, } const session = await ctx.prisma.liveSession.findFirst({