Stockel Village
Full stack rebuild of a Brussels district platform on Laravel 11 + Livewire 3 — reverse-engineered a legacy WordPress install with admin-only credentials, ported 250+ merchant records into a normalised relational schema, and shipped a scheduled membership state machine, FR/NL/EN content layer, and Horizon-backed queue pipeline behind a Laravel Nova 5 admin.
Visit site- CLIENT
- Stockel Village
Brussels, Belgium - ROLE
- Full Stack Engineer
- DURATION
- 5 months
- TEAM
- Solo
14
Nova-managed admin entities — merchants, categories, opening hours, events, cinema screenings, memberships, invoices, suspensions, locales, and media — wired through Eloquent relationships
4
Membership state transitions handled by a scheduled Horizon job — active → reminded → overdue → suspended — with idempotent monthly runs and per-merchant audit trail
3
Locales served from a single content model — FR / NL / EN — with translatable Eloquent attributes, locale-prefixed routes, and hreflang emission per merchant URL
Tech Stack
- Laravel 11
- Livewire 3
- Laravel Nova 5
- Echo
- Tailwind
- Horizon
- AlpineJs
- AWS EC2
- AWS S3
- Cloudflare
- Forge
- Envoyer
- Figma
Delivery
PHASE 01
WordPress Reverse-Engineering & Data Migration
With only WordPress admin credentials and no shell, SSH, or database access, I mapped the legacy schema by walking custom post types, ACF field groups, taxonomies, user records, and media attachments. A Laravel console command pulled records via the WP REST API and admin XML exports, normalised them, and seeded the new MySQL schema with foreign keys, slugs, and locale-aware translations preserved.
PHASE 02
Platform Rebuild — Directory, Events & Cinema
Built the public surface with Livewire 3 components for filterable directory listings, full-text search, and merchant profile pages. Le Stockel cinema lives as a sibling content type with its own Eloquent model and Nova resource — programme schedules and screenings are managed independently from the merchant directory.
PHASE 03
Membership Billing & Automated Suspension Engine
Designed a scheduled Laravel job dispatched on the first of each month that iterates registered merchants, resolves payment status, transitions a membership state machine, and queues localised reminder mail through Horizon. After three consecutive overdue cycles the listing flips to suspended and is hidden from public queries via a global Eloquent scope.
PHASE 04
Multi-Language CMS & Performance Optimisation
Implemented FR / NL / EN translatable attributes across every content model and surfaced parallel locale fields inside Nova. Tuned the directory query path with eager loading, composite indexes, and Cloudflare edge caching on read-heavy routes — sized to absorb 4× the current listing count without infrastructure changes.
Outcomes
- 250+ merchants migrated from WordPress with relational integrity preserved — categories, opening hours, media, and locale variants all survived the port.
- Directory routes serve 10,000+ monthly visitors with sustained uptime since launch, fronted by Cloudflare with Forge/Envoyer zero-downtime deploys.
- Schema, indexes, and query paths sized for 1,000 merchant listings — 4× current volume — without re-architecture.
- Membership engine runs fully unattended each month — Horizon dispatches the scheduled job, a state machine drives transitions, and a per-merchant audit log records every email and status change.
- Merchants overdue for 3+ cycles are automatically suspended via a global scope that drops them from public queries while leaving an admin reactivation path intact.
- Full FR / NL / EN translatable content across the public site and the Nova admin, with hreflang and locale-prefixed canonicals on every indexable URL.
- Real-time event publishing via Laravel Echo — once an event is approved in Nova it broadcasts to the homepage feed without a page reload.
Challenges & Learnings
No database access. Only a WordPress username and password.
There was no MySQL dump, no phpMyAdmin, no shell, and no developer handover. The only entry point was the WP admin panel, so I treated the legacy site as an opaque API and walked every section to map the schema — custom post types, ACF field groups, taxonomies, users, media, and plugin tables — before designing the Laravel target schema. A custom Artisan migration command extracted records via the WordPress REST API and admin-accessible exports, normalised them into Eloquent models, and rebuilt the foreign-key graph (merchant → category, merchant → opening hours, merchant → media). Records that were not reachable through the API were flagged for manual entry up front, so go-live had no surprise gaps.
Building a membership engine that runs itself.
The association needed monthly billing for 250+ merchants without an admin spending hours on follow-up. I modelled membership as an explicit state machine — active, reminded, overdue, suspended — with each transition recorded as a row in a `membership_events` table for auditability. A Laravel scheduled job runs on the first of every month, dispatched onto Horizon, iterates eligible merchants, and queues localised reminder mail in the merchant's preferred locale. Idempotency is keyed on (merchant_id, billing_cycle) so a re-run never double-sends. After three consecutive overdue cycles a merchant flips to suspended and a global Eloquent scope removes them from public queries, while the suspension email carries a signed reactivation URL that restores the listing without touching the admin.