273 lines
9.2 KiB
JavaScript
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'
|
|
})
|
|
}
|
|
})
|