@sfmc-bds/db-server 0.2.0-beta.5 → 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/module-config-routes.ts"],
4
- "sourcesContent": ["/**\n * routes/module-config-routes.ts \u2014 \u6A21\u5757\u914D\u7F6E\u6587\u4EF6\u7BA1\u7406\n *\n * \u7AEF\u70B9:\n * GET /api/sfmc/configs/:configKey \u2192 { config: <configs/<configKey>.json> }\n * POST /api/sfmc/configs/:configKey/set \u2192 \u5199\u4E00\u4E2A key\n * GET /api/sfmc/configs/:configKey/notify (SSE:onChange \u63A8\u9001)\n *\n * \u9274\u6743:\u6A21\u5757\u8EAB\u4EFD\u6765\u81EA ctx.moduleAuth;Permission = config:read:configKey / config:write:configKey\u3002\n *\n * \u8BBE\u8BA1:\u6587\u4EF6 = configs/<configKey>.json;\u6574\u6587\u4EF6 read-modify-write(lock by\n * \u7B80\u5355 mutex),\u4E0D\u662F\u7EC6\u7C92\u5EA6 lock \u2014 \u6A21\u5757 config \u6587\u4EF6\u672C\u6765\u5C31\u5C0F;\u5E76\u53D1\u51B2\u7A81\u6982\u7387\u6781\u4F4E\u3002\n */\n\nimport { join } from \"node:path\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { assertModulePermission, Perm } from \"../permission-gate.js\";\nimport type { ModuleManifestV2 } from \"../manifest-loader.js\";\nimport { jsonV2Fail, type ModuleAuth } from \"./_shared.js\";\n\nexport interface ModuleConfigRoutesDeps {\n projectRoot: string;\n enabled: Map<string, ModuleManifestV2>;\n json?: typeof defaultJson;\n}\n\nfunction configPath(projectRoot: string, key: string): string {\n if (!/^[A-Za-z0-9_-]+$/.test(key)) {\n throw new Error(`invalid configKey \"${key}\"`);\n }\n return join(projectRoot, \"configs\", `${key}.json`);\n}\n\nfunction readConfig(file: string): Record<string, unknown> {\n const parsed = readJson<unknown>(file);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n}\n\nconst subscribers = new Map<string, Set<(payload: unknown) => void>>();\n\nfunction subscribe(configKey: string, cb: (payload: unknown) => void): () => void {\n if (!subscribers.has(configKey)) subscribers.set(configKey, new Set());\n subscribers.get(configKey)!.add(cb);\n return () => {\n subscribers.get(configKey)?.delete(cb);\n };\n}\n\nfunction notify(configKey: string, key: string, value: unknown): void {\n const set = subscribers.get(configKey);\n if (!set) return;\n for (const cb of set) cb({ key, value });\n}\n\nexport function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>) {\n const deps = depsIn as Partial<ModuleConfigRoutesDeps>;\n if (!deps.projectRoot || !deps.enabled) {\n throw new Error(\"createModuleConfigRoutes: \u7F3A\u5C11 projectRoot / enabled map\");\n }\n const json = deps.json || defaultJson;\n const projectRoot = deps.projectRoot;\n const enabled = deps.enabled!;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, req, res } = ctx;\n const m = path.match(/^\\/api\\/sfmc\\/configs\\/([A-Za-z0-9_-]+)(?:\\/(set|notify))?$/);\n if (!m) return false;\n const configKey = m[1];\n const tail = m[2];\n const auth = ctx.moduleAuth;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized\", 401, \"unauthorized\");\n return true;\n }\n const manifest = enabled.get(auth.id);\n if (!manifest || manifest.configKey !== configKey) {\n jsonV2Fail(res, `\u6A21\u5757 ${auth.id} \u4E0D\u6301\u6709 configKey \"${configKey}\"`, 403, \"forbidden\");\n return true;\n }\n\n const file = configPath(projectRoot, configKey);\n\n if (!tail && method === \"GET\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configRead(configKey));\n const cfg = readConfig(file);\n json(res, { config: cfg });\n } catch (e) {\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n // LSP: GET \u5931\u8D25\u4E0E /set \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"set\" && method === \"POST\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configWrite(configKey));\n const body = (await (ctx.body as Promise<Record<string, unknown>>)) || {};\n const key = String(body.key);\n const value = body.value;\n const cfg = readConfig(file);\n cfg[key] = value;\n writeJson(file, cfg);\n notify(configKey, key, value);\n json(res, { ok: true });\n } catch (e) {\n // LSP: /set \u6210\u529F\u7528 ok:true,\u5931\u8D25\u7EDF\u4E00 ok:false(\u52FF\u6DF7\u7528 success)\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"notify\" && method === \"GET\") {\n // SSE \u2014 PoC \u628A\u76D1\u542C\u7559\u5728\u5185\u5B58\u91CC,\u4E0D\u6301\u4E45\u5316\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\":ok\\n\\n\");\n const unsub = subscribe(configKey, (payload) => {\n try {\n res.write(`data: ${JSON.stringify(payload)}\\n\\n`);\n } catch {\n unsub();\n }\n });\n req.on(\"close\", unsub);\n return true;\n }\n\n return false;\n };\n};\n"],
5
- "mappings": "AAcA,SAAS,YAAY;AAErB,SAAS,UAAU,iBAAiB;AACpC,SAAS,QAAQ,mBAAgC;AACjD,SAAS,wBAAwB,YAAY;AAE7C,SAAS,kBAAmC;AAQ5C,SAAS,WAAW,aAAqB,KAAqB;AAC5D,MAAI,CAAC,mBAAmB,KAAK,GAAG,GAAG;AACjC,UAAM,IAAI,MAAM,sBAAsB,GAAG,GAAG;AAAA,EAC9C;AACA,SAAO,KAAK,aAAa,WAAW,GAAG,GAAG,OAAO;AACnD;AAEA,SAAS,WAAW,MAAuC;AACzD,QAAM,SAAS,SAAkB,IAAI;AACrC,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AAC5E,SAAO;AACT;AAEA,MAAM,cAAc,oBAAI,IAA6C;AAErE,SAAS,UAAU,WAAmB,IAA4C;AAChF,MAAI,CAAC,YAAY,IAAI,SAAS,EAAG,aAAY,IAAI,WAAW,oBAAI,IAAI,CAAC;AACrE,cAAY,IAAI,SAAS,EAAG,IAAI,EAAE;AAClC,SAAO,MAAM;AACX,gBAAY,IAAI,SAAS,GAAG,OAAO,EAAE;AAAA,EACvC;AACF;AAEA,SAAS,OAAO,WAAmB,KAAa,OAAsB;AACpE,QAAM,MAAM,YAAY,IAAI,SAAS;AACrC,MAAI,CAAC,IAAK;AACV,aAAW,MAAM,IAAK,IAAG,EAAE,KAAK,MAAM,CAAC;AACzC;AAEO,SAAS,yBAAyB,QAAyC;AAChF,QAAM,OAAO;AACb,MAAI,CAAC,KAAK,eAAe,CAAC,KAAK,SAAS;AACtC,UAAM,IAAI,MAAM,kEAAwD;AAAA,EAC1E;AACA,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,cAAc,KAAK;AACzB,QAAM,UAAU,KAAK;AAErB,SAAO,OAAO,QAOU;AACtB,UAAM,EAAE,MAAM,QAAQ,KAAK,IAAI,IAAI;AACnC,UAAM,IAAI,KAAK,MAAM,6DAA6D;AAClF,QAAI,CAAC,EAAG,QAAO;AACf,UAAM,YAAY,EAAE,CAAC;AACrB,UAAM,OAAO,EAAE,CAAC;AAChB,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,MAAM;AACT,iBAAW,KAAK,gBAAgB,KAAK,cAAc;AACnD,aAAO;AAAA,IACT;AACA,UAAM,WAAW,QAAQ,IAAI,KAAK,EAAE;AACpC,QAAI,CAAC,YAAY,SAAS,cAAc,WAAW;AACjD,iBAAW,KAAK,gBAAM,KAAK,EAAE,kCAAmB,SAAS,KAAK,KAAK,WAAW;AAC9E,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,WAAW,aAAa,SAAS;AAE9C,QAAI,CAAC,QAAQ,WAAW,OAAO;AAC7B,UAAI;AACF,+BAAuB,KAAK,IAAI,SAAS,aAAa,KAAK,WAAW,SAAS,CAAC;AAChF,cAAM,MAAM,WAAW,IAAI;AAC3B,aAAK,KAAK,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3B,SAAS,GAAG;AACV,cAAM,OAAQ,EAAwB,SAAS,0BAA0B,MAAM;AAE/E,mBAAW,KAAM,EAAY,SAAS,IAAI;AAAA,MAC5C;AACA,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,SAAS,WAAW,QAAQ;AACvC,UAAI;AACF,+BAAuB,KAAK,IAAI,SAAS,aAAa,KAAK,YAAY,SAAS,CAAC;AACjF,cAAM,OAAQ,MAAO,IAAI,QAA8C,CAAC;AACxE,cAAM,MAAM,OAAO,KAAK,GAAG;AAC3B,cAAM,QAAQ,KAAK;AACnB,cAAM,MAAM,WAAW,IAAI;AAC3B,YAAI,GAAG,IAAI;AACX,kBAAU,MAAM,GAAG;AACnB,eAAO,WAAW,KAAK,KAAK;AAC5B,aAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,MACxB,SAAS,GAAG;AAEV,cAAM,OAAQ,EAAwB,SAAS,0BAA0B,MAAM;AAC/E,mBAAW,KAAM,EAAY,SAAS,IAAI;AAAA,MAC5C;AACA,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,YAAY,WAAW,OAAO;AAEzC,UAAI,UAAU,KAAK;AAAA,QACjB,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,YAAY;AAAA,MACd,CAAC;AACD,UAAI,MAAM,SAAS;AACnB,YAAM,QAAQ,UAAU,WAAW,CAAC,YAAY;AAC9C,YAAI;AACF,cAAI,MAAM,SAAS,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA,CAAM;AAAA,QAClD,QAAQ;AACN,gBAAM;AAAA,QACR;AAAA,MACF,CAAC;AACD,UAAI,GAAG,SAAS,KAAK;AACrB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;AAAC;",
4
+ "sourcesContent": ["/**\n * routes/module-config-routes.ts \u2014 \u6A21\u5757\u79C1\u6709\u914D\u7F6E\u6587\u4EF6\u8BFB\u5199\u8DEF\u7531\n *\n * REST \u7AEF\u70B9\uFF1A\n * - GET /api/sfmc/configs/:configKey \u8BFB\u53D6\u5BF9\u5E94 `configs/<configKey>.json` \u914D\u7F6E\u6587\u4EF6\n * - POST /api/sfmc/configs/:configKey/set \u4FEE\u6539\u5E76\u6301\u4E45\u5316\u6307\u5B9A\u914D\u7F6E\u952E\u503C\u5BF9\n * - GET /api/sfmc/configs/:configKey/notify \u901A\u8FC7 SSE \u63A8\u9001\u914D\u7F6E\u5B9E\u65F6\u53D8\u66F4\u901A\u77E5\n *\n * \u9274\u6743\u63A7\u5236\uFF1A\n * \u6A21\u5757\u8EAB\u4EFD\u7ECF `ctx.moduleAuth` \u9A8C\u8BC1\uFF1B\u5FC5\u987B\u5177\u5907 `config:read:<configKey>` \u6216 `config:write:<configKey>` \u6743\u9650\u3002\n */\n\nimport { join } from \"node:path\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { assertModulePermission, Perm } from \"../permission-gate.js\";\nimport type { ModuleManifestV2 } from \"../manifest-loader.js\";\nimport { jsonV2Fail, type ModuleAuth } from \"./_shared.js\";\n\nexport interface ModuleConfigRoutesDeps {\n projectRoot: string;\n enabled: Map<string, ModuleManifestV2>;\n json?: typeof defaultJson;\n}\n\nfunction configPath(projectRoot: string, key: string): string {\n if (!/^[A-Za-z0-9_-]+$/.test(key)) {\n throw new Error(`invalid configKey \"${key}\"`);\n }\n return join(projectRoot, \"configs\", `${key}.json`);\n}\n\nfunction readConfig(file: string): Record<string, unknown> {\n const parsed = readJson<unknown>(file);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n}\n\nconst subscribers = new Map<string, Set<(payload: unknown) => void>>();\n\nfunction subscribe(configKey: string, cb: (payload: unknown) => void): () => void {\n if (!subscribers.has(configKey)) subscribers.set(configKey, new Set());\n subscribers.get(configKey)!.add(cb);\n return () => {\n subscribers.get(configKey)?.delete(cb);\n };\n}\n\nfunction notify(configKey: string, key: string, value: unknown): void {\n const set = subscribers.get(configKey);\n if (!set) return;\n for (const cb of set) cb({ key, value });\n}\n\n/**\n * \u521B\u5EFA\u6A21\u5757\u79C1\u6709\u914D\u7F6E\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B\u5DE5\u4F5C\u6839\u76EE\u5F55\u4E0E\u5DF2\u542F\u7528\u6A21\u5757\u5B57\u5178\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>) {\n\n const deps = depsIn as Partial<ModuleConfigRoutesDeps>;\n if (!deps.projectRoot || !deps.enabled) {\n throw new Error(\"createModuleConfigRoutes: \u7F3A\u5C11 projectRoot / enabled map\");\n }\n const json = deps.json || defaultJson;\n const projectRoot = deps.projectRoot;\n const enabled = deps.enabled!;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, req, res } = ctx;\n const m = path.match(/^\\/api\\/sfmc\\/configs\\/([A-Za-z0-9_-]+)(?:\\/(set|notify))?$/);\n if (!m) return false;\n const configKey = m[1];\n const tail = m[2];\n const auth = ctx.moduleAuth;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized\", 401, \"unauthorized\");\n return true;\n }\n const manifest = enabled.get(auth.id);\n if (!manifest || manifest.configKey !== configKey) {\n jsonV2Fail(res, `\u6A21\u5757 ${auth.id} \u4E0D\u6301\u6709 configKey \"${configKey}\"`, 403, \"forbidden\");\n return true;\n }\n\n const file = configPath(projectRoot, configKey);\n\n if (!tail && method === \"GET\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configRead(configKey));\n const cfg = readConfig(file);\n json(res, { config: cfg });\n } catch (e) {\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n // LSP: GET \u5931\u8D25\u4E0E /set \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"set\" && method === \"POST\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configWrite(configKey));\n const body = (await (ctx.body as Promise<Record<string, unknown>>)) || {};\n const key = String(body.key);\n const value = body.value;\n const cfg = readConfig(file);\n cfg[key] = value;\n writeJson(file, cfg);\n notify(configKey, key, value);\n json(res, { ok: true });\n } catch (e) {\n // LSP: /set \u6210\u529F\u7528 ok:true,\u5931\u8D25\u7EDF\u4E00 ok:false(\u52FF\u6DF7\u7528 success)\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"notify\" && method === \"GET\") {\n // SSE \u2014 PoC \u628A\u76D1\u542C\u7559\u5728\u5185\u5B58\u91CC,\u4E0D\u6301\u4E45\u5316\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\":ok\\n\\n\");\n const unsub = subscribe(configKey, (payload) => {\n try {\n res.write(`data: ${JSON.stringify(payload)}\\n\\n`);\n } catch {\n unsub();\n }\n });\n req.on(\"close\", unsub);\n return true;\n }\n\n return false;\n };\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -23,6 +23,12 @@ interface Deps {
23
23
  body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
24
24
  json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
25
25
  }
26
+ /**
27
+ * 创建模块目录检索与启停管控路由处理器。
28
+ *
29
+ * @param deps 依赖注入字典。
30
+ * @returns 异步路由处理函数。
31
+ */
26
32
  declare function createModuleRoutes({ loadModuleCatalog, buildModuleList, resolveModuleByKey, setModuleEnabled, body, json, }: Deps): ({ path, method, params, req, res, }: {
27
33
  path: string;
28
34
  method: string;
@@ -1 +1 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/routes/modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,UAAU,IAAI;IACZ,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;IACnC,eAAe,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,gBAAgB,EAAE,CAChB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,EAC3C,OAAO,EAAE,OAAO,KACb,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAED,iBAAS,kBAAkB,CAAC,EAC1B,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,IAAI,GACL,EAAE,IAAI,yCAOF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAqErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/routes/modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,UAAU,IAAI;IACZ,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;IACnC,eAAe,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,gBAAgB,EAAE,CAChB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,EAC3C,OAAO,EAAE,OAAO,KACb,IAAI,CAAC;IACV,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAED;;;;;GAKG;AACH,iBAAS,kBAAkB,CAAC,EAC1B,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,IAAI,GACL,EAAE,IAAI,yCAQF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAqErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/modules.ts"],
4
- "sourcesContent": ["/**\n * routes/modules.ts \u2014 \u6A21\u5757\u76EE\u5F55\u4E0E\u542F\u505C\uFF08\u57FA\u4E8E modules/catalog.json + module-lock.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/modules \u2014 \u6A21\u5757\u5217\u8868\uFF08\u542B enable \u72B6\u6001\uFF09\n * GET /api/sfmc/modules/catalog \u2014 \u539F\u59CB catalog \u5217\u8868\n * GET /api/sfmc/modules/:key \u2014 \u5355\u4E2A\u6A21\u5757\u8BE6\u60C5\n * PATCH/PUT /api/sfmc/modules/:key \u2014 \u8BBE\u7F6E enabled\n * POST /api/sfmc/modules/:key/enable \u2014 \u542F\u7528\u6A21\u5757\n * POST /api/sfmc/modules/:key/disable \u2014 \u7981\u7528\u6A21\u5757\n */\n\ninterface Deps {\n loadModuleCatalog: () => unknown[];\n buildModuleList: () => Array<Record<string, unknown>>;\n resolveModuleByKey: (key: string) => { id: string; canDisable: boolean } | null;\n setModuleEnabled: (\n module: { id: string; canDisable: boolean },\n enabled: boolean\n ) => void;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\nfunction createModuleRoutes({\n loadModuleCatalog,\n buildModuleList,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n}: Deps) {\n return async function handleModuleRoute({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params; // unused\n if (path === \"/api/sfmc/modules/catalog\") {\n if (method === \"GET\") {\n json(res, { modules: loadModuleCatalog() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n if (path === \"/api/sfmc/modules\") {\n if (method === \"GET\") json(res, { modules: buildModuleList() });\n else json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (!path.startsWith(\"/api/sfmc/modules/\")) return false;\n\n const rest = path.slice(\"/api/sfmc/modules/\".length);\n const [rawKey, action] = rest.split(\"/\");\n const module = resolveModuleByKey(decodeURIComponent(rawKey ?? \"\"));\n if (!module) {\n json(res, { success: false, error: \"module_not_found\" }, 404);\n return true;\n }\n\n const current = () => buildModuleList().find((entry) => entry.id === module.id);\n if (!action && method === \"GET\") {\n json(res, { module: current() ?? null });\n } else if (!action && (method === \"PATCH\" || method === \"PUT\")) {\n const { enabled } = await body(req);\n if (!enabled && !module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n try {\n setModuleEnabled(module, !!enabled);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"enable\" && method === \"POST\") {\n try {\n setModuleEnabled(module, true);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"disable\" && method === \"POST\") {\n if (!module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n setModuleEnabled(module, false);\n json(res, { success: true, module: current() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n };\n}\n\nexport { createModuleRoutes };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/modules.ts \u2014 \u6A21\u5757\u76EE\u5F55\u4E0E\u542F\u505C\uFF08\u57FA\u4E8E modules/catalog.json + module-lock.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/modules \u2014 \u6A21\u5757\u5217\u8868\uFF08\u542B enable \u72B6\u6001\uFF09\n * GET /api/sfmc/modules/catalog \u2014 \u539F\u59CB catalog \u5217\u8868\n * GET /api/sfmc/modules/:key \u2014 \u5355\u4E2A\u6A21\u5757\u8BE6\u60C5\n * PATCH/PUT /api/sfmc/modules/:key \u2014 \u8BBE\u7F6E enabled\n * POST /api/sfmc/modules/:key/enable \u2014 \u542F\u7528\u6A21\u5757\n * POST /api/sfmc/modules/:key/disable \u2014 \u7981\u7528\u6A21\u5757\n */\n\ninterface Deps {\n loadModuleCatalog: () => unknown[];\n buildModuleList: () => Array<Record<string, unknown>>;\n resolveModuleByKey: (key: string) => { id: string; canDisable: boolean } | null;\n setModuleEnabled: (\n module: { id: string; canDisable: boolean },\n enabled: boolean\n ) => void;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\n/**\n * \u521B\u5EFA\u6A21\u5757\u76EE\u5F55\u68C0\u7D22\u4E0E\u542F\u505C\u7BA1\u63A7\u8DEF\u7531\u5904\u7406\u5668\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5B57\u5178\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nfunction createModuleRoutes({\n loadModuleCatalog,\n buildModuleList,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n}: Deps) {\n\n return async function handleModuleRoute({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params; // unused\n if (path === \"/api/sfmc/modules/catalog\") {\n if (method === \"GET\") {\n json(res, { modules: loadModuleCatalog() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n if (path === \"/api/sfmc/modules\") {\n if (method === \"GET\") json(res, { modules: buildModuleList() });\n else json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (!path.startsWith(\"/api/sfmc/modules/\")) return false;\n\n const rest = path.slice(\"/api/sfmc/modules/\".length);\n const [rawKey, action] = rest.split(\"/\");\n const module = resolveModuleByKey(decodeURIComponent(rawKey ?? \"\"));\n if (!module) {\n json(res, { success: false, error: \"module_not_found\" }, 404);\n return true;\n }\n\n const current = () => buildModuleList().find((entry) => entry.id === module.id);\n if (!action && method === \"GET\") {\n json(res, { module: current() ?? null });\n } else if (!action && (method === \"PATCH\" || method === \"PUT\")) {\n const { enabled } = await body(req);\n if (!enabled && !module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n try {\n setModuleEnabled(module, !!enabled);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"enable\" && method === \"POST\") {\n try {\n setModuleEnabled(module, true);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"disable\" && method === \"POST\") {\n if (!module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n setModuleEnabled(module, false);\n json(res, { success: true, module: current() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n };\n}\n\nexport { createModuleRoutes };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * routes/qq-bind.ts — QQ↔MC 身份绑定(平台 HTTP)
3
+ *
4
+ * POST /api/sfmc/qq/bind/request — QQ 侧申请短码
5
+ * POST /api/sfmc/qq/bind/confirm — 游戏侧确认
6
+ * POST /api/sfmc/qq/bind/unbind — 解绑
7
+ * GET /api/sfmc/qq/bind/me — 查询
8
+ */
9
+ import type { QueryFn } from "../lib/sqlite.js";
10
+ declare const CODE_TTL_MS: number;
11
+ interface Deps {
12
+ query: QueryFn;
13
+ body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
14
+ json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
15
+ }
16
+ declare function createQqBindRoutes({ query, body, json }: Deps): ({ path, method, params, req, res, }: {
17
+ path: string;
18
+ method: string;
19
+ params: URLSearchParams;
20
+ req: import("http").IncomingMessage;
21
+ res: import("http").ServerResponse;
22
+ }) => Promise<boolean>;
23
+ export { createQqBindRoutes, CODE_TTL_MS };
24
+ //# sourceMappingURL=qq-bind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-bind.d.ts","sourceRoot":"","sources":["../../src/routes/qq-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,QAAA,MAAM,WAAW,QAAgB,CAAC;AAGlC,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpG;AAQD,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,yCAOlD;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAsLrB;AAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,179 @@
1
+ import { randomInt } from "node:crypto";
2
+ import { SQL } from "sql-template-strings";
3
+ const CODE_TTL_MS = 5 * 60 * 1e3;
4
+ const CODE_LEN = 6;
5
+ function genCode() {
6
+ let s = "";
7
+ for (let i = 0; i < CODE_LEN; i++) s += String(randomInt(0, 10));
8
+ return s;
9
+ }
10
+ function createQqBindRoutes({ query, body, json }) {
11
+ return async function handle({
12
+ path,
13
+ method,
14
+ params,
15
+ req,
16
+ res
17
+ }) {
18
+ if (!path.startsWith("/api/sfmc/qq/bind")) return false;
19
+ if (path === "/api/sfmc/qq/bind/me" && method === "GET") {
20
+ const openid = String(params.get("openid") ?? "").trim();
21
+ const xuid = String(params.get("xuid") ?? "").trim();
22
+ if (!openid && !xuid) {
23
+ json(res, { success: false, error: "openid_or_xuid_required" }, 400);
24
+ return true;
25
+ }
26
+ const rows = openid ? query(SQL`SELECT * FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`) : query(SQL`SELECT * FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`);
27
+ const row = rows[0] ?? null;
28
+ json(res, {
29
+ success: true,
30
+ bound: !!row,
31
+ binding: row ? {
32
+ qq_user_openid: row.qq_user_openid,
33
+ player_xuid: row.player_xuid,
34
+ player_name: row.player_name,
35
+ qq_backend: row.qq_backend,
36
+ bound_at: row.bound_at
37
+ } : null
38
+ });
39
+ return true;
40
+ }
41
+ if (path === "/api/sfmc/qq/bind/request" && method === "POST") {
42
+ const data = await body(req);
43
+ const openid = String(data.openid ?? data.qq_user_openid ?? "").trim();
44
+ const backend = String(data.qq_backend ?? "official").trim() || "official";
45
+ if (!openid) {
46
+ json(res, { success: false, error: "openid_required" }, 400);
47
+ return true;
48
+ }
49
+ const existing = query(
50
+ SQL`SELECT player_name FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`
51
+ );
52
+ if (existing[0]) {
53
+ json(
54
+ res,
55
+ {
56
+ success: false,
57
+ error: "already_bound",
58
+ player_name: existing[0].player_name
59
+ },
60
+ 409
61
+ );
62
+ return true;
63
+ }
64
+ const now = Date.now();
65
+ query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid} OR expires_at < ${now}`);
66
+ let code = "";
67
+ for (let attempt = 0; attempt < 8; attempt++) {
68
+ const candidate = genCode();
69
+ const clash = query(
70
+ SQL`SELECT 1 AS ok FROM sfmc_qq_bind_pending WHERE code = ${candidate} LIMIT 1`
71
+ );
72
+ if (clash.length === 0) {
73
+ code = candidate;
74
+ break;
75
+ }
76
+ }
77
+ if (!code) {
78
+ json(res, { success: false, error: "code_gen_failed" }, 500);
79
+ return true;
80
+ }
81
+ const expiresAt = now + CODE_TTL_MS;
82
+ query(
83
+ SQL`INSERT INTO sfmc_qq_bind_pending (code, qq_user_openid, qq_backend, created_at, expires_at)
84
+ VALUES (${code}, ${openid}, ${backend}, ${now}, ${expiresAt})`
85
+ );
86
+ json(res, { success: true, code, expires_at: expiresAt, ttl_ms: CODE_TTL_MS });
87
+ return true;
88
+ }
89
+ if (path === "/api/sfmc/qq/bind/confirm" && method === "POST") {
90
+ const data = await body(req);
91
+ const code = String(data.code ?? "").trim();
92
+ const xuid = String(data.xuid ?? data.player_xuid ?? "").trim();
93
+ const name = String(data.name ?? data.player_name ?? "").trim();
94
+ if (!code || !xuid) {
95
+ json(res, { success: false, error: "code_and_xuid_required" }, 400);
96
+ return true;
97
+ }
98
+ const now = Date.now();
99
+ const pending = query(
100
+ SQL`SELECT * FROM sfmc_qq_bind_pending WHERE code = ${code} LIMIT 1`
101
+ );
102
+ const row = pending[0];
103
+ if (!row) {
104
+ json(res, { success: false, error: "invalid_code" }, 400);
105
+ return true;
106
+ }
107
+ if (Number(row.expires_at) < now) {
108
+ query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE code = ${code}`);
109
+ json(res, { success: false, error: "code_expired" }, 400);
110
+ return true;
111
+ }
112
+ const byOpenid = query(
113
+ SQL`SELECT player_xuid FROM sfmc_qq_bindings WHERE qq_user_openid = ${row.qq_user_openid} LIMIT 1`
114
+ );
115
+ if (byOpenid[0] && byOpenid[0].player_xuid !== xuid) {
116
+ json(res, { success: false, error: "qq_already_bound", hint: "\u8BF7\u5148\u5728 QQ \u4FA7\u89E3\u7ED1" }, 409);
117
+ return true;
118
+ }
119
+ const byXuid = query(
120
+ SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`
121
+ );
122
+ if (byXuid[0] && byXuid[0].qq_user_openid !== row.qq_user_openid) {
123
+ json(res, { success: false, error: "player_already_bound", hint: "\u8BF7\u5148\u89E3\u7ED1\u5F53\u524D QQ" }, 409);
124
+ return true;
125
+ }
126
+ query(SQL`DELETE FROM sfmc_qq_bindings WHERE qq_user_openid = ${row.qq_user_openid} OR player_xuid = ${xuid}`);
127
+ query(
128
+ SQL`INSERT INTO sfmc_qq_bindings (qq_user_openid, player_xuid, player_name, qq_backend, bound_at)
129
+ VALUES (${row.qq_user_openid}, ${xuid}, ${name}, ${row.qq_backend}, ${now})`
130
+ );
131
+ query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE code = ${code}`);
132
+ json(res, {
133
+ success: true,
134
+ binding: {
135
+ qq_user_openid: row.qq_user_openid,
136
+ player_xuid: xuid,
137
+ player_name: name,
138
+ qq_backend: row.qq_backend,
139
+ bound_at: now
140
+ }
141
+ });
142
+ return true;
143
+ }
144
+ if (path === "/api/sfmc/qq/bind/unbind" && method === "POST") {
145
+ const data = await body(req);
146
+ const openid = String(data.openid ?? data.qq_user_openid ?? "").trim();
147
+ const xuid = String(data.xuid ?? data.player_xuid ?? "").trim();
148
+ if (!openid && !xuid) {
149
+ json(res, { success: false, error: "openid_or_xuid_required" }, 400);
150
+ return true;
151
+ }
152
+ let removed = 0;
153
+ if (openid) {
154
+ const before = query(
155
+ SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid}`
156
+ );
157
+ query(SQL`DELETE FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid}`);
158
+ query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid}`);
159
+ removed += before.length;
160
+ }
161
+ if (xuid) {
162
+ const before = query(
163
+ SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE player_xuid = ${xuid}`
164
+ );
165
+ query(SQL`DELETE FROM sfmc_qq_bindings WHERE player_xuid = ${xuid}`);
166
+ removed += before.length;
167
+ }
168
+ json(res, { success: true, unbound: removed > 0, removed });
169
+ return true;
170
+ }
171
+ json(res, { success: false, error: "not_found" }, 404);
172
+ return true;
173
+ };
174
+ }
175
+ export {
176
+ CODE_TTL_MS,
177
+ createQqBindRoutes
178
+ };
179
+ //# sourceMappingURL=qq-bind.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/routes/qq-bind.ts"],
4
+ "sourcesContent": ["/**\n * routes/qq-bind.ts \u2014 QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0 HTTP\uFF09\n *\n * POST /api/sfmc/qq/bind/request \u2014 QQ \u4FA7\u7533\u8BF7\u77ED\u7801\n * POST /api/sfmc/qq/bind/confirm \u2014 \u6E38\u620F\u4FA7\u786E\u8BA4\n * POST /api/sfmc/qq/bind/unbind \u2014 \u89E3\u7ED1\n * GET /api/sfmc/qq/bind/me \u2014 \u67E5\u8BE2\n */\n\nimport { randomInt } from \"node:crypto\";\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nconst CODE_TTL_MS = 5 * 60 * 1000;\nconst CODE_LEN = 6;\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\nfunction genCode(): string {\n let s = \"\";\n for (let i = 0; i < CODE_LEN; i++) s += String(randomInt(0, 10));\n return s;\n}\n\nfunction createQqBindRoutes({ query, body, json }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (!path.startsWith(\"/api/sfmc/qq/bind\")) return false;\n\n if (path === \"/api/sfmc/qq/bind/me\" && method === \"GET\") {\n const openid = String(params.get(\"openid\") ?? \"\").trim();\n const xuid = String(params.get(\"xuid\") ?? \"\").trim();\n if (!openid && !xuid) {\n json(res, { success: false, error: \"openid_or_xuid_required\" }, 400);\n return true;\n }\n const rows = openid\n ? (query(SQL`SELECT * FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`) as Array<\n Record<string, unknown>\n >)\n : (query(SQL`SELECT * FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`) as Array<\n Record<string, unknown>\n >);\n const row = rows[0] ?? null;\n json(res, {\n success: true,\n bound: !!row,\n binding: row\n ? {\n qq_user_openid: row.qq_user_openid,\n player_xuid: row.player_xuid,\n player_name: row.player_name,\n qq_backend: row.qq_backend,\n bound_at: row.bound_at,\n }\n : null,\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/request\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.qq_user_openid ?? \"\").trim();\n const backend = String(data.qq_backend ?? \"official\").trim() || \"official\";\n if (!openid) {\n json(res, { success: false, error: \"openid_required\" }, 400);\n return true;\n }\n // \u5DF2\u7ED1\u5B9A\u5219\u62D2\u7EDD\u518D\u7533\u8BF7\uFF08\u987B\u5148\u89E3\u7ED1\uFF09\n const existing = query(\n SQL`SELECT player_name FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`\n ) as Array<{ player_name: string }>;\n if (existing[0]) {\n json(\n res,\n {\n success: false,\n error: \"already_bound\",\n player_name: existing[0].player_name,\n },\n 409\n );\n return true;\n }\n\n const now = Date.now();\n query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid} OR expires_at < ${now}`);\n\n let code = \"\";\n for (let attempt = 0; attempt < 8; attempt++) {\n const candidate = genCode();\n const clash = query(\n SQL`SELECT 1 AS ok FROM sfmc_qq_bind_pending WHERE code = ${candidate} LIMIT 1`\n ) as unknown[];\n if (clash.length === 0) {\n code = candidate;\n break;\n }\n }\n if (!code) {\n json(res, { success: false, error: \"code_gen_failed\" }, 500);\n return true;\n }\n\n const expiresAt = now + CODE_TTL_MS;\n query(\n SQL`INSERT INTO sfmc_qq_bind_pending (code, qq_user_openid, qq_backend, created_at, expires_at)\n VALUES (${code}, ${openid}, ${backend}, ${now}, ${expiresAt})`\n );\n json(res, { success: true, code, expires_at: expiresAt, ttl_ms: CODE_TTL_MS });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/confirm\" && method === \"POST\") {\n const data = await body(req);\n const code = String(data.code ?? \"\").trim();\n const xuid = String(data.xuid ?? data.player_xuid ?? \"\").trim();\n const name = String(data.name ?? data.player_name ?? \"\").trim();\n if (!code || !xuid) {\n json(res, { success: false, error: \"code_and_xuid_required\" }, 400);\n return true;\n }\n\n const now = Date.now();\n const pending = query(\n SQL`SELECT * FROM sfmc_qq_bind_pending WHERE code = ${code} LIMIT 1`\n ) as Array<{\n code: string;\n qq_user_openid: string;\n qq_backend: string;\n expires_at: number;\n }>;\n const row = pending[0];\n if (!row) {\n json(res, { success: false, error: \"invalid_code\" }, 400);\n return true;\n }\n if (Number(row.expires_at) < now) {\n query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE code = ${code}`);\n json(res, { success: false, error: \"code_expired\" }, 400);\n return true;\n }\n\n const byOpenid = query(\n SQL`SELECT player_xuid FROM sfmc_qq_bindings WHERE qq_user_openid = ${row.qq_user_openid} LIMIT 1`\n ) as Array<{ player_xuid: string }>;\n if (byOpenid[0] && byOpenid[0].player_xuid !== xuid) {\n json(res, { success: false, error: \"qq_already_bound\", hint: \"\u8BF7\u5148\u5728 QQ \u4FA7\u89E3\u7ED1\" }, 409);\n return true;\n }\n const byXuid = query(\n SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`\n ) as Array<{ qq_user_openid: string }>;\n if (byXuid[0] && byXuid[0].qq_user_openid !== row.qq_user_openid) {\n json(res, { success: false, error: \"player_already_bound\", hint: \"\u8BF7\u5148\u89E3\u7ED1\u5F53\u524D QQ\" }, 409);\n return true;\n }\n\n query(SQL`DELETE FROM sfmc_qq_bindings WHERE qq_user_openid = ${row.qq_user_openid} OR player_xuid = ${xuid}`);\n query(\n SQL`INSERT INTO sfmc_qq_bindings (qq_user_openid, player_xuid, player_name, qq_backend, bound_at)\n VALUES (${row.qq_user_openid}, ${xuid}, ${name}, ${row.qq_backend}, ${now})`\n );\n query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE code = ${code}`);\n json(res, {\n success: true,\n binding: {\n qq_user_openid: row.qq_user_openid,\n player_xuid: xuid,\n player_name: name,\n qq_backend: row.qq_backend,\n bound_at: now,\n },\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/unbind\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.qq_user_openid ?? \"\").trim();\n const xuid = String(data.xuid ?? data.player_xuid ?? \"\").trim();\n if (!openid && !xuid) {\n json(res, { success: false, error: \"openid_or_xuid_required\" }, 400);\n return true;\n }\n let removed = 0;\n if (openid) {\n const before = query(\n SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid}`\n ) as unknown[];\n query(SQL`DELETE FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid}`);\n query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid}`);\n removed += before.length;\n }\n if (xuid) {\n const before = query(\n SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE player_xuid = ${xuid}`\n ) as unknown[];\n query(SQL`DELETE FROM sfmc_qq_bindings WHERE player_xuid = ${xuid}`);\n removed += before.length;\n }\n json(res, { success: true, unbound: removed > 0, removed });\n return true;\n }\n\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n };\n}\n\nexport { createQqBindRoutes, CODE_TTL_MS };\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * routes/qq-events.ts — MC 事件入站
3
+ *
4
+ * POST /api/sfmc/qq/events — 单条或 { events: [...] }(≤100)
5
+ */
6
+ import type { QqEventsAggregator } from "../domain/qq-events.js";
7
+ interface Deps {
8
+ body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
9
+ json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
10
+ aggregator: QqEventsAggregator;
11
+ }
12
+ declare function createQqEventsRoutes({ body, json, aggregator }: Deps): ({ path, method, req, res, }: {
13
+ path: string;
14
+ method: string;
15
+ params: URLSearchParams;
16
+ req: import("http").IncomingMessage;
17
+ res: import("http").ServerResponse;
18
+ }) => Promise<boolean>;
19
+ export { createQqEventsRoutes };
20
+ //# sourceMappingURL=qq-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/routes/qq-events.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,wBAAwB,CAAC;AAGjF,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAaD,iBAAS,oBAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,iCAMzD;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAwBrB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { normalizeEventPayload } from "../domain/qq-events.js";
2
+ function collectPayloads(data) {
3
+ if (Array.isArray(data.events)) {
4
+ return data.events.map((x) => normalizeEventPayload(x)).filter((x) => x != null);
5
+ }
6
+ const one = normalizeEventPayload(data);
7
+ return one ? [one] : [];
8
+ }
9
+ function createQqEventsRoutes({ body, json, aggregator }) {
10
+ return async function handle({
11
+ path,
12
+ method,
13
+ req,
14
+ res
15
+ }) {
16
+ if (path !== "/api/sfmc/qq/events") return false;
17
+ if (method !== "POST") {
18
+ json(res, { success: false, error: "not_found" }, 404);
19
+ return true;
20
+ }
21
+ const data = await body(req);
22
+ if (Array.isArray(data.events) && data.events.length > 100) {
23
+ json(res, { success: false, error: "too many requests" }, 413);
24
+ return true;
25
+ }
26
+ const payloads = collectPayloads(data);
27
+ if (payloads.length === 0) {
28
+ json(res, { success: false, error: "invalid" }, 400);
29
+ return true;
30
+ }
31
+ const result = aggregator.ingestMany(payloads);
32
+ json(res, { success: true, ...result });
33
+ return true;
34
+ };
35
+ }
36
+ export {
37
+ createQqEventsRoutes
38
+ };
39
+ //# sourceMappingURL=qq-events.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/routes/qq-events.ts"],
4
+ "sourcesContent": ["/**\n * routes/qq-events.ts \u2014 MC \u4E8B\u4EF6\u5165\u7AD9\n *\n * POST /api/sfmc/qq/events \u2014 \u5355\u6761\u6216 { events: [...] }\uFF08\u2264100\uFF09\n */\n\nimport type { QqEventsAggregator, QqEventPayload } from \"../domain/qq-events.js\";\nimport { normalizeEventPayload } from \"../domain/qq-events.js\";\n\ninterface Deps {\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n aggregator: QqEventsAggregator;\n}\n\nfunction collectPayloads(data: Record<string, unknown>): QqEventPayload[] {\n if (Array.isArray(data.events)) {\n return (data.events as unknown[])\n .map((x) => normalizeEventPayload(x))\n .filter((x): x is QqEventPayload => x != null);\n }\n // \u5355\u6761\uFF1A\u9876\u5C42\u5373\u4E8B\u4EF6\n const one = normalizeEventPayload(data);\n return one ? [one] : [];\n}\n\nfunction createQqEventsRoutes({ body, json, aggregator }: Deps) {\n return async function handle({\n path,\n method,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path !== \"/api/sfmc/qq/events\") return false;\n\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n const data = await body(req);\n if (Array.isArray(data.events) && (data.events as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n\n const payloads = collectPayloads(data);\n if (payloads.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n\n const result = aggregator.ingestMany(payloads);\n json(res, { success: true, ...result });\n return true;\n };\n}\n\nexport { createQqEventsRoutes };\n"],
5
+ "mappings": "AAOA,SAAS,6BAA6B;AAQtC,SAAS,gBAAgB,MAAiD;AACxE,MAAI,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC9B,WAAQ,KAAK,OACV,IAAI,CAAC,MAAM,sBAAsB,CAAC,CAAC,EACnC,OAAO,CAAC,MAA2B,KAAK,IAAI;AAAA,EACjD;AAEA,QAAM,MAAM,sBAAsB,IAAI;AACtC,SAAO,MAAM,CAAC,GAAG,IAAI,CAAC;AACxB;AAEA,SAAS,qBAAqB,EAAE,MAAM,MAAM,WAAW,GAAS;AAC9D,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,sBAAuB,QAAO;AAE3C,QAAI,WAAW,QAAQ;AACrB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,QAAI,MAAM,QAAQ,KAAK,MAAM,KAAM,KAAK,OAAqB,SAAS,KAAK;AACzE,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,GAAG,GAAG;AAC7D,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,gBAAgB,IAAI;AACrC,QAAI,SAAS,WAAW,GAAG;AACzB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,UAAU,GAAG,GAAG;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,WAAW,WAAW,QAAQ;AAC7C,SAAK,KAAK,EAAE,SAAS,MAAM,GAAG,OAAO,CAAC;AACtC,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * routes/qq-join.ts — 入服审批状态机 + BDS 生效队列(不写 allowlist 文件)
3
+ *
4
+ * GET /api/sfmc/qq/join/settings
5
+ * POST /api/sfmc/qq/join/settings
6
+ * POST /api/sfmc/qq/join/request
7
+ * POST /api/sfmc/qq/join/decide
8
+ * GET /api/sfmc/qq/join/pending
9
+ * GET /api/sfmc/qq/join/apply-queue
10
+ * POST /api/sfmc/qq/join/applied
11
+ * POST /api/sfmc/qq/admin/kick
12
+ * GET /api/sfmc/qq/admin/action-queue
13
+ * POST /api/sfmc/qq/admin/action-done
14
+ */
15
+ import type { QueryFn } from "../lib/sqlite.js";
16
+ export type JoinFeatureFlags = {
17
+ /** 入服白名单总开关 */
18
+ allowlistEnabled: boolean;
19
+ /** 是否需要管理员审批 */
20
+ requireApproval: boolean;
21
+ /**
22
+ * 是否将 QQ 群主/群管理员视作 SFMC 管理员。
23
+ * 仅 configs/qq_link.json 可改,API/群聊不可写。
24
+ */
25
+ treatGroupAdminsAsAdmins: boolean;
26
+ };
27
+ interface Deps {
28
+ query: QueryFn;
29
+ body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
30
+ json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
31
+ /** 当前管理员 openid 列表(来自 qq_config) */
32
+ getAdminOpenids: () => string[];
33
+ /** 读取入服开关(应读最新 qq_config) */
34
+ getJoinFlags: () => JoinFeatureFlags;
35
+ /** 写回入服开关(持久化 + 内存) */
36
+ setJoinFlags: (partial: Partial<JoinFeatureFlags>) => JoinFeatureFlags;
37
+ }
38
+ declare function createQqJoinRoutes({ query, body, json, getAdminOpenids, getJoinFlags, setJoinFlags }: Deps): ({ path, method, params, req, res, }: {
39
+ path: string;
40
+ method: string;
41
+ params: URLSearchParams;
42
+ req: import("http").IncomingMessage;
43
+ res: import("http").ServerResponse;
44
+ }) => Promise<boolean>;
45
+ export { createQqJoinRoutes };
46
+ //# sourceMappingURL=qq-join.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-join.d.ts","sourceRoot":"","sources":["../../src/routes/qq-join.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,wBAAwB,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,oCAAoC;IACpC,eAAe,EAAE,MAAM,MAAM,EAAE,CAAC;IAChC,6BAA6B;IAC7B,YAAY,EAAE,MAAM,gBAAgB,CAAC;IACrC,uBAAuB;IACvB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,gBAAgB,CAAC;CACxE;AA4BD,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,IAAI,yCAO/F;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAkRrB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}