refactor(api): 请求体解析收成 readJson / parseBody

54 处 handler 都手写同一段:
  const parsed = X.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) return failure(c, 400, "invalid-request", ...)
漏写 .catch 就是一个畸形请求打出 500。

- http.ts 加 readJson(读 + 校验)和 parseBody(再加上 400)。parseBody 不给
  message 时回 zod 的第一条 issue —— 后台表单原来就是这么写的,fallback 文案
  「Invalid payload」/「参数错误」只在 issues 为空时才用得上,实际走不到
- 51 处换成 parseBody;另 3 处失败时带别的错误码或和路径参数一起判,换成 readJson
- 错误码、状态码、文案、判断顺序都不变

实跑核过:非 JSON 请求体回原来的固定文案,公告标题为空回 zod issue。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-27 15:04:19 +08:00
1 parent c43da01ff2
commit f2ca34fd4b
21 files changed
+234 -432

No files matched your search

+7 -28
View File
@@ -33,7 +33,7 @@ import { isUserOnline, onlineUserIds } from "../../auth/presence"
import { revokeUserSessions } from "../../auth/session"
import { requireSuperAdmin, type AppEnv } from "../../auth/middleware"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { queryInteger, sampleUser } from "../helpers"
export const adminAccountRoutes = new Hono<AppEnv>()
@@ -252,17 +252,8 @@ adminAccountRoutes.get("/users/:id", requireSuperAdmin, async (c) => {
adminAccountRoutes.put("/users/:id", requireSuperAdmin, async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateUserRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateUserRequestSchema)
if (!parsed.success) return parsed.response
const data = parsed.data
const [existing] = await selectUser(id)
if (!existing) return failure(c, 404, "user-not-found", "User does not exist")
@@ -350,17 +341,8 @@ adminAccountRoutes.put("/users/:id", requireSuperAdmin, async (c) => {
})
adminAccountRoutes.post("/users", requireSuperAdmin, async (c) => {
const parsed = importUsersRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, importUsersRequestSchema)
if (!parsed.success) return parsed.response
const rows = parsed.data.users
type Prepared = {
username: string
@@ -544,11 +526,8 @@ function isForeignKeyViolation(error: unknown) {
class UserHasSubmissionsError extends Error {}
adminAccountRoutes.delete("/users", requireSuperAdmin, async (c) => {
const parsed = deleteUsersRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "ids is required")
const parsed = await parseBody(c, deleteUsersRequestSchema, "ids is required")
if (!parsed.success) return parsed.response
const me = c.get("user")!.id
if (parsed.data.ids.includes(me)) {
return failure(
+5 -23
View File
@@ -9,7 +9,7 @@ import { Hono } from "hono"
import { requireSuperAdmin, type AppEnv } from "../../auth/middleware"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import {
ACHIEVEMENT_METRICS,
findMetric,
@@ -72,17 +72,8 @@ adminAchievementRoutes.get(
)
adminAchievementRoutes.post("/achievements", requireSuperAdmin, async (c) => {
const parsed = createAchievementRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, createAchievementRequestSchema)
if (!parsed.success) return parsed.response
if (!findMetric(parsed.data.metric))
return failure(c, 400, "invalid-metric", "指标不存在")
@@ -111,17 +102,8 @@ adminAchievementRoutes.put(
requireSuperAdmin,
async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateAchievementRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, updateAchievementRequestSchema)
if (!parsed.success) return parsed.response
if (!findMetric(parsed.data.metric))
return failure(c, 400, "invalid-metric", "指标不存在")
+5 -23
View File
@@ -9,7 +9,7 @@ import { Hono } from "hono"
import { requireSuperAdmin, type AppEnv } from "../../auth/middleware"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { queryInteger, sampleUser } from "../helpers"
export const adminAnnouncementRoutes = new Hono<AppEnv>()
@@ -78,17 +78,8 @@ adminAnnouncementRoutes.get("/announcements", requireSuperAdmin, async (c) => {
})
adminAnnouncementRoutes.post("/announcements", requireSuperAdmin, async (c) => {
const parsed = createAnnouncementRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, createAnnouncementRequestSchema)
if (!parsed.success) return parsed.response
const now = new Date().toISOString()
const [created] = await db
.insert(schema.announcement)
@@ -126,17 +117,8 @@ adminAnnouncementRoutes.put(
requireSuperAdmin,
async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateAnnouncementRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateAnnouncementRequestSchema)
if (!parsed.success) return parsed.response
const updated = await db
.update(schema.announcement)
.set({ ...parsed.data, lastUpdateTime: new Date().toISOString() })
+8 -16
View File
@@ -22,7 +22,7 @@ import {
import { config } from "../../config"
import { db, schema } from "../../db"
import { publishConfigUpdate } from "../../events"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { getWebsiteOptions } from "../../services/options"
import { todayStart } from "../../time"
import { queryInteger } from "../helpers"
@@ -77,17 +77,8 @@ adminConfRoutes.get("/website", requireSuperAdmin, async (c) => {
})
adminConfRoutes.post("/website", requireSuperAdmin, async (c) => {
const parsed = updateWebsiteConfigRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateWebsiteConfigRequestSchema)
if (!parsed.success) return parsed.response
const entries = (
Object.entries(OPTION_KEYS) as [keyof typeof OPTION_KEYS, string][]
).map(([field, key]) => ({ field, key, value: parsed.data[field] }))
@@ -131,11 +122,12 @@ adminConfRoutes.get("/judge-servers", requireSuperAdmin, async (c) => {
})
adminConfRoutes.put("/judge-servers/:id", requireSuperAdmin, async (c) => {
const parsed = updateJudgeServerRequestSchema.safeParse(
await c.req.json().catch(() => null),
const parsed = await parseBody(
c,
updateJudgeServerRequestSchema,
"isDisabled is required",
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "isDisabled is required")
if (!parsed.success) return parsed.response
const updated = await db
.update(schema.judgeServer)
.set({ isDisabled: parsed.data.isDisabled })
+7 -34
View File
@@ -12,7 +12,7 @@ import { Hono } from "hono"
import { requireTeacher, type AppEnv } from "../../auth/middleware"
import type { AuthUser } from "../../auth/session"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { contestStatus } from "../../services/contest"
import { objectValue, queryInteger, sampleUser } from "../helpers"
@@ -120,17 +120,8 @@ adminContestRoutes.get("/contests/:id", requireTeacher, async (c) => {
})
adminContestRoutes.post("/contests", requireTeacher, async (c) => {
const parsed = createContestRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, createContestRequestSchema)
if (!parsed.success) return parsed.response
const error = validatePayload(parsed.data)
if (error) return failure(c, 400, "invalid-contest", error)
@@ -157,17 +148,8 @@ adminContestRoutes.post("/contests", requireTeacher, async (c) => {
adminContestRoutes.put("/contests/:id", requireTeacher, async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateContestRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateContestRequestSchema)
if (!parsed.success) return parsed.response
const [existing] = await selectContest(id)
if (!existing || !ownedBy(c.get("user")!, existing.contest)) {
return failure(c, 404, "contest-not-found", "Contest does not exist")
@@ -386,17 +368,8 @@ adminContestRoutes.put(
requireTeacher,
async (c) => {
const contestId = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateAcmHelperRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateAcmHelperRequestSchema)
if (!parsed.success) return parsed.response
const [contest] = await db
.select()
.from(schema.contest)
+18 -72
View File
@@ -32,7 +32,7 @@ import { Hono } from "hono"
import { requireProblemPermission, type AppEnv } from "../../auth/middleware"
import type { AuthUser } from "../../auth/session"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { astRulesError, pickAstRules } from "../../judge/ast"
import { buildSqlDisplay } from "../../judge/sql"
import { completeChat } from "../../services/ai"
@@ -432,17 +432,8 @@ adminProblemRoutes.get("/problems/:id", requireProblemPermission, async (c) => {
})
adminProblemRoutes.post("/problems", requireProblemPermission, async (c) => {
const parsed = createProblemRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, createProblemRequestSchema)
if (!parsed.success) return parsed.response
const checked = commonChecks(parsed.data)
if ("error" in checked)
return failure(c, 400, "invalid-problem", checked.error)
@@ -495,17 +486,8 @@ adminProblemRoutes.post("/problems", requireProblemPermission, async (c) => {
adminProblemRoutes.put("/problems/:id", requireProblemPermission, async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateProblemRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, updateProblemRequestSchema)
if (!parsed.success) return parsed.response
const [existing] = await db
.select()
.from(schema.problem)
@@ -702,17 +684,8 @@ adminProblemRoutes.post(
) {
return failure(c, 404, "contest-not-found", "Contest does not exist")
}
const parsed = createProblemRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, createProblemRequestSchema)
if (!parsed.success) return parsed.response
const checked = commonChecks(parsed.data)
if ("error" in checked)
return failure(c, 400, "invalid-problem", checked.error)
@@ -774,12 +747,12 @@ adminProblemRoutes.post(
requireProblemPermission,
async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = makeProblemPublicRequestSchema.safeParse(
await c.req.json().catch(() => null),
const parsed = await parseBody(
c,
makeProblemPublicRequestSchema,
"displayId 不能为空",
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "displayId 不能为空")
if (!parsed.success) return parsed.response
const [problem] = await db
.select()
.from(schema.problem)
@@ -856,17 +829,8 @@ adminProblemRoutes.post(
requireProblemPermission,
async (c) => {
const contestId = queryInteger(c.req.param("contestId"), 0, { min: 1 })
const parsed = addContestProblemRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, addContestProblemRequestSchema)
if (!parsed.success) return parsed.response
const [contest] = await db
.select()
.from(schema.contest)
@@ -1055,17 +1019,8 @@ adminProblemRoutes.post(
"/sql-test-cases/preview",
requireProblemPermission,
async (c) => {
const parsed = sqlPreviewRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, sqlPreviewRequestSchema)
if (!parsed.success) return parsed.response
const outcome = await buildSqlDisplay(
parsed.data.initSql,
parsed.data.refSql,
@@ -1082,17 +1037,8 @@ adminProblemRoutes.post(
"/sql-test-cases/generate",
requireProblemPermission,
async (c) => {
const parsed = generateSqlTestCaseRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, generateSqlTestCaseRequestSchema)
if (!parsed.success) return parsed.response
try {
const sql = await completeChat(
`你是一个 SQL 出题助手。用户会给你一道 SQL 题的标准答案(查询题的
+21 -63
View File
@@ -29,7 +29,7 @@ import { Hono } from "hono"
import { requireTeacher, type AppEnv } from "../../auth/middleware"
import type { AuthUser } from "../../auth/session"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { recalculateBadge, resyncProgress } from "../../services/problemset"
import { asFilterValue, queryInteger, sampleUser } from "../helpers"
@@ -175,17 +175,8 @@ adminProblemSetRoutes.get("/problem-sets", requireTeacher, async (c) => {
})
adminProblemSetRoutes.post("/problem-sets", requireTeacher, async (c) => {
const parsed = createProblemSetRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, createProblemSetRequestSchema)
if (!parsed.success) return parsed.response
const now = new Date().toISOString()
const [created] = await db
.insert(schema.problemset)
@@ -211,17 +202,8 @@ adminProblemSetRoutes.get("/problem-sets/:id", requireTeacher, async (c) => {
adminProblemSetRoutes.put("/problem-sets/:id", requireTeacher, async (c) => {
const row = await loadOwned(c, c.get("user")!)
if (!row) return failure(c, 404, "problem-set-not-found", "题单不存在")
const parsed = updateProblemSetRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, updateProblemSetRequestSchema)
if (!parsed.success) return parsed.response
const [updated] = await db
.update(schema.problemset)
.set({
@@ -258,11 +240,12 @@ adminProblemSetRoutes.put(
async (c) => {
const row = await loadOwned(c, c.get("user")!)
if (!row) return failure(c, 404, "problem-set-not-found", "题单不存在")
const parsed = updateProblemSetStatusRequestSchema.safeParse(
await c.req.json().catch(() => null),
const parsed = await parseBody(
c,
updateProblemSetStatusRequestSchema,
"status 不合法",
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "status 不合法")
if (!parsed.success) return parsed.response
const [updated] = await db
.update(schema.problemset)
.set({
@@ -331,17 +314,8 @@ adminProblemSetRoutes.post(
async (c) => {
const row = await loadOwned(c, c.get("user")!)
if (!row) return failure(c, 404, "problem-set-not-found", "题单不存在")
const parsed = addProblemToSetRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, addProblemToSetRequestSchema)
if (!parsed.success) return parsed.response
const [problem] = await db
.select({ id: schema.problem.id })
.from(schema.problem)
@@ -394,10 +368,12 @@ adminProblemSetRoutes.put(
async (c) => {
const row = await loadOwned(c, c.get("user")!)
if (!row) return failure(c, 404, "problem-set-not-found", "题单不存在")
const parsed = updateProblemInSetRequestSchema.safeParse(
await c.req.json().catch(() => null),
const parsed = await parseBody(
c,
updateProblemInSetRequestSchema,
"参数错误",
)
if (!parsed.success) return failure(c, 400, "invalid-request", "参数错误")
if (!parsed.success) return parsed.response
const updated = await db
.update(schema.problemsetProblem)
.set(parsed.data)
@@ -512,17 +488,8 @@ adminProblemSetRoutes.post(
async (c) => {
const row = await loadOwned(c, c.get("user")!)
if (!row) return failure(c, 404, "problem-set-not-found", "题单不存在")
const parsed = createProblemSetBadgeRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, createProblemSetBadgeRequestSchema)
if (!parsed.success) return parsed.response
const [created] = await db
.insert(schema.problemsetBadge)
.values({
@@ -542,17 +509,8 @@ adminProblemSetRoutes.put(
async (c) => {
const row = await loadOwned(c, c.get("user")!)
if (!row) return failure(c, 404, "problem-set-not-found", "题单不存在")
const parsed = updateProblemSetBadgeRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, updateProblemSetBadgeRequestSchema)
if (!parsed.success) return parsed.response
const [updated] = await db
.update(schema.problemsetBadge)
.set(parsed.data)
+10 -21
View File
@@ -33,7 +33,7 @@ import {
} from "../../auth/middleware"
import type { AuthUser } from "../../auth/session"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { JudgeStatus } from "../../judge/status"
import { completeChat } from "../../services/ai"
import { localTime, localYear } from "../../time"
@@ -82,11 +82,8 @@ adminTagRoutes.get("/problem-tags", requireProblemPermission, async (c) => {
adminTagRoutes.put("/problem-tags/:id", requireProblemPermission, async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = renameTagRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "标签名不能为空")
const parsed = await parseBody(c, renameTagRequestSchema, "标签名不能为空")
if (!parsed.success) return parsed.response
const name = parsed.data.name
const [tag] = await db
@@ -177,17 +174,8 @@ adminTagRoutes.post(
"/problems/batch-tag",
requireProblemPermission,
async (c) => {
const parsed = batchProblemTagRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "参数错误",
)
}
const parsed = await parseBody(c, batchProblemTagRequestSchema)
if (!parsed.success) return parsed.response
const user = c.get("user")!
const filters = [
inArray(schema.problem.id, parsed.data.problemIds),
@@ -462,11 +450,12 @@ adminTagRoutes.post(
"/problems/flowchart",
requireProblemPermission,
async (c) => {
const parsed = generateFlowchartRequestSchema.safeParse(
await c.req.json().catch(() => null),
const parsed = await parseBody(
c,
generateFlowchartRequestSchema,
"python 代码不能为空",
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "python 代码不能为空")
if (!parsed.success) return parsed.response
try {
const flowchart = await completeChat(
`你是一个可以将Python代码转换为mermaid的助手。
+14 -49
View File
@@ -13,7 +13,7 @@ import { Hono } from "hono"
import { requireSuperAdmin, type AppEnv } from "../../auth/middleware"
import { db, schema } from "../../db"
import { failure, success } from "../../http"
import { failure, parseBody, success } from "../../http"
import { exerciseDataError } from "../../services/exercise"
import { objectValue, queryInteger, sampleUser } from "../helpers"
@@ -72,17 +72,8 @@ adminTutorialRoutes.get("/tutorials", requireSuperAdmin, async (c) => {
})
adminTutorialRoutes.post("/tutorials", requireSuperAdmin, async (c) => {
const parsed = createTutorialRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, createTutorialRequestSchema)
if (!parsed.success) return parsed.response
const now = new Date().toISOString()
const [created] = await db
.insert(schema.tutorial)
@@ -108,17 +99,8 @@ adminTutorialRoutes.get("/tutorials/:id", requireSuperAdmin, async (c) => {
adminTutorialRoutes.put("/tutorials/:id", requireSuperAdmin, async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = updateTutorialRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateTutorialRequestSchema)
if (!parsed.success) return parsed.response
const updated = await db
.update(schema.tutorial)
.set({ ...parsed.data, updatedAt: new Date().toISOString() })
@@ -135,11 +117,12 @@ adminTutorialRoutes.put(
requireSuperAdmin,
async (c) => {
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
const parsed = setTutorialVisibilityRequestSchema.safeParse(
await c.req.json().catch(() => null),
const parsed = await parseBody(
c,
setTutorialVisibilityRequestSchema,
"isPublic is required",
)
if (!parsed.success)
return failure(c, 400, "invalid-request", "isPublic is required")
if (!parsed.success) return parsed.response
// 只改可见性,不动 updatedAt —— 上下架不是内容修改,改了会打乱按更新时间排序的直觉
const updated = await db
.update(schema.tutorial)
@@ -199,17 +182,8 @@ adminTutorialRoutes.get(
)
adminTutorialRoutes.post("/exercises", requireSuperAdmin, async (c) => {
const parsed = createExerciseRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, createExerciseRequestSchema)
if (!parsed.success) return parsed.response
const [tutorial] = await db
.select({ id: schema.tutorial.id })
.from(schema.tutorial)
@@ -233,17 +207,8 @@ adminTutorialRoutes.post("/exercises", requireSuperAdmin, async (c) => {
})
adminTutorialRoutes.put("/exercises/:id", requireSuperAdmin, async (c) => {
const parsed = updateExerciseRequestSchema.safeParse(
await c.req.json().catch(() => null),
)
if (!parsed.success) {
return failure(
c,
400,
"invalid-request",
parsed.error.issues[0]?.message ?? "Invalid payload",
)
}
const parsed = await parseBody(c, updateExerciseRequestSchema)
if (!parsed.success) return parsed.response
const dataError = exerciseDataError(parsed.data.type, parsed.data.data)
if (dataError) return failure(c, 400, "invalid-exercise", dataError)
const [updated] = await db