Update various configuration files, components, and assets; enhance notification system and API endpoints; improve documentation and styles across the application.
This commit is contained in:
222
database/migrations/001_create_notification_tables.sql
Normal file
222
database/migrations/001_create_notification_tables.sql
Normal file
@@ -0,0 +1,222 @@
|
||||
-- Notification Categories
|
||||
CREATE TABLE notification_categories (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
value VARCHAR(50) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Notification Templates
|
||||
CREATE TABLE notification_templates (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
value VARCHAR(50) UNIQUE NOT NULL,
|
||||
subject VARCHAR(255),
|
||||
email_content TEXT,
|
||||
push_title VARCHAR(100),
|
||||
push_body VARCHAR(300),
|
||||
variables JSON DEFAULT ('[]'),
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- User Segments
|
||||
CREATE TABLE user_segments (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
value VARCHAR(50) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
criteria JSON NOT NULL,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Main Notifications Table
|
||||
CREATE TABLE notifications (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(20) NOT NULL CHECK (type IN ('single', 'bulk')),
|
||||
priority VARCHAR(20) NOT NULL CHECK (priority IN ('low', 'medium', 'high', 'critical')),
|
||||
category_id VARCHAR(36),
|
||||
status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft', 'scheduled', 'sending', 'sent', 'failed', 'cancelled')),
|
||||
|
||||
-- Scheduling
|
||||
delivery_type VARCHAR(20) NOT NULL CHECK (delivery_type IN ('immediate', 'scheduled')),
|
||||
scheduled_at TIMESTAMP NULL,
|
||||
timezone VARCHAR(50) DEFAULT 'UTC',
|
||||
expires_at TIMESTAMP NULL,
|
||||
|
||||
-- A/B Testing
|
||||
enable_ab_testing BOOLEAN DEFAULT false,
|
||||
ab_test_split INTEGER DEFAULT 50 CHECK (ab_test_split BETWEEN 10 AND 90),
|
||||
ab_test_name VARCHAR(100),
|
||||
|
||||
-- Tracking & Analytics
|
||||
enable_tracking BOOLEAN DEFAULT true,
|
||||
|
||||
-- Audience Settings
|
||||
audience_type VARCHAR(20) NOT NULL CHECK (audience_type IN ('all', 'specific', 'segmented')),
|
||||
specific_users TEXT, -- Comma-separated user IDs or emails
|
||||
user_status VARCHAR(20),
|
||||
registration_period VARCHAR(50),
|
||||
exclude_unsubscribed BOOLEAN DEFAULT true,
|
||||
respect_do_not_disturb BOOLEAN DEFAULT true,
|
||||
|
||||
-- Content
|
||||
content_type VARCHAR(20) NOT NULL CHECK (content_type IN ('new', 'template')),
|
||||
template_id VARCHAR(36),
|
||||
|
||||
-- Email Content
|
||||
email_subject VARCHAR(255),
|
||||
email_content TEXT,
|
||||
call_to_action_text VARCHAR(100),
|
||||
call_to_action_url TEXT,
|
||||
|
||||
-- Push Content
|
||||
push_title VARCHAR(100),
|
||||
push_body VARCHAR(300),
|
||||
push_image_url TEXT,
|
||||
|
||||
-- Metadata
|
||||
estimated_reach INTEGER DEFAULT 0,
|
||||
actual_sent INTEGER DEFAULT 0,
|
||||
created_by VARCHAR(36) NOT NULL, -- User ID who created this
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
sent_at TIMESTAMP NULL,
|
||||
|
||||
FOREIGN KEY (category_id) REFERENCES notification_categories(id),
|
||||
FOREIGN KEY (template_id) REFERENCES notification_templates(id)
|
||||
);
|
||||
|
||||
-- Notification Channels (Many-to-Many)
|
||||
CREATE TABLE notification_channels (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
notification_id VARCHAR(36) NOT NULL,
|
||||
channel_type VARCHAR(20) NOT NULL CHECK (channel_type IN ('email', 'push', 'sms', 'in_app')),
|
||||
is_enabled BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Notification User Segments (Many-to-Many)
|
||||
CREATE TABLE notification_user_segments (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
notification_id VARCHAR(36) NOT NULL,
|
||||
segment_id VARCHAR(36) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (segment_id) REFERENCES user_segments(id)
|
||||
);
|
||||
|
||||
-- Notification Recipients (For tracking who received what)
|
||||
CREATE TABLE notification_recipients (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
notification_id VARCHAR(36) NOT NULL,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
email VARCHAR(255),
|
||||
channel_type VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'delivered', 'failed', 'bounced')),
|
||||
sent_at TIMESTAMP NULL,
|
||||
delivered_at TIMESTAMP NULL,
|
||||
opened_at TIMESTAMP NULL,
|
||||
clicked_at TIMESTAMP NULL,
|
||||
error_message TEXT,
|
||||
ab_test_variant VARCHAR(1) CHECK (ab_test_variant IN ('A', 'B')),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Notification Queue (For scheduled notifications)
|
||||
CREATE TABLE notification_queue (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
notification_id VARCHAR(36) NOT NULL,
|
||||
recipient_id VARCHAR(36) NOT NULL,
|
||||
scheduled_for TIMESTAMP NOT NULL,
|
||||
priority INTEGER DEFAULT 5,
|
||||
attempts INTEGER DEFAULT 0,
|
||||
max_attempts INTEGER DEFAULT 3,
|
||||
status VARCHAR(20) DEFAULT 'queued' CHECK (status IN ('queued', 'processing', 'sent', 'failed', 'cancelled')),
|
||||
last_attempt_at TIMESTAMP NULL,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (recipient_id) REFERENCES notification_recipients(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Notification Analytics
|
||||
CREATE TABLE notification_analytics (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
notification_id VARCHAR(36) NOT NULL,
|
||||
channel_type VARCHAR(20) NOT NULL,
|
||||
metric_type VARCHAR(30) NOT NULL CHECK (metric_type IN ('sent', 'delivered', 'opened', 'clicked', 'bounced', 'unsubscribed')),
|
||||
metric_value INTEGER DEFAULT 0,
|
||||
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
metadata JSON DEFAULT ('{}'),
|
||||
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- User Notification Preferences
|
||||
CREATE TABLE user_notification_preferences (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
channel_type VARCHAR(20) NOT NULL,
|
||||
category_value VARCHAR(50),
|
||||
is_enabled BOOLEAN DEFAULT true,
|
||||
do_not_disturb_start TIME,
|
||||
do_not_disturb_end TIME,
|
||||
timezone VARCHAR(50) DEFAULT 'UTC',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, channel_type, category_value)
|
||||
);
|
||||
|
||||
-- Indexes for better performance
|
||||
CREATE INDEX idx_notifications_status ON notifications(status);
|
||||
CREATE INDEX idx_notifications_scheduled_at ON notifications(scheduled_at);
|
||||
CREATE INDEX idx_notifications_created_by ON notifications(created_by);
|
||||
CREATE INDEX idx_notification_recipients_user_id ON notification_recipients(user_id);
|
||||
CREATE INDEX idx_notification_recipients_status ON notification_recipients(status);
|
||||
CREATE INDEX idx_notification_queue_scheduled_for ON notification_queue(scheduled_for);
|
||||
CREATE INDEX idx_notification_queue_status ON notification_queue(status);
|
||||
CREATE INDEX idx_notification_analytics_notification_id ON notification_analytics(notification_id);
|
||||
CREATE INDEX idx_user_notification_preferences_user_id ON user_notification_preferences(user_id);
|
||||
|
||||
-- Insert default categories
|
||||
INSERT INTO notification_categories (name, value, description) VALUES
|
||||
('User Management', 'user_management', 'User registration, profile updates, account changes'),
|
||||
('Orders & Transactions', 'orders', 'Order confirmations, payment notifications, transaction updates'),
|
||||
('Security & Authentication', 'security', 'Login alerts, password changes, security notifications'),
|
||||
('Marketing & Promotions', 'marketing', 'Promotional offers, newsletters, product announcements'),
|
||||
('System Updates', 'system', 'System maintenance, feature updates, service announcements'),
|
||||
('General Information', 'general', 'General notifications and information');
|
||||
|
||||
-- Insert default templates
|
||||
INSERT INTO notification_templates (name, value, subject, email_content, push_title, push_body, variables) VALUES
|
||||
('Welcome Message', 'welcome', 'Welcome to {{company_name}}!',
|
||||
'<h1>Welcome {{first_name}}!</h1><p>Thank you for joining {{company_name}}. We''re excited to have you on board.</p>',
|
||||
'Welcome to {{company_name}}!', 'Hi {{first_name}}, welcome to our platform!',
|
||||
'["first_name", "last_name", "company_name"]'),
|
||||
('Password Reset', 'password_reset', 'Reset your password',
|
||||
'<h1>Password Reset Request</h1><p>Hi {{first_name}}, click the link below to reset your password.</p>',
|
||||
'Password Reset', 'Tap to reset your password',
|
||||
'["first_name"]'),
|
||||
('Order Confirmation', 'order_confirmation', 'Order Confirmation #{{order_id}}',
|
||||
'<h1>Order Confirmed!</h1><p>Thank you {{first_name}}, your order #{{order_id}} has been confirmed.</p>',
|
||||
'Order Confirmed!', 'Your order #{{order_id}} is confirmed',
|
||||
'["first_name", "order_id", "order_total"]');
|
||||
|
||||
-- Insert default user segments
|
||||
INSERT INTO user_segments (name, value, description, criteria) VALUES
|
||||
('New Users', 'new_users', 'Users registered within last 30 days', '{"registration_days": 30}'),
|
||||
('Active Users', 'active_users', 'Users active in last 7 days', '{"last_activity_days": 7}'),
|
||||
('Premium Subscribers', 'premium_users', 'Users with premium subscription', '{"subscription_type": "premium"}'),
|
||||
('Inactive Users', 'inactive_users', 'Users not active in 30+ days', '{"inactive_days": 30}'),
|
||||
('High-Value Customers', 'high_value', 'Customers with high lifetime value', '{"min_order_value": 1000}'),
|
||||
('Mobile App Users', 'mobile_users', 'Users who use mobile app', '{"platform": "mobile"}');
|
||||
41
database/migrations/002_update_notification_templates.sql
Normal file
41
database/migrations/002_update_notification_templates.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
-- Migration: Update notification_templates table with additional fields
|
||||
-- Description: Add missing fields required by the template creation form
|
||||
|
||||
-- Add new columns to notification_templates table
|
||||
ALTER TABLE notification_templates
|
||||
ADD COLUMN description TEXT,
|
||||
ADD COLUMN preheader VARCHAR(255),
|
||||
ADD COLUMN push_icon VARCHAR(500),
|
||||
ADD COLUMN push_url TEXT,
|
||||
ADD COLUMN sms_content TEXT,
|
||||
ADD COLUMN category VARCHAR(50),
|
||||
ADD COLUMN channels JSON,
|
||||
ADD COLUMN status VARCHAR(20) DEFAULT 'Draft',
|
||||
ADD COLUMN version VARCHAR(20) DEFAULT '1.0',
|
||||
ADD COLUMN tags TEXT,
|
||||
ADD COLUMN is_personal BOOLEAN DEFAULT false,
|
||||
ADD COLUMN from_name VARCHAR(100),
|
||||
ADD COLUMN reply_to VARCHAR(255),
|
||||
ADD COLUMN track_opens BOOLEAN DEFAULT true,
|
||||
ADD COLUMN created_by VARCHAR(36),
|
||||
ADD COLUMN updated_by VARCHAR(36);
|
||||
|
||||
-- Add check constraint for status
|
||||
ALTER TABLE notification_templates
|
||||
ADD CONSTRAINT chk_template_status
|
||||
CHECK (status IN ('Draft', 'Active', 'Archived'));
|
||||
|
||||
-- Update existing templates to have default values for new fields
|
||||
UPDATE notification_templates
|
||||
SET
|
||||
status = 'Active',
|
||||
version = '1.0',
|
||||
is_personal = false,
|
||||
track_opens = true,
|
||||
channels = JSON_ARRAY('email')
|
||||
WHERE status IS NULL;
|
||||
|
||||
-- Create index for better performance on commonly queried fields
|
||||
CREATE INDEX idx_notification_templates_status ON notification_templates(status);
|
||||
CREATE INDEX idx_notification_templates_category ON notification_templates(category);
|
||||
CREATE INDEX idx_notification_templates_created_by ON notification_templates(created_by);
|
||||
42
database/migrations/003_create_template_version_history.sql
Normal file
42
database/migrations/003_create_template_version_history.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
-- Migration: Create template version history table
|
||||
-- Date: 2024-01-15
|
||||
-- Description: Create table to store version history of notification templates
|
||||
|
||||
CREATE TABLE `notification_template_versions` (
|
||||
`id` varchar(36) NOT NULL DEFAULT (UUID()),
|
||||
`template_id` varchar(36) NOT NULL,
|
||||
`version` varchar(20) NOT NULL,
|
||||
`name` varchar(100) NOT NULL,
|
||||
`description` text,
|
||||
`subject` varchar(255) DEFAULT NULL,
|
||||
`preheader` varchar(255) DEFAULT NULL,
|
||||
`email_content` text,
|
||||
`push_title` varchar(100) DEFAULT NULL,
|
||||
`push_body` varchar(300) DEFAULT NULL,
|
||||
`push_icon` varchar(500) DEFAULT NULL,
|
||||
`push_url` text,
|
||||
`sms_content` text,
|
||||
`category` varchar(50) DEFAULT NULL,
|
||||
`channels` json DEFAULT NULL,
|
||||
`status` varchar(20) DEFAULT 'Draft',
|
||||
`tags` text,
|
||||
`is_personal` tinyint(1) DEFAULT '0',
|
||||
`from_name` varchar(100) DEFAULT NULL,
|
||||
`reply_to` varchar(255) DEFAULT NULL,
|
||||
`track_opens` tinyint(1) DEFAULT '1',
|
||||
`variables` json DEFAULT NULL,
|
||||
`is_active` tinyint(1) DEFAULT '1',
|
||||
`change_description` text,
|
||||
`is_current` tinyint(1) DEFAULT '0',
|
||||
`created_by` varchar(36) DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_template_versions_template_id` (`template_id`),
|
||||
KEY `idx_template_versions_version` (`version`),
|
||||
KEY `idx_template_versions_created_at` (`created_at`),
|
||||
CONSTRAINT `fk_template_versions_template_id` FOREIGN KEY (`template_id`) REFERENCES `notification_templates` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create index for better performance
|
||||
CREATE INDEX `idx_template_versions_template_version` ON `notification_template_versions` (`template_id`, `version`);
|
||||
CREATE INDEX `idx_template_versions_is_current` ON `notification_template_versions` (`is_current`);
|
||||
22
database/migrations/004_create_notification_logs_table.sql
Normal file
22
database/migrations/004_create_notification_logs_table.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Create notification logs table for audit trail
|
||||
CREATE TABLE IF NOT EXISTS `notification_logs` (
|
||||
`id` VARCHAR(36) NOT NULL DEFAULT (UUID()),
|
||||
`notification_id` VARCHAR(36) NULL,
|
||||
`action` VARCHAR(50) NOT NULL,
|
||||
`actor` VARCHAR(100) NULL,
|
||||
`actor_id` VARCHAR(36) NULL,
|
||||
`channel_type` VARCHAR(20) NULL,
|
||||
`status` VARCHAR(30) NULL,
|
||||
`details` TEXT NULL,
|
||||
`source_ip` VARCHAR(50) NULL,
|
||||
`error_code` VARCHAR(50) NULL,
|
||||
`error_message` TEXT NULL,
|
||||
`metadata` JSON NULL,
|
||||
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_notification_logs_notification_id` (`notification_id`),
|
||||
INDEX `idx_notification_logs_action` (`action`),
|
||||
INDEX `idx_notification_logs_created_at` (`created_at`),
|
||||
INDEX `idx_notification_logs_status` (`status`),
|
||||
INDEX `idx_notification_logs_channel_type` (`channel_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
90
database/migrations/005_support_multiple_email_providers.sql
Normal file
90
database/migrations/005_support_multiple_email_providers.sql
Normal file
@@ -0,0 +1,90 @@
|
||||
-- Migration: Support Multiple Email Providers
|
||||
-- This migration allows storing multiple provider configurations per channel type
|
||||
-- Previously: Only one config per channel_type (unique constraint)
|
||||
-- Now: Multiple configs per channel_type, identified by provider
|
||||
|
||||
-- Step 1: Drop the unique constraint on channel_type
|
||||
ALTER TABLE notification_delivery_config
|
||||
DROP INDEX channel_type;
|
||||
|
||||
-- Step 2: Add unique constraint on (channel_type, provider) combination
|
||||
ALTER TABLE notification_delivery_config
|
||||
ADD UNIQUE KEY unique_channel_provider (channel_type, provider);
|
||||
|
||||
-- Step 3: Add active_provider column to track which provider is currently active
|
||||
ALTER TABLE notification_delivery_config
|
||||
ADD COLUMN is_active BOOLEAN DEFAULT false AFTER is_enabled;
|
||||
|
||||
-- Step 4: Set current email config as active (if exists)
|
||||
UPDATE notification_delivery_config
|
||||
SET is_active = true
|
||||
WHERE channel_type = 'email'
|
||||
AND is_enabled = true
|
||||
LIMIT 1;
|
||||
|
||||
-- Step 5: Insert Mailtrap configuration if not exists
|
||||
INSERT IGNORE INTO notification_delivery_config
|
||||
(channel_type, is_enabled, is_active, provider, provider_config, status, success_rate, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (
|
||||
'email',
|
||||
false,
|
||||
false,
|
||||
'Mailtrap',
|
||||
JSON_OBJECT(
|
||||
'host', 'live.smtp.mailtrap.io',
|
||||
'port', 587,
|
||||
'secure', false,
|
||||
'user', 'apismtp@mailtrap.io',
|
||||
'pass', '',
|
||||
'senderEmail', '',
|
||||
'senderName', ''
|
||||
),
|
||||
'Not Configured',
|
||||
0.0,
|
||||
1,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
||||
-- Step 6: Insert AWS SES configuration if not exists
|
||||
INSERT IGNORE INTO notification_delivery_config
|
||||
(channel_type, is_enabled, is_active, provider, provider_config, status, success_rate, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (
|
||||
'email',
|
||||
false,
|
||||
false,
|
||||
'AWS SES',
|
||||
JSON_OBJECT(
|
||||
'host', '',
|
||||
'port', 587,
|
||||
'secure', false,
|
||||
'user', '',
|
||||
'pass', '',
|
||||
'senderEmail', '',
|
||||
'senderName', '',
|
||||
'region', 'us-east-1',
|
||||
'configurationSet', ''
|
||||
),
|
||||
'Not Configured',
|
||||
0.0,
|
||||
1,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
||||
-- Verify the migration
|
||||
SELECT
|
||||
id,
|
||||
channel_type,
|
||||
provider,
|
||||
is_enabled,
|
||||
is_active,
|
||||
status,
|
||||
JSON_EXTRACT(provider_config, '$.host') as smtp_host,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM notification_delivery_config
|
||||
WHERE channel_type = 'email'
|
||||
ORDER BY provider;
|
||||
@@ -0,0 +1,126 @@
|
||||
-- Migration: Support Multiple Email Providers (SAFE VERSION)
|
||||
-- This migration allows storing multiple provider configurations per channel type
|
||||
|
||||
-- STEP 0: First, check what indexes exist on the table
|
||||
-- Run this query to see existing indexes:
|
||||
SHOW INDEX FROM notification_delivery_config;
|
||||
|
||||
-- You should see output showing index names. Look for a unique index on 'channel_type'
|
||||
-- Common names: 'channel_type', 'notification_delivery_config_channel_type_key', or similar
|
||||
|
||||
-- STEP 1: Drop the unique constraint on channel_type
|
||||
-- Option A: If the index is named 'channel_type'
|
||||
-- ALTER TABLE notification_delivery_config DROP INDEX channel_type;
|
||||
|
||||
-- Option B: If you don't have a unique constraint yet, skip step 1
|
||||
|
||||
-- Check if constraint exists before dropping (MySQL 5.7+)
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_delivery_config'
|
||||
AND index_name = 'channel_type');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'ALTER TABLE notification_delivery_config DROP INDEX channel_type',
|
||||
'SELECT "Index does not exist, skipping..."');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- STEP 2: Add unique constraint on (channel_type, provider) combination
|
||||
-- First check if it already exists
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_delivery_config'
|
||||
AND index_name = 'unique_channel_provider');
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE notification_delivery_config ADD UNIQUE KEY unique_channel_provider (channel_type, provider)',
|
||||
'SELECT "Unique index already exists, skipping..."');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- STEP 3: Add is_active column if it doesn't exist
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_delivery_config'
|
||||
AND column_name = 'is_active');
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE notification_delivery_config ADD COLUMN is_active BOOLEAN DEFAULT false AFTER is_enabled',
|
||||
'SELECT "Column is_active already exists, skipping..."');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- STEP 4: Set current email config as active (if exists)
|
||||
UPDATE notification_delivery_config
|
||||
SET is_active = true
|
||||
WHERE channel_type = 'email'
|
||||
AND is_enabled = true
|
||||
LIMIT 1;
|
||||
|
||||
-- STEP 5: Insert Mailtrap configuration if not exists
|
||||
INSERT IGNORE INTO notification_delivery_config
|
||||
(channel_type, is_enabled, is_active, provider, provider_config, status, success_rate, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (
|
||||
'email',
|
||||
false,
|
||||
false,
|
||||
'Mailtrap',
|
||||
JSON_OBJECT(
|
||||
'host', 'live.smtp.mailtrap.io',
|
||||
'port', 587,
|
||||
'secure', false,
|
||||
'user', 'apismtp@mailtrap.io',
|
||||
'pass', '',
|
||||
'senderEmail', '',
|
||||
'senderName', ''
|
||||
),
|
||||
'Not Configured',
|
||||
0.0,
|
||||
1,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
||||
-- STEP 6: Insert AWS SES configuration if not exists
|
||||
INSERT IGNORE INTO notification_delivery_config
|
||||
(channel_type, is_enabled, is_active, provider, provider_config, status, success_rate, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (
|
||||
'email',
|
||||
false,
|
||||
false,
|
||||
'AWS SES',
|
||||
JSON_OBJECT(
|
||||
'host', '',
|
||||
'port', 587,
|
||||
'secure', false,
|
||||
'user', '',
|
||||
'pass', '',
|
||||
'senderEmail', '',
|
||||
'senderName', '',
|
||||
'region', 'us-east-1',
|
||||
'configurationSet', ''
|
||||
),
|
||||
'Not Configured',
|
||||
0.0,
|
||||
1,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
||||
-- STEP 7: Verify the migration
|
||||
SELECT
|
||||
id,
|
||||
channel_type,
|
||||
provider,
|
||||
is_enabled,
|
||||
is_active,
|
||||
status,
|
||||
JSON_EXTRACT(provider_config, '$.host') as smtp_host,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM notification_delivery_config
|
||||
WHERE channel_type = 'email'
|
||||
ORDER BY provider;
|
||||
128
database/migrations/006_add_performance_indexes.sql
Normal file
128
database/migrations/006_add_performance_indexes.sql
Normal file
@@ -0,0 +1,128 @@
|
||||
-- Migration: Add performance indexes for notification system
|
||||
-- Purpose: Optimize queue processing queries and recipient lookups
|
||||
-- Date: 2025-10-16
|
||||
|
||||
-- ==================================================================
|
||||
-- 1. Composite index for queue processing
|
||||
-- ==================================================================
|
||||
-- This index optimizes the main queue processing query that filters by:
|
||||
-- - status (queued/processing)
|
||||
-- - scheduled_for (less than or equal to current time)
|
||||
-- - priority (for ordering)
|
||||
-- - last_attempt_at (for timeout recovery)
|
||||
|
||||
-- Check if index exists, drop if it does, then create
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_queue'
|
||||
AND index_name = 'idx_queue_processing_composite');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_queue_processing_composite already exists"',
|
||||
'CREATE INDEX idx_queue_processing_composite ON notification_queue(status, scheduled_for, priority, last_attempt_at)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- 2. Index for stuck job recovery
|
||||
-- ==================================================================
|
||||
-- Note: WHERE clause not supported in MySQL, using regular index instead
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_queue'
|
||||
AND index_name = 'idx_queue_stuck_recovery');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_queue_stuck_recovery already exists"',
|
||||
'CREATE INDEX idx_queue_stuck_recovery ON notification_queue(status, last_attempt_at)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- 3. Composite index for recipient status tracking
|
||||
-- ==================================================================
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_recipients'
|
||||
AND index_name = 'idx_recipients_notification_status');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_recipients_notification_status already exists"',
|
||||
'CREATE INDEX idx_recipients_notification_status ON notification_recipients(notification_id, status)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- 4. Index for notification logs by notification and date
|
||||
-- ==================================================================
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_logs'
|
||||
AND index_name = 'idx_logs_notification_created');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_logs_notification_created already exists"',
|
||||
'CREATE INDEX idx_logs_notification_created ON notification_logs(notification_id, created_at)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- 5. Index for notification logs by status and date
|
||||
-- ==================================================================
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_logs'
|
||||
AND index_name = 'idx_logs_status_created');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_logs_status_created already exists"',
|
||||
'CREATE INDEX idx_logs_status_created ON notification_logs(status, created_at)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- 6. Index for notification delivery config lookups
|
||||
-- ==================================================================
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_delivery_config'
|
||||
AND index_name = 'idx_delivery_config_channel_enabled');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_delivery_config_channel_enabled already exists"',
|
||||
'CREATE INDEX idx_delivery_config_channel_enabled ON notification_delivery_config(channel_type, is_enabled)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- 7. Index for queue items by recipient
|
||||
-- ==================================================================
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notification_queue'
|
||||
AND index_name = 'idx_queue_recipient');
|
||||
SET @sqlstmt := IF(@exist > 0,
|
||||
'SELECT "Index idx_queue_recipient already exists"',
|
||||
'CREATE INDEX idx_queue_recipient ON notification_queue(recipient_id)');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ==================================================================
|
||||
-- Verify indexes were created
|
||||
-- ==================================================================
|
||||
SELECT
|
||||
table_name,
|
||||
index_name,
|
||||
column_name
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name IN ('notification_queue', 'notification_recipients', 'notification_logs', 'notification_delivery_config')
|
||||
AND index_name LIKE 'idx_%'
|
||||
ORDER BY table_name, index_name, seq_in_index;
|
||||
167
database/migrations/MIGRATION_GUIDE.md
Normal file
167
database/migrations/MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Migration Guide: Multi-Provider Email Support
|
||||
|
||||
## Step 1: Check Current Schema
|
||||
|
||||
First, check what indexes exist on your table:
|
||||
|
||||
```sql
|
||||
SHOW INDEX FROM notification_delivery_config;
|
||||
```
|
||||
|
||||
Look for a unique index on `channel_type`. It might be named:
|
||||
- `channel_type`
|
||||
- `notification_delivery_config_channel_type_key`
|
||||
- Or something similar
|
||||
|
||||
**If you see a unique index on `channel_type`, note the exact name.**
|
||||
|
||||
## Step 2: Drop Old Unique Constraint (if exists)
|
||||
|
||||
Replace `INDEX_NAME_HERE` with the actual name from Step 1:
|
||||
|
||||
```sql
|
||||
-- If the index name is 'channel_type':
|
||||
ALTER TABLE notification_delivery_config DROP INDEX channel_type;
|
||||
|
||||
-- OR if it has a different name:
|
||||
ALTER TABLE notification_delivery_config DROP INDEX notification_delivery_config_channel_type_key;
|
||||
```
|
||||
|
||||
**If you got error "Can't DROP"**, it means there's no unique constraint. **This is fine! Skip to Step 3.**
|
||||
|
||||
## Step 3: Add New Unique Constraint
|
||||
|
||||
```sql
|
||||
ALTER TABLE notification_delivery_config
|
||||
ADD UNIQUE KEY unique_channel_provider (channel_type, provider);
|
||||
```
|
||||
|
||||
This allows multiple providers per channel.
|
||||
|
||||
## Step 4: Add is_active Column
|
||||
|
||||
```sql
|
||||
ALTER TABLE notification_delivery_config
|
||||
ADD COLUMN is_active BOOLEAN DEFAULT false AFTER is_enabled;
|
||||
```
|
||||
|
||||
## Step 5: Mark Current Config as Active
|
||||
|
||||
```sql
|
||||
UPDATE notification_delivery_config
|
||||
SET is_active = true
|
||||
WHERE channel_type = 'email'
|
||||
AND is_enabled = true
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
## Step 6: Insert Mailtrap Config
|
||||
|
||||
```sql
|
||||
INSERT IGNORE INTO notification_delivery_config
|
||||
(channel_type, is_enabled, is_active, provider, provider_config, status, success_rate, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (
|
||||
'email',
|
||||
false,
|
||||
false,
|
||||
'Mailtrap',
|
||||
JSON_OBJECT(
|
||||
'host', 'live.smtp.mailtrap.io',
|
||||
'port', 587,
|
||||
'secure', false,
|
||||
'user', 'apismtp@mailtrap.io',
|
||||
'pass', '',
|
||||
'senderEmail', '',
|
||||
'senderName', ''
|
||||
),
|
||||
'Not Configured',
|
||||
0.0,
|
||||
1,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
```
|
||||
|
||||
## Step 7: Insert AWS SES Config
|
||||
|
||||
```sql
|
||||
INSERT IGNORE INTO notification_delivery_config
|
||||
(channel_type, is_enabled, is_active, provider, provider_config, status, success_rate, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (
|
||||
'email',
|
||||
false,
|
||||
false,
|
||||
'AWS SES',
|
||||
JSON_OBJECT(
|
||||
'host', '',
|
||||
'port', 587,
|
||||
'secure', false,
|
||||
'user', '',
|
||||
'pass', '',
|
||||
'senderEmail', '',
|
||||
'senderName', '',
|
||||
'region', 'us-east-1',
|
||||
'configurationSet', ''
|
||||
),
|
||||
'Not Configured',
|
||||
0.0,
|
||||
1,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
```
|
||||
|
||||
## Step 8: Verify Migration
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
channel_type,
|
||||
provider,
|
||||
is_enabled,
|
||||
is_active,
|
||||
status,
|
||||
JSON_EXTRACT(provider_config, '$.host') as smtp_host
|
||||
FROM notification_delivery_config
|
||||
WHERE channel_type = 'email'
|
||||
ORDER BY provider;
|
||||
```
|
||||
|
||||
You should see:
|
||||
- At least 2 rows for email (Mailtrap and AWS SES)
|
||||
- One might be `is_enabled = true` (your current config)
|
||||
- Only one should have `is_active = true`
|
||||
|
||||
## Step 9: Regenerate Prisma Client
|
||||
|
||||
After database migration, update your Prisma client:
|
||||
|
||||
```bash
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Can't DROP 'channel_type'"
|
||||
**Solution:** Your table doesn't have a unique constraint on `channel_type`. This is fine! Skip Step 2 and continue with Step 3.
|
||||
|
||||
### Error: "Duplicate entry for key 'unique_channel_provider'"
|
||||
**Solution:** The unique constraint already exists. Skip Step 3.
|
||||
|
||||
### Error: "Duplicate column name 'is_active'"
|
||||
**Solution:** The column already exists. Skip Step 4.
|
||||
|
||||
### Error: "Duplicate entry" on INSERT
|
||||
**Solution:** The config already exists. This is normal with `INSERT IGNORE`.
|
||||
|
||||
## Alternative: Use Safe Migration Script
|
||||
|
||||
If you prefer, run the safe migration script that handles all checks automatically:
|
||||
|
||||
```bash
|
||||
mysql -h 18.138.137.105 -u admin -p corrad-notification < database/migrations/005_support_multiple_email_providers_safe.sql
|
||||
```
|
||||
|
||||
This script checks for existing indexes/columns before making changes.
|
||||
Reference in New Issue
Block a user