Files
ohmj2/AGENTS.md
T

8.1 KiB

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)

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)

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

./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

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:

[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:

# 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.