KHUDA AI Engineering 3주차 과제. 1주차에 만든 워들을 2주차 ERD(erd.sql) 기반으로 3계층 FastAPI 앱으로 완성했다.
유럽 5대리그 축구선수를 하루에 한 명 맞추는 게임. 찍을 때마다 8개 항목 힌트가 나온다.
uv sync
uv run python src/seed.py # 샘플 선수 데이터 넣기 (최초 1회)
uv run uvicorn main:app --app-dir src --reloadsrc/
├── main.py 앱 생성, 라우터 등록
├── database.py DB 연결 (SQLite + SQLAlchemy)
├── dependencies.py 의존성 주입 (get_db -> repository -> service, 로그인)
├── seed.py 샘플 데이터
└── footdle/
├── router.py 컨트롤러 계층 — 엔드포인트, ValueError를 HTTP 오류로 변환
├── service.py 서비스 계층 — 게임 규칙 (FastAPI 안 씀)
├── repository.py 레포지토리 계층 — DB 접근만
├── models.py 엔티티 (erd.sql 과 1:1)
├── schemas.py API 스키마 (Pydantic)
└── dto.py 서비스 계층용 DTO
계층은 아래로만 의존한다. 서비스는 HTTPException 대신 ValueError를 던지고 라우터가 받아서 변환한다.
| 메서드 | 경로 | 설명 | 로그인 |
|---|---|---|---|
| POST | /footdle/users |
회원가입 | |
| GET | /footdle/players?q= |
입력 후보 검색 | |
| GET | /footdle/puzzles/today |
오늘 문제, 남은 시도 횟수 | O |
| POST | /footdle/guesses |
선수 찍기 | O |
| GET | /footdle/puzzles/today/guesses |
오늘 내 기록 | O |
| GET | /footdle/puzzles/today/answer |
정답 확인 | O |
로그인은 HTTP Basic. 비밀번호는 hashlib.pbkdf2_hmac로 해싱해서 저장한다.
ORM은 SQLAlchemy, DB는 SQLite를 썼다.
curl -u son:1234 -X POST localhost:8000/footdle/guesses \
-H "content-type: application/json" -d '{"player_id":1}'{
"attempt_no": 1, "player_name": "Cody Gakpo",
"is_correct": false, "attempts_left": 5,
"birth_year_hint": "DOWN", "nationality_match": false,
"position_match": true, "rank_hint": "EQUAL",
"league_titles_hint": "UP", "ucl_titles_hint": "UP",
"debut_team_match": false, "height_hint": "DOWN"
}UP은 정답이 더 큼, DOWN은 더 작음. 순위는 숫자가 작을수록 상위권이다.
1주차엔 DB가 없어서 무상태를 지키려고 클라이언트가 attempt를 직접 보냈다.
이제 시도 기록이 guesses 테이블에 남아서 서버가 직접 센다.
요청마다 로그인 정보로 사용자를 식별하므로 여전히 서버에 세션 상태는 없다.
seed.py의 값은 동작 확인용 예시 데이터다. 실제로는 이렇게 채운다.
- 출생연도, 국적, 포지션, 키, 소속팀, 리그 순위 → API-Football
- 프로 데뷔팀, 리그/챔스 우승 횟수 → Transfermarkt
uv run pytest