style(格式化): 改成单引号、一行最长 160,全仓重新格式化
Deploy / deploy (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-26 13:52:24 +08:00
1 parent 530e1a3d5d
commit 195069450f
480 files changed
+15507 -24650

No files matched your search

+60 -80
View File
@@ -10,14 +10,14 @@ import {
type RankScope,
type SampleUser,
type WeeklyChampion,
} from "@oj2/contract"
import { and, eq, inArray, isNotNull } from "drizzle-orm"
import { Hono } from "hono"
} from '@oj2/contract'
import { and, eq, inArray, isNotNull } from 'drizzle-orm'
import { Hono } from 'hono'
import { optionalAuth, requireAuth, requireTeacher, type AppEnv } from "../auth/middleware"
import { db, schema } from "../db"
import { failure, parseBody, success } from "../http"
import { dayStart, termStart, weekStart } from "../time"
import { optionalAuth, requireAuth, requireTeacher, type AppEnv } from '../auth/middleware'
import { db, schema } from '../db'
import { failure, parseBody, success } from '../http'
import { dayStart, termStart, weekStart } from '../time'
import {
audienceWhere,
classBattle,
@@ -32,8 +32,8 @@ import {
standings,
WEEK_MS,
type Standing,
} from "../services/ranking"
import { isTeacherOrAbove, sampleUser } from "./helpers"
} from '../services/ranking'
import { isTeacherOrAbove, sampleUser } from './helpers'
/**
* 排名页(设计稿「排名重设计」G1–G3)。口径见契约 `ranking.ts` 的注释:
@@ -70,8 +70,8 @@ function toRow(standing: Standing, change: number | null): RankRow {
}
function periodStart(period: RankPeriod) {
if (period === "week") return weekStart()
if (period === "term") return termStart()
if (period === 'week') return weekStart()
if (period === 'term') return termStart()
return null
}
@@ -79,32 +79,26 @@ function periodStart(period: RankPeriod) {
* 这次请求看的是哪个班 / 哪个年级。学生只能看自己的班和年级;老师用 `className` 选,
* 不给就取最近布置过作业的班(`lessonClassFor`)。返回 undefined 表示该报「没有班级」。
*/
async function resolveGroup(
user: AppEnv["Variables"]["user"],
scope: RankScope,
requested: string | undefined,
): Promise<string | null | undefined> {
if (scope === "all") return null
async function resolveGroup(user: AppEnv['Variables']['user'], scope: RankScope, requested: string | undefined): Promise<string | null | undefined> {
if (scope === 'all') return null
let className = user?.className || null
if (isTeacherOrAbove(user)) className = requested || (await lessonClassFor(user!.id))
if (!className) return undefined
return scope === "class" ? className : className.slice(0, 2)
return scope === 'class' ? className : className.slice(0, 2)
}
rankingRoutes.get("/rankings/board", optionalAuth, async (c) => {
const user = c.get("user")
const scope = rankScopeSchema.safeParse(c.req.query("scope"))
const period = rankPeriodSchema.safeParse(c.req.query("period"))
if (!scope.success || !period.success)
return failure(c, 400, "invalid-query", "排名范围或时间段不对")
const group = await resolveGroup(user, scope.data, c.req.query("className")?.trim())
if (group === undefined) return failure(c, 400, "class-missing", "没有班级,看不了本班和本年级")
if (scope.data === "grade" && !/^\d{2}$/.test(group!))
return failure(c, 400, "invalid-grade", "年级不对")
rankingRoutes.get('/rankings/board', optionalAuth, async (c) => {
const user = c.get('user')
const scope = rankScopeSchema.safeParse(c.req.query('scope'))
const period = rankPeriodSchema.safeParse(c.req.query('period'))
if (!scope.success || !period.success) return failure(c, 400, 'invalid-query', '排名范围或时间段不对')
const group = await resolveGroup(user, scope.data, c.req.query('className')?.trim())
if (group === undefined) return failure(c, 400, 'class-missing', '没有班级,看不了本班和本年级')
if (scope.data === 'grade' && !/^\d{2}$/.test(group!)) return failure(c, 400, 'invalid-grade', '年级不对')
const start = periodStart(period.data)
const changeSince = period.data === "week" ? dayStart() : weekStart()
const includeZero = scope.data === "class"
const changeSince = period.data === 'week' ? dayStart() : weekStart()
const includeZero = scope.data === 'class'
const isTeacher = isTeacherOrAbove(user)
const [entrants, mine, hiddenUsers] = await Promise.all([
@@ -116,7 +110,7 @@ rankingRoutes.get("/rankings/board", optionalAuth, async (c) => {
.where(eq(schema.user.id, user.id))
.then(([row]) => row)
: undefined,
isTeacher && scope.data === "class"
isTeacher && scope.data === 'class'
? db
.select({ id: schema.user.id, username: schema.user.username })
.from(schema.user)
@@ -139,39 +133,30 @@ rankingRoutes.get("/rankings/board", optionalAuth, async (c) => {
.map((row) => [row.entrant.id, row.rank]),
)
const rowOf = (standing: Standing | undefined) =>
standing
? toRow(
standing,
before.has(standing.entrant.id) ? before.get(standing.entrant.id)! - standing.rank : null,
)
: null
standing ? toRow(standing, before.has(standing.entrant.id) ? before.get(standing.entrant.id)! - standing.rank : null) : null
// 一道没做对就不算上榜(本班的全班名单里也列着他,但没有名次可说,也没有前后一名)
const meIndex = user ? now.findIndex((row) => row.entrant.id === user.id && row.solved > 0) : -1
const me = meIndex >= 0 ? now[meIndex] : undefined
const cap = scope.data === "all" ? ALL_CAP : null
const cap = scope.data === 'all' ? ALL_CAP : null
const listable = cap ? now.slice(0, cap) : now
const full = includeZero || c.req.query("full") === "1"
const full = includeZero || c.req.query('full') === '1'
let picked = listable
if (!full) {
const head = me ? BOARD_HEAD : BOARD_HEAD_NO_ME
picked = listable.filter(
(_, index) =>
index < head || (me && index >= meIndex - WINDOW_BEFORE && index <= meIndex + WINDOW_AFTER),
)
picked = listable.filter((_, index) => index < head || (me && index >= meIndex - WINDOW_BEFORE && index <= meIndex + WINDOW_AFTER))
}
// 全服 100 名以外:不报具体名次,前后一名、走势都不给,只给第 100 名算还差几道
const beyond = !!(cap && me && me.rank > cap)
let trend: RankBoard["trend"] = null
if (me && !beyond && period.data !== "week") {
let trend: RankBoard['trend'] = null
if (me && !beyond && period.data !== 'week') {
const thisWeek = Date.parse(weekStart())
const points = Array.from(
{ length: TREND_WEEKS },
(_, k) => thisWeek - (TREND_WEEKS - 1 - k) * WEEK_MS,
).filter((point) => !start || point > Date.parse(start))
const points = Array.from({ length: TREND_WEEKS }, (_, k) => thisWeek - (TREND_WEEKS - 1 - k) * WEEK_MS).filter(
(point) => !start || point > Date.parse(start),
)
const tracked = [now[meIndex - 1], me, now[meIndex + 1]].filter((row): row is Standing => !!row)
const snapshots = points.map((point) => {
const ranks = new Map(
@@ -214,16 +199,16 @@ rankingRoutes.get("/rankings/board", optionalAuth, async (c) => {
* 班级对抗:全服每个班这学期人均做对几道,外加这周人均涨了多少(口径见 services/ranking.ts
* 的 classBattle)
*/
rankingRoutes.get("/rankings/classes", async (c) => {
rankingRoutes.get('/rankings/classes', async (c) => {
const { items } = await classBattle()
return success(c, items)
})
/** 班级详情:谁都能看(原来的弹框就是),「要多关心的同学」只给老师 */
rankingRoutes.get("/rankings/class-detail", optionalAuth, async (c) => {
const className = c.req.query("className")?.trim()
if (!className) return failure(c, 400, "class-missing", "没有班级")
return success(c, await classDetail(className, isTeacherOrAbove(c.get("user"))))
rankingRoutes.get('/rankings/class-detail', optionalAuth, async (c) => {
const className = c.req.query('className')?.trim()
if (!className) return failure(c, 400, 'class-missing', '没有班级')
return success(c, await classDetail(className, isTeacherOrAbove(c.get('user'))))
})
/**
@@ -231,26 +216,23 @@ rankingRoutes.get("/rankings/class-detail", optionalAuth, async (c) => {
* 层面的数,没有点名到人。只给一个班(或者不给,用自己的班 / 老师默认的班)就配一个对手,
* 见 `pkPartner`。
*/
rankingRoutes.get("/rankings/pk", requireAuth, async (c) => {
const user = c.get("user")!
const period = classPkPeriodSchema.safeParse(c.req.query("period") ?? "term")
if (!period.success) return failure(c, 400, "invalid-query", "时间段不对")
rankingRoutes.get('/rankings/pk', requireAuth, async (c) => {
const user = c.get('user')!
const period = classPkPeriodSchema.safeParse(c.req.query('period') ?? 'term')
if (!period.success) return failure(c, 400, 'invalid-query', '时间段不对')
const requested = [
...new Set(
(c.req.query("classes") ?? "")
.split(",")
(c.req.query('classes') ?? '')
.split(',')
.map((name) => name.trim())
.filter((name) => name && name.length <= 32),
),
]
if (requested.length > PK_MAX_CLASSES)
return failure(c, 400, "too-many-classes", `一次最多比 ${PK_MAX_CLASSES} 个班`)
if (requested.length > PK_MAX_CLASSES) return failure(c, 400, 'too-many-classes', `一次最多比 ${PK_MAX_CLASSES} 个班`)
const isTeacher = isTeacherOrAbove(user)
const mine = isTeacher ? null : user.className || null
const classNames = requested.length
? requested
: [isTeacher ? await defaultClassFor(user.id) : mine].filter((name): name is string => !!name)
const classNames = requested.length ? requested : [isTeacher ? await defaultClassFor(user.id) : mine].filter((name): name is string => !!name)
if (classNames.length === 1) {
const partner = await pkPartner(classNames[0]!)
if (partner) classNames.push(partner)
@@ -259,21 +241,19 @@ rankingRoutes.get("/rankings/pk", requireAuth, async (c) => {
})
/** 本班每周冠军:最近几个已经结束的周,每周新做对最多的那个人(一样多先做到的) */
rankingRoutes.get("/rankings/champions", optionalAuth, async (c) => {
const className = await resolveGroup(c.get("user"), "class", c.req.query("className")?.trim())
if (!className) return failure(c, 400, "class-missing", "没有班级")
rankingRoutes.get('/rankings/champions', optionalAuth, async (c) => {
const className = await resolveGroup(c.get('user'), 'class', c.req.query('className')?.trim())
if (!className) return failure(c, 400, 'class-missing', '没有班级')
const thisWeek = Date.parse(weekStart())
const since = new Date(thisWeek - CHAMPION_WEEKS * WEEK_MS).toISOString()
const entrants = await loadEntrants(audienceWhere("class", className), since)
const entrants = await loadEntrants(audienceWhere('class', className), since)
const champions: WeeklyChampion[] = []
for (let k = 1; k <= CHAMPION_WEEKS; k++) {
const from = thisWeek - k * WEEK_MS
const inWeek = entrants.map((entrant) => {
const keep = entrant.times
.map((time, index) => [time, entrant.stamps[index]!] as const)
.filter(([time]) => time >= from && time < from + WEEK_MS)
const keep = entrant.times.map((time, index) => [time, entrant.stamps[index]!] as const).filter(([time]) => time >= from && time < from + WEEK_MS)
return {
...entrant,
times: keep.map(([time]) => time),
@@ -296,9 +276,9 @@ rankingRoutes.get("/rankings/champions", optionalAuth, async (c) => {
* 老师把一个学生设成不计入排名 / 恢复。怀疑抄代码时用(用户 2026-10-10 定的)。
* 所有排名页的榜和首页周榜都不出现他;他自己看得到「不计入排名」;比赛排名不受影响。
*/
rankingRoutes.put("/rankings/hidden/:userId", requireTeacher, async (c) => {
const userId = Number(c.req.param("userId"))
if (!Number.isInteger(userId)) return failure(c, 400, "invalid-user", "用户不对")
rankingRoutes.put('/rankings/hidden/:userId', requireTeacher, async (c) => {
const userId = Number(c.req.param('userId'))
if (!Number.isInteger(userId)) return failure(c, 400, 'invalid-user', '用户不对')
const body = await parseBody(c, rankHiddenRequestSchema)
if (!body.success) return body.response
@@ -307,7 +287,7 @@ rankingRoutes.put("/rankings/hidden/:userId", requireTeacher, async (c) => {
.set({ rankHiddenAt: body.data.hidden ? new Date().toISOString() : null })
.where(and(eq(schema.user.id, userId), inArray(schema.user.adminType, [...STUDENT_ROLES])))
.returning({ id: schema.user.id })
if (!updated.length) return failure(c, 404, "user-not-found", "没有这个学生")
if (!updated.length) return failure(c, 404, 'user-not-found', '没有这个学生')
return success(c, null)
})
@@ -315,9 +295,9 @@ rankingRoutes.put("/rankings/hidden/:userId", requireTeacher, async (c) => {
* 老师清空一个学生的个性签名。签名在排名上挂出来以后更显眼,拿同学开涮、留电话号码的
* 得有人能拿掉(用户 2026-10-10 定的)。只清这一句,他之后还能重新写。
*/
rankingRoutes.delete("/rankings/mood/:userId", requireTeacher, async (c) => {
const userId = Number(c.req.param("userId"))
if (!Number.isInteger(userId)) return failure(c, 400, "invalid-user", "用户不对")
rankingRoutes.delete('/rankings/mood/:userId', requireTeacher, async (c) => {
const userId = Number(c.req.param('userId'))
if (!Number.isInteger(userId)) return failure(c, 400, 'invalid-user', '用户不对')
const updated = await db
.update(schema.userProfile)
@@ -335,6 +315,6 @@ rankingRoutes.delete("/rankings/mood/:userId", requireTeacher, async (c) => {
),
)
.returning({ id: schema.userProfile.userId })
if (!updated.length) return failure(c, 404, "user-not-found", "没有这个学生")
if (!updated.length) return failure(c, 404, 'user-not-found', '没有这个学生')
return success(c, null)
})