initial commit
This commit is contained in:
335
docs/frontend-api-surface.md
Normal file
335
docs/frontend-api-surface.md
Normal file
@ -0,0 +1,335 @@
|
||||
# Frontend API Surface (Backend: Current Spring Boot)
|
||||
|
||||
Use this as the exact frontend integration reference for the existing backend.
|
||||
|
||||
## 1) API Envelope
|
||||
|
||||
Most responses use:
|
||||
|
||||
```ts
|
||||
type ApiResponse<T> = {
|
||||
success: boolean
|
||||
message: string
|
||||
data: T
|
||||
timestamp: string
|
||||
}
|
||||
```
|
||||
|
||||
### Error policy (actual backend behavior)
|
||||
- Business errors and validation in request payloads return HTTP `400` with:
|
||||
- `{ success: false, message: "...", data: null, timestamp: "..." }`
|
||||
- Authorization failures return HTTP `403`:
|
||||
- `{ success: false, message: "Access denied", data: null, timestamp: "..." }`
|
||||
- Unhandled internal exceptions return HTTP `500` with:
|
||||
- `{ success: false, message: "Internal server error", data: null, timestamp: "..." }`
|
||||
- Authentication failures from Spring Security typically return `401` and are handled by frontend interceptor.
|
||||
- JWT/session validation/blacklist failures can return `401` or be handled by security filters before controller.
|
||||
|
||||
## 2) Global request headers
|
||||
|
||||
For **every protected request** after login:
|
||||
- `Authorization: Bearer <accessToken>`
|
||||
- `X-Tenant-Id: <tenantId>`
|
||||
- Optional: `Accept-Language: en-US` or `id-ID`
|
||||
|
||||
`POST /api/auth/login` also requires:
|
||||
- `X-Tenant-Id`
|
||||
|
||||
## 3) Auth APIs
|
||||
|
||||
### POST `/api/auth/login`
|
||||
|
||||
Request:
|
||||
|
||||
```ts
|
||||
{ username: string; password: string }
|
||||
```
|
||||
|
||||
Success (`200`):
|
||||
|
||||
```ts
|
||||
{
|
||||
"success": true,
|
||||
"message": "Login successful",
|
||||
"data": {
|
||||
"tokenType": "Bearer",
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
|
||||
"refreshToken": "...",
|
||||
"expiresInSeconds": 900
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Common login failures:
|
||||
- `401`: invalid credentials from security layer
|
||||
- `400`: `{ message: "Invalid username or password" }`
|
||||
- `400`: `{ message: "Account locked. Please try again in {0} seconds" }`
|
||||
- LDAP mode + not provisioned local tenant user: `{ message: "LDAP user authenticated but not provisioned in this tenant" }`
|
||||
|
||||
### POST `/api/auth/refresh`
|
||||
|
||||
Request:
|
||||
|
||||
```ts
|
||||
{ refreshToken: string }
|
||||
```
|
||||
|
||||
Success:
|
||||
|
||||
```ts
|
||||
{
|
||||
"success": true,
|
||||
"message": "Token refreshed successfully",
|
||||
"data": {
|
||||
"tokenType": "Bearer",
|
||||
"accessToken": "...",
|
||||
"refreshToken": "...",
|
||||
"expiresInSeconds": 900
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Failure:
|
||||
- `400` with message `Refresh token not found` / `Token expired or revoked`
|
||||
|
||||
### POST `/api/auth/logout`
|
||||
|
||||
Request headers:
|
||||
- `Authorization` and `X-Tenant-Id`
|
||||
- optional body
|
||||
|
||||
Success:
|
||||
|
||||
```ts
|
||||
{ "success": true, "message": "Logout successful", "data": null }
|
||||
```
|
||||
|
||||
## 4) Profile API
|
||||
|
||||
### GET `/api/users/me`
|
||||
|
||||
Success:
|
||||
|
||||
```ts
|
||||
{
|
||||
"success": true,
|
||||
"message": "Current user fetched successfully",
|
||||
"data": {
|
||||
"tenantId": "acme",
|
||||
"username": "alice",
|
||||
"roles": ["ADMIN", "USER_ROLE_ADMIN"],
|
||||
"permissions": ["USER_MANAGE", "WORKFLOW_APPROVE", "ROLE_MANAGE"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `roles` and `permissions` for:
|
||||
- menu visibility
|
||||
- action visibility
|
||||
- route guards
|
||||
|
||||
## 5) Tenant APIs
|
||||
|
||||
### GET `/api/tenant/context`
|
||||
|
||||
```ts
|
||||
{ tenantId: "acme" }
|
||||
```
|
||||
|
||||
## 6) User Management APIs (Workflow-first)
|
||||
|
||||
### POST `/api/users/management/requests/create`
|
||||
|
||||
#### Local mode (default)
|
||||
```ts
|
||||
{
|
||||
username: string,
|
||||
password: string, // required local mode
|
||||
enabled?: boolean,
|
||||
roleCodes: string[]
|
||||
}
|
||||
```
|
||||
|
||||
#### LDAP mode
|
||||
```ts
|
||||
{
|
||||
username: string,
|
||||
ldapDn?: string, // optional metadata
|
||||
enabled?: boolean,
|
||||
roleCodes: string[]
|
||||
}
|
||||
```
|
||||
|
||||
Success always returns workflow request:
|
||||
|
||||
```ts
|
||||
{
|
||||
"success": true,
|
||||
"message": "User management request created",
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"resourceType": "USER_MANAGEMENT",
|
||||
"resourceId": "jane",
|
||||
"status": "PENDING",
|
||||
"requiredSteps": 1,
|
||||
"currentStep": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/users/management/requests/update-roles`
|
||||
|
||||
```ts
|
||||
{ username: string; roleCodes: string[] }
|
||||
```
|
||||
|
||||
Returns same response shape as approval response.
|
||||
|
||||
## 7) Role Management APIs (Workflow-first)
|
||||
|
||||
### POST `/api/roles/management/requests/create`
|
||||
|
||||
```ts
|
||||
{ code: string; name: string; permissionCodes: string[] }
|
||||
```
|
||||
|
||||
### POST `/api/roles/management/requests/update-permissions`
|
||||
|
||||
```ts
|
||||
{ code: string; permissionCodes: string[] }
|
||||
```
|
||||
|
||||
Both return `ApprovalResponse` with request status PENDING.
|
||||
|
||||
## 8) Workflow APIs
|
||||
|
||||
### POST `/api/workflow/request`
|
||||
|
||||
Generic endpoint for custom workflow request:
|
||||
|
||||
```ts
|
||||
{
|
||||
resourceType: string,
|
||||
resourceId: string,
|
||||
payload: string,
|
||||
requiredSteps: number
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/workflow/requests`
|
||||
|
||||
Query params:
|
||||
- `status` = `DRAFT|PENDING|APPROVED|REJECTED` (optional)
|
||||
- `resourceType` (optional)
|
||||
- `makerUsername` (optional)
|
||||
- `limit` default `50`, max internally clamped to `200`
|
||||
|
||||
Response list item:
|
||||
|
||||
```ts
|
||||
{
|
||||
id: "uuid",
|
||||
tenantId: "acme",
|
||||
resourceType: "USER_MANAGEMENT",
|
||||
resourceId: "jane",
|
||||
makerUsername: "alice",
|
||||
payload: "{\"operation\":\"CREATE_USER\",...}",
|
||||
status: "PENDING",
|
||||
requiredSteps: 1,
|
||||
currentStep: 0,
|
||||
createdAt: "2026-04-20T08:00:00Z",
|
||||
updatedAt: "2026-04-20T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/workflow/{id}/approve`
|
||||
|
||||
```ts
|
||||
{ notes?: string; checkerRole?: string }
|
||||
```
|
||||
|
||||
- If `checkerRole` omitted, backend uses step role default (`CHECKER` unless overridden by system default).
|
||||
- Maker cannot approve own request.
|
||||
- Success returns updated `ApprovalResponse`.
|
||||
|
||||
### POST `/api/workflow/{id}/reject`
|
||||
|
||||
```ts
|
||||
{ notes?: string; checkerRole?: string }
|
||||
```
|
||||
|
||||
Same behavior and guards as approve.
|
||||
|
||||
## 9) Module APIs (Admin only)
|
||||
|
||||
### GET `/api/modules`
|
||||
|
||||
Returns:
|
||||
|
||||
```ts
|
||||
{ code: string; name: string; enabled: boolean }[]
|
||||
```
|
||||
|
||||
### POST `/api/modules/{code}/toggle`
|
||||
|
||||
```ts
|
||||
{ enabled: boolean }
|
||||
```
|
||||
|
||||
Requires admin role.
|
||||
|
||||
## 10) Audit APIs (Admin only)
|
||||
|
||||
### GET `/api/audit?limit=50`
|
||||
|
||||
Response items include at least:
|
||||
- `id`, `tenantId`, `actor`, `correlationId`, `action`, `domain`, `resourceType`, `resourceId`, `outcome`, `httpMethod`, `requestPath`, `beforeState`, `afterState`, `details`, `createdAt`.
|
||||
|
||||
Used for security/auditor trail and troubleshooting.
|
||||
|
||||
## 11) Statuses and RBAC to gate UI
|
||||
|
||||
- Approval status from backend: `DRAFT`, `PENDING`, `APPROVED`, `REJECTED`.
|
||||
- User create / update role actions: `USER_MANAGE` OR `USER_ROLE_ADMIN`
|
||||
- Role create / update permissions: `ROLE_MANAGE` OR `USER_ROLE_ADMIN`
|
||||
- Workflow approve/reject: `WORKFLOW_APPROVE` OR `CHECKER`
|
||||
- Workflow list: `WORKFLOW_APPROVE` OR `CHECKER` OR `ADMIN`
|
||||
- Audit & modules listing/toggle: `ADMIN`
|
||||
- Profile (`/api/users/me`): `USER_READ` OR `ADMIN`
|
||||
|
||||
## 12) Frontend negative-path checklist (QA-ready)
|
||||
|
||||
1. Login without tenant header should fail on protected flows.
|
||||
2. Login with valid credentials but wrong tenant should fail on tenant-dependent services.
|
||||
3. Repeated wrong password:
|
||||
- eventually returns lockout message after configured threshold.
|
||||
4. Create user (local mode) without password -> shows localized required validation error.
|
||||
5. Create user (LDAP mode) with password payload should still be accepted by UI only if intentionally sent; backend ignores and should not rely on it.
|
||||
6. Create user request duplicate username returns `400 User already exists`.
|
||||
7. Workflow approve/reject where maker == checker returns error message `Maker cannot approve own request`.
|
||||
8. Approving/rejecting without proper role returns `403`.
|
||||
9. Audit API called by non-admin should return `403`.
|
||||
10. Refresh with invalid token returns `400` and clear token state.
|
||||
|
||||
## 13) Suggested QA smoke script
|
||||
|
||||
- Validate auth:
|
||||
- login, refresh, me, logout
|
||||
- Validate tenant:
|
||||
- switch tenant header and ensure data partitions by tenant
|
||||
- Validate management flow:
|
||||
- create user (local/LDAP variant) -> should appear in workflow as `PENDING`
|
||||
- role create -> approval created
|
||||
- approve/reject -> state transition to `APPROVED/REJECTED`
|
||||
- Validate guard:
|
||||
- hide actions by permissions and re-check with token from restricted user
|
||||
|
||||
## 14) Setup checklist
|
||||
|
||||
- Create `.env.local`:
|
||||
- `NEXT_PUBLIC_API_BASE_URL=http://localhost:9191`
|
||||
- `NEXT_PUBLIC_DEFAULT_TENANT=acme`
|
||||
- `NEXT_PUBLIC_LOCALE=en`
|
||||
- npm install / pnpm install
|
||||
- Add Axios interceptor for auth and tenant headers
|
||||
- Add 401/403 interceptor handling for logout and route redirect
|
||||
299
docs/frontend-initial-prompt.md
Normal file
299
docs/frontend-initial-prompt.md
Normal file
@ -0,0 +1,299 @@
|
||||
You are a senior frontend engineer. Generate a **production-ready Next.js (App Router) admin dashboard** using **Tabler UI** as the design system.
|
||||
|
||||
Use this prompt as the current source of truth for backend behavior.
|
||||
|
||||
## Backend Summary (Current)
|
||||
|
||||
- Base URL: `http://<host>` (Swagger: `/swagger-ui.html`, OpenAPI: `/v3/api-docs`)
|
||||
- Security: JWT (Bearer token)
|
||||
- Multi-tenancy: required via header `X-Tenant-Id`
|
||||
- Optional LDAP mode: `app.ldap.enabled` (backend switch)
|
||||
- API pattern: most state-changing endpoints are workflow-driven approvals
|
||||
- Default responses use:
|
||||
|
||||
```ts
|
||||
type ApiResponse<T> = {
|
||||
success: boolean
|
||||
message: string
|
||||
data: T
|
||||
timestamp: string
|
||||
}
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Next.js (App Router)
|
||||
- React 18+
|
||||
- TypeScript
|
||||
- Tabler UI (CSS/React components)
|
||||
- Axios
|
||||
- Zustand (recommended)
|
||||
- Tailwind (optional only for utility overrides)
|
||||
- react-intl or next-intl for i18n
|
||||
|
||||
## Recommended Project Structure
|
||||
|
||||
- `app/`
|
||||
- `(auth)/login/page.tsx`
|
||||
- `(dashboard)/layout.tsx`
|
||||
- `(dashboard)/page.tsx`
|
||||
- `api-proxy/` or service barrel exports
|
||||
- `components/`
|
||||
- `layout/` (DashboardShell, Sidebar, Header)
|
||||
- `ui/` (Table, Form, Modal, Alert, Badge, Drawer)
|
||||
- `workflow/` (ApprovalTable, StatusBadge, ApprovalActionModal)
|
||||
- `user/` (UserForm, UpdateRolesForm)
|
||||
- `role/` (RoleForm, RolePermissionForm)
|
||||
- `services/`
|
||||
- `api.ts`
|
||||
- `auth.ts`
|
||||
- `users.ts`
|
||||
- `workflow.ts`
|
||||
- `tenant.ts`
|
||||
- `audit.ts`
|
||||
- `store/`
|
||||
- `authStore.ts`
|
||||
- `uiStore.ts`
|
||||
- `tenantStore.ts`
|
||||
- `permissionStore.ts`
|
||||
- `hooks/`
|
||||
- `useAuth.ts`, `useTenantHeader.ts`, `useApi.ts`, `usePermissions.ts`
|
||||
- `types/`
|
||||
- API contracts and DTO types
|
||||
|
||||
## API Endpoints to Use (exact)
|
||||
|
||||
### Auth
|
||||
- `POST /api/auth/login`
|
||||
- `POST /api/auth/refresh`
|
||||
- `POST /api/auth/logout`
|
||||
|
||||
### Authenticated profile
|
||||
- `GET /api/users/me`
|
||||
|
||||
### User management (workflow requests)
|
||||
- `POST /api/users/management/requests/create`
|
||||
- `POST /api/users/management/requests/update-roles`
|
||||
|
||||
### Role management (workflow requests)
|
||||
- `POST /api/roles/management/requests/create`
|
||||
- `POST /api/roles/management/requests/update-permissions`
|
||||
|
||||
### Workflow
|
||||
- `POST /api/workflow/request`
|
||||
- `POST /api/workflow/{id}/approve`
|
||||
- `POST /api/workflow/{id}/reject`
|
||||
- `GET /api/workflow/requests?status=PENDING&resourceType=...&makerUsername=...&limit=50`
|
||||
|
||||
### Modules
|
||||
- `GET /api/modules`
|
||||
- `POST /api/modules/{code}/toggle`
|
||||
|
||||
### Tenant & audit
|
||||
- `GET /api/tenant/context`
|
||||
- `GET /api/audit?limit=50`
|
||||
|
||||
## Authentication and request headers
|
||||
|
||||
For **every request** after login:
|
||||
- `Authorization: Bearer <accessToken>`
|
||||
- `X-Tenant-Id: <tenantId>`
|
||||
|
||||
Login request also requires tenant context because backend resolves tenant at auth time:
|
||||
- `POST /api/auth/login` with header `X-Tenant-Id`
|
||||
|
||||
Logout request behavior:
|
||||
- `POST /api/auth/logout` requires a valid Bearer token because backend invalidates/revokes refresh/session context.
|
||||
|
||||
Optional:
|
||||
- `Accept-Language: en-US` or `id-ID`
|
||||
|
||||
## JWT/session behavior
|
||||
|
||||
- Access token in response includes `tokenType: "Bearer"`, `accessToken`, `refreshToken`, `expiresIn`
|
||||
- Store tokens in secure storage strategy (HTTP-only cookies preferred if possible; otherwise memory + storage hardening)
|
||||
- Add request interceptor to attach token and `X-Tenant-Id`
|
||||
- Add response interceptor for 401:
|
||||
- clear auth state
|
||||
- redirect to login
|
||||
- keep tenant and locale selections persisted
|
||||
|
||||
## Important authorization model
|
||||
|
||||
Backend sends authorities as roles/permissions:
|
||||
- Roles come as `ROLE_<code>` (from DB role code)
|
||||
- Permissions come as plain `...` codes
|
||||
- Controllers currently check:
|
||||
- User create/update-roles: `hasAuthority('USER_MANAGE') or hasRole('USER_ROLE_ADMIN')`
|
||||
- Role create/update-permissions: `hasAuthority('ROLE_MANAGE') or hasRole('USER_ROLE_ADMIN')`
|
||||
- Create workflow request: `hasAuthority('WORKFLOW_CREATE') or hasRole('MAKER')`
|
||||
- Approve/reject: `hasAuthority('WORKFLOW_APPROVE') or hasRole('CHECKER')`
|
||||
- Workflow list: `hasAuthority('WORKFLOW_APPROVE') or hasRole('CHECKER') or hasRole('ADMIN')`
|
||||
- `/api/audit`: `hasRole('ADMIN')`
|
||||
- `/api/users/me`: `hasAuthority('USER_READ') or hasRole('ADMIN')`
|
||||
|
||||
So frontend should render actions conditionally using permissions derived from `/api/users/me`.
|
||||
|
||||
## LDAP mode alignment
|
||||
|
||||
Backend has optional LDAP mode (`app.ldap.enabled`).
|
||||
|
||||
- **Local mode**
|
||||
- `/api/users/management/requests/create` requires `password`
|
||||
- **LDAP mode**
|
||||
- Password is managed in directory (backend does not require password for user provisioning)
|
||||
- `password` should not be sent for user creation
|
||||
- optional `ldapDn` may be included
|
||||
- Common for both modes
|
||||
- user update roles still workflow-driven
|
||||
- role create/update-permissions still workflow-driven
|
||||
- no direct mutation endpoints for user/role entities
|
||||
|
||||
## Required front-end behavior by page
|
||||
|
||||
### 1) Login page
|
||||
- Input: username, password, tenant selector
|
||||
- Submit `POST /api/auth/login`
|
||||
- Pass `X-Tenant-Id` header
|
||||
- Handle error responses from backend localization keys and lockout messages
|
||||
|
||||
### 2) Dashboard shell
|
||||
- Sidebar: Dashboard, Users, Roles, Workflow, Audit, Modules, Settings
|
||||
- Top bar: tenant selector, locale switch, user menu/logout
|
||||
- Display auth mode indicator (Local / LDAP) when available
|
||||
|
||||
### 3) Dashboard home
|
||||
- Show summary cards:
|
||||
- pending workflow count
|
||||
- pending checker workload (from `/api/workflow/requests?status=PENDING`)
|
||||
- audit/approval health snapshots (from `/api/audit?limit=50`)
|
||||
- recent audits (from /api/audit)
|
||||
|
||||
### 4) Users page
|
||||
- There is no direct `/api/users` list endpoint in current backend, so derive list/context from workflow/request history and `/api/users/me` context.
|
||||
- Actions:
|
||||
- create user request (workflow)
|
||||
- update user roles request (workflow)
|
||||
- In LDAP mode hide password input on create form
|
||||
- In local mode enforce password validation before submit
|
||||
|
||||
### 5) Roles page
|
||||
- No direct role list endpoint exists in current backend; show role/permission operations using current user context and workflow history as available.
|
||||
- Implement create role request + permission update request flows.
|
||||
- Permission selector from current in-app permission catalog (from `/api/users/me`, seeded defaults, and known workflow operations).
|
||||
|
||||
### 6) Workflow page
|
||||
- Show `/api/workflow/requests` with filters
|
||||
- `status` (`DRAFT`, `PENDING`, `APPROVED`, `REJECTED`)
|
||||
- `resourceType`
|
||||
- `makerUsername`
|
||||
- `limit`
|
||||
- Actions:
|
||||
- Approve modal
|
||||
- Reject modal
|
||||
- show notes and optional checkerRole (if omitted, backend uses step role default `CHECKER`)
|
||||
|
||||
### 7) Audit page
|
||||
- Admin-only
|
||||
- `GET /api/audit?limit=50`
|
||||
- render `action`, `resourceType`, `resourceId`, before/after snapshots, outcome, correlation id
|
||||
- Keep pagination/infinite-load support for audit + workflow lists.
|
||||
|
||||
## DTO references for implementation
|
||||
|
||||
### Login
|
||||
```ts
|
||||
{ username: string; password: string }
|
||||
```
|
||||
|
||||
### Create user management request
|
||||
- Local mode:
|
||||
```ts
|
||||
{
|
||||
username: string
|
||||
password: string
|
||||
enabled?: boolean
|
||||
roleCodes: string[]
|
||||
}
|
||||
```
|
||||
- LDAP mode:
|
||||
```ts
|
||||
{
|
||||
username: string
|
||||
ldapDn?: string
|
||||
enabled?: boolean
|
||||
roleCodes: string[]
|
||||
}
|
||||
```
|
||||
|
||||
### Update user roles
|
||||
```ts
|
||||
{ username: string; roleCodes: string[] }
|
||||
```
|
||||
|
||||
### Create role request
|
||||
```ts
|
||||
{ code: string; name: string; permissionCodes: string[] }
|
||||
```
|
||||
|
||||
### Update role permissions
|
||||
```ts
|
||||
{ code: string; permissionCodes: string[] }
|
||||
```
|
||||
|
||||
### Workflow action
|
||||
```ts
|
||||
{ notes?: string; checkerRole?: string }
|
||||
```
|
||||
|
||||
### Create approval request (generic)
|
||||
```ts
|
||||
{ resourceType: string; resourceId: string; payload?: string; requiredSteps: number }
|
||||
```
|
||||
|
||||
### Response from `/api/users/me`
|
||||
```ts
|
||||
{ tenantId: string; username: string; roles: string[]; permissions: string[] }
|
||||
```
|
||||
|
||||
## UI requirements
|
||||
|
||||
- Use Tabler-inspired components for
|
||||
- tables
|
||||
- forms
|
||||
- modals
|
||||
- badges
|
||||
- alerts
|
||||
- Keep navigation corporate and simple
|
||||
- Add loading states, inline error states, and toast notifications
|
||||
- Keep table columns configurable (search, sort, pagination)
|
||||
|
||||
## Error handling
|
||||
|
||||
Backend may return these patterns:
|
||||
- Login failures with localized message
|
||||
- Lockout message key in i18n when brute force threshold exceeded
|
||||
- Standard `ApiResponse` with `success` false
|
||||
|
||||
Frontend should:
|
||||
- show notification from `message`
|
||||
- maintain tenant context in state across page refresh/login switch
|
||||
- keep unauthorized navigation blocked by RBAC-derived route guards
|
||||
|
||||
## Delivery expectations
|
||||
|
||||
Please generate runnable code for:
|
||||
- `app/` shell and route layout
|
||||
- Axios client with interceptors
|
||||
- Login/auth flow
|
||||
- Tenant-aware request wrapper
|
||||
- Users module screens + workflow request forms
|
||||
- Roles module screens + workflow request forms
|
||||
- Workflow list/detail with approve/reject action
|
||||
- Audit list
|
||||
- Reusable table/form/modal components
|
||||
|
||||
Please include a short setup checklist:
|
||||
- env vars (`NEXT_PUBLIC_API_BASE_URL` etc)
|
||||
- install commands
|
||||
- run instructions
|
||||
417
docs/sequence-diagrams.md
Normal file
417
docs/sequence-diagrams.md
Normal file
@ -0,0 +1,417 @@
|
||||
# Controller Sequence Diagrams
|
||||
|
||||
All diagrams are in Mermaid syntax (` ```mermaid `) and can be rendered by GitHub, IntelliJ, VS Code Mermaid extensions, and most markdown tools.
|
||||
|
||||
## 1) AuthController (`/api/auth`)
|
||||
|
||||
### 1.1 POST `/api/auth/login`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant AC as AuthController
|
||||
participant AF as AuthService
|
||||
participant TF as TenantFilter
|
||||
participant TS as TenantService
|
||||
participant LT as LoginThrottleService
|
||||
participant AM as AuthenticationManager
|
||||
participant JR as JwtService
|
||||
participant UR as UserRepository
|
||||
participant RTR as RefreshTokenRepository
|
||||
|
||||
Client->>AC: POST /api/auth/login {tenant, username, password}
|
||||
AC->>AC: MessageResolver message key
|
||||
AC-->>TF: tenant header (X-Tenant-Id)
|
||||
TF->>TF: TenantContext.setTenantId(tenant)
|
||||
|
||||
AC->>AF: login(request)
|
||||
AF->>TS: getActiveTenant(tenantId)
|
||||
TS-->>AF: tenant entity
|
||||
AF->>LT: ensureAllowed(tenantId, username)
|
||||
alt account locked
|
||||
LT-->>AF: AppException(auth.login.locked)
|
||||
else allowed
|
||||
AF->>AM: authenticate(UsernamePasswordAuthenticationToken)
|
||||
alt invalid credential
|
||||
AM-->>AF: AuthenticationException
|
||||
AF->>LT: recordFailure(tenantId, username)
|
||||
AF-->>AC: throw AppException(auth.invalid.credentials)
|
||||
AC-->>Client: 400 Error via GlobalExceptionHandler
|
||||
else success
|
||||
AF->>LT: recordSuccess(tenantId, username)
|
||||
AF->>UR: findByTenantIdAndUsername
|
||||
UR-->>AF: User
|
||||
AF->>AF: build UserPrincipal
|
||||
AF->>JR: generateAccessToken(principal)
|
||||
AF->>JR: generateRefreshToken(principal)
|
||||
AF->>RTR: delete old refresh tokens
|
||||
AF->>RTR: save RefreshToken
|
||||
AF-->>AC: AuthTokenResponse(Bearer, access, refresh)
|
||||
AC-->>Client: 200 {success:true, message, token}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 1.2 POST `/api/auth/refresh`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant AF as AuthService
|
||||
participant AC as AuthController
|
||||
participant TF as TenantFilter
|
||||
participant RTR as RefreshTokenRepository
|
||||
participant JR as JwtService
|
||||
participant UR as UserRepository
|
||||
|
||||
Client->>AC: POST /api/auth/refresh {tenant, refreshToken}
|
||||
AC-->>TF: resolve tenant header
|
||||
AC->>AF: refresh(request)
|
||||
AF->>AF: TenantContext.getRequiredTenantId
|
||||
AF->>RTR: findByTokenAndTenantId(refreshToken, tenantId)
|
||||
alt token not found
|
||||
RTR-->>AF: empty
|
||||
AF-->>AC: AppException("Refresh token not found")
|
||||
AC-->>Client: 400 via GlobalExceptionHandler
|
||||
else token found
|
||||
AF->>AF: validate revoked/expired
|
||||
AF->>JR: parseClaims(refreshToken)
|
||||
AF->>UR: findByTenantIdAndUsername(claims.sub)
|
||||
AF->>JR: generateAccessToken(principal)
|
||||
AF-->>AC: AuthTokenResponse(new access token)
|
||||
AC-->>Client: 200 {success:true, message, token}
|
||||
end
|
||||
```
|
||||
|
||||
### 1.3 POST `/api/auth/logout`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant AC as AuthController
|
||||
participant SE as Spring Security
|
||||
participant AF as AuthService
|
||||
participant TBS as TokenBlacklistService
|
||||
participant JR as JwtService
|
||||
|
||||
Client->>AC: POST /api/auth/logout Authorization: Bearer <token>
|
||||
AC->>SE: isAuthenticated() method security
|
||||
alt unauthenticated
|
||||
SE-->>Client: 401/403
|
||||
else authenticated
|
||||
AC->>AF: logout(bearer token)
|
||||
alt token missing/blank
|
||||
AF->>AF: return
|
||||
else token present
|
||||
AF->>JR: parseClaims(access token)
|
||||
AF->>AF: compute ttl from exp
|
||||
AF->>TBS: blacklist(token, ttl)
|
||||
end
|
||||
AF-->>AC: void
|
||||
AC-->>Client: 200 {success:true, logout message}
|
||||
end
|
||||
```
|
||||
|
||||
## 2) WorkflowController (`/api/workflow`)
|
||||
|
||||
### 2.1 POST `/api/workflow/request`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant WC as ApprovalWorkflowController
|
||||
participant AFS as ApprovalWorkflowService
|
||||
participant TS as TenantService
|
||||
participant AR as ApprovalRequestRepository
|
||||
participant ASR as ApprovalStepRepository
|
||||
participant AH as ApprovalHistoryRepository
|
||||
participant AT as AuditTrailService
|
||||
|
||||
Client->>WC: POST /api/workflow/request (workflow payload)
|
||||
WC->>SC: hasAuthority('WORKFLOW_CREATE') OR hasRole('MAKER')
|
||||
alt auth failed
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
WC->>AFS: createRequest(dto, servletRequest)
|
||||
AFS->>TS: getActiveTenant(tenantId)
|
||||
AFS->>AFS: resolve maker + checkerRole default
|
||||
AFS->>AR: save ApprovalRequest(PENDING, status=0)
|
||||
loop for each requiredSteps
|
||||
AFS->>ASR: save ApprovalStep(stepOrder, CHECKER role)
|
||||
end
|
||||
AFS->>AH: addHistory(action=CREATE)
|
||||
AFS->>AT: record(ACTION_CREATE, before=null, after=snapshot)
|
||||
AFS-->>WC: ApprovalResponse
|
||||
WC-->>Client: 200 workflow.request.created
|
||||
end
|
||||
```
|
||||
|
||||
### 2.2 POST `/api/workflow/{id}/approve`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant WC as ApprovalWorkflowController
|
||||
participant AFS as ApprovalWorkflowService
|
||||
participant AR as ApprovalRequestRepository
|
||||
participant ASR as ApprovalStepRepository
|
||||
participant AH as ApprovalHistoryRepository
|
||||
participant EVT as ApprovalEventProducer
|
||||
participant AT as AuditTrailService
|
||||
|
||||
Client->>WC: POST /api/workflow/{id}/approve (action notes)
|
||||
WC->>SC: hasAuthority('WORKFLOW_APPROVE') OR hasRole('CHECKER')
|
||||
alt auth failed
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
WC->>AFS: approve(id, dto, auth, servletRequest)
|
||||
AFS->>AR: findByIdAndTenantId(id, tenantId)
|
||||
alt request not found or not pending
|
||||
AFS-->>WC: AppException
|
||||
WC-->>Client: 400 via handler
|
||||
else valid
|
||||
AFS->>ASR: find current step
|
||||
AFS->>SC: validateCheckerRole(auth, expectedRole)
|
||||
AFS->>ASR: save step status=APPROVED
|
||||
AFS->>AR: update currentStep
|
||||
alt all steps completed
|
||||
AFS->>AR: set request status=APPROVED
|
||||
AFS->>EVT: publishCompleted(ApprovalCompletedEvent)
|
||||
end
|
||||
AFS->>AH: addHistory(action=APPROVE)
|
||||
AFS->>AT: record(ACTION_APPROVE, before/after states)
|
||||
AFS-->>WC: ApprovalResponse
|
||||
WC-->>Client: 200 workflow.request.approved
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 2.3 POST `/api/workflow/{id}/reject`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant WC as ApprovalWorkflowController
|
||||
participant AFS as ApprovalWorkflowService
|
||||
participant AR as ApprovalRequestRepository
|
||||
participant ASR as ApprovalStepRepository
|
||||
participant AH as ApprovalHistoryRepository
|
||||
participant AT as AuditTrailService
|
||||
|
||||
Client->>WC: POST /api/workflow/{id}/reject (action notes)
|
||||
WC->>SC: hasAuthority('WORKFLOW_APPROVE') OR hasRole('CHECKER')
|
||||
alt auth failed
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
WC->>AFS: reject(id, dto, auth, servletRequest)
|
||||
AFS->>AR: findByIdAndTenantId(id, tenantId)
|
||||
alt request not found or not pending
|
||||
AFS-->>WC: AppException
|
||||
WC-->>Client: 400 via handler
|
||||
else valid
|
||||
AFS->>ASR: find current step
|
||||
AFS->>SC: validateCheckerRole(auth, expectedRole)
|
||||
AFS->>ASR: save step status=REJECTED
|
||||
AFS->>AR: set request status=REJECTED
|
||||
AFS->>AH: addHistory(action=REJECT)
|
||||
AFS->>AT: record(ACTION_REJECT, before/after states)
|
||||
AFS-->>WC: ApprovalResponse
|
||||
WC-->>Client: 200 workflow.request.rejected
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## 3) UserController (`/api/users`)
|
||||
|
||||
### 3.1 GET `/api/users/me`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Security Filter + Method Security
|
||||
participant UC as UserController
|
||||
participant US as UserService
|
||||
participant UR as UserRepository
|
||||
|
||||
Client->>UC: GET /api/users/me
|
||||
UC->>SC: hasAuthority('USER_READ') OR hasRole('ADMIN')
|
||||
alt unauthorized
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
UC->>US: me(authentication.username)
|
||||
US->>UR: findByTenantIdAndUsername
|
||||
UR-->>US: User
|
||||
US-->>UC: CurrentUserResponse(roles, permissions)
|
||||
UC-->>Client: 200 {tenantId, user details}
|
||||
end
|
||||
```
|
||||
|
||||
### 3.2 POST `/api/users/management/requests/create`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant UC as UserController
|
||||
participant URS as UserRoleManagementService
|
||||
participant AS as ApprovalWorkflowService
|
||||
participant AR as ApprovalRequestRepository
|
||||
|
||||
Client->>UC: POST /api/users/management/requests/create
|
||||
UC->>SC: hasAuthority('USER_MANAGE') OR hasRole('USER_ROLE_ADMIN')
|
||||
alt unauthorized
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
UC->>URS: submitCreateUserRequest(request, servletRequest)
|
||||
URS->>AS: createRequest(resource=USER_MANAGEMENT, requiredSteps=1, checkerRole=USER_ROLE_ADMIN)
|
||||
AS->>AR: persist pending approval request + steps
|
||||
AS-->>URS: ApprovalResponse
|
||||
URS-->>UC: ApprovalResponse
|
||||
UC-->>Client: 200 user.management.request.created
|
||||
end
|
||||
```
|
||||
|
||||
### 3.3 POST `/api/users/management/requests/update-roles`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant UC as UserController
|
||||
participant URS as UserRoleManagementService
|
||||
participant AR as ApprovalRequestRepository
|
||||
|
||||
Client->>UC: POST /api/users/management/requests/update-roles
|
||||
UC->>SC: hasAuthority('USER_MANAGE') OR hasRole('USER_ROLE_ADMIN')
|
||||
alt unauthorized
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
UC->>URS: submitUpdateUserRolesRequest(request)
|
||||
URS->>AR: validate user + roles, build payload
|
||||
URS->>AR: create approval request with step checker role
|
||||
URS-->>UC: ApprovalResponse
|
||||
UC-->>Client: 200 user.management.request.created
|
||||
end
|
||||
```
|
||||
|
||||
## 4) RoleController (`/api/roles`)
|
||||
|
||||
### 4.1 POST `/api/roles/management/requests/create`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant RC as RoleController
|
||||
participant URS as UserRoleManagementService
|
||||
participant AS as ApprovalWorkflowService
|
||||
|
||||
Client->>RC: POST /api/roles/management/requests/create
|
||||
RC->>SC: hasAuthority('ROLE_MANAGE') OR hasRole('USER_ROLE_ADMIN')
|
||||
alt unauthorized
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
RC->>URS: submitCreateRoleRequest(request, servletRequest)
|
||||
URS->>AS: createRequest(resource=ROLE_MANAGEMENT, requiredSteps=1)
|
||||
AS-->>URS: ApprovalResponse
|
||||
URS-->>RC: ApprovalResponse
|
||||
RC-->>Client: 200 role.management.request.created
|
||||
end
|
||||
```
|
||||
|
||||
### 4.2 POST `/api/roles/management/requests/update-permissions`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant RC as RoleController
|
||||
participant URS as UserRoleManagementService
|
||||
participant AS as ApprovalWorkflowService
|
||||
|
||||
Client->>RC: POST /api/roles/management/requests/update-permissions
|
||||
RC->>SC: hasAuthority('ROLE_MANAGE') OR hasRole('USER_ROLE_ADMIN')
|
||||
alt unauthorized
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
RC->>URS: submitUpdateRolePermissionsRequest(request, servletRequest)
|
||||
URS->>AS: createRequest(resource=ROLE_MANAGEMENT, requiredSteps=1)
|
||||
AS-->>URS: ApprovalResponse
|
||||
URS-->>RC: ApprovalResponse
|
||||
RC-->>Client: 200 role.management.request.created
|
||||
end
|
||||
```
|
||||
|
||||
## 5) AuditController (`/api/audit`)
|
||||
|
||||
### 5.1 GET `/api/audit?limit={n}`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant AC as AuditController
|
||||
participant TS as TenantContext
|
||||
participant ATS as AuditTrailService
|
||||
participant ARepo as AuditTrailRepository
|
||||
|
||||
Client->>AC: GET /api/audit?limit=50
|
||||
AC->>SC: hasRole('ADMIN')
|
||||
alt unauthorized
|
||||
SC-->>Client: 403
|
||||
else authorized
|
||||
AC->>TS: getRequiredTenantId()
|
||||
AC->>ATS: listRecent(tenantId, limit)
|
||||
ATS->>ARepo: find top by tenant/order by createdAt desc
|
||||
ARepo-->>ATS: list audit entities
|
||||
ATS-->>AC: mapped list entities
|
||||
AC-->>AC: map to AuditTrailResponse DTOs
|
||||
AC-->>Client: 200 audit.list.success
|
||||
end
|
||||
```
|
||||
|
||||
## 6) TenantController (`/api/tenant`)
|
||||
|
||||
### 6.1 GET `/api/tenant/context`
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant SC as Spring Security
|
||||
participant TC as TenantController
|
||||
participant TCX as TenantContext
|
||||
|
||||
Client->>TC: GET /api/tenant/context
|
||||
TC->>SC: isAuthenticated()
|
||||
alt unauthenticated
|
||||
SC-->>Client: 401/403
|
||||
else authenticated
|
||||
TC->>TCX: getRequiredTenantId()
|
||||
TC-->>Client: 200 {tenantId}
|
||||
end
|
||||
```
|
||||
|
||||
## Common Cross-Cutting Exception Flow
|
||||
|
||||
### Validation and Error handling for all controllers
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Client
|
||||
participant Controller
|
||||
participant Handler as GlobalExceptionHandler
|
||||
|
||||
Client->>Controller: Invalid body / business rule violation
|
||||
Controller-->>Handler: throws MethodArgumentNotValidException or AppException
|
||||
alt AppException
|
||||
Handler-->>Client: 400 ApiResponse.fail(message)
|
||||
else AccessDenied
|
||||
Handler-->>Client: 403 ApiResponse.fail("error.forbidden")
|
||||
else General exception
|
||||
Handler-->>Client: 500 ApiResponse.fail("error.internal")
|
||||
end
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user