418 lines
14 KiB
Markdown
418 lines
14 KiB
Markdown
# 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
|
|
```
|
|
|