refactor(前端): 拆掉 camelCase→snake_case 转换层,契约成为唯一真相
utils/legacy.ts 是迁移期的临时层:新后端一律 camelCase,而组件读的还是
旧 Django 的 snake_case,于是在 api 层做一次递归键名重写。它自己的注释就
写了「迁移完成后这一层应当整体拆掉」。现在拆了。
代价不只是那 96 处包装:每个响应都要递归遍历整个对象重写一遍键名,而且
utils/types.ts 和 packages/contract 是两份真相 —— 手抄的那份还抄歪了好几处。
做法是按域推进,每域都用 vue-tsc 相对基线做差,确认零新增错误后再往下走。
前端的类型现在一律以契约为准,只在必要处窄化(比如 languages/template 的键
窄化成 LANGUAGE),删掉的重复定义包括 WebsiteConfig、LoginSummary、
AchievementSummary、ProblemSet、Contest、User、Profile、AdminTag、
StuckProblem 等等,其中 ClassComparison 有两个组件各手抄了一份。
## 顺带修掉的真 bug
- 管理端公告列表的「可见」开关每次都 400:列表响应被契约 omit 掉了 content,
而更新接口要求 content 必填,toggleVisible 把列表行原样回传。而且是乐观
翻转、不 await 不 catch,管理员看到开关动了、实际没存也没有提示。
改成先 GET 整条再 PUT,加失败提示。
- 删有提交的题时只显示笼统的「删除失败」:前端还在 match 旧 Django 的英文
文案,而后端返回的是 problem-has-submissions + 中文。连同另外 8 处同类
匹配一起改成判错误码 —— 文案是后端随时能改的,match 文案改一个字就静默失效。
- SubmissionStatus.time_limit_exceeded 写成 `1 | 2`,TS 按位或算成 3,和
memory_limit_exceeded 撞了同一个值。后端 judge/status.ts 里这是分开的
两个码,按后端拆成 cpu_/real_ 两项。当前没有代码读这两个成员,但
CLAUDE.md 明确要求判题状态码三处同步。
- 流程图历史翻到没有提交的那一页会直接抛:契约里 submission 是 nullable,
被 any 掩盖成看起来非空。补了 null 分支。
## 契约里被逼出来的三处不诚实
- grade 写成 z.string(),但 averageGrade() 在没有可用数据时返回空串,
前端三张图表拿它查 Record<Grade,...> 会查出 undefined。按实际收紧成
z.enum([...,""]),四个查表点都补了「无评级」分支。
- difficulty 写成 z.string()。核对过生产库 dump:956 道题只有
Low/Mid/High 三个值(761/149/46)。收紧成枚举。
- topReaction 写成 z.string(),既对不上前端渲染的 {type,count},也对不上
旧后端 get_top_reactions 下发的形状。改成正确形状并注明当前恒传 null。
## 明确保留 snake_case 的 54 处
判题沙箱原始输出(cpu_time/exit_code/output_md5/compile_output)、
statistic_info 内容(err_info/time_cost/ast_results)、submission_info
JSONB(is_ac/ac_time/error_number,回滚时旧后端还要读)、SQL 判题引擎的
total_rows/order_sensitive/changed_tables、WebSocket 的 submission_id、
以及数据库选项键 enable_maxkb。每一处都在类型定义旁写了为什么不能改。
language 没有跟着收紧契约 —— 它是配置项、随时可能加语言,收紧会让新语言
在后端 parse 时直接抛。改在 api 边界一处窄化。
## 另外
- utils/http.ts 整个模块已是死代码(四处引用全是 import type),删除。
- profile 的 blog/github/school/major/language 五个字段全链路空转,没有
任何组件读,从契约到类型一并摘除(数据库列不动)。
- admin/account.ts 往 user_profile 塞的 totalScore 是 OI 模式遗留,表里
没这一列。Drizzle 按表定义拼列名会把它静默丢弃,所以没出过错,是死代码。
验证:vue-tsc 143 → 54 条且无新增,apps/api tsc、check:routes、web build
全通过;各域响应形状逐条打接口核对过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,46 @@
|
||||
import {
|
||||
createSubmissionResponseSchema,
|
||||
type AiAnalysisRecord,
|
||||
type Contest as OjContest,
|
||||
type ContestList,
|
||||
type ActivityRankItem,
|
||||
type ClassComparisonResponse,
|
||||
type ClassRankItem,
|
||||
type ClassUserRank,
|
||||
type ContestRank,
|
||||
type UserRank,
|
||||
type ProblemRank,
|
||||
type CreateSubmissionResponse,
|
||||
type ProblemAuthor,
|
||||
type ProblemListItem,
|
||||
type YearlyAc,
|
||||
type ProblemList,
|
||||
type CreateFlowchartResponse,
|
||||
type FlowchartCurrent,
|
||||
type FlowchartDetail,
|
||||
type FlowchartList,
|
||||
type FlowchartSubmission,
|
||||
type AiDetail,
|
||||
type DurationData,
|
||||
type HeatmapItem,
|
||||
type LoginSummary,
|
||||
type ProblemSet,
|
||||
type ProblemSetBadge,
|
||||
type ProblemSetList,
|
||||
type ProblemSetProblem,
|
||||
type ProblemSetProgressList,
|
||||
type UserBadge,
|
||||
problemDetailSchema,
|
||||
submissionDetailSchema,
|
||||
type FlowchartStatistics,
|
||||
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 {
|
||||
Announcement,
|
||||
Profile,
|
||||
Message,
|
||||
SubmissionListItem,
|
||||
Exercise,
|
||||
Problem,
|
||||
ReactionKey,
|
||||
@@ -18,97 +49,39 @@ import type {
|
||||
SubmissionListPayload,
|
||||
SubmitCodePayload,
|
||||
WebsiteConfig,
|
||||
Tutorial,
|
||||
} from "utils/types"
|
||||
|
||||
function listProblem(value: any): Problem {
|
||||
return {
|
||||
id: value.id,
|
||||
_id: value._id,
|
||||
title: value.title,
|
||||
difficulty: value.difficulty,
|
||||
submission_number: value.submissionNumber,
|
||||
accepted_number: value.acceptedNumber,
|
||||
created_by: toLegacy(value.createdBy),
|
||||
tags: value.tags,
|
||||
contest: value.contestId,
|
||||
allow_flowchart: value.allowFlowchart,
|
||||
show_flowchart: value.showFlowchart,
|
||||
has_ast_rules: value.hasAstRules,
|
||||
my_status: value.myStatus,
|
||||
} as Problem
|
||||
}
|
||||
|
||||
/**
|
||||
* 题目详情。走契约的 zod 解析,形状即契约 —— 之前这里手抄了一份 camel→snake 的
|
||||
* 键名映射,抄漏一个字段就是静默 undefined。
|
||||
*/
|
||||
function detailProblem(value: unknown): Problem {
|
||||
const problem = problemDetailSchema.parse(value)
|
||||
return {
|
||||
id: problem.id,
|
||||
_id: problem._id,
|
||||
title: problem.title,
|
||||
description: problem.description,
|
||||
input_description: problem.inputDescription,
|
||||
output_description: problem.outputDescription,
|
||||
samples: problem.samples,
|
||||
hint: problem.hint ?? "",
|
||||
languages: problem.languages,
|
||||
template: problem.template,
|
||||
create_time: problem.createTime,
|
||||
last_update_time: problem.lastUpdateTime,
|
||||
time_limit: problem.timeLimit,
|
||||
memory_limit: problem.memoryLimit,
|
||||
difficulty: problem.difficulty,
|
||||
source: problem.source ?? "",
|
||||
prompt: problem.prompt ?? "",
|
||||
answers: [],
|
||||
submission_number: problem.submissionNumber,
|
||||
accepted_number: problem.acceptedNumber,
|
||||
statistic_info: problem.statisticInfo,
|
||||
share_submission: problem.shareSubmission,
|
||||
contest: problem.contestId,
|
||||
tags: problem.tags,
|
||||
created_by: {
|
||||
id: problem.createdBy.id,
|
||||
username: problem.createdBy.username,
|
||||
real_name: problem.createdBy.realName,
|
||||
},
|
||||
my_status: problem.myStatus,
|
||||
my_failed_count: problem.myFailedCount,
|
||||
visible: true,
|
||||
allow_flowchart: problem.allowFlowchart,
|
||||
show_flowchart: problem.showFlowchart,
|
||||
mermaid_code: problem.mermaidCode ?? undefined,
|
||||
flowchart_data: problem.flowchartData ?? undefined,
|
||||
flowchart_hint: problem.flowchartHint ?? undefined,
|
||||
sql_config: problem.sqlConfig as Problem["sql_config"],
|
||||
sql_display: problem.sqlDisplay as Problem["sql_display"],
|
||||
} as Problem
|
||||
return problemDetailSchema.parse(value) as Problem
|
||||
}
|
||||
|
||||
export function getWebsiteConfig() {
|
||||
return legacyResponse<WebsiteConfig>(api2.get("site"))
|
||||
return api2.get<WebsiteConfig>("site")
|
||||
}
|
||||
|
||||
export async function getProblemList(
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
searchParams: any = {},
|
||||
searchParams: Record<string, unknown> = {},
|
||||
) {
|
||||
const res = await api2.get<{ results: any[]; total: number }>("problems", {
|
||||
const res = await api2.get<ProblemList>("problems", {
|
||||
params: { paging: true, offset, limit, ...searchParams },
|
||||
})
|
||||
return {
|
||||
results: res.data.results.map(listProblem).map(filterResult),
|
||||
results: res.data.results.map(filterResult),
|
||||
total: res.data.total,
|
||||
}
|
||||
}
|
||||
|
||||
export function getAuthors(all = false) {
|
||||
return legacyResponse(
|
||||
api2.get("problem-authors", {
|
||||
params: {
|
||||
all: all ? "1" : "0",
|
||||
},
|
||||
}),
|
||||
)
|
||||
return api2.get<ProblemAuthor[]>("problem-authors", {
|
||||
params: { all: all ? "1" : "0" },
|
||||
})
|
||||
}
|
||||
|
||||
export function getRandomProblemID() {
|
||||
@@ -131,41 +104,14 @@ export async function getSubmission(id: string) {
|
||||
const response = await api2.get<unknown>(
|
||||
`submissions/${encodeURIComponent(id)}`,
|
||||
)
|
||||
const submission = submissionDetailSchema.parse(response.data)
|
||||
return {
|
||||
error: null,
|
||||
data: {
|
||||
id: submission.id,
|
||||
create_time: submission.createTime,
|
||||
user_id: submission.userId,
|
||||
username: submission.username,
|
||||
code: submission.code,
|
||||
result: submission.result,
|
||||
info: submission.info,
|
||||
language: submission.language,
|
||||
shared: submission.shared,
|
||||
show_link: submission.showLink,
|
||||
statistic_info: submission.statisticInfo,
|
||||
ip: submission.ip,
|
||||
contest: submission.contestId,
|
||||
problem: submission.problemId,
|
||||
can_unshare: submission.canUnshare,
|
||||
} as Submission,
|
||||
data: submissionDetailSchema.parse(response.data) as Submission,
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitCode(data: SubmitCodePayload) {
|
||||
const response = await api2.post<unknown>("submissions", {
|
||||
problemId: data.problem_id,
|
||||
language: data.language,
|
||||
code: data.code,
|
||||
contestId: data.contest_id,
|
||||
})
|
||||
const created = createSubmissionResponseSchema.parse(response.data)
|
||||
return {
|
||||
error: null,
|
||||
data: { submission_id: created.submissionId },
|
||||
}
|
||||
export function submitCode(data: SubmitCodePayload) {
|
||||
return api2.post<CreateSubmissionResponse>("submissions", data)
|
||||
}
|
||||
|
||||
export function formatCode(data: { code: string; language: string }) {
|
||||
@@ -182,30 +128,23 @@ export function formatCode(data: { code: string; language: string }) {
|
||||
}
|
||||
|
||||
export function getSubmissions(params: Partial<SubmissionListPayload>) {
|
||||
const endpoint = params.contest_id
|
||||
? `contests/${encodeURIComponent(params.contest_id)}/submissions`
|
||||
const endpoint = params.contestId
|
||||
? `contests/${encodeURIComponent(params.contestId)}/submissions`
|
||||
: "submissions"
|
||||
return legacyResponse(
|
||||
api2.get(endpoint, {
|
||||
params: {
|
||||
...params,
|
||||
problemId: params.problem_id,
|
||||
contest_id: undefined,
|
||||
problem_id: undefined,
|
||||
page: undefined,
|
||||
},
|
||||
}),
|
||||
)
|
||||
// 契约里 language 是 z.string()(语言是配置项,随时可能加,收紧成枚举会让
|
||||
// 新加的语言在后端 parse 时直接抛),前端在这一处收窄成 LANGUAGE
|
||||
return api2.get<{ results: SubmissionListItem[]; total: number }>(endpoint, {
|
||||
// contestId 走的是路径,page 只有前端分页器用
|
||||
params: { ...params, contestId: undefined, page: undefined },
|
||||
})
|
||||
}
|
||||
|
||||
export function getRankOfProblem(problem_id: string) {
|
||||
return legacyResponse(
|
||||
api2.get(`problems/${encodeURIComponent(problem_id)}/rank`),
|
||||
)
|
||||
export function getRankOfProblem(problemId: string) {
|
||||
return api2.get<ProblemRank>(`problems/${encodeURIComponent(problemId)}/rank`)
|
||||
}
|
||||
|
||||
export function getTodaySubmissionCount(language?: string) {
|
||||
return api2.get("submissions/today-count", { params: { language } })
|
||||
return api2.get<number>("submissions/today-count", { params: { language } })
|
||||
}
|
||||
|
||||
export function adminRejudge(id: string) {
|
||||
@@ -228,25 +167,19 @@ export function getRank(
|
||||
n: number,
|
||||
username?: string,
|
||||
) {
|
||||
return legacyResponse(
|
||||
api2.get("rankings/users", {
|
||||
params: { offset, limit, username, top: n },
|
||||
}),
|
||||
)
|
||||
return api2.get<UserRank>("rankings/users", {
|
||||
params: { offset, limit, username, top: n },
|
||||
})
|
||||
}
|
||||
|
||||
export function getActivityRank(start: string) {
|
||||
return api2.get("rankings/activity", {
|
||||
return api2.get<ActivityRankItem[]>("rankings/activity", {
|
||||
params: { start },
|
||||
})
|
||||
}
|
||||
|
||||
export function getClassRank(grade?: number | null) {
|
||||
return legacyResponse(
|
||||
api2.get("rankings/classes", {
|
||||
params: { grade },
|
||||
}),
|
||||
)
|
||||
return api2.get<ClassRankItem[]>("rankings/classes", { params: { grade } })
|
||||
}
|
||||
|
||||
export function getUserClassRank(
|
||||
@@ -254,9 +187,9 @@ export function getUserClassRank(
|
||||
offset?: number,
|
||||
limit?: number,
|
||||
) {
|
||||
return legacyResponse(
|
||||
api2.get("me/class-rank", { params: { scope, offset, limit } }),
|
||||
)
|
||||
return api2.get<ClassUserRank>("me/class-rank", {
|
||||
params: { scope, offset, limit },
|
||||
})
|
||||
}
|
||||
|
||||
export function getClassPK(
|
||||
@@ -264,16 +197,11 @@ export function getClassPK(
|
||||
startTime?: string,
|
||||
endTime?: string,
|
||||
) {
|
||||
const payload: any = {
|
||||
return api2.post<ClassComparisonResponse>("classes/comparison", {
|
||||
classNames,
|
||||
}
|
||||
if (startTime) {
|
||||
payload.startTime = startTime
|
||||
}
|
||||
if (endTime) {
|
||||
payload.endTime = endTime
|
||||
}
|
||||
return legacyResponse(api2.post("classes/comparison", payload))
|
||||
...(startTime ? { startTime } : {}),
|
||||
...(endTime ? { endTime } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
export function getContestList(query: {
|
||||
@@ -283,11 +211,11 @@ export function getContestList(query: {
|
||||
status: string
|
||||
tag: string
|
||||
}) {
|
||||
return legacyResponse(api2.get("contests", { params: query }))
|
||||
return api2.get<ContestList>("contests", { params: query })
|
||||
}
|
||||
|
||||
export function getContest(id: string) {
|
||||
return legacyResponse(api2.get(`contests/${encodeURIComponent(id)}`))
|
||||
return api2.get<OjContest>(`contests/${encodeURIComponent(id)}`)
|
||||
}
|
||||
|
||||
export function getContestAccess(id: string) {
|
||||
@@ -301,30 +229,20 @@ export function checkContestPassword(contestID: string, password: string) {
|
||||
}
|
||||
|
||||
export async function getContestProblems(contestID: string) {
|
||||
const res = await api2.get<any[]>(
|
||||
const res = await api2.get<ProblemListItem[]>(
|
||||
`contests/${encodeURIComponent(contestID)}/problems`,
|
||||
)
|
||||
return res.data.map(listProblem).map(filterResult)
|
||||
return res.data.map(filterResult)
|
||||
}
|
||||
|
||||
export function getContestRank(
|
||||
contestID: string,
|
||||
query: { limit: number; offset: number },
|
||||
) {
|
||||
return legacyResponse<any>(
|
||||
api2.get(`contests/${encodeURIComponent(contestID)}/rank`, {
|
||||
params: query,
|
||||
}),
|
||||
).then((response) => ({
|
||||
...response,
|
||||
data: {
|
||||
...response.data,
|
||||
results: response.data.results.map((item: any) => ({
|
||||
...item,
|
||||
contest: item.contest_id,
|
||||
})),
|
||||
},
|
||||
}))
|
||||
return api2.get<ContestRank>(
|
||||
`contests/${encodeURIComponent(contestID)}/rank`,
|
||||
{ params: query },
|
||||
)
|
||||
}
|
||||
|
||||
export function uploadAvatar(file: File) {
|
||||
@@ -335,23 +253,18 @@ export function uploadAvatar(file: File) {
|
||||
})
|
||||
}
|
||||
|
||||
export function updateProfile(data: { real_name: string; mood: string }) {
|
||||
return legacyResponse(
|
||||
api2.put("me/profile", {
|
||||
realName: data.real_name,
|
||||
mood: data.mood,
|
||||
}),
|
||||
)
|
||||
export function updateProfile(data: { realName: string; mood: string }) {
|
||||
return api2.put<Profile>("me/profile", data)
|
||||
}
|
||||
|
||||
export function getAnnouncementList(offset = 0, limit = 10) {
|
||||
return legacyResponse(
|
||||
api2.get("announcements", { params: { limit, offset } }),
|
||||
)
|
||||
return api2.get<{ results: Announcement[]; total: number }>("announcements", {
|
||||
params: { limit, offset },
|
||||
})
|
||||
}
|
||||
|
||||
export function getAnnouncement(id: number) {
|
||||
return legacyResponse(api2.get(`announcements/${id}`))
|
||||
return api2.get<Announcement>(`announcements/${id}`)
|
||||
}
|
||||
|
||||
export function createMessage(data: {
|
||||
@@ -367,7 +280,10 @@ export function createMessage(data: {
|
||||
}
|
||||
|
||||
export function getMessageList(offset = 0, limit = 10) {
|
||||
return legacyResponse(api2.get("messages", { params: { limit, offset } }))
|
||||
// language 的收窄同 getSubmissions,见那里的说明
|
||||
return api2.get<{ results: Message[]; total: number }>("messages", {
|
||||
params: { limit, offset },
|
||||
})
|
||||
}
|
||||
|
||||
export function getReaction(problemID: number) {
|
||||
@@ -388,7 +304,7 @@ export function getMetrics(userid: number) {
|
||||
}
|
||||
|
||||
export function getTutorial(id: number) {
|
||||
return legacyResponse(api2.get(`tutorials/${id}`))
|
||||
return api2.get<Tutorial>(`tutorials/${id}`)
|
||||
}
|
||||
|
||||
export function getTutorials(type: "python" | "c") {
|
||||
@@ -396,19 +312,7 @@ export function getTutorials(type: "python" | "c") {
|
||||
}
|
||||
|
||||
export function getAIDetailData(start: string, end: string, username?: string) {
|
||||
return legacyResponse<any>(
|
||||
api2.get("ai/detail", { params: { start, end, username } }),
|
||||
).then((response) => ({
|
||||
...response,
|
||||
data: {
|
||||
...response.data,
|
||||
flowcharts:
|
||||
response.data.flowcharts?.map((item: any) => ({
|
||||
...item,
|
||||
problem__id: item.problem_id,
|
||||
})) ?? [],
|
||||
},
|
||||
}))
|
||||
return api2.get<AiDetail>("ai/detail", { params: { start, end, username } })
|
||||
}
|
||||
|
||||
export function getAIDurationData(
|
||||
@@ -416,95 +320,68 @@ export function getAIDurationData(
|
||||
duration: string,
|
||||
username?: string,
|
||||
) {
|
||||
return legacyResponse(
|
||||
api2.get("ai/duration", { params: { end, duration, username } }),
|
||||
)
|
||||
return api2.get<DurationData[]>("ai/duration", {
|
||||
params: { end, duration, username },
|
||||
})
|
||||
}
|
||||
|
||||
export function getAIHeatmapData(username?: string) {
|
||||
return api2.get("ai/heatmap", { params: username ? { username } : {} })
|
||||
return api2.get<HeatmapItem[]>("ai/heatmap", {
|
||||
params: username ? { username } : {},
|
||||
})
|
||||
}
|
||||
|
||||
export function getAILoginSummary() {
|
||||
return legacyResponse(api2.get("ai/login-summary"))
|
||||
return api2.get<LoginSummary>("ai/login-summary")
|
||||
}
|
||||
|
||||
export function getAIPinnedReport() {
|
||||
return legacyResponse(api2.get("ai/pinned"))
|
||||
return api2.get<AiAnalysisRecord | null>("ai/pinned")
|
||||
}
|
||||
|
||||
// ==================== 相似题目推荐 ====================
|
||||
|
||||
export function getSimilarProblems(problemId: string) {
|
||||
return api2
|
||||
.get<any[]>(`problems/${encodeURIComponent(problemId)}/similar`)
|
||||
.get<ProblemListItem[]>(`problems/${encodeURIComponent(problemId)}/similar`)
|
||||
.then((response) => ({
|
||||
...response,
|
||||
data: response.data.map(listProblem).map(filterResult),
|
||||
data: response.data.map(filterResult),
|
||||
}))
|
||||
}
|
||||
|
||||
export interface YearlyACData {
|
||||
year: number
|
||||
total: number
|
||||
accepted: number
|
||||
ac_rate: number
|
||||
}
|
||||
export type { YearlyAc as YearlyACData } from "@oj2/contract"
|
||||
|
||||
export function getProblemYearlyAC(problemId: string) {
|
||||
return legacyResponse<YearlyACData[]>(
|
||||
api2.get(`problems/${encodeURIComponent(problemId)}/yearly-ac`),
|
||||
return api2.get<YearlyAc[]>(
|
||||
`problems/${encodeURIComponent(problemId)}/yearly-ac`,
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== 流程图相关API ====================
|
||||
|
||||
export function submitFlowchart(data: {
|
||||
problem_id: number
|
||||
mermaid_code: string
|
||||
flowchart_data: any // 这个是压缩之后的,元数据太长了
|
||||
problemId: number
|
||||
mermaidCode: string
|
||||
flowchartData: Record<string, unknown> // 压缩之后的,元数据太长了
|
||||
}) {
|
||||
return legacyResponse(
|
||||
api2.post("flowcharts", {
|
||||
problemId: data.problem_id,
|
||||
mermaidCode: data.mermaid_code,
|
||||
flowchartData: data.flowchart_data,
|
||||
}),
|
||||
)
|
||||
return api2.post<CreateFlowchartResponse>("flowcharts", data)
|
||||
}
|
||||
|
||||
function legacyFlowchart(value: unknown) {
|
||||
const item = toLegacy<any>(value)
|
||||
return {
|
||||
...item,
|
||||
user: item.user_id ?? 0,
|
||||
problem: item.problem_id,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFlowchartSubmission(id: string) {
|
||||
const response = await api2.get(`flowcharts/${encodeURIComponent(id)}`)
|
||||
return { ...response, data: legacyFlowchart(response.data) }
|
||||
export function getFlowchartSubmission(id: string) {
|
||||
return api2.get<FlowchartSubmission>(`flowcharts/${encodeURIComponent(id)}`)
|
||||
}
|
||||
|
||||
export function getFlowchartSubmissions(params: {
|
||||
username?: string
|
||||
problem_id?: string
|
||||
problemId?: string
|
||||
myself?: string
|
||||
offset?: number
|
||||
limit?: number
|
||||
today?: string
|
||||
grade?: string
|
||||
}) {
|
||||
return legacyResponse<any>(
|
||||
api2.get("flowcharts", {
|
||||
params: {
|
||||
...params,
|
||||
problemId: params.problem_id,
|
||||
problem_id: undefined,
|
||||
},
|
||||
}),
|
||||
)
|
||||
return api2.get<FlowchartList>("flowcharts", { params })
|
||||
}
|
||||
|
||||
export function getFlowchartStatistics(
|
||||
@@ -518,32 +395,19 @@ export function getFlowchartStatistics(
|
||||
}
|
||||
|
||||
export function retryFlowchartSubmission(submissionId: string) {
|
||||
return legacyResponse(
|
||||
api2.post(`flowcharts/${encodeURIComponent(submissionId)}/retry`),
|
||||
return api2.post<{ status: string }>(
|
||||
`flowcharts/${encodeURIComponent(submissionId)}/retry`,
|
||||
)
|
||||
}
|
||||
|
||||
export function getCurrentProblemFlowchartSubmission(problemId: number) {
|
||||
return api2.get(`problems/${problemId}/flowchart/current`)
|
||||
return api2.get<FlowchartCurrent>(`problems/${problemId}/flowchart/current`)
|
||||
}
|
||||
|
||||
export async function getFlowchartSubmissionDetail(
|
||||
problemId: number,
|
||||
page = 0,
|
||||
) {
|
||||
const response = await api2.get<any>(
|
||||
`problems/${problemId}/flowchart/history`,
|
||||
{ params: { page } },
|
||||
)
|
||||
return {
|
||||
...response,
|
||||
data: {
|
||||
...response.data,
|
||||
submission: response.data.submission
|
||||
? legacyFlowchart(response.data.submission)
|
||||
: null,
|
||||
},
|
||||
}
|
||||
export function getFlowchartSubmissionDetail(problemId: number, page = 0) {
|
||||
return api2.get<FlowchartDetail>(`problems/${problemId}/flowchart/history`, {
|
||||
params: { page },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 题单相关API ====================
|
||||
@@ -555,64 +419,17 @@ export function getProblemSetList(
|
||||
difficulty = "",
|
||||
status = "",
|
||||
) {
|
||||
return legacyResponse<any>(
|
||||
api2.get("problem-sets", {
|
||||
params: {
|
||||
offset,
|
||||
limit,
|
||||
keyword,
|
||||
difficulty,
|
||||
status,
|
||||
},
|
||||
}),
|
||||
).then(mapProblemSetResponse)
|
||||
return api2.get<ProblemSetList>("problem-sets", {
|
||||
params: { offset, limit, keyword, difficulty, status },
|
||||
})
|
||||
}
|
||||
|
||||
export function getProblemSetDetail(id: number) {
|
||||
return legacyResponse<any>(api2.get(`problem-sets/${id}`)).then(
|
||||
(response) => ({
|
||||
...response,
|
||||
data: legacyProblemSet(response.data),
|
||||
}),
|
||||
)
|
||||
return api2.get<ProblemSet>(`problem-sets/${id}`)
|
||||
}
|
||||
|
||||
function legacyBadge(value: any) {
|
||||
return { ...value, problemset: value.problemset_id }
|
||||
}
|
||||
|
||||
function legacyProblemSet(value: any) {
|
||||
return {
|
||||
...value,
|
||||
badges: value.badges?.map(legacyBadge),
|
||||
}
|
||||
}
|
||||
|
||||
function mapProblemSetResponse(response: ApiResponse<any>) {
|
||||
return {
|
||||
...response,
|
||||
data: {
|
||||
...response.data,
|
||||
results: response.data.results.map(legacyProblemSet),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProblemSetProblems(problemSetId: number) {
|
||||
const response = await legacyResponse<any[]>(
|
||||
api2.get(`problem-sets/${problemSetId}/problems`),
|
||||
)
|
||||
return {
|
||||
...response,
|
||||
data: response.data.map((item) => ({
|
||||
...item,
|
||||
problemset: item.problemset_id,
|
||||
problem: {
|
||||
...item.problem,
|
||||
contest: item.problem.contest_id,
|
||||
},
|
||||
})),
|
||||
}
|
||||
export function getProblemSetProblems(problemSetId: number) {
|
||||
return api2.get<ProblemSetProblem[]>(`problem-sets/${problemSetId}/problems`)
|
||||
}
|
||||
|
||||
export function joinProblemSet(problemSetId: number) {
|
||||
@@ -624,57 +441,35 @@ export function updateProblemSetProgress(
|
||||
problemId: number,
|
||||
submissionId: string,
|
||||
) {
|
||||
return legacyResponse(
|
||||
api2.put("problem-set-progress", {
|
||||
problemSetId,
|
||||
problemId,
|
||||
submissionId,
|
||||
}),
|
||||
return api2.put("problem-set-progress", {
|
||||
problemSetId,
|
||||
problemId,
|
||||
submissionId,
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserBadges(username?: string) {
|
||||
return api2.get<UserBadge[]>(
|
||||
`users/${encodeURIComponent(username ?? "me")}/badges`,
|
||||
)
|
||||
}
|
||||
|
||||
// 获取用户徽章列表
|
||||
export async function getUserBadges(username?: string) {
|
||||
const response = await legacyResponse<any[]>(
|
||||
api2.get(`users/${encodeURIComponent(username ?? "me")}/badges`),
|
||||
)
|
||||
return {
|
||||
...response,
|
||||
data: response.data.map((item) => ({
|
||||
...item,
|
||||
user: item.user_id,
|
||||
badge: legacyBadge(item.badge),
|
||||
})),
|
||||
}
|
||||
export function getProblemSetBadges(problemSetId: number) {
|
||||
return api2.get<ProblemSetBadge[]>(`problem-sets/${problemSetId}/badges`)
|
||||
}
|
||||
|
||||
// 获取题单徽章列表
|
||||
export async function getProblemSetBadges(problemSetId: number) {
|
||||
const response = await legacyResponse<any[]>(
|
||||
api2.get(`problem-sets/${problemSetId}/badges`),
|
||||
)
|
||||
return { ...response, data: response.data.map(legacyBadge) }
|
||||
}
|
||||
|
||||
// 获取题单用户进度列表
|
||||
export function getProblemSetUserProgress(
|
||||
problemSetId: number,
|
||||
params?: {
|
||||
limit?: number
|
||||
offset?: number
|
||||
class_name?: string
|
||||
completion_status?: "" | "completed" | "in_progress" | "not_started"
|
||||
className?: string
|
||||
completionStatus?: "" | "completed" | "in_progress" | "not_started"
|
||||
},
|
||||
) {
|
||||
return legacyResponse(
|
||||
api2.get(`problem-sets/${problemSetId}/user-progress`, {
|
||||
params: {
|
||||
limit: params?.limit,
|
||||
offset: params?.offset,
|
||||
className: params?.class_name,
|
||||
completionStatus: params?.completion_status,
|
||||
},
|
||||
}),
|
||||
return api2.get<ProblemSetProgressList>(
|
||||
`problem-sets/${problemSetId}/user-progress`,
|
||||
{ params },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user