Developer Guide
This guide covers everything you need to contribute to SparkShop or run it in a development environment — local setup, Docker, branching workflow, and deployment pipeline.
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Laravel 12, PHP 8.3 |
| Admin Panel | Filament 5 |
| Database (dev) | SQLite |
| Database (production) | MySQL 8.0 |
| Image Compression | intervention/image 3.x (GD driver) |
| PDF Generation | DomPDF via barryvdh/laravel-dompdf |
| Queue / Cache / Session | Laravel database driver |
| Assets | Vite 5, Tailwind CSS 3 |
| Containerisation | Docker, Docker Compose |
| CI/CD | GitHub Actions |
| Reverse Proxy | jwilder/nginx-proxy + acme-companion (Let's Encrypt) |
Local Development Setup
Prerequisites
- PHP >= 8.3 with the GD extension enabled
- Composer >= 2.7
- Node >= 20
- SQLite (bundled with PHP)
#### Enable GD
XAMPP (Windows): Open php.ini and uncomment:
extension=gd
Linux:
sudo apt install -y php-gd jpegoptim optipng pngquant gifsicle webp
macOS:
brew install php jpegoptim optipng pngquant gifsicle webp
Installation
git clone https://github.com/monatemedia/sparkshop.git
cd sparkshop
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate:fresh --seed
php artisan storage:link
Running All Processes
You need four terminals running simultaneously:
# Terminal 1 — Web server
php artisan serve
Terminal 2 — Vite (hot reload)
npm run dev
Terminal 3 — Queue worker (image compression jobs)
php artisan queue:work
Terminal 4 — Scheduler (promised date notifications)
php artisan schedule:work
Visit http://localhost:8000/admin and log in with admin@example.com / password.
Project Structure
app/
Console/Commands/ # Artisan commands (e.g. SendPromisedDateNotifications)
Enums/ # PHP-backed enums (JobCardStatus, EmployeeRole, etc.)
Filament/
Pages/ # Custom Filament pages (Dashboard, NotificationPreferences)
Resources/ # Filament resources (JobCardResource, CustomerResource, etc.)
Widgets/ # Dashboard widgets
Http/Controllers/ # Standard Laravel controllers (DocsController, JobCardPdfController)
Models/ # Eloquent models
Notifications/ # Laravel notification classes (5 notification types)
Observers/ # Model observers (JobCardObserver, JobCardPartObserver)
Providers/
AppServiceProvider.php # Registers observers
Filament/
AdminPanelProvider.php # Filament panel configuration
database/
migrations/ # All database migrations
seeders/ # DatabaseSeeder, NotificationPreferenceSeeder
resources/
docs/ # Public documentation markdown files
views/
welcome.blade.php # Public landing page
docs.blade.php # Public documentation page
routes/
web.php # Public routes (/, /docs, /job-cards/{id}/pdf)
Enums Reference
All enums are in app/Enums/ and implement HasLabel and HasColor for Filament integration.
| Enum | Cases |
|---|---|
JobCardStatus | Enquiry, AwaitingParts, BookedIn, InProgress, OnHoldParts, VehicleReleased, ReadyForCollection, Complete, Invoiced, Cancelled |
JobCardType | ServiceOnly, ServiceAndParts, PartsOnly |
EmployeeRole | Admin, Advisor, Technician, PartsManager |
JobCardServiceStatus | Pending, InProgress, Complete, Blocked |
JobCardPartStatus | Required, Ordered, Received, Allocated, Returned |
NotificationTrigger | JobCardAssigned, JobCardStatusChanged, PartStatusChanged, PromisedDateSoon, PromisedDateOverdue |
NotificationChannel | InApp, Email |
Notifications Architecture
Notifications are triggered by two model observers and one scheduled command.
Observers
JobCardObserver— fires onupdated()whenassigned_employee_idorstatuschangesJobCardPartObserver— fires onupdated()whenstatuschanges
Both observers use wasChanged() (not isDirty()) and both are registered in AppServiceProvider.
Scheduled Command
SendPromisedDateNotifications runs daily at 08:00 and dispatches:
PromisedDateSoonNotification— for job cards with a promised date tomorrowPromisedDateOverdueNotification— for overdue job cards not yet complete
Scheduled in routes/console.php:
Schedule::command('sparkshop:promised-date-notifications')->dailyAt('08:00');
Notification Classes
All five notification classes store 'duration' => 'persistent' in their toDatabase() array. This is required — without it, Filament's inline notification renderer auto-dismisses the notification after 6 seconds and deletes the database record.
Image Compression
File attachments are handled by the AttachmentsRelationManager on JobCardResource. Images are compressed server-side using intervention/image with the GD driver (not Imagick).
The GD driver is configured in config/image.php:
'driver' => Intervention\Image\Drivers\Gd\Driver::class,
Compression runs in a queued job so uploads don't block the UI. The queue worker must be running for compression to process.
Adding a Documentation Page
- Create a new markdown file in
resources/docs/, e.g.resources/docs/invoicing.md - Add the page to the
$pagesarray inapp/Http/Controllers/DocsController.php:
['slug' => 'invoicing', 'title' => 'Invoicing'],
- Add the nav link to
resources/views/docs.blade.phpin the sidebar section - The page is immediately accessible at
/docs/invoicing— no cache clearing needed
Docker Build
The Dockerfile uses a three-stage build:
- composer-builder — installs PHP dependencies (optionally with dev deps)
- node-builder — compiles frontend assets with Vite
- final — PHP 8.3 Apache image with GD, MySQL client, cron, and image optimisation tools
The ARG INSTALL_DEV_DEPENDENCIES=false build argument controls whether dev dependencies (e.g. Faker) are included:
# Production build (no dev deps)
docker compose build
Staging build (with dev deps for seeding)
docker compose build --build-arg INSTALL_DEV_DEPENDENCIES=true
The Laravel scheduler runs inside the container via a cron entry written during the Docker build — no separate setup on the server is required.
GitFlow Branching Model
Branch Types
| Branch | Created from | Merges into | Auto-deploys |
|---|---|---|---|
feature/<n> | dev | dev | — |
bugfix/<n> | dev | dev | — |
release/<version> | dev | main + dev | Staging |
hotfix/<n> | main | main + dev | Production |
Feature Branch Workflow
# Start
git checkout dev && git pull origin dev
git checkout -b feature/my-feature
Finish
git checkout dev && git pull origin dev
git merge feature/my-feature
git push origin dev
git branch -d feature/my-feature
git push origin --delete feature/my-feature
Release Workflow
# Create and push (triggers staging deploy)
git checkout dev && git pull origin dev
git checkout -b release/1.0.0
git push origin release/1.0.0
Promote to production
git checkout main && git merge release/1.0.0
git push origin main
git tag -a v1.0.0 -m "Release 1.0.0"
git push origin v1.0.0
Clean up
git checkout dev && git merge release/1.0.0
git push origin dev
git branch -d release/1.0.0
git push origin --delete release/1.0.0
CI/CD Pipeline
The GitHub Actions workflow at .github/workflows/docker-publish.yml handles the full build and deploy cycle.
Triggers
| Trigger | Environment | URL |
|---|---|---|
Push to release/* | Staging | https://sparkshop.monatemedia.com |
Push v* tag | Production | https://sparkshop.co.za |
Push to main | Production | https://sparkshop.co.za |
Pipeline Steps
- Build — multi-stage Docker build, push to GHCR
- SSH — connect to VPS via SSH agent
- Deploy — pull image, run
deploy-prod.sh - Blue/Green swap —
sparkshop-swap.shhandles atomic traffic switch - Migrate — migrations run on the inactive slot before swap
- Health check — external HTTP check against the live URL
- Release — GitHub Release created for version tags
Required GitHub Secrets
| Secret | Value |
|---|---|
PAT | GitHub PAT with write:packages |
SSH_HOST | VPS IP |
SSH_USER | VPS username |
SSH_PRIVATE_KEY | SSH private key |
STAGING_WORK_DIR | /home/user/sparkshop-staging |
PRODUCTION_HOST | Production VPS IP |
PRODUCTION_USER | Production username |
PRODUCTION_SSH_KEY | Production SSH private key |
PRODUCTION_WORK_DIR | /home/user/sparkshop-production |
PRODUCTION_DB_PASSWORD | MySQL app password |
PRODUCTION_DB_ROOT_PASSWORD | MySQL root password |
MAIL_HOST | SMTP host |
MAIL_USERNAME | SMTP username |
MAIL_PASSWORD | SMTP password |
Useful Artisan Commands
# Run migrations
php artisan migrate
Fresh migration with seed
php artisan migrate:fresh --seed
Seed notification preferences only
php artisan db:seed --class=NotificationPreferenceSeeder
Manually trigger promised date notifications
php artisan sparkshop:promised-date-notifications
Clear all caches
php artisan config:clear && php artisan route:clear && php artisan view:clear
Generate IDE helper files (if installed)
php artisan ide-helper:generate