Migración desde workspace-v2/projects/template-saas/apps/database Este repositorio es parte del estándar multi-repo v2 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
161 lines
5.2 KiB
PL/PgSQL
161 lines
5.2 KiB
PL/PgSQL
-- ============================================
|
|
-- TEMPLATE-SAAS: Notifications
|
|
-- Schema: notifications
|
|
-- Version: 1.0.0
|
|
-- ============================================
|
|
|
|
-- Notification templates
|
|
CREATE TABLE notifications.templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Template identification
|
|
code VARCHAR(100) UNIQUE NOT NULL, -- e.g., 'welcome_email', 'invoice_paid'
|
|
name VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
category VARCHAR(100), -- 'transactional', 'marketing', 'system'
|
|
|
|
-- Channel
|
|
channel notifications.channel NOT NULL,
|
|
|
|
-- Content
|
|
subject VARCHAR(500), -- For email
|
|
body TEXT NOT NULL,
|
|
body_html TEXT, -- For email HTML version
|
|
|
|
-- Variables (for template rendering)
|
|
variables JSONB DEFAULT '[]'::jsonb,
|
|
-- Example: [{"name": "user_name", "required": true}, {"name": "company", "default": "SaaS"}]
|
|
|
|
-- Status
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Audit
|
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
|
);
|
|
|
|
-- Notifications (instances)
|
|
CREATE TABLE notifications.notifications (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES users.users(id) ON DELETE SET NULL,
|
|
|
|
-- Template
|
|
template_id UUID REFERENCES notifications.templates(id),
|
|
template_code VARCHAR(100),
|
|
|
|
-- Channel
|
|
channel notifications.channel NOT NULL,
|
|
|
|
-- Content
|
|
subject VARCHAR(500),
|
|
body TEXT NOT NULL,
|
|
body_html TEXT,
|
|
|
|
-- Recipient
|
|
recipient_email VARCHAR(255),
|
|
recipient_phone VARCHAR(50),
|
|
recipient_device_token VARCHAR(500),
|
|
|
|
-- Status
|
|
status notifications.notification_status DEFAULT 'pending' NOT NULL,
|
|
priority notifications.priority DEFAULT 'normal',
|
|
|
|
-- Delivery info
|
|
sent_at TIMESTAMPTZ,
|
|
delivered_at TIMESTAMPTZ,
|
|
is_read BOOLEAN DEFAULT FALSE NOT NULL,
|
|
read_at TIMESTAMPTZ,
|
|
failed_at TIMESTAMPTZ,
|
|
failure_reason TEXT,
|
|
|
|
-- Retry
|
|
retry_count INT DEFAULT 0,
|
|
next_retry_at TIMESTAMPTZ,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::jsonb,
|
|
|
|
-- Audit
|
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
|
);
|
|
|
|
-- User preferences
|
|
CREATE TABLE notifications.user_preferences (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
|
|
|
|
-- Channel preferences
|
|
email_enabled BOOLEAN DEFAULT TRUE,
|
|
push_enabled BOOLEAN DEFAULT TRUE,
|
|
sms_enabled BOOLEAN DEFAULT FALSE,
|
|
in_app_enabled BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Category preferences (JSONB)
|
|
category_preferences JSONB DEFAULT '{}'::jsonb,
|
|
-- Example: { "marketing": false, "transactional": true }
|
|
|
|
-- Quiet hours
|
|
quiet_hours_enabled BOOLEAN DEFAULT FALSE,
|
|
quiet_hours_start TIME,
|
|
quiet_hours_end TIME,
|
|
quiet_hours_timezone VARCHAR(50),
|
|
|
|
-- Digest
|
|
digest_enabled BOOLEAN DEFAULT FALSE,
|
|
digest_frequency VARCHAR(20), -- 'daily', 'weekly'
|
|
|
|
-- Audit
|
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
|
|
|
-- Constraints
|
|
CONSTRAINT unique_user_preferences UNIQUE (user_id)
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_templates_code ON notifications.templates(code) WHERE is_active = TRUE;
|
|
CREATE INDEX idx_templates_channel ON notifications.templates(channel) WHERE is_active = TRUE;
|
|
|
|
CREATE INDEX idx_notifications_tenant ON notifications.notifications(tenant_id, created_at DESC);
|
|
CREATE INDEX idx_notifications_user ON notifications.notifications(user_id, created_at DESC);
|
|
CREATE INDEX idx_notifications_status ON notifications.notifications(status) WHERE status IN ('pending', 'sent');
|
|
CREATE INDEX idx_notifications_retry ON notifications.notifications(next_retry_at) WHERE status = 'pending' AND next_retry_at IS NOT NULL;
|
|
|
|
CREATE INDEX idx_user_preferences_user ON notifications.user_preferences(user_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE notifications.notifications ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE notifications.user_preferences ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY notifications_tenant_isolation ON notifications.notifications
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
CREATE POLICY user_preferences_tenant_isolation ON notifications.user_preferences
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Trigger
|
|
CREATE OR REPLACE FUNCTION notifications.update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_templates_updated_at
|
|
BEFORE UPDATE ON notifications.templates
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION notifications.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_user_preferences_updated_at
|
|
BEFORE UPDATE ON notifications.user_preferences
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION notifications.update_updated_at();
|
|
|
|
-- Comments
|
|
COMMENT ON TABLE notifications.templates IS 'Notification templates with variables';
|
|
COMMENT ON TABLE notifications.notifications IS 'Notification instances and delivery status';
|
|
COMMENT ON TABLE notifications.user_preferences IS 'User notification preferences';
|