@moneypot/hub 1.9.0-dev.12 → 1.9.0-dev.14
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.
- package/dist/src/db/types.d.ts +1 -0
- package/dist/src/db/util.d.ts +4 -0
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +18 -10
- package/dist/src/pg-versions/010-take-request-refunded-at.sql +1 -0
- package/dist/src/process-transfers/websocket-processor.js +2 -0
- package/dist/src/risk-policy.d.ts +3 -5
- package/dist/src/risk-policy.js +3 -6
- package/dist/src/server/graphile.config.js +1 -1
- package/dist/src/take-request/process-take-request.d.ts +11 -1
- package/dist/src/take-request/process-take-request.js +58 -14
- package/package.json +1 -1
package/dist/src/db/types.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ export type DbTakeRequest = {
|
|
|
112
112
|
mp_transfer_status: DbTransferStatusKind | null;
|
|
113
113
|
transfer_needs_completion: boolean;
|
|
114
114
|
transfer_completion_attempted_at: Date | null;
|
|
115
|
+
refunded_at: Date | null;
|
|
115
116
|
updated_at: Date;
|
|
116
117
|
};
|
|
117
118
|
export type DbHashChain = {
|
package/dist/src/db/util.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { QueryResult, QueryResultRow } from "pg";
|
|
2
2
|
import { PgClientResult } from "postgraphile/@dataplan/pg";
|
|
3
|
+
import * as pg from "pg";
|
|
4
|
+
export interface QueryExecutor {
|
|
5
|
+
query<T extends pg.QueryResultRow = any>(queryText: string, values?: any[]): Promise<pg.QueryResult<T>>;
|
|
6
|
+
}
|
|
3
7
|
type ResultType<T> = PgClientResult<T> | QueryResult<T extends QueryResultRow ? T : never>;
|
|
4
8
|
export declare function maybeOneRow<T>(result: ResultType<T>): T | undefined;
|
|
5
9
|
export declare function exactlyOneRow<T>(result: ResultType<T>): T;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type ServerOptions = {
|
|
|
26
26
|
exportSchemaSDLPath?: string;
|
|
27
27
|
userDatabaseMigrationsPath?: string;
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
export declare function startAndListen(options: ServerOptions): Promise<{
|
|
30
30
|
port: number;
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
stop: () => Promise<void>;
|
|
32
|
+
}>;
|
package/dist/src/index.js
CHANGED
|
@@ -46,9 +46,11 @@ async function initialize(options) {
|
|
|
46
46
|
logger.info("Initialization aborted by graceful shutdown");
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
if (process.env.NODE_ENV !== "test") {
|
|
50
|
+
initializeTransferProcessors({
|
|
51
|
+
signal: options.signal,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
52
54
|
}
|
|
53
55
|
export async function startAndListen(options) {
|
|
54
56
|
if (options.plugins && options.plugins.some((p) => typeof p === "function")) {
|
|
@@ -75,7 +77,7 @@ export async function startAndListen(options) {
|
|
|
75
77
|
extraPgSchemas: options.extraPgSchemas,
|
|
76
78
|
abortSignal: abortController.signal,
|
|
77
79
|
});
|
|
78
|
-
const gracefulShutdown = async () => {
|
|
80
|
+
const gracefulShutdown = async ({ exit = true } = {}) => {
|
|
79
81
|
if (isShuttingDown) {
|
|
80
82
|
logger.warn("Already shutting down.");
|
|
81
83
|
return;
|
|
@@ -87,12 +89,13 @@ export async function startAndListen(options) {
|
|
|
87
89
|
logger.info("Closing resources...");
|
|
88
90
|
hubServer.shutdown();
|
|
89
91
|
try {
|
|
92
|
+
const closeTasks = [db.notifier.close()];
|
|
93
|
+
if (process.env.NODE_ENV !== "test") {
|
|
94
|
+
closeTasks.push(db.postgraphilePool.end());
|
|
95
|
+
closeTasks.push(db.superuserPool.end());
|
|
96
|
+
}
|
|
90
97
|
await Promise.race([
|
|
91
|
-
Promise.all(
|
|
92
|
-
db.notifier.close(),
|
|
93
|
-
db.postgraphilePool.end(),
|
|
94
|
-
db.superuserPool.end(),
|
|
95
|
-
]),
|
|
98
|
+
Promise.all(closeTasks),
|
|
96
99
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Database cleanup timeout")), 3000)),
|
|
97
100
|
]);
|
|
98
101
|
logger.info("Cleanup complete.");
|
|
@@ -100,13 +103,18 @@ export async function startAndListen(options) {
|
|
|
100
103
|
catch (err) {
|
|
101
104
|
logger.warn(err, "Cleanup error (proceeding anyway)");
|
|
102
105
|
}
|
|
103
|
-
|
|
106
|
+
if (exit) {
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
104
109
|
};
|
|
105
110
|
process.on("SIGINT", gracefulShutdown);
|
|
106
111
|
process.on("SIGTERM", gracefulShutdown);
|
|
107
112
|
return hubServer.listen().then(() => {
|
|
108
113
|
return {
|
|
109
114
|
port: config.PORT,
|
|
115
|
+
stop: async () => {
|
|
116
|
+
await gracefulShutdown({ exit: false });
|
|
117
|
+
},
|
|
110
118
|
};
|
|
111
119
|
});
|
|
112
120
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alter table hub.take_request add column refunded_at timestamptz
|
|
@@ -7,6 +7,7 @@ import { TRANSFER_FIELDS } from "./graphql.js";
|
|
|
7
7
|
import { logger } from "../logger.js";
|
|
8
8
|
import { processTransfer } from "./process-transfer.js";
|
|
9
9
|
import assert from "assert";
|
|
10
|
+
import { superuserPool } from "../db/index.js";
|
|
10
11
|
import { mpGetTakeRequest, processSingleTakeRequest, } from "../take-request/process-take-request.js";
|
|
11
12
|
function httpToWs(url) {
|
|
12
13
|
if (url.protocol === "http:") {
|
|
@@ -107,6 +108,7 @@ export function startWebsocketProcessor({ casinoId, graphqlUrl, signal, controll
|
|
|
107
108
|
mpTakeRequest,
|
|
108
109
|
casinoId,
|
|
109
110
|
graphqlClient,
|
|
111
|
+
pool: superuserPool,
|
|
110
112
|
});
|
|
111
113
|
});
|
|
112
114
|
signal.addEventListener("abort", () => {
|
|
@@ -7,11 +7,10 @@ export type RiskPolicyArgs = {
|
|
|
7
7
|
maxPotentialPayout: number;
|
|
8
8
|
outcomes: DbOutcome[];
|
|
9
9
|
};
|
|
10
|
-
type
|
|
11
|
-
export type RiskLimits = AtLeastOneKey<{
|
|
10
|
+
export type RiskLimits = {
|
|
12
11
|
maxWager?: number;
|
|
13
|
-
maxPayout
|
|
14
|
-
}
|
|
12
|
+
maxPayout: number;
|
|
13
|
+
};
|
|
15
14
|
export type RiskPolicy = (args: RiskPolicyArgs) => RiskLimits;
|
|
16
15
|
export declare function validateRisk(options: RiskPolicyArgs & {
|
|
17
16
|
riskPolicy: RiskPolicy;
|
|
@@ -19,4 +18,3 @@ export declare function validateRisk(options: RiskPolicyArgs & {
|
|
|
19
18
|
displayUnitName: string;
|
|
20
19
|
displayUnitScale: number;
|
|
21
20
|
}): Result<void, string>;
|
|
22
|
-
export {};
|
package/dist/src/risk-policy.js
CHANGED
|
@@ -4,12 +4,9 @@ import { z } from "zod";
|
|
|
4
4
|
const RiskLimitsSchema = z
|
|
5
5
|
.object({
|
|
6
6
|
maxWager: z.number().positive().optional(),
|
|
7
|
-
maxPayout: z.number().positive()
|
|
7
|
+
maxPayout: z.number().positive(),
|
|
8
8
|
})
|
|
9
|
-
.strict()
|
|
10
|
-
.refine((v) => v.maxWager !== undefined || v.maxPayout !== undefined, {
|
|
11
|
-
message: "Provide at least one of maxWager or maxPayout.",
|
|
12
|
-
});
|
|
9
|
+
.strict();
|
|
13
10
|
export function validateRisk(options) {
|
|
14
11
|
const { wager, bankroll, maxPotentialPayout, riskPolicy } = options;
|
|
15
12
|
if (maxPotentialPayout > bankroll) {
|
|
@@ -48,7 +45,7 @@ export function validateRisk(options) {
|
|
|
48
45
|
})})`,
|
|
49
46
|
};
|
|
50
47
|
}
|
|
51
|
-
if (
|
|
48
|
+
if (maxPotentialPayout > limits.maxPayout) {
|
|
52
49
|
return {
|
|
53
50
|
ok: false,
|
|
54
51
|
error: `Payout (${formatCurrency(maxPotentialPayout, {
|
|
@@ -56,6 +56,7 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
|
|
|
56
56
|
if (!exportSchemaSDLPath.endsWith(".graphql")) {
|
|
57
57
|
throw new Error("exportSchemaSDLPath must end with .graphql");
|
|
58
58
|
}
|
|
59
|
+
logger.info(`Will save generated graphql schema to ${exportSchemaSDLPath}`);
|
|
59
60
|
}
|
|
60
61
|
const mutablePlugins = [...plugins];
|
|
61
62
|
for (const requiredPlugin of requiredPlugins) {
|
|
@@ -64,7 +65,6 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
|
|
|
64
65
|
mutablePlugins.unshift(requiredPlugin);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
logger.info(`Will save generated graphql schema to ${exportSchemaSDLPath}`);
|
|
68
68
|
const preset = {
|
|
69
69
|
extends: [PostGraphileAmberPreset],
|
|
70
70
|
disablePlugins: ["NodePlugin"],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GraphQLClient } from "graphql-request";
|
|
2
2
|
import { MpTakeRequestFieldsFragment } from "../__generated__/graphql.js";
|
|
3
|
+
import * as pg from "pg";
|
|
3
4
|
export declare const MP_TAKE_REQUEST_FIELDS: import("@graphql-typed-document-node/core").TypedDocumentNode<MpTakeRequestFieldsFragment, unknown>;
|
|
4
5
|
export declare function mpGetTakeRequest(graphqlClient: GraphQLClient, id: string): Promise<MpTakeRequestFieldsFragment | null>;
|
|
5
6
|
export declare function processTakeRequests({ abortSignal, controllerId, casinoId, graphqlClient, }: {
|
|
@@ -8,9 +9,18 @@ export declare function processTakeRequests({ abortSignal, controllerId, casinoI
|
|
|
8
9
|
casinoId: string;
|
|
9
10
|
graphqlClient: GraphQLClient;
|
|
10
11
|
}): Promise<void>;
|
|
11
|
-
export declare function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, }: {
|
|
12
|
+
export declare function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, pool, }: {
|
|
12
13
|
mpTakeRequestId: string;
|
|
13
14
|
mpTakeRequest?: MpTakeRequestFieldsFragment;
|
|
14
15
|
casinoId: string;
|
|
15
16
|
graphqlClient: GraphQLClient;
|
|
17
|
+
pool: pg.Pool;
|
|
16
18
|
}): Promise<string | null>;
|
|
19
|
+
export declare function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, pool, }: {
|
|
20
|
+
mpTakeRequestId: string;
|
|
21
|
+
takeRequestId: string;
|
|
22
|
+
mpTransferId: string;
|
|
23
|
+
graphqlClient: GraphQLClient;
|
|
24
|
+
casinoId: string;
|
|
25
|
+
pool: pg.Pool;
|
|
26
|
+
}): Promise<void>;
|
|
@@ -180,6 +180,7 @@ export async function processTakeRequests({ abortSignal, controllerId, casinoId,
|
|
|
180
180
|
mpTakeRequest: takeRequest,
|
|
181
181
|
casinoId,
|
|
182
182
|
graphqlClient,
|
|
183
|
+
pool: superuserPool,
|
|
183
184
|
});
|
|
184
185
|
}
|
|
185
186
|
await processPendingTransferCompletions({
|
|
@@ -200,8 +201,8 @@ async function fetchPendingTakeRequests(graphqlClient, controllerId) {
|
|
|
200
201
|
return useFragment(MP_TAKE_REQUEST_FIELDS, takeRequest);
|
|
201
202
|
}) || []);
|
|
202
203
|
}
|
|
203
|
-
export async function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, }) {
|
|
204
|
-
return withPgPoolTransaction(
|
|
204
|
+
export async function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, pool, }) {
|
|
205
|
+
return withPgPoolTransaction(pool, async (pgClient) => {
|
|
205
206
|
await PgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
|
|
206
207
|
mpTakeRequestId,
|
|
207
208
|
casinoId,
|
|
@@ -247,10 +248,7 @@ async function createAndProcessNewTakeRequest({ pgClient, mpTakeRequest, casinoI
|
|
|
247
248
|
currencyKey: mpTakeRequest.currencyKey,
|
|
248
249
|
casinoId,
|
|
249
250
|
});
|
|
250
|
-
|
|
251
|
-
await rejectMpTakeRequest(pgClient, graphqlClient, mpTakeRequest.id);
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
251
|
+
assert(dbUser && dbExperience && dbCurrency && dbBalance, "Required entities not found");
|
|
254
252
|
const amountToTransfer = Math.floor(typeof mpTakeRequest.amount === "number"
|
|
255
253
|
? Math.min(mpTakeRequest.amount, dbBalance.amount)
|
|
256
254
|
: dbBalance.amount);
|
|
@@ -476,15 +474,29 @@ async function processPendingTransferCompletions({ casinoId, graphqlClient, abor
|
|
|
476
474
|
mpTransferId: request.mp_transfer_id,
|
|
477
475
|
graphqlClient,
|
|
478
476
|
casinoId,
|
|
477
|
+
pool: superuserPool,
|
|
479
478
|
});
|
|
480
479
|
}
|
|
481
480
|
}
|
|
482
|
-
async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, }) {
|
|
483
|
-
return withPgPoolTransaction(
|
|
481
|
+
export async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, pool, }) {
|
|
482
|
+
return withPgPoolTransaction(pool, async (pgClient) => {
|
|
484
483
|
await PgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
|
|
485
484
|
mpTakeRequestId,
|
|
486
485
|
casinoId,
|
|
487
486
|
});
|
|
487
|
+
const dbTakeRequest = await pgClient
|
|
488
|
+
.query({
|
|
489
|
+
text: `
|
|
490
|
+
SELECT transfer_needs_completion
|
|
491
|
+
FROM hub.take_request
|
|
492
|
+
WHERE id = $1
|
|
493
|
+
`,
|
|
494
|
+
values: [takeRequestId],
|
|
495
|
+
})
|
|
496
|
+
.then(exactlyOneRow);
|
|
497
|
+
if (!dbTakeRequest.transfer_needs_completion) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
488
500
|
await pgClient.query({
|
|
489
501
|
text: `
|
|
490
502
|
UPDATE hub.take_request
|
|
@@ -553,26 +565,57 @@ async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId,
|
|
|
553
565
|
if (!mpStatus) {
|
|
554
566
|
throw new Error("No MP status returned from MP API");
|
|
555
567
|
}
|
|
568
|
+
const takeRequestData = await pgClient
|
|
569
|
+
.query({
|
|
570
|
+
text: `
|
|
571
|
+
SELECT *
|
|
572
|
+
FROM hub.take_request
|
|
573
|
+
WHERE id = $1
|
|
574
|
+
`,
|
|
575
|
+
values: [takeRequestId],
|
|
576
|
+
})
|
|
577
|
+
.then(exactlyOneRow);
|
|
578
|
+
if (takeRequestData.refunded_at) {
|
|
579
|
+
logger.warn({ mpTransferId, takeRequestId }, `[completeTransfer] Transfer was already refunded. This should never happen.`);
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
await pgClient.query({
|
|
583
|
+
text: `
|
|
584
|
+
UPDATE hub.balance
|
|
585
|
+
SET amount = amount + $1
|
|
586
|
+
WHERE user_id = $2
|
|
587
|
+
AND experience_id = $3
|
|
588
|
+
AND casino_id = $4
|
|
589
|
+
AND currency_key = $5
|
|
590
|
+
`,
|
|
591
|
+
values: [
|
|
592
|
+
takeRequestData.reserved_amount,
|
|
593
|
+
takeRequestData.user_id,
|
|
594
|
+
takeRequestData.experience_id,
|
|
595
|
+
takeRequestData.casino_id,
|
|
596
|
+
takeRequestData.currency_key,
|
|
597
|
+
],
|
|
598
|
+
});
|
|
556
599
|
await pgClient.query({
|
|
557
600
|
text: `
|
|
558
601
|
UPDATE hub.take_request
|
|
559
602
|
SET
|
|
560
603
|
transfer_needs_completion = FALSE,
|
|
561
604
|
mp_transfer_status = $2,
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
605
|
+
mp_status = $3,
|
|
606
|
+
debug = $4,
|
|
607
|
+
refunded_at = now(),
|
|
608
|
+
updated_at = now()
|
|
565
609
|
WHERE id = $1
|
|
566
610
|
`,
|
|
567
611
|
values: [
|
|
568
612
|
takeRequestId,
|
|
569
613
|
currentStatus,
|
|
570
|
-
LocalTakeRequestStatus.FAILED,
|
|
571
614
|
mpStatus,
|
|
572
|
-
`MP transfer was ${currentStatus}`,
|
|
615
|
+
`MP transfer was ${currentStatus}. Refunded ${takeRequestData.reserved_amount} to user`,
|
|
573
616
|
],
|
|
574
617
|
});
|
|
575
|
-
logger.info(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}
|
|
618
|
+
logger.info(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}. Refunded ${takeRequestData.reserved_amount} base units of ${takeRequestData.currency_key} to hub user.`);
|
|
576
619
|
}
|
|
577
620
|
else {
|
|
578
621
|
logger.info(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}, will retry later`);
|
|
@@ -665,6 +708,7 @@ async function processStuckRequests({ casinoId, graphqlClient, abortSignal, }) {
|
|
|
665
708
|
mpTakeRequestId: request.mp_take_request_id,
|
|
666
709
|
casinoId,
|
|
667
710
|
graphqlClient,
|
|
711
|
+
pool: superuserPool,
|
|
668
712
|
});
|
|
669
713
|
}
|
|
670
714
|
}
|