Django 多个 worker 内存占用 1-2 GB,换成 Bun 单进程后约 25 MB, 同时去掉 Postgres 和 Redis,只剩 Caddy + Bun 两个容器。 - server/:Hono + bun:sqlite + Bun 原生 WebSocket,接口与原 django-ninja 保持兼容;表结构、时间/UUID 存储格式、密码哈希与 Django 一致,旧数据可直接使用 - 评分加权、贝叶斯平滑、成绩册的计算结果与原实现逐位一致(复刻 Python round/sum) - scripts/:Postgres → SQLite 迁移、新建超管、提示词层级标注、重算分数 - 前端:Django Admin 去掉后,用户新建/修改改为页面内弹窗; 非超管的挑战公开按钮置灰 - 权限不足返回 JSON 403(原来是重定向到不存在的登录页) - docker-compose / Caddyfile / dev.sh / CLAUDE.md 更新,删除 AGENTS.md - 服务器切换步骤见 docs/migrate-to-bun.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
WebPreview is a web-based tutorial/challenge platform where users view tutorials and submit HTML/CSS/JS code for challenges. It has a Bun backend (server/) and a Vue 3 frontend (client/) in one repository, deployed via Docker Compose with Caddy as reverse proxy.
The backend was rewritten from Django in 2026-10 to cut memory use (~25 MB vs. 1–2 GB). It keeps Django's exact database schema (table names, column types, UTC YYYY-MM-DD HH:MM:SS.ffffff timestamps, 32-char hex UUIDs, pbkdf2_sha256$... password hashes) so old data loads unchanged. The Django code is in git history before that change.
Architecture
-
server/— Bun + Hono HTTP API, Bun's native WebSocket,bun:sqlite(single SQLite file, WAL). No Redis, no Postgres.src/index.ts—Bun.serve: WebSocket upgrades go tows.ts, everything else to the Hono appsrc/app.ts— mounts routers under/api/<prefix>, CORS, Origin check on writes, session middleware, error →{ detail }JSON (ninja-compatible; validation errors are 422)src/db.ts— schema (CREATE TABLE IF NOT EXISTS, same DDL as Django),all/get/run/txhelpers using$nameparams, time (now(),iso()) and UUID (uuidHex(),uuidOut()) conversionssrc/auth.ts— Django-compatible password hashing, sessions in tableauth_session(cookiesessionid),requireUser/requireAdmin/requireSuper(401 / 403)src/routes/—account,tasks(tutorial / challenge / creation),files(task assets + image upload),submission,showcase(mounted under/submission/showcase),promptsrc/services/—score.ts(rating weights super×0.5, admin×0.3, normal×0.2, then Bayesian smoothing with C=3 and zones),gradebook.ts,llm.ts(OpenAI-compatible streaming via fetch; DeepSeek default, ARK for doubao models),classifier.ts(Bloom-level L1–L6 prompt tagging, runs in background),cascade.ts(Django's CASCADE deletes, done by hand),pyround.ts(pyRound/pySumreproduce Python'sround()andsum()so scores match the old system bit-for-bit)src/ws.ts—/ws/prompt/<task_id>/(code generation chat) and/ws/guidance/<task_id>/(prompt coaching); messages per connection are processed serially; on disconnect the pending user message is deleted and the LLM request abortedscripts/— one-off CLIs:migrate-from-postgres.ts,create-superuser.ts,classify-prompts.ts,recalculate-scores.ts- Roles:
super,admin,normal. Task model is multi-table:task_task+ one oftask_tutorial/task_challenge/task_creation(task_ptr_id)
-
client/— Vue 3 + TypeScript SPA built with Rsbuild- UI framework: Naive UI (auto-imported via
unplugin-vue-components) src/api.ts— Axios-based API client; 401 clears login and goes home, 403 alerts and goes homesrc/router.ts— Vue Router with auth guardssrc/store/— Reactive state (plain Vue refs, not Vuex/Pinia);prompt.ts/guidance.tsown the WebSocketssrc/pages/,src/components/— user management usescomponents/dashboard/UserFormModal.vue(there is no Django Admin anymore)
- UI framework: Naive UI (auto-imported via
-
Caddyfile— serves SPA frombuild/and/media, proxies/api*and/ws*to the backend -
docker-compose.yaml—webpreview-web(Caddy) andwebpreview-api(Bun);webpreview-db(Postgres) exists only under themigrateprofile for the one-time data migration -
.github/workflows/deploy.yaml— on push tomain: build client, rsync to serverbuild/, thengit pull && docker compose up -d --buildon the server
Development Commands
./dev.sh # backend (bun --watch) + frontend together
Server (from server/)
bun run dev # dev server on :8000 with auto-reload
bun test # API + unit tests (in-memory SQLite, LLM disabled)
bun run typecheck
bun scripts/create-superuser.ts <username> <password>
bun scripts/classify-prompts.ts [--task-id N] [--force] [--dry-run]
bun scripts/recalculate-scores.ts [--dry-run]
Config comes from server/.env (see server/.env.example); dev DB is server/data/db.sqlite3, uploads go to the repo-root media/.
Client (from client/)
npm run start # Dev server (Rsbuild) on :3000
npm run build # Production build
npm run fmt # Format with Prettier
Frontend env vars used at build time: PUBLIC_BASE_URL, PUBLIC_WS_URL, PUBLIC_WEB_URL (dev proxy target for /media and /ws).
Docker (from project root)
docker compose up -d --build
docker compose run --rm webpreview-api bun scripts/create-superuser.ts <username> <password>
Key Technical Details
- Schema changes: there is no migration framework. Edit the DDL in
src/db.tsfor new tables; for altering existing tables add an idempotent step guarded byPRAGMA user_version - SQL style: raw SQL with named params (
$id),strict: trueso params are passed without the$prefix ({ id }). Wrap multi-statement writes intx(). Deleting tasks/submissions must go throughservices/cascade.ts - API compatibility: response shapes, status codes and trailing-slash tolerance (
strict: false) mirror the old django-ninja API, so the client needed no changes besides user management. Paginated endpoints takepage/page_size(default 10, max 100) and return{ items, count } - Task
displayfield: integer used as URL slug for tutorials/challenges/creations (unique per task type), not the DBid - Submission / conversation IDs: UUIDs in the API, 32-char hex in the DB
- Language/locale is Chinese; timestamps are stored in UTC and returned as ISO strings ending in
Z - Client build output is served by Caddy from
build/in production