Skip to Content
Ranlanka CRM documentation — pre-release, subject to change.
Architecture

Architecture

An overview of how Ranlanka CRM is structured, for anyone extending or operating the app. For line-level internal notes, see KNOWLEDGE_BASE.md in the project root — this page is the readable summary of that file.

Repo layout

Two independent repos, developed side by side but deployed separately:

    • Spring Boot 3.5 · Java 23 backend
    • React 19 · Vite frontend

Each has its own git history, CI/CD, and pre-commit hooks — there is no shared root repo or shared package.json.

Backend

A modular monolith: every business domain is its own module under modules/* (staff, customer, booking, finance, accounting, quotation, checklist, dashboard, reference, auth), each following the same layering:

Controller → Service → Repository → Entity

Cross-cutting concerns (security, email, scheduling, file storage, logging) live in shared/* and are used by every module.

Conventions

  • IDs are UUIDs, generated by Postgres (gen_random_uuid()), never by the application.
  • Soft deletes — most entities carry a deleted_at column and a @SQLRestriction("deleted_at IS NULL") annotation, so deleted rows are filtered out of every query automatically.
  • Schema changes are migration-only — Hibernate’s DDL auto-generation is disabled (validate mode). Every schema change is a new V<n>__description.sql file under db/migration/, applied by Flyway on startup.
  • RBAC is database-backed, not a Java enum — permission strings (e.g. VISA_CREATE, INVOICE_CANCEL) live in the roles/role_permissions tables and are checked via @PreAuthorize("hasAuthority('X')").
  • Uniform API responses — every controller returns ApiResponse<T>.

Scheduled jobs

Two @Scheduled cron jobs run in-process (no external scheduler):

JobSchedulePurpose
ReminderSchedulerService0 0 8 * * * (08:00 daily)Visa-expiry, flight-today, and recurring-bill reminder emails
QuoteCleanupService0 0 3 * * * (03:00 daily)Soft-deletes quotes past their 3-day expiry

Both are stateless across restarts — the cron expression is fixed in code and all actual state (dedup logs, per-job settings) lives in Postgres, so a redeploy never loses or duplicates a scheduled run.

PDF & email rendering

No templating engine — HTML for both PDFs and emails uses plain {{PLACEHOLDER}} string substitution on .html files. PDFs are rendered via openhtmltopdf, which does not support CSS flexbox — layouts in the three PDF templates (invoice, quote, receipt) use block/table layout instead.

Frontend

React 19 + Vite, Redux Toolkit for state, Tailwind CSS v4 for styling (@theme custom properties, with a :root.dark override block for dark mode). Routing via react-router-dom.

Deployment

  • Backend ships as a Docker image (multi-stage build, non-root user), published to Docker Hub via GitHub Actions on push to main.
  • Database is Supabase-hosted Postgres — no local Postgres container in the deploy compose file.
  • Hosting is a single Oracle Cloud “Always Free” instance, deployed via a manual-trigger GitHub Actions workflow.
  • Logging ships to Grafana Cloud’s hosted Loki via an alloy shipper, toggled by a Compose profile.

See Security for the auth model and permission system.

Last updated on