소켓 연결 및 매칭 요청 흐름

sequenceDiagram
    participant Client as 클라이언트
    participant Socket as Socket.IO
    participant MatchingGW as MatchingGateway
    participant MatchingCtrl as MatchingController
    participant MatchingSvc as MatchingService
    participant Redis as Redis
    
    Note over Client: 메인 페이지 진입
    Client->>Socket: connectBattleSocket()
    Socket-->>Client: 소켓 연결 성공 (/game)
    
    Client->>Client: registerMatchingListeners()
    Note over Client: MATCH_SUCCESS, STATS_UPDATE<br/>이벤트 리스너 등록
    
    Client->>MatchingCtrl: POST /matching/start
    MatchingCtrl->>MatchingSvc: startMatching(userId, tier)
    MatchingSvc->>Redis: ZADD matching:queue<br/>(score=timestamp)
    MatchingSvc->>Redis: HSET matching:user:{userId}<br/>(status=WAITING, tier, socketId)
    Redis-->>MatchingSvc: OK
    MatchingSvc->>MatchingGW: registerUserSocket(userId, socketId)
    MatchingGW-->>MatchingSvc: OK
    MatchingSvc-->>MatchingCtrl: 매칭 대기 시작
    MatchingCtrl-->>Client: 200 OK
    
    Note over Client: 매칭 대기 중...

매칭 성공 및 방 입장 흐름

sequenceDiagram
    participant Scheduler as MatchingScheduler<br/>(Cron 2초마다)
    participant MatchingSvc as MatchingService
    participant RoomSvc as RoomService
    participant BattleSvc as BattleService
    participant BattleRedisSvc as BattleRedisService
    participant Redis as Redis
    participant MatchingGW as MatchingGateway
    participant Client1 as 클라이언트 A
    participant Client2 as 클라이언트 B
    participant RoomGW as RoomGateway
    
    Scheduler->>MatchingSvc: matchUsers() (Cron)
    MatchingSvc->>Redis: ZRANGE matching:queue<br/>(대기 유저 조회)
    Redis-->>MatchingSvc: [user1, user2, ...]
    
    Note over MatchingSvc: 레이팅 범위 내 페어링
    
    MatchingSvc->>RoomSvc: createMatchedRoom(user1, user2, problem)
    RoomSvc->>Redis: SET room:{roomId}:info<br/>(players=[], problem, status=waiting)
    RoomSvc->>BattleSvc: createBattle(roomId, user1, user2, problem)
    BattleSvc->>BattleRedisSvc: createBattle(battleId, roomId, users)
    BattleRedisSvc->>Redis: SET battle:{battleId}
    BattleRedisSvc->>Redis: SET battle:room:{roomId}
    BattleRedisSvc->>Redis: SADD matching:active_battles
    BattleRedisSvc-->>BattleSvc: OK
    BattleSvc-->>RoomSvc: Battle 생성 완료
    RoomSvc-->>MatchingSvc: Room 생성 완료
    
    MatchingSvc->>Redis: ZREM matching:queue (user1, user2)
    MatchingSvc->>Redis: HSET matching:user:{user1Id}<br/>(status=MATCHED, roomId, opponentId)
    MatchingSvc->>Redis: HSET matching:user:{user2Id}<br/>(status=MATCHED, roomId, opponentId)
    MatchingSvc->>Redis: LPUSH matching:recent_wait_times<br/>(대기 시간 기록)
    
    MatchingSvc->>MatchingGW: emitMatchSuccess(user1, user2, roomId, battleId)
    MatchingGW->>Client1: MATCH_SUCCESS<br/>(roomId, battleId, opponent, problem)
    MatchingGW->>Client2: MATCH_SUCCESS<br/>(roomId, battleId, opponent, problem)
    
    Note over Client1,Client2: 자동으로 방 페이지로 이동
    
    Client1->>RoomGW: JOIN_ROOM<br/>(roomId, role=player)
    Client2->>RoomGW: JOIN_ROOM<br/>(roomId, role=player)
    
    RoomGW->>Redis: room:{roomId}:info 조회 및 업데이트<br/>(player 추가)
    RoomGW->>BattleSvc: joinBattle(roomId, player)
    BattleSvc->>Redis: battle:{battleId} 업데이트<br/>(user 추가)
    RoomGW->>Redis: HSET matching:user:{userId}<br/>(status=IN_ROOM, joinedAt)
    
    RoomGW->>Client1: ROOM_PLAYERS (참가자 목록)
    RoomGW->>Client2: ROOM_PLAYERS (참가자 목록)
    RoomGW->>Client1: ROOM_STATE_ROLE (본인 역할)
    RoomGW->>Client1: PROBLEM_INFO (문제 정보)
    RoomGW->>Client1: CODE_UPDATED (초기 코드)
    RoomGW->>Client2: ROOM_STATE_ROLE
    RoomGW->>Client2: PROBLEM_INFO
    RoomGW->>Client2: CODE_UPDATED
    
    Note over RoomGW: 2명 모두 입장 시
    RoomGW->>Redis: room:{roomId}:info 업데이트<br/>(status=in-battle)
    RoomGW->>Client1: ROOM_AVAILABILITY<br/>(status=in-battle)
    RoomGW->>Client2: ROOM_AVAILABILITY<br/>(status=in-battle)

배틀 진행 (코드 작성, 테스트, 제출) 흐름

sequenceDiagram
    participant Client as 클라이언트
    participant BattleGW as BattleGateway
    participant BattleSvc as BattleService
    participant Redis as Redis
    participant SubmissionCtrl as SubmissionController
    participant SubmissionSvc as SubmissionService
    participant DB as PostgreSQL
    participant Queue as BullMQ
    participant Judge as Judge Worker
    participant PubSub as Redis PubSub
    participant Subscriber as PubsubSubscriber
    participant PubsubGW as PubsubGateway
    
    Note over Client: === 코드 작성 중 ===
    Client->>BattleGW: CODE_CHANGE<br/>(code, language)
    BattleGW->>BattleSvc: updateUserCode(battleId, userId, code)
    BattleSvc->>Redis: battle:{battleId} 업데이트<br/>(user.code)
    BattleGW->>Client: CODE_UPDATED (전체 방)
    BattleGW->>Client: CODE_METADATA (상대방에게)
    
    Note over Client: === 테스트 실행 ===
    Client->>SubmissionCtrl: POST /submissions/dry-run<br/>(Header: x-socket-id)
    SubmissionCtrl->>SubmissionSvc: executeTest(code, problemId, socketId)
    Note over SubmissionSvc: DB 저장 X, submissionId만 생성
    SubmissionSvc->>Queue: test-job 큐에 작업 추가
    SubmissionSvc-->>Client: 202 Accepted
    
    Queue->>Judge: 테스트 실행
    Judge->>Judge: 코드 채점
    Judge->>PubSub: PUBLISH submission-result<br/>(type=TEST, submissionId, result)
    
    PubSub->>Subscriber: handleFinalResult(message)
    Subscriber->>Subscriber: getSubmissionType() → TEST
    Subscriber->>PubsubGW: emitFinalResultToSocket(socketId)<br/>(본인에게만)
    PubsubGW->>Client: submission-result<br/>(status, passed, total)
    Subscriber->>PubsubGW: emitTestResult(roomId)<br/>(방 전체)
    PubsubGW->>Client: test-result (전체 방)
    Subscriber->>PubsubGW: emitSystemChat(roomId)
    PubsubGW->>Client: receive-chat (시스템 메시지)
    
    Note over Client: === 코드 제출 ===
    Client->>SubmissionCtrl: POST /submissions<br/>(Header: x-socket-id)
    SubmissionCtrl->>SubmissionSvc: submit(code, problemId, userId, battleId, socketId)
    SubmissionSvc->>DB: Submission 엔티티 생성<br/>(status=PENDING)
    DB-->>SubmissionSvc: submission.id
    SubmissionSvc->>Queue: submission-job 큐에 작업 추가
    SubmissionSvc-->>Client: 202 Accepted
    
    Queue->>Judge: 채점 실행
    Judge->>Judge: 전체 테스트 실행
    Judge->>PubSub: PUBLISH submission-result<br/>(type=SUBMISSION, submissionId, result)
    
    PubSub->>Subscriber: handleFinalResult(message)
    Subscriber->>DB: Submission 조회
    Subscriber->>DB: Submission 업데이트<br/>(status, passedTestCases, executionTime)
    Subscriber->>PubsubGW: emitFinalResult(roomId)<br/>(방 전체)
    PubsubGW->>Client: submission-result
    Subscriber->>BattleGW: handleUserFinished(roomId, userId)
    BattleGW->>Client: receive-chat (제출 완료 시스템 메시지)
    
    alt status === 'ACCEPTED'
        Note over Subscriber: 모든 테스트 통과!
        Subscriber->>BattleSvc: markUserFinished(battleId, userId)
        Note over BattleSvc: 즉시 승리 → endBattle() 호출
    end

배틀 종료 흐름

sequenceDiagram
    participant Client as 클라이언트
    participant BattleGW as BattleGateway
    participant BattleSvc as BattleService
    participant SubmissionRepo as SubmissionRepository
    participant UserSvc as UserService
    participant BattleRepo as BattleRepository
    participant Redis as Redis
    participant MatchingSvc as MatchingService
    participant RoomSvc as RoomService
    participant MatchingGW as MatchingGateway
    participant DB as PostgreSQL
    
    alt 케이스 1: 한 명이 ACCEPTED
        Note over BattleSvc: markUserFinished() → endBattle()
    else 케이스 2: 타이머 종료
        Client->>BattleGW: TIMER_END<br/>(battleId, roomId)
        BattleGW->>BattleSvc: endBattleByTimeout(battleId)
    end
    
    Note over BattleSvc: === endBattle(battleId) 시작 ===
    
    BattleSvc->>Redis: battle:{battleId} 조회
    Redis-->>BattleSvc: battle 데이터
    
    BattleSvc->>SubmissionRepo: 각 참가자의 마지막 Submission 조회
    SubmissionRepo-->>BattleSvc: [submission1, submission2]
    
    Note over BattleSvc: === 승자 결정 로직 ===<br/>1. ACCEPTED가 있으면 먼저 제출한 사람<br/>2. 없으면 passedTestCases가 많은 사람<br/>3. 동점이면 무승부 (winnerId=null)
    
    BattleSvc->>UserSvc: updateRatings(winnerId, loserId, isDraw)
    UserSvc->>DB: User 테이블 업데이트<br/>(wins, losses, rating)
    UserSvc-->>BattleSvc: { winner: {ratingDelta}, loser: {ratingDelta} }
    
    BattleSvc->>BattleRepo: Battle 엔티티 생성 및 저장
    Note over BattleRepo: - winnerId, winnerSubmissionId<br/>- loserSubmissionId, playerIds<br/>- player1RatingChange<br/>- player2RatingChange
    BattleRepo->>DB: INSERT Battle
    DB-->>BattleRepo: savedBattle
    BattleRepo-->>BattleSvc: Battle 엔티티
    
    BattleSvc->>Redis: battle:{battleId} DEL
    BattleSvc->>Redis: battle:room:{roomId} DEL
    BattleSvc->>Redis: SREM matching:active_battles
    BattleSvc->>MatchingSvc: clearUserMatchingStatus(user1, user2)
    MatchingSvc->>Redis: DEL matching:user:{user1Id}
    MatchingSvc->>Redis: DEL matching:user:{user2Id}
    
    BattleSvc-->>BattleGW: Battle 엔티티 반환
    
    BattleGW->>BattleGW: emitBattleEnd(roomId, battleId, winnerId)
    BattleGW->>Client: BATTLE_ENDED<br/>(battleId, winnerId) (전체 방)
    BattleGW->>Client: receive-chat (시스템 메시지)
    
    BattleGW->>RoomSvc: completeBattleRoom(roomId)
    RoomSvc->>Redis: room:{roomId}:info 업데이트<br/>(status=completed)
    
    BattleGW->>MatchingGW: broadcastRoomList()
    MatchingGW->>Client: ROOM_LIST (전체)
    
    Note over Client: BATTLE_ENDED 이벤트 수신
    Client->>Client: navigate(`/result/${battleId}`)
    Note over Client: sessionStorage 정리
    
    Client->>BattleSvc: GET /battles/{battleId}/result
    BattleSvc->>DB: Battle 조회 (with submissions, users)
    DB-->>BattleSvc: Battle + Players + Submissions
    BattleSvc-->>Client: BattleResultResponse<br/>(players, winner, submissions, ratingChanges)