Files
ohmj2/AGENTS.md
T

237 lines
8.1 KiB
Markdown

# AGENTS.md - Development Guidelines
Essential information for AI agents and developers working on this codebase.
## Project Overview
Web application for "Harmonie de Montpellier-Jacou" (music band) to manage and share sheet music (partitions).
- **Frontend**: SvelteKit (Svelte 5, runes) + Tailwind CSS in `partitions/`
- **Backend**: PHP 8.5 REST API in `api/` (no framework)
- **Storage**: flat files under `legacy/Scores/` (no database)
- **Auth**: JWT (HS256), see `api/lib/Auth.php`
- **Production**: `https://partitions.c.nadal-fr.com` (front) + `https://ohmj-api.c.nadal-fr.com` (API)
There is **no MySQL** and **no Vue.js** in the active codebase. The old Vue.js 2 / MySQL
site is archived in `legacy/` and must not be modified.
## Stack & Versions
- Node.js 26 / npm 11
- Svelte 5, SvelteKit 2, `@sveltejs/adapter-static`, Vite 5, Tailwind 3
- PHP 8.5 (CLI built-in server in development)
- Dependencies: `axios`, `pdfjs-dist`, `lucide-svelte` (front); `setasign/fpdi`, `setasign/fpdf`, `tecnickcom/tcpdf` (back)
> PHP 8.5 note: nullable parameters MUST use explicit `?type` syntax
> (e.g. `?string $x = null`). Implicit nullable is deprecated and pollutes API output.
## Build & Run Commands
### Backend (PHP)
```bash
cd api
php -c php-upload.ini -S localhost:8000 router.php
```
- `-c php-upload.ini` is **required** so PDF uploads work (`upload_max_filesize`/`post_max_size` = 64M).
`ini_set()` cannot change those directives at runtime.
- API base URL: `http://localhost:8000` (paths have **no** `/api` prefix in the client).
- Copy `api/.env.example` to `api/.env` and set a strong `JWT_SECRET`.
### Frontend (SvelteKit)
```bash
cd partitions
npm install
npm run dev # http://localhost:5173
npm run build # outputs to partitions/build
npm run preview
```
- `frontend` is a symlink to `partitions/build` (the deployed static site).
- No ESLint / test framework is configured for the frontend.
- `VITE_API_URL` is defined but currently unused (base URL is chosen from `import.meta.env.DEV`).
### Deploy
```bash
./deploy/deploy.sh
```
- Do NOT include `legacy/` or `api/tests.php` in the deploy archive.
- `api/.env` (with `JWT_SECRET`) is part of the deployment.
- The frontend build must be present in `frontend/`.
## Testing
### PHP API tests
```bash
cd api
php -c php-upload.ini -S localhost:8000 router.php & # if not already running
rm -f /tmp/rate_* 2>/dev/null # clear rate limiting first
php tests.php
```
- Expected: **53/53 tests passed (100%)**.
- Covers: auth (login, bad credentials, missing/invalid token), scores CRUD,
create-score-with-pieces, files tree, path traversal, rate limiting, upload
validation, INI injection, security headers.
- `tests.php` is kept for development only and MUST NOT be shipped in the deploy zip.
## Authentication
- Login: `POST /login` with `{"username":"admin","password":"password"}` (development).
- Token: JWT HS256 signed with `JWT_SECRET`, 1 hour expiry.
- API calls: send `Authorization: Bearer <token>`.
- Token is stored in `localStorage` on the frontend and attached via an axios request interceptor.
- Roles: `admin` (full access) and `user` (read-only). Admin routes live under `/admin/...`.
- Frontend dev login page: http://localhost:5173
## API Endpoints
Public / all authenticated users:
- `POST /login`
- `GET /scores`
- `GET /scores/:id`
- `GET /scores/:id/instruments[?piece=N]`
- `GET /pieces/:scoreId`
- `GET /download/:path` (token via `Authorization` header or `?token=`)
Admin only (`role === 'admin'`):
- `POST /admin/scores`
- `PUT /admin/scores/:id`
- `PUT /admin/scores/:id/pieces`
- `DELETE /admin/scores/:id`
- `POST /admin/scores/:id/upload`
- `GET /admin/scores/:id/files`
- `DELETE /admin/scores/:id/files?path=...`
## File Organization
```
/
├── api/ # PHP REST API (active)
│ ├── index.php # Router / entry point
│ ├── router.php # Dev server router (+ CORS, .env loading)
│ ├── lib/Auth.php # JWT auth
│ ├── lib/ScoreScanner.php# File-based score index
│ ├── config/users.json # Users (bcrypt hashes)
│ ├── tests.php # Functional tests (dev only)
│ └── .env # JWT_SECRET, SCORES_PATH
├── partitions/ # SvelteKit app (active)
│ ├── src/lib/api.ts # Axios client + apiService
│ ├── src/lib/stores/ # auth store
│ ├── src/lib/components/ # PdfViewer
│ ├── src/routes/ # pages (login, scores, admin)
│ └── build/ # static output (gitignored)
├── frontend -> partitions/build # symlink used for deployment
├── deploy/deploy.sh # deploy archive builder
├── scripts/convert_final_v2.js # one-off filename converter
├── legacy/ # archived old site + Scores/ (do not modify)
└── _builds/ # temporary work files (gitignored)
```
## Data Model (files)
```
legacy/Scores/<NUM>/<PIECE>/<INSTRUMENT>/<VERSION>/<PARTIE>.pdf
legacy/Scores/<NUM>/score.ini
```
`score.ini`:
```ini
[info]
name = Nom du morceau
compositor = Compositeur
ressource = https://... # optional external link
[pieces]
count = 1
```
- `NUM` = score id (zero-padded, e.g. `102`).
- `PIECE` = movement/piece number (1 for single-piece scores).
- `INSTRUMENT` = trigram code (see below).
- `VERSION` = version number.
- `PARTIE` = part number.
Instrument codes: `dir`, `pic`, `flu`, `cla`, `clb`, `sax`, `sat`, `sab`, `coa`,
`cba`, `cor`, `trp`, `trb`, `tub`, `htb`, `bas`, `per`, `crn`, `eup`, `har`, `pia`,
plus `sup`/`par` for combined parts.
## Code Style
### PHP
- `<?php` opening tags, PascalCase classes, camelCase methods.
- 3-space indentation, opening brace on the same line (legacy style).
- Consistent with surrounding code; prefer `include_once`/`require_once` for includes.
- No database layer: file operations via `ScoreScanner.php`.
### JavaScript / Svelte
- Svelte 5 runes (`$state`, `$props`, `$derived`, `$effect`); prefer `$app/state` over the deprecated `$app/stores`.
- TypeScript, camelCase variables, PascalCase components, 2-space indent, semicolons.
### General
- UTF-8, French for user-facing content.
- Do not add comments unless necessary.
- **NEVER delete files or directories without asking first.**
## Design Guidelines
- Clean, sober, modern.
- Use SVG icons instead of emojis.
- Integrate related actions (view + download) in a single row with consistent styling.
- Hide redundant labels ("Piece 1", "Version 1") when there is only one.
- Use spacing, subtle borders, and hover effects for visual hierarchy.
## Security Checklist (before deployment)
- [ ] `JWT_SECRET` is strong and unique (`openssl rand -base64 32`); never commit `api/.env`.
- [ ] `api/config/users.json` uses non-default credentials (change `admin`/`user`).
- [ ] CORS origins restricted to the real domains (no wildcard fallback).
- [ ] HTTPS enforced in production.
- [ ] Rate limiting active (clear `/tmp/rate_*` only in tests, never in prod).
- [ ] `php tests.php` passes 53/53.
- [ ] `npm audit` reviewed.
- [ ] No secrets in code or git history.
Manual checks:
```bash
# JWT login
curl -s http://localhost:8000/login -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
# Directory traversal must NOT return 200
curl -s http://localhost:8000/download/../../../etc/passwd \
-H "Authorization: Bearer <token>"
# Security headers on any response
curl -s -I http://localhost:8000/login -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
```
> Known security findings from the latest review (not yet fixed): static file
> passthrough in `api/router.php` exposes `.env`/`users.json`; `deleteScoreFile`
> lacks path containment; `api/.env` is tracked by git; JWT is standard base64
> (not base64url) and expiry is optional; stored XSS via the `ressource` link.
> See the project issue tracker / recent review notes.
## Temporary Files
- Put temporary scripts and outputs in `_builds/` (gitignored).
- `scripts/convert_final_v2.js` is the only committed script in `scripts/`.
## Git
- Default branch: `master`.
- No CI/CD configured.
- Only commit when explicitly requested.