@moneypot/hub 0.0.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.
- package/README.md +108 -0
- package/dist/cli/add-casino.d.ts +2 -0
- package/dist/cli/add-casino.js +116 -0
- package/dist/dashboard/assets/index-BtrbrisP.js +360 -0
- package/dist/dashboard/assets/index-tK7EUtyc.css +5 -0
- package/dist/dashboard/index.html +13 -0
- package/dist/src/GraphQLError.d.ts +8 -0
- package/dist/src/GraphQLError.js +79 -0
- package/dist/src/__generated__/fragment-masking.d.ts +19 -0
- package/dist/src/__generated__/fragment-masking.js +16 -0
- package/dist/src/__generated__/gql.d.ts +26 -0
- package/dist/src/__generated__/gql.js +15 -0
- package/dist/src/__generated__/graphql.d.ts +3129 -0
- package/dist/src/__generated__/graphql.js +454 -0
- package/dist/src/__generated__/index.d.ts +2 -0
- package/dist/src/__generated__/index.js +2 -0
- package/dist/src/config.d.ts +14 -0
- package/dist/src/config.js +57 -0
- package/dist/src/db/index.d.ts +89 -0
- package/dist/src/db/index.js +339 -0
- package/dist/src/db/internal.d.ts +7 -0
- package/dist/src/db/internal.js +33 -0
- package/dist/src/db/public.d.ts +7 -0
- package/dist/src/db/public.js +20 -0
- package/dist/src/db/types.d.ts +80 -0
- package/dist/src/db/types.js +1 -0
- package/dist/src/db/util.d.ts +6 -0
- package/dist/src/db/util.js +9 -0
- package/dist/src/express.d.ts +13 -0
- package/dist/src/express.js +1 -0
- package/dist/src/grafast.d.ts +1 -0
- package/dist/src/grafast.js +1 -0
- package/dist/src/graphile.d.ts +1 -0
- package/dist/src/graphile.js +1 -0
- package/dist/src/graphql-client.d.ts +6 -0
- package/dist/src/graphql-client.js +8 -0
- package/dist/src/graphql-queries.d.ts +18 -0
- package/dist/src/graphql-queries.js +123 -0
- package/dist/src/graphql.d.ts +1 -0
- package/dist/src/graphql.js +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +65 -0
- package/dist/src/logger.d.ts +9 -0
- package/dist/src/logger.js +21 -0
- package/dist/src/pg-versions/001-schema.sql +456 -0
- package/dist/src/plugins/caas-add-casino.d.ts +1 -0
- package/dist/src/plugins/caas-add-casino.js +150 -0
- package/dist/src/plugins/caas-authenticate.d.ts +1 -0
- package/dist/src/plugins/caas-authenticate.js +175 -0
- package/dist/src/plugins/caas-balance-alert.d.ts +1 -0
- package/dist/src/plugins/caas-balance-alert.js +43 -0
- package/dist/src/plugins/caas-claim-faucet.d.ts +1 -0
- package/dist/src/plugins/caas-claim-faucet.js +85 -0
- package/dist/src/plugins/caas-current-x.d.ts +1 -0
- package/dist/src/plugins/caas-current-x.js +62 -0
- package/dist/src/plugins/caas-schema-prefix.d.ts +1 -0
- package/dist/src/plugins/caas-schema-prefix.js +25 -0
- package/dist/src/plugins/caas-user-balance-by-currency.d.ts +1 -0
- package/dist/src/plugins/caas-user-balance-by-currency.js +55 -0
- package/dist/src/plugins/caas-withdraw.d.ts +1 -0
- package/dist/src/plugins/caas-withdraw.js +133 -0
- package/dist/src/plugins/debug.d.ts +1 -0
- package/dist/src/plugins/debug.js +14 -0
- package/dist/src/plugins/hub-add-casino.d.ts +1 -0
- package/dist/src/plugins/hub-add-casino.js +150 -0
- package/dist/src/plugins/hub-authenticate.d.ts +1 -0
- package/dist/src/plugins/hub-authenticate.js +175 -0
- package/dist/src/plugins/hub-balance-alert.d.ts +1 -0
- package/dist/src/plugins/hub-balance-alert.js +43 -0
- package/dist/src/plugins/hub-claim-faucet.d.ts +1 -0
- package/dist/src/plugins/hub-claim-faucet.js +85 -0
- package/dist/src/plugins/hub-current-x.d.ts +1 -0
- package/dist/src/plugins/hub-current-x.js +62 -0
- package/dist/src/plugins/hub-schema-prefix.d.ts +1 -0
- package/dist/src/plugins/hub-schema-prefix.js +25 -0
- package/dist/src/plugins/hub-user-balance-by-currency.d.ts +1 -0
- package/dist/src/plugins/hub-user-balance-by-currency.js +55 -0
- package/dist/src/plugins/hub-withdraw.d.ts +1 -0
- package/dist/src/plugins/hub-withdraw.js +133 -0
- package/dist/src/plugins/id-to-node-id.d.ts +1 -0
- package/dist/src/plugins/id-to-node-id.js +31 -0
- package/dist/src/plugins/validate-fields.d.ts +1 -0
- package/dist/src/plugins/validate-fields.js +61 -0
- package/dist/src/process-transfers.d.ts +7 -0
- package/dist/src/process-transfers.js +413 -0
- package/dist/src/process-withdrawal-request.d.ts +5 -0
- package/dist/src/process-withdrawal-request.js +129 -0
- package/dist/src/server/graphile.config.d.ts +33 -0
- package/dist/src/server/graphile.config.js +166 -0
- package/dist/src/server/handle-errors.d.ts +10 -0
- package/dist/src/server/handle-errors.js +88 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.js +69 -0
- package/dist/src/server/middleware/authentication.d.ts +4 -0
- package/dist/src/server/middleware/authentication.js +55 -0
- package/dist/src/server/middleware/cors.d.ts +3 -0
- package/dist/src/server/middleware/cors.js +14 -0
- package/dist/src/services/jwt-service.d.ts +13 -0
- package/dist/src/services/jwt-service.js +131 -0
- package/dist/src/smart-tags.d.ts +1 -0
- package/dist/src/smart-tags.js +55 -0
- package/dist/src/util.d.ts +12 -0
- package/dist/src/util.js +4 -0
- package/dist/src/validate.d.ts +9 -0
- package/dist/src/validate.js +91 -0
- package/package.json +69 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import pg, { QueryResult, QueryResultRow } from "pg";
|
|
2
|
+
import stream from "node:stream";
|
|
3
|
+
import { DbCasino, DbExperience, DbSession, DbTransferStatusKind, DbUser, DbWithdrawal } from "./types.js";
|
|
4
|
+
import { TransferStatusKind } from "../__generated__/graphql.js";
|
|
5
|
+
export * from "./types.js";
|
|
6
|
+
export * from "./public.js";
|
|
7
|
+
export * from "./util.js";
|
|
8
|
+
export declare function getPgClient(connectionString: string): pg.Client;
|
|
9
|
+
export declare const postgraphilePool: pg.Pool;
|
|
10
|
+
export declare const superuserPool: pg.Pool;
|
|
11
|
+
interface QueryExecutor {
|
|
12
|
+
query<T extends QueryResultRow = QueryResultRow>(_queryText: string, _values?: unknown[]): Promise<QueryResult<T>>;
|
|
13
|
+
}
|
|
14
|
+
export declare class UserFriendlyError extends Error {
|
|
15
|
+
constructor(userFriendlyMessage: string);
|
|
16
|
+
}
|
|
17
|
+
export declare function withPgPoolTransaction<T>(pool: pg.Pool, callback: (_client: pg.PoolClient) => Promise<T>, retryCount?: number, maxRetries?: number): Promise<T>;
|
|
18
|
+
export declare function userFromActiveSessionKey(pool: pg.Pool, sessionKey: string): Promise<{
|
|
19
|
+
user: DbUser;
|
|
20
|
+
sessionId: DbSession["id"];
|
|
21
|
+
} | null>;
|
|
22
|
+
declare class DatabaseNotifier extends stream.EventEmitter {
|
|
23
|
+
private client;
|
|
24
|
+
constructor(connectionString: string);
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
listen(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare const notifier: DatabaseNotifier;
|
|
29
|
+
export declare function getTransferCursor(pool: pg.Pool, { casinoId, }: {
|
|
30
|
+
casinoId: string;
|
|
31
|
+
}): Promise<string | undefined>;
|
|
32
|
+
export declare function setTransferCursor(pool: pg.Pool, { cursor, casinoId, }: {
|
|
33
|
+
casinoId: string;
|
|
34
|
+
cursor: string;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
export declare function upsertUser(pool: pg.Pool, { uname, casinoId, mpUserId, }: {
|
|
37
|
+
uname: string;
|
|
38
|
+
casinoId: string;
|
|
39
|
+
mpUserId: string;
|
|
40
|
+
}): Promise<DbUser>;
|
|
41
|
+
export declare function upsertExperience(pool: pg.Pool, { casinoId, mpExperienceId, name, }: {
|
|
42
|
+
casinoId: string;
|
|
43
|
+
mpExperienceId: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}): Promise<DbExperience>;
|
|
46
|
+
export declare function insertDeposit(pool: pg.Pool, o: {
|
|
47
|
+
casinoId: string;
|
|
48
|
+
mpTransferId: string;
|
|
49
|
+
userId: string;
|
|
50
|
+
experienceId: string;
|
|
51
|
+
amount: number;
|
|
52
|
+
currency: string;
|
|
53
|
+
}): Promise<null | undefined>;
|
|
54
|
+
export declare function processWithdrawal(pool: pg.Pool, { casinoId, mpTransferId, userId, experienceId, amount, currency, status, }: {
|
|
55
|
+
casinoId: string;
|
|
56
|
+
mpTransferId: string;
|
|
57
|
+
userId: string;
|
|
58
|
+
experienceId: string;
|
|
59
|
+
amount: number;
|
|
60
|
+
currency: string;
|
|
61
|
+
status: Extract<TransferStatusKind, "PENDING" | "COMPLETED" | "CANCELED">;
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
export declare function getUnconfirmedWithdrawals(pool: pg.Pool, { casinoId, limit, }: {
|
|
64
|
+
casinoId: string;
|
|
65
|
+
limit: number;
|
|
66
|
+
}): Promise<(DbWithdrawal & {
|
|
67
|
+
mp_experience_id: string;
|
|
68
|
+
mp_user_id: string;
|
|
69
|
+
})[]>;
|
|
70
|
+
export declare function getPendingWithdrawals(pool: pg.Pool, { casinoId, limit, }: {
|
|
71
|
+
casinoId: string;
|
|
72
|
+
limit: number;
|
|
73
|
+
}): Promise<(DbWithdrawal & {
|
|
74
|
+
mp_experience_id: string;
|
|
75
|
+
mp_user_id: string;
|
|
76
|
+
})[]>;
|
|
77
|
+
export declare function settleWithdrawal({ withdrawalId, newStatus, }: {
|
|
78
|
+
withdrawalId: string;
|
|
79
|
+
newStatus: Extract<DbTransferStatusKind, "COMPLETED" | "CANCELED">;
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
export declare function listCasinos(pool: pg.Pool): Promise<DbCasino[]>;
|
|
82
|
+
export declare function upsertCurrencies(pgClient: QueryExecutor, { casinoId, currencies, }: {
|
|
83
|
+
casinoId: string;
|
|
84
|
+
currencies: {
|
|
85
|
+
id: string;
|
|
86
|
+
displayUnitName: string;
|
|
87
|
+
displayUnitScale: number;
|
|
88
|
+
}[];
|
|
89
|
+
}): Promise<pg.QueryResult<pg.QueryResultRow>>;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import config from "../config.js";
|
|
3
|
+
import stream from "node:stream";
|
|
4
|
+
import { exactlyOneRow, maybeOneRow } from "./util.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
import { setTimeout } from "node:timers/promises";
|
|
7
|
+
import { assert } from "tsafe";
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
export * from "./public.js";
|
|
10
|
+
export * from "./util.js";
|
|
11
|
+
export function getPgClient(connectionString) {
|
|
12
|
+
const client = new pg.Client({ connectionString });
|
|
13
|
+
return client;
|
|
14
|
+
}
|
|
15
|
+
export const postgraphilePool = new pg.Pool({
|
|
16
|
+
connectionString: config.DATABASE_URL,
|
|
17
|
+
allowExitOnIdle: config.NODE_ENV !== "production",
|
|
18
|
+
});
|
|
19
|
+
export const superuserPool = new pg.Pool({
|
|
20
|
+
connectionString: config.SUPERUSER_DATABASE_URL,
|
|
21
|
+
allowExitOnIdle: config.NODE_ENV !== "production",
|
|
22
|
+
});
|
|
23
|
+
export class UserFriendlyError extends Error {
|
|
24
|
+
constructor(userFriendlyMessage) {
|
|
25
|
+
super(userFriendlyMessage);
|
|
26
|
+
this.name = "UserFriendlyError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const PG_ERROR_CODE = {
|
|
30
|
+
deadlock: "40P01",
|
|
31
|
+
serializationFailure: "40001",
|
|
32
|
+
};
|
|
33
|
+
export async function withPgPoolTransaction(pool, callback, retryCount = 0, maxRetries = 3) {
|
|
34
|
+
let client = null;
|
|
35
|
+
try {
|
|
36
|
+
client = await pool.connect();
|
|
37
|
+
await client.query("begin isolation level serializable");
|
|
38
|
+
const result = await callback(client);
|
|
39
|
+
await client.query("commit");
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (client) {
|
|
44
|
+
try {
|
|
45
|
+
await client.query("rollback");
|
|
46
|
+
}
|
|
47
|
+
catch (rollbackError) {
|
|
48
|
+
logger.error("Original error:", error);
|
|
49
|
+
logger.error("Rollback failed:", rollbackError);
|
|
50
|
+
client.release(true);
|
|
51
|
+
client = null;
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
if (retryCount < maxRetries &&
|
|
55
|
+
error instanceof pg.DatabaseError &&
|
|
56
|
+
(error.code === PG_ERROR_CODE.deadlock ||
|
|
57
|
+
error.code === PG_ERROR_CODE.serializationFailure)) {
|
|
58
|
+
const backoffMs = Math.min(100 * Math.pow(2, retryCount), 2000);
|
|
59
|
+
logger.warn(`Rerunning in ${backoffMs}ms (attempt ${retryCount + 1}/${maxRetries}) due to pg error code ${error.code}`);
|
|
60
|
+
await setTimeout(backoffMs);
|
|
61
|
+
return withPgPoolTransaction(pool, callback, retryCount + 1, maxRetries);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
if (client) {
|
|
68
|
+
client.release();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function userFromActiveSessionKey(pool, sessionKey) {
|
|
73
|
+
const result = await pool
|
|
74
|
+
.query(`
|
|
75
|
+
select u.id, u.uname, u.casino_id, s.experience_id, u.mp_user_id, s.id as session_id
|
|
76
|
+
from hub.active_session s
|
|
77
|
+
join hub.user u on s.user_id = u.id
|
|
78
|
+
join hub.experience e on e.id = s.experience_id
|
|
79
|
+
where s.key = $1
|
|
80
|
+
`, [sessionKey])
|
|
81
|
+
.then(maybeOneRow);
|
|
82
|
+
if (!result) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const { session_id, ...user } = result;
|
|
86
|
+
return {
|
|
87
|
+
user,
|
|
88
|
+
sessionId: session_id,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
class DatabaseNotifier extends stream.EventEmitter {
|
|
92
|
+
client;
|
|
93
|
+
constructor(connectionString) {
|
|
94
|
+
super();
|
|
95
|
+
this.client = new pg.Client({ connectionString });
|
|
96
|
+
}
|
|
97
|
+
async close() {
|
|
98
|
+
await this.client.end();
|
|
99
|
+
logger.info("[DatabaseNotifier] Database connection closed");
|
|
100
|
+
}
|
|
101
|
+
async listen() {
|
|
102
|
+
logger.info("[DatabaseNotifier] listening for balance updates");
|
|
103
|
+
await this.client.connect();
|
|
104
|
+
await this.client.query("LISTEN balance_update");
|
|
105
|
+
this.client.on("notification", (notification) => {
|
|
106
|
+
if (!notification.payload) {
|
|
107
|
+
logger.error("[DatabaseNotifier] postgres notification missing payload");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const payload = JSON.parse(notification.payload);
|
|
111
|
+
switch (notification.channel) {
|
|
112
|
+
case "balance_update":
|
|
113
|
+
this.emit(`balance:${payload.user_id}:${payload.experience_id}`, {
|
|
114
|
+
currencyKey: payload.currency_key,
|
|
115
|
+
amount: payload.amount,
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export const notifier = new DatabaseNotifier(config.DATABASE_URL);
|
|
123
|
+
export async function getTransferCursor(pool, { casinoId, }) {
|
|
124
|
+
const row = await pool
|
|
125
|
+
.query("select cursor from hub_hidden.transfer_cursor where casino_id = $1", [casinoId])
|
|
126
|
+
.then(maybeOneRow);
|
|
127
|
+
return row?.cursor;
|
|
128
|
+
}
|
|
129
|
+
export async function setTransferCursor(pool, { cursor, casinoId, }) {
|
|
130
|
+
console.log(`[setTransferCursor] SETTING CURSOR..... ${cursor}`);
|
|
131
|
+
await pool.query(`
|
|
132
|
+
insert into hub_hidden.transfer_cursor (casino_id, cursor)
|
|
133
|
+
values ($1, $2)
|
|
134
|
+
on conflict (casino_id) do update set cursor = $2
|
|
135
|
+
returning cursor
|
|
136
|
+
`, [casinoId, cursor]);
|
|
137
|
+
}
|
|
138
|
+
export async function upsertUser(pool, { uname, casinoId, mpUserId, }) {
|
|
139
|
+
const user = await pool
|
|
140
|
+
.query(`
|
|
141
|
+
WITH ins AS (
|
|
142
|
+
INSERT INTO hub.user (casino_id, mp_user_id, uname)
|
|
143
|
+
VALUES ($1, $2, $3)
|
|
144
|
+
ON CONFLICT (casino_id, mp_user_id) DO NOTHING
|
|
145
|
+
RETURNING *
|
|
146
|
+
)
|
|
147
|
+
SELECT * FROM ins
|
|
148
|
+
UNION ALL
|
|
149
|
+
SELECT * FROM hub.user
|
|
150
|
+
WHERE casino_id = $1 AND mp_user_id = $2
|
|
151
|
+
LIMIT 1;
|
|
152
|
+
`, [casinoId, mpUserId, uname])
|
|
153
|
+
.then(exactlyOneRow);
|
|
154
|
+
return user;
|
|
155
|
+
}
|
|
156
|
+
export async function upsertExperience(pool, { casinoId, mpExperienceId, name, }) {
|
|
157
|
+
return pool
|
|
158
|
+
.query(`
|
|
159
|
+
WITH ins AS (
|
|
160
|
+
INSERT INTO hub.experience (casino_id, mp_experience_id, name)
|
|
161
|
+
VALUES ($1, $2, $3)
|
|
162
|
+
ON CONFLICT (casino_id, mp_experience_id) DO NOTHING
|
|
163
|
+
RETURNING *
|
|
164
|
+
)
|
|
165
|
+
SELECT * FROM ins
|
|
166
|
+
UNION ALL
|
|
167
|
+
SELECT * FROM hub.experience
|
|
168
|
+
WHERE casino_id = $1 AND mp_experience_id = $2
|
|
169
|
+
LIMIT 1;
|
|
170
|
+
`, [casinoId, mpExperienceId, name])
|
|
171
|
+
.then(exactlyOneRow);
|
|
172
|
+
}
|
|
173
|
+
export async function insertDeposit(pool, o) {
|
|
174
|
+
if (o.amount <= 0) {
|
|
175
|
+
throw new UserFriendlyError("amount must be positive");
|
|
176
|
+
}
|
|
177
|
+
return withPgPoolTransaction(pool, async (client) => {
|
|
178
|
+
const result = await client.query(`
|
|
179
|
+
INSERT INTO hub.deposit(casino_id, mp_transfer_id, user_id, experience_id, amount, currency_key)
|
|
180
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
181
|
+
ON CONFLICT (casino_id, mp_transfer_id) DO NOTHING
|
|
182
|
+
RETURNING id
|
|
183
|
+
`, [
|
|
184
|
+
o.casinoId,
|
|
185
|
+
o.mpTransferId,
|
|
186
|
+
o.userId,
|
|
187
|
+
o.experienceId,
|
|
188
|
+
o.amount,
|
|
189
|
+
o.currency,
|
|
190
|
+
]);
|
|
191
|
+
if (result.rowCount === 0) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
await client.query(`
|
|
195
|
+
insert into hub.balance(casino_id, user_id, experience_id, currency_key, amount)
|
|
196
|
+
values ($1, $2, $3, $4, $5)
|
|
197
|
+
on conflict (casino_id, user_id, experience_id, currency_key) do update
|
|
198
|
+
set amount = balance.amount + excluded.amount
|
|
199
|
+
`, [o.casinoId, o.userId, o.experienceId, o.currency, o.amount]);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
export async function processWithdrawal(pool, { casinoId, mpTransferId, userId, experienceId, amount, currency, status, }) {
|
|
203
|
+
return withPgPoolTransaction(pool, async (client) => {
|
|
204
|
+
await client.query(`
|
|
205
|
+
INSERT INTO hub.withdrawal (
|
|
206
|
+
casino_id,
|
|
207
|
+
mp_transfer_id,
|
|
208
|
+
user_id,
|
|
209
|
+
uname,
|
|
210
|
+
experience_id,
|
|
211
|
+
amount,
|
|
212
|
+
currency_key,
|
|
213
|
+
status,
|
|
214
|
+
status_at
|
|
215
|
+
)
|
|
216
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7::hub.transfer_status_kind, now())
|
|
217
|
+
ON CONFLICT (casino_id, mp_transfer_id) DO UPDATE
|
|
218
|
+
SET status = EXCLUDED.status,
|
|
219
|
+
status_at = CASE
|
|
220
|
+
WHEN withdrawal.status != EXCLUDED.status
|
|
221
|
+
THEN now()
|
|
222
|
+
ELSE withdrawal.status_at
|
|
223
|
+
END
|
|
224
|
+
`, [
|
|
225
|
+
casinoId,
|
|
226
|
+
mpTransferId,
|
|
227
|
+
userId,
|
|
228
|
+
experienceId,
|
|
229
|
+
amount,
|
|
230
|
+
currency,
|
|
231
|
+
status,
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
export async function getUnconfirmedWithdrawals(pool, { casinoId, limit, }) {
|
|
236
|
+
const result = await pool.query(`
|
|
237
|
+
select
|
|
238
|
+
e.mp_experience_id mp_experience_id,
|
|
239
|
+
u.mp_user_id mp_user_id,
|
|
240
|
+
w.*
|
|
241
|
+
from hub.withdrawal w
|
|
242
|
+
join hub.experience e on w.experience_id = e.id
|
|
243
|
+
join hub.user u on w.user_id = u.id
|
|
244
|
+
where w.casino_id = $1
|
|
245
|
+
and w.mp_transfer_id is null
|
|
246
|
+
order by w.id asc
|
|
247
|
+
limit $2
|
|
248
|
+
`, [casinoId, limit]);
|
|
249
|
+
return result.rows;
|
|
250
|
+
}
|
|
251
|
+
export async function getPendingWithdrawals(pool, { casinoId, limit, }) {
|
|
252
|
+
const result = await pool.query(`
|
|
253
|
+
select
|
|
254
|
+
e.mp_experience_id mp_experience_id,
|
|
255
|
+
u.mp_user_id mp_user_id,
|
|
256
|
+
w.*
|
|
257
|
+
from hub.withdrawal w
|
|
258
|
+
join hub.experience e on w.experience_id = e.id
|
|
259
|
+
join hub.user u on w.user_id = u.id
|
|
260
|
+
where w.casino_id = $1
|
|
261
|
+
and w.status = 'PENDING'
|
|
262
|
+
order by w.id asc
|
|
263
|
+
limit $2
|
|
264
|
+
`, [casinoId, limit]);
|
|
265
|
+
return result.rows;
|
|
266
|
+
}
|
|
267
|
+
export async function settleWithdrawal({ withdrawalId, newStatus, }) {
|
|
268
|
+
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
269
|
+
const dbWithdrawal = await pgClient
|
|
270
|
+
.query(`
|
|
271
|
+
select *
|
|
272
|
+
from hub.withdrawal
|
|
273
|
+
where id = $1
|
|
274
|
+
|
|
275
|
+
FOR UPDATE
|
|
276
|
+
|
|
277
|
+
`, [withdrawalId])
|
|
278
|
+
.then(maybeOneRow);
|
|
279
|
+
assert(dbWithdrawal, `Expected to find PENDING withdrawal by id ${withdrawalId}`);
|
|
280
|
+
assert(dbWithdrawal.status === "PENDING", `Expected PENDING withdrawal, got ${dbWithdrawal.status}`);
|
|
281
|
+
await pgClient.query(`
|
|
282
|
+
update hub.withdrawal
|
|
283
|
+
set status = $1,
|
|
284
|
+
status_at = now()
|
|
285
|
+
where id = $2
|
|
286
|
+
`, [newStatus, withdrawalId]);
|
|
287
|
+
if (newStatus === "CANCELED") {
|
|
288
|
+
await pgClient.query(`
|
|
289
|
+
select 1
|
|
290
|
+
from hub.balance
|
|
291
|
+
where casino_id = $1
|
|
292
|
+
and user_id = $2
|
|
293
|
+
and experience_id = $3
|
|
294
|
+
and currency_key = $4
|
|
295
|
+
|
|
296
|
+
FOR UPDATE
|
|
297
|
+
`, [
|
|
298
|
+
dbWithdrawal.casino_id,
|
|
299
|
+
dbWithdrawal.user_id,
|
|
300
|
+
dbWithdrawal.experience_id,
|
|
301
|
+
dbWithdrawal.currency_key,
|
|
302
|
+
]);
|
|
303
|
+
await pgClient.query(`
|
|
304
|
+
insert into hub.balance(casino_id, user_id, experience_id, currency_key, amount)
|
|
305
|
+
values ($1, $2, $3, $4, $5)
|
|
306
|
+
on conflict (casino_id, user_id, experience_id, currency_key) do update
|
|
307
|
+
set amount = balance.amount + excluded.amount
|
|
308
|
+
`, [
|
|
309
|
+
dbWithdrawal.casino_id,
|
|
310
|
+
dbWithdrawal.user_id,
|
|
311
|
+
dbWithdrawal.experience_id,
|
|
312
|
+
dbWithdrawal.currency_key,
|
|
313
|
+
dbWithdrawal.amount,
|
|
314
|
+
]);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
export async function listCasinos(pool) {
|
|
319
|
+
const result = await pool.query("select * from hub.casino");
|
|
320
|
+
return result.rows;
|
|
321
|
+
}
|
|
322
|
+
export async function upsertCurrencies(pgClient, { casinoId, currencies, }) {
|
|
323
|
+
const values = currencies
|
|
324
|
+
.map((_, index) => `($1, $${index * 3 + 2}, $${index * 3 + 3}, $${index * 3 + 4})`)
|
|
325
|
+
.join(", ");
|
|
326
|
+
const query = `
|
|
327
|
+
INSERT INTO hub.currency(casino_id, key, display_unit_name, display_unit_scale)
|
|
328
|
+
VALUES ${values}
|
|
329
|
+
ON CONFLICT (casino_id, key)
|
|
330
|
+
DO UPDATE SET
|
|
331
|
+
display_unit_name = EXCLUDED.display_unit_name,
|
|
332
|
+
display_unit_scale = EXCLUDED.display_unit_scale
|
|
333
|
+
`;
|
|
334
|
+
const queryParams = [casinoId];
|
|
335
|
+
for (const c of currencies) {
|
|
336
|
+
queryParams.push(c.id, c.displayUnitName, String(c.displayUnitScale));
|
|
337
|
+
}
|
|
338
|
+
return pgClient.query(query, queryParams);
|
|
339
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import { DbCasino, DbCasinoSecret } from "./types.js";
|
|
3
|
+
type NewCasino = Pick<DbCasino, "name" | "base_url" | "graphql_url"> & Pick<DbCasinoSecret, "controller_id" | "api_key">;
|
|
4
|
+
export declare function dbInsertCasino(pool: Pool, input: NewCasino): Promise<DbCasino>;
|
|
5
|
+
export declare function dbGetCasinoById(pool: Pool, casinoId: DbCasino["id"]): Promise<DbCasino | undefined>;
|
|
6
|
+
export declare function dbGetCasinoSecretById(pool: Pool, casinoId: DbCasino["id"]): Promise<DbCasinoSecret | undefined>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { exactlyOneRow, maybeOneRow } from "./util.js";
|
|
2
|
+
export async function dbInsertCasino(pool, input) {
|
|
3
|
+
const casino = await pool
|
|
4
|
+
.query(`
|
|
5
|
+
INSERT INTO hub.casino (name, base_url, graphql_url)
|
|
6
|
+
VALUES ($1, $2, $3)
|
|
7
|
+
RETURNING *
|
|
8
|
+
`, [input.name, input.base_url, input.graphql_url])
|
|
9
|
+
.then(exactlyOneRow);
|
|
10
|
+
await pool.query(`
|
|
11
|
+
INSERT INTO hub.casino_secret (id, controller_id, api_key)
|
|
12
|
+
VALUES ($1, $2, $3)
|
|
13
|
+
`, [casino.id, input.controller_id, input.api_key]);
|
|
14
|
+
return casino;
|
|
15
|
+
}
|
|
16
|
+
export async function dbGetCasinoById(pool, casinoId) {
|
|
17
|
+
return pool
|
|
18
|
+
.query(`
|
|
19
|
+
SELECT *
|
|
20
|
+
FROM hub.casino
|
|
21
|
+
WHERE id = $1
|
|
22
|
+
`, [casinoId])
|
|
23
|
+
.then(maybeOneRow);
|
|
24
|
+
}
|
|
25
|
+
export async function dbGetCasinoSecretById(pool, casinoId) {
|
|
26
|
+
return pool
|
|
27
|
+
.query(`
|
|
28
|
+
SELECT *
|
|
29
|
+
FROM hub.casino_secret
|
|
30
|
+
WHERE id = $1
|
|
31
|
+
`, [casinoId])
|
|
32
|
+
.then(maybeOneRow);
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as pg from "pg";
|
|
2
|
+
import { DbCurrency, DbSession } from "./types.js";
|
|
3
|
+
export declare function getActiveSessionById(pgClient: pg.PoolClient, sessionId: string): Promise<DbSession | undefined>;
|
|
4
|
+
export declare function getCasinoCurrencyByKey(pgClient: pg.PoolClient, { currencyKey, casinoId }: {
|
|
5
|
+
currencyKey: string;
|
|
6
|
+
casinoId: string;
|
|
7
|
+
}): Promise<DbCurrency | undefined>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { maybeOneRow } from "./util.js";
|
|
2
|
+
export async function getActiveSessionById(pgClient, sessionId) {
|
|
3
|
+
return pgClient
|
|
4
|
+
.query("select * from hub.active_session where id = $1", [
|
|
5
|
+
sessionId,
|
|
6
|
+
])
|
|
7
|
+
.then(maybeOneRow);
|
|
8
|
+
}
|
|
9
|
+
export async function getCasinoCurrencyByKey(pgClient, { currencyKey, casinoId }) {
|
|
10
|
+
return pgClient
|
|
11
|
+
.query({
|
|
12
|
+
text: `
|
|
13
|
+
SELECT *
|
|
14
|
+
FROM hub.currency
|
|
15
|
+
WHERE key = $1 AND casino_id = $2
|
|
16
|
+
`,
|
|
17
|
+
values: [currencyKey, casinoId],
|
|
18
|
+
})
|
|
19
|
+
.then(maybeOneRow);
|
|
20
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as jose from "jose";
|
|
2
|
+
export type DbSession = {
|
|
3
|
+
id: string;
|
|
4
|
+
user_id: string;
|
|
5
|
+
experience_id: string;
|
|
6
|
+
casino_id: string;
|
|
7
|
+
key: string;
|
|
8
|
+
};
|
|
9
|
+
export type DbUser = {
|
|
10
|
+
id: string;
|
|
11
|
+
mp_user_id: string;
|
|
12
|
+
experience_id: string;
|
|
13
|
+
casino_id: string;
|
|
14
|
+
uname: string;
|
|
15
|
+
};
|
|
16
|
+
export type DbTransferStatusKind = "PENDING" | "COMPLETED" | "CANCELED" | "UNCLAIMED" | "EXPIRED";
|
|
17
|
+
export type DbWithdrawal = {
|
|
18
|
+
id: string;
|
|
19
|
+
mp_transfer_id: string;
|
|
20
|
+
casino_id: string;
|
|
21
|
+
user_id: string;
|
|
22
|
+
experience_id: string;
|
|
23
|
+
uname: string;
|
|
24
|
+
amount: number;
|
|
25
|
+
currency_key: string;
|
|
26
|
+
status: DbTransferStatusKind;
|
|
27
|
+
status_at: Date;
|
|
28
|
+
};
|
|
29
|
+
export type DbWithdrawalRequest = {
|
|
30
|
+
id: string;
|
|
31
|
+
mp_transfer_id: string | null;
|
|
32
|
+
user_id: string;
|
|
33
|
+
experience_id: string;
|
|
34
|
+
casino_id: string;
|
|
35
|
+
currency_key: string;
|
|
36
|
+
amount: number;
|
|
37
|
+
};
|
|
38
|
+
export type DbDeposit = {
|
|
39
|
+
id: string;
|
|
40
|
+
mp_transfer_id: string;
|
|
41
|
+
casino_id: string;
|
|
42
|
+
user_id: string;
|
|
43
|
+
uname: string;
|
|
44
|
+
amount: number;
|
|
45
|
+
currency_key: string;
|
|
46
|
+
};
|
|
47
|
+
export type DbExperience = {
|
|
48
|
+
id: string;
|
|
49
|
+
casino_id: string;
|
|
50
|
+
mp_experience_id: string;
|
|
51
|
+
};
|
|
52
|
+
export type DbCasino = {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
base_url: string;
|
|
56
|
+
graphql_url: string;
|
|
57
|
+
};
|
|
58
|
+
export type DbCasinoSecret = {
|
|
59
|
+
id: string;
|
|
60
|
+
controller_id: string;
|
|
61
|
+
api_key: string;
|
|
62
|
+
};
|
|
63
|
+
export type DbCurrency = {
|
|
64
|
+
key: string;
|
|
65
|
+
casino_id: string;
|
|
66
|
+
display_unit_name: string;
|
|
67
|
+
display_unit_scale: number;
|
|
68
|
+
};
|
|
69
|
+
export type DbCasinoJwks = {
|
|
70
|
+
casino_id: string;
|
|
71
|
+
jwks: jose.JSONWebKeySet;
|
|
72
|
+
updated_at: Date;
|
|
73
|
+
forced_at: Date | null;
|
|
74
|
+
};
|
|
75
|
+
export type DbBankroll = {
|
|
76
|
+
id: string;
|
|
77
|
+
casino_id: string;
|
|
78
|
+
currency_key: string;
|
|
79
|
+
amount: number;
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { QueryResult, QueryResultRow } from "pg";
|
|
2
|
+
import { PgClientResult } from "postgraphile/@dataplan/pg";
|
|
3
|
+
type ResultType<T> = PgClientResult<T> | QueryResult<T extends QueryResultRow ? T : never>;
|
|
4
|
+
export declare function maybeOneRow<T>(result: ResultType<T>): T | undefined;
|
|
5
|
+
export declare function exactlyOneRow<T>(result: ResultType<T>): T;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { assert } from "tsafe";
|
|
2
|
+
export function maybeOneRow(result) {
|
|
3
|
+
assert(result.rows.length <= 1, `maybeOneRow: Expected at most one row, got ${result.rows.length}`);
|
|
4
|
+
return result.rows[0];
|
|
5
|
+
}
|
|
6
|
+
export function exactlyOneRow(result) {
|
|
7
|
+
assert(result.rows.length === 1, `exactlyOneRow: Expected exactly one row, got ${result.rows.length}`);
|
|
8
|
+
return result.rows[0];
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DbSession, DbUser } from "./db/types.js";
|
|
2
|
+
import { Request } from "express";
|
|
3
|
+
export { Express, Request, Response, Router, NextFunction } from "express";
|
|
4
|
+
export interface HubRequest extends Request {
|
|
5
|
+
identity?: {
|
|
6
|
+
kind: "user";
|
|
7
|
+
user: DbUser;
|
|
8
|
+
sessionId: DbSession["id"];
|
|
9
|
+
} | {
|
|
10
|
+
kind: "operator";
|
|
11
|
+
apiKey: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Router } from "express";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "postgraphile/grafast";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "postgraphile/grafast";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { makeExtendSchemaPlugin, gql } from "postgraphile/utils";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { makeExtendSchemaPlugin, gql } from "postgraphile/utils";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GraphQLClient } from "graphql-request";
|
|
2
|
+
import { DbCasino, DbCasinoSecret } from "./db/index.js";
|
|
3
|
+
export declare function createGraphqlClient({ graphqlUrl, apiKey, }: {
|
|
4
|
+
graphqlUrl: DbCasino["graphql_url"];
|
|
5
|
+
apiKey: DbCasinoSecret["api_key"];
|
|
6
|
+
}): GraphQLClient;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const GET_USER_FROM_USER_TOKEN: import("@graphql-typed-document-node/core").TypedDocumentNode<import("./__generated__/graphql.js").GetUserFromUserTokenMutation, import("./__generated__/graphql.js").Exact<{
|
|
2
|
+
token: import("./__generated__/graphql.js").Scalars["UUID"]["input"];
|
|
3
|
+
}>>;
|
|
4
|
+
export declare const START_PENDING_EXPERIENCE_TRANSFER: import("@graphql-typed-document-node/core").TypedDocumentNode<import("./__generated__/graphql.js").StartPendingExperienceTransferMutation, import("./__generated__/graphql.js").Exact<{
|
|
5
|
+
mpUserId: import("./__generated__/graphql.js").Scalars["UUID"]["input"];
|
|
6
|
+
mpExperienceId: import("./__generated__/graphql.js").Scalars["UUID"]["input"];
|
|
7
|
+
amount: import("./__generated__/graphql.js").Scalars["Int"]["input"];
|
|
8
|
+
currency: import("./__generated__/graphql.js").Scalars["String"]["input"];
|
|
9
|
+
metadata: import("./__generated__/graphql.js").Scalars["JSON"]["input"];
|
|
10
|
+
}>>;
|
|
11
|
+
export declare const PAGINATE_TRANSFERS: import("@graphql-typed-document-node/core").TypedDocumentNode<import("./__generated__/graphql.js").PaginateTransfersQuery, import("./__generated__/graphql.js").Exact<{
|
|
12
|
+
controllerId: import("./__generated__/graphql.js").Scalars["UUID"]["input"];
|
|
13
|
+
after?: import("./__generated__/graphql.js").InputMaybe<import("./__generated__/graphql.js").Scalars["Cursor"]["input"]>;
|
|
14
|
+
limit?: import("./__generated__/graphql.js").InputMaybe<import("./__generated__/graphql.js").Scalars["Int"]["input"]>;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare const GET_CURRENCIES: import("@graphql-typed-document-node/core").TypedDocumentNode<import("./__generated__/graphql.js").GetCurrenciesQuery, import("./__generated__/graphql.js").Exact<{
|
|
17
|
+
[key: string]: never;
|
|
18
|
+
}>>;
|