메일 알림 시스템 추가
This commit is contained in:
parent
ddf12a851c
commit
3bf38c50df
22 changed files with 1769 additions and 31 deletions
|
|
@ -27,6 +27,17 @@ FRONTEND_BASE_URL=https://vignette.chanpaca.net
|
|||
FRONTEND_ORIGIN_MAP={"api-vignette.chanpaca.net":"https://vignette.chanpaca.net","api-vnet.18ka.net":"https://vnet.18ka.net"}
|
||||
CORS_ORIGINS=["https://vignette.chanpaca.net","https://vnet.18ka.net","https://vignette-b1q.pages.dev","http://localhost:5170","http://localhost:5171","http://localhost:5172","http://localhost:5173","http://localhost:5174","http://localhost:5175","http://localhost:5176","http://localhost:5177","http://localhost:5178","http://localhost:5179","http://localhost:5180","http://127.0.0.1:5170","http://127.0.0.1:5171","http://127.0.0.1:5172","http://127.0.0.1:5173","http://127.0.0.1:5174","http://127.0.0.1:5175","http://127.0.0.1:5176","http://127.0.0.1:5177","http://127.0.0.1:5178","http://127.0.0.1:5179","http://127.0.0.1:5180"]
|
||||
# For local-only compose development, set ENVIRONMENT=dev and add http://localhost:5173 to CORS_ORIGINS.
|
||||
NOTIFICATION_EMAIL_PROVIDER=disabled
|
||||
NOTIFICATION_EMAIL_MAX_ATTEMPTS=3
|
||||
NOTIFICATION_EMAIL_RETRY_SECONDS=900
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_EMAIL=
|
||||
SMTP_FROM_NAME=Vignette
|
||||
SMTP_STARTTLS=true
|
||||
SMTP_SSL=false
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
VIGNETTE_VOICE_POC_SAMPLE_TTS=false
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,17 @@ services:
|
|||
FRONTEND_ORIGIN_MAP: '${FRONTEND_ORIGIN_MAP:-{"api-vignette.chanpaca.net":"https://vignette.chanpaca.net","api-vnet.18ka.net":"https://vnet.18ka.net"}}'
|
||||
CORS_ORIGINS: '${CORS_ORIGINS:-["https://vignette.chanpaca.net","https://vnet.18ka.net","https://vignette-b1q.pages.dev"]}'
|
||||
USER_UPLOAD_DIR: ${USER_UPLOAD_DIR:-/app/uploads}
|
||||
NOTIFICATION_EMAIL_PROVIDER: ${NOTIFICATION_EMAIL_PROVIDER:-disabled}
|
||||
NOTIFICATION_EMAIL_MAX_ATTEMPTS: ${NOTIFICATION_EMAIL_MAX_ATTEMPTS:-3}
|
||||
NOTIFICATION_EMAIL_RETRY_SECONDS: ${NOTIFICATION_EMAIL_RETRY_SECONDS:-900}
|
||||
SMTP_HOST: ${SMTP_HOST:-}
|
||||
SMTP_PORT: ${SMTP_PORT:-587}
|
||||
SMTP_USERNAME: ${SMTP_USERNAME:-}
|
||||
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
||||
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
|
||||
SMTP_FROM_NAME: ${SMTP_FROM_NAME:-Vignette}
|
||||
SMTP_STARTTLS: ${SMTP_STARTTLS:-true}
|
||||
SMTP_SSL: ${SMTP_SSL:-false}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue