@sfmc-bds/db-server 0.1.0 → 0.2.0-beta.1
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/README.md +1 -1
- package/dist/db-tables.js +16 -17
- package/dist/db-tables.js.map +7 -1
- package/dist/domain/bridge.js +54 -77
- package/dist/domain/bridge.js.map +7 -1
- package/dist/domain/economy.d.ts +83 -2
- package/dist/domain/economy.d.ts.map +1 -1
- package/dist/domain/economy.js +292 -281
- package/dist/domain/economy.js.map +7 -1
- package/dist/domain/schema.js +480 -426
- package/dist/domain/schema.js.map +7 -1
- package/dist/domain/transaction.js +32 -80
- package/dist/domain/transaction.js.map +7 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +85 -84
- package/dist/env.js.map +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +218 -240
- package/dist/index.js.map +7 -1
- package/dist/lib/http.js +30 -39
- package/dist/lib/http.js.map +7 -1
- package/dist/lib/idempotency-store.js +34 -47
- package/dist/lib/idempotency-store.js.map +7 -1
- package/dist/lib/idempotency.js +8 -13
- package/dist/lib/idempotency.js.map +7 -1
- package/dist/lib/identifiers.js +18 -14
- package/dist/lib/identifiers.js.map +7 -1
- package/dist/lib/log.d.ts +3 -6
- package/dist/lib/log.d.ts.map +1 -1
- package/dist/lib/log.js +13 -14
- package/dist/lib/log.js.map +7 -1
- package/dist/lib/module-state.d.ts.map +1 -1
- package/dist/lib/module-state.js +32 -21
- package/dist/lib/module-state.js.map +7 -1
- package/dist/lib/order-by.d.ts +17 -0
- package/dist/lib/order-by.d.ts.map +1 -0
- package/dist/lib/order-by.js +24 -0
- package/dist/lib/order-by.js.map +7 -0
- package/dist/lib/runtime.js +16 -31
- package/dist/lib/runtime.js.map +7 -1
- package/dist/lib/sql-helpers.js +9 -27
- package/dist/lib/sql-helpers.js.map +7 -1
- package/dist/lib/sqlite.js +40 -52
- package/dist/lib/sqlite.js.map +7 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +154 -195
- package/dist/manifest-loader.js.map +7 -1
- package/dist/manifest.d.ts +3 -7
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +76 -115
- package/dist/manifest.js.map +7 -1
- package/dist/module-auth.d.ts +17 -6
- package/dist/module-auth.d.ts.map +1 -1
- package/dist/module-auth.js +93 -107
- package/dist/module-auth.js.map +7 -1
- package/dist/module-runtime-sync.d.ts +37 -0
- package/dist/module-runtime-sync.d.ts.map +1 -0
- package/dist/module-runtime-sync.js +54 -0
- package/dist/module-runtime-sync.js.map +7 -0
- package/dist/permission-gate.d.ts.map +1 -1
- package/dist/permission-gate.js +55 -86
- package/dist/permission-gate.js.map +7 -1
- package/dist/routes/_shared.d.ts +7 -2
- package/dist/routes/_shared.d.ts.map +1 -1
- package/dist/routes/_shared.js +16 -25
- package/dist/routes/_shared.js.map +7 -1
- package/dist/routes/config.d.ts +11 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +167 -213
- package/dist/routes/config.js.map +7 -1
- package/dist/routes/db-routes.d.ts +6 -10
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js +198 -172
- package/dist/routes/db-routes.js.map +7 -1
- package/dist/routes/health.js +15 -20
- package/dist/routes/health.js.map +7 -1
- package/dist/routes/messages.d.ts.map +1 -1
- package/dist/routes/messages.js +56 -63
- package/dist/routes/messages.js.map +7 -1
- package/dist/routes/module-config-routes.d.ts.map +1 -1
- package/dist/routes/module-config-routes.js +92 -111
- package/dist/routes/module-config-routes.js.map +7 -1
- package/dist/routes/modules.d.ts.map +1 -1
- package/dist/routes/modules.js +82 -88
- package/dist/routes/modules.js.map +7 -1
- package/dist/routes/service-routes.d.ts.map +1 -1
- package/dist/routes/service-routes.js +54 -61
- package/dist/routes/service-routes.js.map +7 -1
- package/dist/runtime.test.js +248 -49
- package/dist/runtime.test.js.map +7 -1
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +119 -160
- package/dist/schema-registry.js.map +7 -1
- package/dist/server.js +69 -72
- package/dist/server.js.map +7 -1
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +60 -67
- package/dist/service-registry.js.map +7 -1
- package/dist/services/builtin-handlers.d.ts +4 -0
- package/dist/services/builtin-handlers.d.ts.map +1 -1
- package/dist/services/builtin-handlers.js +34 -20
- package/dist/services/builtin-handlers.js.map +7 -1
- package/dist/services/economy-handlers.js +143 -132
- package/dist/services/economy-handlers.js.map +7 -1
- package/dist/tx-runner.d.ts +35 -11
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +293 -250
- package/dist/tx-runner.js.map +7 -1
- package/dist/where.js +71 -94
- package/dist/where.js.map +7 -1
- package/package.json +4 -4
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 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\u540C\u65F6\u8F6C\u53D1\u81F3 QQ Bridge\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}\n\nfunction createMessagesRoutes({ query, body, json, forwardToQQBridge }: 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 for (const m of messages as Array<Record<string, unknown>>) {\n forwardToQQBridge(m.channelId as string, m.fromName as string, m.content as string, m.fromid as string);\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;AAUpB,SAAS,qBAAqB,EAAE,OAAO,MAAM,MAAM,kBAAkB,GAAS;AAC5E,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,mBAAW,KAAK,UAA4C;AAC1D,4BAAkB,EAAE,WAAqB,EAAE,UAAoB,EAAE,SAAmB,EAAE,MAAgB;AAAA,QACxG;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
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-config-routes.d.ts","sourceRoot":"","sources":["../../src/routes/module-config-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;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,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"module-config-routes.d.ts","sourceRoot":"","sources":["../../src/routes/module-config-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;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,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,SAS3D;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,128 +1,109 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* routes/module-config-routes.ts — 模块配置文件管理
|
|
3
|
-
*
|
|
4
|
-
* 端点:
|
|
5
|
-
* GET /api/sfmc/configs/:configKey → { config: <configs/<configKey>.json> }
|
|
6
|
-
* POST /api/sfmc/configs/:configKey/set → 写一个 key
|
|
7
|
-
* GET /api/sfmc/configs/:configKey/notify (SSE:onChange 推送)
|
|
8
|
-
*
|
|
9
|
-
* 鉴权:模块身份来自 ctx.moduleAuth;Permission = config:read:configKey / config:write:configKey。
|
|
10
|
-
*
|
|
11
|
-
* 设计:文件 = configs/<configKey>.json;整文件 read-modify-write(lock by
|
|
12
|
-
* 简单 mutex),不是细粒度 lock — 模块 config 文件本来就小;并发冲突概率极低。
|
|
13
|
-
*/
|
|
14
1
|
import { join } from "node:path";
|
|
15
2
|
import { readJson, writeJson } from "@sfmc-bds/sdk/node/config";
|
|
16
3
|
import { json as defaultJson } from "../lib/http.js";
|
|
17
4
|
import { assertModulePermission, Perm } from "../permission-gate.js";
|
|
18
5
|
import { jsonV2Fail } from "./_shared.js";
|
|
19
6
|
function configPath(projectRoot, key) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
if (!/^[A-Za-z0-9_-]+$/.test(key)) {
|
|
8
|
+
throw new Error(`invalid configKey "${key}"`);
|
|
9
|
+
}
|
|
10
|
+
return join(projectRoot, "configs", `${key}.json`);
|
|
24
11
|
}
|
|
25
12
|
function readConfig(file) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return parsed;
|
|
13
|
+
const parsed = readJson(file);
|
|
14
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
15
|
+
return parsed;
|
|
30
16
|
}
|
|
31
|
-
const subscribers = new Map();
|
|
17
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
32
18
|
function subscribe(configKey, cb) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
19
|
+
if (!subscribers.has(configKey)) subscribers.set(configKey, /* @__PURE__ */ new Set());
|
|
20
|
+
subscribers.get(configKey).add(cb);
|
|
21
|
+
return () => {
|
|
22
|
+
subscribers.get(configKey)?.delete(cb);
|
|
23
|
+
};
|
|
39
24
|
}
|
|
40
25
|
function notify(configKey, key, value) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const cb of set)
|
|
45
|
-
cb({ key, value });
|
|
26
|
+
const set = subscribers.get(configKey);
|
|
27
|
+
if (!set) return;
|
|
28
|
+
for (const cb of set) cb({ key, value });
|
|
46
29
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
function createModuleConfigRoutes(depsIn) {
|
|
31
|
+
const deps = depsIn;
|
|
32
|
+
if (!deps.projectRoot || !deps.enabled) {
|
|
33
|
+
throw new Error("createModuleConfigRoutes: \u7F3A\u5C11 projectRoot / enabled map");
|
|
34
|
+
}
|
|
35
|
+
const json = deps.json || defaultJson;
|
|
36
|
+
const projectRoot = deps.projectRoot;
|
|
37
|
+
const enabled = deps.enabled;
|
|
38
|
+
return async (ctx) => {
|
|
39
|
+
const { path, method, req, res } = ctx;
|
|
40
|
+
const m = path.match(/^\/api\/sfmc\/configs\/([A-Za-z0-9_-]+)(?:\/(set|notify))?$/);
|
|
41
|
+
if (!m) return false;
|
|
42
|
+
const configKey = m[1];
|
|
43
|
+
const tail = m[2];
|
|
44
|
+
const auth = ctx.moduleAuth;
|
|
45
|
+
if (!auth) {
|
|
46
|
+
jsonV2Fail(res, "unauthorized", 401, "unauthorized");
|
|
47
|
+
return true;
|
|
51
48
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const code = e.name === "PermissionDeniedError" ? 403 : 500;
|
|
101
|
-
jsonV2Fail(res, e.message, code);
|
|
102
|
-
}
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
if (tail === "notify" && method === "GET") {
|
|
106
|
-
// SSE — PoC 把监听留在内存里,不持久化
|
|
107
|
-
res.writeHead(200, {
|
|
108
|
-
"Content-Type": "text/event-stream",
|
|
109
|
-
"Cache-Control": "no-cache",
|
|
110
|
-
Connection: "keep-alive",
|
|
111
|
-
});
|
|
112
|
-
res.write(":ok\n\n");
|
|
113
|
-
const unsub = subscribe(configKey, (payload) => {
|
|
114
|
-
try {
|
|
115
|
-
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
unsub();
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
req.on("close", unsub);
|
|
122
|
-
return true;
|
|
49
|
+
const manifest = enabled.get(auth.id);
|
|
50
|
+
if (!manifest || manifest.configKey !== configKey) {
|
|
51
|
+
jsonV2Fail(res, `\u6A21\u5757 ${auth.id} \u4E0D\u6301\u6709 configKey "${configKey}"`, 403, "forbidden");
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const file = configPath(projectRoot, configKey);
|
|
55
|
+
if (!tail && method === "GET") {
|
|
56
|
+
try {
|
|
57
|
+
assertModulePermission(auth.id, manifest.permissions, Perm.configRead(configKey));
|
|
58
|
+
const cfg = readConfig(file);
|
|
59
|
+
json(res, { config: cfg });
|
|
60
|
+
} catch (e) {
|
|
61
|
+
const code = e.name === "PermissionDeniedError" ? 403 : 500;
|
|
62
|
+
jsonV2Fail(res, e.message, code);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (tail === "set" && method === "POST") {
|
|
67
|
+
try {
|
|
68
|
+
assertModulePermission(auth.id, manifest.permissions, Perm.configWrite(configKey));
|
|
69
|
+
const body = await ctx.body || {};
|
|
70
|
+
const key = String(body.key);
|
|
71
|
+
const value = body.value;
|
|
72
|
+
const cfg = readConfig(file);
|
|
73
|
+
cfg[key] = value;
|
|
74
|
+
writeJson(file, cfg);
|
|
75
|
+
notify(configKey, key, value);
|
|
76
|
+
json(res, { ok: true });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
const code = e.name === "PermissionDeniedError" ? 403 : 500;
|
|
79
|
+
jsonV2Fail(res, e.message, code);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (tail === "notify" && method === "GET") {
|
|
84
|
+
res.writeHead(200, {
|
|
85
|
+
"Content-Type": "text/event-stream",
|
|
86
|
+
"Cache-Control": "no-cache",
|
|
87
|
+
Connection: "keep-alive"
|
|
88
|
+
});
|
|
89
|
+
res.write(":ok\n\n");
|
|
90
|
+
const unsub = subscribe(configKey, (payload) => {
|
|
91
|
+
try {
|
|
92
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
93
|
+
|
|
94
|
+
`);
|
|
95
|
+
} catch {
|
|
96
|
+
unsub();
|
|
123
97
|
}
|
|
124
|
-
|
|
125
|
-
|
|
98
|
+
});
|
|
99
|
+
req.on("close", unsub);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
};
|
|
126
104
|
}
|
|
127
105
|
;
|
|
128
|
-
|
|
106
|
+
export {
|
|
107
|
+
createModuleConfigRoutes
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=module-config-routes.js.map
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/module-config-routes.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * routes/module-config-routes.ts \u2014 \u6A21\u5757\u914D\u7F6E\u6587\u4EF6\u7BA1\u7406\n *\n * \u7AEF\u70B9:\n * GET /api/sfmc/configs/:configKey \u2192 { config: <configs/<configKey>.json> }\n * POST /api/sfmc/configs/:configKey/set \u2192 \u5199\u4E00\u4E2A key\n * GET /api/sfmc/configs/:configKey/notify (SSE:onChange \u63A8\u9001)\n *\n * \u9274\u6743:\u6A21\u5757\u8EAB\u4EFD\u6765\u81EA ctx.moduleAuth;Permission = config:read:configKey / config:write:configKey\u3002\n *\n * \u8BBE\u8BA1:\u6587\u4EF6 = configs/<configKey>.json;\u6574\u6587\u4EF6 read-modify-write(lock by\n * \u7B80\u5355 mutex),\u4E0D\u662F\u7EC6\u7C92\u5EA6 lock \u2014 \u6A21\u5757 config \u6587\u4EF6\u672C\u6765\u5C31\u5C0F;\u5E76\u53D1\u51B2\u7A81\u6982\u7387\u6781\u4F4E\u3002\n */\n\nimport { join } from \"node:path\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { assertModulePermission, Perm } from \"../permission-gate.js\";\nimport type { ModuleManifestV2 } from \"../manifest-loader.js\";\nimport { jsonV2Fail, type ModuleAuth } from \"./_shared.js\";\n\nexport interface ModuleConfigRoutesDeps {\n projectRoot: string;\n enabled: Map<string, ModuleManifestV2>;\n json?: typeof defaultJson;\n}\n\nfunction configPath(projectRoot: string, key: string): string {\n if (!/^[A-Za-z0-9_-]+$/.test(key)) {\n throw new Error(`invalid configKey \"${key}\"`);\n }\n return join(projectRoot, \"configs\", `${key}.json`);\n}\n\nfunction readConfig(file: string): Record<string, unknown> {\n const parsed = readJson<unknown>(file);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n}\n\nconst subscribers = new Map<string, Set<(payload: unknown) => void>>();\n\nfunction subscribe(configKey: string, cb: (payload: unknown) => void): () => void {\n if (!subscribers.has(configKey)) subscribers.set(configKey, new Set());\n subscribers.get(configKey)!.add(cb);\n return () => {\n subscribers.get(configKey)?.delete(cb);\n };\n}\n\nfunction notify(configKey: string, key: string, value: unknown): void {\n const set = subscribers.get(configKey);\n if (!set) return;\n for (const cb of set) cb({ key, value });\n}\n\nexport function createModuleConfigRoutes(depsIn: Partial<ModuleConfigRoutesDeps>) {\n const deps = depsIn as Partial<ModuleConfigRoutesDeps>;\n if (!deps.projectRoot || !deps.enabled) {\n throw new Error(\"createModuleConfigRoutes: \u7F3A\u5C11 projectRoot / enabled map\");\n }\n const json = deps.json || defaultJson;\n const projectRoot = deps.projectRoot;\n const enabled = deps.enabled!;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, req, res } = ctx;\n const m = path.match(/^\\/api\\/sfmc\\/configs\\/([A-Za-z0-9_-]+)(?:\\/(set|notify))?$/);\n if (!m) return false;\n const configKey = m[1];\n const tail = m[2];\n const auth = ctx.moduleAuth;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized\", 401, \"unauthorized\");\n return true;\n }\n const manifest = enabled.get(auth.id);\n if (!manifest || manifest.configKey !== configKey) {\n jsonV2Fail(res, `\u6A21\u5757 ${auth.id} \u4E0D\u6301\u6709 configKey \"${configKey}\"`, 403, \"forbidden\");\n return true;\n }\n\n const file = configPath(projectRoot, configKey);\n\n if (!tail && method === \"GET\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configRead(configKey));\n const cfg = readConfig(file);\n json(res, { config: cfg });\n } catch (e) {\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n // LSP: GET \u5931\u8D25\u4E0E /set \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"set\" && method === \"POST\") {\n try {\n assertModulePermission(auth.id, manifest.permissions, Perm.configWrite(configKey));\n const body = (await (ctx.body as Promise<Record<string, unknown>>)) || {};\n const key = String(body.key);\n const value = body.value;\n const cfg = readConfig(file);\n cfg[key] = value;\n writeJson(file, cfg);\n notify(configKey, key, value);\n json(res, { ok: true });\n } catch (e) {\n // LSP: /set \u6210\u529F\u7528 ok:true,\u5931\u8D25\u7EDF\u4E00 ok:false(\u52FF\u6DF7\u7528 success)\n const code = (e as { name?: string }).name === \"PermissionDeniedError\" ? 403 : 500;\n jsonV2Fail(res, (e as Error).message, code);\n }\n return true;\n }\n\n if (tail === \"notify\" && method === \"GET\") {\n // SSE \u2014 PoC \u628A\u76D1\u542C\u7559\u5728\u5185\u5B58\u91CC,\u4E0D\u6301\u4E45\u5316\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\":ok\\n\\n\");\n const unsub = subscribe(configKey, (payload) => {\n try {\n res.write(`data: ${JSON.stringify(payload)}\\n\\n`);\n } catch {\n unsub();\n }\n });\n req.on(\"close\", unsub);\n return true;\n }\n\n return false;\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -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,iBAAS,kBAAkB,CAAC,EAC1B,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,IAAI,GACL,EAAE,IAAI,yCAOF;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAqErB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/routes/modules.js
CHANGED
|
@@ -1,92 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const e = error;
|
|
55
|
-
if (e.code === "dependency_unmet") {
|
|
56
|
-
json(res, { success: false, error: "dependency_unmet", unmet: e.unmet }, 409);
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
json(res, { success: true, module: current() });
|
|
62
|
-
}
|
|
63
|
-
else if (action === "enable" && method === "POST") {
|
|
64
|
-
try {
|
|
65
|
-
setModuleEnabled(module, true);
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
const e = error;
|
|
69
|
-
if (e.code === "dependency_unmet") {
|
|
70
|
-
json(res, { success: false, error: "dependency_unmet", unmet: e.unmet }, 409);
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
json(res, { success: true, module: current() });
|
|
76
|
-
}
|
|
77
|
-
else if (action === "disable" && method === "POST") {
|
|
78
|
-
if (!module.canDisable) {
|
|
79
|
-
json(res, { success: false, error: "module_cannot_disable" }, 400);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
setModuleEnabled(module, false);
|
|
83
|
-
json(res, { success: true, module: current() });
|
|
1
|
+
function createModuleRoutes({
|
|
2
|
+
loadModuleCatalog,
|
|
3
|
+
buildModuleList,
|
|
4
|
+
resolveModuleByKey,
|
|
5
|
+
setModuleEnabled,
|
|
6
|
+
body,
|
|
7
|
+
json
|
|
8
|
+
}) {
|
|
9
|
+
return async function handleModuleRoute({
|
|
10
|
+
path,
|
|
11
|
+
method,
|
|
12
|
+
params,
|
|
13
|
+
req,
|
|
14
|
+
res
|
|
15
|
+
}) {
|
|
16
|
+
void params;
|
|
17
|
+
if (path === "/api/sfmc/modules/catalog") {
|
|
18
|
+
if (method === "GET") {
|
|
19
|
+
json(res, { modules: loadModuleCatalog() });
|
|
20
|
+
} else {
|
|
21
|
+
json(res, { success: false, error: "not_found" }, 404);
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (path === "/api/sfmc/modules") {
|
|
26
|
+
if (method === "GET") json(res, { modules: buildModuleList() });
|
|
27
|
+
else json(res, { success: false, error: "not_found" }, 404);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (!path.startsWith("/api/sfmc/modules/")) return false;
|
|
31
|
+
const rest = path.slice("/api/sfmc/modules/".length);
|
|
32
|
+
const [rawKey, action] = rest.split("/");
|
|
33
|
+
const module = resolveModuleByKey(decodeURIComponent(rawKey ?? ""));
|
|
34
|
+
if (!module) {
|
|
35
|
+
json(res, { success: false, error: "module_not_found" }, 404);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const current = () => buildModuleList().find((entry) => entry.id === module.id);
|
|
39
|
+
if (!action && method === "GET") {
|
|
40
|
+
json(res, { module: current() ?? null });
|
|
41
|
+
} else if (!action && (method === "PATCH" || method === "PUT")) {
|
|
42
|
+
const { enabled } = await body(req);
|
|
43
|
+
if (!enabled && !module.canDisable) {
|
|
44
|
+
json(res, { success: false, error: "module_cannot_disable" }, 400);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
setModuleEnabled(module, !!enabled);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const e = error;
|
|
51
|
+
if (e.code === "dependency_unmet") {
|
|
52
|
+
json(res, { success: false, error: "dependency_unmet", unmet: e.unmet }, 409);
|
|
53
|
+
return true;
|
|
84
54
|
}
|
|
85
|
-
|
|
86
|
-
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
json(res, { success: true, module: current() });
|
|
58
|
+
} else if (action === "enable" && method === "POST") {
|
|
59
|
+
try {
|
|
60
|
+
setModuleEnabled(module, true);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const e = error;
|
|
63
|
+
if (e.code === "dependency_unmet") {
|
|
64
|
+
json(res, { success: false, error: "dependency_unmet", unmet: e.unmet }, 409);
|
|
65
|
+
return true;
|
|
87
66
|
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
json(res, { success: true, module: current() });
|
|
70
|
+
} else if (action === "disable" && method === "POST") {
|
|
71
|
+
if (!module.canDisable) {
|
|
72
|
+
json(res, { success: false, error: "module_cannot_disable" }, 400);
|
|
88
73
|
return true;
|
|
89
|
-
|
|
74
|
+
}
|
|
75
|
+
setModuleEnabled(module, false);
|
|
76
|
+
json(res, { success: true, module: current() });
|
|
77
|
+
} else {
|
|
78
|
+
json(res, { success: false, error: "not_found" }, 404);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
90
82
|
}
|
|
91
|
-
export {
|
|
92
|
-
|
|
83
|
+
export {
|
|
84
|
+
createModuleRoutes
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/modules.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * routes/modules.ts \u2014 \u6A21\u5757\u76EE\u5F55\u4E0E\u542F\u505C\uFF08\u57FA\u4E8E modules/catalog.json + module-lock.json\uFF09\n *\n * \u8DEF\u7531\u5217\u8868\uFF1A\n * GET /api/sfmc/modules \u2014 \u6A21\u5757\u5217\u8868\uFF08\u542B enable \u72B6\u6001\uFF09\n * GET /api/sfmc/modules/catalog \u2014 \u539F\u59CB catalog \u5217\u8868\n * GET /api/sfmc/modules/:key \u2014 \u5355\u4E2A\u6A21\u5757\u8BE6\u60C5\n * PATCH/PUT /api/sfmc/modules/:key \u2014 \u8BBE\u7F6E enabled\n * POST /api/sfmc/modules/:key/enable \u2014 \u542F\u7528\u6A21\u5757\n * POST /api/sfmc/modules/:key/disable \u2014 \u7981\u7528\u6A21\u5757\n */\n\ninterface Deps {\n loadModuleCatalog: () => unknown[];\n buildModuleList: () => Array<Record<string, unknown>>;\n resolveModuleByKey: (key: string) => { id: string; canDisable: boolean } | null;\n setModuleEnabled: (\n module: { id: string; canDisable: boolean },\n enabled: boolean\n ) => void;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\nfunction createModuleRoutes({\n loadModuleCatalog,\n buildModuleList,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n}: Deps) {\n return async function handleModuleRoute({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n void params; // unused\n if (path === \"/api/sfmc/modules/catalog\") {\n if (method === \"GET\") {\n json(res, { modules: loadModuleCatalog() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n }\n if (path === \"/api/sfmc/modules\") {\n if (method === \"GET\") json(res, { modules: buildModuleList() });\n else json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (!path.startsWith(\"/api/sfmc/modules/\")) return false;\n\n const rest = path.slice(\"/api/sfmc/modules/\".length);\n const [rawKey, action] = rest.split(\"/\");\n const module = resolveModuleByKey(decodeURIComponent(rawKey ?? \"\"));\n if (!module) {\n json(res, { success: false, error: \"module_not_found\" }, 404);\n return true;\n }\n\n const current = () => buildModuleList().find((entry) => entry.id === module.id);\n if (!action && method === \"GET\") {\n json(res, { module: current() ?? null });\n } else if (!action && (method === \"PATCH\" || method === \"PUT\")) {\n const { enabled } = await body(req);\n if (!enabled && !module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n try {\n setModuleEnabled(module, !!enabled);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"enable\" && method === \"POST\") {\n try {\n setModuleEnabled(module, true);\n } catch (error) {\n const e = error as { code?: string; unmet?: Array<{ id: string }> };\n if (e.code === \"dependency_unmet\") {\n json(res, { success: false, error: \"dependency_unmet\", unmet: e.unmet }, 409);\n return true;\n }\n throw error;\n }\n json(res, { success: true, module: current() });\n } else if (action === \"disable\" && method === \"POST\") {\n if (!module.canDisable) {\n json(res, { success: false, error: \"module_cannot_disable\" }, 400);\n return true;\n }\n setModuleEnabled(module, false);\n json(res, { success: true, module: current() });\n } else {\n json(res, { success: false, error: \"not_found\" }, 404);\n }\n return true;\n };\n}\n\nexport { createModuleRoutes };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-routes.d.ts","sourceRoot":"","sources":["../../src/routes/service-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,uBAAuB,EAAE,gBAAgB,CAAC,CAAC;IACvE,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"service-routes.d.ts","sourceRoot":"","sources":["../../src/routes/service-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,uBAAuB,EAAE,gBAAgB,CAAC,CAAC;IACvE,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,SAOjD;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CAsDrB"}
|