diff --git a/.cursor/rules/commit-conventional-ko.mdc b/.cursor/rules/commit-conventional-ko.mdc new file mode 100644 index 0000000..a53c4d2 --- /dev/null +++ b/.cursor/rules/commit-conventional-ko.mdc @@ -0,0 +1,55 @@ +--- +description: 커밋 메시지는 Conventional Commits + 한글 본문 +alwaysApply: true +--- + +# 커밋 메시지 (Conventional Commits + 한글) + +## 형식 + +- **한 줄:** `<타입>(선택 범위): <한글 설명>` +- **타입·콜론·공백 뒤 설명은 반드시 한글**로 작성한다. +- 제목은 **50자 전후**, 명령형·현재형으로 짧게 쓴다. (예: "추가함" 대신 "추가") + +## 허용 타입 + +| 타입 | 용도 | +|------|------| +| `feat` | 새 기능 | +| `fix` | 버그 수정 | +| `docs` | 문서만 변경 | +| `style` | 포맷·세미콜론 등, 동작 변화 없음 | +| `refactor` | 리팩터링 (기능 변화 없음) | +| `test` | 테스트 추가·수정 | +| `chore` | 빌드·설정·잡일 | +| `perf` | 성능 개선 | +| `ci` | CI 설정 | +| `build` | 빌드 시스템·의존성 | +| `revert` | 되돌리기 | + +## 예시 + +```text +feat: 로그인 실패 시 텔레그램 알림 전송 +fix: GAME_COUNT 빈 문자열일 때 기본값 적용 +refactor: 설정 로드 로직을 config 모듈로 분리 +docs: README에 NAS 배포 절차 추가 +test: LottoBuyer 로그인 성공 케이스 mock 테스트 +chore: requirements.txt 버전 고정 +ci: Gitea Actions에서 Secret 기반 설정 검증 +``` + +## 범위(선택) + +- 괄호로 모듈을 짧게: `feat(lotto_buyer): 세션 만료 시 재로그인 시도` +- 필요할 때만 사용. + +## 본문이 필요할 때 + +- 제목 다음 빈 줄 뒤 **본문도 한글**로 상세 설명. +- 푸터: `BREAKING CHANGE:` 등은 프로젝트에 필요할 때만 영문 키워드 유지 가능. + +## 금지 + +- 타입 없이 자유 문장만 쓰기 +- 제목 전체를 영어만으로 쓰기 (이 프로젝트 기본은 한글 설명) diff --git a/.cursorRules b/.cursorRules new file mode 100644 index 0000000..a53c4d2 --- /dev/null +++ b/.cursorRules @@ -0,0 +1,55 @@ +--- +description: 커밋 메시지는 Conventional Commits + 한글 본문 +alwaysApply: true +--- + +# 커밋 메시지 (Conventional Commits + 한글) + +## 형식 + +- **한 줄:** `<타입>(선택 범위): <한글 설명>` +- **타입·콜론·공백 뒤 설명은 반드시 한글**로 작성한다. +- 제목은 **50자 전후**, 명령형·현재형으로 짧게 쓴다. (예: "추가함" 대신 "추가") + +## 허용 타입 + +| 타입 | 용도 | +|------|------| +| `feat` | 새 기능 | +| `fix` | 버그 수정 | +| `docs` | 문서만 변경 | +| `style` | 포맷·세미콜론 등, 동작 변화 없음 | +| `refactor` | 리팩터링 (기능 변화 없음) | +| `test` | 테스트 추가·수정 | +| `chore` | 빌드·설정·잡일 | +| `perf` | 성능 개선 | +| `ci` | CI 설정 | +| `build` | 빌드 시스템·의존성 | +| `revert` | 되돌리기 | + +## 예시 + +```text +feat: 로그인 실패 시 텔레그램 알림 전송 +fix: GAME_COUNT 빈 문자열일 때 기본값 적용 +refactor: 설정 로드 로직을 config 모듈로 분리 +docs: README에 NAS 배포 절차 추가 +test: LottoBuyer 로그인 성공 케이스 mock 테스트 +chore: requirements.txt 버전 고정 +ci: Gitea Actions에서 Secret 기반 설정 검증 +``` + +## 범위(선택) + +- 괄호로 모듈을 짧게: `feat(lotto_buyer): 세션 만료 시 재로그인 시도` +- 필요할 때만 사용. + +## 본문이 필요할 때 + +- 제목 다음 빈 줄 뒤 **본문도 한글**로 상세 설명. +- 푸터: `BREAKING CHANGE:` 등은 프로젝트에 필요할 때만 영문 키워드 유지 가능. + +## 금지 + +- 타입 없이 자유 문장만 쓰기 +- 제목 전체를 영어만으로 쓰기 (이 프로젝트 기본은 한글 설명) diff --git a/.env.example b/.env.example index 0e0a9e8..0ffa3a6 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# 로컬: 이 파일을 .env 로 복사 후 값 입력 +# Gitea Actions: 동일 변수명을 리포지토리 Secret 으로 등록 (CI=true 일 때 .env 는 읽지 않음) + # 동행복권 계정 정보 (필수) LOTTO_USERNAME=your_dhlottery_id LOTTO_PASSWORD=your_dhlottery_password diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..fd599a4 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,40 @@ +# Gitea Actions: 리포지토리 설정 → Actions → Secrets 에 동일 이름 등록 +# 로컬은 프로젝트 루트 .env, CI 는 아래 secrets.* 가 환경변수로 주입됨 + +name: CI + +on: + push: + branches: + - main + - master + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: 의존성 설치 + run: pip install -r requirements.txt + + - name: 단위 테스트 + env: + CI: true + run: PYTHONPATH=src python -m unittest discover -s tests -v + + # PR 은 fork 등에서 Secret 이 없을 수 있어 push 일 때만 검증 + - name: Secret 기반 설정 로드 검증 + if: github.event_name == 'push' + env: + CI: true + LOTTO_USERNAME: ${{ secrets.LOTTO_USERNAME }} + LOTTO_PASSWORD: ${{ secrets.LOTTO_PASSWORD }} + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + run: PYTHONPATH=src python -c "from config import load_config; load_config(); print('config: OK')" diff --git a/src/config.py b/src/config.py index 69dfc90..050e1a6 100644 --- a/src/config.py +++ b/src/config.py @@ -12,7 +12,15 @@ class Config: TELEGRAM_CHAT_ID: str +def _is_ci_environment() -> bool: + v = os.environ.get("CI", "").strip().lower() + return v in ("true", "1", "yes") + + def _load_dotenv_if_present() -> None: + # Gitea Actions 등 CI: Secret으로 주입된 os.environ만 사용 (.env 미로드) + if _is_ci_environment(): + return try: from dotenv import load_dotenv @@ -25,7 +33,8 @@ def _load_dotenv_if_present() -> None: def load_config() -> Config: _load_dotenv_if_present() - game_count = int(os.environ.get("GAME_COUNT", "5")) + raw_gc = os.environ.get("GAME_COUNT", "5").strip() + game_count = int(raw_gc if raw_gc else "5") if not 1 <= game_count <= 5: raise ValueError(f"GAME_COUNT는 1~5 사이여야 합니다. 현재값: {game_count}")