Files
Nas-Notification/server/api/notifications/templates/create.post.js

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 {
}
});