232 lines
7.1 KiB
JavaScript
232 lines
7.1 KiB
JavaScript
import { z } from "zod";
|
|
import prisma from "~/server/utils/prisma";
|
|
|
|
// Input validation schema
|
|
const createTemplateSchema = z.object({
|
|
title: z.string().min(1, "Title is required").max(100, "Title must be less than 100 characters"),
|
|
description: z.string().optional(),
|
|
subject: z.string().min(1, "Subject is required").max(255, "Subject must be less than 255 characters"),
|
|
preheader: z.string().max(255, "Preheader must be less than 255 characters").optional(),
|
|
category: z.string().min(1, "Category is required"),
|
|
channels: z.array(z.enum(["email", "sms", "push"])).min(1, "At least one channel is required"),
|
|
status: z.enum(["Draft", "Active", "Archived"]).default("Draft"),
|
|
version: z.string().default("1.0"),
|
|
content: z.string().min(1, "Content is required"),
|
|
tags: z.string().optional(),
|
|
isPersonal: z.boolean().default(false),
|
|
// Email specific settings
|
|
fromName: z.string().optional(),
|
|
replyTo: z.string().email("Invalid reply-to email format").optional().or(z.literal("")),
|
|
trackOpens: z.boolean().default(true),
|
|
// Push notification specific settings
|
|
pushTitle: z.string().optional(),
|
|
pushIcon: z.string().url("Invalid push icon URL").optional().or(z.literal("")),
|
|
pushUrl: z.string().url("Invalid push URL").optional().or(z.literal("")),
|
|
// SMS specific settings
|
|
smsContent: z.string().max(160, "SMS content must be 160 characters or less").optional(),
|
|
});
|
|
|
|
// Helper function to generate unique value from title
|
|
function generateUniqueValue(title) {
|
|
return title
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9\s]/g, '')
|
|
.replace(/\s+/g, '_')
|
|
.substring(0, 50);
|
|
}
|
|
|
|
// Helper function to validate template content
|
|
function validateTemplateContent(content, channels) {
|
|
const errors = [];
|
|
|
|
// Check for balanced variable syntax
|
|
const variableMatches = content.match(/\{\{[^}]*\}\}/g) || [];
|
|
for (const match of variableMatches) {
|
|
if (!match.endsWith('}}')) {
|
|
errors.push(`Unmatched variable syntax: ${match}`);
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
// Read and validate request body
|
|
const body = await readBody(event);
|
|
|
|
console.log("Template creation request:", body);
|
|
|
|
// Validate input data
|
|
const validationResult = createTemplateSchema.safeParse(body);
|
|
if (!validationResult.success) {
|
|
const errors = validationResult.error.errors.map(err =>
|
|
`${err.path.join('.')}: ${err.message}`
|
|
);
|
|
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Validation failed",
|
|
data: {
|
|
errors: errors
|
|
}
|
|
});
|
|
}
|
|
|
|
const validatedData = validationResult.data;
|
|
|
|
// Get current user (assuming auth middleware provides this)
|
|
const user = event.context.user;
|
|
if (!user || !user.userID) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
statusMessage: "Authentication required",
|
|
});
|
|
}
|
|
|
|
console.log("User authenticated:", user.userID);
|
|
|
|
// Validate template content
|
|
const contentErrors = validateTemplateContent(validatedData.content, validatedData.channels);
|
|
if (contentErrors.length > 0) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Content validation failed",
|
|
data: {
|
|
errors: contentErrors
|
|
}
|
|
});
|
|
}
|
|
|
|
// Generate unique value for the template
|
|
let uniqueValue = generateUniqueValue(validatedData.title);
|
|
|
|
// Check if value already exists and make it unique
|
|
let counter = 1;
|
|
let finalValue = uniqueValue;
|
|
while (true) {
|
|
const existingTemplate = await prisma.notification_templates.findUnique({
|
|
where: { value: finalValue }
|
|
});
|
|
|
|
if (!existingTemplate) {
|
|
break;
|
|
}
|
|
|
|
finalValue = `${uniqueValue}_${counter}`;
|
|
counter++;
|
|
|
|
// Safety check to prevent infinite loop
|
|
if (counter > 100) {
|
|
finalValue = `${uniqueValue}_${Date.now()}`;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Prepare data for database insertion
|
|
const templateData = {
|
|
name: validatedData.title,
|
|
value: finalValue,
|
|
description: validatedData.description || null,
|
|
subject: validatedData.subject,
|
|
preheader: validatedData.preheader || null,
|
|
email_content: validatedData.content,
|
|
push_title: validatedData.pushTitle || null,
|
|
push_body: validatedData.content ? validatedData.content.replace(/<[^>]*>/g, '').substring(0, 300) : null,
|
|
push_icon: validatedData.pushIcon || null,
|
|
push_url: validatedData.pushUrl || null,
|
|
sms_content: validatedData.smsContent || null,
|
|
category: validatedData.category,
|
|
channels: validatedData.channels,
|
|
status: validatedData.status,
|
|
version: validatedData.version,
|
|
tags: validatedData.tags || null,
|
|
is_personal: validatedData.isPersonal,
|
|
from_name: validatedData.fromName || null,
|
|
reply_to: validatedData.replyTo || null,
|
|
track_opens: validatedData.trackOpens,
|
|
is_active: validatedData.status === "Active" ? 1 : validatedData.status === "Draft" ? 0 : 2,
|
|
created_by: user.userID.toString(),
|
|
updated_by: user.userID.toString(),
|
|
};
|
|
|
|
console.log("Creating template with data:", templateData);
|
|
|
|
// Create the template
|
|
const newTemplate = await prisma.notification_templates.create({
|
|
data: templateData
|
|
});
|
|
|
|
console.log("Template created successfully:", newTemplate.id);
|
|
|
|
// Return success response
|
|
return {
|
|
success: true,
|
|
data: {
|
|
id: newTemplate.id,
|
|
message: "Template created successfully",
|
|
template: {
|
|
id: newTemplate.id,
|
|
name: newTemplate.name,
|
|
value: newTemplate.value,
|
|
status: newTemplate.status,
|
|
category: newTemplate.category,
|
|
channels: newTemplate.channels,
|
|
version: newTemplate.version,
|
|
created_at: newTemplate.created_at
|
|
}
|
|
},
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error("Template creation error:", error);
|
|
console.error("Error details:", {
|
|
message: error.message,
|
|
stack: error.stack,
|
|
cause: error.cause,
|
|
code: error.code,
|
|
statusCode: error.statusCode
|
|
});
|
|
|
|
// Handle Prisma errors
|
|
if (error.code && error.code.startsWith('P')) {
|
|
console.error("Prisma error code:", error.code);
|
|
|
|
if (error.code === 'P2002') {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Template with this name already exists",
|
|
data: {
|
|
error: "A template with this name already exists. Please choose a different name.",
|
|
code: error.code
|
|
}
|
|
});
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Database operation failed",
|
|
data: {
|
|
error: error.message,
|
|
code: error.code
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle known errors with status codes
|
|
if (error.statusCode) {
|
|
throw error;
|
|
}
|
|
|
|
// Generic server error
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: "Failed to create template",
|
|
data: {
|
|
error: error.message
|
|
}
|
|
});
|
|
} finally {
|
|
}
|
|
});
|