@sfmc-bds/db-server 0.2.0-beta.4 → 0.2.0-beta.6

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.
Files changed (143) hide show
  1. package/LICENSE +663 -0
  2. package/dist/domain/bridge.d.ts +57 -23
  3. package/dist/domain/bridge.d.ts.map +1 -1
  4. package/dist/domain/bridge.js +77 -6
  5. package/dist/domain/bridge.js.map +2 -2
  6. package/dist/domain/bridge.test.d.ts +2 -0
  7. package/dist/domain/bridge.test.d.ts.map +1 -0
  8. package/dist/domain/bridge.test.js +87 -0
  9. package/dist/domain/bridge.test.js.map +7 -0
  10. package/dist/domain/economy.d.ts +38 -7
  11. package/dist/domain/economy.d.ts.map +1 -1
  12. package/dist/domain/economy.js.map +2 -2
  13. package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
  14. package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
  15. package/dist/domain/messages-bridge-filter.test.js +20 -0
  16. package/dist/domain/messages-bridge-filter.test.js.map +7 -0
  17. package/dist/domain/qq-bind-status.test.d.ts +2 -0
  18. package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
  19. package/dist/domain/qq-bind-status.test.js +211 -0
  20. package/dist/domain/qq-bind-status.test.js.map +7 -0
  21. package/dist/domain/qq-events.d.ts +84 -0
  22. package/dist/domain/qq-events.d.ts.map +1 -0
  23. package/dist/domain/qq-events.js +193 -0
  24. package/dist/domain/qq-events.js.map +7 -0
  25. package/dist/domain/qq-events.test.d.ts +5 -0
  26. package/dist/domain/qq-events.test.d.ts.map +1 -0
  27. package/dist/domain/qq-events.test.js +170 -0
  28. package/dist/domain/qq-events.test.js.map +7 -0
  29. package/dist/domain/qq-join.test.d.ts +2 -0
  30. package/dist/domain/qq-join.test.d.ts.map +1 -0
  31. package/dist/domain/qq-join.test.js +353 -0
  32. package/dist/domain/qq-join.test.js.map +7 -0
  33. package/dist/domain/schema.d.ts +17 -19
  34. package/dist/domain/schema.d.ts.map +1 -1
  35. package/dist/domain/schema.js +69 -0
  36. package/dist/domain/schema.js.map +2 -2
  37. package/dist/domain/system-status.d.ts +75 -0
  38. package/dist/domain/system-status.d.ts.map +1 -0
  39. package/dist/domain/system-status.js +181 -0
  40. package/dist/domain/system-status.js.map +7 -0
  41. package/dist/domain/system-status.test.d.ts +5 -0
  42. package/dist/domain/system-status.test.d.ts.map +1 -0
  43. package/dist/domain/system-status.test.js +19 -0
  44. package/dist/domain/system-status.test.js.map +7 -0
  45. package/dist/domain/transaction.d.ts +5 -4
  46. package/dist/domain/transaction.d.ts.map +1 -1
  47. package/dist/domain/transaction.js.map +2 -2
  48. package/dist/env.d.ts +7 -0
  49. package/dist/env.d.ts.map +1 -1
  50. package/dist/env.js +55 -3
  51. package/dist/env.js.map +2 -2
  52. package/dist/index.d.ts +0 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +105 -19
  55. package/dist/index.js.map +2 -2
  56. package/dist/lib/idempotency-store.d.ts +17 -11
  57. package/dist/lib/idempotency-store.d.ts.map +1 -1
  58. package/dist/lib/idempotency-store.js.map +2 -2
  59. package/dist/lib/idempotency.d.ts +3 -4
  60. package/dist/lib/idempotency.d.ts.map +1 -1
  61. package/dist/lib/idempotency.js.map +2 -2
  62. package/dist/lib/module-state.d.ts +38 -8
  63. package/dist/lib/module-state.d.ts.map +1 -1
  64. package/dist/lib/module-state.js.map +2 -2
  65. package/dist/lib/order-by.d.ts +8 -6
  66. package/dist/lib/order-by.d.ts.map +1 -1
  67. package/dist/lib/order-by.js.map +2 -2
  68. package/dist/lib/sql-helpers.d.ts +25 -13
  69. package/dist/lib/sql-helpers.d.ts.map +1 -1
  70. package/dist/lib/sql-helpers.js.map +2 -2
  71. package/dist/lib/sqlite.d.ts +14 -5
  72. package/dist/lib/sqlite.d.ts.map +1 -1
  73. package/dist/lib/sqlite.js.map +2 -2
  74. package/dist/manifest-loader.d.ts +5 -2
  75. package/dist/manifest-loader.d.ts.map +1 -1
  76. package/dist/manifest-loader.js +16 -6
  77. package/dist/manifest-loader.js.map +2 -2
  78. package/dist/manifest.d.ts +1 -2
  79. package/dist/manifest.d.ts.map +1 -1
  80. package/dist/manifest.js +13 -8
  81. package/dist/manifest.js.map +2 -2
  82. package/dist/project-root.d.ts +1 -1
  83. package/dist/project-root.d.ts.map +1 -1
  84. package/dist/project-root.js +2 -2
  85. package/dist/project-root.js.map +2 -2
  86. package/dist/routes/_shared.d.ts +14 -5
  87. package/dist/routes/_shared.d.ts.map +1 -1
  88. package/dist/routes/_shared.js.map +2 -2
  89. package/dist/routes/config.d.ts +8 -11
  90. package/dist/routes/config.d.ts.map +1 -1
  91. package/dist/routes/config.js +17 -23
  92. package/dist/routes/config.js.map +2 -2
  93. package/dist/routes/db-routes.d.ts +6 -0
  94. package/dist/routes/db-routes.d.ts.map +1 -1
  95. package/dist/routes/db-routes.js.map +2 -2
  96. package/dist/routes/messages.d.ts +6 -2
  97. package/dist/routes/messages.d.ts.map +1 -1
  98. package/dist/routes/messages.js +17 -3
  99. package/dist/routes/messages.js.map +2 -2
  100. package/dist/routes/module-config-routes.d.ts +13 -9
  101. package/dist/routes/module-config-routes.d.ts.map +1 -1
  102. package/dist/routes/module-config-routes.js.map +2 -2
  103. package/dist/routes/modules.d.ts +6 -0
  104. package/dist/routes/modules.d.ts.map +1 -1
  105. package/dist/routes/modules.js.map +2 -2
  106. package/dist/routes/qq-bind.d.ts +24 -0
  107. package/dist/routes/qq-bind.d.ts.map +1 -0
  108. package/dist/routes/qq-bind.js +179 -0
  109. package/dist/routes/qq-bind.js.map +7 -0
  110. package/dist/routes/qq-events.d.ts +20 -0
  111. package/dist/routes/qq-events.d.ts.map +1 -0
  112. package/dist/routes/qq-events.js +39 -0
  113. package/dist/routes/qq-events.js.map +7 -0
  114. package/dist/routes/qq-join.d.ts +46 -0
  115. package/dist/routes/qq-join.d.ts.map +1 -0
  116. package/dist/routes/qq-join.js +277 -0
  117. package/dist/routes/qq-join.js.map +7 -0
  118. package/dist/routes/service-routes.d.ts +6 -0
  119. package/dist/routes/service-routes.d.ts.map +1 -1
  120. package/dist/routes/service-routes.js.map +2 -2
  121. package/dist/routes/status.d.ts +19 -0
  122. package/dist/routes/status.d.ts.map +1 -0
  123. package/dist/routes/status.js +74 -0
  124. package/dist/routes/status.js.map +7 -0
  125. package/dist/schema-registry.d.ts +16 -15
  126. package/dist/schema-registry.d.ts.map +1 -1
  127. package/dist/schema-registry.js +7 -1
  128. package/dist/schema-registry.js.map +2 -2
  129. package/dist/server.d.ts +19 -0
  130. package/dist/server.d.ts.map +1 -1
  131. package/dist/server.js.map +2 -2
  132. package/dist/service-registry.d.ts +39 -9
  133. package/dist/service-registry.d.ts.map +1 -1
  134. package/dist/service-registry.js +30 -2
  135. package/dist/service-registry.js.map +2 -2
  136. package/dist/services/builtin-handlers.d.ts +27 -7
  137. package/dist/services/builtin-handlers.d.ts.map +1 -1
  138. package/dist/services/builtin-handlers.js.map +2 -2
  139. package/dist/tx-runner.d.ts +29 -3
  140. package/dist/tx-runner.d.ts.map +1 -1
  141. package/dist/tx-runner.js +29 -3
  142. package/dist/tx-runner.js.map +2 -2
  143. package/package.json +17 -15
@@ -0,0 +1,353 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { initSchema } from "./schema.js";
5
+ import { createQuery } from "../lib/sqlite.js";
6
+ import { createQqJoinRoutes } from "../routes/qq-join.js";
7
+ function memDb() {
8
+ const db = new DatabaseSync(":memory:");
9
+ initSchema(db);
10
+ return { db, query: createQuery(db) };
11
+ }
12
+ function mockRes() {
13
+ let status = 200;
14
+ let body = {};
15
+ return {
16
+ res: {
17
+ writeHead(code) {
18
+ status = code;
19
+ },
20
+ end(s) {
21
+ body = JSON.parse(s);
22
+ }
23
+ },
24
+ get status() {
25
+ return status;
26
+ },
27
+ get body() {
28
+ return body;
29
+ }
30
+ };
31
+ }
32
+ function makeHandle(admins = ["admin1"], flags = {}) {
33
+ const { query } = memDb();
34
+ let bodyData = {};
35
+ let joinFlags = {
36
+ allowlistEnabled: flags.allowlistEnabled !== false,
37
+ requireApproval: flags.requireApproval !== false,
38
+ treatGroupAdminsAsAdmins: flags.treatGroupAdminsAsAdmins === true
39
+ };
40
+ const handle = createQqJoinRoutes({
41
+ query,
42
+ body: async () => bodyData,
43
+ json: (res, data, status = 200) => {
44
+ res.writeHead(status);
45
+ res.end(JSON.stringify(data));
46
+ },
47
+ getAdminOpenids: () => admins,
48
+ getJoinFlags: () => ({ ...joinFlags }),
49
+ setJoinFlags: (partial) => {
50
+ joinFlags = { ...joinFlags, ...partial };
51
+ return { ...joinFlags };
52
+ }
53
+ });
54
+ return {
55
+ query,
56
+ setBody(d) {
57
+ bodyData = d;
58
+ },
59
+ handle,
60
+ getFlags: () => ({ ...joinFlags })
61
+ };
62
+ }
63
+ test("initSchema \u542B join / admin_actions \u8868", () => {
64
+ const { db } = memDb();
65
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'sfmc_qq_%' ORDER BY name").all();
66
+ assert.ok(tables.some((t) => t.name === "sfmc_qq_join_requests"));
67
+ assert.ok(tables.some((t) => t.name === "sfmc_qq_admin_actions"));
68
+ });
69
+ test("join request \u2192 decide approve \u2192 apply-queue \u2192 applied", async () => {
70
+ const { setBody, handle } = makeHandle(["admin1"]);
71
+ setBody({ openid: "user1", player_name: "Steve", qq_backend: "official" });
72
+ let m = mockRes();
73
+ await handle({
74
+ path: "/api/sfmc/qq/join/request",
75
+ method: "POST",
76
+ params: new URLSearchParams(),
77
+ req: {},
78
+ res: m.res
79
+ });
80
+ assert.equal(m.body.success, true);
81
+ const id = String(m.body.id);
82
+ assert.match(id, /^join_/);
83
+ setBody({ id, decision: "approve", decided_by: "stranger" });
84
+ m = mockRes();
85
+ await handle({
86
+ path: "/api/sfmc/qq/join/decide",
87
+ method: "POST",
88
+ params: new URLSearchParams(),
89
+ req: {},
90
+ res: m.res
91
+ });
92
+ assert.equal(m.status, 403);
93
+ assert.equal(m.body.error, "not_admin");
94
+ setBody({ id, decision: "approve", decided_by: "admin1" });
95
+ m = mockRes();
96
+ await handle({
97
+ path: "/api/sfmc/qq/join/decide",
98
+ method: "POST",
99
+ params: new URLSearchParams(),
100
+ req: {},
101
+ res: m.res
102
+ });
103
+ assert.equal(m.body.success, true);
104
+ assert.equal(m.body.status, "approved");
105
+ m = mockRes();
106
+ await handle({
107
+ path: "/api/sfmc/qq/join/apply-queue",
108
+ method: "GET",
109
+ params: new URLSearchParams(),
110
+ req: {},
111
+ res: m.res
112
+ });
113
+ assert.equal(m.body.success, true);
114
+ const queue = m.body.queue;
115
+ assert.equal(queue.length, 1);
116
+ assert.equal(queue[0].player_name, "Steve");
117
+ setBody({ id, ok: true });
118
+ m = mockRes();
119
+ await handle({
120
+ path: "/api/sfmc/qq/join/applied",
121
+ method: "POST",
122
+ params: new URLSearchParams(),
123
+ req: {},
124
+ res: m.res
125
+ });
126
+ assert.equal(m.body.success, true);
127
+ m = mockRes();
128
+ await handle({
129
+ path: "/api/sfmc/qq/join/apply-queue",
130
+ method: "GET",
131
+ params: new URLSearchParams(),
132
+ req: {},
133
+ res: m.res
134
+ });
135
+ assert.equal(m.body.queue.length, 0);
136
+ });
137
+ test("join reject \u4E0D\u8FDB apply-queue", async () => {
138
+ const { setBody, handle } = makeHandle(["admin1"]);
139
+ setBody({ openid: "u2", player_name: "Alex" });
140
+ let m = mockRes();
141
+ await handle({
142
+ path: "/api/sfmc/qq/join/request",
143
+ method: "POST",
144
+ params: new URLSearchParams(),
145
+ req: {},
146
+ res: m.res
147
+ });
148
+ const id = String(m.body.id);
149
+ setBody({ id, decision: "reject", decided_by: "admin1" });
150
+ m = mockRes();
151
+ await handle({
152
+ path: "/api/sfmc/qq/join/decide",
153
+ method: "POST",
154
+ params: new URLSearchParams(),
155
+ req: {},
156
+ res: m.res
157
+ });
158
+ assert.equal(m.body.status, "rejected");
159
+ m = mockRes();
160
+ await handle({
161
+ path: "/api/sfmc/qq/join/apply-queue",
162
+ method: "GET",
163
+ params: new URLSearchParams(),
164
+ req: {},
165
+ res: m.res
166
+ });
167
+ assert.equal(m.body.queue.length, 0);
168
+ });
169
+ test("kick \u2192 action-queue \u2192 action-done", async () => {
170
+ const { setBody, handle } = makeHandle(["admin1"]);
171
+ setBody({ openid: "admin1", target_name: "Steve", reason: "test" });
172
+ let m = mockRes();
173
+ await handle({
174
+ path: "/api/sfmc/qq/admin/kick",
175
+ method: "POST",
176
+ params: new URLSearchParams(),
177
+ req: {},
178
+ res: m.res
179
+ });
180
+ assert.equal(m.body.success, true);
181
+ const id = String(m.body.id);
182
+ m = mockRes();
183
+ await handle({
184
+ path: "/api/sfmc/qq/admin/action-queue",
185
+ method: "GET",
186
+ params: new URLSearchParams(),
187
+ req: {},
188
+ res: m.res
189
+ });
190
+ const queue = m.body.queue;
191
+ assert.equal(queue.length, 1);
192
+ assert.equal(queue[0].kind, "kick");
193
+ assert.equal(queue[0].target_name, "Steve");
194
+ setBody({ id, ok: true });
195
+ m = mockRes();
196
+ await handle({
197
+ path: "/api/sfmc/qq/admin/action-done",
198
+ method: "POST",
199
+ params: new URLSearchParams(),
200
+ req: {},
201
+ res: m.res
202
+ });
203
+ assert.equal(m.body.success, true);
204
+ m = mockRes();
205
+ await handle({
206
+ path: "/api/sfmc/qq/admin/action-queue",
207
+ method: "GET",
208
+ params: new URLSearchParams(),
209
+ req: {},
210
+ res: m.res
211
+ });
212
+ assert.equal(m.body.queue.length, 0);
213
+ });
214
+ test("\u767D\u540D\u5355\u5173\u95ED\u65F6\u62D2\u7EDD\u7533\u8BF7\u4E14 apply-queue \u7A7A", async () => {
215
+ const { setBody, handle } = makeHandle(["admin1"], { allowlistEnabled: false });
216
+ setBody({ openid: "u", player_name: "Steve" });
217
+ let m = mockRes();
218
+ await handle({
219
+ path: "/api/sfmc/qq/join/request",
220
+ method: "POST",
221
+ params: new URLSearchParams(),
222
+ req: {},
223
+ res: m.res
224
+ });
225
+ assert.equal(m.status, 403);
226
+ assert.equal(m.body.error, "join_allowlist_disabled");
227
+ m = mockRes();
228
+ await handle({
229
+ path: "/api/sfmc/qq/join/apply-queue",
230
+ method: "GET",
231
+ params: new URLSearchParams(),
232
+ req: {},
233
+ res: m.res
234
+ });
235
+ assert.equal(m.body.queue.length, 0);
236
+ });
237
+ test("\u5173\u95ED\u5BA1\u6279\u65F6\u7533\u8BF7\u76F4\u63A5 approved", async () => {
238
+ const { setBody, handle } = makeHandle(["admin1"], { requireApproval: false });
239
+ setBody({ openid: "u", player_name: "Alex" });
240
+ let m = mockRes();
241
+ await handle({
242
+ path: "/api/sfmc/qq/join/request",
243
+ method: "POST",
244
+ params: new URLSearchParams(),
245
+ req: {},
246
+ res: m.res
247
+ });
248
+ assert.equal(m.body.success, true);
249
+ assert.equal(m.body.status, "approved");
250
+ assert.equal(m.body.auto_approved, true);
251
+ m = mockRes();
252
+ await handle({
253
+ path: "/api/sfmc/qq/join/apply-queue",
254
+ method: "GET",
255
+ params: new URLSearchParams(),
256
+ req: {},
257
+ res: m.res
258
+ });
259
+ assert.equal(m.body.queue.length, 1);
260
+ });
261
+ test("\u7BA1\u7406\u5458\u53EF\u6539 settings", async () => {
262
+ const { setBody, handle, getFlags } = makeHandle(["admin1"]);
263
+ let m = mockRes();
264
+ await handle({
265
+ path: "/api/sfmc/qq/join/settings",
266
+ method: "GET",
267
+ params: new URLSearchParams(),
268
+ req: {},
269
+ res: m.res
270
+ });
271
+ assert.equal(m.body.allowlist_enabled, true);
272
+ assert.equal(m.body.require_approval, true);
273
+ assert.equal(m.body.treat_group_admins_as_admins, false);
274
+ setBody({ openid: "stranger", require_approval: false });
275
+ m = mockRes();
276
+ await handle({
277
+ path: "/api/sfmc/qq/join/settings",
278
+ method: "POST",
279
+ params: new URLSearchParams(),
280
+ req: {},
281
+ res: m.res
282
+ });
283
+ assert.equal(m.status, 403);
284
+ setBody({ openid: "admin1", require_approval: false, allowlist_enabled: true });
285
+ m = mockRes();
286
+ await handle({
287
+ path: "/api/sfmc/qq/join/settings",
288
+ method: "POST",
289
+ params: new URLSearchParams(),
290
+ req: {},
291
+ res: m.res
292
+ });
293
+ assert.equal(m.body.success, true);
294
+ assert.equal(m.body.require_approval, false);
295
+ assert.equal(getFlags().requireApproval, false);
296
+ });
297
+ test("treat_group_admins_as_admins \u53EA\u8BFB\uFF1APOST \u6539\u5199\u88AB\u62D2\uFF1B\u7FA4\u7BA1\u53EF\u9274\u6743", async () => {
298
+ const { setBody, handle, getFlags } = makeHandle([], { treatGroupAdminsAsAdmins: true });
299
+ let m = mockRes();
300
+ await handle({
301
+ path: "/api/sfmc/qq/join/settings",
302
+ method: "GET",
303
+ params: new URLSearchParams(),
304
+ req: {},
305
+ res: m.res
306
+ });
307
+ assert.equal(m.body.treat_group_admins_as_admins, true);
308
+ setBody({ openid: "gadmin", treat_group_admins_as_admins: false, as_group_admin: true });
309
+ m = mockRes();
310
+ await handle({
311
+ path: "/api/sfmc/qq/join/settings",
312
+ method: "POST",
313
+ params: new URLSearchParams(),
314
+ req: {},
315
+ res: m.res
316
+ });
317
+ assert.equal(m.status, 400);
318
+ assert.equal(m.body.error, "immutable_field");
319
+ assert.equal(getFlags().treatGroupAdminsAsAdmins, true);
320
+ setBody({ openid: "gadmin", player_name: "Alex", qq_backend: "official" });
321
+ m = mockRes();
322
+ await handle({
323
+ path: "/api/sfmc/qq/join/request",
324
+ method: "POST",
325
+ params: new URLSearchParams(),
326
+ req: {},
327
+ res: m.res
328
+ });
329
+ const id = String(m.body.id);
330
+ setBody({ id, decision: "approve", decided_by: "gadmin", as_group_admin: true });
331
+ m = mockRes();
332
+ await handle({
333
+ path: "/api/sfmc/qq/join/decide",
334
+ method: "POST",
335
+ params: new URLSearchParams(),
336
+ req: {},
337
+ res: m.res
338
+ });
339
+ assert.equal(m.body.success, true);
340
+ assert.equal(m.body.status, "approved");
341
+ const denied = makeHandle([], { treatGroupAdminsAsAdmins: false });
342
+ denied.setBody({ id: "x", decision: "approve", decided_by: "gadmin", as_group_admin: true });
343
+ m = mockRes();
344
+ await denied.handle({
345
+ path: "/api/sfmc/qq/join/decide",
346
+ method: "POST",
347
+ params: new URLSearchParams(),
348
+ req: {},
349
+ res: m.res
350
+ });
351
+ assert.equal(m.status, 403);
352
+ });
353
+ //# sourceMappingURL=qq-join.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/domain/qq-join.test.ts"],
4
+ "sourcesContent": ["/**\n * domain/qq-join.test.ts \u2014 \u5165\u670D\u5BA1\u6279\u72B6\u6001\u673A + \u8E22\u4EBA\u961F\u5217\n */\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { initSchema } from \"./schema.js\";\nimport { createQuery } from \"../lib/sqlite.js\";\nimport { createQqJoinRoutes } from \"../routes/qq-join.js\";\n\nfunction memDb() {\n const db = new DatabaseSync(\":memory:\");\n initSchema(db);\n return { db, query: createQuery(db) };\n}\n\nfunction mockRes() {\n let status = 200;\n let body: Record<string, unknown> = {};\n return {\n res: {\n writeHead(code: number) {\n status = code;\n },\n end(s: string) {\n body = JSON.parse(s) as Record<string, unknown>;\n },\n } as unknown as import(\"http\").ServerResponse,\n get status() {\n return status;\n },\n get body() {\n return body;\n },\n };\n}\n\nfunction makeHandle(\n admins: string[] = [\"admin1\"],\n flags: {\n allowlistEnabled?: boolean;\n requireApproval?: boolean;\n treatGroupAdminsAsAdmins?: boolean;\n } = {}\n) {\n const { query } = memDb();\n let bodyData: Record<string, unknown> = {};\n let joinFlags = {\n allowlistEnabled: flags.allowlistEnabled !== false,\n requireApproval: flags.requireApproval !== false,\n treatGroupAdminsAsAdmins: flags.treatGroupAdminsAsAdmins === true,\n };\n const handle = createQqJoinRoutes({\n query,\n body: async () => bodyData,\n json: (res, data, status = 200) => {\n (res as unknown as { writeHead: (c: number) => void }).writeHead(status);\n (res as unknown as { end: (s: string) => void }).end(JSON.stringify(data));\n },\n getAdminOpenids: () => admins,\n getJoinFlags: () => ({ ...joinFlags }),\n setJoinFlags: (partial) => {\n joinFlags = { ...joinFlags, ...partial };\n return { ...joinFlags };\n },\n });\n return {\n query,\n setBody(d: Record<string, unknown>) {\n bodyData = d;\n },\n handle,\n getFlags: () => ({ ...joinFlags }),\n };\n}\n\ntest(\"initSchema \u542B join / admin_actions \u8868\", () => {\n const { db } = memDb();\n const tables = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'sfmc_qq_%' ORDER BY name\")\n .all() as Array<{ name: string }>;\n assert.ok(tables.some((t) => t.name === \"sfmc_qq_join_requests\"));\n assert.ok(tables.some((t) => t.name === \"sfmc_qq_admin_actions\"));\n});\n\ntest(\"join request \u2192 decide approve \u2192 apply-queue \u2192 applied\", async () => {\n const { setBody, handle } = makeHandle([\"admin1\"]);\n\n setBody({ openid: \"user1\", player_name: \"Steve\", qq_backend: \"official\" });\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/request\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n const id = String(m.body.id);\n assert.match(id, /^join_/);\n\n setBody({ id, decision: \"approve\", decided_by: \"stranger\" });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/decide\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.status, 403);\n assert.equal(m.body.error, \"not_admin\");\n\n setBody({ id, decision: \"approve\", decided_by: \"admin1\" });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/decide\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n assert.equal(m.body.status, \"approved\");\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/apply-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n const queue = m.body.queue as Array<{ id: string; player_name: string }>;\n assert.equal(queue.length, 1);\n assert.equal(queue[0]!.player_name, \"Steve\");\n\n setBody({ id, ok: true });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/applied\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/apply-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal((m.body.queue as unknown[]).length, 0);\n});\n\ntest(\"join reject \u4E0D\u8FDB apply-queue\", async () => {\n const { setBody, handle } = makeHandle([\"admin1\"]);\n setBody({ openid: \"u2\", player_name: \"Alex\" });\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/request\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n const id = String(m.body.id);\n\n setBody({ id, decision: \"reject\", decided_by: \"admin1\" });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/decide\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.status, \"rejected\");\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/apply-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal((m.body.queue as unknown[]).length, 0);\n});\n\ntest(\"kick \u2192 action-queue \u2192 action-done\", async () => {\n const { setBody, handle } = makeHandle([\"admin1\"]);\n\n setBody({ openid: \"admin1\", target_name: \"Steve\", reason: \"test\" });\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/admin/kick\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n const id = String(m.body.id);\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/admin/action-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n const queue = m.body.queue as Array<{ id: string; kind: string; target_name: string }>;\n assert.equal(queue.length, 1);\n assert.equal(queue[0]!.kind, \"kick\");\n assert.equal(queue[0]!.target_name, \"Steve\");\n\n setBody({ id, ok: true });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/admin/action-done\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/admin/action-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal((m.body.queue as unknown[]).length, 0);\n});\n\ntest(\"\u767D\u540D\u5355\u5173\u95ED\u65F6\u62D2\u7EDD\u7533\u8BF7\u4E14 apply-queue \u7A7A\", async () => {\n const { setBody, handle } = makeHandle([\"admin1\"], { allowlistEnabled: false });\n setBody({ openid: \"u\", player_name: \"Steve\" });\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/request\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.status, 403);\n assert.equal(m.body.error, \"join_allowlist_disabled\");\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/apply-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal((m.body.queue as unknown[]).length, 0);\n});\n\ntest(\"\u5173\u95ED\u5BA1\u6279\u65F6\u7533\u8BF7\u76F4\u63A5 approved\", async () => {\n const { setBody, handle } = makeHandle([\"admin1\"], { requireApproval: false });\n setBody({ openid: \"u\", player_name: \"Alex\" });\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/request\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n assert.equal(m.body.status, \"approved\");\n assert.equal(m.body.auto_approved, true);\n\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/apply-queue\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal((m.body.queue as unknown[]).length, 1);\n});\n\ntest(\"\u7BA1\u7406\u5458\u53EF\u6539 settings\", async () => {\n const { setBody, handle, getFlags } = makeHandle([\"admin1\"]);\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/settings\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.allowlist_enabled, true);\n assert.equal(m.body.require_approval, true);\n assert.equal(m.body.treat_group_admins_as_admins, false);\n\n setBody({ openid: \"stranger\", require_approval: false });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/settings\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.status, 403);\n\n setBody({ openid: \"admin1\", require_approval: false, allowlist_enabled: true });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/settings\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n assert.equal(m.body.require_approval, false);\n assert.equal(getFlags().requireApproval, false);\n});\n\ntest(\"treat_group_admins_as_admins \u53EA\u8BFB\uFF1APOST \u6539\u5199\u88AB\u62D2\uFF1B\u7FA4\u7BA1\u53EF\u9274\u6743\", async () => {\n const { setBody, handle, getFlags } = makeHandle([], { treatGroupAdminsAsAdmins: true });\n\n let m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/settings\",\n method: \"GET\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.treat_group_admins_as_admins, true);\n\n setBody({ openid: \"gadmin\", treat_group_admins_as_admins: false, as_group_admin: true });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/settings\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.status, 400);\n assert.equal(m.body.error, \"immutable_field\");\n assert.equal(getFlags().treatGroupAdminsAsAdmins, true);\n\n setBody({ openid: \"gadmin\", player_name: \"Alex\", qq_backend: \"official\" });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/request\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n const id = String(m.body.id);\n\n setBody({ id, decision: \"approve\", decided_by: \"gadmin\", as_group_admin: true });\n m = mockRes();\n await handle({\n path: \"/api/sfmc/qq/join/decide\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.body.success, true);\n assert.equal(m.body.status, \"approved\");\n\n const denied = makeHandle([], { treatGroupAdminsAsAdmins: false });\n denied.setBody({ id: \"x\", decision: \"approve\", decided_by: \"gadmin\", as_group_admin: true });\n m = mockRes();\n await denied.handle({\n path: \"/api/sfmc/qq/join/decide\",\n method: \"POST\",\n params: new URLSearchParams(),\n req: {} as import(\"http\").IncomingMessage,\n res: m.res,\n });\n assert.equal(m.status, 403);\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,26 +1,24 @@
1
1
  /**
2
- * domain/schema.ts — 数据库 schema 初始化(唯一真源)
2
+ * domain/schema.ts — 数据库 Schema 初始化定义(平台内嵌表唯一基线)
3
3
  *
4
- * 所有 CREATE TABLE / CREATE INDEX 语句集中于此。
5
- * 调用方式:initSchema(db) 在 openDatabase 之后、createQuery 之前执行。
4
+ * 集中管理平台核心数据表的 `CREATE TABLE IF NOT EXISTS` 与 `CREATE INDEX IF NOT EXISTS` DDL 语句。
5
+ * 调用方式:`initSchema(db)` ——`openDatabase` 之后、业务查询启动前执行。
6
6
  *
7
- * 事务描述:
8
- * - 无业务 Tx(本文件只做 DDL,不涉及业务事务)
9
- *
10
- * 业务域(按文件内 db.exec 块编号):
11
- * (1) 世界备份 (2) 玩家备份 (3) 计分板
12
- * (4) 玩家行为日志 (5) 聊天频道 (6) 聊天消息
13
- * (7) 红包 (8) 合作社主体/成员/邀请/账户
14
- * (9) 合作社商店项/商店组 (10) 合作社账变流水/审计
15
- * (11) 领地主体/成员/权限 (12) 领地邀请/审计/请求幂等
16
- * (13) 玩家指令用量 (14) 价格指数
17
- * (15) 每日任务/统计 (16) 经济账户/流水/幂等
18
- *
19
- * 完整表名以 `sfmc_` 前缀,具体见下方每个 db.exec 块。
20
- *
21
- * 备注: ALTER / DROP 不在此处;如需迁移请新建 tools/migrate-*.ts,
22
- * 保持本文件只做"全新初始化"。
7
+ * 业务数据域分布:
8
+ * (1) 世界备份 (2) 玩家备份 (3) 计分板
9
+ * (4) 玩家行为日志 (5) 聊天频道 (6) 聊天消息
10
+ * (7) 红包 (8) 合作社主体/成员/邀请/账户
11
+ * (9) 合作社商店项/组 (10) 合作社流水/审计
12
+ * (11) 领地主体/成员 (12) 领地邀请/审计/幂等
13
+ * (13) 指令用量 (14) 价格指数
14
+ * (15) 每日任务/统计 (16) 经济账户/流水/幂等
15
+ * (17) QQ 绑定与申请 (18) 模块自声明元数据表
23
16
  */
24
17
  import type { DatabaseSync } from "node:sqlite";
18
+ /**
19
+ * 初始化数据库结构,创建所有平台内嵌表与索引。
20
+ *
21
+ * @param db SQLite 数据库连接实例。
22
+ */
25
23
  export declare function initSchema(db: DatabaseSync): void;
26
24
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+bjD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAofjD"}
@@ -509,6 +509,75 @@ function initSchema(db) {
509
509
  CREATE INDEX IF NOT EXISTS idx_sfmc_lands_owner ON sfmc_lands(owner_player_id, status);
510
510
  CREATE INDEX IF NOT EXISTS idx_sfmc_lands_location ON sfmc_lands(dimension, min_x, max_x, min_z, max_z, status)`
511
511
  );
512
+ db.exec(
513
+ /* sql */
514
+ `
515
+ CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (
516
+ code TEXT PRIMARY KEY NOT NULL, -- \u77ED\u7801
517
+ qq_user_openid TEXT NOT NULL, -- QQ \u4FA7\u7528\u6237 openid / llbot user id
518
+ qq_backend TEXT NOT NULL DEFAULT 'official', -- official | llbot
519
+ created_at INTEGER NOT NULL,
520
+ expires_at INTEGER NOT NULL
521
+ )`
522
+ );
523
+ db.exec(
524
+ /* sql */
525
+ `
526
+ CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (
527
+ qq_user_openid TEXT PRIMARY KEY NOT NULL,
528
+ player_xuid TEXT NOT NULL UNIQUE,
529
+ player_name TEXT NOT NULL DEFAULT '',
530
+ qq_backend TEXT NOT NULL DEFAULT 'official',
531
+ bound_at INTEGER NOT NULL
532
+ )`
533
+ );
534
+ db.exec(
535
+ /* sql */
536
+ `
537
+ CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);
538
+ CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`
539
+ );
540
+ db.exec(
541
+ /* sql */
542
+ `
543
+ CREATE TABLE IF NOT EXISTS sfmc_qq_join_requests (
544
+ id TEXT PRIMARY KEY NOT NULL,
545
+ applicant_openid TEXT NOT NULL,
546
+ player_name TEXT NOT NULL,
547
+ status TEXT NOT NULL DEFAULT 'pending',
548
+ qq_backend TEXT NOT NULL DEFAULT 'official',
549
+ created_at INTEGER NOT NULL,
550
+ decided_at INTEGER,
551
+ decided_by TEXT,
552
+ applied_at INTEGER,
553
+ apply_error TEXT
554
+ )`
555
+ );
556
+ db.exec(
557
+ /* sql */
558
+ `
559
+ CREATE INDEX IF NOT EXISTS idx_qq_join_status ON sfmc_qq_join_requests(status, created_at)`
560
+ );
561
+ db.exec(
562
+ /* sql */
563
+ `
564
+ CREATE TABLE IF NOT EXISTS sfmc_qq_admin_actions (
565
+ id TEXT PRIMARY KEY NOT NULL,
566
+ kind TEXT NOT NULL,
567
+ target_name TEXT NOT NULL,
568
+ reason TEXT NOT NULL DEFAULT '',
569
+ requested_by TEXT NOT NULL DEFAULT '',
570
+ status TEXT NOT NULL DEFAULT 'pending',
571
+ created_at INTEGER NOT NULL,
572
+ applied_at INTEGER,
573
+ apply_error TEXT
574
+ )`
575
+ );
576
+ db.exec(
577
+ /* sql */
578
+ `
579
+ CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`
580
+ );
512
581
  }
513
582
  export {
514
583
  initSchema