david kimani
Work with me
Property · data model and authorisation

REMS Global: one data model, three audiences

An agency, its landlords and their tenants each mean something different by "my portfolio". How to give all three a view of the same records without building three products that disagree.

Updated 2 min read

View live site ↗

The problem

A real estate agency ran its listings, tenant records and maintenance requests across spreadsheets and WhatsApp groups. The same facts lived in several places and drifted apart. The landlords whose property the agency managed had no view of how that property was performing, so every question about it became a phone call to the agency.

REMS Global replaced that with one platform: the agency’s whole portfolio in one place, a self-service view for landlords, online maintenance requests for tenants, and rent statements and vacancy reports generated from the same records.

The hard part: three meanings of “my portfolio”

Three kinds of user look at the same buildings, units, leases and maintenance requests, and each means something different by “mine”.

For agency staff it is every property the agency manages. For a landlord it is the properties they own, and no other landlord’s. For a tenant it is one lease, the unit it covers, and the requests they have raised against it. The permissions differ as much as the scope. Reading a rent statement, editing a listing and raising a maintenance request are different rights, and a tenant who can raise a request must still never see the owner’s statement.

The data is shared. The views of it must not leak into each other.

One data model Properties, units, leases, maintenance requests, rent statements

  • Agency staffEvery property the agency manages
  • LandlordThe properties they own, and nobody else's
  • TenantOne lease, its unit, and the requests they raised
Three meanings of "my portfolio", read from the same rows.

Two approaches that do not work

Three products wearing one name. Build a staff app, a landlord portal and a tenant portal, each with its own controllers and its own queries for “my units”. Each one is easy to reason about alone. Together they recreate the problem the agency came with: the same facts derived in three places, drifting apart. The first time a lease changes and one portal shows the old rent, the product has three versions of the truth again, only now they are in code.

One product with checks sprinkled through it. Keep one app and hide things per role in the pages and templates. That works until somebody adds a page and forgets a check, and the failure is silent: a landlord sees another landlord’s statement and nothing errors. Authorisation that lives in the screens has to be right on every screen, forever.

The rule that follows

Scope belongs to the query, not the page. Who is asking decides which rows exist for them, and that decision is made once, close to the data, before any page sees a record. A page then asks for “units” and gets the right units for whoever is signed in, whether that is an agency manager, a landlord or a tenant.

Permissions are a separate question from scope and should stay separate. Scope answers “which records can this person see at all”. Permissions answer “what may they do with the ones they can see”. Mixing the two produces rules like “landlords can edit units, but only their own, unless the agency has locked them”, spread over three files. Kept apart, each is small enough to test exhaustively.

Done this way, three audiences cost one data model and one set of reports. A statement generated from those records shows the landlord and the agency the same numbers, because they are reading the same rows.

Stack

Laravel, Inertia and Vue 3, on PostgreSQL.