Notification Schema¶
notificationschema 는 알림 발송 요청과 사용자 선호도를 저장한다.
Schema purpose¶
- PostgreSQL schema —
notification - Corresponding domain —
engine/notification/ - sqlc package —
db/queries/notification/
Event consumer 가 이벤트 수신 시 NotificationRequest 를 INSERT 하고, asynq 워커가 pickup 하여 발송.
Tables¶
notification.requests¶
발송 요청/이력.
CREATE TABLE notification.requests (
id UUID PRIMARY KEY,
recipient_type TEXT NOT NULL CHECK (recipient_type IN ('user_dm', 'guild_audit_channel', 'dashboard_toast')),
recipient_id TEXT NOT NULL,
guild_id UUID REFERENCES guild.guilds(id),
template_key TEXT NOT NULL,
payload JSONB NOT NULL,
state TEXT NOT NULL CHECK (state IN ('pending', 'sent', 'failed', 'suppressed')),
attempt_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
scheduled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Columns
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID v7 | PK | 요청 ID |
recipient_type | TEXT | CHECK | 발송 채널 |
recipient_id | TEXT | NOT NULL | Discord user ID 또는 channel ID |
guild_id | UUID | FK nullable | 관련 길드 |
template_key | TEXT | NOT NULL | 템플릿 식별자 |
payload | JSONB | NOT NULL | 템플릿 변수 |
state | TEXT | CHECK | 상태 |
attempt_count | INTEGER | NOT NULL | 시도 횟수 |
last_error | TEXT | — | 마지막 실패 사유 |
scheduled_at | TIMESTAMPTZ | NOT NULL | 예약 발송 시각 |
sent_at | TIMESTAMPTZ | — | 실제 발송 시각 |
created_at | TIMESTAMPTZ | NOT NULL |
Indexes
| Index | Columns | Purpose |
|---|---|---|
requests_pkey | (id) | PK |
requests_state_scheduled_idx | (state, scheduled_at) WHERE state = 'pending' | asynq 워커 pickup |
requests_guild_created_idx | (guild_id, created_at DESC) | 길드별 최근 알림 |
requests_recipient_idx | (recipient_type, recipient_id, created_at DESC) | User 별 알림 이력 |
Foreign keys
| Column | References | On delete |
|---|---|---|
guild_id | guild.guilds(id) | SET NULL |
Invariants
attempt_count <= 3state = 'sent'이면sent_at IS NOT NULLtemplate_key는 코드베이스 정의와 일치 (CI 검증)
notification.preferences¶
사용자별 알림 선호도.
CREATE TABLE notification.preferences (
user_id UUID PRIMARY KEY REFERENCES identity.users(id),
dm_enabled BOOLEAN NOT NULL DEFAULT TRUE,
payment_notifications BOOLEAN NOT NULL DEFAULT TRUE,
recovery_notifications BOOLEAN NOT NULL DEFAULT TRUE,
antinuke_notifications BOOLEAN NOT NULL DEFAULT TRUE,
marketing_notifications BOOLEAN NOT NULL DEFAULT TRUE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Columns
| Column | Type | Constraints | Description |
|---|---|---|---|
user_id | UUID | PK, FK | → identity.users.id |
dm_enabled | BOOLEAN | NOT NULL | DM 전체 on/off |
payment_notifications | BOOLEAN | NOT NULL | 결제 알림 |
recovery_notifications | BOOLEAN | NOT NULL | 복구 알림 |
antinuke_notifications | BOOLEAN | NOT NULL | AntiNuke 일반 알림 (긴급은 override) |
marketing_notifications | BOOLEAN | NOT NULL | 마케팅성 알림 |
updated_at | TIMESTAMPTZ | NOT NULL |
Foreign keys
| Column | References | On delete |
|---|---|---|
user_id | identity.users(id) | CASCADE |
Invariants
- 신규 User 등록 시 기본값으로 자동 생성 (또는 조회 시 lazy create)
- AntiNuke 긴급 알림(
antinuke.triggered) 은 이 선호도를 무시할 수 있음 (애플리케이션 레이어)
Relationships¶
erDiagram
USERS ||--|| PREFERENCES : has
USERS ||--o{ REQUESTS : "recipient of"
GUILDS ||--o{ REQUESTS : "related to"
Cross-schema references¶
| From | To | Semantics |
|---|---|---|
notification.requests.guild_id | guild.guilds.id | 관련 길드 |
notification.preferences.user_id | identity.users.id | 사용자 |
Query patterns¶
InsertRequest— consumer 가 이벤트 수신 시FetchPendingRequests— asynq 워커 pickupMarkSent— 발송 성공IncrementAttempt— 재시도 카운트MarkFailed— 최대 시도 초과GetPreference— 발송 전 확인UpsertPreference— 사용자 대시보드에서 수정DeleteOldRequests— 90일 경과 정리 cron
Data retention¶
- Requests — 90일 후 삭제 (cron)
- Preferences — User CASCADE
Migration history¶
| Date | Change | Rationale |
|---|---|---|
| 2026-04-xx | 초기 스키마 | MVP |
See also¶
domain/notification.md— Notification 도메인architecture/event-flow.md— Subscribers 컬럼data/identity-schema.md— 참조하는 Userdata/guild-schema.md— 참조하는 Guild