Go to file
Brian Fertig 76ccc53474 Stop tracking compiled app.css
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:04:08 -06:00
docker/app Theme Addition 2026-07-09 15:54:57 -06:00
src Stop tracking compiled app.css 2026-07-10 13:04:08 -06:00
.env.example first commit 2026-07-08 16:31:44 -06:00
.gitattributes first commit 2026-07-08 16:31:44 -06:00
.gitignore Theme Addition 2026-07-09 15:54:57 -06:00
README.md Menu refinement 2026-07-09 14:22:18 -06:00
docker-compose.yml Added Charts, Graphs, Types, Categories, etc. 2026-07-09 12:46:19 -06:00
software.md first commit 2026-07-08 16:31:44 -06:00

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

Nothing else — no local PHP, Composer, or Node needed. The app container installs its own dependencies on first boot.

Quick start

  1. Copy the environment template and set your own passwords:

    copy .env.example .env
    

    Edit .env and set:

    • DB_PASSWORD / DB_ROOT_PASSWORD — Percona database credentials
    • ADMIN_EMAIL / ADMIN_PASSWORD — the initial site administrator login
    • APP_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 .env is already included in this repo.)

  2. Start the stack:

    docker-compose up -d --build
    

    First boot takes a few minutes: the app container runs composer install, waits for the database, runs migrations, and seeds the initial administrator. Watch progress with docker-compose logs -f app.

  3. 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, run docker compose exec app php artisan optimize:clear to 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