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:
54
packages/contract/src/account.ts
Normal file
54
packages/contract/src/account.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
import { userProfileSchema } from "./auth"
|
||||
|
||||
export const registerRequestSchema = z.object({
|
||||
username: z.string().trim().min(1).max(32),
|
||||
email: z.email().max(64),
|
||||
password: z.string().min(6).max(20),
|
||||
})
|
||||
|
||||
export const updateProfileRequestSchema = z.object({
|
||||
realName: z.string().max(32).nullable().optional(),
|
||||
avatar: z.string().max(256).optional(),
|
||||
blog: z.string().max(256).nullable().optional(),
|
||||
mood: z.string().max(256).nullable().optional(),
|
||||
github: z.string().max(256).nullable().optional(),
|
||||
school: z.string().max(64).nullable().optional(),
|
||||
major: z.string().max(64).nullable().optional(),
|
||||
language: z.string().max(32).nullable().optional(),
|
||||
})
|
||||
|
||||
export const metricsSchema = z.object({
|
||||
now: z.string(),
|
||||
latest: z.string(),
|
||||
first: z.string(),
|
||||
})
|
||||
|
||||
export const rankProfileSchema = z.object({
|
||||
id: z.number().int(),
|
||||
user: sampleUserSchema,
|
||||
acceptedNumber: z.number().int(),
|
||||
submissionNumber: z.number().int(),
|
||||
mood: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const userRankSchema = paginatedSchema(rankProfileSchema)
|
||||
|
||||
export const activityRankItemSchema = z.object({
|
||||
username: z.string(),
|
||||
count: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
export const problemRankSchema = z.object({
|
||||
className: z.string(),
|
||||
rank: z.number().int(),
|
||||
classAcCount: z.number().int().nonnegative(),
|
||||
allAcCount: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
export const publicProfileSchema = userProfileSchema
|
||||
|
||||
export type RegisterRequest = z.infer<typeof registerRequestSchema>
|
||||
export type UpdateProfileRequest = z.infer<typeof updateProfileRequestSchema>
|
||||
51
packages/contract/src/achievement.ts
Normal file
51
packages/contract/src/achievement.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const achievementRaritySchema = z.enum(["bronze", "silver", "gold", "platinum"])
|
||||
|
||||
export const achievementSchema = z.object({
|
||||
id: z.number().int(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
icon: z.string(),
|
||||
rarity: achievementRaritySchema,
|
||||
hidden: z.boolean(),
|
||||
metric: z.string().nullable(),
|
||||
operator: z.enum(["gte", "lte"]).nullable(),
|
||||
threshold: z.number().int().nullable(),
|
||||
unlocked: z.boolean(),
|
||||
unlockTime: z.string().nullable(),
|
||||
backfilled: z.boolean(),
|
||||
progress: z.number().nullable(),
|
||||
unlockRate: z.number(),
|
||||
})
|
||||
|
||||
export const achievementListSchema = z.object({
|
||||
username: z.string(),
|
||||
achievements: z.array(achievementSchema),
|
||||
})
|
||||
|
||||
export const pendingAchievementSchema = z.object({
|
||||
id: z.number().int(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
icon: z.string(),
|
||||
rarity: achievementRaritySchema,
|
||||
})
|
||||
|
||||
export const achievementSummarySchema = z.object({
|
||||
username: z.string(),
|
||||
total: z.number().int(),
|
||||
unlocked: z.number().int(),
|
||||
percent: z.number(),
|
||||
rarity: z.array(z.object({
|
||||
rarity: achievementRaritySchema,
|
||||
label: z.string(),
|
||||
total: z.number().int(),
|
||||
unlocked: z.number().int(),
|
||||
})),
|
||||
recent: z.array(pendingAchievementSchema),
|
||||
})
|
||||
|
||||
export const markAchievementsReadSchema = z.object({
|
||||
ids: z.array(z.number().int()),
|
||||
})
|
||||
96
packages/contract/src/ai.ts
Normal file
96
packages/contract/src/ai.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const durationDataSchema = z.object({
|
||||
unit: z.string(),
|
||||
index: z.number().int(),
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
grade: z.string(),
|
||||
problemCount: z.number().int(),
|
||||
submissionCount: z.number().int(),
|
||||
})
|
||||
|
||||
export const solvedProblemSchema = z.object({
|
||||
problem: z.object({
|
||||
title: z.string(),
|
||||
displayId: z.string(),
|
||||
contestTitle: z.string(),
|
||||
contestId: z.number().int().nullable(),
|
||||
}),
|
||||
acTime: z.string(),
|
||||
rank: z.number().int().nullable(),
|
||||
acCount: z.number().int(),
|
||||
grade: z.string(),
|
||||
periodRank: z.number().int().nullable(),
|
||||
periodAcCount: z.number().int(),
|
||||
difficulty: z.string(),
|
||||
})
|
||||
|
||||
export const flowchartSummarySchema = z.object({
|
||||
problemId: z.string(),
|
||||
problemTitle: z.string(),
|
||||
submissionCount: z.number().int(),
|
||||
bestScore: z.number(),
|
||||
bestGrade: z.string(),
|
||||
latestSubmissionTime: z.string(),
|
||||
avgScore: z.number(),
|
||||
})
|
||||
|
||||
export const aiDetailSchema = z.object({
|
||||
user: z.string(),
|
||||
className: z.string().nullable(),
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
solved: z.array(solvedProblemSchema),
|
||||
flowcharts: z.array(flowchartSummarySchema),
|
||||
grade: z.string(),
|
||||
tags: z.record(z.string(), z.number().int()),
|
||||
difficulty: z.record(z.string(), z.number().int()),
|
||||
contestCount: z.number().int(),
|
||||
})
|
||||
|
||||
export const aiAnalysisRequestSchema = z.object({
|
||||
details: z.unknown(),
|
||||
duration: z.unknown(),
|
||||
})
|
||||
|
||||
export const aiHintRequestSchema = z.object({ submissionId: z.string().min(1) })
|
||||
|
||||
export const classAnalysisRequestSchema = z.object({
|
||||
comparison: z.record(z.string(), z.unknown()),
|
||||
})
|
||||
|
||||
export const classPkAnalysisRequestSchema = z.object({
|
||||
comparisons: z.array(z.record(z.string(), z.unknown())).min(2),
|
||||
timeRangeLabel: z.string().default("全部时间"),
|
||||
})
|
||||
|
||||
export const heatmapItemSchema = z.object({
|
||||
timestamp: z.number(),
|
||||
value: z.number().int(),
|
||||
})
|
||||
|
||||
export const aiAnalysisRecordSchema = z.object({
|
||||
id: z.number().int(),
|
||||
provider: z.string(),
|
||||
model: z.string(),
|
||||
data: z.record(z.string(), z.unknown()),
|
||||
analysis: z.string(),
|
||||
createTime: z.string(),
|
||||
isPinned: z.boolean(),
|
||||
username: z.string().optional(),
|
||||
})
|
||||
|
||||
export const loginSummarySchema = z.object({
|
||||
summary: z.object({
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
newProblemCount: z.number().int(),
|
||||
submissionCount: z.number().int(),
|
||||
acceptedCount: z.number().int(),
|
||||
solvedCount: z.number().int(),
|
||||
flowchartSubmissionCount: z.number().int(),
|
||||
}),
|
||||
analysis: z.string(),
|
||||
analysisError: z.string().optional(),
|
||||
})
|
||||
64
packages/contract/src/classroom.ts
Normal file
64
packages/contract/src/classroom.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const classRankItemSchema = z.object({
|
||||
className: z.string(),
|
||||
userCount: z.number().int(),
|
||||
totalAc: z.number().int(),
|
||||
totalSubmission: z.number().int(),
|
||||
avgAc: z.number(),
|
||||
acRate: z.number(),
|
||||
rank: z.number().int(),
|
||||
})
|
||||
|
||||
export const classUserRankItemSchema = z.object({
|
||||
userId: z.number().int(),
|
||||
username: z.string(),
|
||||
acceptedNumber: z.number().int(),
|
||||
submissionNumber: z.number().int(),
|
||||
rank: z.number().int(),
|
||||
})
|
||||
|
||||
export const classUserRankSchema = z.object({
|
||||
className: z.string(),
|
||||
myRank: z.number().int(),
|
||||
total: z.number().int(),
|
||||
ranks: z.array(classUserRankItemSchema),
|
||||
})
|
||||
|
||||
export const classComparisonSchema = z.object({
|
||||
className: z.string(),
|
||||
userCount: z.number().int(),
|
||||
totalAc: z.number().int(),
|
||||
totalSubmission: z.number().int(),
|
||||
avgAc: z.number(),
|
||||
medianAc: z.number(),
|
||||
q1Ac: z.number(),
|
||||
q3Ac: z.number(),
|
||||
iqr: z.number(),
|
||||
stdDev: z.number(),
|
||||
top10Avg: z.number(),
|
||||
middle80Avg: z.number(),
|
||||
bottom10Avg: z.number(),
|
||||
excellentRate: z.number(),
|
||||
passRate: z.number(),
|
||||
activeRate: z.number(),
|
||||
acRate: z.number(),
|
||||
compositeScore: z.number(),
|
||||
recentTotalAc: z.number().int().optional(),
|
||||
recentTotalSubmission: z.number().int().optional(),
|
||||
recentAvgAc: z.number().optional(),
|
||||
recentMedianAc: z.number().optional(),
|
||||
recentTop10Avg: z.number().optional(),
|
||||
recentActiveCount: z.number().int().optional(),
|
||||
})
|
||||
|
||||
export const classComparisonRequestSchema = z.object({
|
||||
classNames: z.array(z.string()).min(1),
|
||||
startTime: z.string().optional(),
|
||||
endTime: z.string().optional(),
|
||||
})
|
||||
|
||||
export const classComparisonResponseSchema = z.object({
|
||||
comparisons: z.array(classComparisonSchema),
|
||||
hasTimeRange: z.boolean(),
|
||||
})
|
||||
21
packages/contract/src/common.ts
Normal file
21
packages/contract/src/common.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const sampleUserSchema = z.object({
|
||||
id: z.number().int(),
|
||||
username: z.string(),
|
||||
realName: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const paginationQuerySchema = z.object({
|
||||
limit: z.coerce.number().int().min(1).max(250).default(10),
|
||||
offset: z.coerce.number().int().min(0).default(0),
|
||||
})
|
||||
|
||||
export function paginatedSchema<T extends z.ZodType>(item: T) {
|
||||
return z.object({
|
||||
results: z.array(item),
|
||||
total: z.number().int().nonnegative(),
|
||||
})
|
||||
}
|
||||
|
||||
export type SampleUser = z.infer<typeof sampleUserSchema>
|
||||
74
packages/contract/src/content.ts
Normal file
74
packages/contract/src/content.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
import { submissionDetailSchema } from "./submission"
|
||||
|
||||
export const announcementSchema = z.object({
|
||||
id: z.number().int(),
|
||||
title: z.string(),
|
||||
tag: z.string(),
|
||||
content: z.string().optional(),
|
||||
top: z.boolean(),
|
||||
createdBy: sampleUserSchema,
|
||||
createTime: z.string(),
|
||||
lastUpdateTime: z.string(),
|
||||
})
|
||||
|
||||
export const announcementListSchema = paginatedSchema(announcementSchema)
|
||||
|
||||
export const messageSchema = z.object({
|
||||
id: z.number().int(),
|
||||
sender: sampleUserSchema,
|
||||
createTime: z.string(),
|
||||
message: z.string(),
|
||||
submission: submissionDetailSchema,
|
||||
})
|
||||
|
||||
export const messageListSchema = paginatedSchema(messageSchema)
|
||||
|
||||
export const createMessageRequestSchema = z.object({
|
||||
recipientId: z.number().int().positive(),
|
||||
submissionId: z.string().min(1),
|
||||
message: z.string().min(1).max(1024 * 1024),
|
||||
})
|
||||
|
||||
export const reactionKeySchema = z.enum([
|
||||
"too_easy",
|
||||
"too_hard",
|
||||
"confusing",
|
||||
"buggy",
|
||||
"learned",
|
||||
"interesting",
|
||||
"want_explain",
|
||||
])
|
||||
|
||||
export const reactionCountsSchema = z.record(reactionKeySchema, z.number().int())
|
||||
export const reactionStateSchema = z.object({
|
||||
mine: reactionKeySchema.nullable(),
|
||||
counts: reactionCountsSchema.nullable(),
|
||||
})
|
||||
|
||||
export const setReactionRequestSchema = z.object({ type: reactionKeySchema })
|
||||
|
||||
export const tutorialSummarySchema = z.object({
|
||||
id: z.number().int(),
|
||||
title: z.string(),
|
||||
})
|
||||
|
||||
export const tutorialSchema = tutorialSummarySchema.extend({
|
||||
content: z.string(),
|
||||
code: z.string().nullable(),
|
||||
isPublic: z.boolean(),
|
||||
order: z.number().int(),
|
||||
type: z.enum(["python", "c"]),
|
||||
createdBy: sampleUserSchema,
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
|
||||
export const exerciseSchema = z.object({
|
||||
id: z.number().int(),
|
||||
type: z.enum(["mcq", "sort", "fill", "match", "predict", "debug", "group"]),
|
||||
data: z.record(z.string(), z.unknown()),
|
||||
order: z.number().int(),
|
||||
})
|
||||
42
packages/contract/src/contest.ts
Normal file
42
packages/contract/src/contest.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
import { problemDetailSchema, problemListItemSchema } from "./problem"
|
||||
|
||||
export const contestStatusSchema = z.enum(["-1", "0", "1"])
|
||||
|
||||
export const contestSchema = z.object({
|
||||
id: z.number().int(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
tag: z.string(),
|
||||
startTime: z.string(),
|
||||
endTime: z.string(),
|
||||
createTime: z.string(),
|
||||
lastUpdateTime: z.string(),
|
||||
createdBy: sampleUserSchema,
|
||||
status: contestStatusSchema,
|
||||
contestType: z.enum(["Public", "Password Protected"]),
|
||||
now: z.string().optional(),
|
||||
})
|
||||
|
||||
export const contestListSchema = paginatedSchema(contestSchema)
|
||||
|
||||
export const contestPasswordRequestSchema = z.object({
|
||||
password: z.string().min(1).max(128),
|
||||
})
|
||||
|
||||
export const contestAccessSchema = z.object({ access: z.boolean() })
|
||||
export const contestProblemsSchema = z.array(z.union([problemListItemSchema, problemDetailSchema]))
|
||||
|
||||
export const contestRankItemSchema = z.object({
|
||||
id: z.number().int(),
|
||||
user: sampleUserSchema,
|
||||
submissionNumber: z.number().int(),
|
||||
acceptedNumber: z.number().int(),
|
||||
totalTime: z.number().int(),
|
||||
submissionInfo: z.record(z.string(), z.unknown()),
|
||||
contestId: z.number().int(),
|
||||
})
|
||||
|
||||
export const contestRankSchema = paginatedSchema(contestRankItemSchema)
|
||||
71
packages/contract/src/flowchart.ts
Normal file
71
packages/contract/src/flowchart.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema } from "./common"
|
||||
|
||||
export const flowchartStatusSchema = z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)])
|
||||
|
||||
export const createFlowchartRequestSchema = z.object({
|
||||
problemId: z.number().int().positive(),
|
||||
mermaidCode: z.string().trim().min(1).max(50_000).refine(
|
||||
(value) => value.split("\n").filter((line) => line.trim()).length <= 200,
|
||||
"Flowchart is too complex",
|
||||
),
|
||||
flowchartData: z.record(z.string(), z.unknown()).default({}),
|
||||
})
|
||||
|
||||
export const flowchartSubmissionSchema = z.object({
|
||||
id: z.string(),
|
||||
username: z.string(),
|
||||
problemId: z.number().int(),
|
||||
mermaidCode: z.string(),
|
||||
flowchartData: z.record(z.string(), z.unknown()),
|
||||
status: flowchartStatusSchema,
|
||||
createTime: z.string(),
|
||||
aiScore: z.number().nullable(),
|
||||
aiGrade: z.string().nullable(),
|
||||
aiFeedback: z.string().nullable(),
|
||||
aiSuggestions: z.string().nullable(),
|
||||
aiCriteriaDetails: z.record(z.string(), z.unknown()),
|
||||
aiProvider: z.string(),
|
||||
aiModel: z.string(),
|
||||
processingTime: z.number().nullable(),
|
||||
evaluationTime: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const flowchartListItemSchema = z.object({
|
||||
id: z.string(),
|
||||
username: z.string(),
|
||||
problem: z.string(),
|
||||
problemTitle: z.string(),
|
||||
status: flowchartStatusSchema,
|
||||
createTime: z.string(),
|
||||
aiScore: z.number().nullable(),
|
||||
aiGrade: z.string().nullable(),
|
||||
aiProvider: z.string(),
|
||||
aiModel: z.string(),
|
||||
processingTime: z.number().nullable(),
|
||||
evaluationTime: z.string().nullable(),
|
||||
showLink: z.boolean(),
|
||||
})
|
||||
|
||||
export const flowchartListSchema = paginatedSchema(flowchartListItemSchema)
|
||||
export const createFlowchartResponseSchema = z.object({ submissionId: z.string(), status: z.literal("pending") })
|
||||
export const flowchartCurrentSchema = z.object({ count: z.number().int(), score: z.number(), grade: z.string() })
|
||||
export const flowchartDetailSchema = z.object({ submission: flowchartSubmissionSchema.nullable(), count: z.number().int() })
|
||||
|
||||
export const flowchartUpdateSchema = z.object({
|
||||
type: z.enum([
|
||||
"flowchart_evaluation_completed",
|
||||
"flowchart_evaluation_failed",
|
||||
"flowchart_evaluation_update",
|
||||
]),
|
||||
submission_id: z.string(),
|
||||
score: z.number().optional(),
|
||||
grade: z.string().optional(),
|
||||
feedback: z.string().optional(),
|
||||
suggestions: z.string().optional(),
|
||||
criteriaDetails: z.unknown().optional(),
|
||||
error: z.string().optional(),
|
||||
})
|
||||
|
||||
export type FlowchartUpdate = z.infer<typeof flowchartUpdateSchema>
|
||||
@@ -1,3 +1,13 @@
|
||||
export * from "./auth"
|
||||
export * from "./account"
|
||||
export * from "./achievement"
|
||||
export * from "./ai"
|
||||
export * from "./classroom"
|
||||
export * from "./common"
|
||||
export * from "./content"
|
||||
export * from "./contest"
|
||||
export * from "./flowchart"
|
||||
export * from "./problem"
|
||||
export * from "./problemset"
|
||||
export * from "./site"
|
||||
export * from "./submission"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
|
||||
/** 题目列表项。字段取自 problem 表,只含列表页需要的列。 */
|
||||
export const problemSummarySchema = z.object({
|
||||
id: z.number().int(),
|
||||
@@ -58,3 +60,39 @@ export const problemDetailSchema = z.object({
|
||||
})
|
||||
|
||||
export type ProblemDetail = z.infer<typeof problemDetailSchema>
|
||||
|
||||
export const problemListItemSchema = z.object({
|
||||
id: z.number().int(),
|
||||
_id: z.string(),
|
||||
title: z.string(),
|
||||
submissionNumber: z.number().int(),
|
||||
acceptedNumber: z.number().int(),
|
||||
difficulty: z.string(),
|
||||
createdBy: sampleUserSchema,
|
||||
tags: z.array(z.string()),
|
||||
contestId: z.number().int().nullable(),
|
||||
allowFlowchart: z.boolean(),
|
||||
showFlowchart: z.boolean(),
|
||||
hasAstRules: z.boolean(),
|
||||
myStatus: z.number().int().nullable(),
|
||||
})
|
||||
|
||||
export const problemListSchema = paginatedSchema(problemListItemSchema)
|
||||
|
||||
export const tagSchema = z.object({
|
||||
id: z.number().int(),
|
||||
name: z.string(),
|
||||
problemCount: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
export const problemAuthorSchema = z.object({
|
||||
username: z.string(),
|
||||
problemCount: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
export const yearlyAcSchema = z.object({
|
||||
year: z.number().int(),
|
||||
total: z.number().int().nonnegative(),
|
||||
accepted: z.number().int().nonnegative(),
|
||||
acRate: z.number(),
|
||||
})
|
||||
|
||||
100
packages/contract/src/problemset.ts
Normal file
100
packages/contract/src/problemset.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
import { problemListItemSchema } from "./problem"
|
||||
|
||||
export const problemSetUserProgressSummarySchema = z.object({
|
||||
isJoined: z.boolean(),
|
||||
progressPercentage: z.number(),
|
||||
completedCount: z.number().int(),
|
||||
totalCount: z.number().int(),
|
||||
isCompleted: z.boolean(),
|
||||
})
|
||||
|
||||
export const problemSetBadgeSchema = z.object({
|
||||
id: z.number().int(),
|
||||
problemsetId: z.number().int(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
icon: z.string(),
|
||||
conditionType: z.enum(["all_problems", "problem_count", "score"]),
|
||||
conditionValue: z.number().int(),
|
||||
isEarned: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const problemSetSchema = z.object({
|
||||
id: z.number().int(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
createdBy: sampleUserSchema,
|
||||
createTime: z.string(),
|
||||
lastUpdateTime: z.string().optional(),
|
||||
difficulty: z.enum(["Easy", "Medium", "Hard"]),
|
||||
status: z.enum(["active", "archived", "draft"]),
|
||||
endTime: z.string().nullable(),
|
||||
visible: z.boolean(),
|
||||
problemsCount: z.number().int(),
|
||||
completedCount: z.number().int().optional(),
|
||||
userProgress: problemSetUserProgressSummarySchema,
|
||||
badges: z.array(problemSetBadgeSchema).optional(),
|
||||
})
|
||||
|
||||
export const problemSetListSchema = paginatedSchema(problemSetSchema)
|
||||
|
||||
export const problemSetProblemSchema = z.object({
|
||||
id: z.number().int(),
|
||||
problemsetId: z.number().int(),
|
||||
problem: problemListItemSchema,
|
||||
order: z.number().int(),
|
||||
isRequired: z.boolean(),
|
||||
score: z.number().int(),
|
||||
hint: z.string().nullable(),
|
||||
isCompleted: z.boolean(),
|
||||
})
|
||||
|
||||
export const joinProblemSetRequestSchema = z.object({
|
||||
problemSetId: z.number().int().positive(),
|
||||
})
|
||||
|
||||
export const updateProblemSetProgressRequestSchema = z.object({
|
||||
problemSetId: z.number().int().positive(),
|
||||
problemId: z.number().int().positive(),
|
||||
submissionId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const completedProblemSchema = z.object({
|
||||
id: z.number().int(),
|
||||
_id: z.string(),
|
||||
title: z.string(),
|
||||
})
|
||||
|
||||
export const problemSetProgressSchema = z.object({
|
||||
id: z.number().int(),
|
||||
problemsetId: z.number().int(),
|
||||
user: sampleUserSchema,
|
||||
joinTime: z.string(),
|
||||
completeTime: z.string().nullable(),
|
||||
isCompleted: z.boolean(),
|
||||
progressPercentage: z.number(),
|
||||
completedProblemsCount: z.number().int(),
|
||||
totalProblemsCount: z.number().int(),
|
||||
totalScore: z.number().int(),
|
||||
completedProblems: z.array(completedProblemSchema),
|
||||
})
|
||||
|
||||
export const problemSetProgressListSchema = paginatedSchema(problemSetProgressSchema).extend({
|
||||
statistics: z.object({
|
||||
total: z.number().int(),
|
||||
completed: z.number().int(),
|
||||
avgProgress: z.number(),
|
||||
}),
|
||||
problems: z.array(completedProblemSchema),
|
||||
})
|
||||
|
||||
export const userBadgeSchema = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
badge: problemSetBadgeSchema,
|
||||
earnedTime: z.string(),
|
||||
problemset: z.object({ id: z.number().int(), title: z.string() }),
|
||||
})
|
||||
19
packages/contract/src/site.ts
Normal file
19
packages/contract/src/site.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const websiteConfigSchema = z.object({
|
||||
websiteBaseUrl: z.string(),
|
||||
websiteName: z.string(),
|
||||
websiteNameShortcut: z.string(),
|
||||
websiteFooter: z.string(),
|
||||
allowRegister: z.boolean(),
|
||||
submissionListShowAll: z.boolean(),
|
||||
classList: z.array(z.string()),
|
||||
enableMaxkb: z.boolean(),
|
||||
})
|
||||
|
||||
export const quoteSchema = z.union([
|
||||
z.string(),
|
||||
z.record(z.string(), z.unknown()),
|
||||
])
|
||||
|
||||
export type WebsiteConfig = z.infer<typeof websiteConfigSchema>
|
||||
@@ -1,5 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema } from "./common"
|
||||
|
||||
export const judgeStatusSchema = z.union([
|
||||
z.literal(-2),
|
||||
z.literal(-1),
|
||||
@@ -55,6 +57,31 @@ export const submissionUpdateSchema = z.object({
|
||||
err_info: z.string().optional(),
|
||||
})
|
||||
|
||||
export const submissionListItemSchema = z.object({
|
||||
id: z.string(),
|
||||
problem: z.string(),
|
||||
problemTitle: z.string(),
|
||||
showLink: z.boolean(),
|
||||
createTime: z.string(),
|
||||
userId: z.number().int(),
|
||||
username: z.string(),
|
||||
result: judgeStatusSchema,
|
||||
language: z.string(),
|
||||
shared: z.boolean(),
|
||||
statisticInfo: z.record(z.string(), z.unknown()),
|
||||
})
|
||||
|
||||
export const submissionListSchema = paginatedSchema(submissionListItemSchema)
|
||||
|
||||
export const shareSubmissionRequestSchema = z.object({ shared: z.boolean() })
|
||||
|
||||
export const formatCodeRequestSchema = z.object({
|
||||
code: z.string().max(1024 * 1024),
|
||||
language: z.enum(["python", "c", "cpp", "sql"]),
|
||||
})
|
||||
|
||||
export const formatCodeResponseSchema = z.object({ code: z.string() })
|
||||
|
||||
export type JudgeStatus = z.infer<typeof judgeStatusSchema>
|
||||
export type CreateSubmissionRequest = z.infer<
|
||||
typeof createSubmissionRequestSchema
|
||||
|
||||
Reference in New Issue
Block a user