Files
xamxam/docs/development.md
T

209 lines
7.9 KiB
Markdown

# 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=<base64-encoded 32 random bytes>
```
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_<timestamp>.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
just cleanup-tmp-uploads [--no-dry-run] # GC abandoned FilePond uploads (>2h or missing session)
# TMP_UPLOAD_MAX_AGE_SECONDS 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`).