Some checks failed
Deploy / deploy (push) Has been cancelled
原来只有 `apps/web` 在 Prettier 下(配置在 `apps/web/.prettierrc.toml`、脚本在 web 的 package.json),后端和契约从来没格式化过 —— 手写在 100 列上下,`db/schema.ts` 还是 drizzle-kit pull 留下的 tab 缩进。两套口径分叉久了,跨端改一处就得记着「这边 什么风格」。 - 配置搬到根目录 `.prettierrc.toml`,内容不变(`semi=false`,其余全默认, printWidth 80 —— 和前端已有的格式一致,不另立一套宽度); - 脚本统一成根目录 `bun run fmt`,覆盖 `apps/*/src`、`apps/web/tests` 和两个构建 配置;web 自己那份 `fmt` 和重复的 prettier 依赖删掉; - `.prettierignore` 挡掉两类不该碰的:drizzle-kit 生成的 `src/db/meta/` 结构快照 (它是 db:generate 的比对输入,只该由 drizzle-kit 写)、unplugin 每次 dev 都会 重写的 `auto-imports.d.ts` / `components.d.ts`; - 全量跑了一遍。纯格式,无行为改动:api typecheck / check:routes / check:ast、 前端 type-check 全过,起 api 打了接口确认正常。前端这 39 个文件的小改动是 prettier 版本漂移(类型断言的换行口径变了),不是新配置带来的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137 lines
3.8 KiB
TypeScript
137 lines
3.8 KiB
TypeScript
/**
|
||
* 课堂求助的内存状态。
|
||
*
|
||
* 不落库是有意的:求助是课堂上的即时行为,学生关掉页面这条请求就该消失。
|
||
* 服务端只有一个 serve 进程(main.ts 单二进制 + 子命令,compose 里 oj-api 一个容器),
|
||
* 所以内存态够用,不需要 Redis 同步。进程重启丢掉全部状态,两端重连后回到干净状态。
|
||
*/
|
||
|
||
export type CollabSocket = Bun.ServerWebSocket<
|
||
import("../websocket").SubmissionSocketData
|
||
>
|
||
|
||
/**
|
||
* 协作支持的语言。和前端 utils/types.ts 里的 LANGUAGE 对齐,去掉 Flowchart ——
|
||
* 流程图题没有代码编辑器,求助入口本身就是隐藏的。
|
||
*/
|
||
export const COLLAB_LANGUAGES = [
|
||
"C",
|
||
"C++",
|
||
"Python2",
|
||
"Python3",
|
||
"Java",
|
||
"JavaScript",
|
||
"Golang",
|
||
"SQL",
|
||
] as const
|
||
|
||
export type CollabLanguage = (typeof COLLAB_LANGUAGES)[number]
|
||
|
||
/** 认不出来的一律当 C:老客户端不带这个字段,而它以前就是写死 C 的 */
|
||
export function normalizeLanguage(value: unknown): CollabLanguage {
|
||
return (COLLAB_LANGUAGES as readonly string[]).includes(value as string)
|
||
? (value as CollabLanguage)
|
||
: "C"
|
||
}
|
||
|
||
export interface HelpRequest {
|
||
studentId: number
|
||
studentName: string
|
||
className: string | null
|
||
/** 题目的展示号(problem._id 列,前端一路用的都是它),不是自增主键 */
|
||
problemId: string
|
||
problemTitle: string
|
||
/**
|
||
* 学生编辑器当前的语言。决定教师端弹框用哪套高亮和补全 —— 求助时带上,
|
||
* 协作期间学生切语言会用 help_language 更新这里
|
||
*/
|
||
language: CollabLanguage
|
||
createdAt: number
|
||
status: "pending" | "active"
|
||
teacherId?: number
|
||
teacherName?: string
|
||
socket: CollabSocket
|
||
}
|
||
|
||
/** 求助表,以学生为键 —— 一个学生同时只有一个求助 */
|
||
const requests = new Map<number, HelpRequest>()
|
||
|
||
/** 在线老师的连接。用于推列表,也用于判断 no_teacher */
|
||
const teachers = new Set<CollabSocket>()
|
||
|
||
export function addRequest(request: HelpRequest) {
|
||
requests.set(request.studentId, request)
|
||
}
|
||
|
||
export function getRequest(studentId: number) {
|
||
return requests.get(studentId)
|
||
}
|
||
|
||
export function removeRequest(studentId: number) {
|
||
return requests.delete(studentId)
|
||
}
|
||
|
||
/** 按发起时间正序。老师端按等待时长排序展示,不强制先来先到 */
|
||
export function listRequests() {
|
||
return Array.from(requests.values()).sort((a, b) => a.createdAt - b.createdAt)
|
||
}
|
||
|
||
/** 比自己早创建、且仍在排队的请求数 */
|
||
export function queueAheadOf(studentId: number) {
|
||
const self = requests.get(studentId)
|
||
if (!self) return 0
|
||
let ahead = 0
|
||
for (const request of requests.values()) {
|
||
if (request.status === "pending" && request.createdAt < self.createdAt)
|
||
ahead += 1
|
||
}
|
||
return ahead
|
||
}
|
||
|
||
export function addTeacher(ws: CollabSocket) {
|
||
teachers.add(ws)
|
||
}
|
||
|
||
export function removeTeacher(ws: CollabSocket) {
|
||
teachers.delete(ws)
|
||
}
|
||
|
||
export function hasTeacherOnline() {
|
||
return teachers.size > 0
|
||
}
|
||
|
||
export function teacherSockets() {
|
||
return teachers
|
||
}
|
||
|
||
export interface Room {
|
||
/** 房主 = 学生。房间以学生为键,因为学生的代码是内容源 */
|
||
studentId: number
|
||
teacherId: number
|
||
studentSocket: CollabSocket
|
||
teacherSocket: CollabSocket
|
||
problemId: string
|
||
/** 建房那一刻学生的语言,之后跟着 help_language 走 */
|
||
language: CollabLanguage
|
||
}
|
||
|
||
const rooms = new Map<number, Room>()
|
||
|
||
export function openRoom(room: Room) {
|
||
rooms.set(room.studentId, room)
|
||
}
|
||
|
||
export function getRoom(studentId: number) {
|
||
return rooms.get(studentId)
|
||
}
|
||
|
||
export function closeRoom(studentId: number) {
|
||
return rooms.delete(studentId)
|
||
}
|
||
|
||
/** 这条连接当前所在的房间。ws.data.roomOwnerId 是房主(学生)的 id */
|
||
export function roomOf(ws: CollabSocket) {
|
||
const ownerId = ws.data.roomOwnerId
|
||
return ownerId === undefined ? undefined : rooms.get(ownerId)
|
||
}
|