Property Manager is a property rental management system built around three applications that share one MySQL database — a public booking site, a REST API, and a Vue admin SPA. It powers listings, a custom day-by-day pricing engine, overlap-aware bookings, multilingual content, and a polymorphic gallery/image pipeline behind a single villa-rental brand.
Property Manager is the platform behind a Croatian villa-rental brand. It splits cleanly into three apps that share a single MySQL database: a Livewire-rendered public booking site, a Laravel 11 REST API, and a Vue 3 + TypeScript admin SPA. Rather than syncing data over the wire, the two Laravel apps read the same Eloquent schema directly, so the public site and the admin tooling never disagree about a property, a price, or an availability window.
The interesting work lives in the domain logic. A single PricingService turns sparse per-night overrides into a full booking quote, and the same service feeds both the admin calculator and the public pricing calendar. Bookings are checked for overlaps using full datetimes drawn from each property’s check-in and check-out times, which quietly enables same-day turnover — a real-world need that trips up most naïve calendars. Content across roughly twenty entities is multilingual via Spatie Translatable, and a polymorphic gallery system with an Intervention Image pipeline handles resizing, thumbnails and per-gallery organization on upload.
The result is a deliberately boring-in-the-right-places deployment: three subdomains on shared hosting, a bash deploy script, and Larastan plus Pint keeping the Laravel side honest.
Each property has a base nightly rate, but any individual date can carry a custom price. A dedicated PricingService walks the date range night by night, applying overrides where they exist and falling back to the base rate.
Bookings check for conflicts against confirmed reservations before saving. The public booking flow uses full datetime comparison so a checkout and a new check-in can share the same day.
Titles, descriptions and SEO meta are stored as JSON and resolved per locale via Spatie Translatable. Languages are managed in the admin and exposed before login so the UI can render in the right language.
Galleries attach to properties, pages or posts via an entity_type/entity_id pair. Uploads go through FileUploadService, which resizes full-size images, generates thumbnails and organizes files into per-gallery folders.
Locations and amenities carry both an icon name and sanitized SVG markup, with Leaflet maps for placing points. Icon pickers map names to SVG paths through marker-utils files shared by the editors.
The Vue admin is built on a single useCrud composable and a generic service layer, so each of the twenty domain features reuses the same fetch/submit/delete plumbing instead of bespoke code.
The public site and the admin API are separate Laravel applications, but duplicating data access or syncing over HTTP would invite drift and latency. They needed to stay perfectly consistent.
Both Laravel apps define their own Eloquent models against the same MySQL schema and read it directly — the public app makes zero HTTP calls to the API (grep for Http:: in omniapp/app = 0). The Vue SPA is the only client that goes through the REST API, keeping write logic centralized while reads stay fast.
Nightly rates change for weekends, seasons and special dates, but storing a row for every property × every day would be wasteful and hard to keep in sync with a base rate.
Only overridden dates get a PropertyDailyPrice row; PricingService iterates the booking range with CarbonPeriod and falls back to price_per_night wherever no override exists. The same service computes the admin quote and the public pricing map, so there is one source of truth for money.
A naïve date-overlap check either blocks legitimate same-day turnovers (one guest leaves, another arrives) or lets two stays collide on shared days.
Conflict detection compares full datetimes built from each property's check_in_time / check_out_time, so a 10:00 checkout and a 14:00 check-in on the same calendar day don't conflict, while genuine overlaps on confirmed bookings are rejected before save.
The admin SPA and public site live on different subdomains but must share a session-cookie auth backed by the API, on shared hosting where document roots can't be freely moved.
Sanctum is configured with stateful domains and a parent SESSION_DOMAIN (.villasmarinovic.com), with the public language and settings endpoints deliberately left unauthenticated so the UI can bootstrap before login. A bash deploy script wires each app to its own document root and patches index.php paths.