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,24 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const query = await getQuery(event);
try {
// Get vue code from path in query
const filePath = path.join(process.cwd() + "/server/", query.path + ".js");
const code = fs.readFileSync(filePath, "utf8");
return {
statusCode: 200,
message: "Code successfully loaded",
data: code,
};
} catch (error) {
// console.log(error);
return {
statusCode: 500,
message: "File not found",
};
}
});

View File

@@ -0,0 +1,191 @@
// import esline vue
import { ESLint } from "eslint";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
if (body.code === undefined) {
return {
statusCode: 400,
message: "Bad Request",
};
}
// run linter
const code = body.code;
const validateNitroCode = (code) => {
// Check if this is a server route file
const isServerRoute = code.includes("defineEventHandler");
if (isServerRoute) {
let lineNumber = 1;
// 1. Validate event handler structure
if (!code.includes("export default defineEventHandler")) {
throw {
message:
"Nitro route handlers must use 'export default defineEventHandler'",
line: 1,
column: 0,
};
}
// 2. Check for proper request handling
const hasRequestBody = code.includes("await readBody(event)");
const hasRequestQuery = code.includes("getQuery(event)");
const usesEventWithoutImport =
code.includes("event.") && !hasRequestBody && !hasRequestQuery;
if (usesEventWithoutImport) {
// Find the line where event is improperly used
const lines = code.split("\n");
for (let i = 0; i < lines.length; i++) {
if (
lines[i].includes("event.") &&
!lines[i].includes("readBody") &&
!lines[i].includes("getQuery")
) {
throw {
message:
"Use 'readBody(event)' for POST data or 'getQuery(event)' for query parameters",
line: i + 1,
column: lines[i].indexOf("event."),
};
}
}
}
// 3. Validate response structure
const responseRegex = /return\s+{([^}]+)}/g;
let match;
let lastIndex = 0;
while ((match = responseRegex.exec(code)) !== null) {
lineNumber += (code.slice(lastIndex, match.index).match(/\n/g) || [])
.length;
lastIndex = match.index;
const responseContent = match[1];
// Check for required response properties
if (!responseContent.includes("statusCode")) {
throw {
message: "API responses must include a 'statusCode' property",
line: lineNumber,
column: match.index - code.lastIndexOf("\n", match.index),
};
}
// Validate status code usage
const statusMatch = responseContent.match(/statusCode:\s*(\d+)/);
if (statusMatch) {
const statusCode = parseInt(statusMatch[1]);
if (![200, 201, 400, 401, 403, 404, 500].includes(statusCode)) {
throw {
message: `Invalid status code: ${statusCode}. Use standard HTTP status codes.`,
line: lineNumber,
column: statusMatch.index,
};
}
}
}
// 4. Check error handling
if (code.includes("try") && !code.includes("catch")) {
throw {
message:
"Missing error handling. Add a catch block for try statements.",
line:
code.split("\n").findIndex((line) => line.includes("try")) + 1,
column: 0,
};
}
// 5. Validate async/await usage
const asyncLines = code.match(/async.*=>/g) || [];
const awaitLines = code.match(/await\s+/g) || [];
if (awaitLines.length > 0 && asyncLines.length === 0) {
throw {
message: "Using 'await' requires an async function",
line:
code.split("\n").findIndex((line) => line.includes("await")) + 1,
column: 0,
};
}
// // 6. Check for proper imports
// const requiredImports = new Set();
// if (hasRequestBody) requiredImports.add("readBody");
// if (hasRequestQuery) requiredImports.add("getQuery");
// const importLines = code.match(/import.*from/g) || [];
// requiredImports.forEach((imp) => {
// if (!importLines.some((line) => line.includes(imp))) {
// throw {
// message: `Missing import for '${imp}' utility`,
// line: 1,
// column: 0,
// };
// }
// });
}
};
try {
validateNitroCode(code);
const eslint = new ESLint({
overrideConfig: {
parser: "@babel/eslint-parser",
extends: ["@kiwicom"],
parserOptions: {
requireConfigFile: false,
ecmaVersion: 2020,
sourceType: "module",
},
},
useEslintrc: false,
});
const results = await eslint.lintText(code);
if (results[0].messages.length > 0) {
const messages = results[0].messages[0];
if (messages.fatal === true) {
return {
statusCode: 400,
message: "Bad Linter Test",
data: messages,
};
}
return {
statusCode: 200,
message: "Good Linter test",
data: messages,
};
}
} catch (error) {
console.log(error);
return {
statusCode: 400,
message: "Bad Linter Test",
data: {
message: error.message,
line: error.line || 1,
column: error.column || 0,
},
};
}
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal Server Error",
errror: error,
};
}
});

View File

@@ -0,0 +1,77 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
try {
// get api folder path from server root folder and its files and folders inside it
const apiFolderPath = path.join(process.cwd() + "/server/api");
const apis = fs.readdirSync(apiFolderPath);
const apiList = getFilesAndFolders(apiFolderPath);
const apiUrls = getApiUrls(apiList);
const jsonObject = JSON.parse(JSON.stringify(apiUrls));
return {
statusCode: 200,
message: "API List successfully fetched",
data: jsonObject,
};
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});
function getFilesAndFolders(folderPath) {
const folderFiles = fs.readdirSync(folderPath);
const files = [];
const folders = [];
const apiURL = "/api";
folderFiles.forEach((file) => {
const filePath = path.join(folderPath + "/" + file);
if (file == "devtool") return;
if (fs.lstatSync(filePath).isDirectory()) {
folders.push(getFilesAndFolders(filePath));
} else {
const processPath = path.join(process.cwd() + "/server/api");
const apiUrl = filePath
.replace(processPath, apiURL)
.replace(/\\/g, "/")
.replace(".js", "");
const fileName = file.replace(".js", "");
const parentFolder = folderPath
.replace(processPath, "")
.replace(/\\/g, "");
files.push({
name: fileName,
parentName: parentFolder,
url: apiUrl,
});
}
});
return { files, folders };
}
function getApiUrls(folder) {
const apiUrls = [];
folder.files.forEach((file) => {
apiUrls.push({
name: file.name,
parentName: file.parentName,
url: file.url,
});
});
folder.folders.forEach((nestedFolder) => {
apiUrls.push(...getApiUrls(nestedFolder));
});
return apiUrls;
}

View File

@@ -0,0 +1,27 @@
import prettier from "prettier";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
if (body.code === undefined) {
return {
statusCode: 400,
message: "Bad Request",
};
}
const code = prettier.format(body.code, { semi: false, parser: "babel" });
return {
statusCode: 200,
message: "Code successfully formatted",
data: code,
};
} catch (error) {
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,71 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
const codeDefault = `
export default defineEventHandler(async (event) => {
// const query = await getQuery(event); // Get Params from URL
// const body = await readBody(event); // Get Body Data
return {
statusCode: 200,
message: "API Route Created",
};
});`;
// Overwrite vue code from path in body with new code
const filePath = path.join(process.cwd() + "/server/", body.path + ".js");
if (body.type == "update") {
fs.writeFileSync(filePath, body.code, "utf8");
} else if (body.type == "add") {
// if the folder doesn't exist, create it
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
fs.writeFileSync(filePath, codeDefault, "utf8");
} else if (body.type == "edit") {
// if the folder doesn't exist, create it
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
// Copy the file from the default path to the new path
const oldPath = path.join(
process.cwd() + "/server/",
body.oldPath + ".js"
);
// Copy file
fs.copyFileSync(oldPath, filePath);
// Delete old file
fs.unlinkSync(oldPath);
} else if (body.type == "delete") {
// Delete file from path
fs.unlinkSync(filePath);
return {
statusCode: 200,
message: "Code successfully deleted",
};
}
return {
statusCode: 200,
message: "Code successfully saved",
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,90 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const method = getMethod(event);
if (method !== "POST") {
return {
statusCode: 405,
message: "Method not allowed",
};
}
try {
const body = await readBody(event);
const { themeName, themeCSS } = body;
if (!themeName || !themeCSS) {
return {
statusCode: 400,
message: "Theme name and CSS are required",
};
}
// Validate theme name (alphanumeric and hyphens only)
if (!/^[a-zA-Z0-9-_]+$/.test(themeName)) {
return {
statusCode: 400,
message: "Theme name can only contain letters, numbers, hyphens, and underscores",
};
}
// Path to theme.css file
const themeCSSPath = path.join(process.cwd(), 'assets', 'style', 'css', 'base', 'theme.css');
// Check if theme.css exists
if (!fs.existsSync(themeCSSPath)) {
return {
statusCode: 404,
message: "theme.css file not found",
};
}
// Read current theme.css content
let currentContent = fs.readFileSync(themeCSSPath, 'utf8');
// Check if theme already exists
const themePattern = new RegExp(`html\\[data-theme="${themeName}"\\]`, 'g');
if (themePattern.test(currentContent)) {
return {
statusCode: 409,
message: `Theme "${themeName}" already exists`,
};
}
// Format the new theme CSS
const formattedThemeCSS = themeCSS.trim();
// Ensure the CSS starts with the correct selector if not provided
let finalThemeCSS;
if (!formattedThemeCSS.includes(`html[data-theme="${themeName}"]`)) {
finalThemeCSS = `html[data-theme="${themeName}"] {\n${formattedThemeCSS}\n}`;
} else {
finalThemeCSS = formattedThemeCSS;
}
// Add the new theme to the end of the file
const newContent = currentContent + '\n\n' + finalThemeCSS + '\n';
// Write the updated content back to the file
fs.writeFileSync(themeCSSPath, newContent, 'utf8');
return {
statusCode: 200,
message: "Custom theme added successfully",
data: {
themeName,
success: true
},
};
} catch (error) {
console.error("Add custom theme error:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message,
};
}
});

View File

@@ -0,0 +1,22 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
// Get .env file, parse and return
const envFile = path.join(process.cwd(), ".env");
if (!fs.existsSync(envFile)) {
return {
statusCode: 404,
message: "File not found",
};
}
const env = fs.readFileSync(envFile, "utf-8");
return {
statusCode: 200,
message: "Success",
data: env,
};
});

View File

@@ -0,0 +1,44 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
const method = getMethod(event);
try {
if (method === "GET") {
// Get only the loading logo and site name for faster loading
const settings = await prisma.site_settings.findFirst({
select: {
siteLoadingLogo: true,
siteName: true,
},
orderBy: { settingID: "desc" },
});
return {
statusCode: 200,
message: "Success",
data: {
siteLoadingLogo: settings?.siteLoadingLogo || '',
siteName: settings?.siteName || 'corradAF',
},
};
}
return {
statusCode: 405,
message: "Method not allowed",
};
} catch (error) {
console.error("Loading logo API error:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message,
};
} finally {
await prisma.$disconnect();
}
});

View File

@@ -0,0 +1,217 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
const method = getMethod(event);
try {
if (method === "GET") {
// Get site settings
let settings = await prisma.site_settings.findFirst({
orderBy: { settingID: "desc" },
});
// If no settings exist, create default ones
if (!settings) {
settings = await prisma.site_settings.create({
data: {
siteName: "corradAF",
siteDescription: "corradAF Base Project",
themeMode: "biasa",
showSiteNameInHeader: true,
seoRobots: "index, follow",
seoTwitterCard: "summary_large_image",
settingCreatedDate: new Date(),
settingModifiedDate: new Date(),
},
});
}
// Transform data to match new structure
const transformedSettings = {
siteName: settings.siteName || "corradAF",
siteNameFontSize: settings.siteNameFontSize || 18,
siteDescription: settings.siteDescription || "corradAF Base Project",
siteLogo: settings.siteLogo || "",
siteLoadingLogo: settings.siteLoadingLogo || "",
siteFavicon: settings.siteFavicon || "",
siteLoginLogo: settings.siteLoginLogo || "",
showSiteNameInHeader: settings.showSiteNameInHeader !== false,
customCSS: settings.customCSS || "",
selectedTheme: settings.themeMode || "biasa", // Use themeMode as selectedTheme
customThemeFile: settings.customThemeFile || "",
currentFont: settings.currentFont || "",
fontSource: settings.fontSource || "",
// SEO fields
seoTitle: settings.seoTitle || "",
seoDescription: settings.seoDescription || "",
seoKeywords: settings.seoKeywords || "",
seoAuthor: settings.seoAuthor || "",
seoOgImage: settings.seoOgImage || "",
seoTwitterCard: settings.seoTwitterCard || "summary_large_image",
seoCanonicalUrl: settings.seoCanonicalUrl || "",
seoRobots: settings.seoRobots || "index, follow",
seoGoogleAnalytics: settings.seoGoogleAnalytics || "",
seoGoogleTagManager: settings.seoGoogleTagManager || "",
seoFacebookPixel: settings.seoFacebookPixel || ""
};
return {
statusCode: 200,
message: "Success",
data: transformedSettings,
};
}
if (method === "POST") {
let body;
try {
body = await readBody(event);
} catch (bodyError) {
console.error("Error reading request body:", bodyError);
return {
statusCode: 400,
message: "Invalid request body",
error: bodyError.message,
};
}
// Validate required fields
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Request body must be a valid JSON object",
};
}
// Check if settings exist
const existingSettings = await prisma.site_settings.findFirst();
// Prepare data for database (use themeMode instead of selectedTheme)
// Filter out undefined values to avoid database errors
const dbData = {};
// Only add fields that are not undefined
if (body.siteName !== undefined) dbData.siteName = body.siteName;
if (body.siteNameFontSize !== undefined) dbData.siteNameFontSize = body.siteNameFontSize;
if (body.siteDescription !== undefined) dbData.siteDescription = body.siteDescription;
if (body.siteLogo !== undefined) dbData.siteLogo = body.siteLogo;
if (body.siteLoadingLogo !== undefined) dbData.siteLoadingLogo = body.siteLoadingLogo;
if (body.siteFavicon !== undefined) dbData.siteFavicon = body.siteFavicon;
if (body.siteLoginLogo !== undefined) dbData.siteLoginLogo = body.siteLoginLogo;
if (body.showSiteNameInHeader !== undefined) dbData.showSiteNameInHeader = body.showSiteNameInHeader;
if (body.customCSS !== undefined) dbData.customCSS = body.customCSS;
if (body.selectedTheme !== undefined) dbData.themeMode = body.selectedTheme;
if (body.customThemeFile !== undefined) dbData.customThemeFile = body.customThemeFile;
if (body.currentFont !== undefined) dbData.currentFont = body.currentFont;
if (body.fontSource !== undefined) dbData.fontSource = body.fontSource;
if (body.seoTitle !== undefined) dbData.seoTitle = body.seoTitle;
if (body.seoDescription !== undefined) dbData.seoDescription = body.seoDescription;
if (body.seoKeywords !== undefined) dbData.seoKeywords = body.seoKeywords;
if (body.seoAuthor !== undefined) dbData.seoAuthor = body.seoAuthor;
if (body.seoOgImage !== undefined) dbData.seoOgImage = body.seoOgImage;
if (body.seoTwitterCard !== undefined) dbData.seoTwitterCard = body.seoTwitterCard;
if (body.seoCanonicalUrl !== undefined) dbData.seoCanonicalUrl = body.seoCanonicalUrl;
if (body.seoRobots !== undefined) dbData.seoRobots = body.seoRobots;
if (body.seoGoogleAnalytics !== undefined) dbData.seoGoogleAnalytics = body.seoGoogleAnalytics;
if (body.seoGoogleTagManager !== undefined) dbData.seoGoogleTagManager = body.seoGoogleTagManager;
if (body.seoFacebookPixel !== undefined) dbData.seoFacebookPixel = body.seoFacebookPixel;
dbData.settingModifiedDate = new Date();
let settings;
if (existingSettings) {
// Update existing settings
settings = await prisma.site_settings.update({
where: { settingID: existingSettings.settingID },
data: dbData,
});
} else {
// Create new settings
settings = await prisma.site_settings.create({
data: {
...dbData,
settingCreatedDate: new Date(),
},
});
}
// Transform response to match new structure
const transformedSettings = {
siteName: settings.siteName || "corradAF",
siteNameFontSize: settings.siteNameFontSize || 18,
siteDescription: settings.siteDescription || "corradAF Base Project",
siteLogo: settings.siteLogo || "",
siteLoadingLogo: settings.siteLoadingLogo || "",
siteFavicon: settings.siteFavicon || "",
siteLoginLogo: settings.siteLoginLogo || "",
showSiteNameInHeader: settings.showSiteNameInHeader !== false,
customCSS: settings.customCSS || "",
selectedTheme: settings.themeMode || "biasa", // Use themeMode as selectedTheme
customThemeFile: settings.customThemeFile || "",
currentFont: settings.currentFont || "",
fontSource: settings.fontSource || "",
// SEO fields
seoTitle: settings.seoTitle || "",
seoDescription: settings.seoDescription || "",
seoKeywords: settings.seoKeywords || "",
seoAuthor: settings.seoAuthor || "",
seoOgImage: settings.seoOgImage || "",
seoTwitterCard: settings.seoTwitterCard || "summary_large_image",
seoCanonicalUrl: settings.seoCanonicalUrl || "",
seoRobots: settings.seoRobots || "index, follow",
seoGoogleAnalytics: settings.seoGoogleAnalytics || "",
seoGoogleTagManager: settings.seoGoogleTagManager || "",
seoFacebookPixel: settings.seoFacebookPixel || ""
};
return {
statusCode: 200,
message: "Settings updated successfully",
data: transformedSettings,
};
}
return {
statusCode: 405,
message: "Method not allowed",
};
} catch (error) {
console.error("Site settings API error:", error);
// Provide more specific error messages
if (error.code === 'P2002') {
return {
statusCode: 400,
message: "Duplicate entry error",
error: error.message,
};
}
if (error.code === 'P2025') {
return {
statusCode: 404,
message: "Record not found",
error: error.message,
};
}
if (error.code && error.code.startsWith('P')) {
return {
statusCode: 400,
message: "Database error",
error: error.message,
code: error.code,
};
}
return {
statusCode: 500,
message: "Internal server error",
error: error.message,
};
} finally {
await prisma.$disconnect();
}
});

View File

@@ -0,0 +1,134 @@
import fs from "fs";
import path from "path";
import { v4 as uuidv4 } from "uuid";
export default defineEventHandler(async (event) => {
const method = getMethod(event);
if (method !== "POST") {
return {
statusCode: 405,
message: "Method not allowed",
};
}
try {
const form = await readMultipartFormData(event);
if (!form || form.length === 0) {
return {
statusCode: 400,
message: "No file uploaded",
};
}
const file = form[0];
const fileType = form.find(field => field.name === 'type')?.data?.toString() || 'logo';
// Validate file type
const allowedTypes = {
logo: ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/svg+xml'],
'loading-logo': ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/svg+xml'],
'login-logo': ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/svg+xml'],
favicon: ['image/x-icon', 'image/vnd.microsoft.icon', 'image/png'],
'og-image': ['image/jpeg', 'image/jpg', 'image/png'],
theme: ['text/css', 'application/octet-stream']
};
if (!allowedTypes[fileType] || !allowedTypes[fileType].includes(file.type)) {
return {
statusCode: 400,
message: `Invalid file type for ${fileType}. Allowed types: ${allowedTypes[fileType].join(', ')}`,
};
}
let uploadDir, fileUrl;
// Determine upload directory based on file type
if (fileType === 'theme') {
// Theme files go to assets/style/css
uploadDir = path.join(process.cwd(), 'assets', 'style', 'css');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
// Generate unique filename for theme
const fileExtension = path.extname(file.filename || '');
const uniqueFilename = `custom-theme-${uuidv4()}${fileExtension}`;
const filePath = path.join(uploadDir, uniqueFilename);
// Save file
fs.writeFileSync(filePath, file.data);
// Return relative path for theme files
fileUrl = `/assets/style/css/${uniqueFilename}`;
} else {
// Logo, loading-logo, favicon, and og-image files go to public/uploads
uploadDir = path.join(process.cwd(), 'public', 'uploads', 'site-settings');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const fileExtension = path.extname(file.filename || '');
let baseFilename;
switch (fileType) {
case 'logo':
baseFilename = 'site-logo';
break;
case 'loading-logo':
baseFilename = 'loading-logo';
break;
case 'login-logo':
baseFilename = 'login-logo';
break;
case 'favicon':
baseFilename = 'favicon';
break;
case 'og-image':
baseFilename = 'og-image';
break;
default:
// This case should ideally not be reached if fileType is validated earlier
// and is one of the image types.
// However, as a fallback, use the fileType itself or a generic name.
// For safety, and to avoid using uuidv4 for these specific types as requested,
// we should ensure this path isn't taken for the specified image types.
// If an unexpected fileType gets here, it might be better to error or use a UUID.
// For now, we'll stick to the primary requirement of fixed names for specified types.
// If we need UUID for other non-logo image types, that logic can be added.
// console.warn(`Unexpected fileType received: ${fileType} for non-theme upload.`);
// For simplicity, if it's an image type not explicitly handled, it will get a name like 'unknown-type.ext'
baseFilename = fileType;
}
const filenameWithExt = `${baseFilename}${fileExtension}`;
const filePath = path.join(uploadDir, filenameWithExt);
// Save file (overwrites if exists)
fs.writeFileSync(filePath, file.data);
// Return file URL
fileUrl = `/uploads/site-settings/${filenameWithExt}`;
}
return {
statusCode: 200,
message: "File uploaded successfully",
data: {
filename: path.basename(fileUrl),
url: fileUrl,
type: fileType,
size: file.data.length,
},
};
} catch (error) {
console.error("Upload error:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message,
};
}
});

View File

@@ -0,0 +1,48 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const query = await getQuery(event);
let code = "";
// console.log(query.path);
try {
// Get vue code from path in query
const filePath = path.join(process.cwd() + "/pages/", query.path + ".vue");
try {
code = fs.readFileSync(filePath, "utf8");
return {
statusCode: 200,
message: "Code successfully loaded",
data: code,
};
} catch (error) {}
// Check if there is path with index.vue
const filePathIndex = path.join(
process.cwd() + "/pages/",
query.path + "/index.vue"
);
code = fs.readFileSync(filePathIndex, "utf8");
// Only get the template part of the code and make sure its from the first template tag to the last template tag
code = code.substring(
code.indexOf("<template>") + 10,
code.lastIndexOf("</template>")
);
return {
statusCode: 200,
message: "Code successfully loaded",
data: code,
mode: "index",
};
} catch (error) {
return {
statusCode: 500,
message: "File not found",
};
}
});

View File

@@ -0,0 +1,42 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const query = await getQuery(event);
let code = "";
// console.log(query.path);
try {
// Get vue code from path in query
const filePath = path.join(process.cwd() + "/pages/", query.path + ".vue");
try {
code = fs.readFileSync(filePath, "utf8");
return {
statusCode: 200,
message: "Code successfully loaded",
data: code,
};
} catch (error) {}
// Check if there is path with index.vue
const filePathIndex = path.join(
process.cwd() + "/pages/",
query.path + "/index.vue"
);
code = fs.readFileSync(filePathIndex, "utf8");
return {
statusCode: 200,
message: "Code successfully loaded",
data: code,
mode: "index",
};
} catch (error) {
return {
statusCode: 500,
message: "File not found",
};
}
});

View File

@@ -0,0 +1,450 @@
import { ESLint } from "eslint";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
if (body.code === undefined) {
return {
statusCode: 400,
message: "Bad Request",
};
}
const code = body.code;
// Extract script and template content once
const scriptContent =
code.match(/<script\b[^>]*>([\s\S]*?)<\/script>/)?.[1] || "";
const templateContent = code.match(/<template>([\s\S]*)<\/template>/)?.[1];
// Validate FormKit inputs
const validateFormKit = (content) => {
// List of valid FormKit input types
const validFormKitTypes = [
"text",
"email",
"url",
"tel",
"password",
"number",
"date",
"datetime-local",
"time",
"month",
"week",
"search",
"color",
"file",
"range",
"checkbox",
"radio",
"select",
"textarea",
"submit",
"button",
"mask",
"form",
];
// Find all FormKit components
const formKitRegex = /<FormKit[^>]*>/g;
let formKitMatch;
// Start counting from template tag
let lineNumber = content
.slice(0, content.indexOf("<template"))
.split("\n").length;
let lastIndex = 0;
while ((formKitMatch = formKitRegex.exec(content)) !== null) {
// Calculate correct line number including the lines before template
lineNumber += (
content.slice(lastIndex, formKitMatch.index).match(/\n/g) || []
).length;
lastIndex = formKitMatch.index;
const formKitTag = formKitMatch[0];
// Extract type attribute
const typeMatch = formKitTag.match(/type=["']([^"']+)["']/);
if (!typeMatch) {
throw {
message: "FormKit component missing required 'type' attribute",
line: lineNumber,
column:
formKitMatch.index -
content.lastIndexOf("\n", formKitMatch.index),
};
}
const inputType = typeMatch[1];
if (!validFormKitTypes.includes(inputType)) {
throw {
message: `Invalid FormKit type: "${inputType}". Please use a valid input type.`,
line: lineNumber,
column:
formKitMatch.index -
content.lastIndexOf("\n", formKitMatch.index),
};
}
// Check for options in select, radio, and checkbox types
if (["select", "radio", "checkbox"].includes(inputType)) {
// Look for :options or v-model
const hasOptions =
formKitTag.includes(":options=") || formKitTag.includes("v-model=");
const hasSlotContent =
content
.slice(
formKitMatch.index,
content.indexOf(">", formKitMatch.index)
)
.includes(">") &&
content
.slice(
formKitMatch.index,
content.indexOf("</FormKit>", formKitMatch.index)
)
.includes("<option");
if (!hasOptions && !hasSlotContent) {
throw {
message: `FormKit ${inputType} requires options. Add :options prop or option slots.`,
line: lineNumber,
column:
formKitMatch.index -
content.lastIndexOf("\n", formKitMatch.index),
};
}
}
}
};
// Add new function to validate mustache syntax
const validateMustacheSyntax = (content) => {
const stack = [];
let lineNumber = 1;
let lastIndex = 0;
for (let i = 0; i < content.length; i++) {
if (content[i] === "\n") {
lineNumber++;
lastIndex = i + 1;
}
if (content[i] === "{" && content[i + 1] === "{") {
stack.push({
position: i,
line: lineNumber,
column: i - lastIndex,
});
i++; // Skip next '{'
} else if (content[i] === "}" && content[i + 1] === "}") {
if (stack.length === 0) {
throw {
message:
"Unexpected closing mustache brackets '}}' without matching opening brackets",
line: lineNumber,
column: i - lastIndex,
};
}
stack.pop();
i++; // Skip next '}'
}
}
if (stack.length > 0) {
const unclosed = stack[0];
throw {
message:
"Unclosed mustache brackets '{{'. Missing closing brackets '}}",
line: unclosed.line,
column: unclosed.column,
};
}
};
// Check template content and FormKit validation
if (templateContent) {
try {
validateMustacheSyntax(templateContent);
validateFormKit(templateContent);
} catch (error) {
return {
statusCode: 400,
message: "Template Syntax Error",
data: {
message: error.message,
line: error.line,
column: error.column,
},
};
}
// Check for undefined variables
const definedVariables = new Set();
// Add common Vue variables
const commonVueVars = [
"$route",
"$router",
"$refs",
"$emit",
"$slots",
"$attrs",
];
commonVueVars.forEach((v) => definedVariables.add(v));
// Extract refs and other variables from script
const refRegex = /(?:const|let|var)\s+(\w+)\s*=/g;
let varMatch;
while ((varMatch = refRegex.exec(scriptContent)) !== null) {
definedVariables.add(varMatch[1]);
}
// Extract defineProps if any
const propsMatch = scriptContent.match(/defineProps\(\s*{([^}]+)}\s*\)/);
if (propsMatch) {
const propsContent = propsMatch[1];
const propNames = propsContent.match(/(\w+)\s*:/g);
propNames?.forEach((prop) => {
definedVariables.add(prop.replace(":", "").trim());
});
}
// Check template for undefined variables
const mustacheRegex = /{{([^}]+)}}/g;
let lineNumber = 1;
let lastIndex = 0;
let mustacheMatch;
while ((mustacheMatch = mustacheRegex.exec(templateContent)) !== null) {
// Calculate line number
lineNumber += (
templateContent.slice(lastIndex, mustacheMatch.index).match(/\n/g) ||
[]
).length;
lastIndex = mustacheMatch.index;
const expression = mustacheMatch[1].trim();
// Split expression and check each variable
const variables = expression.split(/[\s.()[\]]+/);
for (const variable of variables) {
// Skip numbers, operators, and empty strings
if (
!variable ||
variable.match(/^[\d+\-*/&|!%<>=?:]+$/) ||
variable === "true" ||
variable === "false"
) {
continue;
}
if (!definedVariables.has(variable)) {
return {
statusCode: 400,
message: "Template Reference Error",
data: {
message: `Variable "${variable}" is not defined`,
line: lineNumber,
column:
mustacheMatch.index -
templateContent.lastIndexOf("\n", mustacheMatch.index),
},
};
}
}
}
}
// Validate template structure
const validateTemplateStructure = (code) => {
// Add new validation for script tags inside template
const templateContent1 = code.match(
/<template>([\s\S]*)<\/template>/
)?.[1];
if (templateContent1) {
const scriptInTemplate = templateContent1.match(/<script\b[^>]*>/i);
if (scriptInTemplate) {
const lineNumber = templateContent1
.slice(0, scriptInTemplate.index)
.split("\n").length;
const column =
scriptInTemplate.index -
templateContent1.lastIndexOf("\n", scriptInTemplate.index);
throw {
message: "Script tags are not allowed inside template section",
line: lineNumber,
column: column,
};
}
}
// Check for root level template and script tags
const rootTemplateCount = (
code.match(/^[\s\S]*<template>[\s\S]*<\/template>/g) || []
).length;
const rootScriptCount = (
code.match(/^[\s\S]*<script>[\s\S]*<\/script>/g) || []
).length;
if (rootTemplateCount > 1 || rootScriptCount > 1) {
throw new Error(
"Vue components must have only one root <template> and one <script> tag"
);
}
// Extract template content for further validation
const templateContent2 = code.match(
/<template>([\s\S]*)<\/template>/
)?.[1];
if (templateContent2) {
const tagStack = [];
const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9:-]*)\s*([^>]*?)(\/?)>/g;
let match;
let lineNumber = 1;
let lastIndex = 0;
while ((match = tagRegex.exec(templateContent2)) !== null) {
const [fullTag, tagName, attributes, selfClosing] = match;
// Calculate line number
lineNumber += (
templateContent2.slice(lastIndex, match.index).match(/\n/g) || []
).length;
lastIndex = match.index;
// Skip comments
if (templateContent2.slice(match.index).startsWith("<!--")) {
const commentEnd = templateContent2.indexOf("-->", match.index);
if (commentEnd !== -1) {
tagRegex.lastIndex = commentEnd + 3;
continue;
}
}
if (!fullTag.endsWith(">")) {
throw {
message: `Malformed tag found: ${fullTag}`,
line: lineNumber,
column:
match.index - templateContent2.lastIndexOf("\n", match.index),
};
}
if (selfClosing || fullTag.endsWith("/>")) continue;
if (!fullTag.startsWith("</")) {
tagStack.push({
name: tagName,
line: lineNumber,
column:
match.index - templateContent2.lastIndexOf("\n", match.index),
});
} else {
if (tagStack.length === 0) {
throw {
message: `Unexpected closing tag </${tagName}> found without matching opening tag`,
line: lineNumber,
column:
match.index - templateContent2.lastIndexOf("\n", match.index),
};
}
const lastTag = tagStack[tagStack.length - 1];
if (lastTag.name !== tagName) {
throw {
message: `Mismatched tags: expected closing tag for "${lastTag.name}" but found "${tagName}"`,
line: lineNumber,
column:
match.index - templateContent2.lastIndexOf("\n", match.index),
};
}
tagStack.pop();
}
}
if (tagStack.length > 0) {
const unclosedTag = tagStack[tagStack.length - 1];
throw {
message: `Unclosed tag: ${unclosedTag.name}`,
line: unclosedTag.line,
column: unclosedTag.column,
};
}
}
return true;
};
try {
validateTemplateStructure(code);
} catch (structureError) {
return {
statusCode: 400,
message: "Template Structure Error",
data: {
message: structureError.message,
line: structureError.line || 1,
column: structureError.column || 0,
},
};
}
// ESLint configuration
const eslint = new ESLint({
overrideConfig: {
extends: ["plugin:vue/vue3-recommended"],
parserOptions: {
parser: "espree",
ecmaVersion: 2022,
sourceType: "module",
},
},
useEslintrc: false,
});
const results = await eslint.lintText(code);
if (results[0].messages.length > 0) {
const message = results[0].messages[0];
if (message.fatal === true) {
return {
statusCode: 400,
message: "Bad Linter Test",
data: {
message: message.message,
line: message.line,
column: message.column,
},
};
}
return {
statusCode: 200,
message: "Good Linter test",
data: {
message: message.message,
line: message.line,
column: message.column,
},
};
}
return {
statusCode: 200,
message: "Code validation passed",
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal Server Error",
error: error.message,
};
}
});

View File

@@ -0,0 +1,27 @@
import prettier from "prettier";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
if (body.code === undefined) {
return {
statusCode: 400,
message: "Bad Request",
};
}
const code = prettier.format(body.code, { semi: false, parser: "vue" });
return {
statusCode: 200,
message: "Code successfully formatted",
data: code,
};
} catch (error) {
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,22 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Overwrite vue code from path in body with new code
const filePath = path.join(process.cwd() + "/pages/", body.path + ".vue");
fs.writeFileSync(filePath, body.code, "utf8");
return {
statusCode: 200,
message: "Code successfully saved",
};
} catch (error) {
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,29 @@
import templates from "@@/templates/index.js";
export default defineEventHandler(async (event) => {
try {
const query = await getQuery(event);
const id = query.id;
if (!templates || templates?.data.length == 0)
return {
statusCode: 404,
message: "Template data not found",
};
// Search template by id
const template = templates.data.find((item) => item.id == id);
return {
statusCode: 200,
message: "Template data successfully fetched",
data: template,
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal server error",
};
}
});

View File

@@ -0,0 +1,57 @@
import fs from "fs";
import path from "path";
import templates from "@@/templates/index.js";
export default defineEventHandler(async (event) => {
try {
const query = await getQuery(event);
const pagePath = query.path;
const templateId = query.templateId;
// Get pageName path and check if it exists
const filePath = path.join(process.cwd() + "/pages/", pagePath + ".vue");
console.log(filePath);
if (!fs.existsSync(filePath)) {
return {
statusCode: 500,
message: "File path not found",
};
}
// Get template id from templates
const template = templates.data.find(
(template) => template.id === templateId
);
// Get template path and check if it exists
const templatePath = path.join(
process.cwd() + "/templates/",
template.filename + ".vue"
);
if (!fs.existsSync(templatePath)) {
return {
statusCode: 500,
message: "Template not found",
};
}
// Get template code
const templateCode = fs.readFileSync(templatePath, "utf8");
// Write template code to pageName path
fs.writeFileSync(filePath, templateCode, "utf8");
return {
statusCode: 200,
message: "Template successfully imported",
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal server error",
};
}
});

View File

@@ -0,0 +1,23 @@
import templates from "@@/templates/index.js";
export default defineEventHandler(async (event) => {
try {
if (!templates || templates?.data.length == 0)
return {
statusCode: 404,
message: "Template data not found",
};
return {
statusCode: 200,
message: "List template data successfully fetched",
data: templates.data,
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal server error",
};
}
});

View File

@@ -0,0 +1,23 @@
import templates from "@@/templates/index.js";
export default defineEventHandler(async (event) => {
try {
if (!templates || templates?.tags.length == 0)
return {
statusCode: 404,
message: "Template tags not found",
};
return {
statusCode: 200,
message: "List template tags successfully fetched",
data: templates.tags,
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal server error",
};
}
});

View File

View File

@@ -0,0 +1,51 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Check if last character is not slash
if (body.formData.path.slice(-1) != "/") {
body.formData.path = body.formData.path + "/";
}
// Check if the path already exists
if (fs.existsSync(path.join(process.cwd(), "pages", body.formData.path))) {
return {
statusCode: 500,
message: "Path already exists. Please choose another path.",
};
}
// Create new file path with index.vue
const newFilePath = path.join(
process.cwd(),
"pages",
body.formData.path,
"index.vue"
);
// Create the folder if doesn't exist
fs.mkdirSync(path.dirname(newFilePath), { recursive: true });
// Create template content
const templateContent = buildNuxtTemplate({
title: body.formData.title || body.formData.name,
name: body.formData.name,
});
// Write file with template
fs.writeFileSync(newFilePath, templateContent);
return {
statusCode: 200,
message: "Menu successfully added!",
};
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,61 @@
import fs from "fs";
import path from "path";
import navigationData from "~/navigation";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Get file path
const filePath = path.join(process.cwd() + "/pages/", body.filePath);
// Delete path
fs.rmSync(filePath, { recursive: true, force: true });
// Remove menu from navigation
removeMenuFromNavigation(body.filePath);
return {
statusCode: 200,
message: "Menu successfully deleted and removed from navigation!",
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
message: error.message,
};
}
});
function removeMenuFromNavigation(menuPath) {
const removeMenuItem = (items) => {
for (let i = 0; i < items.length; i++) {
if (items[i].path === menuPath) {
items.splice(i, 1);
return true;
}
if (items[i].child && items[i].child.length > 0) {
if (removeMenuItem(items[i].child)) {
return true;
}
}
}
return false;
};
navigationData.forEach((section) => {
if (section.child) {
removeMenuItem(section.child);
}
});
// Save updated navigation data
const navigationFilePath = path.join(process.cwd(), "navigation", "index.js");
const navigationContent = `export default ${JSON.stringify(
navigationData,
null,
2
)};`;
fs.writeFileSync(navigationFilePath, navigationContent, "utf8");
}

View File

@@ -0,0 +1,65 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
// Normalize paths
const oldPath = body.filePath.endsWith("/")
? body.filePath
: body.filePath + "/";
const newPath = body.formData.path.endsWith("/")
? body.formData.path
: body.formData.path + "/";
// Get file paths
const oldFilePath = path.join(process.cwd(), "pages", oldPath, "index.vue");
const newFilePath = path.join(process.cwd(), "pages", newPath, "index.vue");
try {
// Create template content
const templateContent = buildNuxtTemplate({
title: body.formData.title || body.formData.name,
name: body.formData.name,
});
if (oldPath !== newPath) {
// Create the new folder if it doesn't exist
fs.mkdirSync(path.dirname(newFilePath), { recursive: true });
// Write the new file
fs.writeFileSync(newFilePath, templateContent);
// Delete the old file
fs.unlinkSync(oldFilePath);
// Remove empty directories
let dirToCheck = path.dirname(oldFilePath);
while (dirToCheck !== path.join(process.cwd(), "pages")) {
if (fs.readdirSync(dirToCheck).length === 0) {
fs.rmdirSync(dirToCheck);
dirToCheck = path.dirname(dirToCheck);
} else {
break;
}
}
} else {
// Update existing file
fs.writeFileSync(oldFilePath, templateContent);
}
return {
statusCode: 200,
message:
oldPath !== newPath
? "Menu successfully moved and updated"
: "Menu successfully updated",
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,5 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
// try {
});

View File

@@ -0,0 +1,25 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// get menu path
const menuPath = path.join(process.cwd() + "/navigation/", "index.js");
fs.writeFileSync(
menuPath,
`export default ${JSON.stringify(body.menuData, null, 2)}`
);
return {
statusCode: 200,
message: "Menu successfully saved",
};
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,33 @@
export default defineEventHandler(async (event) => {
try {
const roles = await prisma.role.findMany({
select: {
roleID: true,
roleName: true,
},
where: {
roleStatus: {
not: "DELETED",
},
},
});
if (roles) {
return {
statusCode: 200,
message: "Roles successfully fetched",
data: roles,
};
} else {
return {
statusCode: 404,
message: "No Roles found",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,33 @@
export default defineEventHandler(async (event) => {
try {
const users = await prisma.user.findMany({
select: {
userID: true,
userUsername: true,
},
where: {
userStatus: {
not: "DELETED",
},
},
});
if (users) {
return {
statusCode: 200,
message: "Users successfully fetched",
data: users,
};
} else {
return {
statusCode: 404,
message: "No Users found",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,36 @@
export default defineEventHandler(async (event) => {
try {
const { tableName } = getQuery(event);
if (!tableName) {
return {
statusCode: 400,
message: "Table name is required",
};
}
// const JSONSchemaTable = getPrismaSchemaTable(tableName);
// console.log(JSONSchemaTable);
const getData = await prisma.$queryRawUnsafe(`SELECT * FROM ${tableName}`);
if (getData.length === 0) {
return {
statusCode: 404,
message: "Data not found",
};
}
return {
statusCode: 200,
message: "Data successfully fetched",
data: getData,
};
} catch (error) {
console.log(error.message);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,37 @@
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
try {
const { type } = getQuery(event);
if (!type) {
return {
statusCode: 400,
message: "Type is required",
};
}
if (type !== "table" && type !== "field") {
return {
statusCode: 400,
message: "Invalid type",
};
}
let schema = null;
if (type == "table") schema = getPrismaSchemaTable();
return {
statusCode: 200,
message: "Schema successfully fetched",
data: schema,
};
} catch (error) {
console.log(error.message);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,34 @@
import { exec } from "node:child_process";
export default defineEventHandler(async (event) => {
try {
let error = false;
// Run command yarn prisma studio
exec("npx prisma studio", (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
error = true;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
if (error)
return {
statusCode: 500,
message: "Internal Server Error",
};
return {
statusCode: 200,
message: "Prisma Studio successfully launched",
};
} catch (error) {
console.log(error.message);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});

View File

@@ -0,0 +1,106 @@
{
"columnTypes": [
{
"group": "Numbers",
"options": [
"TINYINT",
"SMALLINT",
"MEDIUMINT",
"INT",
"BIGINT",
"DECIMAL",
"FLOAT",
"DOUBLE"
]
},
{
"group": "Date and Time",
"options": ["DATE", "TIME", "DATETIME", "TIMESTAMP", "YEAR"]
},
{
"group": "Strings",
"options": [
"CHAR",
"VARCHAR",
"TINYTEXT",
"TEXT",
"MEDIUMTEXT",
"LONGTEXT",
"JSON"
]
},
{
"group": "Lists",
"options": ["ENUM", "SET"]
},
{
"group": "Binary",
"options": [
"BIT",
"BINARY",
"VARBINARY",
"TINYBLOB",
"BLOB",
"MEDIUMBLOB",
"LONGBLOB"
]
},
{
"group": "Geometry",
"options": [
"GEOMETRY",
"POINT",
"LINESTRING",
"POLYGON",
"MULTIPOINT",
"MULTILINESTRING",
"MULTIPOLYGON",
"GEOMETRYCOLLECTION"
]
}
],
"dataTypes": [
"",
"INT",
"TINYINT",
"SMALLINT",
"MEDIUMINT",
"BIGINT",
"DECIMAL",
"NUMERIC",
"FLOAT",
"DOUBLE",
"CHAR",
"VARCHAR",
"TEXT",
"ENUM",
"SET",
"BINARY",
"VARBINARY",
"BLOB",
"DATE",
"TIME",
"DATETIME",
"TIMESTAMP",
"YEAR",
"BOOL",
"BOOLEAN",
"JSON",
"JSONB",
"XML",
"UUID",
"GEOMETRY",
"POINT",
"LINESTRING",
"POLYGON"
],
"tableField": [
"name",
"type",
"length",
"defaultValue",
"nullable",
"primaryKey",
"actions"
]
}

View File

@@ -0,0 +1,81 @@
import fileConfig from "./configuration.json";
export default defineEventHandler(async (event) => {
try {
// read configuration file if it exists and return error if it doesn't
if (!fileConfig) {
return {
statusCode: 404,
message: "Configuration file not found",
};
}
// Get all tables with primary key
const tables = await getAllTableWithPK();
if (!tables) {
return {
statusCode: 500,
message: "Please check your database connection",
};
}
// Remove columnTypes [{"group": "Foreign Keys", "options": [{"label": "TABLE_NAME (COLUMN_NAME)", "value": "TABLE_NAME"}]}] from fileconfig before appending
fileConfig.columnTypes = fileConfig.columnTypes.filter(
(columnType) => columnType.group !== "Foreign Keys"
);
// Append columnTypes from fileconfig with tables
fileConfig.columnTypes.push({
...tables,
});
return {
statusCode: 200,
message: "Configuration file successfully loaded",
data: fileConfig,
};
} catch (error) {
console.log(error.message);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});
async function getAllTableWithPK() {
try {
const tables = await prisma.$queryRaw` SELECT
table_name,
column_name
FROM
information_schema.columns
WHERE table_schema = DATABASE()
AND column_key = 'PRI'`;
if (!tables) return false;
// Reformat to {group: "table_name", options: [{label: "TABLE_NAME (COLUMN_NAME)", value: "TABLE_NAME"}]}
const remapTables = tables.reduce((acc, table) => {
const group = "Foreign Keys";
const option = {
label: `${table.TABLE_NAME} (${table.COLUMN_NAME})`,
value: `[[${table.TABLE_NAME}]]`,
};
const existingGroup = acc.find((item) => item.group === group);
if (existingGroup) {
existingGroup.options.push(option);
} else {
acc.push({ group, options: [option] });
}
return acc;
}, []);
return remapTables[0];
} catch (error) {
console.log(error.message);
return false;
}
}

View File

@@ -0,0 +1,171 @@
import { spawn } from "node:child_process";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import os from "os";
export default defineEventHandler(async (event) => {
try {
const { tableName, tableSchema, autoIncrementColumn } =
await readBody(event);
if (!tableName || !tableSchema) {
return {
statusCode: 400,
message: "Bad Request",
};
}
// Create Table
const isTableCreated = await createTable(
tableName,
tableSchema,
autoIncrementColumn
);
if (isTableCreated.statusCode !== 200)
return {
statusCode: 500,
message: isTableCreated.message,
};
// Run Prisma Command
const isPrismaCommandRun = await runPrismaCommand();
if (!isPrismaCommandRun)
return {
statusCode: 500,
message: "Prisma Command Failed",
};
return {
statusCode: 200,
message: "Table Created",
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});
async function createTable(tableName, tableSchema) {
try {
let rawSchema = ``;
for (let i = 0; i < tableSchema.length; i++) {
const column = tableSchema[i];
// Sanitize rawSchema
if (column.type.includes("[[") && column.type.includes("]]")) {
const FKTableName = column.type.replace("[[", "").replace("]]", "");
const primaryKey = await prisma.$queryRawUnsafe(
"SHOW COLUMNS from " + FKTableName + " where `Key` = 'PRI'"
);
rawSchema += `${column.name} INT NOT NULL, FOREIGN KEY (${column.name}) REFERENCES ${FKTableName}(${primaryKey[0].Field})`;
} else {
rawSchema += `${column.name}
${column.type}${column.length ? "(" + column.length + ")" : ""}
${column.defaultValue ? " DEFAULT " + column.defaultValue : ""}
${column.nullable ? " NULL" : " NOT NULL "}
${column.primaryKey ? " PRIMARY KEY AUTO_INCREMENT" : ""}`;
}
if (i < tableSchema.length - 1) rawSchema += ", ";
}
const sqlStatement = `CREATE TABLE ${tableName} (${rawSchema})`;
console.log(sqlStatement);
const createTable = await prisma.$queryRawUnsafe(sqlStatement);
if (!createTable)
return {
statusCode: 500,
message: "Table Creation Failed",
};
return {
statusCode: 200,
message: "Table Created",
};
} catch (error) {
console.log(error.message);
// Get Message
if (error.message.includes("already exists")) {
return {
statusCode: 500,
message: `Table '${tableName}' already exists!`,
};
}
if (error.message.includes("1064")) {
return {
statusCode: 500,
message: "Please ensure the SQL syntax is correct!",
};
}
return {
statusCode: 500,
message: "Table Creation Failed",
};
}
}
async function runPrismaCommand() {
try {
console.log("---------- Run Prisma Command ----------");
const __dirname = dirname(fileURLToPath(import.meta.url));
const directory = resolve(__dirname, "../..");
// Command to execute
const command = "npx prisma db pull && npx prisma generate";
// Determine the appropriate shell command based on the platform
let shellCommand;
let spawnOptions;
switch (os.platform()) {
case "win32":
shellCommand = `Start-Process cmd -ArgumentList '/c cd "${directory}" && ${command}' -Verb RunAs`;
spawnOptions = {
shell: "powershell.exe",
args: ["-Command", shellCommand],
};
break;
case "darwin":
case "linux":
shellCommand = `cd "${directory}" && ${command}`;
spawnOptions = {
shell: "sh",
args: ["-c", shellCommand],
};
break;
default:
console.error("Unsupported platform:", os.platform());
return false;
}
// Spawn child process using the appropriate shell command
const childProcess = spawn(spawnOptions.shell, spawnOptions.args, {
stdio: "inherit",
});
// Listen for child process events
return new Promise((resolve, reject) => {
childProcess.on("close", (code) => {
if (code === 0) {
console.log("Prisma commands executed successfully");
resolve(true);
} else {
console.error(`Child process exited with code ${code}`);
reject(new Error(`Child process exited with code ${code}`));
}
});
});
} catch (error) {
console.error("Error running Prisma commands:", error);
return false;
}
}

View File

@@ -0,0 +1,90 @@
import { spawn } from "node:child_process";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import os from "os";
export default defineEventHandler(async (event) => {
const tableName = event.context.params.table;
try {
// Drop the table
await prisma.$executeRawUnsafe(`DROP TABLE IF EXISTS ${tableName}`);
// Run Prisma Command to update the schema
const isPrismaCommandRun = await runPrismaCommand();
if (!isPrismaCommandRun) {
return {
statusCode: 500,
message: "Prisma Command Failed after table deletion",
};
}
return {
statusCode: 200,
message: `Table '${tableName}' has been successfully deleted.`,
};
} catch (error) {
console.error("Error deleting table:", error);
return {
statusCode: 500,
message: `Failed to delete table '${tableName}'. Error: ${error.message}`,
};
}
});
async function runPrismaCommand() {
try {
console.log("---------- Run Prisma Command ----------");
const __dirname = dirname(fileURLToPath(import.meta.url));
const directory = resolve(__dirname, "../..");
// Command to execute
const command = "npx prisma db pull && npx prisma generate";
// Determine the appropriate shell command based on the platform
let shellCommand;
let spawnOptions;
switch (os.platform()) {
case "win32":
shellCommand = `Start-Process cmd -ArgumentList '/c cd "${directory}" && ${command}' -Verb RunAs`;
spawnOptions = {
shell: "powershell.exe",
args: ["-Command", shellCommand],
};
break;
case "darwin":
case "linux":
shellCommand = `cd "${directory}" && ${command}`;
spawnOptions = {
shell: "sh",
args: ["-c", shellCommand],
};
break;
default:
console.error("Unsupported platform:", os.platform());
return false;
}
// Spawn child process using the appropriate shell command
const childProcess = spawn(spawnOptions.shell, spawnOptions.args, {
stdio: "inherit",
});
// Listen for child process events
return new Promise((resolve, reject) => {
childProcess.on("close", (code) => {
if (code === 0) {
console.log("Prisma commands executed successfully");
resolve(true);
} else {
console.error(`Child process exited with code ${code}`);
reject(new Error(`Child process exited with code ${code}`));
}
});
});
} catch (error) {
console.error("Error running Prisma commands:", error);
return false;
}
}

View File

@@ -0,0 +1,113 @@
export default defineEventHandler(async (event) => {
try {
const { tableName } = getQuery(event);
if (!tableName) {
return {
statusCode: 400,
message: "Table name is required",
};
}
const result = await prisma.$queryRaw`SELECT DATABASE() AS db_name`;
// console.log(result[0].db_name);
if (result.length === 0) {
return {
statusCode: 500,
message: "Please check your database connection",
};
}
let sqlRaw = ` SELECT
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH,
c.COLUMN_DEFAULT,
c.IS_NULLABLE,
c.COLUMN_KEY,
kcu.REFERENCED_TABLE_NAME,
kcu.REFERENCED_COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON
c.TABLE_SCHEMA = kcu.TABLE_SCHEMA AND
c.TABLE_NAME = kcu.TABLE_NAME AND
c.COLUMN_NAME = kcu.COLUMN_NAME
WHERE
c.TABLE_SCHEMA = '${result[0].db_name}' AND
c.TABLE_NAME = '${tableName}';`;
// console.log(sqlRaw);
const getTableDetails = await prisma.$queryRawUnsafe(sqlRaw);
// console.log(getTableDetails);
/*
[{
"actions": "",
"defaultValue": "",
"length": "",
"name": "PID",
"nullable": "",
"primaryKey": true,
"type": "INT"
},
{
"actions": "",
"defaultValue": "",
"length": "",
"name": "Pproduct",
"nullable": true,
"primaryKey": "",
"type": "VARCHAR"
},
{
"actions": "",
"defaultValue": "",
"length": "",
"name": "userID",
"nullable": "",
"primaryKey": "",
"type": "[[user]]"
}]
*/
let tableDetailsData = [];
// Loop through the result and convert bigInt to number
for (let i = 0; i < getTableDetails.length; i++) {
const table = getTableDetails[i];
tableDetailsData.push({
name: table.COLUMN_NAME,
type: table.REFERENCED_TABLE_NAME
? `[[${table.REFERENCED_TABLE_NAME}]]`
: table.DATA_TYPE.toUpperCase(),
length: bigIntToNumber(table.CHARACTER_MAXIMUM_LENGTH),
defaultValue: table.COLUMN_DEFAULT,
nullable: table.IS_NULLABLE === "YES",
primaryKey: table.COLUMN_KEY === "PRI",
actions: {},
});
}
return {
statusCode: 200,
message: "Success",
data: tableDetailsData,
};
} catch (error) {
console.log(error.message);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});
function bigIntToNumber(bigInt) {
if (bigInt === null) return null;
return Number(bigInt.toString());
}

View File

@@ -0,0 +1,191 @@
import { spawn } from "node:child_process";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import os from "os";
export default defineEventHandler(async (event) => {
try {
const { tableName, tableSchema, autoIncrementColumn } =
await readBody(event);
if (!tableName || !tableSchema) {
return {
statusCode: 400,
message: "Bad Request",
};
}
// Get existing table structure
const existingColumns = await prisma.$queryRaw`
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ${tableName}
`;
// Compare and modify table structure
for (const column of tableSchema) {
const existingColumn = existingColumns.find(
(c) => c.COLUMN_NAME === column.name
);
if (existingColumn) {
// Modify existing column
await modifyColumn(tableName, column, existingColumn);
} else {
// Add new column
await addColumn(tableName, column);
}
}
// Remove columns that are not in the new schema
for (const existingColumn of existingColumns) {
if (!tableSchema.find((c) => c.name === existingColumn.COLUMN_NAME)) {
await removeColumn(tableName, existingColumn.COLUMN_NAME);
}
}
// Update auto-increment column if necessary
if (autoIncrementColumn) {
await updateAutoIncrement(tableName, autoIncrementColumn);
}
// Run Prisma Command to update the schema
const isPrismaCommandRun = await runPrismaCommand();
if (!isPrismaCommandRun) {
return {
statusCode: 500,
message: "Prisma Command Failed",
};
}
return {
statusCode: 200,
message: "Table modified successfully",
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
message: "Internal Server Error",
};
}
});
async function modifyColumn(tableName, newColumn, existingColumn) {
let alterStatement = `ALTER TABLE ${tableName} MODIFY COLUMN ${newColumn.name} ${newColumn.type}`;
if (newColumn.length) {
alterStatement += `(${newColumn.length})`;
}
alterStatement += newColumn.nullable ? " NULL" : " NOT NULL";
if (newColumn.defaultValue) {
alterStatement += ` DEFAULT ${newColumn.defaultValue}`;
}
await prisma.$executeRawUnsafe(alterStatement);
}
async function addColumn(tableName, column) {
let alterStatement = `ALTER TABLE ${tableName} ADD COLUMN ${column.name} ${column.type}`;
if (column.length) {
alterStatement += `(${column.length})`;
}
alterStatement += column.nullable ? " NULL" : " NOT NULL";
if (column.defaultValue) {
alterStatement += ` DEFAULT ${column.defaultValue}`;
}
await prisma.$executeRawUnsafe(alterStatement);
}
async function removeColumn(tableName, columnName) {
await prisma.$executeRawUnsafe(
`ALTER TABLE ${tableName} DROP COLUMN ${columnName}`
);
}
async function updateAutoIncrement(tableName, autoIncrementColumn) {
await prisma.$executeRawUnsafe(
`ALTER TABLE ${tableName} MODIFY ${autoIncrementColumn} INT AUTO_INCREMENT`
);
}
async function runPrismaCommand(retries = 3) {
try {
console.log("---------- Run Prisma Command ----------");
const __dirname = dirname(fileURLToPath(import.meta.url));
const directory = resolve(__dirname, "../..");
// Command to execute
const command = "npx prisma db pull && npx prisma generate";
// Determine the appropriate shell command based on the platform
let shellCommand;
let spawnOptions;
switch (os.platform()) {
case "win32":
shellCommand = `Start-Process cmd -ArgumentList '/c cd "${directory}" && ${command}' -Verb RunAs`;
spawnOptions = {
shell: "powershell.exe",
args: ["-Command", shellCommand],
};
break;
case "darwin":
case "linux":
shellCommand = `cd "${directory}" && ${command}`;
spawnOptions = {
shell: "sh",
args: ["-c", shellCommand],
};
break;
default:
console.error("Unsupported platform:", os.platform());
return false;
}
// Spawn child process using the appropriate shell command
const childProcess = spawn(spawnOptions.shell, spawnOptions.args, {
stdio: "inherit",
});
// Listen for child process events
return new Promise((resolve, reject) => {
childProcess.on("close", (code) => {
if (code === 0) {
console.log("Prisma commands executed successfully");
resolve(true);
} else {
console.error(`Child process exited with code ${code}`);
reject(new Error(`Child process exited with code ${code}`));
}
});
});
} catch (error) {
console.error("Error running Prisma commands:", error);
return false;
}
}
function spawnCommand(command, args, cwd) {
return new Promise((resolve, reject) => {
const process = spawn(command, args, {
cwd,
stdio: "inherit",
shell: true,
});
process.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command failed with exit code ${code}`));
}
});
});
}

View File

@@ -0,0 +1,87 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Check if the role already exists
const allRole = await prisma.role.findMany({
where: {
roleStatus: "ACTIVE",
},
});
const roleExist = allRole.find((role) => {
return role?.roleName.toLowerCase() === body?.name.toLowerCase();
});
if (roleExist) {
return {
statusCode: 400,
message: "Role already exists",
};
}
// add new role
const role = await prisma.role.create({
data: {
roleName: body.name,
roleDescription: body.description || "",
roleStatus: "ACTIVE",
roleCreatedDate: new Date(),
},
});
if (role) {
// Add User to the role if users are provided
if (body.users && Array.isArray(body.users)) {
const userRoles = await Promise.all(
body.users.map(async (el) => {
const user = await prisma.user.findFirst({
where: {
userUsername: el.value,
},
});
if (user) {
return prisma.userrole.create({
data: {
userRoleUserID: user.userID,
userRoleRoleID: role.roleID,
userRoleCreatedDate: new Date(),
},
});
}
return null;
})
);
const validUserRoles = userRoles.filter(Boolean);
return {
statusCode: 200,
message: "Role successfully added!",
data: {
role,
assignedUsers: validUserRoles.length,
totalUsers: body.users.length,
},
};
}
return {
statusCode: 200,
message: "Role successfully added!",
data: { role },
};
} else {
return {
statusCode: 500,
message: "Something went wrong! Please contact your administrator.",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,28 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Delete user
const user = await prisma.role.updateMany({
where: {
roleID: body.id,
},
data: {
roleStatus: "DELETED",
roleModifiedDate: new Date(),
},
});
if (user) {
return {
statusCode: 200,
message: "User deleted successfully",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,77 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Edit role
const role = await prisma.role.update({
where: {
roleID: body.id,
},
data: {
roleName: body.name,
roleDescription: body.description,
roleModifiedDate: new Date(),
},
});
if (role) {
// Delete all user roles for this role
await prisma.userrole.deleteMany({
where: {
userRoleRoleID: body.id,
},
});
// Add User to the role if users are provided
if (body.users && Array.isArray(body.users)) {
const userRoles = await Promise.all(
body.users.map(async (el) => {
const user = await prisma.user.findFirst({
where: {
userUsername: el.value,
},
});
if (user) {
return prisma.userrole.create({
data: {
userRoleUserID: user.userID,
userRoleRoleID: body.id,
userRoleCreatedDate: new Date(),
},
});
}
return null;
})
);
const validUserRoles = userRoles.filter(Boolean);
return {
statusCode: 200,
message: "Role successfully edited!",
data: {
role,
assignedUsers: validUserRoles.length,
totalUsers: body.users.length,
},
};
}
return {
statusCode: 200,
message: "Role successfully edited!",
data: { role },
};
} else {
return {
statusCode: 500,
message: "Something went wrong! Please contact your administrator.",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,59 @@
export default defineEventHandler(async (event) => {
// Get all users from database
try {
const roles = await prisma.role.findMany({
select: {
roleID: true,
roleName: true,
roleDescription: true,
roleStatus: true,
roleCreatedDate: true,
roleModifiedDate: true,
},
where: {
roleStatus: {
not: "DELETED",
},
roleID: {
not: 1,
},
},
});
if (roles) {
for (let i = 0; i < roles.length; i++) {
let userOfRole = await prisma.userrole.findMany({
select: {
user: {
select: {
userUsername: true,
},
},
},
where: {
userRoleRoleID: roles[i].roleID,
},
});
roles[i].users = userOfRole;
}
return {
statusCode: 200,
message: "Roles successfully fetched",
data: roles,
};
} else {
return {
statusCode: 404,
message: "No Roles found",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,138 @@
import sha256 from "crypto-js/sha256.js";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const password = sha256("abc123").toString();
let secretKey = generateSecretKey();
try {
// Get user from database
const allUser = await prisma.user.findMany({
where: {
userStatus: "ACTIVE",
},
});
// Check if the user already exists
const userExist = allUser.find((user) => {
return user?.userUsername.toLowerCase() === body?.username.toLowerCase();
});
if (userExist)
return {
statusCode: 400,
message: "Username already exists",
};
// Validate secret key
do {
secretKey = generateSecretKey();
} while (
allUser.find((user) => {
return user?.userSecretKey === secretKey;
})
);
// Add New User
const user = await prisma.user.create({
data: {
userSecretKey: secretKey,
userUsername: body.username,
userPassword: password,
userFullName: body?.fullname || "",
userEmail: body?.email || "",
userPhone: body?.phone || "",
userStatus: "ACTIVE",
userCreatedDate: new Date(),
},
});
if (user) {
// Add user roles if provided
if (body.role && Array.isArray(body.role)) {
const userRoles = await Promise.all(
body.role.map(async (role) => {
const existingRole = await prisma.role.findFirst({
where: {
roleID: role.value,
},
});
if (existingRole) {
return prisma.userrole.create({
data: {
userRoleUserID: user.userID,
userRoleRoleID: role.value,
userRoleCreatedDate: new Date(),
},
});
}
return null;
})
);
const validUserRoles = userRoles.filter(Boolean);
return {
statusCode: 200,
message: "User successfully added!",
data: {
user,
assignedRoles: validUserRoles.length,
totalRoles: body.role.length,
},
};
}
return {
statusCode: 200,
message: "User successfully added!",
data: { user },
};
} else {
return {
statusCode: 500,
message: "Something went wrong! Please contact your administrator.",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});
function generateSecretKey() {
// Generate Secret Key number and alphabet. Format : xxxx-xxxx-xxxx-xxxx
let secretKey = "";
let possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
secretKey += possible.charAt(Math.floor(Math.random() * possible.length));
}
if (i < 3) {
secretKey += "-";
}
}
return secretKey;
}
async function checkRoleID(roleID) {
const role = await prisma.role.findFirst({
where: {
roleID: roleID,
},
});
if (!role) {
return false;
} else {
return true;
}
}

View File

@@ -0,0 +1,28 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Delete user
const user = await prisma.user.updateMany({
where: {
userUsername: body.username,
},
data: {
userStatus: "DELETED",
userModifiedDate: new Date(),
},
});
if (user) {
return {
statusCode: 200,
message: "User deleted successfully",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,86 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Update user
const user = await prisma.user.updateMany({
where: {
userUsername: body.username,
},
data: {
userFullName: body?.fullname || "",
userEmail: body?.email || "",
userPhone: body?.phone || "",
userStatus: body.status,
userModifiedDate: new Date(),
},
});
if (user.count > 0) {
const getUserID = await prisma.user.findFirst({
where: {
userUsername: body.username,
},
});
if (getUserID) {
// Delete all user roles
await prisma.userrole.deleteMany({
where: {
userRoleUserID: getUserID.userID,
},
});
// Add new user roles
if (body.role && Array.isArray(body.role)) {
const userRoles = await Promise.all(
body.role.map(async (role) => {
const existingRole = await prisma.role.findFirst({
where: {
roleID: role.value,
},
});
if (existingRole) {
return prisma.userrole.create({
data: {
userRoleUserID: getUserID.userID,
userRoleRoleID: role.value,
userRoleCreatedDate: new Date(),
},
});
}
return null;
})
);
const validUserRoles = userRoles.filter(Boolean);
return {
statusCode: 200,
message: "User updated successfully",
data: {
assignedRoles: validUserRoles.length,
totalRoles: body.role.length,
},
};
}
return {
statusCode: 200,
message: "User updated successfully",
};
}
}
return {
statusCode: 404,
message: "User not found",
};
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});

View File

@@ -0,0 +1,60 @@
export default defineEventHandler(async (event) => {
// Get all users from database except userStatus = DELETED
try {
const users = await prisma.user.findMany({
select: {
userID: true,
userUsername: true,
userFullName: true,
userEmail: true,
userPhone: true,
userStatus: true,
userCreatedDate: true,
userModifiedDate: true,
},
where: {
userStatus: {
not: "DELETED",
},
},
});
if (users) {
// Get all roles for each user
for (let i = 0; i < users.length; i++) {
let roleOfUser = await prisma.userrole.findMany({
select: {
role: {
select: {
roleID: true,
roleName: true,
},
},
},
where: {
userRoleUserID: users[i].userID,
},
});
users[i].roles = roleOfUser;
}
return {
statusCode: 200,
message: "Users successfully fetched",
data: users,
};
} else {
return {
statusCode: 404,
message: "No users found",
};
}
} catch (error) {
return {
statusCode: 500,
message: error.message,
};
}
});