You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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
RepurposedgetAssignedProperties→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).
NewgetEmployeesForPropertyendpoint: 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.
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
getAssignedPropertiesendpoint returned properties individually mapped to the calling user viauser_property_mapping, not all properties in the org — a real behavioral gap, not just a UI one.EmployeeEntityalready hasrole: EmployeeRole(MANAGER | SECURITY | SECURITY_COVER | CLEANING) andpropertyId— 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.propertiestable's only RLS policy is a blanketUSING (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
getAssignedProperties→getProperties: now org-scoped via aGetPropertiesQueryParams(organizationId)query param instead of user-assignment-scoped, RBAC-gated atEMPLOYEErole or higher. Breaking change to existing callers (see below).getEmployeesForPropertyendpoint:GetEmployeesForPropertyQueryParams(propertyId)query param, RBAC-gated atMANAGERrole or higher (matches the existing single-property detail gate). Kept as its own endpoint rather than merged into the existing unfilteredgetEmployees, since the two have different authorization rules.Get<Entity>QueryParamsconvention already used elsewhere in this API (GetTasksQueryParams,GetDocumentsQueryParams) instead of a path param tailored to one field.Acceptance Criteria
GET /property?organization_id={organizationId}returns every non-deleted property in that org, regardless of individualuser_property_mappingassignment, 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.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, andPropertyManager.removePropertyall surfaced during implementation, on top ofPropertyHomeViewModel).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.