diff --git a/README.en.md b/README.en.md index de57ffb..1fae69a 100644 --- a/README.en.md +++ b/README.en.md @@ -1,94 +1,312 @@ -# International Chinese Platform + -A public-beta full-stack platform for international Chinese education. Students, teachers, and administrators share persistent workflows for registration, teacher verification, courses, appointments, live classrooms, assignments, notifications, and dialogue practice. +
+ + International Chinese Platform logo: Chinese language and a globe + -## Implemented capabilities +

International Chinese Platform

-- Email-code registration, login/logout, database sessions, and HttpOnly cookies -- Student, teacher, and administrator authorization with ownership checks -- Teacher applications, administrator review, and public teacher discovery -- Course draft, review, revision, publishing, and ratings -- Appointment acceptance/rejection, classroom creation, and completion -- Assignment questions, submissions, grading, and results -- Notifications, persistent dialogue sessions, and audit logs -- One-time WebSocket tickets, classroom chat, presence, and WebRTC signalling -- PostgreSQL numbered migrations, transactions, and concurrency guards -- Private R2/MinIO storage with upload intents, quota reservations, content validation, conditional promotion, and signed downloads -- One-time production administrator bootstrap with mandatory first-login password reset +

A public-beta, full-stack collaboration platform for international Chinese education.

+

It connects teacher verification, course review, scheduled classrooms, and assignment feedback into one deployable, testable, auditable learning workflow.

-## Architecture +

+ Overview + · + Learning journeys + · + Quick start + · + Architecture + · + Quality evidence + · + Documentation + · + 中文 +

+ +

+ + CI + + Status: public beta + 71 Node tests + 4 cross-role browser workflows + Vue 3 + Fastify 5 + PostgreSQL 15 + Cloudflare R2 and MinIO + Node.js 24 + pnpm 11 + Docker Compose +

+
+ +--- + + + +## Overview + +International Chinese Platform is not a screen-only course prototype. It is a full-stack system that models real teaching collaboration: students, teachers, and administrators work against the same PostgreSQL domain data for authentication, teacher verification, course review, appointments, live classrooms, assignments, notifications, and Chinese dialogue practice. + +The repository contains the Vue 3 client, Fastify domain API, numbered migrations, private object-storage adapter, real-time classroom protocol, tests, deployment configuration, and operating guides. The recommended public-beta topology serves the web application from Vercel, versioned APIs from Railway, private files from Cloudflare R2, and production email through a signed Vercel relay. It does not rely on an undocumented or stale external backend. + +> [!IMPORTANT] +> Completeness contract: within the current public-beta boundary, a reader can install the repository, start PostgreSQL and MinIO, migrate and seed demo data, exercise all three roles, run the tests, and follow the documentation to deploy the core services. Completeness does not mean inventing commercial-LMS features: payments, formal enrollment, recording, multi-node real-time fan-out, and high availability are explicitly future scope. + +### Why this repository is worth a closer look + +- Business workflows instead of a menu of screens: courses, appointments, assignments, and teacher verification have server-enforced transitions, permissions, notifications, and audit records. +- One source of truth for every role: the student, teacher, and administrator workspaces operate on the same PostgreSQL domain data rather than independent mock views. +- Security boundaries live in the implementation: opaque server sessions use HttpOnly cookies; writes validate origin, role, and resource ownership; files are verified before promotion from temporary storage. +- A coherent path from local development to public beta: development uses PostgreSQL and MinIO; production uses Vercel, Railway, PostgreSQL, R2, and an HMAC-protected mail relay. +- Honest operational limits: classroom presence is currently held by one API process, so the beta deliberately runs a single backend instance instead of claiming high availability that does not exist. + + + +## Four verifiable learning journeys + +Each journey should show how a real action crosses roles, state, and persistence boundaries. The four flows below are also the core journeys exercised by browser E2E. ```mermaid -flowchart LR - Browser["Vue 3 browser client"] -->|"HTTPS / page"| Vercel[Vercel] - Vercel -->|"/api/* rewrite"| API["Railway · Fastify"] - Browser -->|"WSS"| API - Browser -->|"10-minute presigned PUT"| R2["Cloudflare R2 · private"] - API --> PG[(Railway PostgreSQL)] - API --> R2 - API --> Gmail["Gmail SMTP"] +flowchart TB + subgraph Verification["Teacher verification"] + V1["Teacher registers or changes profile"] -->|enters review queue| V2["Administrator verifies"] + V2 -->|approves| V3["Verified: publicly discoverable and bookable"] + V3 -->|revoked or profile changes again| V1 + end + + subgraph Course["Course review"] + C1["Teacher drafts a course"] -->|submits| C2["Pending review"] + C2 -->|approves| C3["Published: visible to students"] + C2 -->|rejects with reason| C4["Rejected"] + C4 -->|revises and resubmits| C2 + end + + subgraph Lesson["Appointment and classroom"] + L1["Student requests a lesson"] -->|teacher accepts + conflict checks| L2["Accepted + dedicated classroom"] + L1 -->|teacher rejects or either party cancels| L3["Closed"] + L2 -->|short-lived one-time ticket| L4["Chat, presence, WebRTC signaling"] + L4 -->|participant completes lesson| L5["Completed"] + end + + subgraph Assignment["Assignment and feedback"] + A1["Teacher writes and publishes"] -->|student saves and submits| A2["Submission"] + A2 -->|teacher grades with feedback| A3["Student sees result"] + A1 -->|teacher closes assignment| A4["No new writes"] + end ``` -The Beta keeps one backend instance because live-room membership is held in process memory. Vercel proxies `/api/*` so browser sessions remain first-party cookies; classroom WebSockets connect directly to Railway. +| Journey | What the user experiences | Server-side guarantee | +| ------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Teacher verification | A profile is reviewed; a revoked teacher disappears from discovery and booking. | Administrator decisions, conflict protection, audit events, and notifications; profile changes return the teacher to review. | +| Course review | A draft can be rejected, revised, resubmitted, and published; students see only published courses. | Teacher ownership, explicit review state transitions, and publication visibility. | +| Appointment and classroom | A student requests a lesson; a teacher accepts; both enter a dedicated classroom. | Mutual conflict checks, transactional classroom creation, short-lived one-time WebSocket tickets, and room isolation. | +| Assignment and feedback | A teacher publishes work, a student submits, a teacher grades, and the student sees feedback. | Deadline and closure checks, submission ownership, and persistent grading results. | + +### Three role workspaces -## Local development +| Student | Teacher | Administrator | +| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| Discover verified teachers and published courses; request lessons; submit work; practice persistent Chinese dialogue; read notifications and grades. | Maintain a professional profile; submit courses; manage appointments; join classrooms; publish, close, and grade assignments. | Verify teachers; review courses; inspect aggregated data, audit activity, and platform-level workflow state. | -Requires Node.js 24, pnpm 11, and Docker Desktop / Docker Engine. + + +## Quick local start + +### Prerequisites + +- Node.js 24 recommended; Node.js 22 and later supported +- pnpm 11.9.0, pinned by packageManager +- Docker Desktop or Docker Engine for local PostgreSQL and MinIO +- Optional: Python 3.12 plus Playwright Chromium for browser E2E + +### 1. Clone, install, and create local configuration ```bash +git clone https://github.com/computersciencefreshmen/International_Chinese_Platform.git +cd International_Chinese_Platform +corepack enable pnpm install --frozen-lockfile +``` + +Windows PowerShell: + +```powershell +Copy-Item .env.example .env.local +``` + +macOS / Linux: + +```bash +cp .env.example .env.local +``` + +.env.local keeps the development API, PostgreSQL, and MinIO connections explicit. SMTP, AI, and TURN are not prerequisites for the core teaching journeys. + +### 2. Start dependencies, migrate data, seed the demo, and run the app + +On a first image pull, wait until PostgreSQL is healthy and minio-init has completed before migrating and seeding. + +```bash docker compose up -d postgres minio minio-init pnpm db:migrate pnpm db:seed pnpm dev ``` -- Frontend: `http://localhost:5173` -- API: `http://localhost:7777/api/v1` -- MinIO console: `http://localhost:9001` +Then open: -See [`.env.example`](./.env.example). Local development and CI use PostgreSQL as well, eliminating SQLite/production drift. +- Web: http://localhost:5173 +- API: http://localhost:7777/api/v1 +- MinIO Console: http://localhost:9001 -## Commands +### 3. Verify that startup worked + +macOS / Linux: ```bash -pnpm dev -pnpm build -pnpm db:migrate -pnpm db:seed -pnpm admin:bootstrap -pnpm test:api -pnpm check -pnpm backup:create -pnpm backup:restore +curl --fail http://localhost:7777/api/v1/ready ``` -Production configuration rejects `SEED_ON_START=true`; demo users and courses are never imported automatically. +Windows PowerShell: + +```powershell +Invoke-RestMethod http://localhost:7777/api/v1/ready +``` + +When the API is ready, open the web app and use these local-demo-only accounts: + +| Role | Email | Password | +| ------------- | -------------------------------- | --------------------- | +| Student | student@example.com | Demo123! | +| Teacher | teacher@example.com | Demo123! | +| Administrator | admin@example.com | Demo123! | + +> Teacher verification is a manual platform-administrator review, not issuer-side credential verification. External certificate OCR and authenticity checking remain out of scope. -## File security model +> [!CAUTION] +> Demo data is for explicit local or test seeding only. Production rejects SEED_ON_START=true; do not run pnpm db:seed or pnpm db:reset against a production database. -The browser may write only short-lived keys under `tmp/uploads/`; signatures bind Content-Type and Content-Length. Completion streams the object, validates size, magic bytes, and SHA-256, then conditionally copies the source ETag into a new `files/` key. A final key never receives a PUT URL, so reusing the original URL cannot overwrite a verified file. +### Useful commands -Deletion is backed by a retryable database outbox. Production R2 should also expire `tmp/` after one day. Private video and material downloads first pass API authorization and then redirect to a five-minute signed URL. +```bash +pnpm dev # run Vite and Fastify together +pnpm build # build the production web client +pnpm db:migrate # apply pending numbered PostgreSQL migrations +pnpm db:seed # import idempotent demo data outside production +pnpm db:reset # rebuild local development data; drops the local public schema +pnpm admin:bootstrap # create the first production administrator once +pnpm test:api # Node API, database, and security tests +pnpm check # ESLint + Prettier + API tests + production build +pnpm backup:create # after production backup setup, create an encrypted PostgreSQL backup +pnpm backup:restore # after production restore setup, restore only with explicit CONFIRM_RESTORE +``` + + -## Deployment +## Architecture: clear responsibilities, explicit trust boundaries + +```mermaid +flowchart LR + Browser["Browser · Vue 3"] + Vercel["Vercel · SPA"] + API["Railway · single Fastify instance"] + PG[("Railway PostgreSQL")] + R2["Cloudflare R2 · private objects"] + Relay["Vercel Function · /api/mail-relay"] + Gmail["Gmail SMTP"] + Backup["Railway Cron · encrypted backup"] + BackupBucket["private R2 backup bucket"] + + Browser -->|"HTTPS page"| Vercel + Vercel -->|"/api/v1/* rewrite"| API + Browser -->|"WSS classroom connection"| API + Browser -->|"10-minute presigned PUT"| R2 + API --> PG + API --> R2 + API -->|"HMAC-signed HTTPS payload"| Relay + Relay -->|"SMTP 465"| Gmail + Backup -->|"pg_dump + AES-256-GCM"| BackupBucket +``` -- Frontend: Vercel project `international-chinese-platform` -- Backend: one Railway Docker service -- Database: Railway PostgreSQL -- Files: private Cloudflare R2 bucket -- Mail: Gmail SMTP application password +For local development, Vite, Fastify, PostgreSQL, and MinIO replace Vercel, Railway, and R2. The application keeps the same S3 interface and database-migration discipline so that development behavior does not silently diverge from production. Before a browser receives a presigned PUT, it asks the API for a restricted upload intent; after upload, the API checks size, magic bytes, and SHA-256, then promotes the object only when its ETag still matches. -See the [operations manual](./docs/operations.md) for exact secrets, R2 CORS/lifecycle, administrator bootstrap, backups, restore drills, and rollback. Architecture decisions are in [`docs/adr`](./docs/adr). +| Component | Responsibility | Why it is separated this way | +| --------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| Vue 3 + Vercel | UI, routing, and static assets | Static content stays close to users; only versioned business APIs are rewritten, so the browser still reaches the API with first-party cookies. | +| Fastify + Railway | Domain API, authorization, state transitions, and WebSocket | Teaching rules remain on the server; the beta keeps one instance instead of pretending cross-instance room coordination is solved. | +| PostgreSQL | Source of truth for users, courses, appointments, assignments, reviews, notifications, and audit data | Numbered migrations, transactions, and concurrency protection keep cross-role workflows independent of front-end ordering. | +| Cloudflare R2 / MinIO | Private files and temporary upload objects | Direct browser upload avoids proxying large bodies through the API; MinIO reproduces the S3 contract locally. | +| Vercel mail relay | Holds Gmail SMTP credentials and delivers verification mail | Railway sends a minimal signed payload; SMTP credentials never live in the browser, API environment, or Git. | +| Railway backup Cron | Exports and encrypts PostgreSQL backups | Backup work is separate from the API process, and restore still requires explicit confirmation. | + + + +## Engineering credibility and quality evidence + +| Area | Implementation | Evidence | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| Identity and authorization | scrypt password hashes, server-side session digests, HttpOnly cookies, origin checks, role and resource-ownership checks | [authentication tests](./server/test/auth.test.js), [security policy](./SECURITY.md) | +| Teaching state | PostgreSQL transactions, conditional updates, numbered migrations, audit records, and notifications | [domain routes](./server/routes), [migrations](./server/db/migrations) | +| Private files | upload intents, temporary keys, content type and length binding, magic-byte/SHA-256 validation, ETag-conditional promotion, short-lived downloads | [object-storage ADR](./docs/adr/0004-r2-object-storage.md), [file tests](./server/test/files.test.js) | +| Live classroom | short-lived single-use classroom tickets; history, membership, and WebRTC signaling isolated by classroom | [classroom tests](./server/test/realtime-classroom.test.js) | +| Mail boundary | HMAC request from Railway to a Vercel Function with freshness and strict-payload checks | [mail-relay ADR](./docs/adr/0006-vercel-hmac-mail-relay.md) | +| Delivery and recovery | health/readiness probes, expand-only migrations, encrypted backups, restore confirmation, and rollback guidance | [operations manual](./docs/operations.md) | + +### What CI actually proves + +pnpm check runs ESLint, Prettier, the complete Node API/database/security suite, and a Vite production build. The repository currently contains 71 Node tests. GitHub Actions also starts isolated PostgreSQL and MinIO services, builds the production app, and exercises four cross-role browser journeys: + +1. Course submission → rejection → revision → approval → student catalog visibility. +2. Student request → teacher acceptance → dedicated classroom → classroom completion. +3. Teacher publishes → student submits → teacher grades → student sees feedback. +4. Administrator revokes verification → student cannot discover or book → administrator reapproves → teacher becomes public again. + +To reproduce browser E2E locally, install e2e/requirements.txt and Playwright Chromium, start the service with the same PostgreSQL, MinIO, and production environment used by the [CI workflow](./.github/workflows/ci.yml), then run python e2e/test_workflows.py. See the [Node tests](./server/test) and [browser E2E](./e2e/test_workflows.py). + +## Deployment, operations, and beta scope + +The recommended public-beta topology is Vercel for the Vue SPA and mail-relay function; Railway for one Fastify API instance, PostgreSQL, and an independent backup Cron; and Cloudflare R2 for private files and encrypted backups. Secrets, R2 CORS, migrations, administrator bootstrap, monitoring, restore drills, and rollback are documented in the [operations manual](./docs/operations.md). + +| A good fit today | Not in the current scope | +| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Course projects, portfolio demonstrations, small self-hosted teaching collaboration, and public-beta validation | Payments, formal enrollment and seat deduction, recording, external certificate OCR/verification, paid AI, multi-instance real-time fan-out, and high availability | +| Classrooms that can connect directly or have a configured TURN service | Reliable audio/video behind strict NAT, because TURN is not bundled yet | + +Course price and capacity are informational only; they are not a payment or seat-allocation system. Until a formal enrollment model exists, assignments for published courses are visible to and submittable by every signed-in student; there is no roster or enrollment authorization layer yet. Before the system can scale horizontally, classroom presence needs cross-instance broadcast and coordination. + + + +## Documentation map + +- [Public-beta operations manual](./docs/operations.md): production variables, R2, mail relay, backup, restore, and rollback. +- [ADR 0003: PostgreSQL](./docs/adr/0003-postgresql-production-database.md), [ADR 0004: R2](./docs/adr/0004-r2-object-storage.md), [ADR 0005: Vercel / Railway](./docs/adr/0005-vercel-railway-split-deployment.md), and [ADR 0006: HMAC mail relay](./docs/adr/0006-vercel-hmac-mail-relay.md): the current production architecture decisions. +- [Historical plans](./docs/plans): the migration story from the earlier SQLite prototype to the PostgreSQL/R2 cloud architecture. These are not the source of truth for the current runtime. +- [Security policy](./SECURITY.md): private disclosure, supported scope, and deployment security expectations. + +## Main directories + +```text +src/ Vue 3 client and role workspaces +server/routes/ Fastify domain API +server/db/migrations/ numbered PostgreSQL migrations +server/services/ mail, dialogue, and object-storage adapters +server/ops/ backup and restore +server/test/ API, database, and security integration tests +e2e/ four cross-role Playwright workflows +docs/adr/ architecture decision records +docs/operations.md deployment and operations manual +``` -## Verification +## Contributions, security, and license -CI uses PostgreSQL and isolated MinIO, then runs source checks, API integration tests, the production build, and cross-role Playwright workflows. File tests cover forged extensions, quota races, concurrent completion, presigned URL reuse, ETag-conditional promotion, and unauthorized downloads. +Issues and pull requests are welcome. For larger behavior changes, describe the user journey, server-side state change, test strategy, and documentation impact first; each pull request should keep implementation, tests, and documentation aligned. -## Public Beta limits +Report security issues privately through [SECURITY.md](./SECURITY.md). Do not publish credentials, personal data, or vulnerability details in public issues, screenshots, or demo environments. -- No TURN server; audio/video may fail behind strict NAT. -- No multi-instance room broadcasting, HA, payments, formal enrollment, class recording, or paid external AI. -- Price and capacity are informational course fields, not payment or seat allocation. +This repository currently has no open-source license. Except where applicable law clearly permits otherwise, obtain the maintainer’s permission before reuse, redistribution, or production use. -Security issues should be reported privately through [`SECURITY.md`](./SECURITY.md), never through a public issue containing credentials or vulnerability details. +

Back to top ↑

diff --git a/README.md b/README.md index bca21ff..e6ea0af 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,312 @@ -# International Chinese Platform + -面向国际中文教育的公开 Beta 全栈平台。学生、教师和管理员在同一套持久化数据上完成注册、教师审核、课程、预约、实时课堂、作业、通知和对话流程。 +
+ + International Chinese Platform 标志:汉语与地球 + -## 当前能力 +

International Chinese Platform

-- 邮箱验证码注册、登录、退出、数据库 Session 与 HttpOnly Cookie -- 学生、教师、管理员三角色权限与资源所有权校验 -- 教师资料申请、管理员审核与公开教师检索 -- 课程草稿、提交、驳回、修改、发布和评价 -- 师生预约、接受/拒绝、课堂创建与完成 -- 作业题目、提交、评分和学生结果查看 -- 通知、持久化对话、审计日志 -- 一次性 WebSocket 课堂票据、聊天、在线状态和 WebRTC 信令 -- PostgreSQL 编号迁移、事务和并发状态保护 -- R2/MinIO 私有对象存储、直传意图、配额预留、魔数/SHA-256 校验、条件晋升和签名下载 -- 一次性生产管理员初始化与首次强制改密 +

面向国际中文教育的公开 Beta 全栈教学协作平台

+

将教师认证、课程审核、预约课堂和作业反馈连接为同一条可部署、可验证、可审计的教学闭环。

-## 架构 +

+ 项目概览 + · + 教学闭环 + · + 快速开始 + · + 系统架构 + · + 质量证据 + · + 文档 + · + English +

+ +

+ + CI + + Status: public beta + 71 Node tests + 4 cross-role browser workflows + Vue 3 + Fastify 5 + PostgreSQL 15 + Cloudflare R2 and MinIO + Node.js 24 + pnpm 11 + Docker Compose +

+
+ +--- + + + +## 项目概览 + +International Chinese Platform 不是只展示页面的课程原型,而是一套围绕真实教学协作建模的全栈系统:学生、教师和管理员在同一份 PostgreSQL 业务数据上完成身份认证、教师认证、课程审核、预约、课堂、作业、通知与中文对话练习。 + +这个仓库同时包含 Vue 3 前端、Fastify 领域 API、编号迁移、私有对象存储适配、实时课堂协议、测试、部署配置与运行手册。推荐的公开 Beta 部署拓扑将前端放在 Vercel、版本化 API 放在 Railway,文件放在私有 Cloudflare R2,生产邮件经签名的 Vercel 中继投递。项目不会依赖某个版本不明的外部后端。 + +> [!IMPORTANT] +> 完整性契约:在当前公开 Beta 的边界内,任何人都可以从本仓库安装依赖、启动 PostgreSQL 与 MinIO、迁移和导入演示数据、体验三角色流程、运行测试,并沿着文档部署核心服务。完整不等于虚构商业 LMS 能力;支付、正式选课、课堂录制、多节点实时广播和高可用被明确列为后续范围。 + +### 为什么值得深入阅读 + +- 真正的业务闭环,而不是菜单堆砌:课程、预约、作业和教师认证都有服务端状态转换、权限约束、通知与审计记录。 +- 同一份事实数据:角色页面不是各自模拟;教师、学生和管理员都读写同一套 PostgreSQL 领域数据。 +- 安全边界被写进实现:会话不透明且只通过 HttpOnly Cookie 传递;写操作验证来源、角色与资源归属;文件先进入临时对象再校验并条件晋升。 +- 从本地到公开 Beta 的路径完整:开发环境使用 PostgreSQL + MinIO;生产架构使用 Vercel、Railway、PostgreSQL、R2 与受 HMAC 保护的邮件中继。 +- 诚实地表达限制:实时房间成员状态目前驻留单个 API 进程,因此公开 Beta 只运行一个后端实例;未用“高可用”或“无限扩展”掩盖这一事实。 + + + +## 四条可验证的教学闭环 + +每条旅程都应说明一次真实操作怎样跨越角色、状态与持久化边界。下图中的四条流程也是浏览器 E2E 实际验证的核心旅程。 ```mermaid -flowchart LR - Browser["Vue 3 browser client"] -->|"HTTPS page + /api/v1/*"| Vercel["Vercel · SPA + mail relay"] - Vercel -->|"/api/v1/* rewrite"| API["Railway · Fastify"] - Browser -->|"WSS"| API - Browser -->|"10-minute presigned PUT"| R2["Cloudflare R2 · private"] - API --> PG[(Railway PostgreSQL)] - API --> R2 - API -->|"HMAC HTTPS /api/mail-relay"| Vercel - Vercel -->|"SMTP 465"| Gmail["Gmail SMTP"] +flowchart TB + subgraph Verification["教师认证"] + V1["教师注册 / 修改资料"] -->|进入待核验队列| V2["管理员人工核验"] + V2 -->|批准| V3["已认证:公开展示、可被预约"] + V3 -->|撤销或资料再次变更| V1 + end + + subgraph Course["课程审核"] + C1["教师创建草稿"] -->|提交审核| C2["待审核"] + C2 -->|批准| C3["已发布:学生可发现"] + C2 -->|驳回并附理由| C4["已驳回"] + C4 -->|修改后重提| C2 + end + + subgraph Lesson["预约与课堂"] + L1["学生发起预约"] -->|教师接受 + 双方冲突检查| L2["已接受 + 专属课堂"] + L1 -->|教师拒绝 / 任一方取消| L3["关闭"] + L2 -->|时间窗内获取一次性票据| L4["聊天、在线状态、WebRTC 信令"] + L4 -->|参与者完成课堂| L5["课堂完成"] + end + + subgraph Assignment["作业与反馈"] + A1["教师编写并发布"] -->|学生保存草稿| A2["学生提交"] + A2 -->|教师评分与反馈| A3["学生查看结果"] + A1 -->|关闭作业| A4["禁止新的写入"] + end ``` -Beta 阶段后端保持单实例,因为实时房间成员状态保存在进程内。Vercel 只把 `/api/v1/*` 代理至 Railway,以维持第一方 Cookie;`/api/mail-relay` 留在 Vercel 执行 Gmail SMTP,课堂 WebSocket 则直接连接 Railway。 +| 旅程 | 用户能看到什么 | 服务端承担的关键约束 | +| ---------- | -------------------------------------------------- | ------------------------------------------------------------------- | +| 教师认证 | 教师资料被审核;被撤销后不再被学生发现或预约 | 管理员决定、状态冲突保护、审计与通知;资料变更自动重新待审 | +| 课程审核 | 草稿可被驳回、修改并再次提交;学生只看到已发布课程 | 教师所有权、审核状态机、发布可见性 | +| 预约与课堂 | 学生发起预约,教师接受后进入专属课堂 | 双方时间冲突检查、事务创建课堂、短期单用途 WebSocket 票据、房间隔离 | +| 作业与反馈 | 教师发布,学生提交,教师评分,学生看到结果 | 截止时间和关闭状态校验、提交所有权、评分结果持久化 | + +### 三个工作区 + +| 学生 | 教师 | 管理员 | +| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------ | +| 发现已认证教师和已发布课程;预约课堂;提交作业;练习持久化中文对话;查看通知与评分。 | 维护专业资料;提交课程审核;处理预约;参与课堂;发布、关闭和评分作业。 | 核验教师;审核课程;查看聚合指标、审计活动与平台级工作状态。 | + + -## 本地启动 +## 快速本地启动 -要求 Node.js 24、pnpm 11、Docker Desktop / Docker Engine。 +### 前置条件 + +- 推荐 Node.js 24;项目支持 Node.js 22 及以上版本 +- pnpm 11.9.0(由 packageManager 固定) +- Docker Desktop 或 Docker Engine,用于本地 PostgreSQL 与 MinIO +- 可选:Python 3.12 + Playwright Chromium,用于运行浏览器 E2E + +### 1. 克隆、安装并创建本地配置 ```bash +git clone https://github.com/computersciencefreshmen/International_Chinese_Platform.git +cd International_Chinese_Platform +corepack enable pnpm install --frozen-lockfile +``` + +Windows PowerShell: + +```powershell +Copy-Item .env.example .env.local +``` + +macOS / Linux: + +```bash +cp .env.example .env.local +``` + +.env.local 把开发 API、PostgreSQL 和 MinIO 连接放在同一份显式配置中;无需填写 SMTP、AI 或 TURN 才能体验核心教学流程。 + +### 2. 启动依赖、迁移数据并导入演示内容 + +首次拉取镜像时,请先等待 PostgreSQL 显示为 healthy,且 minio-init 成功完成,再执行迁移与种子命令。 + +```bash docker compose up -d postgres minio minio-init pnpm db:migrate pnpm db:seed pnpm dev ``` -- 前端:`http://localhost:5173` -- API:`http://localhost:7777/api/v1` -- MinIO 控制台:`http://localhost:9001` +打开以下地址: -配置模板见 [`.env.example`](./.env.example)。本地和 CI 也使用 PostgreSQL,避免 SQLite/生产差异。 +- Web:http://localhost:5173 +- API:http://localhost:7777/api/v1 +- MinIO Console:http://localhost:9001 -## 常用命令 +### 3. 验证启动成功 + +macOS / Linux: ```bash -pnpm dev # 前端与 API -pnpm build # Vite 生产构建 -pnpm db:migrate # 应用未执行的编号迁移 -pnpm db:seed # 仅本地/测试演示数据 -pnpm admin:bootstrap # 创建首个生产管理员 -pnpm test:api # PostgreSQL API 集成测试 -pnpm check # lint + format + API tests + build -pnpm backup:create # 加密 pg_dump 并上传备份 Bucket -pnpm backup:restore # 受 CONFIRM_RESTORE 保护的恢复 +curl --fail http://localhost:7777/api/v1/ready +``` + +Windows PowerShell: + +```powershell +Invoke-RestMethod http://localhost:7777/api/v1/ready ``` -生产配置会拒绝 `SEED_ON_START=true`,不会导入本地演示账号或课程。 +如果 API 就绪,访问 Web 地址并使用下列仅限本地演示的账号登录: -## 文件安全模型 +| 角色 | 邮箱 | 密码 | +| ------ | -------------------------------- | --------------------- | +| 学生 | student@example.com | Demo123! | +| 教师 | teacher@example.com | Demo123! | +| 管理员 | admin@example.com | Demo123! | -浏览器只能写入 `tmp/uploads/` 的短期 Key,并且签名绑定 Content-Type 与 Content-Length。完成接口流式检查大小、魔数和 SHA-256,并以源 ETag 为条件复制到新的 `files/` Key。正式 Key 从不签发 PUT URL,因此原上传 URL 即使被重复使用也不能覆盖已验证文件。 +> 教师认证是平台管理员的人工审核,不等同于外部证书颁发方的联网真实性核验;后者仍在当前范围之外。 -对象删除使用数据库 Outbox 重试;生产 R2 还应为 `tmp/` 配置 1 天生命周期兜底。私有视频和资料下载先经过 API 权限判断,再 302 到 5 分钟签名 URL。 +> [!CAUTION] +> 演示数据只能在本地或测试环境显式导入。生产配置拒绝 SEED_ON_START=truepnpm db:seedpnpm db:reset 也不应对生产数据库执行。 -## 健康检查 +### 常用命令 -- `GET /api/v1/health`:进程存活 -- `GET /api/v1/ready`:数据库不可用返回 503;R2 不可用返回 `degraded` +```bash +pnpm dev # 同时启动 Vite 与 Fastify +pnpm build # 构建生产前端 +pnpm db:migrate # 应用尚未执行的 PostgreSQL 编号迁移 +pnpm db:seed # 导入幂等演示数据(非生产) +pnpm db:reset # 重建本地开发数据库(会删除本地 public schema) +pnpm admin:bootstrap # 一次性创建首个生产管理员 +pnpm test:api # Node API / 数据库 / 安全测试 +pnpm check # ESLint + Prettier + API 测试 + 生产构建 +pnpm backup:create # 生产备份配置完成后,创建加密 PostgreSQL 备份 +pnpm backup:restore # 生产恢复配置完成后,受 CONFIRM_RESTORE 保护地恢复 +``` + + + +## 系统架构:职责清晰,信任边界明确 -## 云部署 +```mermaid +flowchart LR + Browser["浏览器 · Vue 3"] + Vercel["Vercel · SPA"] + API["Railway · 单实例 Fastify"] + PG[("Railway PostgreSQL")] + R2["Cloudflare R2 · 私有对象"] + Relay["Vercel Function · /api/mail-relay"] + Gmail["Gmail SMTP"] + Backup["Railway Cron · 加密备份"] + BackupBucket["私有 R2 备份 Bucket"] -- 前端:Vercel 项目 `international-chinese-platform` -- 后端:Railway Docker 单实例 -- 数据库:Railway PostgreSQL -- 文件:Cloudflare R2 私有 Bucket -- 邮件:Railway 通过 HMAC HTTPS 调用 Vercel `/api/mail-relay`;Gmail 应用密码只保存在 Vercel -- 备份:独立 Railway Cron 每日 `18:00 UTC` 运行;公开 Beta 暂时复用私有 staging Bucket + Browser -->|"HTTPS 页面"| Vercel + Vercel -->|"/api/v1/* rewrite"| API + Browser -->|"WSS 课堂连接"| API + Browser -->|"10 分钟预签名 PUT"| R2 + API --> PG + API --> R2 + API -->|"HMAC 签名 HTTPS 请求"| Relay + Relay -->|"SMTP 465"| Gmail + Backup -->|"pg_dump + AES-256-GCM"| BackupBucket +``` -完整 Secret、R2 CORS/生命周期、管理员初始化、备份、恢复和回滚步骤见[运维手册](./docs/operations.md)。架构决策见 [`docs/adr`](./docs/adr)。 +本地图中,Vercel / Railway / R2 分别由 Vite、Fastify、PostgreSQL 和 MinIO 替代;应用层使用同一套 S3 接口和数据库迁移,减少“开发环境能跑、生产环境行为不同”的风险。浏览器会先向 API 申请受限上传意图,再获得临时对象的预签名 PUT;API 在完成后校验大小、魔数和 SHA-256,并仅在 ETag 条件满足时晋升到正式私有 Key。 -## 验证 +| 组件 | 职责 | 为什么这样划分 | +| --------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------- | +| Vue 3 + Vercel | 用户界面、路由与静态资产 | 静态内容靠近用户;仅把版本化业务 API 重写到后端,浏览器仍以第一方 Cookie 访问 API。 | +| Fastify + Railway | 领域 API、权限判断、状态转换、WebSocket | 将教学规则留在服务端;公开 Beta 维持单实例,避免假装已解决跨实例房间协调。 | +| PostgreSQL | 用户、课程、预约、作业、审核、通知与审计的事实来源 | 编号迁移、事务与并发保护使跨角色流程不会依赖前端顺序。 | +| Cloudflare R2 / MinIO | 私有文件和上传临时对象 | 浏览器直传减少 API 传输压力;MinIO 在本地复现 S3 语义。 | +| Vercel 邮件中继 | 持有 Gmail SMTP 凭据并投递验证码 | Railway 只传递最小签名载荷;SMTP 密码不进入浏览器、API 环境或 Git。 | +| Railway Backup Cron | 导出并加密 PostgreSQL 备份 | 备份与 API 进程职责分离;恢复仍需显式确认。 | -CI 使用 PostgreSQL 服务和独立 MinIO,执行源码检查、API 集成测试、生产构建与学生/教师/管理员 Playwright 工作流。文件测试覆盖伪造扩展名、配额竞争、并发完成、签名 URL 重用、ETag 条件复制和越权下载。 + -## 公开 Beta 边界 +## 工程可信度与质量证据 -- 暂无 TURN;严格 NAT 环境可能无法建立音视频。 -- 暂无多实例房间广播、高可用、支付、正式入学、课堂录像和外部付费 AI。 -- 价格与容量当前是课程信息,不代表支付或正式席位占用。 +| 领域 | 实现方式 | 可验证证据 | +| ---------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| 身份与授权 | scrypt 密码哈希、服务端 Session 摘要、HttpOnly Cookie、来源校验、角色与资源所有权检查 | [认证测试](./server/test/auth.test.js)、[安全策略](./SECURITY.md) | +| 教学状态 | PostgreSQL 事务、条件更新、编号迁移、审计与通知 | [领域路由](./server/routes)、[迁移](./server/db/migrations) | +| 私有文件 | 上传意图、临时 Key、签名绑定类型与长度、魔数/SHA-256、ETag 条件晋升、短期签名下载 | [对象存储 ADR](./docs/adr/0004-r2-object-storage.md)、[文件测试](./server/test/files.test.js) | +| 实时课堂 | 短期、单用途课堂票据;聊天室成员、历史与 WebRTC 信令按课堂隔离 | [课堂测试](./server/test/realtime-classroom.test.js) | +| 邮件边界 | Railway 对 Vercel Function 的 HMAC 请求、时间窗与严格 payload 校验 | [邮件中继 ADR](./docs/adr/0006-vercel-hmac-mail-relay.md) | +| 发布与恢复 | 健康/就绪探针、扩展型迁移、加密备份、恢复确认与回滚步骤 | [运维手册](./docs/operations.md) | + +### CI 实际验证什么 + +pnpm check 会运行 ESLint、Prettier、完整 Node API/数据库/安全测试与 Vite 生产构建。本仓库当前包含 71 个 Node 测试;GitHub Actions 还会启动隔离 PostgreSQL 和 MinIO,构建生产应用,并在真实浏览器中验证四条跨角色流程: + +1. 课程“提交 → 驳回 → 修改重提 → 批准 → 学生目录可见”。 +2. 预约“学生申请 → 教师接受 → 专属课堂 → 学生完成课堂”。 +3. 作业“教师发布 → 学生提交 → 教师评分 → 学生查看反馈”。 +4. 教师认证“管理员撤销 → 学生无法发现/预约 → 管理员重新批准 → 重新公开”。 + +若要本地复现浏览器 E2E,请先安装 e2e/requirements.txt 与 Playwright Chromium,以与 [CI 工作流](./.github/workflows/ci.yml) 相同的 PostgreSQL、MinIO 和生产环境变量启动服务,再运行 python e2e/test_workflows.py。查看 [Node 测试](./server/test) 与 [浏览器 E2E](./e2e/test_workflows.py)。 + +## 部署、运维与适用范围 + +推荐的公开 Beta 生产拓扑为:Vercel 托管 Vue 单页应用与邮件中继函数;Railway 托管一个 Fastify API 实例、PostgreSQL 与独立备份 Cron;Cloudflare R2 保存私有文件和加密备份。完整的 Secret、R2 CORS、迁移、管理员初始化、监控、恢复演练和回滚步骤在 [运维手册](./docs/operations.md)。 + +| 现在适合 | 当前未覆盖 | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| 课程项目、作品集展示、小规模自部署教学协作、公开 Beta 验证 | 支付结算、正式入学与席位扣减、课堂录制、外部证书 OCR/真实性核验、付费 AI、多实例实时广播、高可用 | +| 可直连或已配置 TURN 的课堂环境 | 严格 NAT 下的可靠音视频(尚未内置 TURN) | + +价格和容量目前是课程信息,并不代表支付或正式席位占用。在正式选课模型上线前,已发布课程的作业对所有登录学生可见并可提交;当前没有 roster 或 enrollment 权限层。扩展为多实例前,需要先为课堂成员状态引入跨实例广播与协调层。 + + + +## 文档地图 + +- [公开 Beta 运维手册](./docs/operations.md):生产变量、R2、邮件中继、备份、恢复和回滚。 +- [ADR 0003:PostgreSQL](./docs/adr/0003-postgresql-production-database.md)、[ADR 0004:R2](./docs/adr/0004-r2-object-storage.md)、[ADR 0005:Vercel / Railway](./docs/adr/0005-vercel-railway-split-deployment.md)、[ADR 0006:HMAC 邮件中继](./docs/adr/0006-vercel-hmac-mail-relay.md):当前生产架构的关键决策。 +- [历史计划](./docs/plans):保留项目从 SQLite 原型迁移到 PostgreSQL/R2 云架构的演进记录;它们不是当前运行时配置的事实来源。 +- [安全策略](./SECURITY.md):私密披露方式、支持范围和部署安全要求。 ## 主要目录 ```text -src/ Vue 3 前端 +src/ Vue 3 前端与角色工作区 server/routes/ Fastify 领域 API server/db/migrations/ PostgreSQL 编号迁移 server/services/ 邮件、对话与对象存储适配器 server/ops/ 备份与恢复 -server/test/ API/数据库/安全集成测试 -e2e/ 跨角色 Playwright 工作流 +server/test/ API、数据库和安全集成测试 +e2e/ 四条跨角色 Playwright 工作流 docs/adr/ 架构决策记录 -docs/operations.md 云部署与运维手册 +docs/operations.md 部署与运维手册 ``` -安全问题请按 [`SECURITY.md`](./SECURITY.md) 私密报告,不要在公开 Issue 中披露凭据或漏洞细节。 +## 贡献、安全与许可证 + +欢迎以 Issue 或 Pull Request 参与改进。较大的行为变化请先说明用户旅程、服务端状态变化、测试方式与文档影响;每个 PR 应尽量让实现、测试与说明保持同步。 + +安全问题请按 [SECURITY.md](./SECURITY.md) 私密报告,不要在公开 Issue、截图或演示环境中泄露凭据、个人数据或漏洞细节。 + +本仓库当前未随附开源许可证。除适用法律明确允许的情形外,请在复用、再发布或用于生产前先取得维护者许可。 + +

回到顶部 ↑

diff --git a/SECURITY.md b/SECURITY.md index af0c3a7..f07ac23 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -22,7 +22,7 @@ Please allow time for a fix and coordinated disclosure. After remediation, the p - Never enable demo seeding in production and never bake administrator credentials into an image or Compose file. - Store `VERIFICATION_CODE_SECRET`, SMTP, AI, and TURN credentials outside version control; grant access only to deployment operators and rotate them after suspected exposure. - Keep secure, HttpOnly session cookies enabled, disable bearer authentication unless an audited non-browser client requires it, and configure the trusted proxy boundary deliberately. -- Back up both the SQLite database and uploaded files, encrypt backups, test restores, and restrict backup access. +- Back up PostgreSQL data, encrypt backup dumps, test restores, and restrict access to both the private object bucket and the backup bucket. - Rebuild frequently from the lockfile and review dependency/security alerts before deploying. Deployment hardening, backup, restore, migration, and secret-handling procedures are documented in [docs/operations.md](docs/operations.md). diff --git a/docs/adr/0001-self-contained-modular-monolith.md b/docs/adr/0001-self-contained-modular-monolith.md index b2cab6b..bd82f1b 100644 --- a/docs/adr/0001-self-contained-modular-monolith.md +++ b/docs/adr/0001-self-contained-modular-monolith.md @@ -1,7 +1,8 @@ # ADR-0001:采用自包含模块化单体 -**状态:** Accepted -**日期:** 2026-07-19 +**状态:** Superseded by ADR-0003、ADR-0004 和 ADR-0005 · **日期:** 2026-07-19 + +> **历史说明:** 这是最初的 SQLite、同进程部署决策。当前运行时使用 PostgreSQL、R2 兼容对象存储与 Vercel/Railway 分离部署;请以 ADR-0003 至 ADR-0006 为当前生产架构的事实来源。 ## Context diff --git a/docs/adr/0002-server-side-opaque-sessions.md b/docs/adr/0002-server-side-opaque-sessions.md index 97f087c..93b8044 100644 --- a/docs/adr/0002-server-side-opaque-sessions.md +++ b/docs/adr/0002-server-side-opaque-sessions.md @@ -1,7 +1,8 @@ # ADR-0002:使用服务端 opaque session -**状态:** Accepted -**日期:** 2026-07-19 +**状态:** Partially superseded by ADR-0003 · **日期:** 2026-07-19 + +> **历史说明:** 不透明服务端 Session 的安全模型仍然有效;当前实现把 Session 摘要保存在 PostgreSQL,而不是本文记录的 SQLite。数据存储事实以 ADR-0003 为准。 ## Context diff --git a/docs/adr/0005-vercel-railway-split-deployment.md b/docs/adr/0005-vercel-railway-split-deployment.md index 787c492..6ef5e76 100644 --- a/docs/adr/0005-vercel-railway-split-deployment.md +++ b/docs/adr/0005-vercel-railway-split-deployment.md @@ -5,7 +5,7 @@ ## Decision -Serve the Vue SPA from Vercel and the single-instance Fastify API from Railway. Vercel externally rewrites `/api/:path*` to Railway, while classroom WebSockets connect directly to the Railway WSS origin returned in the one-time ticket response. +Serve the Vue SPA from Vercel and the single-instance Fastify API from Railway. Vercel externally rewrites `/api/v1/:path*` to Railway, while classroom WebSockets connect directly to the Railway WSS origin returned in the one-time ticket response. ## Consequences diff --git a/docs/operations.md b/docs/operations.md index 6425a37..35060e4 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -1,6 +1,6 @@ -# 公开 Beta 部署与运维手册 +# 公开 Beta 参考部署与运维手册 -本文档对应当前线上架构:Vercel 托管 Vue 前端和邮件中继函数,Railway 运行单实例 Fastify 与独立备份 Cron 服务,Railway PostgreSQL 保存业务数据,Cloudflare R2 私有 Bucket 保存文件与加密备份。 +本文档定义推荐的公开 Beta 部署拓扑:Vercel 托管 Vue 前端和邮件中继函数,Railway 运行单实例 Fastify 与独立备份 Cron 服务,Railway PostgreSQL 保存业务数据,Cloudflare R2 私有 Bucket 保存文件与加密备份。部署前应在自己的 Vercel/Railway/R2 账号中完成域名、Secret 与健康检查验收。 ```text Browser ── HTTPS ──> Vercel ── /api/v1/* rewrite ──> Railway Fastify ──> PostgreSQL diff --git a/docs/plans/2026-07-24-readme-information-architecture.md b/docs/plans/2026-07-24-readme-information-architecture.md new file mode 100644 index 0000000..3e4ebd6 --- /dev/null +++ b/docs/plans/2026-07-24-readme-information-architecture.md @@ -0,0 +1,38 @@ +# README information architecture overhaul + +- Date: 2026-07-24 +- Status: Implemented + +## Goal + +Rebuild the repository README as a first-class product and engineering entry point. A new reader should understand the platform in 30 seconds, run a verified local workflow in five minutes, and trace major architectural and security decisions within fifteen minutes. + +## Decision + +Use the visual hierarchy from the earlier portfolio-oriented README—brand hero, navigation, concise quality signals, workflow diagrams, and a completeness contract—while treating the current PostgreSQL, R2/MinIO, Vercel/Railway, and HMAC mail-relay implementation as the only runtime source of truth. + +The README remains an entry point rather than an operations manual. Detailed secrets, recovery, R2 CORS, and rollout steps stay in docs/operations.md. Historical SQLite-era plans and ADRs remain available as project history but are explicitly distinguished from current production decisions. + +## Reader flow + +1. Hero: project purpose, public-beta status, CI, and technology signals. +2. Value: why persistent cross-role teaching workflows matter. +3. Evidence: four server-enforced journeys and three role workspaces. +4. Action: reproducible local quick start, readiness probe, and seeded demo accounts. +5. Understanding: deployment topology, component responsibilities, and trust boundaries. +6. Verification: Node tests, browser E2E, CI services, documentation, security, scope, and licensing. + +## Facts protected by the rewrite + +- Production API traffic uses only the /api/v1/* Vercel rewrite to Railway. +- Railway sends a signed minimal payload to Vercel /api/mail-relay; Gmail credentials stay in Vercel. +- PostgreSQL is the current database and R2/MinIO is the object-storage contract. +- The public beta runs one API instance because classroom presence is currently process-local. +- The test suite contains 71 Node tests and four cross-role browser workflows at the time of this rewrite. + +## Acceptance criteria + +- Chinese and English README files tell the same architecture story. +- Every major claim links to code, a test, or a current operations/ADR document. +- A reader can reach a ready API and seeded role account without hidden setup assumptions. +- Limitations are explicit rather than disguised as production capability.