Conversation
- Add Match Detection Scheduler for automatic Valorant match polling - Add Tournament Result Service for aggregating game results - Add Game Scheduler Service for managing game start times - Add domain entities: MatchResult, MatchPlayerStat - Add ports: MatchDetectionPort, MatchResultDatabasePort, GameEventPublisherPort - Add adapters: Valorant API, RabbitMQ event publisher, Match result DB - Update Game controller with scheduling and result query APIs - Update Contest service with tournament result integration - Add DB migration for match detection fields - Update Swagger documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Docker Compose setup for k6, InfluxDB, Grafana - Add Grafana dashboard and datasource provisioning - Add Mock MySQL server for isolated load testing - Add load test execution script (run.sh) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Redis container helper for testcontainer-based integration tests - Enable automatic Redis instance creation and cleanup in tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change module path from GAMERS-BE to github.com/FOR-GAMERS/GAMERS-BE - Update all import references across 144 files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Prevent application crash when clearing applications fails, as this is a non-critical cleanup operation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use BIGINT UNSIGNED for PK/FK columns in match_results and match_player_stats tables to match spec and existing conventions - Add ForceDetectionStatus domain method to encapsulate forced state transitions instead of directly mutating DetectionStatus field Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Feat: Tournament Match Detection & Auto Result Recording
- Skip .env file loading when GIN_MODE=release (production) - Add golang-migrate CLI and Makefile to Docker image for container migrations - Add make package to Alpine runtime stage - Remove outdated ERD_DIAGRAM.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Feat: Tournament Match Detection & Production Docker Improvements (#43)
Fix: fixing 24 Migration failed
Fix: fixing 24 Migration failed and Cloudflare r2 env added
Set cookie domain via COOKIE_DOMAIN env var (e.g. ".gamers.io.kr") to allow frontend (gamers.io.kr) to access cookies set by backend (api.gamers.io.kr). Add WEB_URL to CORS AllowOrigins so cross-origin requests with credentials work. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix: Cross-subdomain cookie sharing and CORS for production
- Add CachedDiscordBotClient decorator with 5-min TTL for bot guilds and guild channels - Parallelize independent Discord API calls using errgroup in GetAvailableGuilds and GetAvailableGuildTextChannels - Add Discord cache Redis key utility functions - Wire CachedDiscordBotClient into Discord dependency graph Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ient Resolves 502 Bad Gateway on POST /api/users/valorant by adding exponential backoff retries (max 3), structured logging for all failure paths, and proper error differentiation (404/429/other) instead of silently discarding the upstream error. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Feat: Optimize Discord API performance with Redis caching and parallel execution
Include Role in JWT Claims so RequireAdmin middleware can verify admin access directly from the token instead of querying the database on every request. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Feat: Add user role to JWT claims (#54)
Resolves conflict in team_redis_adapter.go by keeping both: - develop's IncrementFinalizedTeamCount/GetFinalizedTeamCount methods - main's multi-team ClearTeam(contestID, teamID) signature from PR #57 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello @Sunja-An, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request synchronizes the Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
develop 브랜치를 main으로 동기화하는 중요한 PR입니다. 많은 기능이 추가되고 리팩토링이 이루어졌네요. JWT에 사용자 역할을 포함시켜 RequireAdmin 미들웨어에서 데이터베이스 조회를 제거한 점은 성능 향상에 큰 도움이 되는 훌륭한 개선입니다. Discord API 호출 시 errgroup을 사용한 병렬 처리와 Redis를 이용한 캐싱 데코레이터 패턴 적용으로 응답 속도를 개선한 점도 인상적입니다. 새로 추가된 경기 자동 감지 및 스케줄링 기능은 분산 락을 사용하여 여러 인스턴스 환경에서도 안전하게 동작하도록 설계된 점이 좋습니다. 전반적으로 코드 품질을 높이고 시스템 안정성과 성능을 개선하기 위한 고민이 많이 엿보이는 좋은 변경사항들입니다. 몇 가지 추가 개선 제안을 코멘트로 남겼습니다.
| for _, m := range members { | ||
| user, err := s.userQueryPort.FindById(m.UserID) | ||
| if err != nil { | ||
| log.Printf("[MatchDetection] Failed to find user %d: %v", m.UserID, err) | ||
| continue | ||
| } | ||
| if !user.HasValorantLinked() { | ||
| log.Printf("[MatchDetection] User %d (team %d) has no Valorant account linked, skipping", m.UserID, teamID) | ||
| continue | ||
| } | ||
| accounts = append(accounts, ValorantAccountInfo{ | ||
| UserID: m.UserID, | ||
| TeamID: teamID, | ||
| Name: *user.RiotName, | ||
| Tag: *user.RiotTag, | ||
| }) | ||
| } |
There was a problem hiding this comment.
| func (c *ValorantApiClient) GetMMRByName(region, name, tag string) (*port.ValorantMMRData, error) { | ||
| // Get current MMR | ||
| mmrResp, err := c.client.GetMMRByNameV2(govapi.GetMMRByNameV2Params{ | ||
| Affinity: region, | ||
| Name: name, | ||
| Tag: tag, | ||
| }) | ||
| var mmrResp *govapi.GetMMRByNameV2Response | ||
| var err error | ||
|
|
||
| for attempt := 0; attempt <= c.maxRetries; attempt++ { | ||
| if attempt > 0 { | ||
| backoff := time.Duration(1<<uint(attempt-1)) * time.Second | ||
| log.Printf("[ValorantAPI] Retry attempt %d for GetMMRByName(%s#%s), waiting %v", | ||
| attempt, name, tag, backoff) | ||
| time.Sleep(backoff) | ||
| } | ||
|
|
||
| mmrResp, err = c.client.GetMMRByNameV2(govapi.GetMMRByNameV2Params{ | ||
| Affinity: region, | ||
| Name: name, | ||
| Tag: tag, | ||
| }) | ||
| if err == nil && mmrResp != nil && mmrResp.Status == 200 { | ||
| break | ||
| } | ||
|
|
||
| if mmrResp != nil && mmrResp.Status == 404 { | ||
| log.Printf("[ValorantAPI] Player not found: %s#%s in region %s", name, tag, region) | ||
| return nil, exception.ErrValorantPlayerNotFound | ||
| } | ||
|
|
||
| if mmrResp != nil && mmrResp.Status == 429 { | ||
| log.Printf("[ValorantAPI] Rate limited on GetMMRByName(%s#%s), attempt %d/%d", | ||
| name, tag, attempt, c.maxRetries) | ||
| continue | ||
| } | ||
|
|
||
| if err != nil { | ||
| log.Printf("[ValorantAPI] Error on GetMMRByName(%s#%s): %v (attempt %d/%d)", | ||
| name, tag, err, attempt, c.maxRetries) | ||
| } else if mmrResp != nil { | ||
| log.Printf("[ValorantAPI] Non-200 status %d on GetMMRByName(%s#%s) (attempt %d/%d)", | ||
| mmrResp.Status, name, tag, attempt, c.maxRetries) | ||
| } | ||
| } | ||
|
|
||
| if err != nil { | ||
| log.Printf("[ValorantAPI] All retries exhausted for GetMMRByName(%s#%s), last error: %v", name, tag, err) | ||
| return nil, exception.ErrValorantApiError | ||
| } | ||
|
|
||
| if mmrResp.Status != 200 { | ||
| if mmrResp.Status == 404 { | ||
| return nil, exception.ErrValorantPlayerNotFound | ||
| if mmrResp == nil || mmrResp.Status != 200 { | ||
| status := 0 | ||
| if mmrResp != nil { | ||
| status = mmrResp.Status | ||
| } | ||
| if status == 429 { | ||
| log.Printf("[ValorantAPI] Rate limit exhausted for GetMMRByName(%s#%s) after %d retries", | ||
| name, tag, c.maxRetries) | ||
| return nil, exception.ErrValorantApiRateLimit | ||
| } | ||
| log.Printf("[ValorantAPI] Failed GetMMRByName(%s#%s), final status: %d", name, tag, status) | ||
| return nil, exception.ErrValorantApiError | ||
| } |
There was a problem hiding this comment.
valorant_api_client.go와 match_detection_valorant_adapter.go 두 파일에 Valorant API 호출을 위한 재시도 로직이 중복으로 구현되어 있습니다. 이 로직은 지수 백오프를 포함하여 잘 구현되었지만, 코드가 중복되어 유지보수성을 저해할 수 있습니다.
이 재시도 로직을 공통 유틸리티 함수나 Valorant API 클라이언트를 감싸는 래퍼(wrapper)로 추상화하는 것을 고려해 보세요. 이렇게 하면 코드 중복을 줄이고, 재시도 정책을 한 곳에서 일관되게 관리할 수 있습니다. 예를 들어, API 호출 함수를 인자로 받는 withRetry 헬퍼 함수를 만들 수 있습니다.
Summary
team_redis_adapter.goconflict 해결: develop의IncrementFinalizedTeamCount/GetFinalizedTeamCount메서드 유지 + main의 multi-teamClearTeam(contestID, teamID)시그니처 채택Included Changes
Test plan
go build ./...)🤖 Generated with Claude Code