@sfmc-bds/db-server 0.2.0-beta.0 → 0.2.0-beta.10
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/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 +67 -451
- 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 +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -19
- 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 +3 -7
- 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/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": "AAeA,SAAS,QAAQ,mBAAgC;AAEjD,SAAS,WAAW;AACpB,SAAS,6BAA6B;AAGtC,SAAS,YAAY,gBAAiC;AAe/C,SAAS,eAAe,QAA+B;AAC5D,QAAM,OAAO;AACb,MAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,YAAY,CAAC,KAAK,YAAY;AAC9D,UAAM,IAAI,MAAM,qEAA2D;AAAA,EAC7E;AACA,QAAM,OAAO,KAAK,QAAQ;AAE1B,SAAO,OAAO,QAOU;AACtB,UAAM,EAAE,MAAM,QAAQ,IAAI,IAAI;AAC9B,QAAI,CAAC,KAAK,WAAW,eAAe,EAAG,QAAO;AAC9C,QAAI,WAAW,UAAU,WAAW,SAAU,QAAO;AAErD,UAAM,OAAO,IAAI,cAAc;AAC/B,QAAI,CAAC,MAAM;AACT,iBAAW,KAAK,yCAAyC,KAAK,cAAc;AAC5E,aAAO;AAAA,IACT;AAEA,UAAM,OAAQ,MAAO,IAAI,QAA0D,CAAC;AACpF,UAAM,WAAW,KAAK;AAEtB,QAAI,SAAS,6BAA6B;AACxC,UAAI;AACF,cAAM,OAAO;AACb,eAAQ,KAA+B;AACvC,cAAM,SAAS,KAAK,eAAgB,OAAO,UAAU,IAAI;AACzD,iBAAS,KAAK,EAAE,OAAO,OAAO,OAAO,SAAS,OAAO,QAAQ,CAAC;AAAA,MAChE,SAAS,GAAG;AACV,YAAI,KAAK,yBAAyB,QAAQ,UAAW,MAAc,IAAI,KAAM,EAAY,OAAO,EAAE;AAClG,mBAAW,KAAM,EAAY,SAAS,GAAG;AAAA,MAC3C;AACA,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,mBAAmB;AAC9B,UAAI;AACF,cAAM,QAAQ;AAGd,cAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC1D,cAAM,SAAS,MAAM,KAAK,SAAU,IAAI,EAAE,UAAU,MAAM,CAAC;AAC3D,cAAM,SAAS,OAAO,KAAK,MAAM;AAEjC,aAAK,KAAK,QAA8C,MAAM;AAAA,MAChE,SAAS,GAAG;AACV,mBAAW,KAAM,EAAY,SAAS,GAAG;AAAA,MAC3C;AACA,aAAO;AAAA,IACT;AAKA,UAAM,aAKD;AAAA,MACH;AAAA,QACE,MAAM;AAAA,QACN,KAAK,MAAM,KAAK,SAAU,aAAa,QAAQ;AAAA,MACjD;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,QACb,aAAa;AAAA,QACb,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,KAAK,SAAU,YAAY,MAAM,UAAU,IAAK;AAAA,MAC3E;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,QACb,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK,SAAU,cAAc,MAAM,QAAQ;AAAA,MAChE;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,QACb,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK,SAAU,gBAAgB,MAAM,QAAQ;AAAA,MAClE;AAAA,IACF;AACA,eAAW,MAAM,YAAY;AAC3B,UAAI,SAAS,GAAG,KAAM;AACtB,UAAI;AACF,cAAM,OAAO,OAAO,KAAK,QAAQ,EAAE;AACnC,cAAM,OAAO,KAAK;AAClB,YAAI,GAAG,eAAe,CAAC,MAAM;AAC3B,qBAAW,KAAK,GAAG,GAAG,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,0BAAgB,GAAG;AAC9D,iBAAO;AAAA,QACT;AACA,YAAI,GAAG,gBAAgB,CAAC,QAAQ,OAAO,SAAS,YAAY,EAAE,QAAQ,QAAQ;AAC5E,qBAAW,KAAK,uCAA6B,GAAG;AAChD,iBAAO;AAAA,QACT;AACA,cAAM,SAAS,MAAM,GAAG,IAAI,SAAS,SAAY,EAAE,MAAM,KAAK,IAAI,EAAE,KAAK,CAAC;AAC1E,aAAK,KAAK,QAAmC,OAAO,KAAK,MAAM,GAAG;AAAA,MACpE,SAAS,GAAG;AACV,mBAAW,KAAM,EAAY,SAAS,GAAG;AAAA,MAC3C;AACA,aAAO;AAAA,IACT;AAGA,UAAM,UAA6C;AAAA,MACjD,EAAE,OAAO,4BAA4B,IAAI,QAAQ;AAAA,MACjD,EAAE,OAAO,0BAA0B,IAAI,MAAM;AAAA,MAC7C,EAAE,OAAO,6BAA6B,IAAI,SAAS;AAAA,MACnD,EAAE,OAAO,6BAA6B,IAAI,SAAS;AAAA,MACnD,EAAE,OAAO,6BAA6B,IAAI,SAAS;AAAA,IACrD;AACA,eAAW,QAAQ,SAAS;AAC1B,UAAI,KAAK,MAAM,KAAK,IAAI,GAAG;AACzB,YAAI;AACF,gBAAM,OAAO,gBAAgB,KAAK,IAAI,IAAI;AAC1C,gBAAM,SAAS,MAAM,KAAK,SAAU,IAAI,EAAE,UAAU,OAAO,CAAC,IAAI,EAAE,CAAC;AACnE,cAAI,CAAC,OAAO,IAAI;AACd,kBAAM,OAAO,OAAO,SAAS,sBAAsB,MAAM;AACzD,uBAAW,KAAK,OAAO,OAAO,MAAM,OAAO,MAAM,EAAE,MAAM,OAAO,KAAK,CAAC;AACtE,mBAAO;AAAA,UACT;AACA,gBAAM,QAAQ,OAAO,QAAQ,CAAC;AAC9B,mBAAS,KAAK,aAAa,OAAO,KAAK,EAAE,CAA4B;AAAA,QACvE,SAAS,GAAG;AACV,cAAI,aAAa,uBAAuB;AACtC,uBAAW,KAAK,EAAE,SAAS,KAAK,mBAAmB;AACnD,mBAAO;AAAA,UACT;AACA,qBAAW,KAAM,EAAY,SAAS,GAAG;AAAA,QAC3C;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,SAAS,sBAAsB;AACjC,UAAI;AACF,cAAM,OAAe;AAAA,UACnB,IAAI;AAAA,UACJ,OAAO,OAAO,KAAK,KAAK;AAAA,UACxB,OAAO,OAAO,KAAK,KAAK;AAAA,UACxB,QAAQ,OAAO,KAAK,MAAM;AAAA,UAC1B,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAgC,IAAI,CAAC;AAAA,QACpE;AACA,cAAM,SAAS,MAAM,KAAK,SAAU,IAAI,EAAE,UAAU,OAAO,CAAC,IAAI,EAAE,CAAC;AACnE,YAAI,CAAC,OAAO,IAAI;AACd,qBAAW,KAAK,OAAO,OAAO,KAAK,OAAO,IAAI;AAC9C,iBAAO;AAAA,QACT;AACA,iBAAS,GAAG;AAAA,MACd,SAAS,GAAG;AACV,mBAAW,KAAM,EAAY,SAAS,GAAG;AAAA,MAC3C;AACA,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,KAAK,MAAM,+CAA+C;AACpE,QAAI,GAAG;AACL,UAAI;AACF,YAAI,EAAE,CAAC,MAAM,SAAS;AACpB,gBAAM,IAAI,MAAM,KAAK,WAAY,MAAM,UAAU,OAAO,KAAK,MAAM,GAAG,OAAO,KAAK,GAAG,CAAC;AACtF,eAAK,KAAK,CAAuC;AAAA,QACnD,OAAO;AACL,gBAAM,IAAI,MAAM,KAAK,WAAY,OAAO,UAAU,OAAO,KAAK,MAAM,GAAG,OAAO,KAAK,GAAG,GAAG,KAAK,KAAK;AACnG,mBAAS,KAAK,EAAE,WAAW,EAAE,GAAG,CAAC;AAAA,QACnC;AAAA,MACF,SAAS,GAAG;AACV,mBAAW,KAAM,EAAY,SAAS,GAAG;AAAA,MAC3C;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,IAAsD,MAAuC;AACpH,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,OAAO,KAAK,KAAK;AAAA,QACxB,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAyC,IAAI,CAAC;AAAA,MAC7E;AAAA,IACF,KAAK;AACH,aAAO,EAAE,IAAI,OAAO,OAAO,OAAO,KAAK,KAAK,GAAG,IAAI,OAAO,KAAK,EAAE,EAAE;AAAA,IACrE,KAAK;AACH,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,OAAO,KAAK,KAAK;AAAA,QACxB,KAAK,KAAK;AAAA,MACZ;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,OAAO,KAAK,KAAK;AAAA,QACxB,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,OAAO,KAAK;AAAA,MACd;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,OAAO,KAAK,KAAK;AAAA,QACxB,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,MAAM,QAAQ,KAAK,IAAI;AAAA,MACzB;AAAA,EACJ;AACF;AAQA,SAAS,aAAa,GAAY,IAA0B;AAC1D,QAAM,OAAO;AACb,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,IAC5C,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,IACvD,KAAK;AACH,aAAO,OAAO,KAAK,YAAY,WAAW,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACzE;AACE,aAAO,CAAC;AAAA,EACZ;AACF;",
|
|
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": "AAQA,SAAS,WAAW;AAab,SAAS,sBACd,iBACA,WACA,QACS;AACT,QAAM,SAAS,OAAO,mBAAmB,EAAE,EAAE,KAAK;AAClD,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,aAAa,EAAE,EAAE,KAAK,MAAM,OAAQ,QAAO;AACtD,QAAM,MAAM,OAAO,UAAU,EAAE,EAAE,KAAK;AAEtC,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI,IAAI,WAAW,KAAK,EAAG,QAAO;AAClC,SAAO;AACT;AAEA,SAAS,qBAAqB,EAAE,OAAO,MAAM,MAAM,mBAAmB,mBAAmB,GAAS;AAChG,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,sBAAsB;AACjC,UAAI,WAAW,OAAO;AACpB,cAAM,OAAO;AACb,cAAM,SAAS,OAAO,IAAI,QAAQ,GAAG,KAAK;AAC1C,YAAI,OAAQ,MAAK,OAAO,yBAAyB,IAAI,MAAM,GAAG,GAAG;AACjE,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,kBAAkB,IAAI,EAAE;AAC9C,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,wBAAwB,SAAS,EAAE;AAC9D,cAAM,OAAO,OAAO,IAAI,MAAM,GAAG,KAAK;AACtC,YAAI,KAAM,MAAK,OAAO,qBAAqB,IAAI,EAAE;AACjD,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,cAAM,YAAY,OAAO,IAAI,WAAW,GAAG,KAAK;AAChD,YAAI,UAAW,MAAK,OAAO,yBAAyB,OAAO,SAAS,CAAC,EAAE;AACvE,cAAM,eAAe,OAAO,IAAI,cAAc,GAAG,KAAK;AACtD,YAAI,aAAc,MAAK,OAAO,yBAAyB,OAAO,YAAY,CAAC,EAAE;AAC7E,aAAK,OAAO,6BAA6B;AACzC,aAAK,KAAK,EAAE,UAAU,MAAM,IAAI,EAAE,CAAC;AAAA,MACrC,WAAW,WAAW,QAAQ;AAC5B,cAAM,EAAE,SAAS,IAAI,MAAM,KAAK,GAAG;AACnC,YAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AACrD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,UAAU,GAAG,GAAG;AACnD,iBAAO;AAAA,QACT;AACA,YAAK,SAAuB,SAAS,KAAK;AACxC,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,GAAG,GAAG;AAC7D,iBAAO;AAAA,QACT;AAEA,mBAAW,KAAK,UAA4C;AAC1D;AAAA,YACE;AAAA;AAAA;AAAA,kBAGM,EAAE,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,MAAM,KAAK,EAAE,QAAQ;AAAA,kBAChD,EAAE,QAAQ,MAAM,KAAK,EAAE,OAAO,KAAK,EAAE,cAAc,IAAI;AAAA,kBACvD,EAAE,gBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS;AAAA;AAAA,UAE/C;AAAA,QACF;AACA,cAAM,WAAW,mBAAmB;AACpC,mBAAW,KAAK,UAA4C;AAC1D,gBAAM,YAAY,OAAO,EAAE,aAAa,EAAE;AAC1C,gBAAM,SAAS,OAAO,EAAE,UAAU,EAAE;AACpC,cAAI,CAAC,sBAAsB,UAAU,WAAW,MAAM,EAAG;AACzD,4BAAkB,WAAW,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM;AAAA,QACxF;AACA,aAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,MAC7B,OAAO;AACL,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACF;",
|
|
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": "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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
|
+
}
|