@gbl-uzh/platform 0.4.13 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/index.d.ts +7 -1473
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +11 -2012
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/SSELink.d.ts +9 -0
  7. package/dist/lib/SSELink.d.ts.map +1 -0
  8. package/dist/lib/SSELink.js +24 -0
  9. package/dist/lib/SSELink.js.map +1 -0
  10. package/dist/lib/apollo.d.ts +3 -4
  11. package/dist/lib/apollo.d.ts.map +1 -0
  12. package/dist/lib/apollo.js +50 -108
  13. package/dist/lib/apollo.js.map +1 -0
  14. package/dist/lib/constants.d.ts +8 -0
  15. package/dist/lib/constants.d.ts.map +1 -0
  16. package/dist/lib/logger.d.ts +4 -0
  17. package/dist/lib/logger.d.ts.map +1 -0
  18. package/dist/lib/logger.js +11 -0
  19. package/dist/lib/logger.js.map +1 -0
  20. package/dist/lib/pubsub.d.ts +5 -0
  21. package/dist/lib/pubsub.d.ts.map +1 -0
  22. package/dist/lib/pubsub.js +6 -0
  23. package/dist/lib/pubsub.js.map +1 -0
  24. package/dist/lib/util.d.ts +12 -13
  25. package/dist/lib/util.d.ts.map +1 -0
  26. package/dist/lib/util.js +63 -105
  27. package/dist/lib/util.js.map +1 -0
  28. package/dist/nexus.d.ts +11 -42
  29. package/dist/nexus.d.ts.map +1 -0
  30. package/dist/nexus.js +22 -2605
  31. package/dist/nexus.js.map +1 -0
  32. package/dist/ops/FGameData.graphql +1 -0
  33. package/dist/ops/FPeriodData.graphql +1 -0
  34. package/dist/ops/FPlayerData.graphql +0 -4
  35. package/dist/ops/FSegmentData.graphql +1 -0
  36. package/dist/ops/FStoryElementData.graphql +7 -0
  37. package/dist/ops/MActivateNextSegment.graphql +0 -3
  38. package/dist/ops/MAddGamePeriod.graphql +2 -2
  39. package/dist/ops/MUpdatePlayerData.graphql +1 -3
  40. package/dist/ops/QGame.graphql +2 -3
  41. package/dist/ops/QPastResults.graphql +0 -3
  42. package/dist/ops/QResult.graphql +4 -5
  43. package/dist/ops/QSpecificResults.graphql +11 -0
  44. package/dist/ops/QStoryElements.graphql +7 -0
  45. package/dist/schema.prisma +2 -3
  46. package/dist/services/AccountService.d.ts +65 -0
  47. package/dist/services/AccountService.d.ts.map +1 -0
  48. package/dist/services/AccountService.js +70 -0
  49. package/dist/services/AccountService.js.map +1 -0
  50. package/dist/services/EventService.d.ts +13 -0
  51. package/dist/services/EventService.d.ts.map +1 -0
  52. package/dist/services/EventService.js +180 -0
  53. package/dist/services/EventService.js.map +1 -0
  54. package/dist/services/GameService.d.ts +670 -0
  55. package/dist/services/GameService.d.ts.map +1 -0
  56. package/dist/services/GameService.js +1191 -0
  57. package/dist/services/GameService.js.map +1 -0
  58. package/dist/services/PlayService.d.ts +630 -0
  59. package/dist/services/PlayService.d.ts.map +1 -0
  60. package/dist/services/PlayService.js +534 -0
  61. package/dist/services/PlayService.js.map +1 -0
  62. package/dist/tsconfig.tsbuildinfo +1 -0
  63. package/dist/types/Achievement.d.ts +4 -0
  64. package/dist/types/Achievement.d.ts.map +1 -0
  65. package/dist/types/Achievement.js +35 -0
  66. package/dist/types/Achievement.js.map +1 -0
  67. package/dist/types/Game.d.ts +5 -0
  68. package/dist/types/Game.d.ts.map +1 -0
  69. package/dist/types/Game.js +85 -0
  70. package/dist/types/Game.js.map +1 -0
  71. package/dist/types/LearningElement.d.ts +5 -0
  72. package/dist/types/LearningElement.d.ts.map +1 -0
  73. package/dist/types/LearningElement.js +55 -0
  74. package/dist/types/LearningElement.js.map +1 -0
  75. package/dist/types/Mutation.d.ts +10 -0
  76. package/dist/types/Mutation.d.ts.map +1 -0
  77. package/dist/types/Mutation.js +194 -0
  78. package/dist/types/Mutation.js.map +1 -0
  79. package/dist/types/Player.d.ts +9 -0
  80. package/dist/types/Player.d.ts.map +1 -0
  81. package/dist/types/Player.js +139 -0
  82. package/dist/types/Player.js.map +1 -0
  83. package/dist/types/Query.d.ts +3 -0
  84. package/dist/types/Query.d.ts.map +1 -0
  85. package/dist/types/Query.js +91 -0
  86. package/dist/types/Query.js.map +1 -0
  87. package/dist/types/StoryElement.d.ts +3 -0
  88. package/dist/types/StoryElement.d.ts.map +1 -0
  89. package/dist/types/StoryElement.js +27 -0
  90. package/dist/types/StoryElement.js.map +1 -0
  91. package/dist/types/Subscription.d.ts +3 -0
  92. package/dist/types/Subscription.d.ts.map +1 -0
  93. package/dist/types/Subscription.js +35 -0
  94. package/dist/types/Subscription.js.map +1 -0
  95. package/dist/types.d.ts +139 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +26 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +42 -38
package/dist/index.js CHANGED
@@ -1,2012 +1,11 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- AccountService: () => AccountService_exports,
34
- BaseGlobalNotificationType: () => BaseGlobalNotificationType,
35
- BaseUserNotificationType: () => BaseUserNotificationType,
36
- EventService: () => EventService_exports,
37
- GameService: () => GameService_exports,
38
- LearningElementState: () => LearningElementState,
39
- PlayService: () => PlayService_exports,
40
- UserRole: () => UserRole,
41
- log: () => logger_default
42
- });
43
- module.exports = __toCommonJS(src_exports);
44
-
45
- // src/lib/logger.ts
46
- var import_winston = __toESM(require("winston"));
47
- var logger = import_winston.default.createLogger({
48
- level: process.env.NODE_ENV === "development" ? "debug" : "info",
49
- format: import_winston.default.format.combine(
50
- import_winston.default.format.timestamp(),
51
- import_winston.default.format.json()
52
- ),
53
- defaultMeta: { service: "@gbl-uzh/platform" },
54
- transports: [new import_winston.default.transports.Console()]
55
- });
56
- var logger_default = logger;
57
-
58
- // src/services/AccountService.ts
59
- var AccountService_exports = {};
60
- __export(AccountService_exports, {
61
- createLoginToken: () => createLoginToken,
62
- loginAsTeam: () => loginAsTeam,
63
- logoutAsTeam: () => logoutAsTeam
64
- });
65
- var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
66
- var import_node_assert = require("assert");
67
- var import_nookies = require("nookies");
68
-
69
- // src/types.ts
70
- var UserRole = /* @__PURE__ */ ((UserRole2) => {
71
- UserRole2["PLAYER"] = "PLAYER";
72
- UserRole2["ADMIN"] = "ADMIN";
73
- return UserRole2;
74
- })(UserRole || {});
75
- var LearningElementState = /* @__PURE__ */ ((LearningElementState2) => {
76
- LearningElementState2["NEW"] = "NEW";
77
- LearningElementState2["ATTEMPTED"] = "ATTEMPTED";
78
- LearningElementState2["SOLVED"] = "SOLVED";
79
- return LearningElementState2;
80
- })(LearningElementState || {});
81
- var BaseGlobalNotificationType = /* @__PURE__ */ ((BaseGlobalNotificationType2) => {
82
- BaseGlobalNotificationType2["PERIOD_ACTIVATED"] = "PERIOD_ACTIVATED";
83
- BaseGlobalNotificationType2["SEGMENT_ACTIVATED"] = "SEGMENT_ACTIVATED";
84
- return BaseGlobalNotificationType2;
85
- })(BaseGlobalNotificationType || {});
86
- var BaseUserNotificationType = /* @__PURE__ */ ((BaseUserNotificationType2) => {
87
- BaseUserNotificationType2["LEARNING_ELEMENT_SOLVED"] = "LEARNING_ELEMENT_SOLVED";
88
- BaseUserNotificationType2["LEARNING_ELEMENT_INCORRECT"] = "LEARNING_ELEMENT_INCORRECT";
89
- BaseUserNotificationType2["ACHIEVEMENT_RECEIVED"] = "ACHIEVEMENT_RECEIVED";
90
- BaseUserNotificationType2["LEVEL_UP"] = "LEVEL_UP";
91
- return BaseUserNotificationType2;
92
- })(BaseUserNotificationType || {});
93
-
94
- // src/services/AccountService.ts
95
- function createLoginToken({
96
- sub,
97
- role,
98
- ...extra
99
- }) {
100
- (0, import_node_assert.strict)(typeof process.env.NEXTAUTH_SECRET === "string");
101
- return import_jsonwebtoken.default.sign({ sub, role, ...extra }, process.env.NEXTAUTH_SECRET, {
102
- expiresIn: "1 week"
103
- });
104
- }
105
- async function loginAsTeam({ token }, ctx) {
106
- const matchingPlayer = await ctx.prisma.player.findUnique({
107
- where: { token },
108
- include: {
109
- level: true,
110
- achievements: {
111
- include: {
112
- achievement: true
113
- }
114
- }
115
- }
116
- });
117
- if (!matchingPlayer) {
118
- throw new Error("INVALID_TOKEN");
119
- }
120
- try {
121
- const jwt = createLoginToken({
122
- gameId: matchingPlayer.gameId,
123
- sub: matchingPlayer.id,
124
- role: "PLAYER" /* PLAYER */,
125
- token: matchingPlayer.token
126
- });
127
- const cookieName = process.env.NODE_ENV === "production" ? "__Secure-next-auth.session-token" : "next-auth.session-token";
128
- (0, import_nookies.setCookie)(ctx, cookieName, jwt, {
129
- path: "/",
130
- maxAge: 60 * 60 * 24 * 7,
131
- httpOnly: process.env.NODE_ENV === "production",
132
- secure: process.env.NODE_ENV === "production"
133
- });
134
- } catch (err) {
135
- console.error(err);
136
- return null;
137
- }
138
- return matchingPlayer;
139
- }
140
- async function logoutAsTeam(ctx) {
141
- if (!ctx.user?.sub)
142
- return false;
143
- const matchingPlayer = await ctx.prisma.player.findUnique({
144
- where: {
145
- id: ctx.user.sub
146
- }
147
- });
148
- if (matchingPlayer) {
149
- const cookieName = process.env.NODE_ENV === "production" ? "__Secure-next-auth.session-token" : "next-auth.session-token";
150
- (0, import_nookies.destroyCookie)(ctx, cookieName);
151
- return true;
152
- }
153
- return false;
154
- }
155
-
156
- // src/services/EventService.ts
157
- var EventService_exports = {};
158
- __export(EventService_exports, {
159
- publishGlobalNotification: () => publishGlobalNotification,
160
- publishUserNotification: () => publishUserNotification,
161
- receiveEvent: () => receiveEvent,
162
- receiveEvents: () => receiveEvents
163
- });
164
- var DB = __toESM(require("@prisma/client"));
165
-
166
- // src/lib/pubsub.ts
167
- var import_graphql_yoga = require("graphql-yoga");
168
- var pubSub = (0, import_graphql_yoga.createPubSub)();
169
-
170
- // src/services/EventService.ts
171
- async function receiveEvents({ events, ctx, prisma }) {
172
- if (!Array.isArray(events))
173
- return;
174
- const definedEvents = await prisma.event.findMany({
175
- include: {
176
- achievements: true
177
- }
178
- });
179
- const definedLevels = await prisma.playerLevel.findMany();
180
- const promises = await Promise.all(
181
- events.map(
182
- async (event) => receiveEvent({ ...event, ctx }, definedEvents, definedLevels, prisma)
183
- )
184
- );
185
- const results = prisma.$transaction(promises.flat());
186
- return results;
187
- }
188
- function prepareAchievementData({
189
- achievementId,
190
- gameId,
191
- periodIx,
192
- playerId,
193
- count
194
- }) {
195
- return {
196
- count,
197
- achievement: { connect: { id: achievementId } },
198
- game: { connect: { id: gameId } },
199
- period: { connect: { gameId_index: { gameId, index: periodIx } } },
200
- player: { connect: { id: playerId } }
201
- };
202
- }
203
- async function receiveEvent(event, definedEvents, definedLevels, prisma) {
204
- const matchingEvent = definedEvents.find((item) => item.id === event.type);
205
- if (matchingEvent && matchingEvent.achievements?.length > 0) {
206
- const awardedAchievements = await matchingEvent.achievements.reduce(
207
- async (acc, achievement) => {
208
- if (achievement.when === DB.AchievementFrequency.FIRST && event.ctx.achievements.includes(achievement.id)) {
209
- return acc;
210
- }
211
- const existingInstance = await prisma.achievementInstance.findFirst({
212
- where: {
213
- achievement: {
214
- id: achievement.id
215
- },
216
- player: {
217
- id: event.ctx.args.playerId
218
- }
219
- }
220
- });
221
- let achievementInstance;
222
- if (existingInstance) {
223
- achievementInstance = await prisma.achievementInstance.update({
224
- where: {
225
- id: existingInstance.id
226
- },
227
- data: prepareAchievementData({
228
- count: existingInstance.count + 1,
229
- achievementId: achievement.id,
230
- gameId: event.ctx.args.gameId,
231
- periodIx: event.ctx.args.periodIx,
232
- playerId: event.ctx.args.playerId
233
- })
234
- });
235
- } else {
236
- achievementInstance = await prisma.achievementInstance.create({
237
- data: prepareAchievementData({
238
- count: 1,
239
- achievementId: achievement.id,
240
- gameId: event.ctx.args.gameId,
241
- periodIx: event.ctx.args.periodIx,
242
- playerId: event.ctx.args.playerId
243
- })
244
- });
245
- }
246
- return {
247
- achievements: [
248
- ...acc.achievements,
249
- {
250
- achievement,
251
- achievementInstance
252
- }
253
- ],
254
- achievementKeys: [...acc.achievementKeys, achievement.id],
255
- rewards: {
256
- ...acc.rewards,
257
- xp: (acc.rewards.xp ?? 0) + (achievement.reward?.xp ?? 0)
258
- }
259
- };
260
- },
261
- {
262
- achievements: [],
263
- achievementKeys: [],
264
- rewards: {}
265
- }
266
- );
267
- const currentLevelPlus1 = definedLevels.find(
268
- (level) => level.index === event.ctx.currentLevelIx + 1
269
- );
270
- const currentLevelPlus2 = definedLevels.find(
271
- (level) => level.index === event.ctx.currentLevelIx + 2
272
- );
273
- if (awardedAchievements.achievements.length > 0) {
274
- if (event.ctx.experience + awardedAchievements.rewards.xp >= currentLevelPlus1.requiredXP) {
275
- publishUserNotification(
276
- {
277
- user: {
278
- sub: event.ctx.args.playerId
279
- }
280
- },
281
- [
282
- {
283
- type: "ACHIEVEMENT_RECEIVED" /* ACHIEVEMENT_RECEIVED */
284
- }
285
- ]
286
- );
287
- publishUserNotification(
288
- {
289
- user: {
290
- sub: event.ctx.args.playerId
291
- }
292
- },
293
- [
294
- {
295
- type: "LEVEL_UP" /* LEVEL_UP */
296
- }
297
- ]
298
- );
299
- return [
300
- prisma.player.update({
301
- where: {
302
- id: event.ctx.args.playerId
303
- },
304
- data: {
305
- experience: {
306
- increment: awardedAchievements.rewards.xp
307
- },
308
- experienceToNext: currentLevelPlus2.requiredXP,
309
- level: {
310
- connect: {
311
- index: currentLevelPlus1.index
312
- }
313
- },
314
- achievementKeys: {
315
- push: awardedAchievements.achievementKeys
316
- }
317
- }
318
- })
319
- ];
320
- } else {
321
- publishUserNotification(
322
- {
323
- user: {
324
- sub: event.ctx.args.playerId
325
- }
326
- },
327
- [
328
- {
329
- type: "ACHIEVEMENT_RECEIVED" /* ACHIEVEMENT_RECEIVED */
330
- }
331
- ]
332
- );
333
- return [
334
- prisma.player.update({
335
- where: {
336
- id: event.ctx.args.playerId
337
- },
338
- data: {
339
- experience: {
340
- increment: awardedAchievements.rewards.xp
341
- },
342
- achievementKeys: {
343
- push: awardedAchievements.achievementKeys
344
- }
345
- }
346
- })
347
- ];
348
- }
349
- }
350
- return [];
351
- }
352
- return [];
353
- }
354
- function publishGlobalNotification(event) {
355
- pubSub.publish("global:events", event);
356
- }
357
- function publishUserNotification(ctx, events) {
358
- if (events && events.length > 0) {
359
- pubSub.publish("user:events", ctx.user.sub, events);
360
- }
361
- }
362
-
363
- // src/services/GameService.ts
364
- var GameService_exports = {};
365
- __export(GameService_exports, {
366
- activateNextPeriod: () => activateNextPeriod,
367
- activateNextSegment: () => activateNextSegment,
368
- addGamePeriod: () => addGamePeriod,
369
- addPeriodSegment: () => addPeriodSegment,
370
- computePeriodEndResults: () => computePeriodEndResults,
371
- computePeriodStartResults: () => computePeriodStartResults,
372
- computeSegmentEndResults: () => computeSegmentEndResults,
373
- computeSegmentStartResults: () => computeSegmentStartResults,
374
- createGame: () => createGame,
375
- getGame: () => getGame,
376
- getGameFromContext: () => getGameFromContext,
377
- getGames: () => getGames,
378
- getLearningElements: () => getLearningElements,
379
- updatePlayerData: () => updatePlayerData
380
- });
381
- var DB2 = __toESM(require("@prisma/client"));
382
- var import_nanoid = require("nanoid");
383
- var import_ramda = require("ramda");
384
- var yup = __toESM(require("yup"));
385
- async function createGame({ name, playerCount }, ctx, { roleAssigner }) {
386
- return ctx.prisma.game.create({
387
- data: {
388
- name,
389
- owner: {
390
- connect: {
391
- id: ctx.user.sub
392
- }
393
- },
394
- players: {
395
- create: (0, import_ramda.repeat)(0, playerCount).map((_, ix) => {
396
- return {
397
- facts: {},
398
- token: (0, import_nanoid.nanoid)(),
399
- role: roleAssigner(ix),
400
- number: playerCount - ix,
401
- name: `Team ${playerCount - ix}`,
402
- level: {
403
- connect: {
404
- index: 0
405
- }
406
- }
407
- };
408
- })
409
- }
410
- },
411
- include: {
412
- players: true,
413
- periods: true
414
- }
415
- });
416
- }
417
- async function addGamePeriod({ gameId, facts }, ctx, { schema, reducers }) {
418
- const validatedFacts = schema.validateSync(facts);
419
- const game = await ctx.prisma.game.findUnique({
420
- where: {
421
- id: gameId
422
- },
423
- include: {
424
- periods: {
425
- orderBy: {
426
- index: "desc"
427
- },
428
- take: 1,
429
- include: {
430
- segments: {
431
- orderBy: {
432
- index: "desc"
433
- },
434
- take: 1
435
- }
436
- }
437
- }
438
- }
439
- });
440
- if (!game)
441
- return null;
442
- const index = game.periods[0]?.index + 1 || 0;
443
- const { result: initializedFacts } = reducers.Period.apply(validatedFacts, {
444
- type: reducers.Period.ActionTypes.PERIOD_INITIALIZE,
445
- payload: {
446
- periodFacts: validatedFacts,
447
- previousPeriodFacts: game.periods[0]?.facts,
448
- previousSegmentFacts: game.periods[0]?.segments[0]?.facts,
449
- periodIx: index
450
- }
451
- });
452
- console.log(
453
- game.periods[0]?.facts,
454
- game.periods[0]?.segments[0]?.facts,
455
- initializedFacts
456
- );
457
- return ctx.prisma.period.upsert({
458
- where: {
459
- gameId_index: {
460
- gameId,
461
- index
462
- }
463
- },
464
- create: {
465
- index,
466
- facts: initializedFacts,
467
- game: {
468
- connect: {
469
- id: gameId
470
- }
471
- },
472
- previousPeriod: {
473
- connect: index > 0 ? {
474
- gameId_index: {
475
- gameId,
476
- index: index - 1
477
- }
478
- } : []
479
- }
480
- },
481
- update: {
482
- facts: initializedFacts
483
- },
484
- include: {
485
- segments: {
486
- include: {
487
- learningElements: true,
488
- storyElements: true
489
- }
490
- }
491
- }
492
- });
493
- }
494
- async function addPeriodSegment({
495
- gameId,
496
- periodIx,
497
- facts,
498
- learningElements,
499
- storyElements
500
- }, ctx, { schema, reducers }) {
501
- const validatedFacts = schema.validateSync(facts);
502
- const period = await ctx.prisma.period.findUnique({
503
- where: {
504
- gameId_index: {
505
- gameId,
506
- index: periodIx
507
- }
508
- },
509
- include: {
510
- segments: {
511
- orderBy: {
512
- index: "desc"
513
- },
514
- take: 1
515
- }
516
- }
517
- });
518
- if (!period)
519
- return null;
520
- const index = period.segments[0]?.index + 1 || 0;
521
- const { result: initializedFacts } = reducers.Segment.apply(validatedFacts, {
522
- type: reducers.Segment.ActionTypes.SEGMENT_INITIALIZE,
523
- payload: {
524
- periodFacts: period.facts,
525
- previousSegmentFacts: period.segments[0]?.facts,
526
- segmentIx: index,
527
- segmentCount: 4,
528
- periodIx
529
- }
530
- });
531
- return ctx.prisma.periodSegment.upsert({
532
- where: {
533
- gameId_periodIx_index: {
534
- gameId,
535
- periodIx,
536
- index
537
- }
538
- },
539
- create: {
540
- index,
541
- facts: initializedFacts,
542
- learningElements: {
543
- connect: learningElements ? learningElements.map((item) => ({ id: item })) : []
544
- },
545
- storyElements: {
546
- connect: storyElements ? storyElements.map((item) => ({ id: item })) : []
547
- },
548
- game: {
549
- connect: {
550
- id: gameId
551
- }
552
- },
553
- periodIx,
554
- period: {
555
- connect: {
556
- gameId_index: {
557
- gameId,
558
- index: periodIx
559
- }
560
- }
561
- },
562
- previousSegment: {
563
- connect: index > 0 ? {
564
- gameId_periodIx_index: {
565
- gameId,
566
- periodIx,
567
- index: index - 1
568
- }
569
- } : []
570
- }
571
- },
572
- update: {
573
- facts: initializedFacts,
574
- learningElements: {
575
- connect: learningElements ? learningElements.map((item) => ({ id: item })) : []
576
- },
577
- storyElements: {
578
- connect: storyElements ? storyElements.map((item) => ({ id: item })) : []
579
- }
580
- },
581
- include: {
582
- learningElements: true,
583
- storyElements: true
584
- }
585
- });
586
- }
587
- async function activateNextPeriod({ gameId }, ctx, { reducers }) {
588
- logger_default.info("activating next period");
589
- const game = await ctx.prisma.game.findUnique({
590
- where: {
591
- id: gameId
592
- },
593
- include: {
594
- players: true,
595
- periods: true,
596
- activePeriod: {
597
- include: {
598
- results: {
599
- include: {
600
- player: true
601
- }
602
- },
603
- nextPeriod: true,
604
- previousPeriod: {
605
- include: {
606
- results: {
607
- include: {
608
- player: true
609
- }
610
- }
611
- }
612
- },
613
- activeSegment: {
614
- include: {
615
- results: {
616
- include: {
617
- player: true
618
- }
619
- }
620
- }
621
- },
622
- decisions: {
623
- include: {
624
- player: true
625
- }
626
- }
627
- }
628
- }
629
- }
630
- });
631
- if (!game)
632
- return null;
633
- logger_default.info(`game found, status ${game.status}`);
634
- const currentPeriodIx = game.activePeriodIx;
635
- const currentSegmentIx = game.activePeriod?.activeSegmentIx;
636
- const nextPeriodIx = currentPeriodIx + 1;
637
- switch (game.status) {
638
- case DB2.GameStatus.SCHEDULED: {
639
- const { results, extras } = computePeriodStartResults(
640
- {
641
- results: void 0,
642
- players: game.players,
643
- activePeriodIx: currentPeriodIx,
644
- gameId: game.id,
645
- periodFacts: game.periods?.[0].facts
646
- },
647
- ctx,
648
- { reducers }
649
- );
650
- const result = await ctx.prisma.$transaction([
651
- ctx.prisma.game.update({
652
- where: {
653
- id: gameId
654
- },
655
- include: {
656
- periods: {
657
- include: {
658
- segments: true
659
- }
660
- }
661
- },
662
- data: {
663
- status: DB2.GameStatus.PREPARATION,
664
- activePeriodIx: nextPeriodIx,
665
- activePeriod: {
666
- connect: {
667
- gameId_index: {
668
- gameId,
669
- index: nextPeriodIx
670
- }
671
- }
672
- }
673
- }
674
- }),
675
- ctx.prisma.period.update({
676
- where: {
677
- gameId_index: {
678
- gameId,
679
- index: nextPeriodIx
680
- }
681
- },
682
- data: {
683
- results: {
684
- create: results
685
- }
686
- }
687
- }),
688
- ...extras
689
- ]);
690
- return result;
691
- }
692
- case DB2.GameStatus.RUNNING: {
693
- if (!game.activePeriod?.activeSegment || !currentSegmentIx)
694
- return null;
695
- const { results, extras } = computeSegmentEndResults(game, ctx, {
696
- reducers
697
- });
698
- const { result: consolidatedFacts } = reducers.Period.apply(
699
- game.activePeriod.facts,
700
- {
701
- type: reducers.Period.ActionTypes.PERIOD_CONSOLIDATE,
702
- payload: {
703
- previousSegmentFacts: game.activePeriod.activeSegment.facts,
704
- periodIx: currentPeriodIx
705
- }
706
- }
707
- );
708
- const result = await ctx.prisma.$transaction([
709
- ctx.prisma.game.update({
710
- data: {
711
- status: DB2.GameStatus.CONSOLIDATION
712
- },
713
- include: {
714
- periods: {
715
- include: {
716
- segments: true
717
- }
718
- }
719
- },
720
- where: {
721
- id: gameId
722
- }
723
- }),
724
- ctx.prisma.period.update({
725
- where: {
726
- gameId_index: {
727
- gameId,
728
- index: currentPeriodIx
729
- }
730
- },
731
- data: {
732
- facts: consolidatedFacts
733
- }
734
- }),
735
- ctx.prisma.periodSegment.update({
736
- where: {
737
- gameId_periodIx_index: {
738
- gameId,
739
- periodIx: currentPeriodIx,
740
- index: currentSegmentIx
741
- }
742
- },
743
- data: {
744
- results: {
745
- // compute SEGMENT_END results using model
746
- update: results
747
- }
748
- },
749
- include: {
750
- results: {
751
- include: {
752
- player: true
753
- }
754
- }
755
- }
756
- }),
757
- ...extras
758
- ]);
759
- return result;
760
- }
761
- case DB2.GameStatus.CONSOLIDATION: {
762
- if (!game.activePeriod?.activeSegment)
763
- return null;
764
- const { results, extras, promises } = await computePeriodEndResults(
765
- {
766
- segmentResults: game.activePeriod.activeSegment.results,
767
- segmentFacts: game.activePeriod.activeSegment.facts,
768
- periodFacts: game.activePeriod.facts,
769
- periodDecisions: game.activePeriod.decisions,
770
- activePeriodIx: currentPeriodIx,
771
- activeSegmentIx: currentSegmentIx,
772
- gameId: game.id
773
- },
774
- ctx,
775
- { reducers }
776
- );
777
- await Promise.all(promises);
778
- const result = await ctx.prisma.$transaction([
779
- // update the status and active period of the current game
780
- ctx.prisma.game.update({
781
- where: {
782
- id: gameId
783
- },
784
- include: {
785
- periods: {
786
- include: {
787
- segments: true
788
- }
789
- }
790
- },
791
- data: {
792
- status: DB2.GameStatus.RESULTS,
793
- activePeriodIx: nextPeriodIx,
794
- activePeriod: {
795
- connect: {
796
- gameId_index: {
797
- gameId,
798
- index: nextPeriodIx
799
- }
800
- }
801
- }
802
- }
803
- }),
804
- // create PERIOD_END results based on the previous SEGMENT_END results
805
- ctx.prisma.period.update({
806
- where: {
807
- gameId_index: {
808
- gameId,
809
- index: currentPeriodIx
810
- }
811
- },
812
- data: {
813
- results: {
814
- create: results
815
- }
816
- },
817
- include: {
818
- results: true
819
- }
820
- }),
821
- ...extras
822
- ]);
823
- return result;
824
- }
825
- case DB2.GameStatus.RESULTS: {
826
- if (!game.activePeriod) {
827
- logger_default.warn("no next period available");
828
- return null;
829
- }
830
- const { results, extras } = computePeriodStartResults(
831
- {
832
- results: game.activePeriod.previousPeriod[0].results,
833
- players: game.players,
834
- activePeriodIx: currentPeriodIx,
835
- gameId: game.id,
836
- periodFacts: game.activePeriod.facts
837
- },
838
- ctx,
839
- { reducers }
840
- );
841
- const result = await ctx.prisma.$transaction([
842
- // update the status and active period of the current game
843
- ctx.prisma.game.update({
844
- where: {
845
- id: gameId
846
- },
847
- include: {
848
- periods: {
849
- include: {
850
- segments: true
851
- }
852
- }
853
- },
854
- data: {
855
- status: DB2.GameStatus.PREPARATION
856
- }
857
- }),
858
- // create PERIOD_START results based on the previous PERIOD_END results
859
- ctx.prisma.period.update({
860
- where: {
861
- gameId_index: {
862
- gameId,
863
- index: currentPeriodIx
864
- }
865
- },
866
- data: {
867
- results: {
868
- create: results
869
- }
870
- }
871
- }),
872
- ...extras
873
- ]);
874
- return result;
875
- }
876
- default:
877
- return null;
878
- }
879
- }
880
- async function activateNextSegment({ gameId }, ctx, { reducers }) {
881
- const game = await ctx.prisma.game.findUnique({
882
- where: {
883
- id: gameId
884
- },
885
- include: {
886
- players: true,
887
- periods: true,
888
- segments: true,
889
- activePeriod: {
890
- include: {
891
- results: {
892
- include: {
893
- player: true
894
- }
895
- },
896
- activeSegment: {
897
- include: {
898
- nextSegment: true,
899
- results: {
900
- include: {
901
- player: true
902
- }
903
- }
904
- }
905
- }
906
- }
907
- }
908
- }
909
- });
910
- if (!game?.activePeriod)
911
- return null;
912
- const currentPeriodIx = game.activePeriodIx;
913
- const currentSegmentIx = game.activePeriod.activeSegmentIx;
914
- const nextSegmentIx = currentSegmentIx + 1;
915
- switch (game.status) {
916
- case DB2.GameStatus.PREPARATION:
917
- case DB2.GameStatus.PAUSED: {
918
- const { results, extras } = computeSegmentStartResults(game, ctx, {
919
- reducers
920
- });
921
- const result = await ctx.prisma.$transaction([
922
- ctx.prisma.game.update({
923
- where: {
924
- id: gameId
925
- },
926
- include: {
927
- periods: {
928
- include: {
929
- segments: true
930
- }
931
- },
932
- players: true
933
- },
934
- data: {
935
- status: DB2.GameStatus.RUNNING
936
- }
937
- }),
938
- // update the active segment of the current period
939
- ctx.prisma.period.update({
940
- where: {
941
- gameId_index: {
942
- gameId,
943
- index: currentPeriodIx
944
- }
945
- },
946
- data: {
947
- activeSegmentIx: nextSegmentIx,
948
- activeSegment: {
949
- connect: {
950
- gameId_periodIx_index: {
951
- gameId,
952
- periodIx: currentPeriodIx,
953
- index: nextSegmentIx
954
- }
955
- }
956
- }
957
- }
958
- }),
959
- // SEGMENT INITIALIZATION
960
- ctx.prisma.periodSegment.update({
961
- where: {
962
- gameId_periodIx_index: {
963
- gameId,
964
- periodIx: currentPeriodIx,
965
- index: nextSegmentIx
966
- }
967
- },
968
- data: {
969
- results: {
970
- create: results
971
- }
972
- }
973
- }),
974
- ...extras
975
- ]);
976
- return result;
977
- }
978
- case DB2.GameStatus.RUNNING: {
979
- if (!game.activePeriod?.activeSegment?.nextSegment) {
980
- return null;
981
- }
982
- const { results, extras } = computeSegmentEndResults(game, ctx, {
983
- reducers
984
- });
985
- const result = await ctx.prisma.$transaction([
986
- ctx.prisma.game.update({
987
- where: { id: gameId },
988
- include: {
989
- periods: {
990
- include: {
991
- segments: true
992
- }
993
- },
994
- players: true
995
- },
996
- data: {
997
- status: DB2.GameStatus.PAUSED
998
- }
999
- }),
1000
- ctx.prisma.periodSegment.update({
1001
- where: {
1002
- gameId_periodIx_index: {
1003
- gameId,
1004
- periodIx: currentPeriodIx,
1005
- index: currentSegmentIx
1006
- }
1007
- },
1008
- data: {
1009
- results: {
1010
- update: results
1011
- }
1012
- },
1013
- include: {
1014
- results: true
1015
- }
1016
- }),
1017
- // reset player readiness
1018
- ctx.prisma.player.updateMany({
1019
- where: {
1020
- game: {
1021
- id: gameId
1022
- }
1023
- },
1024
- data: {
1025
- isReady: false
1026
- }
1027
- }),
1028
- ...extras
1029
- ]);
1030
- return result;
1031
- }
1032
- default:
1033
- return null;
1034
- }
1035
- }
1036
- var PlayerFactsSchema = yup.object({
1037
- location: yup.string().default("Zurich")
1038
- });
1039
- async function updatePlayerData({ name, avatar, color, facts }, ctx) {
1040
- if ((0, import_ramda.none)(Boolean, [name, avatar, color, facts])) {
1041
- return null;
1042
- }
1043
- let data = {};
1044
- if (name) {
1045
- data["name"] = name;
1046
- }
1047
- if (avatar) {
1048
- data["avatar"] = avatar;
1049
- }
1050
- if (color) {
1051
- data["color"] = color;
1052
- }
1053
- if (facts) {
1054
- data["facts"] = PlayerFactsSchema.validateSync(facts, {
1055
- stripUnknown: true
1056
- });
1057
- }
1058
- const player = await ctx.prisma.player.update({
1059
- where: {
1060
- id: ctx.user.sub
1061
- },
1062
- data,
1063
- include: {
1064
- level: true,
1065
- achievements: {
1066
- include: {
1067
- achievement: true
1068
- }
1069
- }
1070
- }
1071
- });
1072
- return player;
1073
- }
1074
- async function getGames(args, ctx) {
1075
- return ctx.prisma.game.findMany();
1076
- }
1077
- async function getGame(args, ctx) {
1078
- const gameId = args.id ?? ctx.user.gameId;
1079
- if (!gameId) {
1080
- return null;
1081
- }
1082
- return ctx.prisma.game.findUnique({
1083
- where: {
1084
- id: args.id
1085
- },
1086
- include: {
1087
- players: {
1088
- include: {
1089
- level: true,
1090
- achievements: true
1091
- },
1092
- orderBy: {
1093
- number: "asc"
1094
- }
1095
- },
1096
- periods: {
1097
- orderBy: {
1098
- index: "asc"
1099
- },
1100
- include: {
1101
- segments: {
1102
- orderBy: {
1103
- index: "asc"
1104
- },
1105
- include: {
1106
- learningElements: true,
1107
- storyElements: true
1108
- }
1109
- }
1110
- }
1111
- },
1112
- activePeriod: {
1113
- include: {
1114
- segments: true,
1115
- activeSegment: true
1116
- }
1117
- }
1118
- }
1119
- });
1120
- }
1121
- async function getGameFromContext(ctx) {
1122
- return ctx.prisma.game.findUnique({
1123
- where: {
1124
- id: ctx.user.gameId
1125
- },
1126
- include: {
1127
- activePeriod: true
1128
- }
1129
- });
1130
- }
1131
- async function getLearningElements(args, ctx) {
1132
- return ctx.prisma.learningElement.findMany({
1133
- include: {
1134
- options: true
1135
- }
1136
- });
1137
- }
1138
- function mapAction({ ctx, gameId, activePeriodIx, playerId }) {
1139
- return (action) => ctx.prisma.playerAction.create({
1140
- data: {
1141
- type: action.type,
1142
- facts: action.facts,
1143
- game: {
1144
- connect: { id: gameId }
1145
- },
1146
- player: {
1147
- connect: { id: playerId }
1148
- },
1149
- periodIx: activePeriodIx,
1150
- period: {
1151
- connect: {
1152
- gameId_index: {
1153
- gameId,
1154
- index: activePeriodIx
1155
- }
1156
- }
1157
- },
1158
- segmentIx: typeof action.segment === "number" ? action.segment : void 0,
1159
- segment: typeof action.segment === "number" ? {
1160
- connect: {
1161
- gameId_periodIx_index: {
1162
- gameId,
1163
- periodIx: activePeriodIx,
1164
- index: action.segment
1165
- }
1166
- }
1167
- } : void 0
1168
- }
1169
- });
1170
- }
1171
- function computePeriodStartResults({ results, players, activePeriodIx, gameId, periodFacts }, ctx, { reducers }) {
1172
- const currentPeriodIx = activePeriodIx;
1173
- const nextPeriodIx = currentPeriodIx + 1;
1174
- let extras = [];
1175
- if (currentPeriodIx >= 0) {
1176
- const result2 = results.filter((result3) => result3.type === DB2.PlayerResultType.PERIOD_END).map((result3, ix, allResults) => {
1177
- const { result: facts, actions } = reducers.PeriodResult.apply(result3, {
1178
- type: reducers.PeriodResult.ActionTypes.PERIOD_RESULTS_START,
1179
- payload: {
1180
- playerRole: result3.player.role ?? result3.player.connect.role,
1181
- periodFacts
1182
- }
1183
- });
1184
- const mapper = mapAction({
1185
- ctx,
1186
- gameId,
1187
- activePeriodIx: currentPeriodIx,
1188
- playerId: result3.player.id
1189
- });
1190
- if (actions && actions.length > 0) {
1191
- extras = [...extras, ...actions.map(mapper)];
1192
- }
1193
- return {
1194
- type: DB2.PlayerResultType.PERIOD_START,
1195
- periodIx: currentPeriodIx,
1196
- facts,
1197
- player: {
1198
- connect: {
1199
- id: result3.player.id ?? result3.player.connect.id
1200
- }
1201
- },
1202
- game: {
1203
- connect: {
1204
- id: gameId
1205
- }
1206
- }
1207
- };
1208
- });
1209
- return {
1210
- results: result2,
1211
- extras
1212
- };
1213
- }
1214
- const result = players.map((player, ix, allPlayers) => {
1215
- const { result: facts, actions } = reducers.PeriodResult.apply(
1216
- {},
1217
- {
1218
- type: reducers.PeriodResult.ActionTypes.PERIOD_RESULTS_INITIALIZE,
1219
- payload: {
1220
- playerRole: player.role,
1221
- periodFacts
1222
- }
1223
- }
1224
- );
1225
- const mapper = mapAction({
1226
- ctx,
1227
- gameId,
1228
- activePeriodIx: nextPeriodIx,
1229
- playerId: player.id
1230
- });
1231
- if (actions && actions.length > 0) {
1232
- extras = [...extras, ...actions.map(mapper)];
1233
- }
1234
- return {
1235
- type: DB2.PlayerResultType.PERIOD_START,
1236
- periodIx: nextPeriodIx,
1237
- facts,
1238
- player: {
1239
- connect: {
1240
- id: player.id
1241
- }
1242
- },
1243
- game: {
1244
- connect: {
1245
- id: gameId
1246
- }
1247
- }
1248
- };
1249
- });
1250
- return {
1251
- results: result,
1252
- extras
1253
- };
1254
- }
1255
- async function computePeriodEndResults({
1256
- segmentResults,
1257
- periodFacts,
1258
- periodDecisions,
1259
- segmentFacts,
1260
- activePeriodIx,
1261
- activeSegmentIx,
1262
- gameId
1263
- }, ctx, { reducers }) {
1264
- let extras = [];
1265
- let promises = [];
1266
- const results = segmentResults.filter((result) => result.type === DB2.PlayerResultType.SEGMENT_END).map((result, ix, allResults) => {
1267
- const consolidationDecisions = periodDecisions.find(
1268
- (decision) => decision.playerId === result.playerId
1269
- );
1270
- const {
1271
- result: facts,
1272
- actions,
1273
- events
1274
- } = reducers.PeriodResult.apply(result.facts, {
1275
- type: reducers.PeriodResult.ActionTypes.PERIOD_RESULTS_END,
1276
- payload: {
1277
- periodFacts,
1278
- segmentFacts,
1279
- playerRole: result.player.role,
1280
- playerLevel: result.player.levelIx + 1,
1281
- playerExperience: result.player.experience,
1282
- consolidationDecisions,
1283
- periodIx: activePeriodIx,
1284
- segmentIx: activeSegmentIx
1285
- }
1286
- });
1287
- logger_default.debug(actions);
1288
- const mapper = mapAction({
1289
- ctx,
1290
- gameId,
1291
- activePeriodIx,
1292
- playerId: result.player.id
1293
- });
1294
- if (actions && actions.length > 0) {
1295
- extras = [...extras, ...actions.map(mapper)];
1296
- }
1297
- promises = [
1298
- ...promises,
1299
- receiveEvents({
1300
- events,
1301
- ctx: {
1302
- args: {
1303
- playerId: result.player.id,
1304
- periodIx: activePeriodIx,
1305
- gameId
1306
- },
1307
- user: ctx.user,
1308
- achievements: result.player.achievementKeys,
1309
- experience: result.player.experience,
1310
- currentLevelIx: result.player.levelIx
1311
- },
1312
- prisma: ctx.prisma
1313
- })
1314
- ];
1315
- return {
1316
- type: DB2.PlayerResultType.PERIOD_END,
1317
- periodIx: activePeriodIx,
1318
- facts,
1319
- player: {
1320
- connect: {
1321
- id: result.playerId
1322
- }
1323
- },
1324
- game: {
1325
- connect: {
1326
- id: gameId
1327
- }
1328
- }
1329
- };
1330
- });
1331
- return {
1332
- extras,
1333
- results,
1334
- promises
1335
- };
1336
- }
1337
- function computeSegmentStartResults(game, ctx, { reducers }) {
1338
- const currentSegmentIx = game.activePeriod.activeSegmentIx;
1339
- const nextSegmentIx = currentSegmentIx + 1;
1340
- let extras = [];
1341
- if (currentSegmentIx >= 0) {
1342
- const results2 = game.activePeriod.activeSegment.results.filter((result) => result.type === DB2.PlayerResultType.SEGMENT_END).reduce((acc, result, ix, allResults) => {
1343
- const { result: facts, actions } = reducers.SegmentResult.apply(
1344
- result.facts,
1345
- {
1346
- type: reducers.SegmentResult.ActionTypes.SEGMENT_RESULTS_START,
1347
- payload: {
1348
- playerRole: result.player.role,
1349
- periodFacts: game.activePeriod.facts,
1350
- segmentFacts: game.activePeriod.activeSegment.facts,
1351
- nextSegmentFacts: game.activePeriod.activeSegment.nextSegment?.facts,
1352
- segmentIx: nextSegmentIx
1353
- }
1354
- }
1355
- );
1356
- const mapper = mapAction({
1357
- ctx,
1358
- gameId: game.id,
1359
- activePeriodIx: game.activePeriodIx,
1360
- playerId: result.player.id
1361
- });
1362
- if (actions && actions.length > 0) {
1363
- extras = [...extras, ...actions.map(mapper)];
1364
- }
1365
- const common = {
1366
- facts,
1367
- periodIx: game.activePeriodIx,
1368
- segmentIx: nextSegmentIx,
1369
- player: {
1370
- connect: {
1371
- id: result.playerId
1372
- }
1373
- },
1374
- period: {
1375
- connect: {
1376
- id: game.activePeriodId
1377
- }
1378
- },
1379
- game: {
1380
- connect: {
1381
- id: game.id
1382
- }
1383
- }
1384
- };
1385
- return [
1386
- ...acc,
1387
- {
1388
- ...common,
1389
- type: DB2.PlayerResultType.SEGMENT_START
1390
- },
1391
- {
1392
- ...common,
1393
- type: DB2.PlayerResultType.SEGMENT_END
1394
- }
1395
- ];
1396
- }, []);
1397
- return {
1398
- results: results2,
1399
- extras
1400
- };
1401
- }
1402
- const results = game.activePeriod.results.filter((result) => result.type === DB2.PlayerResultType.PERIOD_START).reduce((acc, result, ix, allResults) => {
1403
- const { result: facts } = reducers.SegmentResult.apply(result.facts, {
1404
- type: reducers.SegmentResult.ActionTypes.SEGMENT_RESULTS_INITIALIZE,
1405
- payload: {
1406
- playerRole: result.player.role,
1407
- periodFacts: game.activePeriod.facts,
1408
- segmentFacts: game.activePeriod.activeSegment?.facts,
1409
- nextSegmentFacts: game.activePeriod.activeSegment?.nextSegment?.facts,
1410
- segmentIx: nextSegmentIx
1411
- }
1412
- });
1413
- const common = {
1414
- facts,
1415
- periodIx: game.activePeriodIx,
1416
- segmentIx: nextSegmentIx,
1417
- player: {
1418
- connect: {
1419
- id: result.playerId
1420
- }
1421
- },
1422
- period: {
1423
- connect: {
1424
- id: game.activePeriodId
1425
- }
1426
- },
1427
- game: {
1428
- connect: {
1429
- id: game.id
1430
- }
1431
- }
1432
- };
1433
- return [
1434
- ...acc,
1435
- {
1436
- ...common,
1437
- type: DB2.PlayerResultType.SEGMENT_START
1438
- },
1439
- {
1440
- ...common,
1441
- type: DB2.PlayerResultType.SEGMENT_END
1442
- }
1443
- ];
1444
- }, []);
1445
- return {
1446
- results,
1447
- extras
1448
- };
1449
- }
1450
- function computeSegmentEndResults(game, ctx, { reducers }) {
1451
- let extras = [];
1452
- const results = game.activePeriod.activeSegment.results.filter((result) => result.type === DB2.PlayerResultType.SEGMENT_END).map((result, ix, allResults) => {
1453
- const { result: facts, actions } = reducers.SegmentResult.apply(
1454
- result.facts,
1455
- {
1456
- type: reducers.SegmentResult.ActionTypes.SEGMENT_RESULTS_END,
1457
- payload: {
1458
- playerRole: result.player.role,
1459
- periodFacts: game.activePeriod.facts,
1460
- segmentFacts: game.activePeriod.activeSegment.facts,
1461
- segmentIx: game.activePeriod.activeSegmentIx
1462
- }
1463
- }
1464
- );
1465
- const mapper = mapAction({
1466
- ctx,
1467
- gameId: game.id,
1468
- activePeriodIx: game.activePeriodIx,
1469
- playerId: result.player.id
1470
- });
1471
- if (actions && actions.length > 0) {
1472
- extras = [...extras, ...actions.map(mapper)];
1473
- }
1474
- return {
1475
- where: {
1476
- periodIx_segmentIx_playerId_type: {
1477
- periodIx: game.activePeriodIx,
1478
- segmentIx: game.activePeriod.activeSegmentIx,
1479
- playerId: result.playerId,
1480
- type: DB2.PlayerResultType.SEGMENT_END
1481
- }
1482
- },
1483
- data: {
1484
- facts,
1485
- game: {
1486
- connect: {
1487
- id: game.id
1488
- }
1489
- }
1490
- }
1491
- };
1492
- });
1493
- return {
1494
- results,
1495
- extras
1496
- };
1497
- }
1498
-
1499
- // src/services/PlayService.ts
1500
- var PlayService_exports = {};
1501
- __export(PlayService_exports, {
1502
- addCountdown: () => addCountdown,
1503
- attemptLearningElement: () => attemptLearningElement,
1504
- getLearningElement: () => getLearningElement,
1505
- getPastResults: () => getPastResults,
1506
- getPlayerData: () => getPlayerData,
1507
- getPlayerResult: () => getPlayerResult,
1508
- getPlayerResults: () => getPlayerResults,
1509
- getPlayerTransactions: () => getPlayerTransactions,
1510
- markStoryElement: () => markStoryElement,
1511
- performAction: () => performAction,
1512
- saveDecisions: () => saveDecisions,
1513
- updateReadyState: () => updateReadyState
1514
- });
1515
- var DB3 = __toESM(require("@prisma/client"));
1516
- async function performAction(args, ctx, { reducers }) {
1517
- const periodIx_segmentIx_playerId_type = {
1518
- periodIx: args.periodIx,
1519
- segmentIx: args.segmentIx,
1520
- playerId: args.playerId,
1521
- type: DB3.PlayerResultType.SEGMENT_END
1522
- };
1523
- const previousResult = await ctx.prisma.playerResult.findUnique({
1524
- where: {
1525
- periodIx_segmentIx_playerId_type
1526
- },
1527
- include: {
1528
- game: true,
1529
- segment: true,
1530
- period: true,
1531
- player: true
1532
- }
1533
- });
1534
- if (!previousResult)
1535
- return null;
1536
- if (previousResult.game.status !== DB3.GameStatus.RUNNING) {
1537
- throw new Error("ACTIONS_NOT_ALLOWED");
1538
- }
1539
- const { result, events, notifications, isDirty, extras } = reducers.Actions.apply(previousResult.facts, {
1540
- type: args.actionType,
1541
- payload: {
1542
- playerArgs: args.facts,
1543
- segmentFacts: previousResult.segment?.facts,
1544
- periodFacts: previousResult.period.facts
1545
- }
1546
- });
1547
- publishUserNotification(ctx, notifications);
1548
- await receiveEvents({
1549
- events,
1550
- ctx: {
1551
- user: ctx.user,
1552
- args,
1553
- achievements: previousResult.player.achievementKeys,
1554
- experience: previousResult.player.experience,
1555
- currentLevelIx: previousResult.player.levelIx
1556
- },
1557
- prisma: ctx.prisma
1558
- });
1559
- if (!isDirty) {
1560
- return previousResult;
1561
- }
1562
- const [updatedResult, _] = await ctx.prisma.$transaction([
1563
- ctx.prisma.playerResult.update({
1564
- where: {
1565
- periodIx_segmentIx_playerId_type
1566
- },
1567
- data: {
1568
- facts: result
1569
- },
1570
- include: {
1571
- period: true
1572
- }
1573
- }),
1574
- ctx.prisma.playerAction.create({
1575
- data: {
1576
- periodIx: args.periodIx,
1577
- period: {
1578
- connect: {
1579
- gameId_index: {
1580
- gameId: args.gameId,
1581
- index: args.periodIx
1582
- }
1583
- }
1584
- },
1585
- segmentIx: args.segmentIx,
1586
- segment: {
1587
- connect: {
1588
- gameId_periodIx_index: {
1589
- gameId: args.gameId,
1590
- periodIx: args.periodIx,
1591
- index: args.segmentIx
1592
- }
1593
- }
1594
- },
1595
- game: {
1596
- connect: {
1597
- id: args.gameId
1598
- }
1599
- },
1600
- player: {
1601
- connect: {
1602
- id: args.playerId
1603
- }
1604
- },
1605
- type: args.actionType,
1606
- facts: {
1607
- ...args.facts,
1608
- ...extras
1609
- }
1610
- }
1611
- })
1612
- ]);
1613
- return updatedResult;
1614
- }
1615
- async function saveDecisions(args, ctx) {
1616
- const game = await ctx.prisma.game.findUnique({
1617
- where: {
1618
- id: ctx.user.gameId
1619
- },
1620
- include: {
1621
- activePeriod: {
1622
- include: {
1623
- activeSegment: true
1624
- }
1625
- }
1626
- }
1627
- });
1628
- if (!game?.activePeriod)
1629
- return null;
1630
- if (args.decisionType !== game.status) {
1631
- throw new Error("INVALID_DECISION");
1632
- }
1633
- const periodIx = game.activePeriod.index;
1634
- const savedDecision = ctx.prisma.playerDecision.upsert({
1635
- where: {
1636
- playerId_periodIx_type: {
1637
- periodIx,
1638
- playerId: ctx.user.sub,
1639
- type: args.decisionType
1640
- }
1641
- },
1642
- create: {
1643
- facts: args.facts,
1644
- type: args.decisionType,
1645
- periodIx,
1646
- period: {
1647
- connect: {
1648
- gameId_index: {
1649
- gameId: game.id,
1650
- index: periodIx
1651
- }
1652
- }
1653
- },
1654
- game: {
1655
- connect: {
1656
- id: game.id
1657
- }
1658
- },
1659
- player: {
1660
- connect: {
1661
- id: ctx.user.sub
1662
- }
1663
- }
1664
- },
1665
- update: {
1666
- facts: args.facts
1667
- }
1668
- });
1669
- return savedDecision;
1670
- }
1671
- async function getPlayerResult(args, ctx) {
1672
- const currentGame = await ctx.prisma.game.findUnique({
1673
- where: {
1674
- id: args.gameId
1675
- },
1676
- include: {
1677
- activePeriod: {
1678
- include: {
1679
- activeSegment: {
1680
- include: {
1681
- learningElements: true,
1682
- storyElements: true
1683
- }
1684
- },
1685
- segments: {
1686
- include: {
1687
- learningElements: true,
1688
- storyElements: true
1689
- }
1690
- }
1691
- }
1692
- },
1693
- periods: {
1694
- include: {
1695
- segments: {
1696
- include: {
1697
- learningElements: true,
1698
- storyElements: true
1699
- }
1700
- }
1701
- }
1702
- }
1703
- }
1704
- });
1705
- if (!currentGame?.activePeriod)
1706
- return null;
1707
- const previousResults = ctx.prisma.playerResult.findMany({
1708
- where: {
1709
- playerId: args.playerId,
1710
- periodIx: {
1711
- lte: currentGame.activePeriod.index
1712
- }
1713
- },
1714
- include: {
1715
- period: true,
1716
- segment: true
1717
- }
1718
- });
1719
- const playerResult = await ctx.prisma.playerResult.findUnique({
1720
- where: {
1721
- periodIx_segmentIx_playerId_type: {
1722
- periodIx: currentGame.activePeriodIx,
1723
- segmentIx: currentGame.activePeriod.activeSegmentIx,
1724
- playerId: args.playerId,
1725
- type: DB3.PlayerResultType.SEGMENT_END
1726
- }
1727
- },
1728
- include: {
1729
- period: true,
1730
- player: {
1731
- include: {
1732
- completedLearningElements: true,
1733
- visitedStoryElements: true
1734
- }
1735
- }
1736
- }
1737
- });
1738
- const transactions = ctx.prisma.playerAction.findMany({
1739
- where: {
1740
- player: {
1741
- id: args.playerId
1742
- }
1743
- },
1744
- orderBy: {
1745
- createdAt: "asc"
1746
- }
1747
- });
1748
- return {
1749
- currentGame,
1750
- playerResult,
1751
- previousResults,
1752
- transactions
1753
- };
1754
- }
1755
- async function getPlayerData(args, ctx) {
1756
- return ctx.prisma.player.findUnique({
1757
- where: {
1758
- id: args.playerId
1759
- },
1760
- include: {
1761
- level: true,
1762
- achievements: {
1763
- include: {
1764
- achievement: true
1765
- }
1766
- }
1767
- }
1768
- });
1769
- }
1770
- async function getLearningElement(args, ctx) {
1771
- const playerWithLearningElements = await ctx.prisma.player.findUnique({
1772
- where: {
1773
- id: ctx.user.sub
1774
- },
1775
- include: {
1776
- completedLearningElements: {
1777
- where: {
1778
- id: args.id
1779
- }
1780
- }
1781
- }
1782
- });
1783
- if (!playerWithLearningElements)
1784
- return null;
1785
- const elementSolved = playerWithLearningElements.completedLearningElements.length > 0;
1786
- const element = await ctx.prisma.learningElement.findUnique({
1787
- where: {
1788
- id: args.id
1789
- },
1790
- include: {
1791
- options: true
1792
- }
1793
- });
1794
- if (!element)
1795
- return null;
1796
- return {
1797
- id: args.id,
1798
- element: {
1799
- ...element,
1800
- feedback: elementSolved ? element.feedback : null
1801
- },
1802
- state: elementSolved ? "SOLVED" /* SOLVED */ : "NEW" /* NEW */,
1803
- solution: elementSolved ? JSON.stringify(
1804
- element.options.flatMap((option, ix) => option.correct ? [ix] : [])
1805
- ) : null
1806
- };
1807
- }
1808
- async function attemptLearningElement(args, ctx) {
1809
- const learningElement = await ctx.prisma.learningElement.findUnique({
1810
- where: { id: args.elementId },
1811
- include: { options: true }
1812
- });
1813
- if (!learningElement)
1814
- return null;
1815
- try {
1816
- const selectedOptions = JSON.parse(args.selection);
1817
- const pointsAchieved = learningElement.options.reduce((acc, option, ix) => {
1818
- if (option.correct && selectedOptions.includes(ix)) {
1819
- return acc + 1;
1820
- }
1821
- if (!option.correct && !selectedOptions.includes(ix)) {
1822
- return acc + 1;
1823
- }
1824
- return acc;
1825
- }, 0);
1826
- const pointsMax = learningElement.options.length;
1827
- let updatedPlayer;
1828
- if (pointsAchieved === pointsMax) {
1829
- updatedPlayer = await ctx.prisma.player.update({
1830
- where: {
1831
- id: ctx.user.sub
1832
- },
1833
- data: {
1834
- completedLearningElements: {
1835
- connect: {
1836
- id: args.elementId
1837
- }
1838
- },
1839
- completedLearningElementIds: {
1840
- push: args.elementId
1841
- }
1842
- },
1843
- include: {
1844
- game: {
1845
- select: {
1846
- activePeriodIx: true
1847
- }
1848
- }
1849
- }
1850
- });
1851
- await receiveEvents({
1852
- events: [
1853
- {
1854
- type: "LEARNING_ELEMENT_SOLVED" /* LEARNING_ELEMENT_SOLVED */,
1855
- facts: {
1856
- elementId: args.elementId
1857
- }
1858
- }
1859
- ],
1860
- ctx: {
1861
- user: ctx.user,
1862
- args: {
1863
- gameId: ctx.user.gameId,
1864
- periodIx: updatedPlayer.game.activePeriodIx,
1865
- playerId: ctx.user.sub
1866
- },
1867
- achievements: updatedPlayer.achievementKeys,
1868
- experience: updatedPlayer.experience,
1869
- currentLevelIx: updatedPlayer.levelIx
1870
- },
1871
- prisma: ctx.prisma
1872
- });
1873
- } else {
1874
- publishUserNotification(ctx, [
1875
- {
1876
- type: "LEARNING_ELEMENT_INCORRECT" /* LEARNING_ELEMENT_INCORRECT */
1877
- }
1878
- ]);
1879
- }
1880
- return {
1881
- id: args.elementId,
1882
- pointsAchieved,
1883
- pointsMax,
1884
- element: {
1885
- id: args.elementId,
1886
- feedback: learningElement.feedback
1887
- },
1888
- player: updatedPlayer
1889
- };
1890
- } catch (e) {
1891
- console.warn(e);
1892
- return null;
1893
- }
1894
- }
1895
- async function markStoryElement(args, ctx) {
1896
- const storyElement = await ctx.prisma.storyElement.findUnique({
1897
- where: { id: args.elementId }
1898
- });
1899
- if (!storyElement)
1900
- return null;
1901
- return ctx.prisma.player.update({
1902
- where: {
1903
- id: ctx.user.sub
1904
- },
1905
- data: {
1906
- visitedStoryElements: {
1907
- connect: {
1908
- id: args.elementId
1909
- }
1910
- },
1911
- visitedStoryElementIds: {
1912
- push: args.elementId
1913
- }
1914
- }
1915
- });
1916
- }
1917
- async function getPlayerTransactions(args, ctx) {
1918
- const playerActions = ctx.prisma.playerAction.findMany({
1919
- where: {
1920
- player: {
1921
- id: ctx.user.sub
1922
- }
1923
- },
1924
- orderBy: {
1925
- createdAt: "asc"
1926
- }
1927
- });
1928
- return playerActions;
1929
- }
1930
- async function getPlayerResults(args, ctx) {
1931
- const playerResults = await ctx.prisma.playerResult.findMany({
1932
- where: {
1933
- gameId: ctx.user.gameId
1934
- },
1935
- include: {
1936
- period: true,
1937
- player: true,
1938
- segment: true
1939
- }
1940
- });
1941
- return playerResults;
1942
- }
1943
- async function getPastResults(args, ctx) {
1944
- const currentGame = await ctx.prisma.game.findUnique({
1945
- where: { id: ctx.user.gameId }
1946
- });
1947
- if (!currentGame)
1948
- return null;
1949
- const playerResults = await ctx.prisma.playerResult.findMany({
1950
- where: {
1951
- gameId: ctx.user.gameId,
1952
- periodIx: {
1953
- lt: currentGame.activePeriodIx
1954
- },
1955
- type: "PERIOD_END"
1956
- },
1957
- include: {
1958
- period: true,
1959
- player: {
1960
- include: {
1961
- level: true
1962
- }
1963
- }
1964
- }
1965
- });
1966
- return playerResults;
1967
- }
1968
- async function updateReadyState(args, ctx) {
1969
- return ctx.prisma.player.update({
1970
- where: {
1971
- id: ctx.user.sub
1972
- },
1973
- data: {
1974
- isReady: args.isReady
1975
- }
1976
- });
1977
- }
1978
- async function addCountdown(args, ctx) {
1979
- const currentGame = await ctx.prisma.game.findUnique({
1980
- where: { id: args.gameId },
1981
- include: {
1982
- activePeriod: {
1983
- include: {
1984
- activeSegment: true
1985
- }
1986
- }
1987
- }
1988
- });
1989
- if (!currentGame?.activePeriod?.activeSegment)
1990
- return null;
1991
- await ctx.prisma.periodSegment.update({
1992
- where: {
1993
- id: currentGame.activePeriod.activeSegment.id
1994
- },
1995
- data: {
1996
- countdownExpiresAt: new Date(Date.now() + args.seconds * 1e3)
1997
- }
1998
- });
1999
- return true;
2000
- }
2001
- // Annotate the CommonJS export names for ESM import in node:
2002
- 0 && (module.exports = {
2003
- AccountService,
2004
- BaseGlobalNotificationType,
2005
- BaseUserNotificationType,
2006
- EventService,
2007
- GameService,
2008
- LearningElementState,
2009
- PlayService,
2010
- UserRole,
2011
- log
2012
- });
1
+ export { default as log } from './lib/logger.js';
2
+ import * as AccountService from './services/AccountService.js';
3
+ export { AccountService };
4
+ import * as EventService from './services/EventService.js';
5
+ export { EventService };
6
+ import * as GameService from './services/GameService.js';
7
+ export { GameService };
8
+ import * as PlayService from './services/PlayService.js';
9
+ export { PlayService };
10
+ export { BaseGlobalNotificationType, BaseUserNotificationType, LearningElementState, UserRole } from './types.js';
11
+ //# sourceMappingURL=index.js.map