Skip to content

Commit 885321c

Browse files
authored
fix(packages/graphql): make sure avatars and usernames are returned on live quiz leaderboard query with hmac (#4739)
1 parent 5c82856 commit 885321c

8 files changed

Lines changed: 37 additions & 11 deletions

File tree

packages/graphql/src/graphql/ops/QGetLiveQuizEvaluation.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ query GetLiveQuizEvaluation($id: String!, $hmac: String) {
3030
createdAt
3131
}
3232
}
33-
liveQuizLeaderboard(quizId: $id) {
33+
liveQuizLeaderboard(quizId: $id, hmac: $hmac) {
3434
id
3535
participantId
3636
rank

packages/graphql/src/ops.schema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32498,6 +32498,18 @@
3249832498
"name": "liveQuizLeaderboard",
3249932499
"description": null,
3250032500
"args": [
32501+
{
32502+
"name": "hmac",
32503+
"description": null,
32504+
"type": {
32505+
"kind": "SCALAR",
32506+
"name": "String",
32507+
"ofType": null
32508+
},
32509+
"defaultValue": null,
32510+
"isDeprecated": false,
32511+
"deprecationReason": null
32512+
},
3250132513
{
3250232514
"name": "quizId",
3250332515
"description": null,

packages/graphql/src/ops.ts

Lines changed: 2 additions & 1 deletion
Large diffs are not rendered by default.

packages/graphql/src/public/client.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@
205205
"GetGroupActivitySummary": "7a3935db0e7cae8d5baf574a1c0506f568ffaaae1522a49363245946cff64d2d",
206206
"GetInstanceUpdateActivities": "5ddcb8ccd6ca8328d13df033a3b487710a2e62210acd3e942364f28472bdf7c1",
207207
"GetLecturerViewLiveQuiz": "d1ab35eb40e5193d9ef4a4563d1057e860db1b9774d82076ccc1c380350139ff",
208-
"GetLiveQuizEvaluation": "eac375272c244160b5b257a65899f12c76cf12d5dc6209eea98fc111f61acde2",
208+
"GetLiveQuizEvaluation": "af44e84fed3e7119587adad54e43b7cfcb84b6f53ca92b8821f2dbc5ec2b64f6",
209209
"GetLiveQuizHMAC": "f172839e4e15bdd42039bd90c6f5ecace80f0d2ce00f34bedaceed9a4dbe39d4",
210210
"GetLiveQuizLeaderboard": "7f849e9a714a388215d6c7cce9582debe85faa83b11871b330de7261aaf6205d",
211211
"GetLiveQuizSummary": "fd17c092be3d096abd6d31e89d8249bd4b482a2a6310f6893e9e32c5a70666c0",

packages/graphql/src/public/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,7 @@ type Query {
21042104
liveQuiz(id: String!): LiveQuiz
21052105
liveQuizEvaluation(hmac: String, id: String!): ActivityEvaluation
21062106
liveQuizHMAC(id: String!): String
2107-
liveQuizLeaderboard(quizId: String!): [LeaderboardEntry!]
2107+
liveQuizLeaderboard(hmac: String, quizId: String!): [LeaderboardEntry!]
21082108
microLearning(id: String!): MicroLearning
21092109
participantCourses: [Course!]
21102110
participantGroups(courseId: String!): [ParticipantGroup!]

packages/graphql/src/public/server.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@
205205
"7a3935db0e7cae8d5baf574a1c0506f568ffaaae1522a49363245946cff64d2d": "query GetGroupActivitySummary($id: String!) {\n getGroupActivitySummary(id: $id) {\n numOfStartedInstances\n numOfSubmissions\n __typename\n }\n}",
206206
"5ddcb8ccd6ca8328d13df033a3b487710a2e62210acd3e942364f28472bdf7c1": "query GetInstanceUpdateActivities($elementId: Int!, $hasSampleSolution: Boolean, $includeTemplateInstances: Boolean!) {\n getInstanceUpdateActivities(\n elementId: $elementId\n hasSampleSolution: $hasSampleSolution\n includeTemplateInstances: $includeTemplateInstances\n ) {\n activityName\n activityType\n status\n __typename\n }\n}",
207207
"d1ab35eb40e5193d9ef4a4563d1057e860db1b9774d82076ccc1c380350139ff": "query GetLecturerViewLiveQuiz($id: String!) {\n getLecturerViewLiveQuiz(id: $id) {\n id\n isLiveQAEnabled\n isConfusionFeedbackEnabled\n isModerationEnabled\n isGamificationEnabled\n confusionSummary {\n speed\n difficulty\n numberOfParticipants\n __typename\n }\n feedbacks {\n id\n isPublished\n isPinned\n isResolved\n content\n votes\n createdAt\n resolvedAt\n responses {\n id\n content\n positiveReactions\n negativeReactions\n createdAt\n __typename\n }\n __typename\n }\n __typename\n }\n}",
208-
"eac375272c244160b5b257a65899f12c76cf12d5dc6209eea98fc111f61acde2": "fragment EvaluationResults on ActivityEvaluation {\n results {\n stackId\n stackName\n stackDescription\n stackOrder\n instances {\n ... on ChoicesActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n choices {\n value\n count\n correct\n feedback\n __typename\n }\n __typename\n }\n }\n ... on NumericalActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n maxValue\n minValue\n solutionRanges {\n min\n max\n __typename\n }\n exactSolutions\n responseValues {\n value\n correct\n count\n __typename\n }\n __typename\n }\n statistics {\n max\n mean\n median\n min\n q1\n q3\n sd\n __typename\n }\n }\n ... on FreeTextActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n maxLength\n solutions\n responses {\n value\n correct\n count\n __typename\n }\n __typename\n }\n }\n ... on SelectionActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n numberOfInputs\n answerSolutionIds\n selectionResponses {\n answerId\n value\n count\n __typename\n }\n __typename\n }\n }\n ... on CaseStudyActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n cases {\n id\n name\n description\n __typename\n }\n items {\n id\n name\n __typename\n }\n criteria {\n id\n name\n labels {\n min\n mid\n max\n __typename\n }\n __typename\n }\n results {\n totalAnswers\n anonymousAnswers\n caseResults {\n caseId\n items {\n itemId\n criteria {\n criterionId\n name\n min\n max\n step\n unit\n solutionMin\n solutionMax\n statistics {\n min\n max\n mean\n median\n q1\n q3\n sd\n __typename\n }\n responses {\n value\n count\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n }\n ... on FlashcardActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n correctCount\n partialCount\n incorrectCount\n __typename\n }\n }\n ... on ContentActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n __typename\n }\n }\n __typename\n }\n __typename\n }\n __typename\n}\nquery GetLiveQuizEvaluation($id: String!, $hmac: String) {\n liveQuizEvaluation(id: $id, hmac: $hmac) {\n id\n name\n displayName\n description\n ...EvaluationResults\n feedbacks {\n id\n isPublished\n isPinned\n isResolved\n content\n votes\n resolvedAt\n createdAt\n responses {\n id\n createdAt\n content\n positiveReactions\n negativeReactions\n __typename\n }\n __typename\n }\n confusionFeedbacks {\n speed\n difficulty\n createdAt\n __typename\n }\n __typename\n }\n liveQuizLeaderboard(quizId: $id) {\n id\n participantId\n rank\n username\n avatar\n score\n isTemporary\n __typename\n }\n}",
208+
"af44e84fed3e7119587adad54e43b7cfcb84b6f53ca92b8821f2dbc5ec2b64f6": "fragment EvaluationResults on ActivityEvaluation {\n results {\n stackId\n stackName\n stackDescription\n stackOrder\n instances {\n ... on ChoicesActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n choices {\n value\n count\n correct\n feedback\n __typename\n }\n __typename\n }\n }\n ... on NumericalActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n maxValue\n minValue\n solutionRanges {\n min\n max\n __typename\n }\n exactSolutions\n responseValues {\n value\n correct\n count\n __typename\n }\n __typename\n }\n statistics {\n max\n mean\n median\n min\n q1\n q3\n sd\n __typename\n }\n }\n ... on FreeTextActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n maxLength\n solutions\n responses {\n value\n correct\n count\n __typename\n }\n __typename\n }\n }\n ... on SelectionActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n numberOfInputs\n answerSolutionIds\n selectionResponses {\n answerId\n value\n count\n __typename\n }\n __typename\n }\n }\n ... on CaseStudyActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n cases {\n id\n name\n description\n __typename\n }\n items {\n id\n name\n __typename\n }\n criteria {\n id\n name\n labels {\n min\n mid\n max\n __typename\n }\n __typename\n }\n results {\n totalAnswers\n anonymousAnswers\n caseResults {\n caseId\n items {\n itemId\n criteria {\n criterionId\n name\n min\n max\n step\n unit\n solutionMin\n solutionMax\n statistics {\n min\n max\n mean\n median\n q1\n q3\n sd\n __typename\n }\n responses {\n value\n count\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n }\n ... on FlashcardActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n correctCount\n partialCount\n incorrectCount\n __typename\n }\n }\n ... on ContentActivityEvaluationData {\n __typename\n id\n type\n name\n content\n explanation\n hasSampleSolution\n hasAnswerFeedbacks\n results {\n totalAnswers\n anonymousAnswers\n __typename\n }\n }\n __typename\n }\n __typename\n }\n __typename\n}\nquery GetLiveQuizEvaluation($id: String!, $hmac: String) {\n liveQuizEvaluation(id: $id, hmac: $hmac) {\n id\n name\n displayName\n description\n ...EvaluationResults\n feedbacks {\n id\n isPublished\n isPinned\n isResolved\n content\n votes\n resolvedAt\n createdAt\n responses {\n id\n createdAt\n content\n positiveReactions\n negativeReactions\n __typename\n }\n __typename\n }\n confusionFeedbacks {\n speed\n difficulty\n createdAt\n __typename\n }\n __typename\n }\n liveQuizLeaderboard(quizId: $id, hmac: $hmac) {\n id\n participantId\n rank\n username\n avatar\n score\n isTemporary\n __typename\n }\n}",
209209
"f172839e4e15bdd42039bd90c6f5ecace80f0d2ce00f34bedaceed9a4dbe39d4": "query GetLiveQuizHMAC($id: String!) {\n liveQuizHMAC(id: $id)\n}",
210210
"7f849e9a714a388215d6c7cce9582debe85faa83b11871b330de7261aaf6205d": "query GetLiveQuizLeaderboard($quizId: String!) {\n liveQuizLeaderboard(quizId: $quizId) {\n id\n participantId\n rank\n username\n avatar\n score\n isSelf\n lastBlockOrder\n __typename\n }\n}",
211211
"fd17c092be3d096abd6d31e89d8249bd4b482a2a6310f6893e9e32c5a70666c0": "query GetLiveQuizSummary($quizId: String!) {\n getLiveQuizSummary(quizId: $quizId) {\n numOfResponses\n numOfFeedbacks\n numOfConfusionFeedbacks\n numOfLeaderboardEntries\n __typename\n }\n}",

packages/graphql/src/schema/query.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ export const Query = builder.queryType({
732732
type: [LeaderboardEntry],
733733
args: {
734734
quizId: t.arg.string({ required: true }),
735+
hmac: t.arg.string({ required: false }),
735736
},
736737
resolve: async (_, args, ctx) => {
737738
return await LiveQuizService.getLiveQuizLeaderboard(args, ctx)

packages/graphql/src/services/liveQuizzes.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,7 +2333,7 @@ export async function getCourseRunningLiveQuizzes(
23332333
}
23342334

23352335
export async function getLiveQuizLeaderboard(
2336-
{ quizId }: { quizId: string },
2336+
{ quizId, hmac }: { quizId: string; hmac?: string | null },
23372337
ctx: Context
23382338
) {
23392339
const quiz = await ctx.prisma.liveQuiz.findUnique({
@@ -2363,12 +2363,24 @@ export async function getLiveQuizLeaderboard(
23632363
})
23642364
: null
23652365

2366-
const participantProfilePublic =
2366+
let participantProfilesVisible =
23672367
(participant?.isProfilePublic ?? false) ||
23682368
ctx.user?.role === DB.UserRole.TEMPORARY_PARTICIPANT ||
23692369
ctx.user?.role === DB.UserRole.USER ||
23702370
ctx.user?.role === DB.UserRole.ADMIN
23712371

2372+
// if a valid hmac is passed, the participant profile is also visible
2373+
if (typeof hmac === 'string' && hmac !== null && hmac !== '') {
2374+
const hmacEncoder = createHmac('sha256', process.env.APP_SECRET as string)
2375+
hmacEncoder.update(quiz.namespace + quiz.id)
2376+
const quizHmac = hmacEncoder.digest('hex')
2377+
2378+
// evaluate whether the hashed quiz.namespace and quiz.id equals the hmac
2379+
if (quizHmac === hmac) {
2380+
participantProfilesVisible = true
2381+
}
2382+
}
2383+
23722384
// find the order attribute of the last exectued block
23732385
const executedBlockOrders = quiz?.blocks
23742386
.filter((quizBlock) => quizBlock.status === DB.ElementBlockStatus.EXECUTED)
@@ -2388,11 +2400,11 @@ export async function getLiveQuizLeaderboard(
23882400
id: entry.id,
23892401
participantId: entry.participant.id,
23902402
username:
2391-
entry.participant.isProfilePublic && participantProfilePublic
2403+
entry.participant.isProfilePublic && participantProfilesVisible
23922404
? entry.participant.username
23932405
: 'Anonymous',
23942406
avatar:
2395-
entry.participant.isProfilePublic && participantProfilePublic
2407+
entry.participant.isProfilePublic && participantProfilesVisible
23962408
? entry.participant.avatar
23972409
: null,
23982410
score: entry.score,
@@ -2407,8 +2419,8 @@ export async function getLiveQuizLeaderboard(
24072419
return {
24082420
id: Math.floor(Math.random() * 1000000000), // generate a random large number for temporary leaderboard entries
24092421
participantId: entry.id,
2410-
username: participantProfilePublic ? entry.username : 'Anonymous',
2411-
avatar: participantProfilePublic ? entry.avatar : null,
2422+
username: participantProfilesVisible ? entry.username : 'Anonymous',
2423+
avatar: participantProfilesVisible ? entry.avatar : null,
24122424
score: entry.score,
24132425
level: 1, // temporary leaderboard entries do not have a experience points
24142426
// isSelf: entry.id === ctx.user.sub,

0 commit comments

Comments
 (0)