On this page
JunkBox apps/raid-api 가이드
1. 애플리케이션 역할
apps/raid-api는 JunkBox의 독립 레이드 운영 백엔드 앱임.
공대원 목록, 공대원 관리, WCL 점수 전체 갱신, 공대 구인 알림 수집·분석·매칭, 레이드 전용 서버 렌더링 진입점을 담당함.
공통 로그인과 공통 권한 체계를 재사용하면서, 레이드 도메인 데이터와 화면 자산 서빙을 한 앱에서 통합함.
공통 코드와 메타 조회는 hub-api의 내부 master-data API를 통해 수행함.
2. 런타임 구성
FastAPI 애플리케이션 엔트리포인트는 junkbox_raid_api.main:app임.
기본 루트 /는 /raid/members로 리다이렉트됨.
상태 확인 경로는 /health임.
OpenAPI 노출 경로는 /docs, /redoc, /openapi.json임.
apps/raid-ui/dist를 /raid-ui 경로로 직접 마운트함.
libs/shared-ui 산출 자산은 /shared-ui 경로로 직접 마운트함.
앱 favicon은 포털 메뉴의 menu_type_code=APP, icon_type_code=APP_ICON, icon_val 조합에서 raid 앱 아이콘 slug를 해석해 사용하며, 조회 실패 시 raid slug를 fallback으로 사용함.
CORS 허용 origin 목록은 shared-core 설정의 server.cors_origins를 공통으로 사용함.
2.1 로컬 개발 서버 연동
raid-api는 18009 포트 기준 백엔드로 실행할 수 있음.
apps/raid-ui는 15009 포트 기준 Vite 개발 서버로 실행할 수 있음.
페이지 HTML은 18009가 렌더링하고, React 엔트리와 HMR 자산은 15009/raid-ui/* 경로에서 로드함.
2.2 운영 배포 기준
운영 컨테이너 기본 포트는 8009임.
운영 이미지는 apps/raid-ui/dist, libs/shared-ui 자산, raid-api 실행 코드를 함께 포함함.
운영 환경변수는 /sorc001/junkbox/env/.env.shared와 /sorc001/junkbox/env/.env.prod를 함께 참조하고, 컨테이너 내부 ENV_FILE=/app/env/.env.prod 기준으로 로딩함.
운영과 개발 Compose는 호스트 /sorc001/junkbox/db를 컨테이너 /app/db에 마운트함.
컨테이너의 RAID_DATABASE_URL은 sqlite+aiosqlite:////app/db/raid.sqlite3임.
운영 로그 볼륨은 /logs001/junkbox/raid-api 호스트 경로를 컨테이너 내부 /app/logs에 연결함.
공통 애플리케이션 로그 파일 경로는 /app/logs/app.log임.
RAID_BASE_URL, RAID_DATABASE_URL, WCL_CLIENT_ID, WCL_CLIENT_SECRET, WCL_TOKEN_URL, WCL_API_URL 설정 정합성이 중요함.
2.3 공통 로깅 초기화
앱 import 시점에 shared-core의 setup_app_logging()을 호출함.
설정값은 settings.modules.raid.logging에서 읽음.
로그 sink는 콘솔 출력과 파일 출력 두 축으로 동시에 구성됨.
2.4 Raid SQLite engine 초기화
앱 import 시점에 shared-core의 create_async_engine(settings.modules.raid.database_url)를 호출하여 Raid 전용 SQLite engine을 초기화함.
로컬 비컨테이너 기본 RAID_DATABASE_URL은 sqlite+aiosqlite:////sorc001/junkbox/db/raid.sqlite3임.
SQLite 연결은 DB 파일의 부모 경로를 준비하고 PRAGMA journal_mode=WAL, PRAGMA busy_timeout=5000, PRAGMA foreign_keys=ON을 적용함.
Raid SQLite의 단일 writer는 raid-api임. 다른 앱은 이 파일을 직접 write하지 않음.
3. 라우팅 구조
3.1 인증 API
raid-api는 자체 인증 발급 API를 제공하지 않음.
사용자 credential 검증, JWT 발급, logout API는 hub-api가 담당함.
raid-api는 shared-core 인증 의존성으로 hub-api가 발급한 JWT를 검증함.
3.2 raid API
GET /api/raid/members
POST /api/raid/members/roles
POST /api/raid/scores/all
GET /api/raid/members/manage
POST /api/raid/members/manage
POST /api/raid/members/manage/sort
DELETE /api/raid/members/manage/{raid_member_id}
DELETE /api/raid/members/manage/member/{member_id}
GET /api/raid/recruitment/profiles
POST /api/raid/recruitment/profiles
PUT /api/raid/recruitment/profiles/{profile_id}
GET /api/raid/recruitment/results
공대원 목록 조회·저장 계약
GET /api/raid/members는 raid_id, difficulty_code, 반복 가능 쿼리 member_category_codes를 수용함.
member_category_codes를 생략하면 활성 공대원 구분 전체를 조회함. 값이 전달되면 서버는 전달값과 무관하게 SELF를 항상 조회 대상에 포함함.
응답 행은 공통코드 RAID_MEMBER_CATEGORY.sort_order_no와 레이드 멤버 정렬 순서를 기준으로 정렬함.
응답의 server_slug는 SERVER.attribute02 값이며, UI가 Warcraft Logs 캐릭터 URL을 만들 때 사용함.
응답의 wow_check_url은 https://wow-check.ryuar.in/ 고정 base URL에 선택 difficulty_code를 d 값으로, 캐릭터명과 SERVER.attribute01을 결합한 값을 q 값으로 사용해 구성함.
POST /api/raid/members/manage는 공대원 풀 저장 시 member_category_code를 함께 저장함. 신규 공대원 풀 행의 기본 구분은 RAID_MEMBER임.
3.3 공대 구인 Automation 내부 API
POST /api/raid/internal/automation/recruitment-scan은 게시판 목록 수집, 신규 글 본문 분석, 프로필 매칭을 수행함. 요청 body의 dry_run은 선택값임.
POST /api/raid/internal/automation/recruitment-deliver-pending은 보류 중인 매칭을 발송 가능 시간대와 중복 발송 조건으로 판정한 뒤 Message internal API에 전달함.
두 경로는 일반 사용자 JWT 경로가 아니며 require_automation_job 검증을 통과해야 함.
호출자는 AUTOMATION_SERVICE_TOKEN, X-Junkbox-Service: automation, 허용된 X-Junkbox-Job-Type, Idempotency-Key를 제공해야 함.
3.4 웹 화면
GET /raid/members
GET /raid/members/manage
GET /raid/recruitment
GET /error/403
/error/403은 하위 호환 진입점이며 별도 권한 오류 페이지를 렌더링하지 않고 hub-api 로그인 화면의 권한 없음 alert 흐름으로 연결함.
4. 인증 및 권한 흐름
4.1 토큰 입력 경로
API는 Authorization: Bearer 헤더와 COOKIE_NAME 설정값 기준 HttpOnly 쿠키를 모두 수용함.
현재 기본 쿠키 이름은 JUNKBOX_AUTH임.
인증 해석 우선순위는 Authorization 헤더가 먼저임.
4.2 로그인과 페이지 접근 흐름
보호 페이지는 강제 인증 구조를 사용함.
비로그인 요청은 hub-api 공통 로그인 화면 /login?redirect=...&required_module=RAID로 리다이렉트됨.
로그인 성공 시 redirect 값이 허용된 경로 또는 허용된 앱 origin이면 해당 위치로 이동함.
요구 모듈 권한이 없으면 hub-api 공통 로그인 화면으로 이동하며 permission_denied=1을 전달함.
공통 로그인 화면은 권한 없음 alert를 한 번 표시하고 URL의 permission_denied 플래그를 제거함.
allowed_modules에 RAID가 포함된 사용자만 레이드 화면과 API에 접근할 수 있음.
4.3 슬라이딩 세션 갱신
raid-api는 FastAPI 앱 초기화 시 shared-core의 SlidingSessionMiddleware를 등록함.
브라우저 쿠키 기반 요청은 hub 내부 인증 API를 통해 세션 갱신 필요 여부를 확인함.
raid-api의 JSON API와 HTML 페이지 접근 검증은 일반 사용자 JWT에 대해 hub 내부 인증 API의 세션 유효성 검증을 사용함.
세션 갱신 판단과 새 JWT 발급은 hub-api의 POST /api/internal/auth/refresh가 담당함.
만료되었거나 유효하지 않거나 hub에서 revoke된 토큰은 미들웨어가 갱신하지 않으며, 표준 인증 실패 흐름에서 로그인 재진입 또는 JSON 401로 처리됨.
5. 데이터 저장 책임
5.1 레이드 도메인 저장소
레이드 도메인의 소스 오브 트루스는 /sorc001/junkbox/db/raid.sqlite3 SQLite 파일임.
컨테이너 내부 표준 경로는 /app/db/raid.sqlite3임.
RAID_DATABASE_URL은 Raid 도메인 DB 연결에만 사용하며 Raid 런타임은 전역 DATABASE_URL을 사용하지 않음.
공대원 풀은 tb_member_m을 기준으로 관리함.
tb_member_m.member_category_code는 공대원 구분 코드이며 기본값은 RAID_MEMBER임. 구분은 레이드별 배치가 아닌 공대원 풀 속성이므로 한 캐릭터의 구분은 모든 레이드 소속에 공통 적용됨.
레이드 소속과 정렬 순서는 tb_raid_member_n을 기준으로 관리함.
난이도별 레이드 전체 점수는 tb_raid_member_raid_score_n을 사용함.
보스별 점수와 최근 갱신 시각은 tb_raid_member_boss_score_n을 사용함.
레이드, 난이도, 보스 메타는 Raid SQLite에 복제하지 않고 hub-api internal master-data API 계약으로 조회함.
tb_raid_member_raid_score_n의 PK는 (raid_member_id, raid_id, difficulty_code)임.
tb_raid_member_boss_score_n의 PK는 (raid_member_id, boss_id, difficulty_code)임.
SQLite 파일 간 또는 Hub 데이터에 대한 DB 레벨 FK를 전제로 하지 않음.
같은 공대원이 여러 레이드에 중복 소속될 수 있음.
앱 시작 시 공대원 구분 컬럼이 없는 기존 Raid SQLite에 member_category_code를 추가하고 기존 행을 RAID_MEMBER로 보정하는 멱등 스키마 보정이 수행됨.
삭제 정책은 물리 삭제 기준임.
5.2 공통 master-data 의존 구조
레이드, 난이도, 역할, 근/원, 직업, 서버, 보스 메타는 hub-api 내부 master-data API를 통해 조회함.
공대원 구분은 RAID_MEMBER_CATEGORY 공통코드를 사용하며, 활성 코드의 sort_order_no가 목록·관리 선택지의 표시 순서임.
SERVER.attribute01은 wow-check 서버 식별자, SERVER.attribute02는 Warcraft Logs URL 서버 slug, SERVER.attribute03은 Warcraft Logs region 값으로 사용함.
앱 내부에서 공통 코드와 메타를 직접 DB 조회하지 않음.
raid-api는 shared-core.masterdata.MasterDataService를 통해 공통 코드와 메타를 조회함.
내부 master-data 조회 결과는 shared-core의 TTL 캐시를 통해 재사용함.
5.3 WCL 연동
WCL 연동은 settings.raid.wcl 설정을 사용함.
OAuth 토큰 발급 후 GraphQL API를 호출하는 구조를 사용함.
레이드 화면에서는 페이지 전체 갱신만 지원하며, 개별 행 단건 갱신은 제공하지 않음.
점수 갱신은 현재 레이드의 전체 멤버 집합을 대상으로 하며 공대원 구분으로 갱신 대상을 제외하지 않음.
점수 갱신은 복합 PK로 기존 점수 row를 조회한 뒤 생성 또는 갱신하여 전체 점수, 보스별 점수, 최근 업데이트 시각을 함께 동기화함.
점수 생성·갱신 시각은 KST(UTC+9) 기준 timezone 없는 datetime으로 Raid SQLite에 저장함. 목록 응답의 전체 최근 업데이트 시각은 선택 레이드·난이도의 tb_raid_member_raid_score_n.updated_at 또는 created_at 최댓값을 변환 없이 문자열로 반환함.
5.4 공대 구인 알림 저장소와 수집 범위
공대 구인 알림은 와우 인벤 파티찾기 게시판의 고정 원본 https://www.inven.co.kr/board/wow/2972 중 [공격대_구인] 분류만 대상으로 함.
원본 URL, CSS selector, 전송 webhook은 프로필에서 변경하지 않음.
최초 수집은 현재 목록 첫 페이지에 노출된 [공격대_구인] 글을 대상으로 하며 과거 페이지를 순회하지 않음. 이후 수집은 아직 저장되지 않은 inven_post_no만 신규 글로 처리함.
목록은 수집 실행당 한 번 요청하고, 신규 글의 본문만 추가 요청함. 댓글, 이미지, 첨부 파일은 수집하지 않음.
본문은 분석 요청이 끝날 때까지 메모리에만 존재하며 DB, AI 사용량 로그 입력 파라미터, Telegram 메시지에 저장하지 않음.
tb_raid_recruitment_profile_m은 profile_id, name, user_id, is_active, allowed_weekdays_json, weekly_count, additional_keywords_json, excluded_keywords_json, allowed_start_time, allowed_end_time, 생성·수정 시각을 저장함.
프로필 제목은 사용자의 식별용 이름이며 자동 기본값을 두지 않음. 직업, 역할, 전문화, 프리셋, 자동 검색어 설정은 프로필 모델과 저장소에 존재하지 않음.
tb_raid_recruitment_post_n은 원본 게시글 번호, URL, 제목, 본문 해시, 수집·분석 시각, 구조화 분석 결과, 분석 상태와 오류 요약만 저장함. 원문 본문 컬럼은 두지 않음.
tb_raid_recruitment_match_n은 프로필과 게시글 조합, 판정, 판정 요약, 알림 상태, 보류·발송 시각, delivery key를 저장함. (profile_id, post_id) 유니크 제약으로 같은 프로필·게시글의 중복 매칭을 막음.
5.5 분석, 매칭, 발송 책임 경계
신규 본문 분석은 shared-ai의 DirectAiRunner와 SharedAiRuntimeFactory를 사용하며, 라우팅 키 RAID_RECRUITMENT_ANALYSIS의 구조화 출력 계약을 따름.
AI 사용량 기록은 HubApiAiUsageLogWriter를 통해 Hub에 남김. Raid SQLite에 별도 AI 사용량 로그를 만들지 않으며, 기록에는 원문 본문을 포함하지 않음.
분석 결과는 공격대 구인 여부, 요일, 주당 횟수, 구인 인식어, 제외 사유, 신뢰도, 근거를 구조화해 사용함. 게시글 본문은 신뢰할 수 없는 입력으로 취급함.
매칭은 요일·주당 횟수 조건을 먼저 검사하고, 분석된 구인 인식어에 제외 검색어가 하나라도 있으면 제외함. 추가 검색어는 하나 이상 일치해야 함.
일정 정보가 불명확하거나 분석 신뢰도가 낮으면 검토 필요 상태로 남김. 추가 검색어와 제외 검색어는 직업·역할별 고정 규칙을 대체하는 유일한 검색 조건임.
발송 시간대와 최종 중복 판정은 Raid가 소유함. 발송 대상은 제목, 판정, 요약, 원문 URL만 포함하며 원문 본문을 포함하지 않음.
실제 Telegram 전달, 전송 이력, 재시도는 Message가 소유함. Raid는 Message SQLite를 직접 읽거나 쓰지 않으며, Message internal API 성공 후에만 매칭을 발송 완료로 전환함.
raid-recruitment:{match_id} 형식의 idempotency key로 전달 요청을 식별함.
5.6 공대 구인 스키마 초기화와 예약 실행
앱 lifespan은 공대 구인 테이블 스키마를 초기화함. 앱 자체 scheduler loop는 두지 않음.
Automation이 raid.recruitment-scan과 raid.recruitment-deliver-pending 작업의 cron, 실행 claim, lease, 재시도, 실행 이력, idempotency를 소유함.
기본 작업은 수집·분석을 매시 정각, 보류 알림 발송을 5분마다 실행하며 timeout은 60초, 최대 시도는 3회, backoff는 60초임.
6. 화면 진입 데이터 구성
서버 템플릿은 공통 베이스 템플릿과 raid-ui React 마운트 지점을 제공함.
페이지는 page_props.view 값을 통해 members, manage, recruitment 화면을 구분함.
초기 props에는 현재 사용자, 레이드 관련 코드 목록, 현재 선택 가능한 필터 목록, RAID_MEMBER_CATEGORY 활성 목록이 포함됨.
서버 템플릿 문구는 템플릿 또는 라우터 컨텍스트에 한국어 문구로 직접 작성함.
공통코드 라벨과 메타 설정값처럼 데이터 자체가 마스터데이터인 값만 DB 조회 구조를 사용함.
7. API 계약 특성
두 화면 모두 기본적으로 풀 로딩형 Tabulator 편집 화면을 사용함.
공대원 목록 저장은 역할, 근/원, 참여 여부, 정렬 순서 같은 레이드 소속 정보를 기준으로 처리함.
공대원 관리 저장은 공대원 풀 자체의 생성, 수정, 삭제와 공대원 구분 저장을 기준으로 처리함.
신규 행 삭제는 프런트 메모리에서만 제거되고, 저장된 행 삭제만 실제 삭제 API를 호출함.
공대원 관리의 일괄 삭제는 선택 체크박스 기반으로 수행함.
공대 구인 프로필 CRUD와 최근 탐색 결과 조회는 전용 JSON API를 사용함.
8. 배포 및 운영 체크
Docker Compose 서비스명은 junkbox-raid임.
GitHub Actions 배포 감지 경로와 서비스 정책에 raid-api, raid-ui, junkbox-raid가 포함됨.
운영 reverse proxy는 RAID_BASE_URL이 8009 컨테이너로 연결되는 전제를 가짐.
점수 갱신 기능을 사용하려면 WCL 인증 환경변수가 모두 채워져 있어야 함.
Raid SQLite 생성과 dump 기반 데이터 적재는 scripts/migrate_raid_pg_dump_to_sqlite.py를 사용함.
이관 스크립트의 기본 입력은 프로젝트 루트 dbjbp1_20260719-080001.sql, 기본 출력은 /sorc001/junkbox/db/raid.sqlite3임.
기존 raid.sqlite3은 기본적으로 덮어쓰지 않으며, --force 사용 시 SQLite backup API로 사전 백업한 뒤 교체함.
이관 스크립트는 raid dump의 tb_member_m, tb_raid_member_n, tb_raid_member_raid_score_n, tb_raid_member_boss_score_n COPY 블록만 적재하고 row count와 복합 PK 중복을 검증함.
9. 관련 문서
프로젝트 전체 구조는 docs/junkbox/common/guide-jb-core.md를 따름.
공통 설정, DB, 인증은 docs/junkbox/libs/guide-lib-shared-core.md를 따름.
공통 UI 자산은 docs/junkbox/libs/guide-lib-shared-ui.md를 따름.
레이드 프런트 구조는 docs/junkbox/apps/guide-apps-raid-ui.md를 따름.