fix(阶段3): 补齐 contestId 脱敏与仓库根 .env 加载

两处是首轮六条修复的收尾,均由验证过程暴露:

1. contestId 未脱敏 —— 旧后端 SubmissionSafeModelSerializer 排除的是
   info/contest/ip 三个字段,首轮只处理了 info 与 ip。

2. 根 .env 读不到 —— Bun 只自动加载 cwd 下的 .env,而启动方式
   (bun run --filter '@oj2/api' dev) 会把 cwd 切到 apps/api/,
   于是 .env.example 教人写在仓库根的 JUDGE_SERVER_TOKEN 静默失效,
   后端降级成随机 token,判题全部卡在 PENDING。config 改为显式补读
   根 .env,且只填充未设置的键(真实环境变量与 cwd 下的 .env 优先)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 02:12:31 -06:00
parent b7adf2993d
commit f548aef9f0
2 changed files with 31 additions and 1 deletions

View File

@@ -1,4 +1,32 @@
import { randomBytes } from "node:crypto" import { randomBytes } from "node:crypto"
import { readFileSync } from "node:fs"
import { resolve } from "node:path"
/**
* Bun 只自动加载「当前工作目录」下的 .env。而本应用的启动方式`bun run --filter '@oj2/api' dev`
* 会把 cwd 切到 apps/api/,于是仓库根的 .env 读不到 —— 而 .env.example 恰恰教人写在根目录。
* 这里显式补读仓库根的 .env让文档指引真正生效且不管从哪个目录启动都一致。
*
* 只填充尚未设置的键:真实环境变量与 cwd 下的 .env 优先级更高,不被覆盖。
*/
function loadRepoRootEnv() {
try {
const text = readFileSync(resolve(import.meta.dir, "../../../.env"), "utf8")
for (const line of text.split("\n")) {
const trimmed = line.trim()
if (!trimmed || trimmed.startsWith("#")) continue
const eq = trimmed.indexOf("=")
if (eq <= 0) continue
const key = trimmed.slice(0, eq).trim()
if (process.env[key] !== undefined) continue
process.env[key] = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "")
}
} catch {
// 根目录没有 .env 是正常情况(例如生产用真实环境变量注入),静默跳过
}
}
loadRepoRootEnv()
/** /**
* 判题机 token。对齐旧后端 `options/options.py:93` * 判题机 token。对齐旧后端 `options/options.py:93`

View File

@@ -209,7 +209,9 @@ async function submissionDetail(id: string, user: AuthUser) {
shared: row.submission.shared, shared: row.submission.shared,
statisticInfo: objectValue(row.submission.statisticInfo), statisticInfo: objectValue(row.submission.statisticInfo),
ip: full ? row.submission.ip : null, ip: full ? row.submission.ip : null,
contestId: row.submission.contestId, // contest 也在旧后端的排除名单里exclude 的三个字段是 info / contest / ip
// 首轮修复只处理了 info 与 ip这里补齐。
contestId: full ? row.submission.contestId : null,
problemId: row.submission.problemId, problemId: row.submission.problemId,
showLink: true, showLink: true,
canUnshare: canViewSubmission(user, row.submission, row.problem, row.contest, false), canUnshare: canViewSubmission(user, row.submission, row.problem, row.contest, false),