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:
Haqeem Solehan
2025-10-16 16:05:39 +08:00
commit b124ff8092
336 changed files with 94392 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
import prisma from "~/server/utils/prisma";
// 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);
}
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",
});
}
console.log("Duplicating template:", templateId);
// Get current user (assuming auth middleware provides this)
const user = event.context.user;
if (!user || !user.userID) {
throw createError({
statusCode: 401,
statusMessage: "Authentication required",
});
}
// Find the original template
const originalTemplate = await prisma.notification_templates.findUnique({
where: {
id: templateId
}
});
if (!originalTemplate) {
throw createError({
statusCode: 404,
statusMessage: "Template not found",
});
}
console.log("Original template found:", originalTemplate.name);
// Generate new name and value for the duplicate
const newName = `${originalTemplate.name} (Copy)`;
let uniqueValue = generateUniqueValue(newName);
// 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}_copy_${counter}`;
counter++;
// Safety check to prevent infinite loop
if (counter > 100) {
finalValue = `${uniqueValue}_copy_${Date.now()}`;
break;
}
}
// Prepare duplicate template data
const duplicateData = {
name: newName,
value: finalValue,
description: originalTemplate.description,
subject: originalTemplate.subject,
preheader: originalTemplate.preheader,
email_content: originalTemplate.email_content,
push_title: originalTemplate.push_title,
push_body: originalTemplate.push_body,
push_icon: originalTemplate.push_icon,
push_url: originalTemplate.push_url,
sms_content: originalTemplate.sms_content,
category: originalTemplate.category,
channels: originalTemplate.channels,
status: "Draft", // Always start as draft
version: "1.0", // Reset version for new template
tags: originalTemplate.tags,
is_personal: originalTemplate.is_personal,
from_name: originalTemplate.from_name,
reply_to: originalTemplate.reply_to,
track_opens: originalTemplate.track_opens,
variables: originalTemplate.variables,
is_active: false, // Inactive by default
created_by: user.userID.toString(),
updated_by: user.userID.toString(),
};
console.log("Creating duplicate with data:", duplicateData);
// Create the duplicate template
const duplicateTemplate = await prisma.notification_templates.create({
data: duplicateData
});
console.log("Template duplicated successfully:", duplicateTemplate.id);
// Return success response
return {
success: true,
data: {
message: `Template "${originalTemplate.name}" has been duplicated successfully`,
originalTemplate: {
id: originalTemplate.id,
name: originalTemplate.name,
value: originalTemplate.value
},
duplicateTemplate: {
id: duplicateTemplate.id,
name: duplicateTemplate.name,
value: duplicateTemplate.value,
status: duplicateTemplate.status,
version: duplicateTemplate.version,
created_at: duplicateTemplate.created_at
}
}
};
} catch (error) {
console.error("Template duplication 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: "Duplicate value conflict",
data: {
error: "Unable to generate unique identifier for the duplicate template. Please try again.",
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 duplicate template",
data: {
error: error.message
}
});
} finally {
}
});

View File

@@ -0,0 +1,149 @@
import prisma from "~/server/utils/prisma";
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",
});
}
console.log("Fetching version history for template:", templateId);
// Get current user (assuming auth middleware provides this)
const user = event.context.user;
if (!user || !user.userID) {
throw createError({
statusCode: 401,
statusMessage: "Authentication required",
});
}
// Check if template exists
const template = await prisma.notification_templates.findUnique({
where: { id: templateId }
});
if (!template) {
throw createError({
statusCode: 404,
statusMessage: "Template not found",
});
}
// Check if the version history table exists
let versions = [];
try {
// Fetch version history for the template
versions = await prisma.notification_template_versions.findMany({
where: {
template_id: templateId
},
orderBy: {
created_at: 'desc'
}
});
} catch (dbError) {
console.error("Database error when fetching versions:", dbError);
// If table doesn't exist, return empty array
if (dbError.code === 'P2021' || dbError.message.includes('doesn\'t exist')) {
console.log("Version history table doesn't exist, returning empty array");
versions = [];
} else {
throw dbError;
}
}
// Format the response data
const formattedVersions = versions.map(version => ({
id: version.id,
version: version.version,
name: version.name,
description: version.description,
subject: version.subject,
content: version.email_content,
changeDescription: version.change_description,
isCurrent: version.is_current,
status: version.status,
createdBy: version.created_by,
createdAt: version.created_at,
formattedCreatedAt: version.created_at
? new Date(version.created_at).toLocaleDateString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})
: "",
}));
console.log(`Found ${formattedVersions.length} versions for template ${templateId}`);
// Return success response
return {
success: true,
data: {
templateId,
templateName: template.name,
versions: formattedVersions,
totalCount: formattedVersions.length
}
};
} catch (error) {
console.error("Version history fetch 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 === 'P2025') {
throw createError({
statusCode: 404,
statusMessage: "Template not found",
data: {
error: "The template you're looking for 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 fetch version history",
data: {
error: error.message
}
});
} finally {
}
});

View File

@@ -0,0 +1,145 @@
import prisma from "~/server/utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get template ID and version ID from route parameters
const templateId = getRouterParam(event, "id");
const versionId = getRouterParam(event, "versionId");
if (!templateId || !versionId) {
throw createError({
statusCode: 400,
statusMessage: "Template ID and Version ID are required",
});
}
console.log(`Deleting version ${versionId} for template ${templateId}`);
// Get current user (assuming auth middleware provides this)
const user = event.context.user;
if (!user || !user.userID) {
throw createError({
statusCode: 401,
statusMessage: "Authentication required",
});
}
// Check if template exists
const template = await prisma.notification_templates.findUnique({
where: { id: templateId }
});
if (!template) {
throw createError({
statusCode: 404,
statusMessage: "Template not found",
});
}
// Check if version exists
const version = await prisma.notification_template_versions.findUnique({
where: { id: versionId }
});
if (!version || version.template_id !== templateId) {
throw createError({
statusCode: 404,
statusMessage: "Version not found",
});
}
// Check if this is the current version
if (version.is_current) {
throw createError({
statusCode: 400,
statusMessage: "Cannot delete current version",
data: {
error: "You cannot delete the current version of a template. Please restore a different version first."
}
});
}
// Check if there are other versions (prevent deletion of the only version)
const versionCount = await prisma.notification_template_versions.count({
where: { template_id: templateId }
});
if (versionCount <= 1) {
throw createError({
statusCode: 400,
statusMessage: "Cannot delete the only version",
data: {
error: "This is the only version of the template. At least one version must exist."
}
});
}
// Delete the version
await prisma.notification_template_versions.delete({
where: { id: versionId }
});
console.log(`Version ${version.version} deleted successfully`);
// Return success response
return {
success: true,
data: {
message: `Version ${version.version} has been deleted successfully`,
templateId,
deletedVersion: version.version,
deletedVersionId: versionId
}
};
} catch (error) {
console.error("Version delete 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 === 'P2025') {
throw createError({
statusCode: 404,
statusMessage: "Template or version not found",
data: {
error: "The template or version you're looking for 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 delete version",
data: {
error: error.message
}
});
} finally {
}
});

View File

@@ -0,0 +1,231 @@
import prisma from "~/server/utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get template ID and version ID from route parameters
const templateId = getRouterParam(event, "id");
const versionId = getRouterParam(event, "versionId");
if (!templateId || !versionId) {
throw createError({
statusCode: 400,
statusMessage: "Template ID and Version ID are required",
});
}
console.log(`Restoring version ${versionId} for template ${templateId}`);
// Get current user (assuming auth middleware provides this)
const user = event.context.user;
if (!user || !user.userID) {
throw createError({
statusCode: 401,
statusMessage: "Authentication required",
});
}
// Check if template exists
const template = await prisma.notification_templates.findUnique({
where: { id: templateId }
});
if (!template) {
throw createError({
statusCode: 404,
statusMessage: "Template not found",
});
}
// Check if version exists
const version = await prisma.notification_template_versions.findUnique({
where: { id: versionId }
});
if (!version || version.template_id !== templateId) {
throw createError({
statusCode: 404,
statusMessage: "Version not found",
});
}
// Generate new version number (increment current version)
const currentVersion = template.version || "1.0";
const versionParts = currentVersion.split('.');
const majorVersion = parseInt(versionParts[0]) || 1;
const minorVersion = parseInt(versionParts[1]) || 0;
const newVersion = `${majorVersion}.${minorVersion + 1}`;
// Create version history entry for current template state before restore
await prisma.notification_template_versions.create({
data: {
template_id: templateId,
version: currentVersion,
name: template.name,
description: template.description,
subject: template.subject,
preheader: template.preheader,
email_content: template.email_content,
push_title: template.push_title,
push_body: template.push_body,
push_icon: template.push_icon,
push_url: template.push_url,
sms_content: template.sms_content,
category: template.category,
channels: template.channels,
status: template.status,
tags: template.tags,
is_personal: template.is_personal,
from_name: template.from_name,
reply_to: template.reply_to,
track_opens: template.track_opens,
variables: template.variables,
is_active: template.is_active,
change_description: `Automatic backup before restoring version ${version.version}`,
is_current: false,
created_by: user.userID.toString(),
}
});
// Update the current template with the version data
const updatedTemplate = await prisma.notification_templates.update({
where: { id: templateId },
data: {
name: version.name,
description: version.description,
subject: version.subject,
preheader: version.preheader,
email_content: version.email_content,
push_title: version.push_title,
push_body: version.push_body,
push_icon: version.push_icon,
push_url: version.push_url,
sms_content: version.sms_content,
category: version.category,
channels: version.channels,
status: version.status,
tags: version.tags,
is_personal: version.is_personal,
from_name: version.from_name,
reply_to: version.reply_to,
track_opens: version.track_opens,
variables: version.variables,
is_active: version.is_active,
version: newVersion,
updated_by: user.userID.toString(),
updated_at: new Date(),
}
});
// Create version history entry for the restored version
await prisma.notification_template_versions.create({
data: {
template_id: templateId,
version: newVersion,
name: version.name,
description: version.description,
subject: version.subject,
preheader: version.preheader,
email_content: version.email_content,
push_title: version.push_title,
push_body: version.push_body,
push_icon: version.push_icon,
push_url: version.push_url,
sms_content: version.sms_content,
category: version.category,
channels: version.channels,
status: version.status,
tags: version.tags,
is_personal: version.is_personal,
from_name: version.from_name,
reply_to: version.reply_to,
track_opens: version.track_opens,
variables: version.variables,
is_active: version.is_active,
change_description: `Restored from version ${version.version}`,
is_current: true,
created_by: user.userID.toString(),
}
});
// Mark previous current version as not current
await prisma.notification_template_versions.updateMany({
where: {
template_id: templateId,
is_current: true,
version: { not: newVersion }
},
data: {
is_current: false
}
});
console.log(`Version ${version.version} restored successfully as version ${newVersion}`);
// Return success response
return {
success: true,
data: {
message: `Version ${version.version} has been restored successfully as version ${newVersion}`,
templateId,
restoredVersion: version.version,
newVersion,
template: {
id: updatedTemplate.id,
title: updatedTemplate.name,
version: updatedTemplate.version,
updatedAt: updatedTemplate.updated_at
}
}
};
} catch (error) {
console.error("Version restore 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 === 'P2025') {
throw createError({
statusCode: 404,
statusMessage: "Template or version not found",
data: {
error: "The template or version you're looking for 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 restore version",
data: {
error: error.message
}
});
} finally {
}
});