Add API key authenticated REST API to back office - #191
Merged
Conversation
Expose the back office operations as REST controllers so they can be driven without the Blazor UI. Authentication reads the X-API-Key header and compares it against the ARENA_API_KEY configuration value. The Blazor UI keeps using Google OAuth; only the controllers opt into the new scheme. Controllers mirror the existing pages one to one: seasons (ManageSeasons), ticket policies (Policy), transaction review (ConfirmTx), cache preparation (RankingCache, CacheInitialization), leaderboards and settlement CSVs (Leaderboard), and battle tx tracker progress (BlockTrackerStatus). Season and Round reference each other, so responses project into records instead of serializing entities. Every response uses a common ApiResponse envelope, and Swagger documents the endpoints with the API key security definition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsR64huwuS3v6GB8RejLCy
Apply the fixes planetarium#189 already landed on its own admin API, and cover the back office controllers with tests. - AddSeasonWithRoundsAsync computed endBlock with an int multiplication that could overflow before widening to long. Compute it checked, on long. - Ticket purchase review now takes a type parameter and updates refresh ticket purchase logs too. It previously only updated battle ticket logs, mirroring a limitation of the Blazor page even though UpdateRefreshTicketPurchaseLog exists. - Unreviewed battle and refresh purchases are now queried concurrently. - Addresses are lower cased, matching the existing API responses and the leaderboard CSV. Tests live in a new ArenaService.BackOffice.Tests project rather than ArenaService.Tests: both web projects generate ArenaService.Client types, so a single test project referencing both cannot resolve IHeadlessClient. The project is added to the solution. The CI matrix in .github/workflows/test.yaml still lists only ArenaService.Tests, so the new project is not run yet. Adding it needs a token with the workflow scope; see the pull request description. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsR64huwuS3v6GB8RejLCy
4 tasks
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.
목적
백오피스 기능이 Blazor UI(Google OAuth)에서만 사용 가능해서 외부 연동·자동화가 불가능했습니다. 기존 페이지의 동작을 그대로 REST 엔드포인트로 노출해 API Key 로 호출할 수 있게 합니다.
NineChronicles.Backoffice가 같은 방식(X-API-Key+NC_API_KEY)으로 API 를 노출한 것과 동일한 패턴입니다.인증
Authentication/ApiKeyAuthenticationHandler.cs—X-API-Key헤더를ARENA_API_KEY설정값과 비교[Authorize(AuthenticationSchemes = "ApiKey")]적용. Blazor UI 는 기존 Google OAuth 그대로ARENA_API_KEY미설정 시에도 401(에러 로그 남김)엔드포인트 (24개)
기존 페이지와 1:1 대응합니다.
SeasonsControllerapi/seasons— 목록/조회/생성/수정/종료블록 조정/삭제/다음 시작블록PoliciesControllerapi/policies/battle,api/policies/refresh— 조회/추가ConfirmTxControllerapi/confirm-tx/battles,/ticket-purchases+ 리뷰 처리RankingCacheControllerapi/ranking-cache상태/시즌 준비/다음 라운드 준비/캐시 초기화LeaderboardControllerapi/leaderboard/seasons,/{id},/{id}/csv,/{id}/staking-csvBlockTrackerControllerapi/block-tracker설계 메모
Season과Round가 서로를 참조해 순환이 생기므로Models/ApiModels.cs의 record 로 투영합니다.ApiResponse<T> { success, message, data }를 사용합니다.GenerateDocumentationFile).UI 동작을 그대로 옮긴 부분 (의도적)
리뷰 시 확인이 필요할 수 있어 남깁니다. 둘 다 기존 UI 동작을 바꾸지 않으려고 그대로 옮겼고, 코드에 주석을 달아뒀습니다.
POST api/ranking-cache/initialize는 캐시된 라운드의Id를ICacheInitializationService.InitializeRankingCacheAsync의 두 번째 인자로 넘깁니다. 인터페이스 파라미터명은roundIndex입니다 (CacheInitialization.razor 와 동일).POST api/confirm-tx/ticket-purchases/{id}/review는 battle ticket purchase log 만 갱신합니다 (ConfirmTx.razor 와 동일). refresh ticket purchase id 로는 갱신되지 않습니다.배포
ARENA_API_KEY환경변수 주입이 필요합니다. 미설정 상태로 배포되면 API 는 전부 401 이 되고 기존 Blazor UI 는 영향받지 않습니다.검증
dotnet build ArenaService.BackOffice/ArenaService.BackOffice.csproj— 성공, 신규 코드에서 경고 없음/swagger/v1/swagger.json200, 24개 엔드포인트 노출/(Blazor UI) → 302 Google 로그인 (기존과 동일)🤖 Generated with Claude Code
https://claude.ai/code/session_01LsR64huwuS3v6GB8RejLCy
#189 과의 관계 (2번째 커밋에서 반영)
같은 목적의 선행 PR #189 (
Add admin REST API endpoints for backoffice operations) 가 열려 있습니다. 확인 후 접근이 달라 이 PR 을 유지하되, #189 이 이미 잡아둔 지적사항을 흡수했습니다.ArenaService(게임 API 본체)ArenaService.BackOffice[Authorize(Roles = "Admin")]— ES256K JWT +ALLOWED_ADMIN_PUBLIC_KEYX-API-Key+ARENA_API_KEY두 PR 은 변경 파일이 겹치지 않아 충돌 없이 공존 가능합니다. 어느 쪽을 살릴지는 리뷰에서 정해주시면 따르겠습니다.
#189 에서 가져온 수정
SeasonRepository.AddSeasonWithRoundsAsync의endBlock계산이int곱셈이라 오버플로 가능 →checked+long연산type파라미터(case-insensitive)로 battle/refresh 모두 지원.UpdateRefreshTicketPurchaseLog가 이미 있는데 UI 제약을 그대로 옮겼던 부분입니다Task.WhenAll로 병렬화단위 테스트 (44개)
ArenaService.BackOffice.Tests프로젝트를 새로 추가했습니다.ArenaService.Tests에 넣지 않은 이유는 두 웹 프로젝트가 각각ArenaService.Client타입을 생성해서, 양쪽을 참조하면IHeadlessClient가CS0433로 모호해지기 때문입니다.ApiKeyAuthenticationHandlerTests— 키 일치/불일치/공백/헤더 없음/대소문자 차이/미설정 (7)SeasonsControllerTests— 페이징, deletable 판정, 캐시 장애 시 null, 블록범위 겹침 409, 오버플로 방지, 서버사이드 block index 삭제 검증 (13)PoliciesControllerTests— 가격 개수 검증 실패/성공 (6)ConfirmTxControllerTests— battle/refresh 분기, 대소문자 무시, 알 수 없는 type 400 (8)RankingCacheControllerTests— 상태 조회, 첫 라운드 경계, 라운드 없는 시즌 400, 초기화 실패 500 (7)LeaderboardControllerTests— 주소 소문자, 랭크/전적 투영, CSV 파일명 (3)검증:
dotnet test ArenaService.BackOffice.Tests44개 통과,dotnet test ArenaService.Tests기존 39개 통과 (회귀 없음).머지 전 필요한 조치
.github/workflows/test.yaml의 CI matrix 에 새 테스트 프로젝트를 추가해야 합니다. 봇 토큰에workflow스코프가 없어 이 PR 에 포함하지 못했습니다.이 줄을 고치지 않으면 새 테스트 44개가 CI 에서 실행되지 않습니다.