Skip to content

[Backend] Org-wide property list + assigned-staff endpoint (Part 1 of 2 — #356) #549

Description

@CRamsan

Part of #356. See also #550 — the frontend slice, which depends on this issue merging first.

Goal

Give the front-end a way to fetch all properties in the caller's active organization (not just the ones the caller is individually assigned to via user_property_mapping), and a way to fetch the staff (manager/security/cleaning) assigned to a single property. Both endpoints are shaped as generic, query-param-based list operations (one required filter field today, room for more later) rather than single-field path-param lookups.

Why this is its own PR

#356 spans backend and frontend. This slice is the backend foundation; the frontend rework depends on it and is tracked as a separate, sibling issue so each PR stays reviewable on its own.

Background

  • The existing getAssignedProperties endpoint returned properties individually mapped to the calling user via user_property_mapping, not all properties in the org — a real behavioral gap, not just a UI one.
  • EmployeeEntity already has role: EmployeeRole (MANAGER | SECURITY | SECURITY_COVER | CLEANING) and propertyId — the "assigned staff" data [TASK] Update the Properties page to show all properties (Phase 3) #356 wants already existed, it just wasn't queryable by property yet.
  • RLS was investigated and intentionally not touched. The properties table's only RLS policy is a blanket USING (true) allow, and [TASK] Update the Properties page to show all properties (Phase 3) #356's Security Risk section calls out server-side org-filtering. An org-scoped RLS migration was written, then dropped after confirming the backend's Postgrest client is a single service-role-keyed singleton (DatastoreModule.kt) shared across every datastore, with no per-request JWT impersonation — service-role bypasses RLS by design, and the front-end has no Postgrest client at all. RLS is inert for every table's traffic today, not just this one. The actual and sole enforcement is the RBAC check in each controller.

Scope

  • Repurposed getAssignedProperties getProperties: now org-scoped via a GetPropertiesQueryParams(organizationId) query param instead of user-assignment-scoped, RBAC-gated at EMPLOYEE role or higher. Breaking change to existing callers (see below).
  • New getEmployeesForProperty endpoint: GetEmployeesForPropertyQueryParams(propertyId) query param, RBAC-gated at MANAGER role or higher (matches the existing single-property detail gate). Kept as its own endpoint rather than merged into the existing unfiltered getEmployees, since the two have different authorization rules.
  • Both endpoints follow the existing Get<Entity>QueryParams convention already used elsewhere in this API (GetTasksQueryParams, GetDocumentsQueryParams) instead of a path param tailored to one field.
  • No RLS migration — see Background.

Acceptance Criteria

  • GET /property?organization_id={organizationId} returns every non-deleted property in that org, regardless of individual user_property_mapping assignment, and 401s for a non-member (role < EMPLOYEE).
  • GET /employee/by-property?property_id={propertyId} returns all non-deleted employees assigned to that property for a MANAGER-or-higher caller, and 401s below that.
  • Every existing front-end caller of the old getAssignedProperties-backed method keeps compiling and functioning (now showing all org properties instead of only user-assigned ones) until the frontend slice replaces the relevant screen. This ended up being five call sites, not the one originally anticipated (PropertiesOverviewScreen, MyOrganizationsViewModel, and PropertyManager.removeProperty all surfaced during implementation, on top of PropertyHomeViewModel).
  • No existing property/employee test regresses; tests asserting the old user-assignment-scoped behavior are updated to assert org-scoping instead.

All items above are implemented and verified via unit/controller tests and green builds of the affected modules. The two new datastore integration tests are written but not executed in this environment (no live Supabase instance available) — flagged for verification before merge.

Dependencies

None blocking — this is the foundation slice.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions