Add stage 2: Vue/TypeScript PWA shell with auth-gated routing

Backend: new migration adds users.email_verified_at (null = unverified);
registration leaves it null, and the register/login/me payloads now expose
email_verified and email_verified_at.

Frontend (web/): Vite + Vue 3 + TypeScript PWA (vite-plugin-pwa). Pinia auth
store keeps the token in localStorage and validates it via GET /api/me on
load. vue-router guards redirect unauthenticated visitors to /login,
preserving the intended path; /register creates an account and signs in
immediately (with the email unverified). Placeholder home page, minimal
styling, generated icons. Dev server proxies /api to the API.

docker-compose.yml gains an optional "web" service (profile: frontend) so
`docker compose --profile frontend up -d` runs the dev server alongside the
API; `docker compose up -d` still starts the API alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 18:12:31 +01:00
co-authored by Claude Sonnet 5
parent e91fc89e23
commit 5e3b8dbd7e
35 changed files with 7454 additions and 10 deletions
+47 -6
View File
@@ -1,15 +1,19 @@
# PHP Todo List
A small todo-list application: a REST API written in PHP (Slim 4) backed by an
SQLite file, plus a single-page frontend (added in a later stage).
SQLite file, plus a Vue 3 + TypeScript PWA frontend in [web/](web/).
## Status
| Stage | Scope | State |
|-------|-------|-------|
| 1 | Auth API — register, login, `GET /me` | ✅ done |
| 2 | Todo CRUD API | planned |
| 3 | Single-page frontend | planned |
| 2 | Frontend shell — Vite PWA, auth-gated routing, register/login pages | ✅ done |
| 3 | Todo CRUD (API + UI) | planned |
Registration signs the user in immediately, with the account's email marked
unverified (`user.email_verified` is `false` until a future stage adds a
verification endpoint).
## Run with Docker
@@ -62,6 +66,26 @@ composer serve # http://localhost:8080 (php -S localhost:8080 -t pub
Any web server can serve the app as long as the document root is `public/` and
unknown paths fall through to `public/index.php`.
## Frontend
The Vue/TypeScript PWA lives in [web/](web/) and talks to this API. With the API
running (`docker compose up -d`):
```bash
cd web
npm install
npm run dev # http://localhost:5173, proxies /api to localhost:8080
```
Or run it inside Compose alongside the API:
```bash
docker compose --profile frontend up -d
```
Unauthenticated visitors are redirected to `/login`; `/register` creates an
account and signs in immediately. See [web/README.md](web/README.md).
## Configuration
All settings are optional environment variables (read from `.env` or the real
@@ -97,12 +121,20 @@ Request:
```json
{
"user": { "id": 1, "email": "ada@example.com", "created_at": "2026-09-03T12:00:00Z" },
"user": {
"id": 1,
"email": "ada@example.com",
"email_verified": false,
"email_verified_at": null,
"created_at": "2026-09-03T12:00:00Z"
},
"token": "<jwt>",
"expires_at": "2026-09-04T12:00:00+00:00"
}
```
New accounts are created with an unverified email (`email_verified: false`).
Errors: `422` invalid input, `409` email already registered.
Validation: `email` must be a valid address (≤ 255 chars); `password` must be
@@ -126,7 +158,15 @@ Requires `Authorization: Bearer <jwt>`.
`200 OK`:
```json
{ "user": { "id": 1, "email": "ada@example.com", "created_at": "2026-09-03T12:00:00Z" } }
{
"user": {
"id": 1,
"email": "ada@example.com",
"email_verified": false,
"email_verified_at": null,
"created_at": "2026-09-03T12:00:00Z"
}
}
```
`401` if the header is missing, malformed, or the token is invalid/expired.
@@ -178,8 +218,9 @@ src/Http/Controllers/ Request handlers
src/Repository/ Database access
migrations/*.sql Schema, applied by bin/migrate.php
Dockerfile PHP 8.3 + Apache image
docker-compose.yml One-command local stack
docker-compose.yml One-command local stack (API; web via --profile frontend)
docker/ Apache vhost + container entrypoint
web/ Vue 3 + TypeScript + Vite PWA frontend
```
## Provenance