2026-09-05 01:41:55 +01:00
|
|
|
# Architecture
|
|
|
|
|
|
|
|
|
|
A REST API in PHP 8 (Slim 4) over a single SQLite file, plus a Vue 3 +
|
|
|
|
|
TypeScript PWA frontend. Auth is a bearer JWT, obtained via a magic link or a
|
|
|
|
|
passkey (see [docs/api.md](api.md)) — there's no session store or cookie.
|
|
|
|
|
|
|
|
|
|
## Backend layout
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
public/index.php Front controller
|
|
|
|
|
src/bootstrap.php App wiring and route definitions
|
|
|
|
|
src/Support/Config.php Environment-driven configuration
|
|
|
|
|
src/Support/Database.php PDO/SQLite connection
|
|
|
|
|
src/Auth/JwtService.php Issue/verify JWTs
|
|
|
|
|
src/Auth/AuthMiddleware.php Bearer-token authentication
|
|
|
|
|
src/Auth/SessionPayload.php Shared user + session JSON shape
|
|
|
|
|
src/Mail/ Mailer interface, SMTP/mail()/log transports, EmailVerifier
|
|
|
|
|
src/Http/JsonErrorHandler.php Uniform JSON error envelope
|
|
|
|
|
src/Http/Controllers/ Request handlers (Auth, EmailVerification, Passkey, Project, Card, CardStatus)
|
|
|
|
|
src/Repository/ Database access (User, EmailVerification, Passkey, WebAuthnChallenge, Project, Card, CardStatus)
|
|
|
|
|
src/Support/Validator.php Request-body validation helper
|
|
|
|
|
migrations/*.sql Schema, applied by bin/migrate.php
|
|
|
|
|
Dockerfile Multi-stage: Node frontend build + PHP 8.3/Apache runtime
|
2026-09-05 02:15:39 +01:00
|
|
|
(Alpine -- apk's own php83/apache2 packages, not a
|
|
|
|
|
from-source compile; ~90MB image)
|
2026-09-05 01:41:55 +01:00
|
|
|
docker-compose.yml Local stack: app (SPA + API) + Mailpit
|
2026-09-05 02:15:39 +01:00
|
|
|
docker/ Apache vhost (mod_rewrite, document root) + entrypoint
|
|
|
|
|
(runs pending migrations, then hands off to Apache)
|
2026-09-05 01:41:55 +01:00
|
|
|
web/ Vue 3 + TypeScript + Vite PWA frontend (dev on the host)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A `ProjectScopedController` base class centralizes "look up a project owned
|
|
|
|
|
by the caller, or 404" for the controllers that need it (`Project`, `Card`,
|
|
|
|
|
`CardStatus`). Every table a request can reach is scoped to the
|
|
|
|
|
authenticated user one way or another — directly (`owner_id`/`user_id`) or
|
|
|
|
|
via a project that is.
|
|
|
|
|
|
|
|
|
|
## Frontend
|
|
|
|
|
|
|
|
|
|
The Vue/TypeScript PWA lives in [web/](../web/) and is a separate concern
|
|
|
|
|
with its own conventions (routing, state, styling, drag-and-drop). See
|
|
|
|
|
[web/README.md](../web/README.md) for all of that — this document only
|
|
|
|
|
covers the backend.
|
|
|
|
|
|
|
|
|
|
## Database
|
|
|
|
|
|
|
|
|
|
One SQLite file, migrated forward-only by `bin/migrate.php` from
|
|
|
|
|
`migrations/*.sql` (each applied file is recorded in a `schema_migrations`
|
|
|
|
|
table, so re-running is safe). See [docs/setup.md](setup.md) for how to run
|
|
|
|
|
migrations, and [docs/history.md](history.md) for how the schema and the
|
|
|
|
|
rest of the app got here.
|