개요
ABL(Amateur Bowl League)은 아마추어 볼링 리그를 운영하는 플랫폼입니다. 리그 편성 · 참가 신청 · 팀/레인 매칭 · 경기 점수 기록과 검증 · 결제와 정산 · 랭킹 · 게이미피케이션까지 리그 운영 전 과정을 다룹니다.
시스템은 백엔드 · 웹 · 모바일 3-surface로 구성된 pnpm 모노레포(apps/*)입니다. 백엔드가 단일 API를 제공하고, 웹(관리자·얼라이언스 매니저용)과 모바일(회원용)이 이 API를 공유합니다.
이 문서는 "무엇을 만드는가(요구사항)"가 아니라 "어떻게 지어져 있는가"를 개발자 관점에서 서술합니다. 모든 기술 서술은 실제 코드 조사에 근거하며, 확정되지 않은 부분은 §09에 "확정 대기 · 미확인"으로 따로 표시했습니다.
시스템 구성
웹과 모바일은 각각 독립된 클라이언트이고, 둘 다 백엔드가 제공하는 REST API(/api, JWT 인증)와 Socket.IO(실시간)로만 백엔드와 통신합니다. 백엔드는 PostgreSQL(도메인 데이터) · Redis(캐시·큐) · S3(파일) · 외부 결제(PG)에 연결됩니다.
시스템 구성 개념도 — 실제 서버·네트워크 배치가 아닙니다.
abl.plaq.co.kr/api.도메인 모델
대회 구조는 Season → League → Round → MatchDay → Match → Game의 계층으로 내려갑니다. 회원의 참가는 MatchDay에 Participation으로 붙고, 실제 점수는 Match의 MatchPlayer 아래 GameScore로 기록됩니다.
대회 구조 계층과 참가 · 점수 분기 — 주요 엔티티만 표시.
엔티티는 도메인 그룹으로 나뉩니다. 아래는 코드에서 확인된 핵심 그룹입니다.
Member(members) · Alliance · BowlingCenter · Admin/Role/Permission.Season → League → Round → MatchDay(match_sessions) → Match → Game.Participation ─1:1─ Payment · Settlement. 대기(waitlist) · 듀오(duoPartner) · 고스트(isGhost) 개념 포함.MatchPlayer → GameScore ─1:1─ ScoreVerification, ─1:N─ ScoreEditHistory. GameScore에 낙관적 락(@VersionColumn).TierDefinition · MemberTier · RpHistory + 평균(average) 집계.데이터 흐름 — 참가부터 정산까지
회원이 경기일에 참가 신청을 하면 (참가비가 있으면) 결제가 이어지고, 신청 마감 후 매칭이 팀(A/B)과 레인을 배정합니다. 경기가 진행되며 점수가 제출되고, 사진 증빙 검증을 거친 뒤 정산이 이루어집니다.
참가 → 결제 → 매칭 → 경기·점수 → 검증 → 정산.
waitlistOrder), 밸런스용 고스트(isGhost), 듀오는 함께 배정.SUBMITTED 상태로 저장 → 사진 증빙 검증 후 정산 대상이 됨. 핸디캡은 (상대 평균 − 본인 평균) × 0.8 로 자동 적용.Settlement 생성(상금 규칙 적용). settledAt 이후에는 SettlementGuard로 수정이 잠김.인증 · 권한
인증은 JWT + refresh 토큰 방식입니다. 접근 토큰은 짧게(1시간) 유지하고, refresh 토큰은 해시해 DB에 저장하며 family 기반 회전으로 재사용 공격을 막습니다.
권한은 RBAC(역할–권한, resource:action 쌍)로 관리하고, JwtAuthGuard · RolesGuard · @RequirePermission() · ProfileCompletionGuard 등의 가드로 강제합니다. @Public()이 붙은 라우트만 인증을 건너뜁니다.
/admin vs /alliance)로 구분하고 서로 다른 토큰을 사용. 접근 토큰은 메모리, refresh는 localStorage.flutter_secure_storage(키체인/암호화 저장소)에 보관.API 구조
백엔드는 하나의 REST API(/api)를 제공하며, 컨트롤러는 공개 · 회원 · 관리자 · 웹훅 그룹으로 나뉩니다. 개발용 문서는 /api/docs(Swagger)로 제공되며 운영에서는 비활성입니다.
| 그룹 | 예시 경로 | 권한 |
|---|---|---|
| 공개 | GET /api/leagues, /api/members/:id/public, /api/stats/leaderboard, /api/health | @Public() |
| 회원 | POST /api/match-days/:id/apply, /api/my/participations, /api/payments/my, /api/settlements/my | JWT |
| 관리자 | /api/admin/leagues(+generate-schedule), /api/admin/scores(verify), /api/matching/batch-generate, /api/lanes/:id/assign | JWT + RBAC |
| 웹훅 | POST /api/webhooks/payment/pg-callback | 공개(HMAC 검증) |
인프라 · 운영
ScheduleModule로 오케스트레이션.pg-callback)으로 수신하며 HMAC로 검증.DB 규약
synchronize:false가 원칙입니다(2026-03-27 데이터 손실 사고 이후 코드에 고정). 스키마 변경은 반드시 마이그레이션으로만 반영하며, 현재 69개가 존재합니다.
MigrationInterface 구현, up()/down() 모두 작성(되돌리기 가능). raw SQL은 queryRunner.query()로.database.config.ts, autoLoadEntities:true)과 CLI 전용(typeorm.datasource.ts)을 분리.type. 비트랜잭션 DDL(예: CREATE INDEX CONCURRENTLY)은 -t none으로 실행.확정 대기 · 미확인
아래는 코드 조사에서 확정되지 않았거나 확인되지 않은 항목입니다. 사실처럼 서술하지 않고 따로 표시합니다.
staging-api.abl.kr가 언급되나 배포 여부 미확인. 운영은 abl.plaq.co.kr/api.api_client.dart에 TODO로 남아 있음(운영 SSL 검증 강화 예정).변경 이력
이 문서가 바뀐 내용을 시간순으로 남깁니다. 내용이 바뀌면 본문에서 옛 문장은 취소선으로, 새 내용은 추가 표시로 남기고, 아래에 날짜·요약을 덧붙입니다.