feat: LoL Temporal 포지션 선호도 기반 5:5 팀 매칭 알고리즘 구현 (#84)#85
Merged
Conversation
Add POST /api/contests/:id/thumbnail endpoint that uploads a thumbnail image to R2 storage and updates the contest's thumbnail URL and banner_key fields in a single operation. Only contest leaders can upload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Feat: Add Contest Thumbnail Upload API (#59)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ication (#62) - Replace string comparison with domain method (IsPending) - Replace silent error swallowing with log.Printf - Rename ErrContestAlreadyStarted to ErrNotContestLeader - Add ErrContestStartTimePassed, ErrAlreadyContestMemberExists - Use typed BusinessError in UpdateContestRequest validation - Handle duplicate application with 409 Conflict response Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#63) - Extract OAuth2TokenExchangerPort, DiscordUserInfoPort, OAuth2StatePort interfaces - Create OAuth2TokenExchangerAdapter for production oauth2.Config wrapping - Refactor DiscordService to depend on port interfaces for testability - Add 9 auth service tests (Login, Logout, Refresh) - Add 7 auth controller tests (httptest) - Add 5 Discord service tests (Callback, LoginURL) - Add 5 Discord controller tests (Redirect, Callback scenarios) - Add 4 auth integration tests (testcontainers: MySQL + Redis) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move OAuth2 state validation to controller layer for all callback paths (error, missing_code, success) - Fix incorrect error variable usage (err → appErr) in contest application controller - Update tests to reflect state validation changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix: Handle Discord OAuth2 deny button 500 error (#9)
…ntest application - [feat #64] Calculate Valorant tier point (avg of current/peak) on RequestParticipate and propagate stored point to ContestMember on AcceptApplication - [feat #64] Add Point, CurrentTier, PeakTier fields to SenderSnapshot and SenderResponse - [feat #64] NewContestMember now accepts a point parameter instead of hardcoding 0 - [feat #64] Wire SetScoreTablePort in cmd/server.go to avoid circular dependency - [refactor #65] Move getTierPoint logic into ValorantScoreTable.GetTierPoint domain method - [feat #66] Add ErrValorantRankNotFound (VAL007) and nil-guard for tier data in CalculateContestPoint - [test #67] Add unit tests for ContestApplicationService point calculation (10 cases) - [test #67] Add integration tests for end-to-end point persistence with testcontainers - [test #67] Add unit tests for ValorantScoreTable.GetTierPoint domain method - [chore #68] Remove K6 load-test infrastructure (replaced by testcontainers integration tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- [HIGH] Propagate score table fetch error instead of silent fallback to 0: When contest has GamePointTableId but score table is not found, return the error to caller (server-side configuration error) - [SECURITY] Add nil-check for application before AcceptApplication: Return ErrApplicationNotFound when application is nil to prevent unauthorized member addition by contest leaders - [SECURITY] Add status check for application before AcceptApplication: Return ErrApplicationNotPending when application is not in PENDING state - [TEST] Update TestRequestParticipate_ScoreTableNotFound to expect error return - [TEST] Add TestAcceptApplication_NilApplication_ReturnsError - [TEST] Add TestAcceptApplication_NonPendingApplication_ReturnsError - [MEDIUM] Fix confusing test data initialization in integration test: Initialize Gold2+Platinum1 applicant with correct value (35) directly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JWT expiry uses second-precision, so tokens generated within the same second produce identical strings. Added time.Sleep(time.Second) before the Refresh call in TestFullLoginFlow to ensure unique timestamps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Test: Add unit and integration tests for Auth and Discord OAuth2 login (#63)
- Return error from AcceptApplication when memberRepo.Save fails instead of silently logging it, preventing inconsistent state where application is ACCEPTED in Redis but member record is absent in DB - Remove redundant SaveBatch call in startNonTournamentContest that was re-creating members with hardcoded point=0; members are already persisted with correct points during AcceptApplication Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix: Member point saved as 0 when contest application is accepted (#71)
…r list (#73) - Define ValorantRole type (DUELIST, INITIATOR, CONTROLLER, SENTINEL) in domain - Add ValorantRoles (custom JSON type) and Description to ContestMember entity - Extend SenderSnapshot and ContestMemberWithUser with new fields - Accept optional request body in POST /api/contests/:id/applications - Validate description max 64 chars and role deduplication in service layer - Include valorant_roles and description in GET /api/contests/:id/members response - Add DB migration 000025 to add valorant_roles JSON and description VARCHAR(64) columns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: Add Valorant roles/description to contest application and member list (#73)
Prometheus + Grafana 기반 모니터링 인프라를 추가한다. - PrometheusMetrics() Gin 미들웨어 추가 (http_requests_total, http_request_duration_seconds, http_requests_in_flight) - GET /metrics 엔드포인트 등록 (Prometheus scrape 용) - docker-compose에 Prometheus(9090), Grafana(3001) 서비스 추가 - Prometheus scrape 설정 (15s interval, app:8080/metrics) - Grafana datasource / dashboard 프로비저닝 자동화 - HTTP RPS, 에러율, p50/p95/p99 응답 시간, In-Flight, Goroutine, Heap, GC 패널 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace hardcoded Grafana admin credentials with env vars (${GRAFANA_ADMIN_USER}, ${GRAFANA_ADMIN_PASSWORD})
- Add docker/.env.example as reference for required env vars
- Downgrade Prometheus image from v3.4.0 to stable v2.53.1
- Use defer for httpRequestsInFlight.Dec() to guarantee decrement on panic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: Grafana 모니터링 스택 연동 (#75)
- r2_storage_adapter.go 제거 (R2 커스텀 엔드포인트 방식 삭제) - s3_storage_adapter.go 추가 (표준 AWS 리전 엔드포인트 사용) - StoragePort 인터페이스 동일하게 구현 (Upload/Delete/GetPublicURL) - BucketName, Region 누락 시 초기화 실패 처리 - provider.go: R2 → S3 어댑터 초기화로 교체 - env/.env.example: R2 변수 제거, AWS S3 변수 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- s3_storage_adapter.go 제거 - gcs_storage_adapter.go 추가 (cloud.google.com/go/storage v1.61.3) - GCS_CREDENTIALS_JSON 설정 시 서비스 계정 JSON 인증 - 미설정 시 ADC(Application Default Credentials) 자동 폴백 - GCS_BUCKET_NAME 누락 시 초기화 실패 처리 - provider.go: S3 → GCS 어댑터 초기화로 교체 - env/.env.example: AWS S3 변수 제거, GCS 변수 추가 - go.mod/go.sum: cloud.google.com/go/storage 의존성 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…API (#79) Implements a type-safe Go package that communicates with the locally running League of Legends client via the LCU HTTP/WebSocket API. - lockfile.go: auto-discover lockfile on Windows/macOS/Linux and extract port + password - client.go: http.Client with InsecureSkipVerify (self-signed cert) and automatic Basic auth header injection - session.go: GetGameflowPhase, GetGameflowSession, GetCurrentSummoner - match.go: GetLastGameID, GetMatchDetail, GetMatchHistory with full struct mappings for Game, ParticipantStats, Timeline, etc. - event.go: WebSocket subscription via gorilla/websocket using WAMP protocol; delivers typed Event{Name,Type,URI,Data} to handler func - process.go: LeagueClient.exe process detection and WaitForClientAndConnect helper Adds github.com/gorilla/websocket v1.5.3 dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- client.go: Add functional options pattern (ClientOption/WithTimeout/WithTLSRootCA) for TLS and timeout customisation; store tlsConfig on Client for WebSocket reuse - client.go: Handle io.ReadAll errors explicitly in Get() and RawGet() - event.go: Log WebSocket Close() error instead of silently discarding it - event.go: Share Client.tlsConfig with WebSocket dialer (remove duplicate InsecureSkipVerify) - event.go: Handle json.Marshal error for WAMP subscribe message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- match.go: Fix Participants field type []Participant → []GameParticipant to match the actual LCU match history API response structure - process.go: Replace shell-out (tasklist/pgrep) with gopsutil/v4/process for cross-platform process detection without external command dependency - gcs_storage_adapter.go: Fix potential double-slash in GetPublicURL by trimming trailing slash from publicURL before concatenation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: Add pkg/lcu — Go LCU API client library (#79)
- exception: Add ErrContestStartTimePassed (CT038) - port: Add Point, CurrentTier, PeakTier fields to SenderSnapshot to capture tier info at the time of application - service: Fix NewContestMember call — pass point from sender snapshot, defaulting to 0 if no application snapshot is available Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: Resolve build errors introduced by pkg/lcu merge (#79)
POST /api/contests/lol/temporal 엔드포인트 추가. 5개 라인(TOP/JG/MID/ADC/SUP)에 각 2명 플레이어 랭크 기반으로 2^5=32 완전 탐색으로 팀 점수 차를 최소화하는 5v5 팀 편성 반환. DB 저장 없는 순수 연산(Temporal) 처리. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ParseLolRankScore: 전 티어/디비전 + 에러 케이스 (5개) - BalanceLolTemporalContest: 정상/밸런싱 검증/에러 케이스 (7개) - HandleLolTemporalContest: HTTP 201/400 케이스 (5개) - 기존 RequestParticipate 시그니처 변경에 따른 테스트 수정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 기존 라인별 2명 고정 방식 → 10명 자유 입력 + 포지션 선호도 기반으로 교체 - C(10,5)=252 × 5!×5!=14,400 조합 완전 탐색 (약 360만 케이스) - 3단계 필터링: 팀 MMR 밸런스 → 라인별 맞대결 편차 → 포지션 만족도 - 포지션 보정(Model A): 1순위 100% ~ 5순위 75% (6.25%씩 선형 감소) - 새 에러코드: ErrLolTemporalInvalidPlayerCount(CT048), ErrLolTemporalInvalidPositions(CT049) - 단위 테스트 13개 추가 (서비스 9개, 컨트롤러 8개) - Swagger 문서 갱신 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces several key features: Valorant-related fields (roles, description) for contest applications, point calculation based on Valorant tiers, thumbnail upload functionality for contests using Google Cloud Storage, and a new LoL team balancing algorithm. Additionally, it adds Prometheus metrics for monitoring and includes necessary infrastructure and test updates. My feedback focuses on improving the robustness of the application point calculation, optimizing the performance of the computationally intensive team balancing algorithm, and ensuring proper context management for asynchronous storage operations.
6 tasks
- AcceptApplication에 application 조회 에러/nil/상태 검증 추가 - UploadThumbnail goroutine에서 context race condition 수정 (oldKey 캡처) - balanceLolTeamsV2 조합/순열 테이블 패키지 레벨 precompute로 성능 최적화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
작업 범위
LolTemporalPlayerV2,LolTemporalContestRequestV2,LolTemporalAssignedPlayer,LolTemporalContestResponseV2ErrLolTemporalInvalidPlayerCount(CT048),ErrLolTemporalInvalidPositions(CT049)balanceLolTeamsV2— 조합/순열 생성 + 3단계 필터링 (lol_temporal_balance.go)BalanceLolTemporalContestV2메서드POST /api/contests/lol/temporal신규 메서드로 교체알고리즘 상세
3단계 필터링
|팀A 보정MMR합 - 팀B 보정MMR합|포지션 보정 공식
API 변경
Request
POST /api/contests/lol/temporal{ "members": [ { "username": "P1", "tag": "KR1", "rank": "GOLD II", "positions": ["TOP", "JG", "MID", "ADC", "SUP"] }, ... // 총 10명 ] }Response
{ "team_a": [{ "username": "P1", "tag": "KR1", "rank": "GOLD II", "position": "TOP", "position_preference": 1 }, ...], "team_b": [...] }Test plan
TestBalanceLolTemporalContestV2_*(서비스 단위 테스트 9개) 전체 통과TestLolTemporalV2Controller_*(컨트롤러 단위 테스트 8개) 전체 통과TestLolTemporalController_*,TestBalanceLolTemporalContest_*하위 호환 통과go build ./...통과Closes #84
🤖 Generated with Claude Code