@gbl-uzh/platform 0.4.21 → 0.4.22

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.
@@ -4,10 +4,12 @@ import { repeat, none } from 'ramda';
4
4
  import logger from '../lib/logger.js';
5
5
  import { receiveEvents } from './EventService.js';
6
6
 
7
- async function createGame({ name, playerCount }, ctx, { roleAssigner }) {
7
+ async function createGame({ name, playerCount, facts }, ctx, { schema, roleAssigner, }) {
8
+ const validatedFacts = schema.validateSync(facts);
8
9
  return ctx.prisma.game.create({
9
10
  data: {
10
11
  name,
12
+ facts: validatedFacts,
11
13
  owner: {
12
14
  connect: {
13
15
  id: ctx.user.sub,
@@ -64,7 +66,8 @@ async function addGamePeriod({ gameId, facts, segmentCount }, ctx, { schema, ser
64
66
  const index = (game.periods[0]?.index ?? -1) + 1;
65
67
  // TODO(JJ): Why do we provide validatedFacts twice?
66
68
  // - remove periodFacts from payload for initialize?
67
- const { resultFacts: initializedFacts } = services.Period.initialize(validatedFacts, {
69
+ const { resultFacts: initializedFacts, updatedGameFacts } = services.Period.initialize(validatedFacts, {
70
+ gameFacts: game.facts,
68
71
  // TODO(JJ): replace with undefined
69
72
  // At RS: If we replace validatedFacts with periodFacts the
70
73
  // Derivative Game will be broken, as it computes the trend from
@@ -75,49 +78,63 @@ async function addGamePeriod({ gameId, facts, segmentCount }, ctx, { schema, ser
75
78
  periodIx: index,
76
79
  });
77
80
  console.log(game.periods[0]?.facts, game.periods[0]?.segments[0]?.facts, initializedFacts);
78
- // create or update the facts and settings of a game period
79
- return ctx.prisma.period.upsert({
80
- where: {
81
- gameId_index: {
82
- gameId,
83
- index,
81
+ const res = await ctx.prisma.$transaction(async (tx) => {
82
+ const updatedPeriod = await tx.period.upsert({
83
+ where: {
84
+ gameId_index: {
85
+ gameId,
86
+ index,
87
+ },
84
88
  },
85
- },
86
- create: {
87
- index,
88
- facts: initializedFacts,
89
- game: {
90
- connect: {
91
- id: gameId,
89
+ create: {
90
+ index,
91
+ facts: initializedFacts,
92
+ game: {
93
+ connect: {
94
+ id: gameId,
95
+ },
96
+ },
97
+ segmentCount,
98
+ previousPeriod: {
99
+ connect: index > 0
100
+ ? {
101
+ gameId_index: {
102
+ gameId,
103
+ index: index - 1,
104
+ },
105
+ }
106
+ : [],
92
107
  },
93
108
  },
94
- segmentCount,
95
- previousPeriod: {
96
- connect: index > 0
97
- ? {
98
- gameId_index: {
99
- gameId,
100
- index: index - 1,
101
- },
102
- }
103
- : [],
109
+ update: {
110
+ facts: initializedFacts,
104
111
  },
105
- },
106
- update: {
107
- facts: initializedFacts,
108
- },
109
- include: {
110
- segments: {
111
- include: {
112
- learningElements: true,
113
- storyElements: true,
112
+ include: {
113
+ segments: {
114
+ include: {
115
+ learningElements: true,
116
+ storyElements: true,
117
+ },
114
118
  },
115
119
  },
116
- },
120
+ });
121
+ if (updatedGameFacts) {
122
+ await ctx.prisma.game.update({
123
+ where: { id: gameId },
124
+ data: { facts: updatedGameFacts },
125
+ });
126
+ }
127
+ return updatedPeriod;
128
+ // NOTE(JJ): We don't a serialization isolation level here, as only one admin adds
129
+ // a period at a time
117
130
  });
131
+ return res;
118
132
  }
119
133
  async function addPeriodSegment({ gameId, periodIx, facts, learningElements, storyElements, }, ctx, { schema, services }) {
120
134
  const validatedFacts = schema.validateSync(facts);
135
+ const game = await ctx.prisma.game.findUnique({ where: { id: gameId } });
136
+ if (!game)
137
+ return null;
121
138
  const period = await ctx.prisma.period.findUnique({
122
139
  where: {
123
140
  gameId_index: {
@@ -137,79 +154,90 @@ async function addPeriodSegment({ gameId, periodIx, facts, learningElements, sto
137
154
  if (!period)
138
155
  return null;
139
156
  const index = (period.segments[0]?.index ?? -1) + 1;
140
- const { resultFacts: initializedFacts } = services.Segment.initialize(validatedFacts, {
157
+ const { resultFacts: initializedFacts, updatedGameFacts } = services.Segment.initialize(validatedFacts, {
158
+ gameFacts: game.facts,
141
159
  periodFacts: period.facts,
142
160
  previousSegmentFacts: period.segments[0]?.facts,
143
161
  segmentIx: index,
144
162
  segmentCount: period.segmentCount,
145
163
  periodIx,
146
164
  });
147
- // create or update the facts and settings of a period segment
148
- return ctx.prisma.periodSegment.upsert({
149
- where: {
150
- gameId_periodIx_index: {
151
- gameId,
152
- periodIx,
153
- index,
154
- },
155
- },
156
- create: {
157
- index,
158
- facts: initializedFacts,
159
- learningElements: {
160
- connect: learningElements
161
- ? learningElements.map((item) => ({ id: item }))
162
- : [],
163
- },
164
- storyElements: {
165
- connect: storyElements
166
- ? storyElements.map((item) => ({ id: item }))
167
- : [],
168
- },
169
- game: {
170
- connect: {
171
- id: gameId,
165
+ const res = await ctx.prisma.$transaction(async (tx) => {
166
+ // create or update the facts and settings of a period segment
167
+ const updatedSegment = ctx.prisma.periodSegment.upsert({
168
+ where: {
169
+ gameId_periodIx_index: {
170
+ gameId,
171
+ periodIx,
172
+ index,
172
173
  },
173
174
  },
174
- periodIx: periodIx,
175
- period: {
176
- connect: {
177
- gameId_index: {
178
- gameId,
179
- index: periodIx,
175
+ create: {
176
+ index,
177
+ facts: initializedFacts,
178
+ learningElements: {
179
+ connect: learningElements
180
+ ? learningElements.map((item) => ({ id: item }))
181
+ : [],
182
+ },
183
+ storyElements: {
184
+ connect: storyElements
185
+ ? storyElements.map((item) => ({ id: item }))
186
+ : [],
187
+ },
188
+ game: {
189
+ connect: {
190
+ id: gameId,
180
191
  },
181
192
  },
182
- },
183
- previousSegment: {
184
- connect: index > 0
185
- ? {
186
- gameId_periodIx_index: {
193
+ periodIx: periodIx,
194
+ period: {
195
+ connect: {
196
+ gameId_index: {
187
197
  gameId,
188
- periodIx,
189
- index: index - 1,
198
+ index: periodIx,
190
199
  },
191
- }
192
- : [],
200
+ },
201
+ },
202
+ previousSegment: {
203
+ connect: index > 0
204
+ ? {
205
+ gameId_periodIx_index: {
206
+ gameId,
207
+ periodIx,
208
+ index: index - 1,
209
+ },
210
+ }
211
+ : [],
212
+ },
193
213
  },
194
- },
195
- update: {
196
- facts: initializedFacts,
197
- learningElements: {
198
- connect: learningElements
199
- ? learningElements.map((item) => ({ id: item }))
200
- : [],
214
+ update: {
215
+ facts: initializedFacts,
216
+ learningElements: {
217
+ connect: learningElements
218
+ ? learningElements.map((item) => ({ id: item }))
219
+ : [],
220
+ },
221
+ storyElements: {
222
+ connect: storyElements
223
+ ? storyElements.map((item) => ({ id: item }))
224
+ : [],
225
+ },
201
226
  },
202
- storyElements: {
203
- connect: storyElements
204
- ? storyElements.map((item) => ({ id: item }))
205
- : [],
227
+ include: {
228
+ learningElements: true,
229
+ storyElements: true,
206
230
  },
207
- },
208
- include: {
209
- learningElements: true,
210
- storyElements: true,
211
- },
231
+ });
232
+ if (updatedGameFacts) {
233
+ await ctx.prisma.game.update({
234
+ where: { id: gameId },
235
+ data: { facts: updatedGameFacts },
236
+ });
237
+ }
238
+ return updatedSegment;
212
239
  });
240
+ return res;
213
241
  }
214
242
  async function activateNextPeriod({ gameId }, ctx, { services }) {
215
243
  logger.info('activating next period');
@@ -275,16 +303,32 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
275
303
  switch (game.status) {
276
304
  // SCHEDULED -> PREPARATION
277
305
  // if the game is scheduled, initialize period results and move to PREPARATION
306
+ // TODO(JJ):
307
+ // - The game facts should now be updated by the results
308
+ // - They should be updated by the game status, e.g. when going to the next
309
+ // period or segment
310
+ // - Done: the game facts are updated on user interaction in PlayService
278
311
  case DB.GameStatus.SCHEDULED: {
279
312
  const { results, extras } = computePeriodStartResults({
280
313
  results: undefined,
281
314
  players: game.players,
282
315
  activePeriodIx: currentPeriodIx,
283
- gameId: game.id,
316
+ game,
284
317
  periodFacts: game.periods?.[0]?.facts,
285
318
  }, ctx, { services });
286
319
  // update the status and active period of the current game
287
320
  // and prepare PERIOD_START results
321
+ const gameData = {
322
+ status: DB.GameStatus.PREPARATION,
323
+ activePeriodIx: nextPeriodIx,
324
+ activePeriod: {
325
+ connect: { gameId_index: { gameId, index: nextPeriodIx } },
326
+ },
327
+ };
328
+ // TODO(JJ): Somewhere here we need to have a gameFactsUpdateService
329
+ // if (Object.keys(gameFactsToUpdate).length > 0) {
330
+ // gameData.facts = gameFactsToUpdate
331
+ // }
288
332
  const result = await ctx.prisma.$transaction([
289
333
  ctx.prisma.game.update({
290
334
  where: {
@@ -297,18 +341,7 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
297
341
  },
298
342
  },
299
343
  },
300
- data: {
301
- status: DB.GameStatus.PREPARATION,
302
- activePeriodIx: nextPeriodIx,
303
- activePeriod: {
304
- connect: {
305
- gameId_index: {
306
- gameId,
307
- index: nextPeriodIx,
308
- },
309
- },
310
- },
311
- },
344
+ data: gameData,
312
345
  }),
313
346
  ctx.prisma.period.update({
314
347
  where: {
@@ -336,16 +369,16 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
336
369
  const { results, extras } = computeSegmentEndResults(game, ctx, {
337
370
  services,
338
371
  });
372
+ // TODO(JJ): Check if we need to update the game facts as well
339
373
  // update period facts when starting consolidation
340
374
  const { resultFacts: consolidatedFacts } = services.Period.consolidate(game.activePeriod.facts, {
375
+ gameFacts: game.facts,
341
376
  previousSegmentFacts: game.activePeriod.activeSegment.facts,
342
377
  periodIx: currentPeriodIx,
343
378
  });
344
379
  const result = await ctx.prisma.$transaction([
345
380
  ctx.prisma.game.update({
346
- data: {
347
- status: DB.GameStatus.CONSOLIDATION,
348
- },
381
+ data: { status: DB.GameStatus.CONSOLIDATION },
349
382
  include: {
350
383
  periods: {
351
384
  include: {
@@ -408,7 +441,7 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
408
441
  periodDecisions: game.activePeriod.decisions,
409
442
  activePeriodIx: currentPeriodIx,
410
443
  activeSegmentIx: currentSegmentIx,
411
- gameId: game.id,
444
+ game,
412
445
  }, ctx, { services });
413
446
  await Promise.all(promises);
414
447
  // TODO(JJ): The error happens here for the last consolidation
@@ -439,6 +472,13 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
439
472
  // },
440
473
  // }
441
474
  // }
475
+ const gameData = {
476
+ status: DB.GameStatus.RESULTS,
477
+ activePeriodIx: periodIx,
478
+ activePeriod: {
479
+ connect: { gameId_index: { gameId, index: periodIx } },
480
+ },
481
+ };
442
482
  // TODO(JJ): Check with RS
443
483
  // - when updating the game with the nextPeriodIx it crashes
444
484
  const result = await ctx.prisma.$transaction([
@@ -454,18 +494,7 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
454
494
  },
455
495
  },
456
496
  },
457
- data: {
458
- status: DB.GameStatus.RESULTS,
459
- activePeriodIx: periodIx,
460
- activePeriod: {
461
- connect: {
462
- gameId_index: {
463
- gameId,
464
- index: periodIx,
465
- },
466
- },
467
- },
468
- },
497
+ data: gameData,
469
498
  }),
470
499
  // create PERIOD_END results based on the previous SEGMENT_END results
471
500
  ctx.prisma.period.update({
@@ -522,7 +551,7 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
522
551
  results: game.activePeriod.previousPeriod[0]?.results,
523
552
  players: game.players,
524
553
  activePeriodIx: currentPeriodIx,
525
- gameId: game.id,
554
+ game,
526
555
  periodFacts: game.activePeriod.facts,
527
556
  }, ctx, { services });
528
557
  const result = await ctx.prisma.$transaction([
@@ -538,9 +567,7 @@ async function activateNextPeriod({ gameId }, ctx, { services }) {
538
567
  },
539
568
  },
540
569
  },
541
- data: {
542
- status: DB.GameStatus.PREPARATION,
543
- },
570
+ data: { status: DB.GameStatus.PREPARATION },
544
571
  }),
545
572
  // create PERIOD_START results based on the previous PERIOD_END results
546
573
  ctx.prisma.period.update({
@@ -610,6 +637,15 @@ async function activateNextSegment({ gameId }, ctx, { services }) {
610
637
  // PAUSED -> RUNNING
611
638
  case DB.GameStatus.PREPARATION:
612
639
  case DB.GameStatus.PAUSED: {
640
+ // NOTE(JJ): Update game facts per segment, but not for the initialization
641
+ const { updatedGameFacts } = services.GameFacts.update(game.facts, {
642
+ periodIx: currentPeriodIx,
643
+ segmentIx: nextSegmentIx, // TODO(JJ): Double-check if this is right
644
+ });
645
+ const isVeryFirst = currentPeriodIx === 0 && currentSegmentIx === -1;
646
+ if (updatedGameFacts && !isVeryFirst) {
647
+ game.facts = updatedGameFacts;
648
+ }
613
649
  const { results, extras } = computeSegmentStartResults(game, ctx, {
614
650
  services,
615
651
  });
@@ -628,6 +664,7 @@ async function activateNextSegment({ gameId }, ctx, { services }) {
628
664
  },
629
665
  data: {
630
666
  status: DB.GameStatus.RUNNING,
667
+ ...(updatedGameFacts ? { facts: game.facts } : {}),
631
668
  },
632
669
  }),
633
670
  // update the active segment of the current period
@@ -691,9 +728,7 @@ async function activateNextSegment({ gameId }, ctx, { services }) {
691
728
  },
692
729
  players: true,
693
730
  },
694
- data: {
695
- status: DB.GameStatus.PAUSED,
696
- },
731
+ data: { status: DB.GameStatus.PAUSED },
697
732
  }),
698
733
  ctx.prisma.periodSegment.update({
699
734
  where: {
@@ -881,7 +916,7 @@ function mapAction({ ctx, gameId, activePeriodIx, playerId }) {
881
916
  },
882
917
  });
883
918
  }
884
- function computePeriodStartResults({ results, players, activePeriodIx, gameId, periodFacts }, ctx, { services }) {
919
+ function computePeriodStartResults({ results, players, activePeriodIx, game, periodFacts }, ctx, { services }) {
885
920
  const currentPeriodIx = activePeriodIx;
886
921
  const nextPeriodIx = currentPeriodIx + 1;
887
922
  let extras = [];
@@ -893,12 +928,13 @@ function computePeriodStartResults({ results, players, activePeriodIx, gameId, p
893
928
  .map((result, ix, allResults) => {
894
929
  const { resultFacts: facts, actions } = services.PeriodResult.start(result.facts, {
895
930
  playerRole: result.player?.role ?? result.player.connect?.role,
931
+ gameFacts: game.facts,
896
932
  periodFacts,
897
933
  });
898
934
  if (actions && actions.length > 0) {
899
935
  const mapper = mapAction({
900
936
  ctx,
901
- gameId,
937
+ gameId: game.id,
902
938
  activePeriodIx: currentPeriodIx,
903
939
  playerId: result.player.id,
904
940
  });
@@ -915,7 +951,7 @@ function computePeriodStartResults({ results, players, activePeriodIx, gameId, p
915
951
  },
916
952
  game: {
917
953
  connect: {
918
- id: gameId,
954
+ id: game.id,
919
955
  },
920
956
  },
921
957
  };
@@ -927,11 +963,11 @@ function computePeriodStartResults({ results, players, activePeriodIx, gameId, p
927
963
  }
928
964
  // if the game has not started yet, generate initial PERIOD_START results
929
965
  const result = players.map((player, ix, allPlayers) => {
930
- const { resultFacts: facts, actions } = services.PeriodResult.initialize({}, { playerRole: player.role, periodFacts });
966
+ const { resultFacts: facts, actions } = services.PeriodResult.initialize({}, { playerRole: player.role, gameFacts: game.facts, periodFacts });
931
967
  if (actions && actions.length > 0) {
932
968
  const mapper = mapAction({
933
969
  ctx,
934
- gameId,
970
+ gameId: game.id,
935
971
  activePeriodIx: nextPeriodIx,
936
972
  playerId: player.id,
937
973
  });
@@ -948,7 +984,7 @@ function computePeriodStartResults({ results, players, activePeriodIx, gameId, p
948
984
  },
949
985
  game: {
950
986
  connect: {
951
- id: gameId,
987
+ id: game.id,
952
988
  },
953
989
  },
954
990
  };
@@ -958,7 +994,7 @@ function computePeriodStartResults({ results, players, activePeriodIx, gameId, p
958
994
  extras,
959
995
  };
960
996
  }
961
- async function computePeriodEndResults({ segmentEndResults, players, activeSegmentResults, periodFacts, periodDecisions, segmentFacts, activePeriodIx, activeSegmentIx, gameId, }, ctx, { services }) {
997
+ async function computePeriodEndResults({ segmentEndResults, players, activeSegmentResults, periodFacts, periodDecisions, segmentFacts, activePeriodIx, activeSegmentIx, game, }, ctx, { services }) {
962
998
  let extras = [];
963
999
  let promises = [];
964
1000
  const perPlayer = {};
@@ -975,6 +1011,7 @@ async function computePeriodEndResults({ segmentEndResults, players, activeSegme
975
1011
  const consolidationDecisions = perPlayer[result.playerId].consolidationDecisions;
976
1012
  const { resultFacts: facts, actions, events, } = services.PeriodResult.end(result.facts, {
977
1013
  segmentEndResults,
1014
+ gameFacts: game.facts,
978
1015
  periodFacts,
979
1016
  segmentFacts,
980
1017
  playerRole: result.player.role,
@@ -988,7 +1025,7 @@ async function computePeriodEndResults({ segmentEndResults, players, activeSegme
988
1025
  if (actions && actions.length > 0) {
989
1026
  const mapper = mapAction({
990
1027
  ctx,
991
- gameId,
1028
+ gameId: game.id,
992
1029
  activePeriodIx,
993
1030
  playerId: result.player.id,
994
1031
  });
@@ -1002,7 +1039,7 @@ async function computePeriodEndResults({ segmentEndResults, players, activeSegme
1002
1039
  args: {
1003
1040
  playerId: result.player.id,
1004
1041
  periodIx: activePeriodIx,
1005
- gameId,
1042
+ gameId: game.id,
1006
1043
  },
1007
1044
  user: ctx.user,
1008
1045
  achievements: result.player.achievementKeys,
@@ -1023,7 +1060,7 @@ async function computePeriodEndResults({ segmentEndResults, players, activeSegme
1023
1060
  },
1024
1061
  game: {
1025
1062
  connect: {
1026
- id: gameId,
1063
+ id: game.id,
1027
1064
  },
1028
1065
  },
1029
1066
  };
@@ -1045,6 +1082,7 @@ function computeSegmentStartResults(game, ctx, { services }) {
1045
1082
  .reduce((acc, result, ix, allResults) => {
1046
1083
  const { resultFacts: facts, actions } = services.SegmentResult.start(result.facts, {
1047
1084
  playerRole: result.player.role,
1085
+ gameFacts: game.facts,
1048
1086
  periodFacts: game.activePeriod.facts,
1049
1087
  segmentFacts: game.activePeriod.activeSegment.facts,
1050
1088
  nextSegmentFacts: game.activePeriod.activeSegment.nextSegment?.facts,
@@ -1057,6 +1095,7 @@ function computeSegmentStartResults(game, ctx, { services }) {
1057
1095
  activePeriodIx: game.activePeriodIx,
1058
1096
  playerId: result.player.id,
1059
1097
  });
1098
+ // TODO(JJ): @RS Careful, here!!! We add this array to $transaction
1060
1099
  extras = [...extras, ...actions.map(mapper)];
1061
1100
  }
1062
1101
  const common = {
@@ -1106,6 +1145,7 @@ function computeSegmentStartResults(game, ctx, { services }) {
1106
1145
  .reduce((acc, result, ix, allResults) => {
1107
1146
  let { resultFacts: facts } = services.SegmentResult.initialize(result.facts, {
1108
1147
  playerRole: result.player.role,
1148
+ gameFacts: game.facts,
1109
1149
  periodFacts: activePeriod.facts,
1110
1150
  segmentFacts: aboutToBeactiveSegment.facts,
1111
1151
  nextSegmentFacts: aboutToBeactiveSegment.nextSegment?.facts,
@@ -1150,11 +1190,14 @@ function computeSegmentStartResults(game, ctx, { services }) {
1150
1190
  }
1151
1191
  function computeSegmentEndResults(game, ctx, { services }) {
1152
1192
  let extras = [];
1193
+ // NOTE(JJ): We go through all results of the active segment of each player
1194
+ // and update the game facts if needed
1153
1195
  const results = game.activePeriod.activeSegment.results
1154
1196
  .filter((result) => result.type === DB.PlayerResultType.SEGMENT_END)
1155
1197
  .map((result, ix, allResults) => {
1156
1198
  const { resultFacts: facts, actions } = services.SegmentResult.end(result.facts, {
1157
1199
  playerRole: result.player.role,
1200
+ gameFacts: game.facts,
1158
1201
  periodFacts: game.activePeriod.facts,
1159
1202
  segmentFacts: game.activePeriod.activeSegment.facts,
1160
1203
  segmentIx: game.activePeriod.activeSegmentIx,