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:
@@ -1,28 +1,23 @@
|
||||
# Development Guide
|
||||
작성: AI / 수정: nkey
|
||||
|
||||
## Project Layout
|
||||
- `app.py` : FastAPI 엔트리포인트, 라우팅(`/`, `/today`, `/admin/...`)
|
||||
- `utils.py` : 환경변수 헬퍼, solved.ac 검색 쿼리 생성, admin 인증, solved.ac 검색 호출
|
||||
- `db.py` : SQLAlchemy Async 엔진/세션 및 `get_db()` DI
|
||||
- `workbook_picker.py` : DB에서 “아직 보내지 않은 문제” 1개 선택 + send 기록
|
||||
- `workbook_enricher.py` : solved.ac `problem/show`로 workbook 문제 메타(제목/레벨/태그) 채우기
|
||||
- `workbook_importer.py` : BOJ 문제집 페이지에서 문제 ID 수집 + DB upsert (현재 `app.py`에서 라우팅은 주석 처리됨)
|
||||
- `requirements.txt` : 파이썬 의존성
|
||||
- `dockerfile` : 컨테이너 실행 정의(uvicorn, 8000)
|
||||
## 프로젝트 구조
|
||||
|
||||
## Prerequisites
|
||||
- Python 3.12+ 권장 (Docker 이미지 기준: `python:3.12-slim`)
|
||||
- PostgreSQL (workbook 모드/관리 API 사용 시)
|
||||
- pip / venv
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `app.py` | FastAPI 엔트리포인트. `/`, `/today`, `/admin/*` 라우팅 |
|
||||
| `utils.py` | 환경변수 헬퍼, solved.ac 검색 쿼리 빌드, HTTP 호출(retry), admin 인증 |
|
||||
| `db.py` | SQLAlchemy async 엔진/세션 설정, `get_db()` DI |
|
||||
| `workbook_picker.py` | 문제집에서 미발송 문제 1개 선택 + `workbook_sends` 기록 |
|
||||
| `workbook_enricher.py` | solved.ac `problem/show`로 문제 메타데이터 채우기 |
|
||||
| `workbook_importer.py` | BOJ 문제집 페이지 크롤링 (현재 미사용, BOJ 스크래핑 차단) |
|
||||
|
||||
## 로컬 셋업
|
||||
|
||||
## Local Setup
|
||||
```bash
|
||||
git clone https://nkeystudy.site/gitea/nkey/baekjoon-bot.git
|
||||
cd baekjoon-bot
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m venv .venv && source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
cat > .env <<'EOF'
|
||||
@@ -30,52 +25,39 @@ DATABASE_URL=postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME
|
||||
ADMIN_PASSWORD=change-me
|
||||
EOF
|
||||
|
||||
# FastAPI runs
|
||||
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# sanity check
|
||||
curl -s http://localhost:8000/ | cat
|
||||
```
|
||||
|
||||
## Common Commands
|
||||
레포에 Makefile/스크립트/테스트 명령이 따로 정의되어 있지 않아, 일반적인 실행 명령만 기재합니다.
|
||||
```bash
|
||||
# run
|
||||
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
||||
## 의존성
|
||||
|
||||
# install deps
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
`requirements.txt` 기준:
|
||||
|
||||
## Environment Variables (Dev)
|
||||
- 필수: `DATABASE_URL`
|
||||
- Admin API 개발/테스트 시: `ADMIN_PASSWORD`
|
||||
- `/today`의 기본값을 바꾸려면 아래 변수를 사용:
|
||||
- `SOURCE_MODE_DEFAULT`, `WORKBOOK_ID_DEFAULT`
|
||||
- `DIFFICULTY_MODE_DEFAULT`, `TAG_MODE_DEFAULT`, `LANG_DEFAULT`
|
||||
- `DIFFICULTY_EASY|HARD|ALL`, `TAGS_EASY|HARD|ALL`, `TAG_PICK*`, `TAGS_JOIN`
|
||||
- `fastapi` / `uvicorn` - 웹 프레임워크 / ASGI 서버
|
||||
- `sqlalchemy` / `asyncpg` - 비동기 PostgreSQL ORM
|
||||
- `requests` / `httpx` - 동기/비동기 HTTP 클라이언트
|
||||
- `python-dotenv` - `.env` 파일 로딩
|
||||
- `beautifulsoup4` / `lxml` - HTML 파싱 (workbook_importer용, 현재 미사용)
|
||||
|
||||
## DB/Migrations (if applicable)
|
||||
- 코드에서 참조하는 테이블(DDL은 레포에 없음):
|
||||
- `workbooks`, `workbook_problems`, `workbook_sends`
|
||||
```bash
|
||||
-- 1) 문제집
|
||||
## 데이터베이스
|
||||
|
||||
PostgreSQL 필수. DDL은 레포에 포함되어 있지 않으므로 수동으로 생성해야 한다.
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS workbooks (
|
||||
id BIGINT PRIMARY KEY, -- BOJ workbook id 그대로 사용
|
||||
id BIGINT PRIMARY KEY,
|
||||
title TEXT,
|
||||
source TEXT NOT NULL DEFAULT 'boj', -- 'boj' 고정(필요하면 확장)
|
||||
source TEXT NOT NULL DEFAULT 'boj',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 2) 문제집-문제 목록
|
||||
CREATE TABLE IF NOT EXISTS workbook_problems (
|
||||
workbook_id BIGINT NOT NULL REFERENCES workbooks(id) ON DELETE CASCADE,
|
||||
problem_id INTEGER NOT NULL,
|
||||
title_ko TEXT,
|
||||
title_en TEXT,
|
||||
level INTEGER, -- solved.ac level(0~30)
|
||||
tags TEXT[] DEFAULT NULL, -- optional
|
||||
level INTEGER,
|
||||
tags TEXT[] DEFAULT NULL,
|
||||
added_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (workbook_id, problem_id)
|
||||
);
|
||||
@@ -83,7 +65,6 @@ CREATE TABLE IF NOT EXISTS workbook_problems (
|
||||
CREATE INDEX IF NOT EXISTS idx_workbook_problems_workbook
|
||||
ON workbook_problems(workbook_id);
|
||||
|
||||
-- 3) 발송/사용 기록(문제집 단위 중복 방지 핵심)
|
||||
CREATE TABLE IF NOT EXISTS workbook_sends (
|
||||
workbook_id BIGINT NOT NULL REFERENCES workbooks(id) ON DELETE CASCADE,
|
||||
problem_id INTEGER NOT NULL,
|
||||
@@ -93,40 +74,26 @@ CREATE TABLE IF NOT EXISTS workbook_sends (
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workbook_sends_workbook
|
||||
ON workbook_sends(workbook_id);
|
||||
|
||||
```
|
||||
|
||||
## CI/CD (Gitea Actions)
|
||||
Workflow: `.gitea/workflows/cicd.yml`
|
||||
### 테이블 설명
|
||||
|
||||
### Trigger
|
||||
- `main` 브랜치로 `push` 시 실행
|
||||
| 테이블 | 역할 |
|
||||
|--------|------|
|
||||
| `workbooks` | 문제집 메타 (BOJ workbook id를 PK로 사용) |
|
||||
| `workbook_problems` | 문제집-문제 매핑 + 메타데이터 (제목, 난이도, 태그) |
|
||||
| `workbook_sends` | 발송 기록. 중복 추천 방지의 핵심 |
|
||||
|
||||
### Secrets (레포 Actions/Secrets에 등록)
|
||||
| Key | Purpose |
|
||||
|---|---|
|
||||
| `NKEY_PAT` | 워크플로우에서 Gitea repo를 수동 checkout(fetch)할 때 사용 |
|
||||
| `DOCKERHUB_USERNAME` | Docker Hub 이미지 네임스페이스 |
|
||||
| `DOCKERHUB_TOKEN` | Docker Hub 로그인 토큰 |
|
||||
| `DISCORD_WEBHOOK` | 성공/실패 알림 전송 |
|
||||
## 환경변수
|
||||
|
||||
### Build & Push
|
||||
- 워크플로우는 아래와 동일한 형태로 이미지를 빌드/푸시합니다.
|
||||
```bash
|
||||
IMAGE="${DOCKERHUB_USERNAME}/baekjoon-bot:latest"
|
||||
docker build -t "${IMAGE}" .
|
||||
docker push "${IMAGE}"
|
||||
```
|
||||
필수:
|
||||
- `DATABASE_URL` - PostgreSQL 연결 URL (미설정 시 앱 시작 불가)
|
||||
|
||||
> NOTE: 위 커맨드는 기본 Dockerfile을 사용합니다. 레포에 있는 파일은 `dockerfile`(소문자)이므로, 로컬/CI에서 동일하게 동작시키려면 파일명/옵션(`-f dockerfile`) 정합성을 확인하세요.
|
||||
Admin API 사용 시:
|
||||
- `ADMIN_PASSWORD` - `X-Admin-Password` 헤더와 비교
|
||||
|
||||
### Deploy
|
||||
- 워크플로우 배포는 레포 내부 파일이 아닌 서버(또는 self-hosted runner)에 존재하는 compose 파일을 사용합니다.
|
||||
```bash
|
||||
docker compose -f /nkeysworld/compose.apps.yml pull baekjoon-bot
|
||||
docker compose -f /nkeysworld/compose.apps.yml up -d baekjoon-bot
|
||||
docker image prune -f
|
||||
```
|
||||
|
||||
### Notifications
|
||||
- 워크플로우는 항상(`if: always()`) Discord webhook으로 결과를 전송합니다.
|
||||
검색 기본값 커스터마이징:
|
||||
- `SOURCE_MODE_DEFAULT`, `WORKBOOK_ID_DEFAULT`
|
||||
- `DIFFICULTY_MODE_DEFAULT`, `TAG_MODE_DEFAULT`, `LANG_DEFAULT`
|
||||
- `DIFFICULTY_EASY`/`HARD`/`ALL`, `TAGS_EASY`/`HARD`/`ALL`
|
||||
- `TAG_PICK`, `TAG_PICK_EASY`/`HARD`/`ALL`, `TAGS_JOIN`
|
||||
|
||||
Reference in New Issue
Block a user