PropertySpot: Property Listing Management Platform
A case study on PropertySpot, a full-stack property listing platform I built to manage real-estate listings end to end, from publishing and access control through payments and email verification, and ship it reliably with containers and a CI/CD pipeline. It runs in production at propertyspot.net, and the source is on GitHub.
Overview
PropertySpot is a multi-tenant listing platform where users publish property listings and browse others’. The requirements pushed toward a conventional, batteries-included stack rather than something bespoke:
- A relational model of users, listings, and payments.
- Per-user isolation so owners only ever see and edit their own listings.
- Paid listings via Stripe, with email verification and password recovery.
- A repeatable, container-based deploy that a pipeline could build and push.
Architecture
The application is a Laravel 8.x monolith backed by MySQL. Migrations in
database/migrations keep the schema versioned, and the test suite runs against
a containerized MySQL database so CI exercises the same engine as production.
Deployment is container-first. The production image pairs nginx with
php-fpm via Dockerfile, with Dockerfile-dev and a docker-compose-dev.yml
for local work. A Jenkinsfile drives the CI/CD pipeline: build the image, run
the test suite, and publish.
nginx + php-fpm ──► Laravel 8.x app ──► MySQL
│
Dockerfile / docker-compose.yml
│
Jenkinsfile ──► build → test → deploy
Authentication and role-based access
Auth uses Laravel’s built-in system, extended with custom middleware and
policies. Users carry a user or admin role:
CheckIfUserCanAccessListing(and acheck-usermiddleware) guarantee a user can only read or mutate their own listings. Isolation is enforced at the request boundary, not just in the UI.- Admin routes are gated by a
can:accessAdminpolicy. - Password reset tokens expire after 30 minutes, and an email-verification workflow gates full account access.
Payments and email
Listings are monetized through Stripe, with webhooks handled at
/stripe/payment-hook. Email verification, password resets, and
notifications go out over SMTP (Brevo, formerly Sendinblue) and simply log
locally in development. Profile photos upload through /users/profile-photo,
and public listing pages render at /{slug}.
Photo pipeline
Listing photos live in a dedicated image API shipped as a private Composer
package (rk-image-api) and wired into the app through its config. It exposes
token-authenticated upload, rename, delete, and reorder endpoints — up to 50
photos per listing — with per-owner middleware guarding every request. Files are
written straight to the web-served image directory rather than through a
storage abstraction, and the server rejects images below a minimum width, so
tiny or corrupt uploads never reach a listing page.
Front-end
The interactive surface is a React-powered UI layered over Blade-rendered pages, so browsing and editing feel immediate without a full SPA rewrite. Bugsnag reports errors in production so failures surface before users do.
Deployment
The whole thing ships as Docker containers fronted by nginx. The Jenkinsfile
turns each change into a built, tested, and deployed artifact, and per-environment
.env files keep credentials out of the image. The result is a platform that
builds the same way locally, in CI, and in production.