Skip to content

[EDIFIKANA] Org-wide property list + assigned-staff endpoints (#549)#556

Open
CRamsan wants to merge 7 commits into
mainfrom
cramsan/549-property-list-backend-rls
Open

[EDIFIKANA] Org-wide property list + assigned-staff endpoints (#549)#556
CRamsan wants to merge 7 commits into
mainfrom
cramsan/549-property-list-backend-rls

Conversation

@CRamsan

@CRamsan CRamsan commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Repurposes PropertyApi.getAssignedPropertiesgetProperties: now returns all properties in an organization (query-param GetPropertiesQueryParams(organizationId)), not just the ones the caller is individually mapped to via user_property_mapping. RBAC-gated at EMPLOYEE role or higher.
  • Adds EmployeeApi.getEmployeesForProperty (GetEmployeesForPropertyQueryParams(propertyId)): returns the manager/security/cleaning staff assigned to a property, RBAC-gated at MANAGER role or higher. Kept separate from the existing unfiltered getEmployees since the two have different authorization rules.
  • Both new endpoints are query-param-based generic list operations (matching the existing GetTasksQueryParams/GetDocumentsQueryParams convention) rather than single-field path params — room for more optional filters later without another redesign.
  • Threads the resulting organizationId requirement through every front-end caller of the old signature — five call sites in total, not the one originally anticipated: PropertyHomeViewModel, PropertiesOverviewViewModel/OrganizationHomeScreen, PropertyManager.removeProperty/PropertyDetailViewModel, and MyOrganizationsViewModel (whose per-org property counts now come from a per-org query instead of one cross-org query — incidentally fixes a latent undercount for orgs where the current user didn't personally create every property).
  • No RLS migration. Investigated and confirmed the backend's Postgrest client uses a shared service-role key and bypasses RLS entirely for every table's traffic — an RLS policy would add no real protection here. The RBAC checks above are the actual and sufficient enforcement.
  • Removes two pieces of dead test code found along the way: HomeViewModelTest.kt (a full duplicate of PropertyHomeViewModelTest.kt) and an unused mock in SplashViewModelTest.kt.

Demo

N/A — no visual/UI changes. The touched ViewModels change data scoping only (org-wide vs. user-assigned), not screen appearance.

References

Test plan

  • PropertyControllerTest, EmployeeControllerTest — new/updated cases for both endpoints, success + 401 paths.
  • PropertyServiceTest, EmployeeServiceTest — delegation updated for the new signatures.
  • SupabasePropertyDatastoreIntegrationTest, SupabaseEmployeeDatastoreIntegrationTest — new cross-org / cross-property isolation cases, run against a live Supabase instance (./gradlew :edifikana:back-end:integTest) — all passing.
  • PropertyHomeViewModelTest, PropertiesOverviewViewModelTest, PropertyDetailViewModelTest, MyOrganizationsViewModelTest, PropertyManagerTest, PropertyServiceImplTest — updated for the organizationId plumbing.
  • ./gradlew :edifikana:api:release :edifikana:back-end:release :edifikana:front-end:app:release --quiet — all green.

Cramsan and others added 7 commits July 22, 2026 17:16
Replaces the blanket authenticated_all_properties policy with an
org-scoped one matching the org_scoped_units/org_scoped_common_areas
pattern, closing a cross-tenant read gap on the properties table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…es; add getEmployeesForProperty (#549)

PropertyApi.getAssignedProperties becomes getProperties(organizationId),
returning all properties in an org instead of only the caller's
individually-mapped ones. EmployeeApi gains getEmployeesForProperty for
the property-detail assigned-staff feature. Both use an explicit `path`
sub-segment (by-organization / by-property) to avoid colliding with the
existing single-entity routes at the same base path, following the
precedent in CommonAreaApi.getCommonAreasForProperty.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Datastore/Service/Controller for Property now source getProperties from
the raw properties table filtered by organization_id (instead of the
v_user_properties view filtered by user_id), with an EMPLOYEE-role-or-
higher RBAC gate the old user-assignment-scoped version didn't need.
Removes the now-unused v_user_properties view usage and UserPropertyViewEntity.
Updates existing controller/service/integration tests to match, adding a
negative RBAC case and a cross-org isolation case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
)

New Datastore/Service/Controller method returning all non-deleted
employees assigned to a property, RBAC-gated at MANAGER role or higher
to match the existing single-property detail gate. Backs the
property-detail assigned-staff feature planned for the frontend slice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…st callers (#549)

Repurposing the backend's getProperties to be org-scoped breaks every
front-end consumer of PropertyService/PropertyManager.getPropertyList().
Updates all of them found while implementing this slice — three more
than the plan anticipated, since PropertiesOverviewScreen and
MyOrganizationsViewModel landed in #548 after this issue was planned:

- PropertyHomeViewModel (Properties tab) - resolves the active org via
  OrganizationManager, same pattern already used elsewhere.
- PropertiesOverviewScreen/ViewModel (Dashboard tab's Properties sub-tab)
  - now takes organizationId from OrganizationHomeScreen, which already
  had it in scope but wasn't passing it to this screen.
- PropertyManager.removeProperty - needed organizationId threaded from
  PropertyDetailViewModel (added to PropertyDetailUIState) to re-fetch
  the list after deletion.
- MyOrganizationsViewModel - previously computed per-org property counts
  from a single cross-org query; now queries per-org (matches the
  org-scoped endpoint and, incidentally, fixes an undercount for any org
  where the current user didn't personally create every property).

Also removes two pieces of dead test code discovered while fixing
compilation: HomeViewModelTest.kt (a complete duplicate of
PropertyHomeViewModelTest.kt testing the same ViewModel) and an unused
propertyManager mock in SplashViewModelTest.kt (never wired into the
ViewModel under test).

Includes the regenerated openapi.yaml reflecting the Part 1 API changes
(side effect of rebuilding after they'd already landed in an earlier
commit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The backend's Postgrest client is a single service_role-keyed singleton
(DatastoreModule.kt) shared across all datastores, with no per-request
JWT impersonation anywhere — every backend query already bypasses RLS
by design (service_role has BYPASSRLS), and the front-end has no
Postgrest client at all, so nothing ever queries these tables with a
real user JWT. The RBAC check in PropertyController.getProperties is
the actual and only enforcement for this path; the RLS policy added in
d9efc6a8f had no effect on it. Reverting to the pre-existing blanket
"authenticated_all_properties" policy, consistent with every other
backend-only table today.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per review feedback: PropertyApi.getProperties and
EmployeeApi.getEmployeesForProperty were each hard-shaped to search by
exactly one field via a path param. Redesigned both as generic
query-param-based list operations (GetPropertiesQueryParams,
GetEmployeesForPropertyQueryParams), matching the existing
Get<Entity>QueryParams convention (GetTasksQueryParams,
GetDocumentsQueryParams) — one required field today, room for more
optional filters later without another route redesign.

getProperties now lives at the bare GET /property (no custom path
needed — query params don't factor into route matching, so it doesn't
collide with getProperty's GET /property/{id}). getEmployeesForProperty
keeps its `by-property` sub-path since the existing unfiltered
getEmployees already claims GET /employee; kept it as its own endpoint
rather than merging into getEmployees since the two have different
authorization rules (unfiltered vs MANAGER+-gated) and merging would
mean query-param-dependent auth branching in one handler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@CRamsan
CRamsan force-pushed the cramsan/549-property-list-backend-rls branch from ec0f82a to 98953c1 Compare July 23, 2026 00:16
@gaaliciA1990
gaaliciA1990 self-requested a review July 23, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants