Files
OJ2/apps/api/src/collab/state.ts
yuetsh ed56a209ea
Some checks failed
Deploy / deploy (push) Has been cancelled
chore(格式): Prettier 统一到全仓,后端和契约一次性格式化
原来只有 `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>
2026-09-16 08:27:34 -06:00

137 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 课堂求助的内存状态。
*
* 不落库是有意的:求助是课堂上的即时行为,学生关掉页面这条请求就该消失。
* 服务端只有一个 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)
}