feat(阶段4): 后台地基 + 公告管理
地基: - auth/middleware.ts 加四个角色守卫 requireAdmin / requireTeacher / requireSuperAdmin / requireProblemPermission,对应旧 account/decorators.py 的 四个装饰器。未登录 401 login-required、角色不够 403 permission-denied, 与前端 api2 拦截器按 code 分流的两支对上。 - routes/admin/ 目录 + 总入口挂在 /api/admin。角色守卫由各子路由自己挂, 不在总入口兜一层 —— 否则「这个接口要什么角色」从注册行看不出来, 正是阶段 3 Minor M2 踩过的坑。 - packages/contract/src/admin.ts 独立放后台契约。同一张表两侧下发的字段集不同 (后台要 visible,oj 侧连键都不该出现),混在一起迟早有人在 oj 侧复用后台那个。 - utils/legacy.ts:把 toLegacy / legacyResponse 从 oj/api.ts 抽出来共用。 admin 侧组件同样读 snake_case,走同一层适配,组件不动。 公告管理(旧 /api/admin/announcement 一个路径四个动词)拆成: GET/POST admin/announcements GET/PUT/DELETE admin/announcements/:id 一处有意不对齐旧后端:删除不存在的公告,旧后端 filter().delete() 静默成功, 这里返回 404 —— 后台是人手点删除,静默成功会让人以为删掉了,刷新后它还在。 实测:匿名 401 / 学生 403 / 超管 200;增删改查、列表不含 content、 空标题 400、不存在 404 全部符合预期。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
type SubmissionStatistics,
|
||||
} from "@oj2/contract"
|
||||
import api2 from "utils/api2"
|
||||
import { legacyResponse, toLegacy } from "utils/legacy"
|
||||
import type { ApiResponse } from "utils/http"
|
||||
import { filterResult } from "oj/transforms"
|
||||
import type {
|
||||
@@ -19,25 +20,6 @@ import type {
|
||||
WebsiteConfig,
|
||||
} from "utils/types"
|
||||
|
||||
function snakeKey(key: string) {
|
||||
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||
}
|
||||
|
||||
function toLegacy<T>(value: unknown): T {
|
||||
if (Array.isArray(value)) return value.map((item) => toLegacy(item)) as T
|
||||
if (!value || typeof value !== "object") return value as T
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [snakeKey(key), toLegacy(item)]),
|
||||
) as T
|
||||
}
|
||||
|
||||
async function legacyResponse<T>(
|
||||
request: Promise<ApiResponse<unknown>>,
|
||||
): Promise<ApiResponse<T>> {
|
||||
const response = await request
|
||||
return { error: response.error, data: toLegacy<T>(response.data) }
|
||||
}
|
||||
|
||||
function listProblem(value: any): Problem {
|
||||
return {
|
||||
id: value.id,
|
||||
|
||||
Reference in New Issue
Block a user