100 lines
3.2 KiB
Markdown
100 lines
3.2 KiB
Markdown
# Development Guide
|
|
|
|
## 프로젝트 구조
|
|
|
|
| 파일 | 역할 |
|
|
|------|------|
|
|
| `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 스크래핑 차단) |
|
|
|
|
## 로컬 셋업
|
|
|
|
```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
|
|
|
|
cat > .env <<'EOF'
|
|
DATABASE_URL=postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME
|
|
ADMIN_PASSWORD=change-me
|
|
EOF
|
|
|
|
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
|
```
|
|
|
|
## 의존성
|
|
|
|
`requirements.txt` 기준:
|
|
|
|
- `fastapi` / `uvicorn` - 웹 프레임워크 / ASGI 서버
|
|
- `sqlalchemy` / `asyncpg` - 비동기 PostgreSQL ORM
|
|
- `requests` / `httpx` - 동기/비동기 HTTP 클라이언트
|
|
- `python-dotenv` - `.env` 파일 로딩
|
|
- `beautifulsoup4` / `lxml` - HTML 파싱 (workbook_importer용, 현재 미사용)
|
|
|
|
## 데이터베이스
|
|
|
|
PostgreSQL 필수. DDL은 레포에 포함되어 있지 않으므로 수동으로 생성해야 한다.
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS workbooks (
|
|
id BIGINT PRIMARY KEY,
|
|
title TEXT,
|
|
source TEXT NOT NULL DEFAULT 'boj',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
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,
|
|
tags TEXT[] DEFAULT NULL,
|
|
added_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (workbook_id, problem_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_workbook_problems_workbook
|
|
ON workbook_problems(workbook_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS workbook_sends (
|
|
workbook_id BIGINT NOT NULL REFERENCES workbooks(id) ON DELETE CASCADE,
|
|
problem_id INTEGER NOT NULL,
|
|
sent_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (workbook_id, problem_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_workbook_sends_workbook
|
|
ON workbook_sends(workbook_id);
|
|
```
|
|
|
|
### 테이블 설명
|
|
|
|
| 테이블 | 역할 |
|
|
|--------|------|
|
|
| `workbooks` | 문제집 메타 (BOJ workbook id를 PK로 사용) |
|
|
| `workbook_problems` | 문제집-문제 매핑 + 메타데이터 (제목, 난이도, 태그) |
|
|
| `workbook_sends` | 발송 기록. 중복 추천 방지의 핵심 |
|
|
|
|
## 환경변수
|
|
|
|
필수:
|
|
- `DATABASE_URL` - PostgreSQL 연결 URL (미설정 시 앱 시작 불가)
|
|
|
|
Admin API 사용 시:
|
|
- `ADMIN_PASSWORD` - `X-Admin-Password` 헤더와 비교
|
|
|
|
검색 기본값 커스터마이징:
|
|
- `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`
|