273 lines
9.2 KiB
JavaScript

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'
})
}
})