Skip to content

[Feat] 수집 실행 기록 및 admin 수집 상태·수동 실행 API 추가 - #92

Merged
JangDongHo merged 5 commits into
devfrom
feat/scrape-run-admin-api
Sep 25, 2026
Merged

JangDongHo merged 5 commits into
devfrom
feat/scrape-run-admin-api

Conversation

@JangDongHo

@JangDongHo JangDongHo commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

📌 개요

  • 수집기(셔틀, 이후 공지사항·학식·학사일정)의 실행 기록을 DB에 남기고, 관리자가 수집 상태를 조회하거나 수동으로 수집을 실행할 수 있는 admin API를 추가했습니다.
  • API 서버와 배치는 별도 컨테이너이고 배치에는 HTTP 포트가 없어서, scrape_run 테이블을 작업 큐로 사용합니다. API가 실행 요청을 등록하면 배치가 가져가 실행합니다.

✨ 작업 내용

DB (scrape_run)

  • 타입, 트리거(cron/manual), 상태(pending/running/succeeded/failed), 에러 메시지, 생성·시작·종료 시각을 저장합니다.
  • (type) WHERE status IN ('pending','running') 부분 유니크 인덱스를 걸었습니다. 타입별로 대기 또는 실행 중인 run은 하나만 존재할 수 있어서, cron 실행과 수동 실행의 중복 방지를 DB가 맡습니다.

배치

  • 정각 cron 실행 시 잡마다 run을 기록하고 성공/실패로 마무리합니다. 같은 타입이 이미 대기나 실행 중이면 그 잡은 건너뜁니다.
  • 10초마다 pending run을 FOR UPDATE SKIP LOCKED로 가져와 실행합니다. 등록되지 않은 타입이면 실패 처리합니다.
  • 기동할 때 프로세스 종료로 running에 남은 run을 실패 처리합니다. 이렇게 하지 않으면 유니크 인덱스 때문에 해당 타입이 영구히 막힙니다.

admin API (X-ADMIN-API-KEY 헤더 필요)

메서드 경로 설명
GET /api/admin/scrapers 타입별 최근 run, 최근 성공 run
GET /api/admin/scrape-runs type/status 필터, 커서 기반 페이지네이션
GET /api/admin/scrape-runs/:id 단건 조회, 없으면 404
POST /api/admin/scrape-runs { type }로 수동 실행 등록 후 202, 이미 대기/실행 중이면 409
  • AdminApiKeyGuard는 헤더 값을 ADMIN_API_KEY 환경변수와 비교합니다. 비교는 timing-safe 방식이고, 환경변수가 없으면 모든 요청을 막습니다.
  • Swagger의 Authorize(자물쇠) 버튼에서 X-ADMIN-API-KEY를 입력할 수 있습니다.

CI/CD

  • db-migrate.yml을 추가했습니다. supabase db push로 마이그레이션을 적용하는 재사용 워크플로우입니다.
  • API/Batch CD에서 이미지 빌드와 병렬로 마이그레이션을 실행하고, 둘 다 성공해야 deploy가 진행됩니다. 두 CD가 동시에 호출해도 concurrency 그룹으로 순서대로 적용됩니다.
  • 마이그레이션만 바꾼 경우에는 Actions에서 수동 실행합니다. 단독 push 트리거를 두면 concurrency 대기열에서 CD 쪽 마이그레이션 job이 취소되고 deploy가 건너뛰어질 수 있어서 넣지 않았습니다.

nginx

  • Swagger(/api/api-docs)의 IP 제한을 없앴습니다. 전용 location 블록을 지웠고, 이제 /api location이 함께 처리합니다.

🔥 변경 이유

  • 수동 실행을 받는 방법으로 배치에 내부 HTTP 포트를 여는 방식과 Redis 큐도 검토했습니다. 이미 두 서비스가 같은 DB를 쓰고 실행 기록 테이블도 어차피 필요해서, 인프라를 늘리지 않는 DB 큐를 택했습니다. 대신 수동 실행은 최대 10초 늦게 시작됩니다.
  • 기존의 메모리 플래그(isRunning)로는 API와 배치 사이에 락을 공유할 수 없습니다. 그래서 중복 실행 방지를 DB 유니크 인덱스로 옮겼습니다. cron 루프 전체의 중복 실행 방지용 isRunning은 그대로 둡니다.
  • 새 배치는 기동할 때 scrape_run을 조회합니다. 테이블 없이 배포되면 재시작을 반복하므로, 마이그레이션이 deploy보다 먼저 적용되도록 CD에 붙였습니다.

🧪 테스트

  • batch: lint:check, test(5 suites / 26 tests), build 통과
  • app: tsc --noEmit, eslint, test(12 suites / 83 tests) 통과
  • BatchService 테스트: cron run 기록, 중복 시 건너뛰기, pending 처리, 실패 후 다음 run 계속, 중단 run 정리 순서
  • ScrapeRunsService 테스트: 커서 페이지네이션, 404, 409, 타입별 상태 / AdminApiKeyGuard 테스트
  • Postgres 17 컨테이너에서 마이그레이션 적용, 중복 등록 시 유니크 제약 에러, SKIP LOCKED claim, 완료 후 재등록, CHECK 제약, DISTINCT ON 조회 확인
  • Swagger 문서에서 admin 엔드포인트 4개에 X-ADMIN-API-KEY 보안이 적용되는 것 확인
  • actionlint 통과, nginx prod.conf nginx -t 통과
  • 배포 후 수동 실행 → 배치 처리 → 상태 조회 흐름 확인

⚠️ main 머지 전 확인

  • GitHub Secret SUPABASE_DB_URL을 등록해야 합니다. Session pooler URI(포트 5432)를 쓰고, 비밀번호에 특수문자가 있으면 URL 인코딩해야 합니다.
  • supabase migration list로 운영 DB의 마이그레이션 이력을 확인해야 합니다. 예전 마이그레이션을 직접 적용해서 이력에 빠진 버전이 있으면 supabase migration repair --status applied <버전>으로 맞춰야 합니다. 그대로 두면 첫 db push가 예전 마이그레이션을 다시 실행하려다 실패합니다.
  • 서버 .env에 ADMIN_API_KEY를 추가해야 합니다(openssl rand -hex 32). 없으면 admin API가 전부 막힙니다.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새 기능
    • 관리자 API에서 스크래퍼 상태와 실행 기록을 조회하고, 수집 작업을 요청할 수 있습니다. 실행 기록은 유형·상태별로 필터링하고 페이지 단위로 확인할 수 있습니다.
    • 정기 및 수동 수집 작업의 진행 상태와 성공·실패 기록을 확인할 수 있습니다. 실행 중이거나 대기 중인 동일 유형의 작업은 중복으로 등록되지 않습니다.
    • 배포 과정에서 데이터베이스 마이그레이션이 완료된 뒤 서비스가 배포됩니다.
  • 문서
    • API 문서에 관리자 API 키 인증 방식이 표시됩니다.

JangDongHo and others added 5 commits September 25, 2026 18:58
- scrape_run 테이블 마이그레이션 추가 (타입별 대기/실행 중 run 1개 제한 부분 유니크 인덱스)
- cron 실행 시 잡마다 run을 기록하고 성공/실패로 마무리, 같은 타입이 이미 대기/실행 중이면 건너뜀
- 10초 주기로 pending run을 FOR UPDATE SKIP LOCKED로 가져와 실행 (admin API 수동 실행용)
- 기동 시 프로세스 종료로 running에 남은 run을 실패 처리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GET /api/admin/scrapers: 타입별 최근 run과 최근 성공 run
- GET /api/admin/scrape-runs: type/status 필터, 커서 기반 페이지네이션
- GET /api/admin/scrape-runs/:id
- POST /api/admin/scrape-runs: pending run 등록 후 202 반환, 이미 대기/실행 중이면 409
- X-ADMIN-API-KEY 헤더를 ADMIN_API_KEY 환경변수와 비교하는 AdminApiKeyGuard 추가 (미설정 시 차단)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 응답 body에 run id가 있어 헤더가 불필요하고, 전역 prefix(/api)를 하드코딩하던 문제 제거
- @res 및 express Response 의존 제거

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- supabase db push로 마이그레이션을 적용하는 재사용 워크플로우(db-migrate.yml) 추가
- API/Batch CD에서 이미지 빌드와 병렬로 실행하고, 둘 다 성공해야 deploy 진행
- concurrency 그룹으로 두 CD의 동시 호출을 직렬화 (마이그레이션만 바꾼 경우 수동 실행)
- SUPABASE_DB_URL 시크릿(Session pooler URI) 필요

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /api/api-docs 전용 location 블록을 삭제해 /api location에서 함께 처리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: GNU-connect/Server-Node/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: e71bbc45-5f9a-479b-b365-c596100ce58f

📥 Commits

Reviewing files that changed from the base of the PR and between 54e47db and c128c69.

📒 Files selected for processing (24)
  • .github/workflows/api-cd.yml
  • .github/workflows/batch-cd.yml
  • .github/workflows/db-migrate.yml
  • services/api/app/src/api/admin/common/guards/admin-api-key.guard.spec.ts
  • services/api/app/src/api/admin/common/guards/admin-api-key.guard.ts
  • services/api/app/src/api/admin/scrape-runs/application/dtos/results/scraper-status-result.dto.ts
  • services/api/app/src/api/admin/scrape-runs/application/scrape-runs.service.spec.ts
  • services/api/app/src/api/admin/scrape-runs/application/scrape-runs.service.ts
  • services/api/app/src/api/admin/scrape-runs/domain/entities/scrape-run.entity.ts
  • services/api/app/src/api/admin/scrape-runs/infrastructure/scrape-run.repository.ts
  • services/api/app/src/api/admin/scrape-runs/presentation/dtos/requests/create-scrape-run-request.dto.ts
  • services/api/app/src/api/admin/scrape-runs/presentation/dtos/requests/list-scrape-runs-query.dto.ts
  • services/api/app/src/api/admin/scrape-runs/presentation/dtos/responses/scrape-run-response.dto.ts
  • services/api/app/src/api/admin/scrape-runs/presentation/scrape-runs.controller.ts
  • services/api/app/src/api/admin/scrape-runs/scrape-runs.module.ts
  • services/api/app/src/app.module.ts
  • services/api/app/src/main.ts
  • services/api/batch/src/batch/batch.module.ts
  • services/api/batch/src/batch/batch.service.spec.ts
  • services/api/batch/src/batch/batch.service.ts
  • services/api/batch/src/batch/scrape-run/domain/scrape-run.entity.ts
  • services/api/batch/src/batch/scrape-run/scrape-run.repository.ts
  • services/api/nginx/prod.conf
  • supabase/migrations/20260925000000_add_scrape_run.sql
 ____________________________________________________________________
< Sometimes, I feel like a code reviewer in a world of copy-pasters. >
 --------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@JangDongHo
JangDongHo merged commit cfe3218 into dev Sep 25, 2026
2 of 3 checks passed
@JangDongHo
JangDongHo deleted the feat/scrape-run-admin-api branch September 25, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant