Enterprise admin app2025Full-stack engineer5 months
Ops Console
A multi-tenant operations console with typed API clients generated from OpenAPI and permission-aware UI, replacing three internal tools.
- React
- TanStack Query
- Spring Security
- OpenAPI
3 to 1
internal tools consolidated
0
permission incidents after launch
60fps
virtualised tables at 100k rows
The problem
Support, finance and warehouse teams each had their own tool with its own login and inconsistent data. Permission bugs leaked actions to the wrong roles.
Constraints
- Tenant isolation is non-negotiable
- Tables with 100k+ rows
- Works on old warehouse laptops
Architecture
Client
React SPA
Generated SDK
API
Spring Boot
Policy engine
Data
PostgreSQL RLS
Audit log
Key decisions
Decision 01
OpenAPI as the contract
The Spring controllers publish an OpenAPI spec in CI; the React SDK is generated from it, so a breaking API change fails the frontend build.
Trade-off: Generated code needs a thin hand-written layer for ergonomics.
Decision 02
Permissions as data, enforced twice
The server is the authority and Postgres row-level security backs it up. The UI reads the same policy to hide actions instead of disabling them.
Trade-off: Policy changes must be deployed to both layers together.
In the code
web/src/hooks/useCan.ts
1export function useCan(action: Action, resource: Resource) {2 const { data: policy } = useQuery({3 queryKey: ["policy"],4 queryFn: api.auth.getPolicy,5 staleTime: 5 * 60_000,6 });78 return useMemo(9 () => !!policy && evaluate(policy, action, resource),10 [policy, action, resource],11 );12}What I learned
- Generated clients pay for themselves the first time an API changes.