@moneypot/hub 1.19.7 → 1.19.9

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 CHANGED
@@ -17,7 +17,7 @@ View our docs: <https://docs.moneypot.com>
17
17
  ## Install
18
18
 
19
19
  ```sh
20
- $ npm install @moneypot/hub
20
+ $ pnpm install @moneypot/hub
21
21
  ```
22
22
 
23
23
  ## Usage
@@ -80,7 +80,8 @@ You should always keep your hub server up to date as soon as possible. Never ins
80
80
  ### 1.19.x
81
81
 
82
82
  - All of hub's internal graphql-schema-modifying plugins have been moved to hub's set of required plugins.
83
- - Added `FAILED` take request auto-handling
83
+ - Added `FAILED` take request auto-handling.
84
+ - Exposed a `@moneypot/hub/test` module for end-user server testing.
84
85
 
85
86
  ### 1.18.x
86
87
 
@@ -1,3 +1,4 @@
1
1
  export declare function runMigrations(options: {
2
2
  userDatabaseMigrationsPath?: string;
3
+ superuserDatabaseUrl?: string;
3
4
  }): Promise<void>;
@@ -14,7 +14,10 @@ export async function runMigrations(options) {
14
14
  throw new Error(`userDatabaseMigrationsPath is not a directory: ${options.userDatabaseMigrationsPath}`);
15
15
  }
16
16
  }
17
- const pgClient = db.getPgClient(config.SUPERUSER_DATABASE_URL);
17
+ const superuserDatabaseUrl = options.superuserDatabaseUrl ??
18
+ process.env.SUPERUSER_DATABASE_URL ??
19
+ config.SUPERUSER_DATABASE_URL;
20
+ const pgClient = db.getPgClient(superuserDatabaseUrl);
18
21
  await pgClient.connect();
19
22
  try {
20
23
  try {
@@ -32,7 +32,7 @@ const OutcomeSchema = z
32
32
  const InputSchema = z
33
33
  .object({
34
34
  kind: z.string(),
35
- clientSeed: z.string(),
35
+ clientSeed: z.string().max(32),
36
36
  wager: z
37
37
  .number()
38
38
  .int("Wager must be an integer")
@@ -79,11 +79,12 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
79
79
  mutablePlugins.push(p);
80
80
  }
81
81
  }
82
- for (const requiredPlugin of requiredPlugins) {
83
- if (!mutablePlugins.some((plugin) => plugin === requiredPlugin)) {
84
- logger.warn(`Adding required plugin "${requiredPlugin.name}"`);
85
- mutablePlugins.unshift(requiredPlugin);
86
- }
82
+ const missingPlugins = requiredPlugins.filter((rp) => !mutablePlugins.some((p) => p === rp));
83
+ for (const p of missingPlugins) {
84
+ logger.warn(`Adding required plugin "${p.name}"`);
85
+ }
86
+ if (missingPlugins.length > 0) {
87
+ mutablePlugins.unshift(...missingPlugins);
87
88
  }
88
89
  if (enablePlayground) {
89
90
  mutablePlugins.push(HubCreatePlaygroundSessionPlugin);
@@ -20,5 +20,7 @@ export type CreateHubServerOptions = {
20
20
  enablePlayground: boolean;
21
21
  port: number;
22
22
  runProcessors: boolean;
23
+ superuserDatabaseUrl?: string;
24
+ postgraphileDatabaseUrl?: string;
23
25
  };
24
- export declare function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, enableChat, enablePlayground, port, runProcessors, }: CreateHubServerOptions): HubServer;
26
+ export declare function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, enableChat, enablePlayground, port, runProcessors, superuserDatabaseUrl, postgraphileDatabaseUrl, }: CreateHubServerOptions): HubServer;
@@ -46,9 +46,12 @@ function createExpressServer(context) {
46
46
  });
47
47
  return app;
48
48
  }
49
- export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, enableChat, enablePlayground, port, runProcessors, }) {
49
+ export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, enableChat, enablePlayground, port, runProcessors, superuserDatabaseUrl, postgraphileDatabaseUrl, }) {
50
50
  const abortController = new AbortController();
51
- const context = createServerContext();
51
+ const context = createServerContext({
52
+ superuserDatabaseUrl,
53
+ postgraphileDatabaseUrl,
54
+ });
52
55
  const expressServer = createExpressServer(context);
53
56
  const { preset, pgService } = createPreset({
54
57
  plugins: plugins ?? defaultPlugins,
@@ -0,0 +1,62 @@
1
+ import { Pool, PoolClient } from "pg";
2
+ import { DbUser, DbBalance, DbHashChain, DbCasino, DbExperience, DbCurrency, DbBankroll } from "../db/types.js";
3
+ import { GraphQLClient } from "graphql-request";
4
+ import { ServerOptions } from "../index.js";
5
+ export type HubTestServer = {
6
+ port: number;
7
+ stop: () => Promise<void>;
8
+ dbPool: Pool;
9
+ playgroundCasinoId: string;
10
+ authenticate: (userId: DbUser["id"], experienceId: DbExperience["id"]) => Promise<{
11
+ sessionKey: string;
12
+ graphqlClient: GraphQLClient;
13
+ }>;
14
+ };
15
+ export type TestServerConfig = {
16
+ plugins?: ServerOptions["plugins"];
17
+ userDatabaseMigrationsPath?: string;
18
+ databaseName?: string;
19
+ extraPgSchemas?: string[];
20
+ };
21
+ export declare function startTestServer({ plugins, userDatabaseMigrationsPath, databaseName, extraPgSchemas, }?: TestServerConfig): Promise<HubTestServer>;
22
+ type PgClient = Pool | PoolClient;
23
+ export declare function createUser(pgClient: PgClient, { casinoId, uname, }: {
24
+ casinoId: DbCasino["id"];
25
+ uname: string;
26
+ }): Promise<DbUser>;
27
+ export declare function createPlayerBalance(pgClient: PgClient, { userId, experienceId, currencyKey, amount, }: {
28
+ userId: DbUser["id"];
29
+ experienceId: DbExperience["id"];
30
+ currencyKey: DbCurrency["key"];
31
+ amount: number;
32
+ }): Promise<DbBalance>;
33
+ export declare function createHashChain(pgClient: PgClient, { userId, experienceId, casinoId, maxIterations, }: {
34
+ userId: DbUser["id"];
35
+ experienceId: DbExperience["id"];
36
+ casinoId: DbCasino["id"];
37
+ maxIterations?: number;
38
+ }): Promise<DbHashChain>;
39
+ export declare function createExperience(pgClient: PgClient, options: {
40
+ casinoId: DbCasino["id"];
41
+ } | {
42
+ casinoId: DbCasino["id"];
43
+ mpExperienceId: string;
44
+ name: string;
45
+ }): Promise<DbExperience>;
46
+ export declare function createCurrency(pgClient: PgClient, { key, casinoId, displayUnitName, displayUnitScale, }: {
47
+ key: DbCurrency["key"];
48
+ casinoId: DbCasino["id"];
49
+ displayUnitName: string;
50
+ displayUnitScale: number;
51
+ }): Promise<DbCurrency>;
52
+ export declare function createHouseBankroll(pgClient: PgClient, { casinoId, currencyKey, amount, }: {
53
+ casinoId: DbCasino["id"];
54
+ currencyKey: DbCurrency["key"];
55
+ amount: number;
56
+ }): Promise<DbBankroll>;
57
+ export { createUser as createPlayer };
58
+ export declare function getPlayerBalance(pgClient: PgClient, { userId, experienceId, currencyKey, }: {
59
+ userId: DbUser["id"];
60
+ experienceId: DbExperience["id"];
61
+ currencyKey: DbCurrency["key"];
62
+ }): Promise<DbBalance | null>;
@@ -0,0 +1,156 @@
1
+ import { Pool } from "pg";
2
+ import { randomBytes, randomUUID } from "node:crypto";
3
+ import { execSync } from "node:child_process";
4
+ import { DbHashKind, } from "../db/types.js";
5
+ import { exactlyOneRow, maybeOneRow, } from "../db/index.js";
6
+ import { GraphQLClient } from "graphql-request";
7
+ import { defaultPlugins, runMigrations } from "../index.js";
8
+ import { createHubServer } from "../server/index.js";
9
+ const createdDatabases = new Set();
10
+ export async function startTestServer({ plugins = [...defaultPlugins], userDatabaseMigrationsPath, databaseName, extraPgSchemas = [], } = {}) {
11
+ const dbName = databaseName ?? `hub-test-${randomUUID().slice(0, 8)}`;
12
+ const connectionString = `postgres://postgres@localhost/${dbName}`;
13
+ try {
14
+ execSync(`psql -U postgres -c "CREATE DATABASE \\"${dbName}\\""`, {
15
+ stdio: "pipe",
16
+ });
17
+ createdDatabases.add(dbName);
18
+ }
19
+ catch (_e) {
20
+ const pool = new Pool({ connectionString });
21
+ try {
22
+ await pool.query(`
23
+ DROP SCHEMA IF EXISTS hub CASCADE;
24
+ DROP SCHEMA IF EXISTS hub_hidden CASCADE;
25
+ DROP SCHEMA IF EXISTS hub_secret CASCADE;
26
+ DROP SCHEMA IF EXISTS hub_core_versions CASCADE;
27
+ DROP SCHEMA IF EXISTS hub_user_versions CASCADE;
28
+ `);
29
+ }
30
+ finally {
31
+ await pool.end();
32
+ }
33
+ }
34
+ process.env.NODE_ENV = "test";
35
+ process.env.LOG_LEVEL = process.env.LOG_LEVEL ?? "error";
36
+ process.env.HASHCHAINSERVER_URL = "mock-server";
37
+ process.env.HASHCHAINSERVER_APPLICATION_SECRET = "test-secret";
38
+ await runMigrations({
39
+ userDatabaseMigrationsPath,
40
+ superuserDatabaseUrl: connectionString,
41
+ });
42
+ const hubServer = createHubServer({
43
+ port: 0,
44
+ plugins,
45
+ extraPgSchemas,
46
+ exportSchemaSDLPath: undefined,
47
+ enableChat: false,
48
+ enablePlayground: false,
49
+ runProcessors: false,
50
+ superuserDatabaseUrl: connectionString,
51
+ postgraphileDatabaseUrl: connectionString,
52
+ });
53
+ const { port } = await hubServer.listen();
54
+ const dbPool = new Pool({ connectionString });
55
+ const url = `http://localhost:${port}`;
56
+ const graphqlUrl = `${url}/graphql`;
57
+ const { rows } = await dbPool.query("SELECT id FROM hub.casino WHERE is_playground = true LIMIT 1");
58
+ const playgroundCasinoId = rows[0]?.id;
59
+ if (!playgroundCasinoId) {
60
+ throw new Error("Playground casino not found - migrations may have failed");
61
+ }
62
+ return {
63
+ port,
64
+ dbPool,
65
+ playgroundCasinoId,
66
+ stop: async () => {
67
+ await dbPool.end();
68
+ await hubServer.shutdown();
69
+ },
70
+ authenticate: async (userId, experienceId) => {
71
+ const user = await dbPool
72
+ .query(`SELECT casino_id FROM hub.user WHERE id = $1`, [userId])
73
+ .then(exactlyOneRow);
74
+ const userToken = randomUUID();
75
+ const sessionKey = randomUUID();
76
+ await dbPool.query(`INSERT INTO hub.session (user_id, casino_id, experience_id, user_token, key)
77
+ VALUES ($1, $2, $3, $4, $5)`, [userId, user.casino_id, experienceId, userToken, sessionKey]);
78
+ const graphqlClient = new GraphQLClient(graphqlUrl, {
79
+ headers: {
80
+ Authorization: `session:${sessionKey}`,
81
+ },
82
+ });
83
+ return { sessionKey, graphqlClient };
84
+ },
85
+ };
86
+ }
87
+ export async function createUser(pgClient, { casinoId, uname, }) {
88
+ const mpUserId = randomUUID();
89
+ return pgClient
90
+ .query(`INSERT INTO hub.user (mp_user_id, casino_id, uname)
91
+ VALUES ($1, $2, $3)
92
+ RETURNING *`, [mpUserId, casinoId, uname])
93
+ .then(exactlyOneRow);
94
+ }
95
+ export async function createPlayerBalance(pgClient, { userId, experienceId, currencyKey, amount, }) {
96
+ return pgClient
97
+ .query(`INSERT INTO hub.balance (user_id, experience_id, currency_key, amount, casino_id)
98
+ VALUES ($1, $2, $3, $4, (SELECT casino_id FROM hub.user WHERE id = $1) )
99
+ RETURNING *`, [userId, experienceId, currencyKey, amount])
100
+ .then(exactlyOneRow);
101
+ }
102
+ export async function createHashChain(pgClient, { userId, experienceId, casinoId, maxIterations = 10, }) {
103
+ const hashChain = await pgClient
104
+ .query(`INSERT INTO hub.hash_chain (user_id, experience_id, casino_id, max_iteration, current_iteration, active)
105
+ VALUES ($1, $2, $3, $4, $4, TRUE)
106
+ RETURNING *`, [userId, experienceId, casinoId, maxIterations])
107
+ .then(exactlyOneRow);
108
+ const terminalHash = randomBytes(32);
109
+ await pgClient.query(`INSERT INTO hub.hash (hash_chain_id, kind, digest, iteration, metadata)
110
+ VALUES ($1, $2, $3, $4, '{}')`, [hashChain.id, DbHashKind.TERMINAL, terminalHash, maxIterations]);
111
+ return hashChain;
112
+ }
113
+ export async function createExperience(pgClient, options) {
114
+ const casinoId = options.casinoId;
115
+ let mpExperienceId;
116
+ let name;
117
+ if ("mpExperienceId" in options) {
118
+ ({ mpExperienceId, name } = options);
119
+ }
120
+ else {
121
+ name = "experience-" + randomUUID();
122
+ mpExperienceId = randomUUID();
123
+ }
124
+ return pgClient
125
+ .query(`INSERT INTO hub.experience (mp_experience_id, casino_id, name)
126
+ VALUES ($1, $2, $3)
127
+ RETURNING *`, [mpExperienceId, casinoId, name])
128
+ .then(exactlyOneRow);
129
+ }
130
+ export async function createCurrency(pgClient, { key, casinoId, displayUnitName, displayUnitScale, }) {
131
+ return pgClient
132
+ .query(`
133
+ INSERT INTO hub.currency (key, casino_id, display_unit_name, display_unit_scale)
134
+ VALUES ($1, $2, $3, $4)
135
+ RETURNING *
136
+ `, [key, casinoId, displayUnitName, displayUnitScale])
137
+ .then(exactlyOneRow);
138
+ }
139
+ export async function createHouseBankroll(pgClient, { casinoId, currencyKey, amount, }) {
140
+ return pgClient
141
+ .query(`INSERT INTO hub.bankroll (casino_id, currency_key, amount)
142
+ VALUES ($1, $2, $3)
143
+ ON CONFLICT (casino_id, currency_key) DO UPDATE SET amount = $3
144
+ RETURNING *`, [casinoId, currencyKey, amount])
145
+ .then(exactlyOneRow);
146
+ }
147
+ export { createUser as createPlayer };
148
+ export async function getPlayerBalance(pgClient, { userId, experienceId, currencyKey, }) {
149
+ return pgClient
150
+ .query(`
151
+ SELECT * FROM hub.balance
152
+ WHERE experience_id = $1 AND user_id = $2 AND currency_key = $3
153
+ `, [experienceId, userId, currencyKey])
154
+ .then(maybeOneRow)
155
+ .then((row) => row ?? null);
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneypot/hub",
3
- "version": "1.19.7",
3
+ "version": "1.19.9",
4
4
  "author": "moneypot.com",
5
5
  "homepage": "https://moneypot.com/hub",
6
6
  "keywords": [
@@ -25,7 +25,8 @@
25
25
  "./pg-sql2": "./dist/src/pg-sql2.js",
26
26
  "./hash-chain": "./dist/src/hash-chain/index.js",
27
27
  "./logger": "./dist/src/logger.js",
28
- "./audit-log": "./dist/src/audit-log.js"
28
+ "./audit-log": "./dist/src/audit-log.js",
29
+ "./test": "./dist/src/test/index.js"
29
30
  },
30
31
  "files": [
31
32
  "/dist"
@@ -35,7 +36,8 @@
35
36
  "db-migrate": "./dist/cli/db-migrate.js"
36
37
  },
37
38
  "scripts": {
38
- "build": "rm -rf dist && tsc && cp -R src/pg-versions dist/src/ && ./scripts/strip-sql.sh && pnpm run build-dashboard",
39
+ "build:hub": "tsc && cp -R src/pg-versions dist/src/ && ./scripts/strip-sql.sh",
40
+ "build": "pnpm run build:hub && pnpm run build-dashboard",
39
41
  "check": "tsc --noEmit && pnpm run check-dashboard",
40
42
  "check-tests": "tsc -p tests/tsconfig.json --noEmit",
41
43
  "check-dashboard": "cd ./dashboard && pnpm run check",