import { z } from 'zod' // Similar schema to the main creation but with relaxed validation for drafts const draftNotificationSchema = z.object({ title: z.string().optional(), type: z.enum(['single', 'bulk']).optional(), priority: z.enum(['low', 'medium', 'high', 'critical']).optional(), category: z.string().optional(), channels: z.array(z.enum(['email', 'push', 'sms', 'in_app'])).optional(), emailSubject: z.string().optional(), expiresAt: z.string().datetime().optional(), deliveryType: z.enum(['immediate', 'scheduled']).optional(), scheduledAt: z.string().datetime().optional(), timezone: z.string().default('UTC'), enableAbTesting: z.boolean().default(false), abTestSplit: z.number().int().min(10).max(90).default(50), abTestName: z.string().optional(), enableTracking: z.boolean().default(true), audienceType: z.enum(['all', 'specific', 'segmented']).optional(), specificUsers: z.string().optional(), userSegments: z.array(z.string()).optional(), userStatus: z.string().optional(), registrationPeriod: z.string().optional(), excludeUnsubscribed: z.boolean().default(true), respectDoNotDisturb: z.boolean().default(true), contentType: z.enum(['new', 'template']).optional(), selectedTemplate: z.string().optional(), emailContent: z.string().optional(), callToActionText: z.string().optional(), callToActionUrl: z.string().url().optional(), pushTitle: z.string().max(100).optional(), pushBody: z.string().max(300).optional(), pushImageUrl: z.string().url().optional(), // Draft-specific fields draftId: z.string().uuid().optional(), // For updating existing drafts }) export default defineEventHandler(async (event) => { try { const body = await readValidatedBody(event, draftNotificationSchema.parse) // Get current user const user = event.context.user if (!user) { throw createError({ statusCode: 401, statusMessage: 'Authentication required' }) } const { $db } = useNitroApp() // Start transaction await $db.query('BEGIN') try { let notificationId = body.draftId // Get category ID if category is provided let categoryId = null if (body.category) { const categoryResult = await $db.query( 'SELECT id FROM notification_categories WHERE value = $1', [body.category] ) if (categoryResult.rows.length > 0) { categoryId = categoryResult.rows[0].id } } // Get template ID if template is selected let templateId = null if (body.selectedTemplate) { const templateResult = await $db.query( 'SELECT id FROM notification_templates WHERE value = $1 AND is_active = true', [body.selectedTemplate] ) if (templateResult.rows.length > 0) { templateId = templateResult.rows[0].id } } if (notificationId) { // Update existing draft const updateResult = await $db.query(` UPDATE notifications SET title = COALESCE($1, title), type = COALESCE($2, type), priority = COALESCE($3, priority), category_id = COALESCE($4, category_id), delivery_type = COALESCE($5, delivery_type), scheduled_at = $6, timezone = COALESCE($7, timezone), expires_at = $8, enable_ab_testing = COALESCE($9, enable_ab_testing), ab_test_split = COALESCE($10, ab_test_split), ab_test_name = $11, enable_tracking = COALESCE($12, enable_tracking), audience_type = COALESCE($13, audience_type), specific_users = $14, user_status = $15, registration_period = $16, exclude_unsubscribed = COALESCE($17, exclude_unsubscribed), respect_do_not_disturb = COALESCE($18, respect_do_not_disturb), content_type = COALESCE($19, content_type), template_id = $20, email_subject = $21, email_content = $22, call_to_action_text = $23, call_to_action_url = $24, push_title = $25, push_body = $26, push_image_url = $27, updated_at = CURRENT_TIMESTAMP WHERE id = $28 AND created_by = $29 AND status = 'draft' RETURNING id `, [ body.title || null, body.type || null, body.priority || null, categoryId, body.deliveryType || null, body.scheduledAt || null, body.timezone, body.expiresAt || null, body.enableAbTesting, body.abTestSplit, body.abTestName || null, body.enableTracking, body.audienceType || null, body.specificUsers || null, body.userStatus || null, body.registrationPeriod || null, body.excludeUnsubscribed, body.respectDoNotDisturb, body.contentType || null, templateId, body.emailSubject || null, body.emailContent || null, body.callToActionText || null, body.callToActionUrl || null, body.pushTitle || null, body.pushBody || null, body.pushImageUrl || null, notificationId, user.id ]) if (updateResult.rows.length === 0) { throw createError({ statusCode: 404, statusMessage: 'Draft not found or you do not have permission to update it' }) } } else { // Create new draft const insertResult = await $db.query(` INSERT INTO notifications ( title, type, priority, category_id, delivery_type, scheduled_at, timezone, expires_at, enable_ab_testing, ab_test_split, ab_test_name, enable_tracking, audience_type, specific_users, user_status, registration_period, exclude_unsubscribed, respect_do_not_disturb, content_type, template_id, email_subject, email_content, call_to_action_text, call_to_action_url, push_title, push_body, push_image_url, created_by, status ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, 'draft' ) RETURNING id `, [ body.title || 'Untitled Draft', body.type || 'single', body.priority || 'medium', categoryId, body.deliveryType || 'immediate', body.scheduledAt || null, body.timezone, body.expiresAt || null, body.enableAbTesting, body.abTestSplit, body.abTestName || null, body.enableTracking, body.audienceType || 'all', body.specificUsers || null, body.userStatus || null, body.registrationPeriod || null, body.excludeUnsubscribed, body.respectDoNotDisturb, body.contentType || 'new', templateId, body.emailSubject || null, body.emailContent || null, body.callToActionText || null, body.callToActionUrl || null, body.pushTitle || null, body.pushBody || null, body.pushImageUrl || null, user.id ]) notificationId = insertResult.rows[0].id } // Update channels if provided if (body.channels && body.channels.length > 0) { // Delete existing channels await $db.query( 'DELETE FROM notification_channels WHERE notification_id = $1', [notificationId] ) // Insert new channels for (const channel of body.channels) { await $db.query( 'INSERT INTO notification_channels (notification_id, channel_type) VALUES ($1, $2)', [notificationId, channel] ) } } // Update user segments if provided if (body.userSegments && body.userSegments.length > 0) { // Delete existing segments await $db.query( 'DELETE FROM notification_user_segments WHERE notification_id = $1', [notificationId] ) // Insert new segments for (const segment of body.userSegments) { const segmentResult = await $db.query( 'SELECT id FROM user_segments WHERE value = $1 AND is_active = true', [segment] ) if (segmentResult.rows.length > 0) { await $db.query( 'INSERT INTO notification_user_segments (notification_id, segment_id) VALUES ($1, $2)', [notificationId, segmentResult.rows[0].id] ) } } } // Commit transaction await $db.query('COMMIT') return { success: true, data: { id: notificationId, message: 'Draft saved successfully' } } } catch (error) { await $db.query('ROLLBACK') throw error } } catch (error) { console.error('Error saving draft:', error) if (error.statusCode) { throw error } throw createError({ statusCode: 500, statusMessage: 'Failed to save draft' }) } })