Update various configuration files, components, and assets; enhance notification system and API endpoints; improve documentation and styles across the application.
This commit is contained in:
308
server/api/notifications/templates/[id].put.js
Normal file
308
server/api/notifications/templates/[id].put.js
Normal file
@@ -0,0 +1,308 @@
|
||||
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 {
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user