# baekjoon-bot 매일 백준(BOJ) 문제를 추천하고 Discord embed payload로 반환하는 FastAPI 서비스. solved.ac 검색 기반 추천과 PostgreSQL 문제집(workbook) 기반 추천 두 가지 모드를 지원하며, n8n 등 외부 스케줄러와 연동하여 Discord 자동 알림을 구성할 수 있다. ## 주요 기능 - **Search 모드**: solved.ac API로 난이도/태그/언어 조건에 맞는 문제를 랜덤 추천 - **Workbook 모드**: DB에 저장된 문제집에서 아직 보내지 않은 문제를 순서대로 또는 랜덤으로 추천 - **Discord embed**: 모든 응답에 Discord webhook용 embed payload 포함 - **Admin API**: 문제집 메타데이터 보강(enrich), 진행상황 초기화(reset) ## 기술 스택 - Python 3.12+ / FastAPI / Uvicorn - SQLAlchemy 2.0 (async) + asyncpg (PostgreSQL) - httpx (비동기 HTTP) / requests (동기 HTTP) - Docker / Gitea Actions CI/CD ## 빠른 시작 ```bash git clone https://nkeystudy.site/gitea/nkey/baekjoon-bot.git cd baekjoon-bot python -m venv .venv && source .venv/bin/activate pip install -r requirements.txt # .env 파일 생성 cat > .env <<'EOF' DATABASE_URL=postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME ADMIN_PASSWORD=change-me EOF uvicorn app:app --host 0.0.0.0 --port 8000 ``` Docker로 실행: ```bash docker build -f dockerfile -t baekjoon-bot:local . docker run --rm -p 8000:8000 --env-file .env baekjoon-bot:local ``` ## API 엔드포인트 | Method | Endpoint | 설명 | 인증 | |--------|----------|------|------| | GET | `/` | 헬스체크 | - | | GET | `/today` | 오늘의 추천 문제 | - | | POST | `/admin/workbooks/{id}/enrich` | 문제집 메타데이터 보강 | O | | DELETE | `/admin/workbooks/{id}/reset` | 문제집 발송 기록 초기화 | O | ### 사용 예시 ```bash # 기본 추천 (search 모드) curl -s "http://localhost:8000/today" # 난이도/태그/언어 지정 curl -s "http://localhost:8000/today?difficulty=6..10&tags=dp,graphs&lang=ko" # 문제집 모드 curl -s "http://localhost:8000/today?source_mode=workbook&workbook_id=12345" # 문제집 메타 보강 curl -s -X POST -H "X-Admin-Password: change-me" \ "http://localhost:8000/admin/workbooks/12345/enrich" # 문제집 진행상황 초기화 curl -s -X DELETE -H "X-Admin-Password: change-me" \ "http://localhost:8000/admin/workbooks/12345/reset" ``` ## Workbook 모드 셋업 Workbook 모드를 사용하려면 DB에 문제집 데이터를 직접 넣어야 한다. (BOJ 자동 크롤링은 스크래핑 차단으로 현재 미지원) ### 1. 문제집 등록 ```sql -- id는 BOJ 문제집 URL의 번호 (acmicpc.net/workbook/view/12345) INSERT INTO workbooks (id, title) VALUES (12345, '내 문제집 이름'); ``` ### 2. 문제 목록 등록 ```sql -- 문제 번호만 넣으면 됨 (제목/난이도/태그는 3단계에서 자동으로 채워짐) INSERT INTO workbook_problems (workbook_id, problem_id) VALUES (12345, 1000), (12345, 1001), (12345, 1002); ``` ### 3. 메타데이터 자동 보강 ```bash # solved.ac API로 제목, 난이도, 태그를 자동으로 가져와서 DB에 채움 curl -s -X POST \ -H "X-Admin-Password: change-me" \ "http://localhost:8000/admin/workbooks/12345/enrich" ``` 이후 `/today?source_mode=workbook&workbook_id=12345`로 문제를 추천받을 수 있다. 모든 문제를 다 뽑은 뒤 다시 처음부터 시작하려면 reset API를 호출하면 된다. ## 환경변수 | 변수 | 설명 | 기본값 | 필수 | |------|------|--------|:----:| | `DATABASE_URL` | PostgreSQL 비동기 연결 URL | - | O | | `ADMIN_PASSWORD` | Admin API 비밀번호 | `""` | Admin API 사용 시 | | `SOURCE_MODE_DEFAULT` | 기본 소스 모드 (`search`/`workbook`) | `search` | | | `WORKBOOK_ID_DEFAULT` | workbook 모드 기본 ID | - | | | `DIFFICULTY_MODE_DEFAULT` | 기본 난이도 모드 (`easy`/`hard`/`all`) | `easy` | | | `TAG_MODE_DEFAULT` | 기본 태그 모드 | `easy` | | | `LANG_DEFAULT` | 기본 언어 필터 | `all` | | | `DIFFICULTY_EASY` | easy 난이도 범위 | `6..10` | | | `DIFFICULTY_HARD` | hard 난이도 범위 | `11..15` | | | `DIFFICULTY_ALL` | all 난이도 범위 | `1..30` | | | `TAGS_EASY` / `TAGS_HARD` / `TAGS_ALL` | 태그 프리셋 (CSV) | `""` | | | `TAG_PICK` / `TAG_PICK_EASY` 등 | 태그 선택 정책 (`random`/`none`/전체) | `random` | | | `TAGS_JOIN` | 태그 결합 방식 (`or`/`and`) | `or` | | ## 프로젝트 구조 ``` app.py # FastAPI 엔트리포인트, 라우팅 utils.py # 환경변수 헬퍼, solved.ac 검색 쿼리 빌드, HTTP 호출 db.py # SQLAlchemy async 엔진/세션 workbook_picker.py # 문제집에서 미발송 문제 1개 선택 + 발송 기록 workbook_enricher.py # solved.ac로 문제 메타데이터 채우기 workbook_importer.py # BOJ 문제집 크롤링 (현재 미사용) dockerfile # Docker 이미지 정의 requirements.txt # Python 의존성 ``` ## CI/CD - **Workflow**: `.gitea/workflows/cicd.yml` - **Trigger**: `main` 브랜치 push (단, 문서 파일만 변경된 경우 스킵) - **Flow**: 수동 checkout → Docker Hub 빌드/푸시 → 서버 배포(`compose.apps.yml`) → Discord 알림 ### 필요한 Secrets | Secret | 용도 | |--------|------| | `NKEY_PAT` | Gitea repo fetch 인증 | | `DOCKERHUB_USERNAME` | Docker Hub 네임스페이스 | | `DOCKERHUB_TOKEN` | Docker Hub 로그인 | | `DISCORD_WEBHOOK` | CI/CD 결과 알림 | ## 상세 문서 - [API 레퍼런스](docs/API.md) - [개발 가이드](docs/DEVELOPMENT.md) - [운영 가이드](docs/OPERATIONS.md)