Back to Home

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

LayerTechnology
BackendLaravel 12, PHP 8.3
Admin PanelFilament 5
Database (dev)SQLite
Database (production)MySQL 8.0
Image Compressionintervention/image 3.x (GD driver)
PDF GenerationDomPDF via barryvdh/laravel-dompdf
Queue / Cache / SessionLaravel database driver
AssetsVite 5, Tailwind CSS 3
ContainerisationDocker, Docker Compose
CI/CDGitHub Actions
Reverse Proxyjwilder/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.

EnumCases
JobCardStatusEnquiry, AwaitingParts, BookedIn, InProgress, OnHoldParts, VehicleReleased, ReadyForCollection, Complete, Invoiced, Cancelled
JobCardTypeServiceOnly, ServiceAndParts, PartsOnly
EmployeeRoleAdmin, Advisor, Technician, PartsManager
JobCardServiceStatusPending, InProgress, Complete, Blocked
JobCardPartStatusRequired, Ordered, Received, Allocated, Returned
NotificationTriggerJobCardAssigned, JobCardStatusChanged, PartStatusChanged, PromisedDateSoon, PromisedDateOverdue
NotificationChannelInApp, Email

Notifications Architecture

Notifications are triggered by two model observers and one scheduled command.

Observers

  • JobCardObserver — fires on updated() when assigned_employee_id or status changes
  • JobCardPartObserver — fires on updated() when status changes

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 tomorrow
  • PromisedDateOverdueNotification — 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

  1. Create a new markdown file in resources/docs/, e.g. resources/docs/invoicing.md
  2. Add the page to the $pages array in app/Http/Controllers/DocsController.php:
   ['slug' => 'invoicing', 'title' => 'Invoicing'],

  1. Add the nav link to resources/views/docs.blade.php in the sidebar section
  2. The page is immediately accessible at /docs/invoicing — no cache clearing needed

Docker Build

The Dockerfile uses a three-stage build:

  1. composer-builder — installs PHP dependencies (optionally with dev deps)
  2. node-builder — compiles frontend assets with Vite
  3. 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

BranchCreated fromMerges intoAuto-deploys
feature/<n>devdev
bugfix/<n>devdev
release/<version>devmain + devStaging
hotfix/<n>mainmain + devProduction

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

TriggerEnvironmentURL
Push to release/*Staginghttps://sparkshop.monatemedia.com
Push v* tagProductionhttps://sparkshop.co.za
Push to mainProductionhttps://sparkshop.co.za

Pipeline Steps

  1. Build — multi-stage Docker build, push to GHCR
  2. SSH — connect to VPS via SSH agent
  3. Deploy — pull image, run deploy-prod.sh
  4. Blue/Green swapsparkshop-swap.sh handles atomic traffic switch
  5. Migrate — migrations run on the inactive slot before swap
  6. Health check — external HTTP check against the live URL
  7. Release — GitHub Release created for version tags

Required GitHub Secrets

SecretValue
PATGitHub PAT with write:packages
SSH_HOSTVPS IP
SSH_USERVPS username
SSH_PRIVATE_KEYSSH private key
STAGING_WORK_DIR/home/user/sparkshop-staging
PRODUCTION_HOSTProduction VPS IP
PRODUCTION_USERProduction username
PRODUCTION_SSH_KEYProduction SSH private key
PRODUCTION_WORK_DIR/home/user/sparkshop-production
PRODUCTION_DB_PASSWORDMySQL app password
PRODUCTION_DB_ROOT_PASSWORDMySQL root password
MAIL_HOSTSMTP host
MAIL_USERNAMESMTP username
MAIL_PASSWORDSMTP 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