feat(server): ServerModule skeleton with shared ServerContext, worker runs engine + outbox relay (B6)
modules/{auth-settings,sources-identity,acquisition-organize,agent} placeholders listed in
SERVER_MODULES; ServerModule registers routes (under /api/v1, authenticated), tasks,
outbox routes, capabilities and background loops. bootstrap() builds ServerContext (db,
config, logger, events, engine, outbox writer, secrets placeholder, settings reader,
capability registry), fills unregistered tasks with stubs and rejects duplicate task or
outbox ownership. worker starts the OpenWorkflow worker, OutboxRelay and module start().
--role migrate also migrates the engine schema; api/worker connect without migrating.
tasks: TaskRegistry.has, migrateEngineSchema.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ef3998767c
commit
9858b8494d
23 files changed
+829
-22
No files matched your search
@@ -0,0 +1,12 @@
|
||||
import type { ServerModule } from "../types.ts";
|
||||
|
||||
/**
|
||||
* W2-A 认证/设置/实例/引擎面板:管理员登录门禁、settings、api_key、secret 加密服务、
|
||||
* downloader/media_server 实例与 library_root/path_mapping、审计查询、SSE 总线改 PG 通知、引擎面板只读 API、自动化总开关。
|
||||
* secrets / settings 的实现放在 ./provided.ts(context 从这里取),SSE 总线在 http/sse.ts,鉴权在 http/auth.ts。
|
||||
*/
|
||||
export const authSettingsModule: ServerModule = {
|
||||
name: "auth-settings",
|
||||
registerRoutes() {},
|
||||
registerTasks() {},
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { DEFAULT_SETTINGS, GlobalSettings, type SettingsKey } from "@umt/contracts";
|
||||
import type { DB, Kysely } from "@umt/db";
|
||||
import type { Logger } from "pino";
|
||||
import type { SecretStore, SettingsReader } from "../types.ts";
|
||||
|
||||
// auth-settings 模块向 ServerContext 提供的基础服务。W2-A 在这里替换实现,其它模块只用 ctx 上的接口。
|
||||
|
||||
/** 占位:W2-A 实现 AES-GCM 加密存取(secret 表,SECRET_MASTER_KEY) */
|
||||
export function createSecretStore(_deps: { db: Kysely<DB>; masterKey: string }): SecretStore {
|
||||
const notYet = async (): Promise<never> => {
|
||||
throw new Error("SecretStore 尚未实现(W2-A)");
|
||||
};
|
||||
return { resolve: notYet, create: notYet, replace: notYet, remove: notYet };
|
||||
}
|
||||
|
||||
/** settings 表(key → jsonb)按分组合并缺省值;每次读库,不缓存(设置变更立即生效) */
|
||||
export function createSettingsReader(deps: { db: Kysely<DB>; logger: Logger }): SettingsReader {
|
||||
const all = async (): Promise<GlobalSettings> => {
|
||||
const rows = await deps.db.selectFrom("settings").select(["key", "value"]).execute();
|
||||
const stored = new Map(rows.map((r) => [r.key, r.value]));
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const key of Object.keys(DEFAULT_SETTINGS) as SettingsKey[]) {
|
||||
const def = DEFAULT_SETTINGS[key];
|
||||
const v = stored.get(key);
|
||||
const merged = v && typeof v === "object" && !Array.isArray(v) ? { ...def, ...(v as object) } : def;
|
||||
const parsed = GlobalSettings.shape[key].safeParse(merged);
|
||||
if (!parsed.success) deps.logger.warn({ key }, "settings 值不合法,回退缺省");
|
||||
out[key] = parsed.success ? parsed.data : def;
|
||||
}
|
||||
return out as GlobalSettings;
|
||||
};
|
||||
return { all, get: async (key) => (await all())[key] };
|
||||
}
|
||||
Reference in new issue
Block a user