This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/apps/api/src/app/config.ts
T

78 lines
3.1 KiB
TypeScript

/**
* 环境配置 —— 只在 app/ 层读环境变量;模块内一律经 port 注入(§5.3)。
*/
import { join } from 'node:path';
/** 统一鉴权中心(ADR-012)。本服务只验不调,**不配 client secret**(手册 13.2) */
export type KeycloakConfig = {
readonly url: string;
readonly realm: string;
/** 校 aud 用 —— 填错全线 401 */
readonly clientId: string;
readonly issuer: string;
readonly jwksUrl: string;
/** master realm 的 provisioning 服务账号(只读名册,view-users);可选 */
readonly provisioning?: { readonly clientId: string; readonly secret: string };
};
export type AppConfig = {
readonly nodeEnv: 'development' | 'test' | 'production';
readonly port: number;
readonly databaseUrl: string;
/** 原件存档根目录 —— 信封的原件指针指向这里 */
readonly attachmentRoot: string;
/** 派发器轮询间隔(毫秒) */
readonly dispatchIntervalMs: number;
/** 邮件轮询工作者的 tick(毫秒);每个账号自己的间隔另算 */
readonly mailPollTickMs: number;
/** 三样齐了才有;缺一样即 undefined —— main.ts 据此拒绝启动,集成测试可不配 */
readonly keycloak?: KeycloakConfig;
/** 测试账号(Keycloak 用户名):首次建档 / 播种时落 TEST 岗 + 独立工作组 */
readonly testAccounts: readonly string[];
};
const DEFAULT_DATABASE_URL = 'postgres://jcai:jcai@localhost:54329/jcai_wos';
function nonEmpty(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed === undefined || trimmed === '' ? undefined : trimmed;
}
function keycloakOf(env: NodeJS.ProcessEnv): KeycloakConfig | undefined {
const url = nonEmpty(env['KEYCLOAK_URL'])?.replace(/\/+$/, '');
const realm = nonEmpty(env['KEYCLOAK_REALM']);
const clientId = nonEmpty(env['KEYCLOAK_CLIENT_ID']);
if (url === undefined || realm === undefined || clientId === undefined) return undefined;
const issuer = `${url}/realms/${realm}`;
const provisioningId = nonEmpty(env['KEYCLOAK_PROVISIONING_CLIENT_ID']);
const provisioningSecret = nonEmpty(env['KEYCLOAK_PROVISIONING_CLIENT_SECRET']);
return {
url,
realm,
clientId,
issuer,
jwksUrl: `${issuer}/protocol/openid-connect/certs`,
...(provisioningId === undefined || provisioningSecret === undefined
? {}
: { provisioning: { clientId: provisioningId, secret: provisioningSecret } }),
};
}
export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
const nodeEnv = env['NODE_ENV'];
const keycloak = keycloakOf(env);
return {
nodeEnv: nodeEnv === 'production' || nodeEnv === 'test' ? nodeEnv : 'development',
port: Number(env['PORT'] ?? 8787),
databaseUrl: env['DATABASE_URL'] ?? DEFAULT_DATABASE_URL,
attachmentRoot: env['JCAI_ATTACHMENT_ROOT'] ?? join(process.cwd(), '.data', 'attachments'),
dispatchIntervalMs: Number(env['JCAI_DISPATCH_INTERVAL_MS'] ?? 2000),
mailPollTickMs: Number(env['JCAI_MAIL_POLL_TICK_MS'] ?? 5000),
...(keycloak === undefined ? {} : { keycloak }),
testAccounts: (env['JCAI_TEST_ACCOUNTS'] ?? '')
.split(',')
.map((s) => s.trim())
.filter((s) => s !== ''),
};
}