diff --git a/CLAUDE.md b/CLAUDE.md index 34184fa..15eaa09 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,16 +3,17 @@ ## Quick Start ```bash -# Backend (from backend/) -python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload +# Backend (from backend/, venv must be active) +uvicorn main:app --host 0.0.0.0 --port 8000 --reload # Frontend (from frontend/) -npx vite --host +npm run dev ``` - Backend API: http://localhost:8000 (docs: http://localhost:8000/docs) -- Frontend: http://localhost:5173 -- Default admin: `admin` / (random, printed at first startup; reset via `python -c "from core.auth.utils import hash_password; print(hash_password('admin123'))"` then update SQLite) +- Frontend: http://localhost:5173 (Vite proxies /api and /ws to :8000) +- Default admin: `admin` / (random, printed at first startup) +- Full setup instructions (secrets, venv, debug configs): see README.md ## Architecture @@ -94,29 +95,13 @@ cd frontend && npx tsc --noEmit - **Arma 3 per-mission params**: `ServerConfig.missions` is now `list[MissionRotationItem]` (adds optional `params: dict`). A new `default_mission_params` field holds server-wide defaults. Config version bumped to `"1.1.0"`. `_render_server_cfg()` now emits a `class Missions { ... }` block when the rotation is non-empty; `class Params` inside each mission uses per-mission params → global defaults → omit (in that priority order). The `MissionRotationEntry.params` is edited per-row in the Missions tab via `MissionParamsEditor`; `default_mission_params` is edited in the Config tab via the `key-value` widget. - **Config version migration**: `migrate_config("1.0.0", ...)` backfills `params: {}` on each existing rotation entry and adds `default_mission_params: {}`. `normalize_section()` does the same on reads for stored rows that pre-date the migration run. -## Known Bugs — Mods Tab (fix next session) +## Mods Tab — Implementation Notes -Three bugs prevent the Mods tab from working correctly: - -### Bug 1 — Save fails with TypeError (critical) -`Arma3ModManager.set_enabled_mods()` calls `config_repo.upsert_section()` with wrong keyword argument names: -- Passes `data=` → should be `config_data=` -- Passes `expected_version=` → should be `expected_config_version=` -- Missing required `game_type=` argument -- Missing required `schema_version=` argument - -**File:** `backend/adapters/arma3/mod_manager.py`, `set_enabled_mods()` method (~line 127) - -### Bug 2 — Mods not applied on server start (critical) -`service.py` `start_server()` reads mods from a `server_mods` JOIN `mods` table (SQLAlchemy query, ~line 246) — but those tables are never populated by the Mods tab UI. The correct source is `config_repo.get_section(server_id, "mods")["enabled_mods"]`. The start flow needs to read from `config_repo` instead of the dead `server_mods` table join. - -**File:** `backend/core/servers/service.py`, `start_server()` method (~line 242–255) - -### Bug 3 — Wrong mod folder location (UX) -`list_available_mods()` scans the server root (`get_server_dir()`) for `@*` folders. Mods should live in a `mods/` subfolder: `{server_dir}/mods/@ModName`. Needs: -1. Change scan path: `server_dir / "mods"` instead of `server_dir` -2. Ensure the `mods/` subdirectory is created by `ensure_server_dirs` (add `"mods"` to the Arma3 `get_server_dir_layout()`) -3. Update CLAUDE.md + user docs to say mods go in `D:/ImContainer/Arma3Server/{id}/mods/@ModName` - -**File:** `backend/adapters/arma3/mod_manager.py`, `list_available_mods()` and `_server_dir()` (~line 47–88) -Also: `backend/adapters/arma3/adapter.py`, `get_server_dir_layout()` (add `"mods"` entry) \ No newline at end of file +- Mods go in `{server_data_dir}/{server_id}/mods/@ModName` (e.g. `D:/ImContainer/Arma3Server/1/mods/@CBA_A3/`) +- Enabled mods config schema: `{"enabled_mods": [{"name": "@CBA_A3", "is_server_mod": false}]}` + - Old string-list format is auto-migrated to the dict format on read + - `is_server_mod: true` → `-serverMod=` arg; `false` → `-mod=` arg +- `list_available_mods()` scans `{server_dir}/mods/` for `@*` directories +- `set_enabled_mods()` stores the new dict format; validates names against disk +- Server start reads mods from `game_configs` via `config_repo`, NOT from the dead `server_mods` table +- Directory scaffold: all 4 Arma3 subdirs (`server/`, `battleye/`, `mpmissions/`, `mods/`) are created on server create and backfilled on startup; each gets a `README.txt` if not already present \ No newline at end of file diff --git a/README.md b/README.md index 5ec440b..d7c0943 100644 --- a/README.md +++ b/README.md @@ -19,25 +19,78 @@ A multi-game server management platform with a Python/FastAPI backend and React/ - **TanStack Query v5** — server state management - **Zustand 5** — client state (auth, UI) - **Tailwind CSS** — dark neumorphic design system -- **Playwright** — E2E testing (38 tests) -- **Vitest** + **React Testing Library** — unit tests (167 tests) +- **Playwright** — E2E testing +- **Vitest** + **React Testing Library** — unit tests (173 tests) ## Quick Start -### Backend +### 1 — Backend setup ```bash cd backend + +# Create and activate a virtual environment python -m venv venv -source venv/bin/activate # Windows: venv\Scripts\activate +source venv/bin/activate # macOS / Linux +# venv\Scripts\activate # Windows (cmd) +# venv\Scripts\Activate.ps1 # Windows (PowerShell) + pip install -r requirements.txt -cp .env.example .env # Edit with your settings -uvicorn main:app --reload ``` -First run prints a generated admin password. Change it immediately via `PUT /api/auth/password`. +**Generate required secrets** (one-time): -### Frontend +```bash +# Secret key (JWT signing) +openssl rand -hex 32 + +# Fernet encryption key (sensitive config fields at rest) +python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" +``` + +Copy `.env.example` to `.env` and fill in the two keys: + +```bash +cp .env.example .env # then open .env in your editor +``` + +```ini +# .env — minimum required values +LANGUARD_SECRET_KEY= +LANGUARD_ENCRYPTION_KEY= +LANGUARD_ARMA3_DEFAULT_EXE=C:/path/to/arma3server_x64.exe +``` + +**Start the backend** (development — auto-reload on file changes): + +```bash +uvicorn main:app --host 0.0.0.0 --port 8000 --reload +``` + +First run prints a randomly-generated admin password to the console. Log in and change it immediately via Settings → Change Password (or `PUT /api/auth/password`). + +- API root: `http://localhost:8000` +- Interactive docs: `http://localhost:8000/docs` + +**Debug in VS Code:** add this `launch.json` configuration: + +```json +{ + "name": "Backend — uvicorn", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": ["main:app", "--host", "0.0.0.0", "--port", "8000"], + "cwd": "${workspaceFolder}/backend", + "env": { "PYTHONDONTWRITEBYTECODE": "1" }, + "jinja": true, + "justMyCode": false +} +``` + +--- + +### 2 — Frontend setup ```bash cd frontend @@ -45,28 +98,64 @@ npm install npm run dev ``` -Opens at `http://localhost:5173`. The dev server proxies `/api` to the backend on port 8000. +The Vite dev server starts at `http://localhost:5173` and automatically proxies: +- `/api/*` → `http://localhost:8000` (REST) +- `/ws/*` → `ws://localhost:8000` (WebSocket) + +**Debug in VS Code:** install the [JavaScript Debugger](https://marketplace.visualstudio.com/items?itemName=ms-vscode.js-debug) extension (bundled by default), then add: + +```json +{ + "name": "Frontend — Vite (Chrome)", + "type": "chrome", + "request": "launch", + "url": "http://localhost:5173", + "webRoot": "${workspaceFolder}/frontend/src", + "sourceMapPathOverrides": { + "/@fs/*": "${workspaceFolder}/frontend/*" + } +} +``` + +Start the Vite dev server first (`npm run dev`), then launch this config to attach Chrome DevTools with source-map support. + +--- ## Running Tests -### Frontend Unit Tests +### Backend + +```bash +cd backend +source venv/bin/activate # (if not already active) + +pytest # all tests +pytest tests/adapters/arma3/ -v # adapter tests only +pytest --tb=short -q # quiet output +``` + +### Frontend unit tests ```bash cd frontend -npm test # Watch mode -npx vitest run # Single run -npx vitest run --coverage # With coverage +npm test # single run (CI-friendly) +npm run test:watch # watch mode during development +npx vitest run --coverage # with coverage report ``` -### Frontend E2E Tests +### Frontend E2E tests (Playwright) + +Start the backend and the Vite dev server first, then: ```bash cd frontend -# Start backend + frontend dev server first -npx playwright test # All tests (mocked + integration) -npx playwright tests-e2e/integration/ # Full-stack integration tests only +npm run test:e2e # all E2E tests (headless) +npm run test:e2e:ui # Playwright UI mode (interactive, great for debugging) +npx playwright test --headed # watch tests run in an actual browser ``` +Integration tests (require a live backend) live in `tests-e2e/integration/`. All other tests use API mocks and run without a backend. + ## Project Structure ``` @@ -101,7 +190,7 @@ languard-servers-manager/ │ │ ├── hooks/ # useServers, useServerDetail, useAuth, useGames, useWebSocket │ │ ├── store/ # auth.store, ui.store (Zustand) │ │ ├── lib/ # api.ts (Axios client) -│ │ └── __tests__/ # Vitest unit tests (~120 tests) +│ │ └── __tests__/ # Vitest unit tests (173 tests) │ ├── tests-e2e/ # Playwright E2E tests │ └── playwright.config.ts ├── API.md # REST + WebSocket API reference