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:
Haqeem Solehan
2025-10-16 16:05:39 +08:00
commit b124ff8092
336 changed files with 94392 additions and 0 deletions

View 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"}');

View 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);

View 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`);

View 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;

View 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;

View File

@@ -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;

View 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;

View 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.