Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| docker/app | ||
| src | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| README.md | ||
| docker-compose.yml | ||
| software.md | ||
README.md
Bowler
A multi-user website for managing shared bowler charts — tracking project status and KPIs.
This is the Phase 1 foundation: Docker environment, Hello World landing page, login, and user administration. Bowler chart functionality comes next.
Requirements
- Docker Desktop (or Docker Engine + Compose v2)
Nothing else — no local PHP, Composer, or Node needed. The app container installs its own dependencies on first boot.
Quick start
-
Copy the environment template and set your own passwords:
copy .env.example .envEdit
.envand set:DB_PASSWORD/DB_ROOT_PASSWORD— Percona database credentialsADMIN_EMAIL/ADMIN_PASSWORD— the initial site administrator loginAPP_KEY— a random base64 key. Generate one with:docker run --rm php:8.4-cli php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
(A ready-to-use development
.envis already included in this repo.) -
Start the stack:
docker-compose up -d --buildFirst boot takes a few minutes: the app container runs
composer install, waits for the database, runs migrations, and seeds the initial administrator. Watch progress withdocker-compose logs -f app. -
Open http://localhost:8080 and log in with
ADMIN_EMAIL/ADMIN_PASSWORD.
What's included
| URL | Description |
|---|---|
/ |
Hello World landing page |
/login |
Login (rejects disabled accounts, throttled to 10 attempts/min) |
/charts |
Bowler charts of your groups (group owners and admins manage them) |
/my-bowler |
Your personal bowler chart — all of your own KPIs and projects |
/kpis |
My KPIs: threshold-based metrics with auto-computed Green/Yellow/Red status |
/projects |
My projects: manually-statused G/Y/R with notes, headwinds, tailwinds, highlights, blockers |
/teams, /categories |
Personal lists used to group chart rows (Team → Category) |
/api-keys |
Personal API keys (multiple per user) for the automation API |
/api/docs |
Swagger UI for the REST API |
/admin/users |
User management (admins only): create users, reset passwords, enable/disable accounts |
/admin/groups |
Group management (admins only): create groups, assign members with member/owner roles |
Admins cannot disable their own account, so you can't lock yourself out.
User types
Every user is either standard (default) or admin (users.type). Admins manage users
and groups, see every bowler chart, and can edit any user's content; standard users see only
their own content plus what's shared with their groups. New abilities can be gated on
$user->isAdmin() (or new UserType enum cases) as the app grows.
KPIs and entries
A KPI is defined once (measurement type: number / percentage / duration, plus an evaluation mode) and then receives dated entries; each entry's status is computed from the KPI's thresholds:
- Higher is better — green when value ≥ green threshold, yellow when ≥ yellow threshold, else red
- Lower is better — the reverse
- Target band — green within ± green tolerance of target, yellow within ± yellow tolerance, else red
- Milestone — met (green) or not met (red)
Projects work the same way but with manually chosen statuses and narrative fields.
Bowler charts
Charts belong to groups; group owners (and site admins) can create any number of named charts per group. Members' KPIs and projects appear automatically on all of their groups' charts — including charts created later and groups joined later — unless the item's owner deselects specific charts on the KPI/project form (stored as per-chart exclusions).
Every KPI and project belongs to one of its owner's Teams (e.g. "Website Development") and Categories (e.g. "Performance") — required on create/edit; the chart groups rows Team → Category, merging same-named teams from different owners case-insensitively. A team or category cannot be deleted while KPIs/projects still use it — reassign the items first. (Items grandfathered from before teams existed appear under "No Team" / "Uncategorized".)
The grid shows the trailing 12 months; each cell is the most recent entry in that month
(color + value for KPIs, color for projects, tooltip with date and notes). Rows expand in place:
click a KPI row for an inline SVG trend chart with its threshold zones shaded (line + status-colored
points for numeric KPIs, a met/missed timeline for milestones); click a project's month cell for that
month's notes/headwinds/tailwinds/highlights/blockers. Clicking anywhere else collapses the row.
Group members can click through to any charted item's entries read-only; only the owner (or an
admin) can edit. Everyone also has a personal chart at /my-bowler covering all of their own items.
REST API
Create a key on /api-keys, then call /api/v1/... with Authorization: Bearer <key>.
Full interactive documentation at /api/docs (OpenAPI spec: /api-docs/openapi.json).
curl -H "Authorization: Bearer $KEY" http://localhost:8080/api/v1/kpis
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"entry_date":"2026-07-09","value":96.2}' \
http://localhost:8080/api/v1/kpis/1/entries
Project layout
docker-compose.yml # app (PHP 8.4 + Apache) + db (Percona latest)
.env # all credentials & app config (never commit real secrets)
docker/app/ # app image: Dockerfile + startup script
src/ # the Laravel application (bind-mounted into the container)
db-data/ # Percona data files (created on first run)
Styling (Tailwind CSS)
The UI is styled with Tailwind CSS v4 using the standalone CLI
baked into the app image — no Node.js/npm required anywhere. The source file is
src/resources/css/app.css; the compiled stylesheet src/public/css/app.css is generated
automatically on every container start.
While editing Blade views, run the optional watcher so CSS rebuilds live:
docker compose --profile dev up -d # starts the app + a tailwind --watch container
Or rebuild once by hand:
docker compose exec app tailwindcss -i resources/css/app.css -o public/css/app.css --minify
Common operations
docker-compose logs -f app # tail application logs
docker-compose exec app php artisan migrate # run new migrations
docker-compose exec app php artisan tinker # interactive REPL
docker-compose down # stop (data persists in db-data/)
Performance notes
The stack is tuned for running from Windows bind mounts (the slowest part of this setup):
- OPcache + realpath cache (
docker/app/php-perf.ini): compiled PHP stays in memory and source files are re-checked at most every 60 s — after editing PHP code, wait up to a minute or restart the app container. - Laravel caches: the entrypoint runs
php artisan optimize(config/route/view caches) on every container start. While actively developing routes or config, rundocker compose exec app php artisan optimize:clearto work uncached; restart to re-cache. Keep routes controller-based — closures break route caching. - Percona flags (docker-compose.yml): redo log flushed once per second instead of per commit (dev-appropriate durability trade-off), larger buffer pool, no reverse-DNS.
If you ever want a substantially faster database and can accept the data living inside
Docker's managed storage instead of ./db-data, switch the db volume to a named volume.
Roadmap
- Bowler chart data model and CRUD
- Multiple charts with per-chart owners and collaborators
- PDF / printer export of bowler summaries