# Development Guide Setup, workflow, building assets, and testing for XAMXAM development. --- ## Requirements - **PHP** ≥ 8.4 with `ext-json`, `ext-openssl`, `ext-pdo` (and `ext-sqlite3` for the local DB) - **Composer** for PHP dependencies - **Node.js / npm** for frontend asset builds (rolldown, lightningcss, biome) - **SQLite3** CLI (for `just query`, `just init-db`) ## One-time setup From the repo root, run the provisioning script: ```bash just provision ``` This is **idempotent** and safe to re-run. It: 1. Ensures `app/.env` has an `APP_KEY` — **never overwrites an existing key** (it prints a message and keeps the current one so encrypted credentials stay decryptable). 2. Installs Composer deps (`composer install`) and JS deps (`npm ci`). 3. Runs the DB schema + migrations (`just migrate`). 4. Checks the first-admin-password state and tells you if `/admin/` is still unauthenticated. If you prefer to run the steps manually, or `just` isn't installed yet: ```bash composer install # PHP deps (vendor/) npm ci # JS build deps (node_modules/) just migrate # create/update the SQLite DB from schema + migrations ``` `just setup` exists but its backing script (`scripts/setup-dev.sh`) is **stale** — it still clones the old `php-live-reload` library and creates legacy `admin/data/` directories. Live-reload now ships inside the app (`app/public/live-reload.php`), and assets are built with rolldown/lightningcss, not the live-reload watcher. Prefer `just provision`. ### APP_KEY and the app/.env file The app derives DB/storage paths from the filesystem layout, so the public site and admin panel run without any env file. But `Crypto` (decrypting the SMTP password, PeerTube credentials, and Nextcloud WebDAV sync) requires an `app/.env` containing an `APP_KEY`: ``` APP_KEY= ``` Generate one manually with: ```bash php -r 'echo "APP_KEY=" . base64_encode(random_bytes(32)) . PHP_EOL;' > app/.env chmod 600 app/.env ``` `app/.env` must **never** be committed. `just provision` only creates `app/.env` (or appends `APP_KEY`) when the key is absent — if the value already exists it is left untouched so existing encrypted credentials (SMTP password, PeerTube OAuth, Nextcloud WebDAV) remain decryptable. If the file is missing, the public site still runs but any path that reads encrypted credentials throws `RuntimeException: APP_KEY not found`. ### First admin login A brand-new DB seeds `site_settings.admin_password_hash` as an **empty string**, which makes `AdminAuth::requireLogin()` a pass-through — i.e. `/admin/` starts **unauthenticated** until a password is set. To secure it, open `http://127.0.0.1:8000/admin/account` (or the production admin) and set the admin password, which stores the bcrypt hash in `site_settings`. ## Project Structure ``` xamxam/ ├── app/ # All application code (the project root on the server) │ ├── bootstrap.php # App bootstrapping (constants, autoload, config) │ ├── router.php # Router for the PHP built-in dev server │ ├── public/ # DocumentRoot (web-accessible only) │ │ ├── index.php # Front controller / entry point │ │ ├── request-access.php │ │ ├── live-reload.php │ │ ├── admin/ # Admin panel │ │ ├── partage/ # Student submission via share links │ │ └── assets/ # Built CSS/JS, fonts, images (dist/ is generated) │ ├── src/ # PHP classes │ │ ├── Controllers/ # Request controllers (Home, Search, Tfe, Export, …) │ │ ├── Form/ # Form helper │ │ ├── Database.php # DB access + queries │ │ ├── AdminAuth.php # Session auth for admin │ │ ├── RateLimit.php │ │ ├── FilepondHandler.php │ │ ├── PeerTubeService.php │ │ └── … # (see list in src/) │ ├── templates/ # Template partials (public, admin, partials, partage) │ ├── storage/ # Database + uploads (private) │ │ ├── xamxam.db # SQLite database │ │ ├── schema.sql # Base schema │ │ ├── theses/ tfe/ # Uploaded files │ │ └── cache/ logs/ # Runtime data │ └── migrations/ # Migration runner + applied migrations ├── scripts/ # Build / deploy / utility scripts ├── tests/phpunit/ # PHPUnit tests ├── nginx/ # Server configuration + docs ├── deploy/ # Cron configs (backup, cleanup) ├── justfile # Task runner ├── composer.json / package.json └── TODO.md ``` ## Development Workflow ### Start the dev server ```bash just dev ``` This runs a one-shot asset build, applies pending migrations, opens `http://127.0.0.1:8000/` (public) and `/admin/` (admin), then starts the PHP built-in server (`php -S 127.0.0.1:8000`) with a `chokidar` watcher that rebuilds CSS/JS on change. - **Public site:** `http://127.0.0.1:8000/` - **Admin panel:** `http://127.0.0.1:8000/admin/` Stop it with `just stop`. If you only need the server without browsers/ui, run `just dev-build` first, then start the PHP built-in server manually with the `php -d … -S` command shown in the `dev` recipe (or add a `just serve` alias locally). ### Frontend assets Source CSS lives in `app/public/assets/css/`, source JS in `app/public/assets/js/app/`. They are **bundled and minified** into `app/public/assets/dist/` by `scripts/build*.mjs` (rolldown + lightningcss). ```bash just dev-build # one-shot build (quicker output than full) just build # full build just build-css # CSS only just build-js # JS only just build-check # verify build output is up to date ``` There is no live-reloading in production — `app/public/live-reload.php` only activates under the PHP built-in server. ## Testing PHPUnit is used. Configuration lives in `phpunit.xml`, tests in `tests/phpunit/` (with `tests/bootstrap.php` and `tests/TestDatabase.php` helpers). ```bash just test # run all PHPUnit tests just test-coverage # run with HTML coverage into coverage/ ``` ### Linting / formatting ```bash just lint-php # phpstan (static analysis) + php-cs-fixer (coding standards) just lint-css # biome lint on app/public/assets/css/ just lint-js # biome lint on app/public/assets/js/app/ + scripts/ just lint # all linters just fix # auto-fix (biome + php-cs-fixer) ``` ## Database Operations ```bash just migrate # run pending migrations just init-db # create/reset DB from app/storage/schema.sql just reset-db # rm DB + init-db just query # open an interactive SQLite shell just backup # SQL dump into app/storage/backup_.sql just backup-snapshot # WAL-safe hot backup + gzip into storage/backups/ just cleanup-drafts [--no-dry-run] # remove orphaned drafts > 7 days (default) # OLDER_THAN_HOURS=24 to change the threshold ``` See [database.md](database.md) for the full schema reference. ## Deployment See [deployment.md](deployment.md). Files are pushed to the server with `just deploy` (rsync → `xamxam:/var/www/xamxam/`). There is no git repo on the remote. ## Troubleshooting ```bash just logs # tail the dev error log (error.log) just stop # kill the dev server / asset watcher ``` If the browser doesn't hot-reload, confirm the asset watcher is still running and the rebuild succeeded (`just dev-build`).