initial commit
This commit is contained in:
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