Fix: [main] 리드미 수정 및 cicd md 파일 제외
Some checks failed
baekjoon-bot-cicd / build_push_deploy (push) Failing after 1m34s
Some checks failed
baekjoon-bot-cicd / build_push_deploy (push) Failing after 1m34s
This commit is contained in:
211
README.md
211
README.md
@@ -1,112 +1,159 @@
|
||||
# baekjoon-bot
|
||||
작성: AI / 수정: nkey
|
||||
|
||||
매일 백준(BOJ) 문제를 추천해주고, 디스코드 메시지(payload)로 쓸 수 있는 형태로 반환하는 FastAPI 서비스입니다.
|
||||
`/today`에서 추천 문제 + 링크 + Discord embed payload를 생성하며, (선택) PostgreSQL에 문제집(workbook) 진행상황을 저장해 “문제집에서 아직 안 보낸 문제”를 고르는 모드도 제공합니다.
|
||||
디스코드 메시지 자동화는 n8n을 활용하였습니다.
|
||||
매일 백준(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
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
## Quickstart
|
||||
### Recommended
|
||||
```bash
|
||||
# clone
|
||||
git clone https://nkeystudy.site/gitea/nkey/baekjoon-bot.git
|
||||
cd baekjoon-bot
|
||||
|
||||
# venv
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# deps
|
||||
python -m venv .venv && source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# env (필수: DATABASE_URL)
|
||||
# .env 파일 생성
|
||||
cat > .env <<'EOF'
|
||||
DATABASE_URL=postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME
|
||||
# (admin API 사용 시 필수)
|
||||
ADMIN_PASSWORD=change-me
|
||||
EOF
|
||||
|
||||
# run
|
||||
uvicorn app:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# verify
|
||||
curl -s http://localhost:8000/ | cat
|
||||
curl -s "http://localhost:8000/today" | cat
|
||||
```
|
||||
|
||||
### Alternative (optional) - Docker
|
||||
Docker로 실행:
|
||||
```bash
|
||||
docker build -f dockerfile -t baekjoon-bot:local .
|
||||
docker run --rm -p 8000:8000 --env-file .env baekjoon-bot:local
|
||||
|
||||
# verify
|
||||
curl -s http://localhost:8000/ | cat
|
||||
```
|
||||
|
||||
## Requirements
|
||||
- Runtime/Language: Python 3.12+ (Dockerfile 기준)
|
||||
- Dependencies: `fastapi`, `uvicorn`, `sqlalchemy>=2.0`, `asyncpg`, `requests`, `httpx`, `python-dotenv` 등 (`requirements.txt`)
|
||||
- Tools: (optional) Docker
|
||||
## API 엔드포인트
|
||||
|
||||
## Configuration
|
||||
### Environment Variables
|
||||
| Key | Description | Default | Required |
|
||||
|---|---|---:|:---:|
|
||||
| `DATABASE_URL` | SQLAlchemy async DB URL (예: `postgresql+asyncpg://...`) | - | ✅ |
|
||||
| `ADMIN_PASSWORD` | Admin API 인증 비밀번호 (`X-Admin-Password` 헤더와 비교) | `""` | ✅ (admin API) |
|
||||
| `SOURCE_MODE_DEFAULT` | `/today` 기본 소스 모드 | `search` | |
|
||||
| `WORKBOOK_ID_DEFAULT` | `source_mode=workbook`에서 `workbook_id` 미지정 시 기본값 | - | |
|
||||
| `DIFFICULTY_MODE_DEFAULT` | `/today` 기본 난이도 모드 | `easy` | |
|
||||
| `TAG_MODE_DEFAULT` | `/today` 기본 태그 모드 | `easy` | |
|
||||
| `LANG_DEFAULT` | `/today` 기본 언어 | `all` | |
|
||||
| `DIFFICULTY_EASY` | easy 난이도 범위 | `6..10` | |
|
||||
| `DIFFICULTY_HARD` | hard 난이도 범위 | `11..15` | |
|
||||
| `DIFFICULTY_ALL` | all 난이도 범위 | `1..30` | |
|
||||
| `TAGS_EASY` | easy 태그 프리셋(CSV) | `""` | |
|
||||
| `TAGS_HARD` | hard 태그 프리셋(CSV) | `""` | |
|
||||
| `TAGS_ALL` | all 태그 프리셋(CSV) | `""` | |
|
||||
| `TAG_PICK` | 태그 선택 정책 (random/none/전체) | `random` | |
|
||||
| `TAG_PICK_EASY` | easy 태그 선택 정책 | `TAG_PICK` | |
|
||||
| `TAG_PICK_HARD` | hard 태그 선택 정책 | `TAG_PICK` | |
|
||||
| `TAG_PICK_ALL` | all 태그 선택 정책 | `TAG_PICK` 또는 `none` | |
|
||||
| `TAGS_JOIN` | 태그 결합 방식 | `or` | |
|
||||
| Method | Endpoint | 설명 | 인증 |
|
||||
|--------|----------|------|------|
|
||||
| GET | `/` | 헬스체크 | - |
|
||||
| GET | `/today` | 오늘의 추천 문제 | - |
|
||||
| POST | `/admin/workbooks/{id}/enrich` | 문제집 메타데이터 보강 | O |
|
||||
| DELETE | `/admin/workbooks/{id}/reset` | 문제집 발송 기록 초기화 | O |
|
||||
|
||||
### Ports
|
||||
| Service | Port | Description |
|
||||
|---|---:|---|
|
||||
| API | 8000 | FastAPI(Uvicorn) |
|
||||
### 사용 예시
|
||||
|
||||
## Usage (minimal)
|
||||
- 오늘의 추천 문제(검색 모드, 기본값)
|
||||
- `GET /today`
|
||||
- 난이도/태그/언어 지정
|
||||
- `GET /today?difficulty=6..10&tags=dp,graphs&lang=ko,en`
|
||||
- 문제집(workbook) 모드로 추천
|
||||
- `GET /today?source_mode=workbook&workbook_id=12345&workbook_pick=level_asc`
|
||||
- Admin: 문제집 메타(제목/레벨/태그) 보강
|
||||
- `POST /admin/workbooks/{workbook_id}/enrich` + `X-Admin-Password: ...`
|
||||
```bash
|
||||
# 기본 추천 (search 모드)
|
||||
curl -s "http://localhost:8000/today"
|
||||
|
||||
## Docs
|
||||
- Operations: `docs/OPERATIONS.md`
|
||||
- Development: `docs/DEVELOPMENT.md`
|
||||
- API: `docs/API.md`
|
||||
# 난이도/태그/언어 지정
|
||||
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:
|
||||
1) (수동) checkout: Gitea 서브패스(`/gitea`)를 고려해 `git init` + `git fetch`로 소스 가져옴
|
||||
2) Docker Hub 로그인
|
||||
3) 이미지 빌드/푸시: `${DOCKERHUB_USERNAME}/baekjoon-bot:latest`
|
||||
4) 서버 배포: 서버에 존재하는 app 관련 compose 파일(`/nkeysworld/compose.apps.yml`)로 `pull/up -d` 수행
|
||||
5) Discord Webhook으로 성공/실패 알림 전송
|
||||
|
||||
### Required Secrets
|
||||
| Key | Used for |
|
||||
|---|---|
|
||||
| `NKEY_PAT` | workflow 내 수동 checkout 시 Gitea repo fetch 인증 |
|
||||
| `DOCKERHUB_USERNAME` | Docker Hub 이미지 네임스페이스 |
|
||||
| `DOCKERHUB_TOKEN` | Docker Hub 로그인 토큰 |
|
||||
| `DISCORD_WEBHOOK` | CI/CD 결과 알림 전송 |
|
||||
- **Workflow**: `.gitea/workflows/cicd.yml`
|
||||
- **Trigger**: `main` 브랜치 push (단, 문서 파일만 변경된 경우 스킵)
|
||||
- **Flow**: 수동 checkout → Docker Hub 빌드/푸시 → 서버 배포(`compose.apps.yml`) → Discord 알림
|
||||
|
||||
> NOTE: workflow의 빌드 단계는 `docker build -t ... .`(기본 Dockerfile 사용) 형태입니다. 레포의 빌드 파일명은 `dockerfile`(소문자)이므로, CI 환경에서 기본 Dockerfile을 쓰려면 파일명/옵션 정합성을 확인하세요.
|
||||
### 필요한 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)
|
||||
|
||||
Reference in New Issue
Block a user