@moneypot/hub 1.1.1 → 1.2.0-dev.1

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.
@@ -0,0 +1,783 @@
1
+ import { gql } from "../__generated__/gql.js";
2
+ import { TakeRequestStatus as MpTakeRequestStatus, TransferStatusKind as MpTransferStatus, } from "../__generated__/graphql.js";
3
+ import { exactlyOneRow, maybeOneRow, superuserPool, withPgPoolTransaction, } from "../db/index.js";
4
+ import { assert } from "tsafe";
5
+ import { pgAdvisoryLock } from "../pg-advisory-lock.js";
6
+ const MP_PAGINATE_PENDING_TAKE_REQUESTS = gql(`
7
+ query MpPaginatedPendingTakeRequests($controllerId: UUID!, $after: Cursor) {
8
+ allTakeRequests(
9
+ condition: { controllerId: $controllerId, status: PENDING }
10
+ after: $after
11
+ orderBy: ID_ASC
12
+ ) {
13
+ pageInfo {
14
+ endCursor
15
+ hasNextPage
16
+ }
17
+ edges {
18
+ cursor
19
+ node {
20
+ id
21
+ status
22
+ amount
23
+ currencyKey
24
+ userId
25
+ experienceId
26
+ experienceTransfer {
27
+ id
28
+ amount
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ `);
35
+ const MP_REJECT_TAKE_REQUEST = gql(`
36
+ mutation MpRejectTakeRequest($mpTakeRequestId: UUID!) {
37
+ rejectTakeRequest(input: { id: $mpTakeRequestId }) {
38
+ result {
39
+ ... on RejectTakeRequestSuccess {
40
+ __typename
41
+ takeRequest {
42
+ id
43
+ }
44
+ }
45
+ ... on TakeRequestAlreadyTerminal {
46
+ __typename
47
+ takeRequest {
48
+ id
49
+ status
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ `);
56
+ const MP_TRANSFER_TAKE_REQUEST = gql(`
57
+ mutation MpTransferTakeRequest(
58
+ $mpTakeRequestId: UUID!
59
+ $mpExperienceId: UUID!
60
+ $mpUserId: UUID!
61
+ $amount: Int!
62
+ $currencyKey: String!
63
+ ) {
64
+ transferCurrencyExperienceToUser(
65
+ input: {
66
+ takeRequestId: $mpTakeRequestId
67
+ experienceId: $mpExperienceId
68
+ userId: $mpUserId
69
+ amount: $amount
70
+ currency: $currencyKey
71
+ }
72
+ ) {
73
+ result {
74
+ ... on TransferCurrencyExperienceToUserSuccess {
75
+ __typename
76
+ transfer {
77
+ id
78
+ status
79
+ }
80
+ }
81
+ ... on TransferMetadataIdExists {
82
+ __typename
83
+ transfer {
84
+ id
85
+ status
86
+ }
87
+ }
88
+ ... on TakeRequestAlreadyTerminal {
89
+ __typename
90
+ takeRequest {
91
+ id
92
+ status
93
+ experienceTransfer {
94
+ id
95
+ status
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ `);
103
+ const MP_COMPLETE_TRANSFER = gql(`
104
+ mutation CompleteTransfer2($mpTransferId: UUID!) {
105
+ completeTransfer(input: { id: $mpTransferId }) {
106
+ result {
107
+ ... on CompleteTransferSuccess {
108
+ __typename
109
+ transfer {
110
+ __typename
111
+ id
112
+ status
113
+ ... on ExperienceTransfer {
114
+ id
115
+ takeRequest {
116
+ id
117
+ status
118
+ }
119
+ }
120
+ }
121
+ }
122
+ ... on InvalidTransferStatus {
123
+ __typename
124
+ currentStatus
125
+ message
126
+ transfer {
127
+ __typename
128
+ ... on ExperienceTransfer {
129
+ id
130
+ takeRequest {
131
+ id
132
+ status
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ `);
141
+ var LocalTakeRequestStatus;
142
+ (function (LocalTakeRequestStatus) {
143
+ LocalTakeRequestStatus["PENDING"] = "PENDING";
144
+ LocalTakeRequestStatus["PROCESSING"] = "PROCESSING";
145
+ LocalTakeRequestStatus["COMPLETED"] = "COMPLETED";
146
+ LocalTakeRequestStatus["FAILED"] = "FAILED";
147
+ LocalTakeRequestStatus["REJECTED"] = "REJECTED";
148
+ })(LocalTakeRequestStatus || (LocalTakeRequestStatus = {}));
149
+ export async function processTakeRequests({ abortSignal, controllerId, casinoId, graphqlClient, }) {
150
+ if (abortSignal.aborted) {
151
+ return;
152
+ }
153
+ const takeRequests = await fetchPendingTakeRequests(graphqlClient, controllerId);
154
+ console.log(`[processTakeRequests] Found ${takeRequests.length} take requests`);
155
+ for (const takeRequest of takeRequests) {
156
+ if (abortSignal.aborted) {
157
+ break;
158
+ }
159
+ await processSingleTakeRequest({
160
+ mpTakeRequestId: takeRequest.id,
161
+ mpTakeRequest: takeRequest,
162
+ casinoId,
163
+ graphqlClient,
164
+ });
165
+ }
166
+ await processPendingTransferCompletions({
167
+ casinoId,
168
+ graphqlClient,
169
+ abortSignal,
170
+ });
171
+ await processStuckRequests({ casinoId, graphqlClient, abortSignal });
172
+ }
173
+ async function fetchPendingTakeRequests(graphqlClient, controllerId) {
174
+ const result = await graphqlClient.request(MP_PAGINATE_PENDING_TAKE_REQUESTS, {
175
+ controllerId,
176
+ });
177
+ return (result.allTakeRequests?.edges.flatMap((edge) => edge?.node || []) || []);
178
+ }
179
+ async function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, }) {
180
+ return withPgPoolTransaction(superuserPool, async (pgClient) => {
181
+ await pgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
182
+ mpTakeRequestId,
183
+ casinoId,
184
+ });
185
+ const existingRequest = await pgClient
186
+ .query({
187
+ text: `
188
+ SELECT id, mp_take_request_id, status, reserved_amount, user_id, experience_id, currency_key
189
+ FROM hub.take_request
190
+ WHERE mp_take_request_id = $1 AND casino_id = $2
191
+ FOR UPDATE
192
+ `,
193
+ values: [mpTakeRequestId, casinoId],
194
+ })
195
+ .then(maybeOneRow);
196
+ if (!existingRequest && mpTakeRequest) {
197
+ return await createAndProcessNewTakeRequest({
198
+ pgClient,
199
+ mpTakeRequest,
200
+ casinoId,
201
+ graphqlClient,
202
+ });
203
+ }
204
+ if (existingRequest) {
205
+ return await processExistingTakeRequest({
206
+ pgClient,
207
+ takeRequest: existingRequest,
208
+ casinoId,
209
+ graphqlClient,
210
+ });
211
+ }
212
+ console.log(`[processSingleTakeRequest] Take request ${mpTakeRequestId} not found in MP or our DB for casino ${casinoId}`);
213
+ return null;
214
+ });
215
+ }
216
+ async function createAndProcessNewTakeRequest({ pgClient, mpTakeRequest, casinoId, graphqlClient, }) {
217
+ assert(pgClient._inTransaction, "pgClient must be in a transaction");
218
+ assert(mpTakeRequest.status === MpTakeRequestStatus.Pending);
219
+ const { dbUser, dbExperience, dbCurrency, dbBalance } = await loadRequiredEntities(pgClient, {
220
+ type: "mpId",
221
+ mpUserId: mpTakeRequest.userId,
222
+ mpExperienceId: mpTakeRequest.experienceId,
223
+ currencyKey: mpTakeRequest.currencyKey,
224
+ casinoId,
225
+ });
226
+ if (!dbUser || !dbExperience || !dbCurrency || !dbBalance) {
227
+ await rejectMpTakeRequest(pgClient, graphqlClient, mpTakeRequest.id);
228
+ return null;
229
+ }
230
+ const amountToTransfer = Math.floor(typeof mpTakeRequest.amount === "number"
231
+ ? Math.min(mpTakeRequest.amount, dbBalance.amount)
232
+ : dbBalance.amount);
233
+ if (amountToTransfer < 1) {
234
+ await rejectMpTakeRequest(pgClient, graphqlClient, mpTakeRequest.id);
235
+ return null;
236
+ }
237
+ const newTakeRequest = await pgClient
238
+ .query({
239
+ text: `
240
+ INSERT INTO hub.take_request (
241
+ mp_take_request_id,
242
+ user_id,
243
+ experience_id,
244
+ casino_id,
245
+ currency_key,
246
+ amount,
247
+ status,
248
+ mp_status,
249
+ reserved_amount
250
+ )
251
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
252
+ RETURNING id
253
+ `,
254
+ values: [
255
+ mpTakeRequest.id,
256
+ dbUser.id,
257
+ dbExperience.id,
258
+ casinoId,
259
+ dbCurrency.key,
260
+ mpTakeRequest.amount,
261
+ LocalTakeRequestStatus.PENDING,
262
+ mpTakeRequest.status,
263
+ amountToTransfer,
264
+ ],
265
+ })
266
+ .then(exactlyOneRow);
267
+ await pgClient.query({
268
+ text: `
269
+ UPDATE hub.balance
270
+ SET amount = amount - $1
271
+ WHERE id = $2
272
+ `,
273
+ values: [amountToTransfer, dbBalance.id],
274
+ });
275
+ await pgClient.query({
276
+ text: `
277
+ UPDATE hub.take_request
278
+ SET status = $1, updated_at = now()
279
+ WHERE id = $2
280
+ `,
281
+ values: [LocalTakeRequestStatus.PROCESSING, newTakeRequest.id],
282
+ });
283
+ return await attemptTransfer({
284
+ pgClient,
285
+ takeRequestId: newTakeRequest.id,
286
+ mpTakeRequestId: mpTakeRequest.id,
287
+ mpExperienceId: dbExperience.mp_experience_id,
288
+ mpUserId: dbUser.mp_user_id,
289
+ amount: amountToTransfer,
290
+ currencyKey: dbCurrency.key,
291
+ graphqlClient,
292
+ });
293
+ }
294
+ async function processExistingTakeRequest({ pgClient, takeRequest, casinoId, graphqlClient, }) {
295
+ assert(pgClient._inTransaction, "pgClient must be in a transaction");
296
+ switch (takeRequest.status) {
297
+ case LocalTakeRequestStatus.PENDING:
298
+ console.log(`[processExistingTakeRequest] Take request ${takeRequest.id} in PENDING state`);
299
+ break;
300
+ case LocalTakeRequestStatus.PROCESSING: {
301
+ const { dbUser, dbExperience, dbCurrency } = await loadRequiredEntities(pgClient, {
302
+ type: "localId",
303
+ userId: takeRequest.user_id,
304
+ experienceId: takeRequest.experience_id,
305
+ currencyKey: takeRequest.currency_key,
306
+ casinoId,
307
+ });
308
+ if (!dbUser || !dbExperience || !dbCurrency) {
309
+ await updateTakeRequestStatus(pgClient, takeRequest.id, LocalTakeRequestStatus.FAILED);
310
+ return null;
311
+ }
312
+ return await attemptTransfer({
313
+ pgClient,
314
+ takeRequestId: takeRequest.id,
315
+ mpTakeRequestId: takeRequest.mp_take_request_id,
316
+ mpExperienceId: dbExperience.mp_experience_id,
317
+ mpUserId: dbUser.mp_user_id,
318
+ amount: takeRequest.reserved_amount,
319
+ currencyKey: takeRequest.currency_key,
320
+ graphqlClient,
321
+ });
322
+ }
323
+ case LocalTakeRequestStatus.COMPLETED:
324
+ console.log(`[processExistingTakeRequest] Take request ${takeRequest.id} already COMPLETED`);
325
+ break;
326
+ case LocalTakeRequestStatus.FAILED:
327
+ console.log(`[processExistingTakeRequest] Take request ${takeRequest.id} in FAILED state`);
328
+ break;
329
+ case LocalTakeRequestStatus.REJECTED:
330
+ console.log(`[processExistingTakeRequest] Take request ${takeRequest.id} already REJECTED`);
331
+ break;
332
+ default:
333
+ console.error(`[processExistingTakeRequest] Unknown status: ${takeRequest.status}`);
334
+ break;
335
+ }
336
+ return null;
337
+ }
338
+ async function attemptTransfer({ pgClient, takeRequestId, mpTakeRequestId, mpExperienceId, mpUserId, amount, currencyKey, graphqlClient, }) {
339
+ assert(pgClient._inTransaction, "pgClient must be in a transaction");
340
+ try {
341
+ const transferResult = await graphqlClient.request(MP_TRANSFER_TAKE_REQUEST, {
342
+ mpTakeRequestId,
343
+ mpExperienceId,
344
+ mpUserId,
345
+ amount,
346
+ currencyKey,
347
+ });
348
+ if (!transferResult.transferCurrencyExperienceToUser?.result) {
349
+ throw new Error("No transfer result");
350
+ }
351
+ const result = transferResult.transferCurrencyExperienceToUser.result;
352
+ let transferId = null;
353
+ let resultStatus = LocalTakeRequestStatus.PROCESSING;
354
+ switch (result.__typename) {
355
+ case "TransferCurrencyExperienceToUserSuccess":
356
+ transferId = result.transfer.id;
357
+ break;
358
+ case "TransferMetadataIdExists":
359
+ transferId = result.transfer.id;
360
+ break;
361
+ case "TakeRequestAlreadyTerminal": {
362
+ const mpStatus = result.takeRequest.status;
363
+ if (result.takeRequest.experienceTransfer) {
364
+ transferId = result.takeRequest.experienceTransfer.id;
365
+ if (mpStatus === MpTakeRequestStatus.UserCanceled) {
366
+ resultStatus = LocalTakeRequestStatus.FAILED;
367
+ }
368
+ }
369
+ else {
370
+ resultStatus = LocalTakeRequestStatus.FAILED;
371
+ }
372
+ break;
373
+ }
374
+ default:
375
+ throw new Error(`Unexpected result type: ${result.__typename}`);
376
+ }
377
+ await pgClient.query({
378
+ text: `
379
+ UPDATE hub.take_request
380
+ SET status = $1,
381
+ mp_transfer_id = $2,
382
+ mp_transfer_status = $3,
383
+ transfer_needs_completion = $4,
384
+ mp_status = $5
385
+ WHERE id = $6
386
+ `,
387
+ values: [
388
+ resultStatus,
389
+ transferId,
390
+ MpTransferStatus.Pending,
391
+ true,
392
+ result.__typename === "TakeRequestAlreadyTerminal"
393
+ ? result.takeRequest.status
394
+ : MpTakeRequestStatus.Pending,
395
+ takeRequestId,
396
+ ],
397
+ });
398
+ return transferId;
399
+ }
400
+ catch (error) {
401
+ console.error(`[attemptTransfer] Error: ${error}`);
402
+ await pgClient.query({
403
+ text: `
404
+ UPDATE hub.take_request
405
+ SET status = $1
406
+ WHERE id = $2
407
+ `,
408
+ values: [LocalTakeRequestStatus.FAILED, takeRequestId],
409
+ });
410
+ return null;
411
+ }
412
+ }
413
+ async function processPendingTransferCompletions({ casinoId, graphqlClient, abortSignal, }) {
414
+ if (abortSignal.aborted) {
415
+ return;
416
+ }
417
+ const pendingCompletions = await superuserPool
418
+ .query({
419
+ text: `
420
+ SELECT id, mp_take_request_id, mp_transfer_id, transfer_completion_attempted_at
421
+ FROM hub.take_request
422
+ WHERE casino_id = $1
423
+ AND mp_transfer_id IS NOT NULL
424
+ AND transfer_needs_completion = TRUE
425
+ AND (
426
+ transfer_completion_attempted_at IS NULL OR
427
+ transfer_completion_attempted_at < now() - CASE
428
+ -- Exponential backoff: 10s, 1m, 5m, 15m, 30m, 1h, 3h
429
+ WHEN transfer_completion_attempted_at IS NULL THEN interval '0 seconds'
430
+ WHEN transfer_completion_attempted_at > now() - interval '10 minutes' THEN interval '10 seconds'
431
+ WHEN transfer_completion_attempted_at > now() - interval '30 minutes' THEN interval '1 minute'
432
+ WHEN transfer_completion_attempted_at > now() - interval '1 hour' THEN interval '5 minutes'
433
+ WHEN transfer_completion_attempted_at > now() - interval '3 hours' THEN interval '15 minutes'
434
+ WHEN transfer_completion_attempted_at > now() - interval '6 hours' THEN interval '30 minutes'
435
+ WHEN transfer_completion_attempted_at > now() - interval '24 hours' THEN interval '1 hour'
436
+ ELSE interval '3 hours'
437
+ END
438
+ )
439
+ LIMIT 10
440
+ `,
441
+ values: [casinoId],
442
+ })
443
+ .then((result) => result.rows);
444
+ console.log(`[processPendingTransferCompletions] Found ${pendingCompletions.length} transfers needing completion`);
445
+ for (const request of pendingCompletions) {
446
+ if (abortSignal.aborted) {
447
+ break;
448
+ }
449
+ await completeTransfer({
450
+ mpTakeRequestId: request.mp_take_request_id,
451
+ takeRequestId: request.id,
452
+ mpTransferId: request.mp_transfer_id,
453
+ graphqlClient,
454
+ casinoId,
455
+ });
456
+ }
457
+ }
458
+ async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, }) {
459
+ return withPgPoolTransaction(superuserPool, async (pgClient) => {
460
+ await pgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
461
+ mpTakeRequestId,
462
+ casinoId,
463
+ });
464
+ await pgClient.query({
465
+ text: `
466
+ UPDATE hub.take_request
467
+ SET transfer_completion_attempted_at = now()
468
+ WHERE id = $1
469
+ `,
470
+ values: [takeRequestId],
471
+ });
472
+ try {
473
+ const result = await graphqlClient.request(MP_COMPLETE_TRANSFER, {
474
+ mpTransferId,
475
+ });
476
+ const completionResult = result.completeTransfer?.result;
477
+ if (!completionResult) {
478
+ throw new Error("No completion result returned from MP API");
479
+ }
480
+ switch (completionResult.__typename) {
481
+ case "CompleteTransferSuccess":
482
+ await pgClient.query({
483
+ text: `
484
+ UPDATE hub.take_request
485
+ SET
486
+ transfer_needs_completion = FALSE,
487
+ mp_transfer_status = $2,
488
+ status = $3,
489
+ mp_status = $4
490
+ WHERE id = $1
491
+ `,
492
+ values: [
493
+ takeRequestId,
494
+ completionResult.transfer.status,
495
+ LocalTakeRequestStatus.COMPLETED,
496
+ MpTakeRequestStatus.Transferred,
497
+ ],
498
+ });
499
+ console.log(`[completeTransfer] Successfully completed transfer ${mpTransferId}`);
500
+ break;
501
+ case "InvalidTransferStatus": {
502
+ const currentStatus = completionResult.currentStatus;
503
+ if (currentStatus === "COMPLETED") {
504
+ await pgClient.query({
505
+ text: `
506
+ UPDATE hub.take_request
507
+ SET
508
+ transfer_needs_completion = FALSE,
509
+ mp_transfer_status = $2,
510
+ status = $3,
511
+ mp_status = $4
512
+ WHERE id = $1
513
+ `,
514
+ values: [
515
+ takeRequestId,
516
+ currentStatus,
517
+ LocalTakeRequestStatus.COMPLETED,
518
+ MpTakeRequestStatus.Transferred,
519
+ ],
520
+ });
521
+ console.log(`[completeTransfer] Transfer ${mpTransferId} was already completed`);
522
+ }
523
+ else if (currentStatus === "CANCELED" ||
524
+ currentStatus === "EXPIRED") {
525
+ const mpStatus = completionResult.transfer.__typename === "ExperienceTransfer"
526
+ ? completionResult.transfer.takeRequest?.status
527
+ : null;
528
+ if (!mpStatus) {
529
+ throw new Error("No MP status returned from MP API");
530
+ }
531
+ await pgClient.query({
532
+ text: `
533
+ UPDATE hub.take_request
534
+ SET
535
+ transfer_needs_completion = FALSE,
536
+ mp_transfer_status = $2,
537
+ status = $3,
538
+ mp_status = $4
539
+ WHERE id = $1
540
+ `,
541
+ values: [
542
+ takeRequestId,
543
+ currentStatus,
544
+ LocalTakeRequestStatus.FAILED,
545
+ mpStatus,
546
+ ],
547
+ });
548
+ console.log(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}`);
549
+ }
550
+ else {
551
+ console.log(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}, will retry later`);
552
+ }
553
+ break;
554
+ }
555
+ default: {
556
+ const exhaustiveCheck = completionResult;
557
+ throw new Error(`Unknown completion result type: ${exhaustiveCheck}`);
558
+ }
559
+ }
560
+ }
561
+ catch (error) {
562
+ console.error(`[completeTransfer] Error completing transfer ${mpTransferId}:`, error);
563
+ }
564
+ });
565
+ }
566
+ async function processStuckRequests({ casinoId, graphqlClient, abortSignal, }) {
567
+ if (abortSignal.aborted) {
568
+ return;
569
+ }
570
+ const stuckRequests = await superuserPool
571
+ .query({
572
+ text: `
573
+ SELECT id, mp_take_request_id
574
+ FROM hub.take_request
575
+ WHERE casino_id = $1
576
+ AND status = $2
577
+ AND mp_transfer_id IS NULL
578
+ AND updated_at < now() - interval '5 minutes'
579
+ LIMIT 10
580
+ `,
581
+ values: [casinoId, LocalTakeRequestStatus.PROCESSING],
582
+ })
583
+ .then((result) => result.rows);
584
+ console.log(`[processStuckRequests] Found ${stuckRequests.length} stuck take requests`);
585
+ for (const request of stuckRequests) {
586
+ if (abortSignal.aborted) {
587
+ break;
588
+ }
589
+ await processSingleTakeRequest({
590
+ mpTakeRequestId: request.mp_take_request_id,
591
+ casinoId,
592
+ graphqlClient,
593
+ });
594
+ }
595
+ }
596
+ async function loadRequiredEntities(pgClient, params) {
597
+ const { type, currencyKey, casinoId } = params;
598
+ assert(pgClient._inTransaction, "pgClient must be in a transaction");
599
+ const dbCurrency = await pgClient
600
+ .query({
601
+ text: `
602
+ SELECT key
603
+ FROM hub.currency
604
+ WHERE key = $1 AND casino_id = $2
605
+ `,
606
+ values: [currencyKey, casinoId],
607
+ })
608
+ .then(maybeOneRow);
609
+ if (!dbCurrency) {
610
+ console.warn(`[loadRequiredEntities] Currency ${currencyKey} not found`);
611
+ return {
612
+ dbCurrency: null,
613
+ dbUser: null,
614
+ dbExperience: null,
615
+ dbBalance: null,
616
+ };
617
+ }
618
+ let dbUser;
619
+ if (type === "localId") {
620
+ dbUser = await pgClient
621
+ .query({
622
+ text: `
623
+ SELECT *
624
+ FROM hub.user
625
+ WHERE id = $1 AND casino_id = $2
626
+ `,
627
+ values: [params.userId, casinoId],
628
+ })
629
+ .then(maybeOneRow);
630
+ }
631
+ else {
632
+ dbUser = await pgClient
633
+ .query({
634
+ text: `
635
+ SELECT *
636
+ FROM hub.user
637
+ WHERE mp_user_id = $1 AND casino_id = $2
638
+ `,
639
+ values: [params.mpUserId, casinoId],
640
+ })
641
+ .then(maybeOneRow);
642
+ }
643
+ if (!dbUser) {
644
+ console.warn(`[loadRequiredEntities] User not found`);
645
+ return { dbCurrency, dbUser: null, dbExperience: null, dbBalance: null };
646
+ }
647
+ let dbExperience;
648
+ if (type === "localId") {
649
+ dbExperience = await pgClient
650
+ .query({
651
+ text: `
652
+ SELECT *
653
+ FROM hub.experience
654
+ WHERE id = $1 AND casino_id = $2
655
+ `,
656
+ values: [params.experienceId, casinoId],
657
+ })
658
+ .then(maybeOneRow);
659
+ }
660
+ else {
661
+ dbExperience = await pgClient
662
+ .query({
663
+ text: `
664
+ SELECT *
665
+ FROM hub.experience
666
+ WHERE mp_experience_id = $1 AND casino_id = $2
667
+ `,
668
+ values: [params.mpExperienceId, casinoId],
669
+ })
670
+ .then(maybeOneRow);
671
+ }
672
+ if (!dbExperience) {
673
+ console.warn(`[loadRequiredEntities] Experience not found`);
674
+ return { dbCurrency, dbUser, dbExperience: null, dbBalance: null };
675
+ }
676
+ let dbBalance = null;
677
+ if (dbUser && dbExperience) {
678
+ dbBalance = await pgClient
679
+ .query({
680
+ text: `
681
+ SELECT id, amount
682
+ FROM hub.balance
683
+ WHERE user_id = $1
684
+ AND experience_id = $2
685
+ AND casino_id = $3
686
+ AND currency_key = $4
687
+ FOR UPDATE
688
+ `,
689
+ values: [dbUser.id, dbExperience.id, casinoId, dbCurrency.key],
690
+ })
691
+ .then(maybeOneRow)
692
+ .then((row) => row ?? null);
693
+ }
694
+ return { dbCurrency, dbUser, dbExperience, dbBalance };
695
+ }
696
+ async function rejectMpTakeRequest(pgClient, graphqlClient, mpTakeRequestId, takeRequestId) {
697
+ assert(pgClient._inTransaction, "pgClient must be in a transaction");
698
+ try {
699
+ console.log(`[rejectMpTakeRequest] Rejecting take request ${mpTakeRequestId}`);
700
+ const rejectResult = await graphqlClient.request(MP_REJECT_TAKE_REQUEST, {
701
+ mpTakeRequestId,
702
+ });
703
+ let success = false;
704
+ let updatedMpStatus = null;
705
+ switch (rejectResult.rejectTakeRequest.result.__typename) {
706
+ case "RejectTakeRequestSuccess":
707
+ updatedMpStatus = MpTakeRequestStatus.ControllerRejected;
708
+ success = true;
709
+ break;
710
+ case "TakeRequestAlreadyTerminal":
711
+ if (rejectResult.rejectTakeRequest.result.takeRequest?.status) {
712
+ updatedMpStatus = rejectResult.rejectTakeRequest.result.takeRequest
713
+ .status;
714
+ }
715
+ success = true;
716
+ break;
717
+ default: {
718
+ const exhaustiveCheck = rejectResult.rejectTakeRequest.result;
719
+ throw new Error(`Unexpected result type: ${exhaustiveCheck}`);
720
+ }
721
+ }
722
+ if (updatedMpStatus) {
723
+ if (takeRequestId) {
724
+ await pgClient.query({
725
+ text: `
726
+ UPDATE hub.take_request
727
+ SET status = $1,
728
+ mp_status = $2
729
+ WHERE id = $3
730
+ `,
731
+ values: [
732
+ LocalTakeRequestStatus.REJECTED,
733
+ updatedMpStatus,
734
+ takeRequestId,
735
+ ],
736
+ });
737
+ }
738
+ else {
739
+ const existingRequest = await pgClient
740
+ .query({
741
+ text: `
742
+ SELECT id FROM hub.take_request
743
+ WHERE mp_take_request_id = $1
744
+ FOR UPDATE
745
+ `,
746
+ values: [mpTakeRequestId],
747
+ })
748
+ .then(maybeOneRow);
749
+ if (existingRequest) {
750
+ await pgClient.query({
751
+ text: `
752
+ UPDATE hub.take_request
753
+ SET status = $1,
754
+ mp_status = $2
755
+ WHERE id = $3
756
+ `,
757
+ values: [
758
+ LocalTakeRequestStatus.REJECTED,
759
+ updatedMpStatus,
760
+ existingRequest.id,
761
+ ],
762
+ });
763
+ }
764
+ }
765
+ }
766
+ return success;
767
+ }
768
+ catch (error) {
769
+ console.error(`[rejectMpTakeRequest] Error: ${error}`);
770
+ return false;
771
+ }
772
+ }
773
+ async function updateTakeRequestStatus(pgClient, takeRequestId, status) {
774
+ assert(pgClient._inTransaction, "pgClient must be in a transaction");
775
+ return pgClient.query({
776
+ text: `
777
+ UPDATE hub.take_request
778
+ SET status = $1, updated_at = now()
779
+ WHERE id = $2
780
+ `,
781
+ values: [status, takeRequestId],
782
+ });
783
+ }