Files
OJ2/apps/api/src/db/index.ts
T
xuyueandClaude Opus 5.5 b38ad8ccee feat(配置): 启动时用 zod 一次解析环境变量,有错拒绝启动
原来是 `Number(env ?? 默认值)` 散写:JUDGE_CONCURRENCY=two 得到 NaN 照样起;
DATABASE_URL 漏配就静默连上开发库的默认账号;AI_KEY 缺了要等第一次 AI 调用才报错。

- config.ts 一张 envSchema 覆盖全部变量,失败时列出所有问题后 exit 1
- 编译形态(线上)下 DATABASE_URL / REDIS_URL / JUDGE_SERVER_TOKEN 必须显式给,
  dev 默认值指向本机 5433 / 6380,放进容器里只会连不上或连错
- 空字符串按未设置处理(compose 的 ${X:-} 展开就是空串)
- AI_KEY 为空启动时告警(AI 是可选功能,不退出)
- db/index.ts 改读 config.databaseUrl;flowchart/run.ts 的 provider / model 改读 config

实跑核过:非法值被列出并拒绝启动;编译产物在仓库外无 env 时报三项必填,
给齐后 /health 正常;dev 的 serve / worker 照常起。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:53:51 +08:00

34 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { config } from "../config"
import * as schema from "./schema"
// 不设会话时区:日历语义的 SQL 一律显式 `at time zone`(`../time` 的 localTime),
// 不靠会话默认值兜底 —— 兜底会把漏写的地方在线上掩盖掉,dev 上又是另一个答案。
const client = postgres(config.databaseUrl)
export const db = drizzle(client, { schema })
/**
* 读出来的时刻统一成 ISO 8601 UTC,和写侧的 `new Date().toISOString()` 同形状。
*
* drizzle 的 `construct()`(`drizzle-orm/postgres-js/driver.js`)把 1184(timestamptz) 等
* OID 的 parser 换成了恒等函数,不处理的话读出来是 PG 文本(`2026-09-14 20:00:00+08`),
* 接口上同一个字段就有两种形状。所以**必须在 `drizzle(client)` 之后**覆盖回来。
*
* - **只换 1184。** 1082(date) 要的就是 `2026-09-14`;全库时间列都是 timestamptz。
* - **`::text` 的 OID 是 25,绕过这里**:别再为了拿字符串形状给时间列加 `::text`。
* - **保留微秒。** `Date` 只到毫秒,而 Django 时代的提交几乎全带微秒;读出的时刻常被
* 原样塞回查询条件(提交列表翻页的分界行、班级 AC 排名的 `<= min(create_time)`),
* 截掉会让分界行把自己排除。所以偏移换算交给 `Date`(先去掉小数,免得进位),
* 小数位原文拼回去、至少补足 3 位。Bun、老 Chrome 和 date-fns 都能解析 6 位小数。
*/
client.options.parsers[1184] = (value: string) => {
const fraction = /\.\d+/.exec(value)?.[0]
if (!fraction) return new Date(value).toISOString()
return `${new Date(value.replace(fraction, "")).toISOString().slice(0, 19)}${fraction.padEnd(4, "0")}Z`
}
export { schema }