@moneypot/hub 1.16.0-dev.5 → 1.16.0-dev.7

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,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Dashboard</title>
7
- <script type="module" crossorigin src="/dashboard/assets/index-CrY0xaa9.js"></script>
7
+ <script type="module" crossorigin src="/dashboard/assets/index-D7SlWXgD.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/dashboard/assets/index-LZVcTrKv.css">
9
9
  </head>
10
10
  <body>
@@ -10,9 +10,13 @@ export declare const PgAdvisoryLock: {
10
10
  experienceId: DbExperience["id"];
11
11
  casinoId: DbCasino["id"];
12
12
  }) => Promise<void>;
13
- forChatUserAction: (pgClient: PgClientInTransaction, params: {
13
+ forChatPlayerAction: (pgClient: PgClientInTransaction, params: {
14
14
  userId: DbUser["id"];
15
15
  experienceId: DbExperience["id"];
16
16
  casinoId: DbCasino["id"];
17
17
  }) => Promise<void>;
18
+ forChatModManagement: (pgClient: PgClientInTransaction, params: {
19
+ experienceId: DbExperience["id"];
20
+ casinoId: DbCasino["id"];
21
+ }) => Promise<void>;
18
22
  };
@@ -3,7 +3,8 @@ var LockNamespace;
3
3
  (function (LockNamespace) {
4
4
  LockNamespace[LockNamespace["MP_TAKE_REQUEST"] = 1] = "MP_TAKE_REQUEST";
5
5
  LockNamespace[LockNamespace["NEW_HASH_CHAIN"] = 2] = "NEW_HASH_CHAIN";
6
- LockNamespace[LockNamespace["CHAT_USER_ACTION"] = 3] = "CHAT_USER_ACTION";
6
+ LockNamespace[LockNamespace["CHAT_PLAYER_ACTION"] = 3] = "CHAT_PLAYER_ACTION";
7
+ LockNamespace[LockNamespace["CHAT_MOD_MANAGEMENT"] = 4] = "CHAT_MOD_MANAGEMENT";
7
8
  })(LockNamespace || (LockNamespace = {}));
8
9
  function simpleHash32(text) {
9
10
  let hash = 0;
@@ -32,8 +33,11 @@ export const PgAdvisoryLock = {
32
33
  assert(pgClient._inTransaction, "pgClient must be in a transaction");
33
34
  await acquireAdvisoryLock(pgClient, LockNamespace.NEW_HASH_CHAIN, createHashKey(params.userId, params.experienceId, params.casinoId));
34
35
  },
35
- forChatUserAction: async (pgClient, params) => {
36
+ forChatPlayerAction: async (pgClient, params) => {
36
37
  assert(pgClient._inTransaction, "pgClient must be in a transaction");
37
- await acquireAdvisoryLock(pgClient, LockNamespace.CHAT_USER_ACTION, createHashKey(params.userId, params.experienceId, params.casinoId));
38
+ await acquireAdvisoryLock(pgClient, LockNamespace.CHAT_PLAYER_ACTION, createHashKey(params.userId, params.experienceId, params.casinoId));
39
+ },
40
+ forChatModManagement: async (pgClient, params) => {
41
+ await acquireAdvisoryLock(pgClient, LockNamespace.CHAT_MOD_MANAGEMENT, createHashKey(params.experienceId, params.casinoId));
38
42
  },
39
43
  };
@@ -81,7 +81,7 @@ export const HubChatCreateUserMessagePlugin = extendSchema((build) => {
81
81
  throw e;
82
82
  }
83
83
  return await withPgPoolTransaction(superuserPool, async (pgClient) => {
84
- await PgAdvisoryLock.forChatUserAction(pgClient, {
84
+ await PgAdvisoryLock.forChatPlayerAction(pgClient, {
85
85
  userId: identity.session.user_id,
86
86
  experienceId: identity.session.experience_id,
87
87
  casinoId: identity.session.casino_id,
@@ -0,0 +1 @@
1
+ export declare const HubChatModManagementPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,227 @@
1
+ import { GraphQLError } from "graphql";
2
+ import { constant, context, object, sideEffect } from "postgraphile/grafast";
3
+ import { gql, extendSchema } from "postgraphile/utils";
4
+ import z from "zod/v4";
5
+ import { extractFirstZodErrorMessage, uuidEqual } from "../../util.js";
6
+ import { exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../../db/index.js";
7
+ import { PgAdvisoryLock } from "../../pg-advisory-lock.js";
8
+ const AddModInputSchema = z.object({
9
+ userId: z.uuid("Invalid user ID"),
10
+ experienceId: z.uuid("Invalid experience ID"),
11
+ });
12
+ const RemoveModInputSchema = z.object({
13
+ userId: z.uuid("Invalid user ID"),
14
+ experienceId: z.uuid("Invalid experience ID"),
15
+ });
16
+ export const HubChatModManagementPlugin = extendSchema((build) => {
17
+ const chatModTable = build.input.pgRegistry.pgResources.hub_chat_mod;
18
+ return {
19
+ typeDefs: gql `
20
+ input HubChatAddModInput {
21
+ userId: UUID!
22
+ experienceId: UUID!
23
+ }
24
+
25
+ input HubChatRemoveModInput {
26
+ userId: UUID!
27
+ experienceId: UUID!
28
+ }
29
+
30
+ type HubChatAddModPayload {
31
+ chatMod: HubChatMod!
32
+ }
33
+
34
+ type HubChatRemoveModPayload {
35
+ _: Boolean
36
+ }
37
+
38
+ extend type Mutation {
39
+ hubChatAddMod(input: HubChatAddModInput!): HubChatAddModPayload
40
+ hubChatRemoveMod(input: HubChatRemoveModInput!): HubChatRemoveModPayload
41
+ }
42
+ `,
43
+ objects: {
44
+ Mutation: {
45
+ plans: {
46
+ hubChatRemoveMod(_, { $input }) {
47
+ const $identity = context().get("identity");
48
+ const $superuserPool = context().get("superuserPool");
49
+ const _$result = sideEffect([$input, $identity, $superuserPool], async ([rawInput, identity, superuserPool]) => {
50
+ if (identity?.kind === "operator" ||
51
+ identity?.session.is_experience_owner) {
52
+ }
53
+ else {
54
+ throw new GraphQLError("Unauthorized");
55
+ }
56
+ let input;
57
+ try {
58
+ input = RemoveModInputSchema.parse(rawInput);
59
+ }
60
+ catch (e) {
61
+ if (e instanceof z.ZodError) {
62
+ throw new GraphQLError(extractFirstZodErrorMessage(e));
63
+ }
64
+ throw e;
65
+ }
66
+ const dbTargetExperience = await superuserPool
67
+ .query({
68
+ text: `
69
+ SELECT id, user_id, casino_id
70
+ FROM hub.experience
71
+ WHERE id = $1
72
+ `,
73
+ values: [input.experienceId],
74
+ })
75
+ .then(maybeOneRow);
76
+ if (!dbTargetExperience) {
77
+ throw new GraphQLError("Experience not found");
78
+ }
79
+ if (identity?.kind === "user" &&
80
+ !uuidEqual(identity?.session.experience_id, dbTargetExperience.id)) {
81
+ throw new GraphQLError("Your session does not match the experienceId");
82
+ }
83
+ const dbTargetUser = await superuserPool
84
+ .query({
85
+ text: `
86
+ SELECT id
87
+ FROM hub.user
88
+ WHERE id = $1 AND experience_id = $2
89
+ `,
90
+ values: [input.userId, input.experienceId],
91
+ })
92
+ .then(maybeOneRow);
93
+ if (!dbTargetUser) {
94
+ throw new GraphQLError("User not found");
95
+ }
96
+ return withPgPoolTransaction(superuserPool, async (pgClient) => {
97
+ await PgAdvisoryLock.forChatModManagement(pgClient, {
98
+ experienceId: dbTargetExperience.id,
99
+ casinoId: dbTargetExperience.casino_id,
100
+ });
101
+ const dbChatMod = await pgClient
102
+ .query({
103
+ text: `
104
+ SELECT id
105
+ FROM hub.chat_mod
106
+ WHERE experience_id = $1 AND user_id = $2 AND casino_id = $3
107
+ `,
108
+ values: [
109
+ dbTargetExperience.id,
110
+ dbTargetUser.id,
111
+ dbTargetExperience.casino_id,
112
+ ],
113
+ })
114
+ .then(maybeOneRow);
115
+ if (!dbChatMod) {
116
+ return;
117
+ }
118
+ await pgClient.query(`
119
+ DELETE FROM hub.chat_mod
120
+ WHERE id = $1
121
+ `, [dbChatMod.id]);
122
+ });
123
+ });
124
+ return object({
125
+ _: constant(true),
126
+ });
127
+ },
128
+ hubChatAddMod(_, { $input }) {
129
+ const $identity = context().get("identity");
130
+ const $superuserPool = context().get("superuserPool");
131
+ const $chatModId = sideEffect([$input, $identity, $superuserPool], async ([rawInput, identity, superuserPool]) => {
132
+ if (identity?.kind === "operator" ||
133
+ identity?.session.is_experience_owner) {
134
+ }
135
+ else {
136
+ throw new GraphQLError("Unauthorized");
137
+ }
138
+ let input;
139
+ try {
140
+ input = AddModInputSchema.parse(rawInput);
141
+ }
142
+ catch (e) {
143
+ if (e instanceof z.ZodError) {
144
+ throw new GraphQLError(extractFirstZodErrorMessage(e));
145
+ }
146
+ throw e;
147
+ }
148
+ const dbTargetExperience = await superuserPool
149
+ .query({
150
+ text: `
151
+ SELECT id, user_id, casino_id
152
+ FROM hub.experience
153
+ WHERE id = $1
154
+ `,
155
+ values: [input.experienceId],
156
+ })
157
+ .then(maybeOneRow);
158
+ if (!dbTargetExperience) {
159
+ throw new GraphQLError("Experience not found");
160
+ }
161
+ if (identity?.kind === "user" &&
162
+ !uuidEqual(identity?.session.experience_id, dbTargetExperience.id)) {
163
+ throw new GraphQLError("Your session does not match the experienceId");
164
+ }
165
+ const dbTargetUser = await superuserPool
166
+ .query({
167
+ text: `
168
+ SELECT id
169
+ FROM hub.user
170
+ WHERE id = $1 AND experience_id = $2
171
+ `,
172
+ values: [input.userId, input.experienceId],
173
+ })
174
+ .then(maybeOneRow);
175
+ if (!dbTargetUser) {
176
+ throw new GraphQLError("User not found");
177
+ }
178
+ if (dbTargetExperience.user_id &&
179
+ uuidEqual(dbTargetUser.id, dbTargetExperience.user_id)) {
180
+ throw new GraphQLError("User cannot be a chat mod");
181
+ }
182
+ const chatModId = await withPgPoolTransaction(superuserPool, async (pgClient) => {
183
+ await PgAdvisoryLock.forChatModManagement(pgClient, {
184
+ experienceId: dbTargetExperience.id,
185
+ casinoId: dbTargetExperience.casino_id,
186
+ });
187
+ const dbExistingChatMod = await pgClient
188
+ .query({
189
+ text: `
190
+ SELECT id
191
+ FROM hub.chat_mod
192
+ WHERE experience_id = $1 AND user_id = $2 AND casino_id = $3
193
+ `,
194
+ values: [
195
+ dbTargetExperience.id,
196
+ dbTargetUser.id,
197
+ dbTargetExperience.casino_id,
198
+ ],
199
+ })
200
+ .then(maybeOneRow);
201
+ if (dbExistingChatMod) {
202
+ return dbExistingChatMod.id;
203
+ }
204
+ const dbCreatedChatMod = await pgClient
205
+ .query(`
206
+ INSERT INTO hub.chat_mod (experience_id, user_id, casino_id)
207
+ VALUES ($1, $2, $3)
208
+ RETURNING id
209
+ `, [
210
+ dbTargetExperience.id,
211
+ dbTargetUser.id,
212
+ dbTargetExperience.casino_id,
213
+ ])
214
+ .then(exactlyOneRow);
215
+ return dbCreatedChatMod.id;
216
+ });
217
+ return chatModId;
218
+ });
219
+ return object({
220
+ chatMod: chatModTable.get({ id: $chatModId }),
221
+ });
222
+ },
223
+ },
224
+ },
225
+ },
226
+ };
227
+ });
@@ -128,7 +128,7 @@ export const HubChatMuteUserPlugin = extendSchema((build) => {
128
128
  throw new GraphQLError("Cannot mute yourself");
129
129
  }
130
130
  const muteId = await withPgPoolTransaction(superuserPool, async (pgClient) => {
131
- await PgAdvisoryLock.forChatUserAction(pgClient, {
131
+ await PgAdvisoryLock.forChatPlayerAction(pgClient, {
132
132
  userId: dbTargetUser.id,
133
133
  experienceId: identity.session.experience_id,
134
134
  casinoId: identity.session.casino_id,
@@ -79,7 +79,7 @@ export const HubChatUnmuteUserPlugin = extendSchema((build) => {
79
79
  throw new GraphQLError("User not found");
80
80
  }
81
81
  return withPgPoolTransaction(superuserPool, async (pgClient) => {
82
- await PgAdvisoryLock.forChatUserAction(pgClient, {
82
+ await PgAdvisoryLock.forChatPlayerAction(pgClient, {
83
83
  userId: dbTargetUser.id,
84
84
  experienceId: identity.session.experience_id,
85
85
  casinoId: identity.session.casino_id,
@@ -32,6 +32,7 @@ import { HubChatUnmuteUserPlugin } from "../plugins/chat/hub-chat-unmute-user.js
32
32
  import { HubChatMuteUserPlugin } from "../plugins/chat/hub-chat-mute-user.js";
33
33
  import { HubChatCreateSystemMessagePlugin } from "../plugins/chat/hub-chat-create-system-message.js";
34
34
  import { HubChatAfterIdConditionPlugin } from "../plugins/chat/hub-chat-after-id-condition.js";
35
+ import { HubChatModManagementPlugin } from "../plugins/chat/hub-chat-mod-management.js";
35
36
  export const requiredPlugins = [
36
37
  SmartTagsPlugin,
37
38
  IdToNodeIdPlugin,
@@ -76,7 +77,7 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
76
77
  mutablePlugins.push(HubCreatePlaygroundSessionPlugin);
77
78
  }
78
79
  if (enableChat) {
79
- mutablePlugins.push(HubChatCreateUserMessagePlugin, HubChatCreateSystemMessagePlugin, HubChatSubscriptionPlugin, HubChatMuteUserPlugin, HubChatUnmuteUserPlugin, HubChatAfterIdConditionPlugin);
80
+ mutablePlugins.push(HubChatCreateUserMessagePlugin, HubChatCreateSystemMessagePlugin, HubChatSubscriptionPlugin, HubChatMuteUserPlugin, HubChatUnmuteUserPlugin, HubChatAfterIdConditionPlugin, HubChatModManagementPlugin);
80
81
  }
81
82
  const preset = {
82
83
  extends: [PostGraphileAmberPreset],
package/dist/src/util.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { assert } from "tsafe";
1
2
  const UUID_REGEX = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
2
3
  export function isUuid(input) {
3
4
  return typeof input === "string" && UUID_REGEX.test(input);
@@ -6,8 +7,7 @@ export function extractFirstZodErrorMessage(e) {
6
7
  return e.issues[0].message;
7
8
  }
8
9
  export function uuidEqual(a, b) {
9
- return normalizeUuid(a) === normalizeUuid(b);
10
- }
11
- function normalizeUuid(uuid) {
12
- return uuid.toLowerCase();
10
+ assert(isUuid(a), `Expected valid uuid string: "${a}"`);
11
+ assert(isUuid(b), `Expected valid uuid string: "${b}"`);
12
+ return a.toLowerCase() === b.toLowerCase();
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneypot/hub",
3
- "version": "1.16.0-dev.5",
3
+ "version": "1.16.0-dev.7",
4
4
  "author": "moneypot.com",
5
5
  "homepage": "https://moneypot.com/hub",
6
6
  "keywords": [