Files
project-manager/README.md
T
aneurinandClaude Sonnet 5 15145865d4 Add Docker Compose setup for local runs
PHP 8.3 + Apache image serving public/ on port 8080, with pdo_sqlite and
mbstring built in. The container entrypoint applies migrations as www-data
before starting Apache; the SQLite database and generated JWT signing key
persist in a named "storage" volume. `docker compose up -d` is now the
primary way to run the project without a local PHP toolchain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 17:45:35 +01:00

4.9 KiB
Raw Blame History

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

Status

Stage Scope State
1 Auth API — register, login, GET /me done
2 Todo CRUD API planned
3 Single-page frontend planned

Run with Docker

The only requirement is Docker with the Compose plugin.

docker compose up -d

This builds a PHP 8.3 + Apache image, applies migrations, and serves the API at http://localhost:8080 (e.g. curl http://localhost:8080/api/health).

  • The SQLite database and the generated JWT signing key live in the storage named volume, so they survive docker compose restart / down + up.
  • After changing PHP source or composer.json, rebuild: docker compose up -d --build.
  • docker compose down -v removes the volume and gives you a clean database.
  • Override settings via the environment or a .env file in this directory (Compose substitutes APP_DEBUG, JWT_SECRET, JWT_TTL — see docker-compose.yml).

Run without Docker

Requires PHP 8.1+ with the pdo_sqlite and mbstring extensions, plus Composer. On Fedora:

sudo dnf install php-cli php-pdo php-mbstring composer

Setup

composer install
cp .env.example .env      # optional; sane defaults are used without it
composer migrate          # creates storage/database.sqlite and its tables

Running

composer serve            # http://localhost:8080  (php -S localhost:8080 -t public)

Any web server can serve the app as long as the document root is public/ and unknown paths fall through to public/index.php.

Configuration

All settings are optional environment variables (read from .env or the real environment). See .env.example.

Variable Default Purpose
APP_DEBUG false Include exception details in error responses
DATABASE_PATH storage/database.sqlite SQLite file location
JWT_SECRET auto-generated into storage/secret.key Token signing key
JWT_TTL 86400 Token lifetime in seconds

API

Base path: /api. All request and response bodies are JSON; send Content-Type: application/json.

GET /api/health

{ "status": "ok" }

POST /api/auth/register

Request:

{ "email": "ada@example.com", "password": "correct horse battery staple" }

201 Created:

{
  "user": { "id": 1, "email": "ada@example.com", "created_at": "2026-09-03T12:00:00Z" },
  "token": "<jwt>",
  "expires_at": "2026-09-04T12:00:00+00:00"
}

Errors: 422 invalid input, 409 email already registered.

Validation: email must be a valid address (≤ 255 chars); password must be 872 characters.

POST /api/auth/login

Request:

{ "email": "ada@example.com", "password": "correct horse battery staple" }

200 OK: same shape as register. 401 on bad credentials (the message does not say whether it was the email or the password that was wrong).

GET /api/me

Requires Authorization: Bearer <jwt>.

200 OK:

{ "user": { "id": 1, "email": "ada@example.com", "created_at": "2026-09-03T12:00:00Z" } }

401 if the header is missing, malformed, or the token is invalid/expired.

Error shape

Every error response looks like:

{ "error": { "message": "The submitted data was invalid.", "details": { "email": ["Email must be a valid address."] } } }

details is present only when relevant (e.g. validation).

Try it

BASE=http://localhost:8080

curl -s -X POST $BASE/api/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"ada@example.com","password":"password123"}'

TOKEN=$(curl -s -X POST $BASE/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"ada@example.com","password":"password123"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)

curl -s $BASE/api/me -H "Authorization: Bearer $TOKEN"

Tests

composer install         # installs phpunit (require-dev)
vendor/bin/phpunit

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/Http/JsonErrorHandler.php Uniform JSON error envelope
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/                      Apache vhost + container entrypoint

Provenance

This project was generated with Claude Code.