메일 알림 시스템 추가

This commit is contained in:
Yun Chan 2026-06-29 17:07:01 +09:00
parent ddf12a851c
commit 3bf38c50df
22 changed files with 1769 additions and 31 deletions

View file

@ -161,6 +161,49 @@ CREATE INDEX IF NOT EXISTS idx_support_ticket_parent
ON app.support_ticket(parent_ticket_id)
WHERE parent_ticket_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS app.notification_event (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
kind TEXT NOT NULL CHECK (
kind IN ('account_pending_approval','session_review_ready','admin_test_email')
),
actor_user_id UUID REFERENCES app.app_user(user_id) ON DELETE SET NULL,
subject_user_id UUID REFERENCES app.app_user(user_id) ON DELETE SET NULL,
session_id UUID REFERENCES app.sessions(id) ON DELETE CASCADE,
idempotency_key TEXT NOT NULL UNIQUE,
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS app.notification_delivery (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES app.notification_event(id) ON DELETE CASCADE,
channel TEXT NOT NULL DEFAULT 'email' CHECK (channel IN ('email')),
recipient_user_id UUID REFERENCES app.app_user(user_id) ON DELETE SET NULL,
recipient_email TEXT NOT NULL,
recipient_name TEXT NOT NULL DEFAULT '',
recipient_role TEXT NOT NULL DEFAULT '',
subject TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued' CHECK (
status IN ('queued','sending','sent','failed','skipped')
),
attempts INTEGER NOT NULL DEFAULT 0,
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
sent_at TIMESTAMPTZ,
last_error TEXT,
provider_message_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (event_id, channel, recipient_email)
);
CREATE INDEX IF NOT EXISTS idx_notification_event_created
ON app.notification_event(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_queue
ON app.notification_delivery(status, next_attempt_at, created_at)
WHERE status IN ('queued','failed');
CREATE INDEX IF NOT EXISTS idx_notification_delivery_event
ON app.notification_delivery(event_id, created_at);
CREATE TABLE IF NOT EXISTS app.learner_prepost_measure (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
learner_id UUID NOT NULL REFERENCES app.app_user(user_id) ON DELETE CASCADE,
@ -241,6 +284,19 @@ CREATE POLICY p_support_ticket_delete
ON app.support_ticket FOR DELETE
USING (app.current_role_name() = 'admin');
ALTER TABLE app.notification_event ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.notification_delivery ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_notification_event_admin_all ON app.notification_event;
DROP POLICY IF EXISTS p_notification_delivery_admin_all ON app.notification_delivery;
CREATE POLICY p_notification_event_admin_all
ON app.notification_event FOR ALL
USING (app.current_role_name() = 'admin')
WITH CHECK (app.current_role_name() = 'admin');
CREATE POLICY p_notification_delivery_admin_all
ON app.notification_delivery FOR ALL
USING (app.current_role_name() = 'admin')
WITH CHECK (app.current_role_name() = 'admin');
ALTER TABLE app.learner_prepost_measure ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_learner_prepost_measure_select ON app.learner_prepost_measure;
DROP POLICY IF EXISTS p_learner_prepost_measure_insert ON app.learner_prepost_measure;