@sfmc-bds/db-server 0.2.0-beta.1 → 0.2.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +663 -0
- package/dist/domain/bridge.d.ts +57 -23
- package/dist/domain/bridge.d.ts.map +1 -1
- package/dist/domain/bridge.js +77 -6
- package/dist/domain/bridge.js.map +2 -2
- package/dist/domain/bridge.test.d.ts +2 -0
- package/dist/domain/bridge.test.d.ts.map +1 -0
- package/dist/domain/bridge.test.js +87 -0
- package/dist/domain/bridge.test.js.map +7 -0
- package/dist/domain/economy.d.ts +38 -7
- package/dist/domain/economy.d.ts.map +1 -1
- package/dist/domain/economy.js.map +2 -2
- package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
- package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
- package/dist/domain/messages-bridge-filter.test.js +20 -0
- package/dist/domain/messages-bridge-filter.test.js.map +7 -0
- package/dist/domain/player-session.d.ts +8 -0
- package/dist/domain/player-session.d.ts.map +1 -0
- package/dist/domain/player-session.js +16 -0
- package/dist/domain/player-session.js.map +7 -0
- package/dist/domain/player-session.test.d.ts +2 -0
- package/dist/domain/player-session.test.d.ts.map +1 -0
- package/dist/domain/player-session.test.js +33 -0
- package/dist/domain/player-session.test.js.map +7 -0
- package/dist/domain/qq-bind-status.test.d.ts +2 -0
- package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
- package/dist/domain/qq-bind-status.test.js +211 -0
- package/dist/domain/qq-bind-status.test.js.map +7 -0
- package/dist/domain/qq-events.d.ts +84 -0
- package/dist/domain/qq-events.d.ts.map +1 -0
- package/dist/domain/qq-events.js +193 -0
- package/dist/domain/qq-events.js.map +7 -0
- package/dist/domain/qq-events.test.d.ts +5 -0
- package/dist/domain/qq-events.test.d.ts.map +1 -0
- package/dist/domain/qq-events.test.js +170 -0
- package/dist/domain/qq-events.test.js.map +7 -0
- package/dist/domain/qq-join.test.d.ts +2 -0
- package/dist/domain/qq-join.test.d.ts.map +1 -0
- package/dist/domain/qq-join.test.js +353 -0
- package/dist/domain/qq-join.test.js.map +7 -0
- package/dist/domain/schema.d.ts +15 -18
- package/dist/domain/schema.d.ts.map +1 -1
- package/dist/domain/schema.js +104 -453
- package/dist/domain/schema.js.map +2 -2
- package/dist/domain/system-status.d.ts +75 -0
- package/dist/domain/system-status.d.ts.map +1 -0
- package/dist/domain/system-status.js +181 -0
- package/dist/domain/system-status.js.map +7 -0
- package/dist/domain/system-status.test.d.ts +5 -0
- package/dist/domain/system-status.test.d.ts.map +1 -0
- package/dist/domain/system-status.test.js +19 -0
- package/dist/domain/system-status.test.js.map +7 -0
- package/dist/domain/transaction.d.ts +5 -4
- package/dist/domain/transaction.d.ts.map +1 -1
- package/dist/domain/transaction.js.map +2 -2
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +57 -9
- package/dist/env.js.map +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -38
- package/dist/index.js.map +2 -2
- package/dist/lib/idempotency-store.d.ts +17 -11
- package/dist/lib/idempotency-store.d.ts.map +1 -1
- package/dist/lib/idempotency-store.js.map +2 -2
- package/dist/lib/idempotency.d.ts +3 -4
- package/dist/lib/idempotency.d.ts.map +1 -1
- package/dist/lib/idempotency.js.map +2 -2
- package/dist/lib/log.d.ts.map +1 -1
- package/dist/lib/log.js +3 -6
- package/dist/lib/log.js.map +2 -2
- package/dist/lib/module-state.d.ts +38 -8
- package/dist/lib/module-state.d.ts.map +1 -1
- package/dist/lib/module-state.js.map +2 -2
- package/dist/lib/order-by.d.ts +8 -6
- package/dist/lib/order-by.d.ts.map +1 -1
- package/dist/lib/order-by.js.map +2 -2
- package/dist/lib/sql-helpers.d.ts +25 -13
- package/dist/lib/sql-helpers.d.ts.map +1 -1
- package/dist/lib/sql-helpers.js.map +2 -2
- package/dist/lib/sqlite.d.ts +14 -5
- package/dist/lib/sqlite.d.ts.map +1 -1
- package/dist/lib/sqlite.js.map +2 -2
- package/dist/manifest-loader.d.ts +5 -2
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +16 -6
- package/dist/manifest-loader.js.map +2 -2
- package/dist/manifest.d.ts +1 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +13 -8
- package/dist/manifest.js.map +2 -2
- package/dist/module-auth.d.ts.map +1 -1
- package/dist/module-auth.js +2 -6
- package/dist/module-auth.js.map +2 -2
- package/dist/permission-gate.d.ts.map +1 -1
- package/dist/permission-gate.js +8 -2
- package/dist/permission-gate.js.map +2 -2
- package/dist/project-root.d.ts +6 -0
- package/dist/project-root.d.ts.map +1 -0
- package/dist/project-root.js +9 -0
- package/dist/project-root.js.map +7 -0
- package/dist/routes/_shared.d.ts +14 -5
- package/dist/routes/_shared.d.ts.map +1 -1
- package/dist/routes/_shared.js.map +2 -2
- package/dist/routes/config.d.ts +8 -11
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +17 -23
- package/dist/routes/config.js.map +2 -2
- package/dist/routes/db-routes.d.ts +8 -2
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js +5 -12
- package/dist/routes/db-routes.js.map +2 -2
- package/dist/routes/messages.d.ts +6 -2
- package/dist/routes/messages.d.ts.map +1 -1
- package/dist/routes/messages.js +17 -3
- package/dist/routes/messages.js.map +2 -2
- package/dist/routes/module-config-routes.d.ts +13 -9
- package/dist/routes/module-config-routes.d.ts.map +1 -1
- package/dist/routes/module-config-routes.js.map +2 -2
- package/dist/routes/modules.d.ts +6 -0
- package/dist/routes/modules.d.ts.map +1 -1
- package/dist/routes/modules.js.map +2 -2
- package/dist/routes/qq-bind.d.ts +24 -0
- package/dist/routes/qq-bind.d.ts.map +1 -0
- package/dist/routes/qq-bind.js +179 -0
- package/dist/routes/qq-bind.js.map +7 -0
- package/dist/routes/qq-events.d.ts +20 -0
- package/dist/routes/qq-events.d.ts.map +1 -0
- package/dist/routes/qq-events.js +39 -0
- package/dist/routes/qq-events.js.map +7 -0
- package/dist/routes/qq-join.d.ts +46 -0
- package/dist/routes/qq-join.d.ts.map +1 -0
- package/dist/routes/qq-join.js +277 -0
- package/dist/routes/qq-join.js.map +7 -0
- package/dist/routes/service-routes.d.ts +6 -0
- package/dist/routes/service-routes.d.ts.map +1 -1
- package/dist/routes/service-routes.js.map +2 -2
- package/dist/routes/status.d.ts +19 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +74 -0
- package/dist/routes/status.js.map +7 -0
- package/dist/runtime.test.js +216 -4
- package/dist/runtime.test.js.map +2 -2
- package/dist/schema-registry.d.ts +17 -16
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +76 -6
- package/dist/schema-registry.js.map +2 -2
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js.map +2 -2
- package/dist/service-registry.d.ts +39 -9
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +30 -2
- package/dist/service-registry.js.map +2 -2
- package/dist/services/builtin-handlers.d.ts +27 -7
- package/dist/services/builtin-handlers.d.ts.map +1 -1
- package/dist/services/builtin-handlers.js.map +2 -2
- package/dist/tx-runner.d.ts +48 -9
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +99 -27
- package/dist/tx-runner.js.map +2 -2
- package/package.json +26 -24
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/config.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/config.ts \u2014 \u914D\u7F6E\u8DEF\u7531\uFF08\u76F4\u63A5\u8BFB\u53D6 configs/*.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/configs/all \u2014 SAPI \u542F\u52A8\u65F6\u4E00\u6B21\u6027\u62C9\u53D6\u6240\u6709\u914D\u7F6E\n * GET /api/sfmc/settings \u2014 \u5E73\u94FA settings.json\n * GET /api/sfmc/settings/:key \u2014 \u542B land:* / bridge_channel_id \u7B49 fallback \u67E5\u8BE2\n * GET /api/sfmc/areas \u2014 areas.json\n * GET /api/sfmc/permissions \u2014 permissions.json\n * GET /api/sfmc/banned_items \u2014 banned_items.json\n * GET /api/sfmc/clean \u2014 clean.json\n * GET /api/sfmc/grids \u2014 grids.json\n * GET /api/sfmc/peace_filters \u2014 peace_filters.json\n * GET /api/sfmc/qa \u2014 questions.json\n */\n\nimport { configPath, readJson, type ConfigName } from \"@sfmc-bds/sdk/node/config\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n projectRoot: string;\n /**\n * \u6CE8\u5165\u6A21\u5757\u5217\u8868(\u4E0E GET /api/sfmc/modules \u540C\u6E90 \u2014 DRY)\u3002\n * \u52FF\u5728\u672C\u8DEF\u7531\u518D\u8BFB catalog/lock(DIP:\u9AD8\u5C42\u88C5\u914D,\u8DEF\u7531\u53EA\u6D88\u8D39\u62BD\u8C61)\u3002\n */\n listModules?: () => Array<Record<string, unknown>>;\n /**\n * \u6CE8\u5165\u6A21\u5757 HMAC token \u8868(\u4EC5 loopback \u53EF\u8FBE;\u4F9B SAPI host-bootstrap \u6CE8\u5165\n * set*ModuleContext,\u56E0 SAPI \u4E0D\u80FD\u76F4\u63A5\u8BFB\u4E0E DB \u540C\u76EE\u5F55\u7684 module-tokens.json)\u3002\n */\n getModuleTokens?: () => Record<string, string>;\n}\n\nfunction isMetaKey(k: string): boolean {\n return k === \"$schema\" || String(k).startsWith(\"_\");\n}\n\nfunction stripMetaDeep(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripMetaDeep);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (!isMetaKey(k)) out[k] = stripMetaDeep(val);\n }\n return out;\n }\n return v;\n}\n\nfunction stripMeta(obj: Record<string, unknown> | null): Record<string, unknown> {\n if (!obj || typeof obj !== \"object\" || Array.isArray(obj)) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isMetaKey(k)) out[k] = v;\n }\n return out;\n}\n\nfunction jsonValue(v: unknown): string {\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v ?? \"\");\n}\n\nfunction arrayOrEmpty(v: unknown): unknown[] {\n return Array.isArray(v) ? v : [];\n}\n\nfunction createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }: Deps) {\n /** \u4ED3\u9876\u670D\u52A1 config \u8BFB\u53D6\u7EDF\u4E00\u8D70 SDK;\u95ED\u5305\u6355\u83B7 projectRoot\u3002 */\n const readCfg = (name: ConfigName): unknown => readJson(configPath(projectRoot, name));\n\n function getAllConfigs(): Record<string, unknown> {\n const modules = typeof listModules === \"function\" ? listModules() : [];\n const module_tokens = typeof getModuleTokens === \"function\" ? getModuleTokens() : {};\n /**\n * SAPI ConfigManager \u5951\u7EA6:banned_items \u4E3A string[](\u4E0E GET /banned_items \u540C\u6E90)\u3002\n * \u52FF\u518D\u6620\u5C04\u6210 {item_id} \u2014 \u5426\u5219 filter(typeof s === \"string\") \u4F1A\u5F97\u5230\u7A7A\u7F13\u5B58(LSP)\u3002\n * \u5176\u4F59\u8D44\u6E90\u590D\u7528\u5355\u8D44\u6E90 helpers,\u907F\u514D getAllConfigs \u4E0E\u4E13\u7528\u8DEF\u7531\u53CC\u5199(DRY)\u3002\n */\n return {\n // \u4E0E /api/sfmc/modules \u540C\u6E90;ConfigManager.init \u4E00\u6B21\u62C9\u9F50\u542F\u505C\u6001(DRY)\n modules,\n // loopback-only \u4E0B\u53D1;SAPI \u65E0 fs,\u9760\u6B64\u6CE8\u5165\u6A21\u5757\u8EAB\u4EFD(DIP)\n module_tokens,\n settings: stripMeta(readCfg(\"settings.json\") as Record<string, unknown> | null),\n areas: getAreas(),\n permissions: getPermissions(),\n banned_items: getBannedItems(),\n clean: getClean(),\n grids: getGrids(),\n peace_filters: getPeaceFilters(),\n questions: getQA(),\n };\n }\n\n function getSettingsFlat(): Array<{ key: string; value: string }> {\n const obj = stripMeta(readJson(configPath(projectRoot, \"settings.json\")) as Record<string, unknown> | null);\n return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));\n }\n\n function getSettingByKey(key: string): { value: unknown; source?: string } {\n const settings = (readJson(configPath(projectRoot, \"settings.json\")) as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {\n return { value: settings[key], source: \"settings.json\" };\n }\n if (key === \"bridge_channel_id\") {\n const qq = (readCfg(\"qq_config.json\") as Record<string, unknown> | null) ?? {};\n if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: \"qq_config.json\" };\n }\n if (key.startsWith(\"land:\")) {\n const land = (readCfg(\"land.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {\n return { value: land[key], source: \"land.json\" };\n }\n }\n return { value: null };\n }\n\n function getAreas(): unknown[] {\n return (arrayOrEmpty(readCfg(\"areas.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.module && r.dimension != null)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPermissions(): unknown[] {\n return (arrayOrEmpty(readCfg(\"permissions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.player_name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getBannedItems(): string[] {\n return (arrayOrEmpty(readCfg(\"banned_items.json\")) as Array<string>).filter(\n (i) => typeof i === \"string\" && i && !i.startsWith(\"_\")\n );\n }\n\n function getClean(): { item_max: number; poll_interval: number } {\n const c = stripMetaDeep(readCfg(\"clean.json\") ?? {}) as Record<string, unknown>;\n return { item_max: (c.item_max as number) ?? 192, poll_interval: (c.poll_interval as number) ?? 60 };\n }\n\n function getGrids(): unknown[] {\n return (arrayOrEmpty(readCfg(\"grids.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPeaceFilters(): unknown[] {\n return (arrayOrEmpty(readCfg(\"peace_filters.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.family)\n .map((r) => stripMetaDeep(r));\n }\n\n function getQA(): Array<Record<string, unknown>> {\n return (arrayOrEmpty(readCfg(\"questions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.question)\n .map((r, idx) => {\n const clean = stripMetaDeep(r) as Record<string, unknown>;\n return {\n id: idx + 1,\n weight: clean.weight ?? 1,\n question: clean.question,\n answers: clean.answers ?? [],\n msg_right: clean.msg_right ?? \"\",\n msg_wrong: clean.msg_wrong ?? \"\",\n explanation: clean.explanation ?? \"\",\n min_rank: clean.min_rank ?? null,\n max_rank: clean.max_rank ?? null,\n rewards: clean.rewards ?? [],\n punishments: clean.punishments ?? [],\n };\n });\n }\n\n return async function handleConfigRoute({\n path: requestPath,\n method,\n params,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params;\n if (requestPath === \"/api/sfmc/configs/all\") {\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, getAllConfigs());\n return true;\n }\n if (requestPath === \"/api/sfmc/settings\") {\n if (method === \"GET\") {\n json(res, { settings: getSettingsFlat() });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (requestPath.startsWith(\"/api/sfmc/settings/\")) {\n const key = decodeURIComponent(requestPath.slice(\"/api/sfmc/settings/\".length));\n if (method === \"GET\") {\n json(res, getSettingByKey(key));\n return true;\n }\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n if (requestPath === \"/api/sfmc/areas\") {\n if (method === \"GET\") {\n json(res, { areas: getAreas() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/permissions\") {\n if (method === \"GET\") {\n json(res, { permissions: getPermissions() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/banned_items\") {\n if (method === \"GET\") {\n json(res, { items: getBannedItems() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/clean\") {\n if (method === \"GET\") {\n json(res, { clean: getClean() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/grids\") {\n if (method === \"GET\") {\n json(res, { grids: getGrids() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/peace_filters\") {\n if (method === \"GET\") {\n json(res, { filters: getPeaceFilters() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/qa\") {\n if (method === \"GET\") {\n json(res, { questions: getQA() });\n return true;\n }\n }\n return false;\n };\n}\n\nexport { createConfigRoutes };\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * routes/config.ts \u2014 \u914D\u7F6E\u8DEF\u7531\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/configs/all \u2014 SAPI ConfigManager \u542F\u52A8\u5FEB\u7167\uFF08\u4EC5\u5E73\u53F0\u57DF\uFF09\n * GET /api/sfmc/settings \u2014 \u5E73\u94FA settings.json\uFF08legacy\uFF09\n * GET /api/sfmc/settings/:key \u2014 \u542B land:* / bridge_channel_id \u7B49 fallback\n * GET /api/sfmc/areas|permissions|\u2026 \u2014 legacy \u53EA\u8BFB JSON\uFF08\u6587\u4EF6\u540D\u5BF9 db-server \u4E3A\u9ED1\u7BB1\uFF09\n *\n * DIP\uFF1A\u6A21\u5757/legacy JSON \u4E0D\u8FDB SDK ConfigName\uFF1B\u672C\u8DEF\u7531\u7528 configDir + \u6587\u4EF6\u540D\u62FC\u63A5\u8DEF\u5F84\u3002\n * \u6A21\u5757\u79C1\u6709\u914D\u7F6E\u7684\u8BFB\u5199\u6743\u5A01\u5165\u53E3\u662F module-config-routes\uFF08configs/:configKey\uFF09\u3002\n */\n\nimport { join } from \"node:path\";\nimport { configDir, configPath, readJson, type ConfigName } from \"@sfmc-bds/sdk/node/config\";\n\ninterface Deps {\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n projectRoot: string;\n /**\n * \u6CE8\u5165\u6A21\u5757\u5217\u8868(\u4E0E GET /api/sfmc/modules \u540C\u6E90 \u2014 DRY)\u3002\n * \u52FF\u5728\u672C\u8DEF\u7531\u518D\u8BFB catalog/lock(DIP:\u9AD8\u5C42\u88C5\u914D,\u8DEF\u7531\u53EA\u6D88\u8D39\u62BD\u8C61)\u3002\n */\n listModules?: () => Array<Record<string, unknown>>;\n /**\n * \u6CE8\u5165\u6A21\u5757 HMAC token \u8868(\u4EC5 loopback \u53EF\u8FBE;\u4F9B SAPI host-bootstrap \u6CE8\u5165\n * set*ModuleContext,\u56E0 SAPI \u4E0D\u80FD\u76F4\u63A5\u8BFB\u4E0E DB \u540C\u76EE\u5F55\u7684 module-tokens.json)\u3002\n */\n getModuleTokens?: () => Record<string, string>;\n}\n\nfunction isMetaKey(k: string): boolean {\n return k === \"$schema\" || String(k).startsWith(\"_\");\n}\n\nfunction stripMetaDeep(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripMetaDeep);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n if (!isMetaKey(k)) out[k] = stripMetaDeep(val);\n }\n return out;\n }\n return v;\n}\n\nfunction stripMeta(obj: Record<string, unknown> | null): Record<string, unknown> {\n if (!obj || typeof obj !== \"object\" || Array.isArray(obj)) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isMetaKey(k)) out[k] = v;\n }\n return out;\n}\n\nfunction jsonValue(v: unknown): string {\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v ?? \"\");\n}\n\nfunction arrayOrEmpty(v: unknown): unknown[] {\n return Array.isArray(v) ? v : [];\n}\n\nfunction createConfigRoutes({ json, projectRoot, listModules, getModuleTokens }: Deps) {\n /** \u5E73\u53F0 ConfigName\uFF08qq_config \u7B49\uFF09 */\n const readPlatform = (name: ConfigName): unknown => readJson(configPath(projectRoot, name));\n /** \u9ED1\u7BB1 JSON\uFF1A\u4EFB\u610F configs/<file>\uFF0C\u4E0D\u8FDB ConfigName */\n const readOpaque = (fileName: string): unknown => readJson(join(configDir(projectRoot), fileName));\n\n /**\n * SAPI ConfigManager \u5951\u7EA6\uFF1A\u53EA\u7F13\u5B58 modules / settings / permissions\u3002\n * \u52FF\u518D\u5F80 all \u585E areas \u7B49 legacy \u57DF\uFF08\u90A3\u4E9B\u8D70\u4E13\u7528 GET \u6216\u6A21\u5757 configKey\uFF09\u3002\n */\n function getAllConfigs(): Record<string, unknown> {\n const modules = typeof listModules === \"function\" ? listModules() : [];\n const module_tokens = typeof getModuleTokens === \"function\" ? getModuleTokens() : {};\n return {\n modules,\n module_tokens,\n settings: stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null),\n permissions: getPermissions(),\n };\n }\n\n function getSettingsFlat(): Array<{ key: string; value: string }> {\n const obj = stripMeta(readOpaque(\"settings.json\") as Record<string, unknown> | null);\n return Object.entries(obj).map(([key, value]) => ({ key, value: jsonValue(value) }));\n }\n\n function getSettingByKey(key: string): { value: unknown; source?: string } {\n const settings = (readOpaque(\"settings.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(settings, key) && !isMetaKey(key)) {\n return { value: settings[key], source: \"settings.json\" };\n }\n if (key === \"bridge_channel_id\") {\n const qq = (readPlatform(\"qq_config.json\") as Record<string, unknown> | null) ?? {};\n if (qq.bridge_channel_id) return { value: qq.bridge_channel_id, source: \"qq_config.json\" };\n }\n if (key.startsWith(\"land:\")) {\n const land = (readOpaque(\"land.json\") as Record<string, unknown> | null) ?? {};\n if (Object.prototype.hasOwnProperty.call(land, key) && !isMetaKey(key)) {\n return { value: land[key], source: \"land.json\" };\n }\n }\n return { value: null };\n }\n\n function getAreas(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"areas.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.module && r.dimension != null)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPermissions(): unknown[] {\n return (arrayOrEmpty(readPlatform(\"permissions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.player_name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getBannedItems(): string[] {\n return (arrayOrEmpty(readOpaque(\"banned_items.json\")) as Array<string>).filter(\n (i) => typeof i === \"string\" && i && !i.startsWith(\"_\")\n );\n }\n\n function getClean(): { item_max: number; poll_interval: number } {\n const c = stripMetaDeep(readOpaque(\"clean.json\") ?? {}) as Record<string, unknown>;\n return { item_max: (c.item_max as number) ?? 192, poll_interval: (c.poll_interval as number) ?? 60 };\n }\n\n function getGrids(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"grids.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.name)\n .map((r) => stripMetaDeep(r));\n }\n\n function getPeaceFilters(): unknown[] {\n return (arrayOrEmpty(readOpaque(\"peace_filters.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.family)\n .map((r) => stripMetaDeep(r));\n }\n\n function getQA(): Array<Record<string, unknown>> {\n return (arrayOrEmpty(readOpaque(\"questions.json\")) as Array<Record<string, unknown>>)\n .filter((r) => r && r.question)\n .map((r, idx) => {\n const clean = stripMetaDeep(r) as Record<string, unknown>;\n return {\n id: idx + 1,\n weight: clean.weight ?? 1,\n question: clean.question,\n answers: clean.answers ?? [],\n msg_right: clean.msg_right ?? \"\",\n msg_wrong: clean.msg_wrong ?? \"\",\n explanation: clean.explanation ?? \"\",\n min_rank: clean.min_rank ?? null,\n max_rank: clean.max_rank ?? null,\n rewards: clean.rewards ?? [],\n punishments: clean.punishments ?? [],\n };\n });\n }\n\n return async function handleConfigRoute({\n path: requestPath,\n method,\n params,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params;\n if (requestPath === \"/api/sfmc/configs/all\") {\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n json(res, getAllConfigs());\n return true;\n }\n if (requestPath === \"/api/sfmc/settings\") {\n if (method === \"GET\") {\n json(res, { settings: getSettingsFlat() });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (requestPath.startsWith(\"/api/sfmc/settings/\")) {\n const key = decodeURIComponent(requestPath.slice(\"/api/sfmc/settings/\".length));\n if (method === \"GET\") {\n json(res, getSettingByKey(key));\n return true;\n }\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n if (requestPath === \"/api/sfmc/areas\") {\n if (method === \"GET\") {\n json(res, { areas: getAreas() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/permissions\") {\n if (method === \"GET\") {\n json(res, { permissions: getPermissions() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/banned_items\") {\n if (method === \"GET\") {\n json(res, { items: getBannedItems() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/clean\") {\n if (method === \"GET\") {\n json(res, { clean: getClean() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/grids\") {\n if (method === \"GET\") {\n json(res, { grids: getGrids() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/peace_filters\") {\n if (method === \"GET\") {\n json(res, { filters: getPeaceFilters() });\n return true;\n }\n }\n if (requestPath === \"/api/sfmc/qa\") {\n if (method === \"GET\") {\n json(res, { questions: getQA() });\n return true;\n }\n }\n return false;\n };\n}\n\nexport { createConfigRoutes };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,16 +13,22 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
15
15
|
import { json as defaultJson, type Method } from "../lib/http.js";
|
|
16
|
-
import {
|
|
16
|
+
import type { IdempotencyStore } from "../lib/idempotency-store.js";
|
|
17
17
|
import type { SchemaRegistry } from "../schema-registry.js";
|
|
18
18
|
import type { TxRunner } from "../tx-runner.js";
|
|
19
|
-
import type
|
|
19
|
+
import { type ModuleAuth } from "./_shared.js";
|
|
20
20
|
export interface DbRoutesDeps {
|
|
21
21
|
schemaRegistry: SchemaRegistry;
|
|
22
22
|
txRunner: TxRunner;
|
|
23
23
|
idempotent: IdempotencyStore;
|
|
24
24
|
json?: typeof defaultJson;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* 创建 `/api/sfmc/db/*` 数据库路由请求处理器。
|
|
28
|
+
*
|
|
29
|
+
* @param depsIn 依赖注入项(包含 schemaRegistry、txRunner、idempotent 等)。
|
|
30
|
+
* @returns 异步路由处理函数。
|
|
31
|
+
*/
|
|
26
32
|
export declare function createDbRoutes(depsIn: Partial<DbRoutesDeps>): (ctx: {
|
|
27
33
|
path: string;
|
|
28
34
|
method: Method | string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,
|
|
1
|
+
{"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAa,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAoKrB"}
|
package/dist/routes/db-routes.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { json as defaultJson } from "../lib/http.js";
|
|
2
|
-
import {
|
|
2
|
+
import { log } from "../lib/log.js";
|
|
3
3
|
import { PermissionDeniedError } from "../permission-gate.js";
|
|
4
|
+
import { jsonV2Fail, jsonV2Ok } from "./_shared.js";
|
|
4
5
|
function createDbRoutes(depsIn) {
|
|
5
6
|
const deps = depsIn;
|
|
6
7
|
if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {
|
|
@@ -25,6 +26,7 @@ function createDbRoutes(depsIn) {
|
|
|
25
26
|
const result = deps.schemaRegistry.define(moduleId, req2);
|
|
26
27
|
jsonV2Ok(res, { table: result.table, created: result.created });
|
|
27
28
|
} catch (e) {
|
|
29
|
+
log.warn(`[define-table failed] ${moduleId} table=${body?.name}: ${e.message}`);
|
|
28
30
|
jsonV2Fail(res, e.message, 400);
|
|
29
31
|
}
|
|
30
32
|
return true;
|
|
@@ -136,19 +138,10 @@ function createDbRoutes(depsIn) {
|
|
|
136
138
|
if (m) {
|
|
137
139
|
try {
|
|
138
140
|
if (m[1] === "probe") {
|
|
139
|
-
const r = await deps.idempotent.probe(
|
|
140
|
-
moduleId,
|
|
141
|
-
String(body.action),
|
|
142
|
-
String(body.key)
|
|
143
|
-
);
|
|
141
|
+
const r = await deps.idempotent.probe(moduleId, String(body.action), String(body.key));
|
|
144
142
|
json(res, r);
|
|
145
143
|
} else {
|
|
146
|
-
const r = await deps.idempotent.commit(
|
|
147
|
-
moduleId,
|
|
148
|
-
String(body.action),
|
|
149
|
-
String(body.key),
|
|
150
|
-
body.value
|
|
151
|
-
);
|
|
144
|
+
const r = await deps.idempotent.commit(moduleId, String(body.action), String(body.key), body.value);
|
|
152
145
|
jsonV2Ok(res, { committed: r.ok });
|
|
153
146
|
}
|
|
154
147
|
} catch (e) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/db-routes.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\nimport type {\n DefineTableRequest,\n SchemaRegistry,\n} from \"../schema-registry.js\";\nimport type {\n TxRequest,\n TxRunner,\n TxStep,\n} from \"../tx-runner.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u300C\u9274\u6743\u8EAB\u4EFD\u300D\u6267\u884C\u4E8B\u52A1:\u5FFD\u7565 body \u91CC\u81EA\u5E26\u7684 moduleId,\n // \u65E2\u9632\u6B62\u6301 A \u7684 token \u5192\u7528 B \u7684\u6743\u9650\u8D8A\u6743,\u4E5F\u4FEE\u590D\u5BA2\u6237\u7AEF\u53EA\u53D1 {steps} \u65F6\n // moduleId \u7F3A\u5931\u5BFC\u81F4\u7684\u4E8B\u52A1\u6052\u88AB\u62D2\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse/TxError \u81EA\u8EAB\u5DF2\u5E26 ok \u5B57\u6BB5;\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B SDK \u5728\u56DE\u8C03\u5185\u8BFB\u56DE query/get/call \u7ED3\u679C\n // OCP/DRY:\u65B0\u4F1A\u8BDD\u52A8\u8BCD\u53EA\u5F80\u8868\u91CC\u52A0\u4E00\u9879,\u52FF\u518D\u590D\u5236 try/catch \u56DB\u4EFD\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(\n moduleId,\n String(body.action),\n String(body.key)\n );\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(\n moduleId,\n String(body.action),\n String(body.key),\n body.value\n );\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
|
|
5
|
-
"mappings": "AAeA,SAAS,QAAQ,mBAAgC;
|
|
4
|
+
"sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\nimport { log } from \"../lib/log.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { DefineTableRequest, SchemaRegistry } from \"../schema-registry.js\";\nimport type { TxRequest, TxRunner, TxStep } from \"../tx-runner.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(moduleId, String(body.action), String(body.key));\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(moduleId, String(body.action), String(body.key), body.value);\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 路由列表:
|
|
5
5
|
* GET /api/sfmc/messages — 模糊/过滤查询消息
|
|
6
|
-
* POST /api/sfmc/messages — 批量 INSERT OR REPLACE
|
|
6
|
+
* POST /api/sfmc/messages — 批量 INSERT OR REPLACE 消息(匹配 bridge 频道时转发至 QQ)
|
|
7
7
|
*/
|
|
8
8
|
import type { QueryFn } from "../lib/sqlite.js";
|
|
9
9
|
interface Deps {
|
|
@@ -11,8 +11,12 @@ interface Deps {
|
|
|
11
11
|
body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
|
|
12
12
|
json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
13
13
|
forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;
|
|
14
|
+
/** 当前桥接频道;空则不做 MC→QQ 聊天出站(事件通道不受影响) */
|
|
15
|
+
getBridgeChannelId: () => string;
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
/** 是否应将本条消息推到 QQ 群 */
|
|
18
|
+
export declare function shouldForwardChatToQQ(bridgeChannelId: string, channelId: string, fromId: string): boolean;
|
|
19
|
+
declare function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }: Deps): ({ path, method, params, req, res, }: {
|
|
16
20
|
path: string;
|
|
17
21
|
method: string;
|
|
18
22
|
params: URLSearchParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/routes/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,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,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/routes/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,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,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClG,uCAAuC;IACvC,kBAAkB,EAAE,MAAM,MAAM,CAAC;CAClC;AAED,sBAAsB;AACtB,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAUT;AAED,iBAAS,oBAAoB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,EAAE,IAAI,yCAO3F;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,CAyDrB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
package/dist/routes/messages.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { SQL } from "sql-template-strings";
|
|
2
|
-
function
|
|
2
|
+
function shouldForwardChatToQQ(bridgeChannelId, channelId, fromId) {
|
|
3
|
+
const bridge = String(bridgeChannelId ?? "").trim();
|
|
4
|
+
if (!bridge) return false;
|
|
5
|
+
if (String(channelId ?? "").trim() !== bridge) return false;
|
|
6
|
+
const fid = String(fromId ?? "").trim();
|
|
7
|
+
if (!fid) return false;
|
|
8
|
+
if (fid.startsWith("qq_")) return false;
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
function createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }) {
|
|
3
12
|
return async function handle({
|
|
4
13
|
path,
|
|
5
14
|
method,
|
|
@@ -47,8 +56,12 @@ function createMessagesRoutes({ query, body, json, forwardToQQBridge }) {
|
|
|
47
56
|
)`
|
|
48
57
|
);
|
|
49
58
|
}
|
|
59
|
+
const bridgeId = getBridgeChannelId();
|
|
50
60
|
for (const m of messages) {
|
|
51
|
-
|
|
61
|
+
const channelId = String(m.channelId ?? "");
|
|
62
|
+
const fromId = String(m.fromid ?? "");
|
|
63
|
+
if (!shouldForwardChatToQQ(bridgeId, channelId, fromId)) continue;
|
|
64
|
+
forwardToQQBridge(channelId, String(m.fromName ?? ""), String(m.content ?? ""), fromId);
|
|
52
65
|
}
|
|
53
66
|
json(res, { success: true });
|
|
54
67
|
} else {
|
|
@@ -60,6 +73,7 @@ function createMessagesRoutes({ query, body, json, forwardToQQBridge }) {
|
|
|
60
73
|
};
|
|
61
74
|
}
|
|
62
75
|
export {
|
|
63
|
-
createMessagesRoutes
|
|
76
|
+
createMessagesRoutes,
|
|
77
|
+
shouldForwardChatToQQ
|
|
64
78
|
};
|
|
65
79
|
//# sourceMappingURL=messages.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/messages.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/messages.ts \u2014 \u804A\u5929\u6D88\u606F (sfmc_chat_messages)\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/messages \u2014 \u6A21\u7CCA/\u8FC7\u6EE4\u67E5\u8BE2\u6D88\u606F\n * POST /api/sfmc/messages \u2014 \u6279\u91CF INSERT OR REPLACE \u6D88\u606F\uFF08\
|
|
5
|
-
"mappings": "AAQA,SAAS,WAAW;
|
|
4
|
+
"sourcesContent": ["/**\n * routes/messages.ts \u2014 \u804A\u5929\u6D88\u606F (sfmc_chat_messages)\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/messages \u2014 \u6A21\u7CCA/\u8FC7\u6EE4\u67E5\u8BE2\u6D88\u606F\n * POST /api/sfmc/messages \u2014 \u6279\u91CF INSERT OR REPLACE \u6D88\u606F\uFF08\u5339\u914D bridge \u9891\u9053\u65F6\u8F6C\u53D1\u81F3 QQ\uFF09\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\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 forwardToQQBridge: (channelId: string, fromName: string, content: string, fromId: string) => void;\n /** \u5F53\u524D\u6865\u63A5\u9891\u9053\uFF1B\u7A7A\u5219\u4E0D\u505A MC\u2192QQ \u804A\u5929\u51FA\u7AD9\uFF08\u4E8B\u4EF6\u901A\u9053\u4E0D\u53D7\u5F71\u54CD\uFF09 */\n getBridgeChannelId: () => string;\n}\n\n/** \u662F\u5426\u5E94\u5C06\u672C\u6761\u6D88\u606F\u63A8\u5230 QQ \u7FA4 */\nexport function shouldForwardChatToQQ(\n bridgeChannelId: string,\n channelId: string,\n fromId: string\n): boolean {\n const bridge = String(bridgeChannelId ?? \"\").trim();\n if (!bridge) return false;\n if (String(channelId ?? \"\").trim() !== bridge) return false;\n const fid = String(fromId ?? \"\").trim();\n // \u65E0\u53D1\u9001\u8005 id \u4E0D\u51FA\u7AD9\uFF08\u907F\u514D\u89E3\u6790\u5931\u8D25\u65F6\u8BEF\u63A8\uFF09\n if (!fid) return false;\n // QQ\u2192MC \u5199\u5165\u7684 fromid \u5F62\u5982 qq_*\uFF0C\u7981\u6B62\u56DE\u73AF\u63A8\u7FA4\n if (fid.startsWith(\"qq_\")) return false;\n return true;\n}\n\nfunction createMessagesRoutes({ query, body, json, forwardToQQBridge, getBridgeChannelId }: 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 === \"/api/sfmc/messages\") {\n if (method === \"GET\") {\n const stmt = SQL`SELECT * FROM sfmc_chat_messages WHERE 1=1`;\n const search = params.get(\"search\")?.trim();\n if (search) stmt.append(SQL` AND (content LIKE ${`%${search}%`})`);\n const type = params.get(\"type\")?.trim();\n if (type) stmt.append(SQL` AND type = ${type}`);\n const channelId = params.get(\"channelId\")?.trim();\n if (channelId) stmt.append(SQL` AND channel_id = ${channelId}`);\n const from = params.get(\"from\")?.trim();\n if (from) stmt.append(SQL` AND from_id = ${from}`);\n const minCreatedAt = params.get(\"minCreatedAt\")?.trim();\n if (minCreatedAt) stmt.append(SQL` AND created_at >= ${Number(minCreatedAt)}`);\n const minSentAt = params.get(\"minSentAt\")?.trim();\n if (minSentAt) stmt.append(SQL` AND created_at >= ${Number(minSentAt)}`);\n const maxCreatedAt = params.get(\"maxCreatedAt\")?.trim();\n if (maxCreatedAt) stmt.append(SQL` AND created_at <= ${Number(maxCreatedAt)}`);\n stmt.append(SQL` ORDER BY created_at ASC`);\n json(res, { messages: query(stmt) });\n } else if (method === \"POST\") {\n const { messages } = await body(req);\n if (!Array.isArray(messages) || messages.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n if ((messages as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n // \u591A\u884C INSERT \u6539\u6210\u5FAA\u73AF\u5355\u6761 INSERT OR REPLACE \u2014\u2014 \u7B80\u5355\u4F18\u5148\n for (const m of messages as Array<Record<string, unknown>>) {\n query(\n SQL`INSERT OR REPLACE INTO sfmc_chat_messages (\n id, channel_id, from_id, from_name, type, content, attachment, show_timestamp, created_at\n ) VALUES (\n ${m.id}, ${m.channelId}, ${m.fromid}, ${m.fromName},\n ${m.type ?? \"text\"}, ${m.content}, ${m.attachment ?? null},\n ${m.showTimestamp ? 1 : 0}, ${m.timestamp}\n )`\n );\n }\n const bridgeId = getBridgeChannelId();\n for (const m of messages as Array<Record<string, unknown>>) {\n const channelId = String(m.channelId ?? \"\");\n const fromId = String(m.fromid ?? \"\");\n if (!shouldForwardChatToQQ(bridgeId, channelId, fromId)) continue;\n forwardToQQBridge(channelId, String(m.fromName ?? \"\"), String(m.content ?? \"\"), fromId);\n }\n json(res, { success: true });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n return false;\n };\n}\n\nexport { createMessagesRoutes };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* routes/module-config-routes.ts —
|
|
2
|
+
* routes/module-config-routes.ts — 模块私有配置文件读写路由
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* REST 端点:
|
|
5
|
+
* - GET /api/sfmc/configs/:configKey 读取对应 `configs/<configKey>.json` 配置文件
|
|
6
|
+
* - POST /api/sfmc/configs/:configKey/set 修改并持久化指定配置键值对
|
|
7
|
+
* - GET /api/sfmc/configs/:configKey/notify 通过 SSE 推送配置实时变更通知
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 设计:文件 = configs/<configKey>.json;整文件 read-modify-write(lock by
|
|
12
|
-
* 简单 mutex),不是细粒度 lock — 模块 config 文件本来就小;并发冲突概率极低。
|
|
9
|
+
* 鉴权控制:
|
|
10
|
+
* 模块身份经 `ctx.moduleAuth` 验证;必须具备 `config:read:<configKey>` 或 `config:write:<configKey>` 权限。
|
|
13
11
|
*/
|
|
14
12
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
15
13
|
import { json as defaultJson, type Method } from "../lib/http.js";
|
|
@@ -20,6 +18,12 @@ export interface ModuleConfigRoutesDeps {
|
|
|
20
18
|
enabled: Map<string, ModuleManifestV2>;
|
|
21
19
|
json?: typeof defaultJson;
|
|
22
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* 创建模块私有配置路由请求处理器。
|
|
23
|
+
*
|
|
24
|
+
* @param depsIn 依赖注入项(包含工作根目录与已启用模块字典)。
|
|
25
|
+
* @returns 异步路由处理函数。
|
|
26
|
+
*/
|
|
23
27
|
export declare function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>): (ctx: {
|
|
24
28
|
path: string;
|
|
25
29
|
method: Method | string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-config-routes.d.ts","sourceRoot":"","sources":["../../src/routes/module-config-routes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"module-config-routes.d.ts","sourceRoot":"","sources":["../../src/routes/module-config-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AA+BD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,SAU3D;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAwErB"}
|
|
@@ -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\
|
|
5
|
-
"mappings": "
|
|
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
|
}
|
package/dist/routes/modules.d.ts
CHANGED
|
@@ -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,
|
|
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": "
|
|
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": "AA8BA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAS;AAEP,SAAO,eAAe,kBAAkB;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,SAAK;AACL,QAAI,SAAS,6BAA6B;AACxC,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;AAAA,MAC5C,OAAO;AACL,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,QAAI,SAAS,qBAAqB;AAChC,UAAI,WAAW,MAAO,MAAK,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;AAAA,UACzD,MAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAC1D,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,WAAW,oBAAoB,EAAG,QAAO;AAEnD,UAAM,OAAO,KAAK,MAAM,qBAAqB,MAAM;AACnD,UAAM,CAAC,QAAQ,MAAM,IAAI,KAAK,MAAM,GAAG;AACvC,UAAM,SAAS,mBAAmB,mBAAmB,UAAU,EAAE,CAAC;AAClE,QAAI,CAAC,QAAQ;AACX,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mBAAmB,GAAG,GAAG;AAC5D,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,MAAM,gBAAgB,EAAE,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO,EAAE;AAC9E,QAAI,CAAC,UAAU,WAAW,OAAO;AAC/B,WAAK,KAAK,EAAE,QAAQ,QAAQ,KAAK,KAAK,CAAC;AAAA,IACzC,WAAW,CAAC,WAAW,WAAW,WAAW,WAAW,QAAQ;AAC9D,YAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,GAAG;AAClC,UAAI,CAAC,WAAW,CAAC,OAAO,YAAY;AAClC,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,wBAAwB,GAAG,GAAG;AACjE,eAAO;AAAA,MACT;AACA,UAAI;AACF,yBAAiB,QAAQ,CAAC,CAAC,OAAO;AAAA,MACpC,SAAS,OAAO;AACd,cAAM,IAAI;AACV,YAAI,EAAE,SAAS,oBAAoB;AACjC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,OAAO,EAAE,MAAM,GAAG,GAAG;AAC5E,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AACA,WAAK,KAAK,EAAE,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAChD,WAAW,WAAW,YAAY,WAAW,QAAQ;AACnD,UAAI;AACF,yBAAiB,QAAQ,IAAI;AAAA,MAC/B,SAAS,OAAO;AACd,cAAM,IAAI;AACV,YAAI,EAAE,SAAS,oBAAoB;AACjC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,OAAO,EAAE,MAAM,GAAG,GAAG;AAC5E,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AACA,WAAK,KAAK,EAAE,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAChD,WAAW,WAAW,aAAa,WAAW,QAAQ;AACpD,UAAI,CAAC,OAAO,YAAY;AACtB,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,wBAAwB,GAAG,GAAG;AACjE,eAAO;AAAA,MACT;AACA,uBAAiB,QAAQ,KAAK;AAC9B,WAAK,KAAK,EAAE,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAChD,OAAO;AACL,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AACF;",
|
|
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"}
|