beatmania IIDX 성적을 수집해 난이도표별 달성 현황을 확인하고 다른 사용자와 비교·공유하는 개인 프로젝트입니다. 사용자 웹, FastAPI 백엔드, 운영 어드민을 함께 개발했습니다.
CSV · 북마크릿 크롤러→파싱 · 곡 매칭→성적 스냅샷→비교 · 공유 · 복구
해결 과제
분산된 곡·난이도 정보와 개인 성적을 연결하고, 로그인 세션이 있어야만 조회되는 사이트의 개인 성적을 서버가 대신 크롤링할 수 없는 제약 속에서 수집부터 비교·복구·운영까지 이어지는 서비스가 필요했습니다.
담당 업무 · 구현
- 사용자 웹: Next.js와 TanStack Query로 난이도표, 클리어 현황, 사용자 간 비교, 프로필·팔로우, 활동 히트맵을 구현했습니다.
- 인증: 메모리의 액세스 토큰과 쿠키 기반 갱신 흐름을 연결했습니다. 동시 401 응답의 갱신 요청을 공유 Promise로 합치고 원 요청의 재시도를 1회로 제한했습니다.
- 보안(오픈 리다이렉트 방어): OAuth 콜백의 redirect 쿼리가 외부 사이트로 튕기는 오픈 리다이렉트에 악용될 수 있다는 걸 발견해, 같은 출처의 상대경로만 허용하도록 서버 컴포넌트 단에서 사전 검증했습니다. 브라우저(WHATWG URL 표준)가 제어문자를 제거한 뒤 파싱하는 규칙을 검증 전에 그대로 재현해 프로토콜 상대 URL·백슬래시 우회까지 차단했습니다.
- 브라우저 크롤러(별도 패키지, iinfo-dx-crawler): e-Amusement는 로그인 세션이 있어야 성적이 노출돼 서버 크롤링이 불가능하므로, 사용자 브라우저에서 직접 도는 북마크릿 크롤러를 만들었습니다. 로더 북마크릿과 배포용 로직 스크립트를 분리한 2단 구조로 캐시 무효화 없이 로직만 재배포할 수 있게 했고, 크로스 오리진 API 요청은 X-Upload-Token 헤더 인증으로, 수집 UI는 Shadow DOM으로 페이지와 격리했습니다. 수집 도중 실수로 페이지를 이탈해 스크립트 컨텍스트가 사라지는 것을 막는 beforeunload 가드를 넣고, vitest·jsdom 기반 테스트 8개 스펙과 GitHub Actions→GitHub Pages 자동 배포를 구성했습니다.
- 성적 수집(서버): 북마크릿이 넘긴 CSV 업로드용 단기 토큰을 Redis에 10분 TTL로 관리하고, CSV 파싱·곡 매칭·원본 저장·성적 스냅샷 생성을 연결했습니다.
- 데이터 보존: SHA-256 해시로 동일 CSV의 중복 스냅샷 생성을 방지하고, 현재 스냅샷 전환을 통한 복구와 원본 CSV 다운로드 기능을 제공했습니다.
결과
- 성적 수집(브라우저 크롤러) → 난이도표 매칭 → 비교·공유 → 과거 성적 복구로 이어지는 전체 흐름을 4개 저장소로 구현했습니다.
- 로그인 세션 제약으로 서버 크롤링이 불가능한 데이터를, 별도 테스트된 브라우저 크롤러 패키지로 안전하게 수집하는 구조를 직접 설계했습니다.
- 사용자 기능과 별도로 수집 대상·실행 이력·권한을 관리할 수 있는 운영 도구를 마련했습니다.
TypeScriptNext.jsReact RouterTanStack QueryTailwindCSS