Appearance
Architecture
Vroum Solutions is a modular Laravel 13 application with a Vue 3 + Inertia.js v3 frontend. It is multi-tenant by user context (Shop vs Client) within a single database rather than database-per-tenant. Access & scoping are enforced at the domain, middleware & permission layers.
High-Level Layers
- Presentation: Vue 3 (Composition API) delivered via Inertia responses.
- Transport: HTTP (RESTful routes), WebSockets (Laravel Reverb), Queues (Redis + Horizon).
- Application: Actions (command-like units), Controllers (thin HTTP adapters), Policies (authorization gates).
- Domain: Models, Value Objects, Enums, Data DTOs.
- Infrastructure: PostgreSQL 17 (primary DB), Redis (cache, queue, broadcast), Meilisearch (search), S3-compatible storage (Flysystem / MinIO locally), Browsershot + Chromium (PDF), Webhooks.
- Observability: Sentry (errors & performance), Laravel Pulse (app metrics), Nightwatch, Activity Log (Spatie), Ray (debugging in dev).
Directory Structure (Key)
app/
Actions/ # Single-purpose classes using the AsAction trait
Ai/ # LLM tools & agents (laravel/ai)
Casts/ # Custom Eloquent casts (Money, …)
Data/ # Spatie Data DTOs (typed payloads -> TS types)
Entities/ # Backed enums → TS enums with metadata
Facades/ # Facades over integration services (Carfax, NHTSA, SMS, …)
Http/ # Thin controllers, middleware, form requests, Inertia response factory
Models/ # Eloquent models (all extend App\Models\Model)
Normalizers/ # Inbound payload normalization
Policies/ # Authorization rules
Rules/ # Custom validation rules
Services/ # Cross-cutting or integration services
Traits/ # Shared model behaviour (HasShop, ShopSearchable, Userstamps, …)
Transformers/ # TypeScript transform customizations
ValueObjects/ # Immutable domain representations
Webhook/ # Webhook handlers
bootstrap/ # App bootstrap & middleware registration
config/ # Configuration for packages & features
resources/js/ # Frontend (Pages, Components, Elements, Layouts, Stores, types)
resources/css/ # Tailwind entry + theme tokens (see theme.md)
resources/icons/ # Source SVGs, compiled by npm run generate:icons
routes/ # Route files segmented by context
tests/ # PHPUnit test suites (Feature, Unit)Request Flow
- HTTP request hits a route (e.g.,
routes/webShop.php). - Route maps to a Controller or directly to an Action used as a controller.
- Controller delegates to an Action (business logic), often receiving typed Data objects.
- Action performs work (model mutations, service calls).
- Response returned via Inertia (server-side data → Vue page component) or JSON.
- Frontend renders reactive UI; subsequent interactions use Inertia navigation or
useHttp.
Shop-scoped routes sit behind a {shop} prefix; MakeCurrentShop resolves the tenant and Shop::current() is available for the rest of the request (and inside queued jobs).
Frontend Page Resolution
Layout selection happens in resources/js/app.ts:
Pages/Auth/*and the top-levelErrorpage →GuestLayoutPages/Client/*→ClientLayout- everything else (including
Pages/Shop/*,Pages/Profile/*,Pages/Public/*) →AppLayout
Override per page with defineOptions({ layout: MyLayout }), or opt out entirely with noLayout: true.
Multi-Tenancy Strategy
Single database; the User model distinguishes contexts:
User::isShop()vsUser::isClient()shapes authorization, route access, and UI.- Users belong to many
Shops (ShopUser) or manyClients (ClientShop) with a "current" one held on the user.
Permissions layered via spatie/laravel-permission roles & direct permissions. See multi-tenancy.md.
Data & Type Pipeline
- PHP DTOs (
app/Data) and Enums (app/Entities) transformed byspatie/laravel-typescript-transformer&foxbytehq/laravel-backed-enums, with project-specific customizations inapp/Transformers. - A Vite watch plugin runs
composer run transform-typeswhenapp/{Data,Entities}/**/*.phpchanges → regeneratesresources/js/types/generated.tsandresources/js/types/enumRegistry.ts. - A second watch plugin runs
npm run generate:iconswhenresources/icons/**/*.svgchanges. - DO NOT edit the generated files manually. See
types.md.
Search
laravel/scout + Meilisearch. Searchable models use the App\Traits\ShopSearchable trait so every index is shop-scoped: Client, Car, WorkOrder, Product, Service, Supplier, PurchaseOrder. Index settings live in config/scout.php. Tests force scout.driver=null.
Asynchronous & Realtime
- Queues: Redis driver managed by Horizon, running in its own
horizoncontainer in development (withspatie/laravel-horizon-watcherrestarting it on file changes). - WebSockets: Laravel Reverb (self-hosted Pusher-protocol server) +
laravel-echo&pusher-json the frontend. - Models broadcast through the
BroadcastsEventstrait onApp\Models\Model, on a per-instance channel (models.{class}.{id}) only — a class-wide channel would authorize viaviewAny()and leak records across tenants. - Events & Listeners in
app/Eventsandapp/Listenerscoordinate broadcast & persistent side-effects.
Webhooks
Inbound webhooks use spatie/laravel-webhook-client; routes in routes/webhook.php (CSRF-exempt via preventRequestForgery(except: ['webhook/*'])), handlers under app/Webhook. Normalization handled by app/Normalizers & ValueObjects for structured ingestion.
Error Responses
bootstrap/app.php renders 500/503/404/403 as the Inertia Error page outside local/testing, and turns 419 into a back() redirect with a "page expired" message.
Cross-Cutting Concerns
| Concern | Implementation |
|---|---|
| Authentication | laravel/fortify (+ Sanctum guard for API tokens) |
| Authorization | Policies + Spatie Permission |
| Validation | Form Request classes, or rules() on Actions used as controllers |
| Logging & Audit | Spatie Activity Log (via App\Traits\LogsActivity) + standard Laravel logs |
| Error Tracking | Sentry (config in config/sentry.php) |
| App Metrics | Laravel Pulse (config/pulse.php), Nightwatch |
| Caching | Redis (config in config/cache.php) |
| Search | Scout + Meilisearch (config/scout.php) |
| Media | Spatie Media Library (models implement HasMedia) |
spatie/laravel-pdf + Browsershot against the chromium container | |
| Money | App\Casts\Money — bigint cents in the DB, decimal in PHP |
| Internationalization | vue-i18n (resources/js/Locales) + lang/ + Spatie Translatable |
| Recurrence | rlanvin/php-rrule for repeating schedules |
Extensibility Principles
- Favor Actions over fat Services; Actions compose Services.
- Keep models lean (relationships, scopes, attribute casting via the
casts()method). - Use Value Objects for compound primitives.
- Prefer explicit constructor injection; avoid service locator patterns.
- Maintain testability by isolating side effects (queueing, external APIs) behind interfaces and facades.
Proceed to backend.md for deeper Laravel specifics.