308 lines
9.6 KiB
JavaScript
308 lines
9.6 KiB
JavaScript
import { z } from "zod";
|
|
import prisma from "~/server/utils/prisma";
|
|
|
|
// Input validation schema for updating templates
|
|
const updateTemplateSchema = 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 {
|
|
// Get template ID from route parameters
|
|
const templateId = getRouterParam(event, "id");
|
|
|
|
if (!templateId) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Template ID is required",
|
|
});
|
|
}
|
|
|
|
// Read and validate request body
|
|
const body = await readBody(event);
|
|
|
|
console.log("Template update request for ID:", templateId, body);
|
|
|
|
// Validate input data
|
|
const validationResult = updateTemplateSchema.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);
|
|
|
|
// Check if template exists
|
|
const existingTemplate = await prisma.notification_templates.findUnique({
|
|
where: {
|
|
id: templateId
|
|
}
|
|
});
|
|
|
|
if (!existingTemplate) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: "Template not found",
|
|
});
|
|
}
|
|
|
|
console.log("Template found for update:", existingTemplate.name);
|
|
|
|
// 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 if title changed
|
|
let finalValue = existingTemplate.value;
|
|
if (validatedData.title !== existingTemplate.name) {
|
|
let uniqueValue = generateUniqueValue(validatedData.title);
|
|
|
|
// Check if value already exists and make it unique
|
|
let counter = 1;
|
|
finalValue = uniqueValue;
|
|
while (true) {
|
|
const existingValueTemplate = await prisma.notification_templates.findUnique({
|
|
where: {
|
|
value: finalValue,
|
|
NOT: { id: templateId } // Exclude current template
|
|
}
|
|
});
|
|
|
|
if (!existingValueTemplate) {
|
|
break;
|
|
}
|
|
|
|
finalValue = `${uniqueValue}_${counter}`;
|
|
counter++;
|
|
|
|
// Safety check to prevent infinite loop
|
|
if (counter > 100) {
|
|
finalValue = `${uniqueValue}_${Date.now()}`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prepare data for database update
|
|
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",
|
|
updated_by: user.userID.toString(),
|
|
updated_at: new Date(),
|
|
};
|
|
|
|
console.log("Updating template with data:", templateData);
|
|
|
|
// Create version history entry before updating the template
|
|
await prisma.notification_template_versions.create({
|
|
data: {
|
|
template_id: templateId,
|
|
version: existingTemplate.version || "1.0",
|
|
name: existingTemplate.name,
|
|
description: existingTemplate.description,
|
|
subject: existingTemplate.subject,
|
|
preheader: existingTemplate.preheader,
|
|
email_content: existingTemplate.email_content,
|
|
push_title: existingTemplate.push_title,
|
|
push_body: existingTemplate.push_body,
|
|
push_icon: existingTemplate.push_icon,
|
|
push_url: existingTemplate.push_url,
|
|
sms_content: existingTemplate.sms_content,
|
|
category: existingTemplate.category,
|
|
channels: existingTemplate.channels,
|
|
status: existingTemplate.status,
|
|
tags: existingTemplate.tags,
|
|
is_personal: existingTemplate.is_personal,
|
|
from_name: existingTemplate.from_name,
|
|
reply_to: existingTemplate.reply_to,
|
|
track_opens: existingTemplate.track_opens,
|
|
variables: existingTemplate.variables,
|
|
is_active: existingTemplate.is_active,
|
|
change_description: `Template updated - version ${existingTemplate.version}`,
|
|
is_current: false,
|
|
created_by: user.userID.toString(),
|
|
}
|
|
});
|
|
|
|
// Update the template
|
|
const updatedTemplate = await prisma.notification_templates.update({
|
|
where: {
|
|
id: templateId
|
|
},
|
|
data: templateData
|
|
});
|
|
|
|
console.log("Template updated successfully:", updatedTemplate.id);
|
|
|
|
// Return success response
|
|
return {
|
|
success: true,
|
|
data: {
|
|
id: updatedTemplate.id,
|
|
message: "Template updated successfully",
|
|
template: {
|
|
id: updatedTemplate.id,
|
|
name: updatedTemplate.name,
|
|
value: updatedTemplate.value,
|
|
status: updatedTemplate.status,
|
|
category: updatedTemplate.category,
|
|
channels: updatedTemplate.channels,
|
|
version: updatedTemplate.version,
|
|
updated_at: updatedTemplate.updated_at
|
|
}
|
|
},
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error("Template update 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
|
|
}
|
|
});
|
|
}
|
|
|
|
if (error.code === 'P2025') {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: "Template not found",
|
|
data: {
|
|
error: "The template you're trying to update does not exist.",
|
|
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 update template",
|
|
data: {
|
|
error: error.message
|
|
}
|
|
});
|
|
} finally {
|
|
}
|
|
});
|