- PHP 67%
- Dart 27.1%
- C++ 2.8%
- CMake 2.1%
- Swift 0.3%
- Other 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| apis | ||
| core | ||
| cron | ||
| deploy | ||
| docker | ||
| garage | ||
| login | ||
| mobile | ||
| navbar | ||
| parts | ||
| sql | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| composer.json | ||
| index.php | ||
| README.md | ||
Track maintenance, estimate mileage, get reminded before something breaks.
PHP · MySQL · Apache · Tailwind · PHPMailer · NHTSA + Edmunds APIs
🚗 What it does
- Garage — store every vehicle you own, decoded from a VIN via the NHTSA vPIC API and pinned to a year/make/model.
- Maintenance schedules — auto-generated per vehicle from the Edmunds maintenance API when an API key is set, with a sensible hardcoded fallback. Every interval is editable per car.
- Mileage estimation — once you've logged at least one service, the app derives a daily-mileage rate from the (date, mileage) data points and projects today's odometer reading without you needing to update it manually.
- Status engine — every schedule item is classified
Overdue,Due Soon, orGoodbased on the projected mileage and per-vehicle thresholds you control in Vehicle Settings. - Daily email alerts — a cron-driven script emails you the morning a service goes due-soon or overdue, with one consolidated message per vehicle. De-duped via a
maintenance_alertstable so you're not pinged twice. - Dashboard rings — at-a-glance circular gauges show the five items closest to needing attention, color-coded by urgency.
📸 Screenshots
Drop captures into
docs/screenshots/and they'll render here.
docs/screenshots/
├─ dashboard.png
├─ maintenance.png
└─ alert-email.png
🧱 Tech stack
| Layer | Used |
|---|---|
| Language | PHP 8.2 (PDO, prepared statements throughout) |
| Database | MySQL 8 |
| Web server | Apache 2.4 + mod_php (single container, port 80) — TLS terminated at your upstream reverse proxy |
| Frontend | Server-rendered PHP + Tailwind via CDN |
| PHPMailer over SMTP (Gmail app-password by default) | |
| 3rd-party | NHTSA vPIC, Edmunds Vehicle/Maintenance, Google OAuth |
🛠 Quick start (Docker)
You need: Docker + Docker Compose, a Google OAuth client (for login), and any other API keys you want to use.
There are two ways to run the stack: pull the prebuilt image from GHCR (faster, no build toolchain needed), or build from source (when you're hacking on the code).
Option A — Use the prebuilt image (recommended for deploys)
Each v* tag pushed to this repo is built and published to the GitHub Container Registry by the workflow in .github/workflows/release.yml.
Want the smallest possible deploy? See
deploy/for a standalone two-file setup (compose + .env) that doesn't require cloning the source. The instructions below assume you've cloned the repo.
1. Clone and configure
git clone https://github.com/Code-Liberation-Front/CarPartPicker.git
cd CarPartPicker
cp .env.example .env # one file: docker compose + app env
Edit .env — set DB_PASSWORD / DB_PASS (same value in both fields), drop in your Google OAuth credentials, generate a JWT_SECRET (openssl rand -hex 48), and add SMTP creds if you want email alerts. Optionally pin a version by uncommenting APP_IMAGE in .env:
APP_IMAGE=ghcr.io/code-liberation-front/carpartpicker:v1.0.0
If the package is private, log in to GHCR first with a PAT that has read:packages scope:
echo $GITHUB_TOKEN | docker login ghcr.io -u <your-github-user> --password-stdin
Public packages don't need this step.
2. Pull and start
docker compose -f docker/docker-compose.yml --env-file .env pull
docker compose -f docker/docker-compose.yml --env-file .env up -d
Option B — Build from source
Same .env setup as Option A, then build the image locally instead of pulling:
docker compose -f docker/docker-compose.yml --env-file .env up -d --build
Either way, two services come up:
| Container | Image | Role |
|---|---|---|
phpcar |
ghcr.io/code-liberation-front/carpartpicker:latest (or local build) |
Apache 2.4 + mod_php 8.2, blocks /core, /vendor, etc., serves HTTP on ${HTTP_PORT} |
mysql |
mysql:8.0 |
Database (data persists in the db_data volume) |
TLS is meant to be handled by your existing reverse proxy (Caddy/nginx/Traefik). Point it at <host>:<HTTP_PORT>. Example Caddy snippet:
https://car.example.com {
reverse_proxy <host>:8080
}
To upgrade later, bump APP_IMAGE in .env (or just re-pull :latest) and run:
docker compose -f docker/docker-compose.yml --env-file .env pull phpcar
docker compose -f docker/docker-compose.yml --env-file .env up -d phpcar
3. Bootstrap the database (first run only)
docker exec -i mysql mysql -uroot -p"$DB_ROOT_PASSWORD" car < sql/setup.sql
docker exec -i mysql mysql -uroot -p"$DB_ROOT_PASSWORD" car < sql/migrate_estimated_mileage.sql
docker exec -i mysql mysql -uroot -p"$DB_ROOT_PASSWORD" car < sql/migrate_threshold_settings.sql
The first file creates all tables; the two migrations add the mileage-estimation columns and per-vehicle threshold columns introduced after the initial schema.
4. Open it
http://localhost:8080
Sign in with Google, add a vehicle, log a service, and you're off.
⚙️ Configuration
A single .env at the repo root drives both docker compose and the PHP app — compose passes the app keys straight into the phpcar container, no separate in-image config file required. (If a legacy core/.env exists, it is still read as a fallback so older deployments keep working.)
Host-side keys (used by docker compose only)
| Key | Default | Purpose |
|---|---|---|
HTTP_PORT |
8080 |
Host port that phpcar binds to. Point your upstream reverse proxy here. |
APP_IMAGE |
:latest |
Pin a specific image tag for reproducible deploys. |
DB_ROOT_PASSWORD / DB_PASSWORD |
(required) | MySQL provisioning passwords. |
App keys (passed into the phpcar container as env vars)
| Key | Required? | Purpose |
|---|---|---|
DB_HOST, DB_NAME, DB_USER, DB_PASS, DB_CHARSET |
✅ | DB connection. DB_HOST=mysql with the bundled compose. |
JWT_SECRET |
✅ for /apis/v1/* |
HS256 signing key for the mobile API. Must be ≥32 chars. Generate with openssl rand -hex 48. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET / GOOGLE_REDIRECT_URI |
✅ for login | Web OAuth flow. The redirect URI must match what's registered in Google Cloud and end with /login/google-callback.php. |
GOOGLE_OAUTH_ANDROID_CLIENT_ID |
for mobile sign-in | Android OAuth client ID. Verified against the ID token in apis/v1/auth/google.php. |
EDMUNDS_API_KEY |
optional | If set, OEM maintenance schedules are pulled per vehicle. Without it, the app uses hardcoded defaults. |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM / SMTP_FROM_NAME |
optional | Daily alert emails. Leave blank to disable mail. |
APP_URL |
optional | Public URL used in the "Log this service" CTA on alert emails. |
FCM_PROJECT_ID / FCM_SERVICE_ACCOUNT_PATH |
optional | FCM push notifications. Bind-mount the service-account JSON into the container — see deploy/README.md. |
📬 Daily email alerts
The script cron/maintenance_alerts.php:
- Refreshes every vehicle's daily-mileage rate and projected odometer.
- Re-runs the maintenance status check using the projected mileage.
- For each item that just flipped to
due_soonoroverdue, inserts a row intomaintenance_alerts(INSERT IGNOREkeyed on vehicle/schedule/severity) and emails the owner if SMTP is configured. - Clears
maintenance_alertsrows whose item is no longer flagged — so the next time the same service goes due, the user is re-notified.
Schedule it on the host crontab (crontab -e):
0 7 * * * docker exec phpcar php /var/www/html/cron/maintenance_alerts.php >> ~/cpp-cron.log 2>&1
A manual run is fine for testing:
docker exec phpcar php /var/www/html/cron/maintenance_alerts.php
🌐 Production deployment
The same compose stack drives production — the only differences are config:
- TLS / domain. Terminate TLS at your existing reverse proxy (Caddy/nginx/Traefik). Point it at
<host>:<HTTP_PORT>. Example Caddy:https://cars.example.com { reverse_proxy <host>:8080 }. - DNS. Point your domain's A record at the reverse-proxy host (not necessarily the carpartpicker host).
- Google OAuth. In the Google Cloud console, add
https://cars.example.com/login/google-callback.phpto the authorized redirect URIs. SetGOOGLE_REDIRECT_URIin.envto the same URL, andAPP_URL=https://cars.example.com. - SMTP. Generate a Gmail app password (2FA must be enabled) and put it in
SMTP_PASS. Use the same address forSMTP_USER/SMTP_FROM. - Edmunds API key (optional). Sign up at the Edmunds developer portal and drop the key in
EDMUNDS_API_KEY— every vehicle's maintenance schedule will be richer. - Backups. The MySQL data lives in the
db_datanamed volume. Snapshot it withdocker run --rm -v cpp_db_data:/data alpine tar -C /data -czf - . > backup.tgz.
📁 Project layout
.
├── apis/ ← server-side adapters: NHTSA VIN decoder, Edmunds maintenance lookup
├── core/ ← shared PHP: DB singleton, session, env-driven mailer, mileage helpers
├── cron/ ← cron-only entrypoints (daily alert sender)
├── docker/ ← Dockerfile, docker-compose.yml, Apache vhost config
├── garage/ ← user-facing pages (vehicle list, detail dashboard, add)
├── login/ ← email + Google OAuth login flow
├── navbar/ ← shared header partial
├── sql/ ← setup.sql + migration files
├── index.php ← marketing/home page
└── README.md ← you're here
🗺 Roadmap / known gaps
- Move the daily alert cron into the compose stack as a sibling service so deployment doesn't require host-side crontab editing.
- The image speaks plain HTTP; if you don't already have a reverse proxy, document a one-shot path (e.g. Cloudflare Tunnel or a local Caddy container snippet).
- Capture screenshots into
docs/screenshots/for the README preview block. - Test suite. There isn't one yet.
📜 License
Add your preferred license here (MIT / Apache-2.0 / GPL-3.0). The project ships without one by default — until you add a LICENSE file, all rights are reserved.