Files
UTMS-NG-BE/docs/sequence-diagrams.md
2026-04-21 06:25:33 +07:00

14 KiB

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

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

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

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

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

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

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

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

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

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

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

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}

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

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

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