feat(阶段3): oj 侧端点铺开(基线提交,未经评审)

由外部 agent (Codex) 在本会话额度中断期间完成。原样提交作为基线,
后续修复单独成 commit,便于区分与回退。

覆盖 oj 侧 65 个端点,新增 9 组路由(account/achievement/ai/classroom/
content/contest/flowchart/problemset/site)与对应 Zod 契约。

已核验:tsc --noEmit 退出码 0;API 可启动;/api/problems 返回真实数据;
judge 与 flowchart worker 均 ready。
未核验:权限边界与数据泄露,评审进行中。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 01:25:36 -06:00
parent ec274419c3
commit 8c00cdc947
52 changed files with 4273 additions and 300 deletions

View File

@@ -1,11 +1,21 @@
import { Hono } from "hono"
import { basename, resolve } from "node:path"
import { getRequestSessionUser } from "./auth/session"
import { config } from "./config"
import { authRoutes } from "./routes/auth"
import { accountRoutes } from "./routes/account"
import { judgeServerRoutes } from "./routes/judge-server"
import { contestRoutes } from "./routes/contest"
import { contentRoutes } from "./routes/content"
import { classroomRoutes } from "./routes/classroom"
import { problemsetRoutes } from "./routes/problemset"
import { achievementRoutes } from "./routes/achievement"
import { aiRoutes } from "./routes/ai"
import { flowchartRoutes } from "./routes/flowchart"
import { problemRoutes } from "./routes/problem"
import { submissionRoutes } from "./routes/submission"
import { siteRoutes } from "./routes/site"
import {
bridgeSubmissionEvents,
submissionWebSocketHandler,
@@ -16,6 +26,15 @@ const app = new Hono()
app.get("/health", (c) => c.json({ ok: true }))
app.route("/api", authRoutes)
app.route("/api", accountRoutes)
app.route("/api", siteRoutes)
app.route("/api", contestRoutes)
app.route("/api", contentRoutes)
app.route("/api", classroomRoutes)
app.route("/api", problemsetRoutes)
app.route("/api", achievementRoutes)
app.route("/api", aiRoutes)
app.route("/api", flowchartRoutes)
app.route("/api", problemRoutes)
app.route("/api", submissionRoutes)
app.route("/api", judgeServerRoutes)
@@ -32,6 +51,21 @@ const server = Bun.serve<SubmissionSocketData>({
port: config.port,
async fetch(request, bunServer) {
const url = new URL(request.url)
if (url.pathname.startsWith(`${config.avatarUriPrefix}/`)) {
const filename = basename(decodeURIComponent(url.pathname))
if (filename !== decodeURIComponent(url.pathname).split("/").at(-1)) {
return new Response("Not found", { status: 404 })
}
const file = Bun.file(resolve(config.avatarDirectory, filename))
if (await file.exists()) return new Response(file)
if (filename === "default.png") {
return new Response(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><rect width="128" height="128" rx="64" fill="#e2e8f0"/><circle cx="64" cy="48" r="24" fill="#94a3b8"/><path d="M20 120c4-28 22-42 44-42s40 14 44 42" fill="#94a3b8"/></svg>',
{ headers: { "content-type": "image/svg+xml", "cache-control": "public, max-age=3600" } },
)
}
return new Response("Not found", { status: 404 })
}
if (url.pathname === "/ws/submissions") {
const user = await getRequestSessionUser(request)
if (!user) return new Response("Unauthorized", { status: 401 })