A real estate platform backend for agencies, agents, landlords, and clients. It handles property listings, agency and agent management, inquiries, viewing appointments, reviews, favorites, categories, subscription plans, Stripe payments, and media storage via MinIO.
- ✨ Features
- 👥 User Roles
- 🛠️ Tech Stack
- 📁 Project Structure
- 🧩 Main Modules
- 🗄️ Database
- 🔧 Environment Variables
- 🖥️ Run Locally (without Docker)
- 🐳 Run with Docker
- 🗄️ Connecting DataGrip / a DB client
- 🎨 Code Formatting
- 🔮 Future Improvements
- 🔐 JWT-based authentication (access + refresh tokens), login/logout, "me" endpoint
- 🛡️ Role-based access control (
SUPER_ADMIN,ADMIN,AGENCY_OWNER,AGENT,LANDLORD,CLIENT) - 📝 Separate registration flows for regular users and agency owners
- 📧 Password reset via email OTP
- 🏢 Agency management (profile, members, status, admin moderation)
- 👨💼 Agent management
- 🏠 Property listings: create/update/delete, media upload via MinIO, view tracking
- 🗂️ Categories, subscription plans, and agency subscriptions
- 🔖 Favorites
- 📩 Inquiries and viewing appointments
- ⭐ Reviews (property/agency), with admin moderation
- 💳 Stripe-based payments and subscription billing, with a webhook endpoint
- 🚦 Rate limiting (Bucket4j) on sensitive endpoints
- 📚 Swagger / OpenAPI documentation
- ⏰ Scheduled jobs for subscription expiration and notification emails
⬆️ Back to top
Seeded via Liquibase (db/changelog/2.0):
- SUPER_ADMIN — full platform control
- ADMIN — assistant to the platform administrator
- AGENCY_OWNER — head of an agency, manages agents/properties/inquiries for that agency
- AGENT — manages assigned properties, inquiries, and appointments
- LANDLORD — private owner listing their own property directly
- CLIENT — searches properties, saves favorites, sends inquiries, requests viewings
⬆️ Back to top
- Java 21
- Spring Boot 4 (Web, Security, Data JPA, Validation, Mail, Thymeleaf)
- Gradle (wrapper included — no local Gradle install required)
- PostgreSQL + Liquibase (SQL changesets, not XML)
- JWT (jjwt)
- MinIO (S3-compatible object storage) for property/agency/user media
- Stripe (
stripe-java) for payments and subscriptions - Bucket4j for rate limiting
- springdoc-openapi (Swagger UI)
- Spock (Groovy) + JUnit for tests
- Docker / Docker Compose
- Spotless (
google-java-format) for code formatting
⬆️ Back to top
Real-Estate-App/
├── backend/
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── .dockerignore
│ ├── .env.example
│ ├── build.gradle
│ ├── gradlew / gradlew.bat
│ └── src/main/
│ ├── java/com/realestate/backend/
│ │ ├── config/
│ │ ├── controller/
│ │ ├── dto/
│ │ ├── entity/
│ │ ├── enums/
│ │ ├── exception/
│ │ ├── mapper/
│ │ ├── payment/
│ │ ├── repository/
│ │ ├── scheduler/
│ │ ├── security/
│ │ ├── service/
│ │ ├── storage/
│ │ └── utils/
│ └── resources/
│ ├── application.yaml
│ ├── application-dev.yaml
│ ├── application-docker.yaml
│ └── db/changelog/
│ ├── 1.0/ # initial schema
│ └── 2.0/ # seed data + incremental changes
├── docs/
│ └── DOCKER_GUIDE.md
├── .github/workflows/
│ ├── lint-format.yml
│ └── deploy.yml
└── README.md
⬆️ Back to top
Controllers currently implemented:
| Area | Controller |
|---|---|
| Auth | AuthController — register (user/agency owner), login, refresh, logout, me, change/forgot/reset password, reactivate/deactivate |
| Users | UserController, AdminUserController |
| Agencies | AgencyController, AgencyMemberController, AdminAgencyController |
| Agents | AgentController |
| Properties | PropertyController, AdminPropertyController |
| Categories | CategoryController, AdminCategoryController |
| Favorites | FavoriteController |
| Inquiries | InquiryController |
| Appointments (viewings) | AppointmentController |
| Reviews | ReviewController, AdminReviewController |
| Subscriptions | SubscriptionPlanController, AdminSubscriptionController |
| Payments | PaymentController (Stripe checkout + webhook) |
⚠️ Email verification is not yet wired up. Users are created withemailVerified = falseat registration, but there's currently no endpoint or service that flips it totrue— only the password-reset OTP flow exists so far.
⬆️ Back to top
Managed entirely through Liquibase SQL changesets under backend/src/main/resources/db/changelog/. Key tables: users, roles, user_roles, agencies, agency_members, agency_media, categories, properties, property_media, property_views, favorites, inquiries, appointments, reviews, subscription_plans, agency_subscriptions, subscription_notifications, refresh_tokens, password_reset_tokens, password_reset_otp, and payment tables.
Never hand-edit an already-applied changeset — add a new one under 2.0/ (or a new version folder) instead.
⬆️ Back to top
All configuration is env-var driven — see backend/.env.example for the full list with placeholders:
SPRING_PROFILES_ACTIVE=docker # or "dev" for local (non-Docker) runs
DB_HOST=postgres # "localhost" for non-Docker local runs
DB_PORT=5432
DB_NAME=real_estate_db
DB_USERNAME=postgres
DB_PASSWORD=change-me
POSTGRES_HOST_PORT=5432
JWT_SECRET=replace-with-a-long-random-string
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-gmail-app-password
MINIO_URL=http://minio:9000 # "http://localhost:9000" for non-Docker local runs
MINIO_USERNAME=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET_NAME=estateflow
MINIO_HOST_PORT=9000
MINIO_CONSOLE_PORT=9001
STRIPE_SECRET_KEY=sk_test_replace_me
STRIPE_WEBHOOK_SECRET=whsec_replace_me
STRIPE_CURRENCY=usd
STRIPE_SUCCESS_URL=http://localhost:5173/payment/success?session_id={CHECKOUT_SESSION_ID}
STRIPE_CANCEL_URL=http://localhost:5173/payment/cancelled
BACKEND_HOST_PORT=8080
⬆️ Back to top
- Create a local PostgreSQL database matching
DB_NAMEin your.env. - From
backend/, copy the env template and fill in real values:SetCopy-Item .env.example .envSPRING_PROFILES_ACTIVE=dev,DB_HOST=localhost,MINIO_URL=http://localhost:9000. - Run the app (Windows):
macOS/Linux:
.\gradlew.bat bootRun --args='--spring.profiles.active=dev'
./gradlew bootRun --args='--spring.profiles.active=dev' - Liquibase applies all changesets automatically on startup.
Backend: http://localhost:8080
Swagger UI: http://localhost:8080/api/swagger-ui.html
⬆️ Back to top
Everything below runs from backend/, since that's where docker-compose.yml lives.
-
Copy the env template:
Copy-Item .env.example .envFill in
DB_PASSWORD,JWT_SECRET,MAIL_USERNAME/MAIL_PASSWORD,STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET. LeaveDB_HOST=postgresandMINIO_URL=http://minio:9000as-is — those are the in-network service names, notlocalhost. -
Build and start everything:
docker compose up -d --build
-
Check status / logs:
docker compose ps docker compose logs -f backend -
Stop everything (keeps data):
docker compose down
Stop and wipe all data (Postgres + MinIO volumes):
docker compose down -v --remove-orphans
What starts:
| Service | Container | Host port(s) |
|---|---|---|
| postgres | estateflow-postgres | POSTGRES_HOST_PORT (default 5432) |
| minio | estateflow-minio | MINIO_HOST_PORT (9000), MINIO_CONSOLE_PORT (9001) |
| backend | estateflow-backend | BACKEND_HOST_PORT (8080) |
Backend: http://localhost:8080/api
Swagger UI: http://localhost:8080/api/swagger-ui.html
MinIO console: http://localhost:9001 (login with MINIO_USERNAME / MINIO_SECRET_KEY)
The
minioservice usesbitnami/minio— the officialminio/minioimage stopped publishing to Docker Hub after October 2025.
Full walkthrough, troubleshooting, and the DataGrip setup below live in docs/DOCKER_GUIDE.md.
⬆️ Back to top
With the containers running:
- Host:
localhost - Port: value of
POSTGRES_HOST_PORTin your.env(default5432) - Database: value of
DB_NAME(defaultreal_estate_db) - User / Password:
DB_USERNAME/DB_PASSWORDfrom your.env
Data persists in a named Docker volume (postgres_data) across docker compose down / up cycles — only down -v clears it. See docs/DOCKER_GUIDE.md for migrating data from an existing local Postgres instance.
⬆️ Back to top
Formatting is enforced with Spotless (google-java-format) and checked in CI (.github/workflows/lint-format.yml). From backend/:
.\gradlew.bat spotlessApply
.\gradlew.bat spotlessCheckspotlessApply auto-formats the codebase; spotlessCheck only verifies formatting and is what CI runs.
⬆️ Back to top
- Email verification flow (registration currently sets
emailVerified = falsebut no confirmation endpoint exists yet) - Frontend application
- Map-based property search
- Multi-language / multi-currency support
- Saved searches, price alerts
- Real-time chat
- Advanced reporting dashboards