- 개요, 조사(BGA·한국 인기 보드게임·UI/UX), 아키텍처, 실시간 프로토콜, 안정성·보안, 계정, 로비·방, UI/UX, 데이터 모델, 게임 엔진, 테스트, 배포·운영, 로드맵(M0~M9), 사용자 결정 항목 - 게임별 규칙·엔진 설계·UI·테스트 체크리스트 26종 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
5.3 KiB
Markdown
133 lines
5.3 KiB
Markdown
# 08. 데이터 모델 (SQLite)
|
|
|
|
## 1. 설정
|
|
- 파일: `data/app.db` (Docker 볼륨). `PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;`
|
|
- 마이그레이션: `apps/server/src/db/migrations/NNN_name.sql`, 시작 시 `schema_migrations` 기준으로 순서대로 적용. 한 번 배포된 마이그레이션 파일은 수정 금지.
|
|
- ID: 사용자·방은 ULID(시간 정렬 가능) 문자열.
|
|
- 시각: epoch 밀리초 정수.
|
|
|
|
## 2. 테이블
|
|
```sql
|
|
CREATE TABLE users (
|
|
id TEXT PRIMARY KEY, -- ULID
|
|
kind TEXT NOT NULL CHECK (kind IN ('guest','member')),
|
|
nickname TEXT NOT NULL,
|
|
avatar_url TEXT, -- 디스코드 아바타
|
|
use_avatar INTEGER NOT NULL DEFAULT 1,
|
|
settings_json TEXT NOT NULL DEFAULT '{}', -- 글자 크기, 테마, 소리 등
|
|
nick_changed_at INTEGER, nick_change_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at INTEGER NOT NULL,
|
|
last_seen_at INTEGER NOT NULL,
|
|
deleted_at INTEGER
|
|
);
|
|
|
|
CREATE TABLE oauth_accounts (
|
|
provider TEXT NOT NULL, -- 'discord'
|
|
provider_user_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
username TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
PRIMARY KEY (provider, provider_user_id)
|
|
);
|
|
CREATE INDEX oauth_accounts_user ON oauth_accounts(user_id);
|
|
|
|
CREATE TABLE sessions (
|
|
token_hash TEXT PRIMARY KEY, -- sha256(token) hex
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
created_at INTEGER NOT NULL,
|
|
expires_at INTEGER NOT NULL,
|
|
last_used_at INTEGER NOT NULL,
|
|
user_agent TEXT
|
|
);
|
|
CREATE INDEX sessions_user ON sessions(user_id);
|
|
|
|
CREATE TABLE rooms (
|
|
id TEXT PRIMARY KEY, -- ULID
|
|
code TEXT NOT NULL, -- 6자리
|
|
host_id TEXT NOT NULL,
|
|
game_id TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ('lobby','playing','finished','paused','broken','closed')),
|
|
visibility TEXT NOT NULL,
|
|
config_json TEXT NOT NULL, -- 옵션, 인원, 턴 시간, 채팅 설정
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
closed_at INTEGER
|
|
);
|
|
-- 열린 방끼리만 코드 중복 금지
|
|
CREATE UNIQUE INDEX rooms_open_code ON rooms(code) WHERE status <> 'closed';
|
|
CREATE INDEX rooms_status ON rooms(status);
|
|
|
|
CREATE TABLE room_state (
|
|
room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
|
|
seq INTEGER NOT NULL,
|
|
state_json TEXT NOT NULL, -- 자리, 게임 상태, RNG 상태, deadline, stateVersion
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE room_log (
|
|
room_id TEXT NOT NULL,
|
|
seq INTEGER NOT NULL,
|
|
game_no INTEGER NOT NULL, -- 방 안에서 몇 번째 게임인지
|
|
actor_id TEXT, -- null = 시스템(타이머 등)
|
|
kind TEXT NOT NULL, -- 'act' | 'timeout' | 'start' | 'seat' | ...
|
|
data_json TEXT NOT NULL,
|
|
at INTEGER NOT NULL,
|
|
PRIMARY KEY (room_id, seq)
|
|
);
|
|
|
|
CREATE TABLE games (
|
|
id TEXT PRIMARY KEY, -- ULID, 한 판
|
|
room_id TEXT NOT NULL,
|
|
game_no INTEGER NOT NULL,
|
|
game_id TEXT NOT NULL,
|
|
options_json TEXT NOT NULL,
|
|
seed_hash TEXT NOT NULL,
|
|
seed TEXT, -- 끝난 뒤에만 채움(공정성 확인)
|
|
started_at INTEGER NOT NULL,
|
|
ended_at INTEGER,
|
|
end_reason TEXT, -- 'normal' | 'resign' | 'abandoned' | 'void'
|
|
result_json TEXT
|
|
);
|
|
CREATE INDEX games_room ON games(room_id);
|
|
|
|
CREATE TABLE game_players (
|
|
game_pk TEXT NOT NULL REFERENCES games(id) ON DELETE CASCADE,
|
|
user_id TEXT NOT NULL,
|
|
seat INTEGER NOT NULL,
|
|
rank INTEGER, -- 1 = 1등, 무승부는 같은 순위
|
|
score REAL,
|
|
PRIMARY KEY (game_pk, user_id)
|
|
);
|
|
CREATE INDEX game_players_user ON game_players(user_id);
|
|
|
|
CREATE TABLE user_stats (
|
|
user_id TEXT NOT NULL,
|
|
game_id TEXT NOT NULL,
|
|
played INTEGER NOT NULL DEFAULT 0,
|
|
wins INTEGER NOT NULL DEFAULT 0,
|
|
draws INTEGER NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (user_id, game_id)
|
|
);
|
|
```
|
|
|
|
## 3. 쓰기 시점
|
|
| 사건 | 쓰기 |
|
|
|---|---|
|
|
| 방 생성 | `rooms` insert, `room_state` insert |
|
|
| 대기실 변경(자리, 옵션, 준비) | `room_state` update(+ `room_log`) |
|
|
| 게임 시작 | `games` insert, `room_state`, `room_log` |
|
|
| 게임 행동 / 시간 초과 | `room_log` insert + `room_state` update (한 트랜잭션) |
|
|
| 게임 끝 | `games` update(결과, 시드 공개), `game_players`, `user_stats` upsert (한 트랜잭션) |
|
|
| 방 닫힘 | `rooms.status='closed'`, `room_state` 삭제 |
|
|
|
|
## 4. 정리 작업(매시간)
|
|
- 만료된 세션 삭제.
|
|
- 90일 지난 `room_log` 삭제(결과는 `games`에 남음).
|
|
- 1년 이상 접속 없는 게스트: 사용자·전적 삭제.
|
|
- 닫힌 지 24시간이 지난 방의 코드는 자연히 재사용 가능(부분 유니크 인덱스).
|
|
|
|
## 5. 개인정보
|
|
- 저장하는 개인정보: 닉네임, 디스코드 사용자 ID/이름/아바타 해시, 접속 시각, User-Agent(세션 관리용).
|
|
- IP는 DB에 저장하지 않고 속도 제한용 메모리에만 둔다.
|
|
- 계정 삭제 시 `users.deleted_at` 설정 + 닉네임을 "탈퇴한 사용자"로, OAuth 연결·세션 삭제. 다른 사람의 전적 표시가 깨지지 않게 행은 유지.
|