- 前端 oj/api.ts、admin/api.ts、shared/api.ts 的 post / put 请求体一律 `satisfies XxxRequest`, 只在编译期检查,不加运行时校验(和「只在三处挂运行时闸门」的规矩一致) - 契约里的 XxxRequest 从 z.infer 改成 z.input:请求类型描述的是「客户端可以发什么」, 带默认值的字段可以不传。后端从不用这些类型(它用 parse 出来的 data),不受影响 - 补了四个漏导出的请求类型(比赛题目排序、题单题目排序、标签分类、检查用户名) - 接上之后对不上的几处:编辑用户的角色和题目权限经 toAdminType / toProblemPermission 收窄,导入用户的行改成四元组,练习题数据改成 Record,格式化的语言只收后端认的四种 (isFormatLanguage)。出题页和成就的请求体是「表单校验兜底、出站补默认值」, 用 Partial<…> 只核键名和值的类型 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
309ca0ece2
commit
4dfd14f95b
18 files changed
+251
-131
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import type { MarkAchievementsRead } from "@oj2/contract"
|
||||
import api from "utils/api"
|
||||
import type { AchievementList, PendingAchievement } from "utils/types"
|
||||
|
||||
@@ -12,5 +13,5 @@ export function getPendingAchievements() {
|
||||
}
|
||||
|
||||
export function markAchievementsRead(ids: number[]) {
|
||||
return api.post("achievements/pending/read", { ids })
|
||||
return api.post("achievements/pending/read", { ids } satisfies MarkAchievementsRead)
|
||||
}
|
||||
+33
-17
@@ -1,3 +1,14 @@
|
||||
import type {
|
||||
ClassLessonRequest,
|
||||
ContestPasswordRequest,
|
||||
CreateFlowchartRequest,
|
||||
CreateSubmissionRequest,
|
||||
FormatCodeRequest,
|
||||
JoinProblemSetRequest,
|
||||
RankHiddenRequest,
|
||||
SetReactionRequest,
|
||||
UpdateProfileRequest,
|
||||
} from "@oj2/contract"
|
||||
import {
|
||||
type AiAnalysisRecord,
|
||||
type LearningPeriod,
|
||||
@@ -144,21 +155,20 @@ export async function getSubmission(id: string, attempts = false): Promise<Submi
|
||||
}
|
||||
|
||||
export function submitCode(data: SubmitCodePayload) {
|
||||
return api.post<CreateSubmissionResponse>("submissions", data)
|
||||
return api.post<CreateSubmissionResponse>("submissions", data satisfies CreateSubmissionRequest)
|
||||
}
|
||||
|
||||
export function formatCode(data: { code: string; language: string; problemId: number }) {
|
||||
const languages: Record<string, string> = {
|
||||
Python: "python",
|
||||
C: "c",
|
||||
"C++": "cpp",
|
||||
SQL: "sql",
|
||||
}
|
||||
/** 后端格式化认的四种(ruff / clang-format / sqlparse),别的语言提交前不格式化 */
|
||||
export function isFormatLanguage(value: string): value is FormatCodeRequest["language"] {
|
||||
return ["python", "c", "cpp", "sql"].includes(value)
|
||||
}
|
||||
|
||||
export function formatCode(data: FormatCodeRequest & { problemId: number }) {
|
||||
return api.post<FormatCodeResponse>("code/format", {
|
||||
code: data.code,
|
||||
language: languages[data.language] ?? data.language.toLowerCase(),
|
||||
language: data.language,
|
||||
problemId: data.problemId,
|
||||
})
|
||||
} satisfies FormatCodeRequest)
|
||||
}
|
||||
|
||||
export function getSubmissions(params: Partial<SubmissionListPayload>) {
|
||||
@@ -262,7 +272,7 @@ export function getWeeklyChampions(className?: string) {
|
||||
|
||||
/** 老师把学生设成不计入排名 / 恢复 */
|
||||
export function setRankHidden(userId: number, hidden: boolean) {
|
||||
return api.put<null>(`rankings/hidden/${userId}`, { hidden })
|
||||
return api.put<null>(`rankings/hidden/${userId}`, { hidden } satisfies RankHiddenRequest)
|
||||
}
|
||||
|
||||
/** 老师清空一个学生的个性签名 */
|
||||
@@ -294,7 +304,11 @@ export function setClassLesson(
|
||||
problemDisplayIds: string[],
|
||||
language: LessonLanguage | null,
|
||||
) {
|
||||
return api.put<null>("classroom/lesson", { className, problemDisplayIds, language })
|
||||
return api.put<null>("classroom/lesson", {
|
||||
className,
|
||||
problemDisplayIds,
|
||||
language,
|
||||
} satisfies ClassLessonRequest)
|
||||
}
|
||||
|
||||
/** 我的知识点地图(只有自己的,不能查别人) */
|
||||
@@ -350,7 +364,7 @@ export function getContestAccess(id: string) {
|
||||
export function checkContestPassword(contestID: string, password: string) {
|
||||
return api.post<boolean>(`contests/${encodeURIComponent(contestID)}/access`, {
|
||||
password,
|
||||
})
|
||||
} satisfies ContestPasswordRequest)
|
||||
}
|
||||
|
||||
export async function getContestProblems(contestID: string) {
|
||||
@@ -375,7 +389,7 @@ export function uploadAvatar(file: File) {
|
||||
}
|
||||
|
||||
export function updateProfile(data: { realName?: string; mood?: string }) {
|
||||
return api.put<Profile>("me/profile", data)
|
||||
return api.put<Profile>("me/profile", data satisfies UpdateProfileRequest)
|
||||
}
|
||||
|
||||
export function getAnnouncementList(offset = 0, limit = 10) {
|
||||
@@ -393,7 +407,9 @@ export function getReaction(problemID: number) {
|
||||
}
|
||||
|
||||
export function setReaction(problemID: number, type: ReactionKey) {
|
||||
return api.post<ReactionState>(`problems/${problemID}/reaction`, { type })
|
||||
return api.post<ReactionState>(`problems/${problemID}/reaction`, {
|
||||
type,
|
||||
} satisfies SetReactionRequest)
|
||||
}
|
||||
|
||||
/** 个人主页那一页的数据:做对的题、四个时间、本班第几、成就,本人和老师多拿知识点和没做对的题 */
|
||||
@@ -450,7 +466,7 @@ export function submitFlowchart(data: {
|
||||
mermaidCode: string
|
||||
flowchartData: Record<string, unknown> // 压缩之后的,元数据太长了
|
||||
}) {
|
||||
return api.post<CreateFlowchartResponse>("flowcharts", data)
|
||||
return api.post<CreateFlowchartResponse>("flowcharts", data satisfies CreateFlowchartRequest)
|
||||
}
|
||||
|
||||
export function getFlowchartSubmission(id: string) {
|
||||
@@ -507,7 +523,7 @@ export function getProblemSetProblems(problemSetId: number) {
|
||||
}
|
||||
|
||||
export function joinProblemSet(problemSetId: number) {
|
||||
return api.post("problem-set-progress", { problemSetId })
|
||||
return api.post("problem-set-progress", { problemSetId } satisfies JoinProblemSetRequest)
|
||||
}
|
||||
|
||||
export function getUserBadges(username?: string) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from "pinia"
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import { formatCode, submitCode } from "oj/api"
|
||||
import { formatCode, isFormatLanguage, submitCode } from "oj/api"
|
||||
import { useCodeStore } from "oj/store/code"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { useSubmissionMonitor } from "oj/problem/composables/useSubmissionMonitor"
|
||||
@@ -106,7 +106,7 @@ export const useSubmissionStore = defineStore("submission", () => {
|
||||
// Python 在格式化之前先由服务端的 CPython 查一遍语法,有错就不提交;
|
||||
// C / C++ 先在判题机上编一遍,编不过就原样交(格式化会把错的几行并成一行)
|
||||
const formatLang = LANGUAGE_FORMAT_VALUE[codeStore.code.language]
|
||||
if (["python", "c", "cpp", "sql"].includes(formatLang)) {
|
||||
if (isFormatLanguage(formatLang)) {
|
||||
isFormatting.value = true
|
||||
try {
|
||||
const res = await formatCode({
|
||||
|
||||
Reference in new issue
Block a user