initial commit

This commit is contained in:
2026-04-21 06:25:33 +07:00
commit 85efdb7714
214 changed files with 6821 additions and 0 deletions

417
docs/sequence-diagrams.md Normal file
View 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
```