4.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
LA2 Eternal Portal is a web portal for a Lineage 2 private server. It consists of an Express API and a React SPA that manage website user accounts, link them to existing game server accounts, and display character data.
Architecture
This is a monorepo with two packages:
api/— Express.js API server (TypeScript, Node 18+)react-client/— React SPA (Vite, TypeScript)
Dual Database Architecture
The API talks to two completely different databases:
- Portal Database (SQLite) — Stores website users, game account links, and cached character metadata. Accessed via Prisma ORM (
api/prisma/schema.prisma). File path controlled byDATABASE_URLenv var. This is the core database and is available in all environments. - Game Server Databases (MSSQL) — External production-only database containing live Lineage 2 game server data. Not available in local development. Accessed via raw SQL through the
mssqldriver (api/src/services/gameServerService.ts) only when env vars are configured. Two connections:lin2db— account and character tableslin2world— builder_account table
Game server connection config lives in api/src/config/gameServer.ts and is read from env vars (GAME_SERVER_*, GAME_WORLD_*). Code that touches MSSQL should gracefully handle connection failures because the game server is not reachable outside production.
Auth Flow
- Portal uses JWT (8h expiry). Token is generated on login, stored in
localStorage, and sent viaAuthorization: Bearerheader. - The API has two middleware functions in
api/src/middleware/auth.ts:protectRoute(validates JWT) andadminOnly(checksrole === 'ADMIN'). - React auth state is managed by Zustand (
react-client/src/hooks/useAuth.ts).
API Routing Structure
| Route | File | Notes |
|---|---|---|
/api/auth |
api/src/routes/auth.routes.ts |
login, register, me, link game account |
/api/users |
api/src/routes/user.routes.ts |
user/character CRUD (Prisma/SQLite) |
/api/admin |
api/src/routes/admin.routes.ts |
admin-only user management |
/api/characters |
api/src/routes/characters.routes.ts |
character routes (mostly stub) |
Business logic for game server DB operations is in api/src/services/gameServerService.ts. Business logic for portal DB operations is split between api/src/controllers/auth.controller.ts and api/src/controllers/user.controller.ts.
Commands
All commands are run from the respective package directory (api/ or react-client/).
API (cd api/)
npm run dev # Start dev server with tsx watch (port 3001)
npm run build # Compile TypeScript to ./dist
npm run start # Run compiled JS (production)
npm run migrate # Run Prisma migrations
React Client (cd react-client/)
npm run dev # Start Vite dev server (port 5173)
npm run build # Type-check and build for production
npm run preview # Preview production build locally
Note: npm run lint is defined in react-client/package.json but ESLint is not actually installed/configured in the current lockfile.
Docker (from repo root)
docker compose build # Build API and frontend images
docker compose up -d # Start both services
docker compose logs -f api # Tail API logs
docker compose logs -f react # Tail frontend logs
The API container runs tsx watch in development. The React container is a multi-stage build that compiles the SPA and serves it via nginx.
Important Files
api/prisma/schema.prisma— Portal DB schema (SQLite). Models:WebsiteAccount,GameAccount,GameCharacter.api/src/config/gameServer.ts— MSSQL connection config and server metadata.react-client/vite.config.ts— Vite config with path alias@/pointing tosrc/and a dev proxy for/apitolocalhost:3001..env.example— Required environment variables for local development.
Code Patterns
- Controllers are inconsistent in style: some export plain async functions (
auth.controller.ts), others export a controller object (user.controller.ts). Prefer plain async functions for new code. - MSSQL queries use template strings with parameterized inputs via
.input(). Never interpolate user values directly into query strings. - Passwords for the game server are stored as plain binary (
Buffer.from(password)) in theuser_authtable, not hashed. - The
gameServerService.tsconnection pools are lazily initialized module-level singletons. They reconnect automatically if the connection drops.