Identity Schema¶
identityschema 는 Umbra User 와 Session 을 저장한다. Discord OAuth2 인증의 source of truth.
Schema purpose¶
- PostgreSQL schema —
identity - Corresponding domain —
engine/identity/ - sqlc package —
db/queries/identity/
User 는 결제(Billing), 길드 관리(Guild), 감사 로그(Audit) 등 여러 도메인이 참조하는 상류 entity. Session 은 대시보드 인증 상태 추적.
Tables¶
identity.users¶
Umbra 사용자 계정.
CREATE TABLE identity.users (
id UUID PRIMARY KEY,
discord_user_id TEXT NOT NULL,
username TEXT NOT NULL,
avatar_hash TEXT,
locale TEXT NOT NULL DEFAULT 'ko',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
CONSTRAINT users_discord_user_id_unique UNIQUE (discord_user_id)
);
Columns
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID v7 | PK | Umbra 내부 User ID |
discord_user_id | TEXT | UNIQUE, NOT NULL | Discord snowflake (string) |
username | TEXT | NOT NULL | 최근 Discord username (캐시) |
avatar_hash | TEXT | — | Discord avatar hash (캐시, NULL 가능) |
locale | TEXT | NOT NULL | UI 언어 (ko, en 등) |
created_at | TIMESTAMPTZ | NOT NULL | 계정 생성 시각 |
updated_at | TIMESTAMPTZ | NOT NULL | 마지막 업데이트 |
deleted_at | TIMESTAMPTZ | — | Soft delete 시각, NULL 이면 활성 |
Indexes
| Index | Columns | Purpose |
|---|---|---|
users_pkey | (id) | PK |
users_discord_user_id_unique | (discord_user_id) | Discord ID 조회 |
users_deleted_at_idx | (deleted_at) WHERE deleted_at IS NULL | 활성 사용자 조회 |
Invariants
- Discord User 와 1:1 매핑
- Soft delete 원칙 — DELETE 대신
deleted_at설정
identity.sessions¶
OAuth2 세션 그림자 레코드. source of truth 는 Redis.
CREATE TABLE identity.sessions (
id TEXT PRIMARY KEY,
user_id UUID NOT NULL REFERENCES identity.users(id),
encrypted_access_token BYTEA NOT NULL,
encrypted_refresh_token BYTEA NOT NULL,
access_expires_at TIMESTAMPTZ NOT NULL,
user_agent TEXT,
ip_address INET,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ
);
Columns
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | 랜덤 session ID (32 bytes base64) |
user_id | UUID | FK, NOT NULL | → users.id |
encrypted_access_token | BYTEA | NOT NULL | AES-256-GCM 암호화 |
encrypted_refresh_token | BYTEA | NOT NULL | AES-256-GCM 암호화 |
access_expires_at | TIMESTAMPTZ | NOT NULL | Discord access token 만료 |
user_agent | TEXT | — | 브라우저 정보 (감사용) |
ip_address | INET | — | 로그인 IP (단기 보관) |
created_at | TIMESTAMPTZ | NOT NULL | 세션 생성 |
expires_at | TIMESTAMPTZ | NOT NULL | 세션 만료 |
revoked_at | TIMESTAMPTZ | — | 명시적 폐기 시각 |
Indexes
| Index | Columns | Purpose |
|---|---|---|
sessions_pkey | (id) | PK |
sessions_user_id_idx | (user_id) | User 별 세션 조회 |
sessions_expires_at_idx | (expires_at) | 만료 세션 정리 cron |
Foreign keys
| Column | References | On delete |
|---|---|---|
user_id | identity.users(id) | RESTRICT (soft delete 원칙) |
Invariants
expires_at은 항상created_at보다 늦음revoked_at IS NOT NULL이면 세션 무효- IP 주소는 GDPR 고려해 30일 후 NULL 로 초기화 (cron)
Relationships¶
erDiagram
USERS ||--o{ SESSIONS : has
USERS ||--o{ BILLING_KEYS : owns
USERS ||--o{ SUBSCRIPTIONS : pays
USERS ||--o{ GUILD_OWNERS : owns
Cross-schema references¶
이 schema 의 User 를 참조하는 다른 schema:
| From | To | Semantics |
|---|---|---|
billing.billing_keys.user_id | identity.users.id | User 가 빌링키 소유 |
billing.subscriptions.payer_user_id | identity.users.id | 결제 주체 |
guild.guilds.owner_user_id | identity.users.id | 길드 owner (nullable) |
audit.events.actor_user_id | identity.users.id | 이벤트 발생 주체 |
Query patterns¶
주요 쿼리 (db/queries/identity/*.sql):
GetUserByID— User PK 조회GetUserByDiscordID— Discord ID 로 조회 (로그인 경로)UpsertUser— 로그인 시 username/avatar 캐시 갱신SoftDeleteUser— 탈퇴 처리CreateSession— 세션 그림자 레코드 생성RevokeSession— 로그아웃CleanupExpiredSessions— cron 으로 만료 세션 삭제
Data retention¶
- Users — 영구 보관 (결제/감사 참조). soft delete 후에도 삭제 안 함.
- Sessions —
expires_at + 7일경과 시 물리 삭제 (cron) - IP 주소 — 30일 후 NULL (GDPR)
- Token (암호화) — 세션 종료 시 즉시 삭제
Migration history¶
| Date | Change | Rationale |
|---|---|---|
| 2026-04-xx | 초기 스키마 | MVP 구현 |
See also¶
domain/identity.md— Identity 도메인data/schema-overview.md— 전체 스키마 관계adr/0021-pk-uuid-v7.md— UUID v7 PKadr/0020-postgres-schema-per-domain.md— schema 분리 원칙