All checks were successful
baekjoon-bot-cicd / build_push_deploy (push) Successful in 2m3s
4.7 KiB
4.7 KiB
Development Guide
작성: AI / 수정: nkey
Project Layout
app.py: FastAPI 엔트리포인트, 라우팅(/,/today,/admin/...)utils.py: 환경변수 헬퍼, solved.ac 검색 쿼리 생성, admin 인증, solved.ac 검색 호출db.py: SQLAlchemy Async 엔진/세션 및get_db()DIworkbook_picker.py: DB에서 “아직 보내지 않은 문제” 1개 선택 + send 기록workbook_enricher.py: solved.acproblem/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
Local Setup
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
# FastAPI runs
uvicorn app:app --reload --host 0.0.0.0 --port 8000
# sanity check
curl -s http://localhost:8000/ | cat
Common Commands
레포에 Makefile/스크립트/테스트 명령이 따로 정의되어 있지 않아, 일반적인 실행 명령만 기재합니다.
# run
uvicorn app:app --reload --host 0.0.0.0 --port 8000
# install deps
pip install -r requirements.txt
Environment Variables (Dev)
- 필수:
DATABASE_URL - Admin API 개발/테스트 시:
ADMIN_PASSWORD /today의 기본값을 바꾸려면 아래 변수를 사용:SOURCE_MODE_DEFAULT,WORKBOOK_ID_DEFAULTDIFFICULTY_MODE_DEFAULT,TAG_MODE_DEFAULT,LANG_DEFAULTDIFFICULTY_EASY|HARD|ALL,TAGS_EASY|HARD|ALL,TAG_PICK*,TAGS_JOIN
DB/Migrations (if applicable)
- 코드에서 참조하는 테이블(DDL은 레포에 없음):
workbooks,workbook_problems,workbook_sends
-- 1) 문제집
CREATE TABLE IF NOT EXISTS workbooks (
id BIGINT PRIMARY KEY, -- BOJ workbook id 그대로 사용
title TEXT,
source TEXT NOT NULL DEFAULT 'boj', -- '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
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);
-- 3) 발송/사용 기록(문제집 단위 중복 방지 핵심)
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);
CI/CD (Gitea Actions)
Workflow: .gitea/workflows/cicd.yml
Trigger
main브랜치로push시 실행
Secrets (레포 Actions/Secrets에 등록)
| Key | Purpose |
|---|---|
NKEY_PAT |
워크플로우에서 Gitea repo를 수동 checkout(fetch)할 때 사용 |
DOCKERHUB_USERNAME |
Docker Hub 이미지 네임스페이스 |
DOCKERHUB_TOKEN |
Docker Hub 로그인 토큰 |
DISCORD_WEBHOOK |
성공/실패 알림 전송 |
Build & Push
- 워크플로우는 아래와 동일한 형태로 이미지를 빌드/푸시합니다.
IMAGE="${DOCKERHUB_USERNAME}/baekjoon-bot:latest"
docker build -t "${IMAGE}" .
docker push "${IMAGE}"
NOTE: 위 커맨드는 기본 Dockerfile을 사용합니다. 레포에 있는 파일은
dockerfile(소문자)이므로, 로컬/CI에서 동일하게 동작시키려면 파일명/옵션(-f dockerfile) 정합성을 확인하세요.
Deploy
- 워크플로우 배포는 레포 내부 파일이 아닌 서버(또는 self-hosted runner)에 존재하는 compose 파일을 사용합니다.
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으로 결과를 전송합니다.