Files
pwa/backend/README.md
ar.badieiandCursor 8c37c15697 Fix minimum age 12 and ensure assignment catalog seeds on startup.
Persian age validation messages, clearer homework entry points, and seed assignment definitions so «تکالیف من» is not empty after deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 07:24:40 +03:30

8.3 KiB
Raw Permalink Blame History

Novya Backend

Independent HTTP API for the Novya learning PWA. Authenticated progress, PCQ-24 assessments, course sessions, chat, and AI coach run through this service; MySQL is the source of truth.

Tech Stack

  • Runtime: Node.js 20+ + TypeScript (ESM)
  • HTTP: Hono
  • Database: MySQL 8 via Prisma (backend/prisma/schema.prisma)
  • Validation: Zod
  • Auth: Argon2 passwords, opaque Bearer sessions (SHA-256 hashed at rest)
  • Logging: Pino
  • AI: OpenAI-compatible client (default GapGPT via OPENAI_BASE_URL)

Prerequisites

  • Node.js 20+
  • npm
  • MySQL 8+ (local or docker compose -f docker-compose.dev.yml up -d)

Getting Started

cd backend
npm install
cp .env.example .env
# docker compose -f docker-compose.dev.yml up -d
npm run prisma:migrate:dev
npm run prisma:seed
npm run dev

API default: http://localhost:3001
PWA (CORS): http://localhost:5173

prisma:seed upserts the 11 course sessions and 9 assignment definitions. Server startup also upserts both as a safety net.

Scripts

Script Description
npm run dev Dev server with hot reload
npm run build Compile TypeScript → dist/
npm run start Run compiled server
npm run test Domain + e2e API tests
npm run lint ESLint
npm run typecheck tsc --noEmit
npm run prisma:generate Generate Prisma Client
npm run prisma:validate Validate schema
npm run prisma:migrate:dev Dev migrations
npm run prisma:migrate:deploy Apply migrations (prod)
npm run prisma:seed Upsert 11 course_sessions + 9 assignment definitions

Project Structure

backend/
├── prisma/
│   ├── schema.prisma          # Active schema (identity + learning domain)
│   ├── seed.ts                # Catalog seed (11 sessions)
│   └── migrations/
├── src/
│   ├── app.ts / server.ts     # App factory + listen / shutdown
│   ├── config/                # Env (Zod)
│   ├── lib/                   # prisma, logger, response, clientIp
│   ├── middleware/            # requestId, CORS, rate-limit, auth, errors
│   ├── routes/                # health + module mounts
│   ├── modules/
│   │   ├── auth/              # register, login, guest, me, logout
│   │   ├── users/             # profile name/age
│   │   ├── journey/           # learning step + permissions
│   │   ├── assessments/       # PCQ-24 attempts + scoring
│   │   ├── sessions/          # catalog, progress, complete
│   │   ├── chat/              # chat sessions + messages
│   │   └── ai/                # config, runs, coach prompt/client
│   └── shared/                # errors, types, validation, constants
├── tests/
├── docker-compose.dev.yml
└── .env.example

Root prisma/schema.prisma is a legacy design artifact — see ../prisma/README.md. Use only backend/prisma/.

Environment Variables

Variable Required Default Description
NODE_ENV No development development, test, production
PORT No 3001 HTTP port
HOST No 0.0.0.0 Bind host
API_VERSION No 1.0.0 Returned by /version
BUILD_TIMESTAMP No — Optional build stamp for /version
DATABASE_URL Yes — mysql://... (use novya; keep novya_test for tests only)
LOG_LEVEL No info Pino level
CORS_ORIGIN No http://localhost:5173 Comma-separated origins
TRUST_PROXY No false Proxy-aware client IP
RATE_LIMIT_WINDOW_MS No 60000 Rate-limit window
RATE_LIMIT_MAX No 100 Max requests / window / IP
AUTH_RATE_LIMIT_MAX No 20 Cap for register/login/guest
AUTH_SESSION_TTL_HOURS No 168 Opaque session TTL (7 days); no refresh tokens
OPENAI_API_KEY For AI "" Backend-only; empty → AI routes return 503
OPENAI_BASE_URL No https://api.gapgpt.app/v1 OpenAI-compatible base URL
OPENAI_MODEL No gpt-4o-mini Model id
OPENAI_ORG_ID No — Optional OpenAI org header
OPENAI_PROJECT_ID No — Optional OpenAI project header

Do not put VITE_* secrets in the backend.

Auth

  • Bearer token from register/login/guest → header Authorization: Bearer <token>
  • Token stored hashed in auth_sessions; revoke on logout or expiry
  • Profile age must be at least 12 (no upper age limit)
  • One PCQ attempt per user per phase (UNIQUE(userId, phase))

Learning journey (currentStep)

Timestamps are authoritative. Derived steps:

registered → pre_test_done / training_in_progress → training_completed → completed

Permissions on GET /journey: canTakePreTest, canOpenTraining, canTakePostTest, isCompleted.

canTakePostTest is true after pre-test when either training is fully completed or the learner has completed sessions 1–10 (reached Session 11), and the post-test is not yet submitted.

API Endpoints

Health

Method Path Auth Description
GET /health No Liveness
GET /ready No Readiness (MySQL)
GET /version No Version info

Auth & users

Method Path Auth Description
POST /auth/register No Email/password (min 8)
POST /auth/login No Login
POST /auth/guest No Guest user + session
GET /auth/me Yes Current user
POST /auth/logout Yes Revoke session
GET /users/me Yes User + profile
PATCH /users/me/profile Yes Name + age

Journey & assessments

Method Path Auth Description
GET /journey Yes Step + permissions
GET /assessments/scales/pcq24 Yes Scale metadata
POST /assessments/attempts Yes Submit pre/post
GET /assessments/attempts Yes History
GET /assessments/attempts/latest?phase= Yes Latest by phase
GET /assessments/summary Yes Pre/post + journey

Sessions (catalog)

Method Path Auth Description
GET /sessions Yes Catalog + unlock/complete flags
GET /sessions/progress Yes Progress summary
GET /sessions/:order Yes One session
POST /sessions/:order/complete Yes Mark complete (idempotent)

Chat & AI

Method Path Auth Description
GET /chat/sessions Yes List chats (?sessionOrder= optional)
POST /chat/sessions Yes Create/reuse chat for a session order
GET /chat/sessions/:id/messages Yes Message history
POST /chat/sessions/:id/messages Yes User message → coach reply
POST /chat/sessions/:id/start Yes Opening coach turn
GET /ai/config No Public AI config (no secrets)
GET /ai/runs Yes User’s AI run log

Response envelope:

{ "success": true|false, "data"|"error": ..., "meta": { "requestId", "timestamp" } }

Schema enums (MVP usage)

Enum Values Runtime usage
AttemptStatus submitted, scored Always scored. submitted reserved for future drafts.
AiRunStatus queued, running, succeeded, failed Create as queued → running → terminal
SessionProgressStatus not_started, in_progress, completed Chat opens as in_progress; not_started is default only
ChatRole system, user, assistant Only user/assistant persisted; system is prompt-only

Catalog integrity

session_progress.sessionOrder and chat_sessions.sessionOrder FK → course_sessions.order (ON DELETE RESTRICT). Seed the catalog before creating progress/chat rows (npm run prisma:seed).

Deferred (not in this API)

OAuth, phone OTP, guest→account merge, refresh tokens, versioned PCQ tables in DB, production-grade distributed rate limiting.