## 排序 学生端 /problem-sets/:id/problems 只按 order 排,没有 tiebreaker,而卡片是按数组 下标编号的(#1 #2 #3)。order 并列时 Postgres 不保证次序,题单 8(3 道题 order 都是 10)、题单 11(2 道并列 4)、题单 14(3 道并列 0)实际就有并列,于是「第 3 题」指 哪道题每次刷新都可能变。后台那条列表一直是 order + id 排的,两边本来就不一致。 补上 asc(id)。教师进度视图里那份题目清单(同一路由文件 :398)有同样的问题,一并补。 实跑:四道题、三道 order 并列,连打 5 次次序完全一致。 ## 载荷 这个接口原来复用 problemListItemSchema,为此要多 join user + user_profile 凑 createdBy、再多查一次标签表凑 tags —— 而题单卡片只渲染题号、标题、难度、分数和 完成标记。tags / submissionNumber / acceptedNumber / createdBy / contestId / allowFlowchart / showFlowchart / hasAstRules 一个都不用,myStatus 甚至写死是 null。 而且 select 的是 schema.problem 整行,题面、样例、答案、ast_rules、flowchart_data、 sql_display 全都白拉回来一遍。 改成 problemSetProblemItemSchema(id / _id / title / difficulty 四个字段),查询相应 收窄:两次 join 去掉,标签那次查询整条去掉,行查询只取四列。每次请求从 4 条查询降到 3 条,其中最大的那条不再拖整行题面。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
import { z } from "zod"
|
||
|
||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||
import { maskedProblemDifficultySchema } 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)
|
||
|
||
/**
|
||
* 题单页的题目卡片只渲染题号、标题、难度,所以只下发这三样(外加题目主键)。
|
||
*
|
||
* 以前这里复用 problemListItemSchema,代价是服务端每次都要多 join 一次 user +
|
||
* user_profile 凑 createdBy、再多查一次标签表凑 tags,而 tags / submissionNumber /
|
||
* acceptedNumber / createdBy / flowchart / hasAstRules / myStatus 在题单页一个都不渲染
|
||
* —— myStatus 甚至是写死的 null。
|
||
*/
|
||
export const problemSetProblemItemSchema = z.object({
|
||
id: z.number().int(),
|
||
_id: z.string(),
|
||
title: z.string(),
|
||
difficulty: maskedProblemDifficultySchema,
|
||
})
|
||
|
||
export const problemSetProblemSchema = z.object({
|
||
id: z.number().int(),
|
||
problemsetId: z.number().int(),
|
||
problem: problemSetProblemItemSchema,
|
||
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() }),
|
||
})
|
||
|
||
export type ProblemSet = z.infer<typeof problemSetSchema>
|
||
export type ProblemSetList = z.infer<typeof problemSetListSchema>
|
||
export type ProblemSetBadge = z.infer<typeof problemSetBadgeSchema>
|
||
export type ProblemSetProblem = z.infer<typeof problemSetProblemSchema>
|
||
export type ProblemSetProgress = z.infer<typeof problemSetProgressSchema>
|
||
export type ProblemSetProgressList = z.infer<typeof problemSetProgressListSchema>
|
||
export type UserBadge = z.infer<typeof userBadgeSchema>
|
||
export type CompletedProblem = z.infer<typeof completedProblemSchema>
|
||
|
||
export type ProblemSetUserProgressSummary = z.infer<typeof problemSetUserProgressSummarySchema>
|
||
export type JoinProblemSetRequest = z.infer<typeof joinProblemSetRequestSchema>
|
||
export type UpdateProblemSetProgressRequest = z.infer<typeof updateProblemSetProgressRequestSchema>
|