From caab669d51b6247b365b37169cf1891db7d4f320 Mon Sep 17 00:00:00 2001 From: sianida26 Date: Wed, 14 Feb 2024 14:48:40 +0700 Subject: [PATCH] Optimize role --- src/_features/auth/AuthError.ts | 16 -- src/_features/auth/actions/createUser.ts | 104 --------- src/_features/auth/actions/getUser.ts | 31 --- src/_features/auth/actions/guestOnly.ts | 13 -- src/_features/auth/actions/logout.ts | 16 -- src/_features/auth/actions/signIn.ts | 91 -------- src/_features/auth/authUtils.ts | 96 -------- src/_features/auth/contexts/AuthContext.tsx | 0 src/_features/auth/index.ts | 0 .../auth/tools/checkMultiplePermissions.ts | 0 src/_features/auth/tools/checkPermission.ts | 42 ---- src/_features/auth/tools/getCurrentUser.ts | 28 --- src/_features/auth/tools/hashPassword.ts | 12 - src/_features/auth/types/CrudPermissions.d.ts | 0 src/_features/auth/types/UserClaims.d.ts | 7 - .../DashboardTable/DashboardTable.tsx | 0 .../components/DashboardTable/index.ts | 1 - src/_features/dashboard/components/index.ts | 3 - .../dashboard/errors/DashboardError.ts | 45 ---- .../permissions/actions/deletePermission.ts | 26 --- .../permissions/actions/getAllPermissions.ts | 48 ---- .../permissions/actions/getPermissionById.ts | 33 --- .../permissions/actions/upsertPermission.ts | 81 ------- .../permissions/data/getPermissions.ts | 48 ---- .../modals/DeleteModal/DeleteModal.tsx | 0 .../permissions/modals/DeleteModal/index.ts | 1 - .../modals/FormModal/FormModal.tsx | 215 ------------------ .../permissions/modals/FormModal/index.ts | 1 - .../dashboard/permissions/modals/index.ts | 4 - .../PermissionTable/PermissionTable.tsx | 126 ---------- .../tables/PermissionTable/_columns.tsx | 105 --------- .../tables/PermissionTable/index.ts | 1 - .../dashboard/permissions/tables/index.ts | 3 - .../dashboard/roles/actions/deleteRole.ts | 26 --- .../dashboard/utils/createActionButtons.tsx | 0 .../dashboard/utils/withServerAction.ts | 0 .../(auth)/roles/_modals/DeleteModal/index.ts | 1 - .../(auth)/roles/_modals/FormModal/index.ts | 1 - .../dashboard/(auth)/roles/_modals/index.ts | 4 - src/app/dashboard/(auth)/roles/page.tsx | 12 +- src/modules/role/actions/deleteRole.ts | 29 +++ .../role/actions/getAllRoles.ts} | 9 +- .../role}/actions/getRoleById.ts | 11 +- .../role}/actions/upsertRole.ts | 13 +- .../role}/formSchemas/RoleFormData.ts | 0 .../role/modals}/DeleteModal.tsx | 16 +- .../role/modals}/FormModal.tsx | 20 +- .../role/tables}/RolesTable/RolesTable.tsx | 28 +-- .../role/tables}/RolesTable/columns.tsx | 5 +- 49 files changed, 77 insertions(+), 1295 deletions(-) delete mode 100644 src/_features/auth/AuthError.ts delete mode 100644 src/_features/auth/actions/createUser.ts delete mode 100644 src/_features/auth/actions/getUser.ts delete mode 100644 src/_features/auth/actions/guestOnly.ts delete mode 100644 src/_features/auth/actions/logout.ts delete mode 100644 src/_features/auth/actions/signIn.ts delete mode 100644 src/_features/auth/authUtils.ts delete mode 100644 src/_features/auth/contexts/AuthContext.tsx delete mode 100644 src/_features/auth/index.ts delete mode 100644 src/_features/auth/tools/checkMultiplePermissions.ts delete mode 100644 src/_features/auth/tools/checkPermission.ts delete mode 100644 src/_features/auth/tools/getCurrentUser.ts delete mode 100644 src/_features/auth/tools/hashPassword.ts delete mode 100644 src/_features/auth/types/CrudPermissions.d.ts delete mode 100644 src/_features/auth/types/UserClaims.d.ts delete mode 100644 src/_features/dashboard/components/DashboardTable/DashboardTable.tsx delete mode 100644 src/_features/dashboard/components/DashboardTable/index.ts delete mode 100644 src/_features/dashboard/components/index.ts delete mode 100644 src/_features/dashboard/errors/DashboardError.ts delete mode 100644 src/_features/dashboard/permissions/actions/deletePermission.ts delete mode 100644 src/_features/dashboard/permissions/actions/getAllPermissions.ts delete mode 100644 src/_features/dashboard/permissions/actions/getPermissionById.ts delete mode 100644 src/_features/dashboard/permissions/actions/upsertPermission.ts delete mode 100644 src/_features/dashboard/permissions/data/getPermissions.ts delete mode 100644 src/_features/dashboard/permissions/modals/DeleteModal/DeleteModal.tsx delete mode 100644 src/_features/dashboard/permissions/modals/DeleteModal/index.ts delete mode 100644 src/_features/dashboard/permissions/modals/FormModal/FormModal.tsx delete mode 100644 src/_features/dashboard/permissions/modals/FormModal/index.ts delete mode 100644 src/_features/dashboard/permissions/modals/index.ts delete mode 100644 src/_features/dashboard/permissions/tables/PermissionTable/PermissionTable.tsx delete mode 100644 src/_features/dashboard/permissions/tables/PermissionTable/_columns.tsx delete mode 100644 src/_features/dashboard/permissions/tables/PermissionTable/index.ts delete mode 100644 src/_features/dashboard/permissions/tables/index.ts delete mode 100644 src/_features/dashboard/roles/actions/deleteRole.ts delete mode 100644 src/_features/dashboard/utils/createActionButtons.tsx delete mode 100644 src/_features/dashboard/utils/withServerAction.ts delete mode 100644 src/app/dashboard/(auth)/roles/_modals/DeleteModal/index.ts delete mode 100644 src/app/dashboard/(auth)/roles/_modals/FormModal/index.ts delete mode 100644 src/app/dashboard/(auth)/roles/_modals/index.ts create mode 100644 src/modules/role/actions/deleteRole.ts rename src/{_features/dashboard/roles/data/getRoles.ts => modules/role/actions/getAllRoles.ts} (83%) rename src/{_features/dashboard/roles => modules/role}/actions/getRoleById.ts (67%) rename src/{_features/dashboard/roles => modules/role}/actions/upsertRole.ts (85%) rename src/{_features/dashboard/roles => modules/role}/formSchemas/RoleFormData.ts (100%) rename src/{app/dashboard/(auth)/roles/_modals/DeleteModal => modules/role/modals}/DeleteModal.tsx (84%) rename src/{app/dashboard/(auth)/roles/_modals/FormModal => modules/role/modals}/FormModal.tsx (90%) rename src/{app/dashboard/(auth)/roles/_tables => modules/role/tables}/RolesTable/RolesTable.tsx (74%) rename src/{app/dashboard/(auth)/roles/_tables => modules/role/tables}/RolesTable/columns.tsx (92%) diff --git a/src/_features/auth/AuthError.ts b/src/_features/auth/AuthError.ts deleted file mode 100644 index eb9a363..0000000 --- a/src/_features/auth/AuthError.ts +++ /dev/null @@ -1,16 +0,0 @@ -import BaseError from "@/BaseError"; - -export enum AuthErrorCode { - EMAIL_NOT_FOUND = "EMAIL_NOT_FOUND", - EMPTY_USER_HASH = "EMPTY_USER_HASH", - INVALID_CREDENTIALS = "INVALID_CREDENTIALS", - INVALID_JWT_TOKEN = "INVALID_JWT_TOKEN", - JWT_SECRET_EMPTY = "JWT_SECRET_NOT_EMPTY", - USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS", -} - -export default class AuthError extends BaseError { - constructor(errorCode: AuthErrorCode, {statusCode = 500, message, data}: Partial<{statusCode: number, message: string, data: object}> = {}) { - super(message, errorCode, statusCode, data); - } -} diff --git a/src/_features/auth/actions/createUser.ts b/src/_features/auth/actions/createUser.ts deleted file mode 100644 index a190d6f..0000000 --- a/src/_features/auth/actions/createUser.ts +++ /dev/null @@ -1,104 +0,0 @@ -"use server" -import { z } from "zod"; -import prisma from "@/db"; -import AuthError, { AuthErrorCode } from "../AuthError"; -import BaseError, { BaseErrorCodes } from "@/BaseError"; -import { createJwtToken, hashPassword } from "../authUtils"; -import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; - -/** - * Interface for the schema of a new user. - */ -interface CreateUserSchema { - name: string; - email: string; - password: string; -} - -/** - * Validation schema for creating a user. - */ -const createUserSchema = z.object({ - name: z.string(), - email: z.string().email(), - password: z.string().min(6), - passwordConfirmation: z.string().optional(), -}).refine( - (data) => data.password === data.passwordConfirmation, - { - message: "Password confirmation must match the password", - path: ["passwordConfirmation"], - } -); - -/** - * Creates a new user in the system. - * - * @param formData - The form data containing user details. - * @returns An object indicating the result of the operation. - */ -export default async function createUser(formData: FormData){ - //TODO: Add Throttling - //TODO: Add validation check if the user is already logged in - - try { - const parsedData = { - email: formData.get("email")?.toString() ?? '', - name: formData.get("name")?.toString() ?? '', - password: formData.get("password")?.toString() ?? '', - passwordConfirmation: formData.get("passwordConfirmation")?.toString() - }; - const validatedFields = createUserSchema.safeParse(parsedData); - - if (!validatedFields.success) { - return { - success: false, - error: { - message: "", - errors: validatedFields.error.flatten().fieldErrors - } - } - } - - const existingUser = await prisma.user.findUnique({ - where: { email: validatedFields.data.email }, - }); - - if (existingUser){ - return { - success: false, - error: { - message: "", - errors: { - email: ["Email already exists"] - } - } - } - } - - const user = await prisma.user.create({ - data: { - name: validatedFields.data.name, - email: validatedFields.data.email, - passwordHash: await hashPassword(validatedFields.data.password), - }, - }); - - const token = createJwtToken({ id: user.id }); - cookies().set("token", token); - } catch (e: unknown) { - // Handle unexpected errors - console.error(e) - //@ts-ignore - console.log(e.message) - return { - success: false, - error: { - message: "An unexpected error occurred on the server. Please try again or contact the administrator.", - }, - }; - } - - redirect("/dashboard"); -} diff --git a/src/_features/auth/actions/getUser.ts b/src/_features/auth/actions/getUser.ts deleted file mode 100644 index f1851f3..0000000 --- a/src/_features/auth/actions/getUser.ts +++ /dev/null @@ -1,31 +0,0 @@ -"use server" - -import { cookies } from "next/headers" -import "server-only" -import { decodeJwtToken, getUserFromToken } from "../authUtils"; -import prisma from "@/db"; -import AuthError, { AuthErrorCode } from "../AuthError"; -import logout from "./logout"; - -export default async function getUser(){ - try { - const token = cookies().get('token'); - - if (!token) return null; - - const user = await getUserFromToken(token.value); - - if (!user) return null; - - return { - name: user.name ?? "", - email: user.email ?? "", - photoUrl: user.photoProfile?.path ?? null - } - } catch (e: unknown){ - if (e instanceof AuthError && e.errorCode === AuthErrorCode.INVALID_JWT_TOKEN){ - return null; - } - throw e; - } -} diff --git a/src/_features/auth/actions/guestOnly.ts b/src/_features/auth/actions/guestOnly.ts deleted file mode 100644 index 89ab24f..0000000 --- a/src/_features/auth/actions/guestOnly.ts +++ /dev/null @@ -1,13 +0,0 @@ -"use server" - -import { redirect } from "next/navigation"; -import getUser from "./getUser" - -export default async function guestOnly(){ - - const user = await getUser(); - - if (user){ - redirect("dashboard") - } -} diff --git a/src/_features/auth/actions/logout.ts b/src/_features/auth/actions/logout.ts deleted file mode 100644 index f43a041..0000000 --- a/src/_features/auth/actions/logout.ts +++ /dev/null @@ -1,16 +0,0 @@ -"use server" - -import { cookies } from "next/headers" -import { redirect } from "next/navigation"; -import "server-only" - -/** - * Handles user logout by deleting the authentication token and redirecting to the login page. - * This function is intended to be used on the server side. - * - * @returns A promise that resolves when the logout process is complete. - */ -export default async function logout(){ - cookies().delete("token"); - redirect("/login") -} \ No newline at end of file diff --git a/src/_features/auth/actions/signIn.ts b/src/_features/auth/actions/signIn.ts deleted file mode 100644 index 55e87d3..0000000 --- a/src/_features/auth/actions/signIn.ts +++ /dev/null @@ -1,91 +0,0 @@ -"use server"; -import prisma from "@/db"; -import { User } from "@prisma/client"; -import AuthError, { AuthErrorCode } from "../AuthError"; -import { comparePassword, createJwtToken } from "../authUtils"; -import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; -import BaseError from "@/BaseError"; -import { revalidatePath } from "next/cache"; - -/** - * Handles the sign-in process for a user. - * - * This function validates a user's credentials (email and password), checks against the database, - * and on successful validation, redirects the user to the dashboard and sets a cookie with a JWT token. - * If the validation fails at any stage, it throws a custom AuthError. - * - * @param prevState - The previous state of the application, not currently used. - * @param rawFormData - The raw form data containing the user's email and password. - * @returns A promise that resolves to a redirect to the dashboard on successful authentication, - * or an object containing error details on failure. - * @throws {AuthError} - Specific authentication error based on the failure stage. - */ -export default async function signIn(prevState: any, rawFormData: FormData) { - //TODO: Add Throttling - //TODO: Add validation check if the user is already logged in - try { - const formData = { - email: rawFormData.get("email") as string, - password: rawFormData.get("password") as string, - }; - - // Retrieve user from the database by email - const user = await prisma.user.findUnique({ - where: { email: formData.email }, - }); - - // Throw if user not found - if (!user) throw new AuthError(AuthErrorCode.EMAIL_NOT_FOUND); - - // Throw if user has no password hash - // TODO: Add check if the user uses another provider - if (!user.passwordHash) - throw new AuthError(AuthErrorCode.EMPTY_USER_HASH); - - // Compare the provided password with the user's stored password hash - const isMatch = await comparePassword( - formData.password, - user.passwordHash - ); - if (!isMatch) throw new AuthError(AuthErrorCode.INVALID_CREDENTIALS); - - //Set cookie - //TODO: Auth: Add expiry - const token = createJwtToken({ id: user.id }); - - cookies().set("token", token); - } catch (e: unknown) { - // Custom error handling for authentication errors - if (e instanceof BaseError) { - // Specific error handling for known authentication errors - switch (e.errorCode) { - case AuthErrorCode.EMAIL_NOT_FOUND: - case AuthErrorCode.INVALID_CREDENTIALS: - return { - errors: { - message: - "Email/Password combination is incorrect. Please try again.", - }, - }; - default: - // Handle other types of authentication errors - return { - errors: { - message: e.message, - }, - }; - } - } - - // Generic error handling for unexpected server errors - return { - errors: { - message: - "An unexpected error occurred on the server. Please try again or contact the administrator.", - }, - }; - } - - redirect("/dashboard"); -} diff --git a/src/_features/auth/authUtils.ts b/src/_features/auth/authUtils.ts deleted file mode 100644 index 9160a94..0000000 --- a/src/_features/auth/authUtils.ts +++ /dev/null @@ -1,96 +0,0 @@ -import bcrypt from "bcrypt"; -import jwt, { SignOptions, JwtPayload } from "jsonwebtoken"; -import { User } from "@prisma/client"; -import prisma from "@/db"; -import AuthError, { AuthErrorCode } from "./AuthError"; -import authConfig from "@/config/auth"; -import UserClaims from "./types/UserClaims"; -import { cache } from "react"; -import BaseError from "@/BaseError"; - -/** - * Hashes a plain text password using bcrypt. - * - * @deprecated - * @param password - The plain text password to hash. - * @returns The hashed password. - */ -export async function hashPassword(password: string): Promise { - return bcrypt.hash(password, authConfig.saltRounds); -} - -/** - * Compares a plain text password with a hashed password. - * - * @param password - The plain text password to compare. - * @param hash - The hashed password to compare against. - * @returns True if the passwords match, false otherwise. - */ -export async function comparePassword( - password: string, - hash: string -): Promise { - return bcrypt.compare(password, hash); -} - -/** - * Creates a JWT token based on user claims. - * - * @param userClaims - The user claims to encode in the JWT. - * @param options - Optional signing options. - * @returns The generated JWT token. - */ -export function createJwtToken( - userClaims: UserClaims, - options?: SignOptions -): string { - const secret = process.env.JWT_SECRET; - if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY); - return jwt.sign(userClaims, secret, options); -} - -/** - * Decodes a JWT token and retrieves the payload. - * - * @param token - The JWT token to decode. - * @returns The decoded payload. - */ -export function decodeJwtToken(token: string): JwtPayload | string { - const secret = process.env.JWT_SECRET; - if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY); - - try { - return jwt.verify(token, secret) as JwtPayload; - } catch (error) { - throw new AuthError(AuthErrorCode.INVALID_JWT_TOKEN); - } -} - -/** - * Retrieves user data from the database based on the provided JWT token. - * - * This function decodes the JWT token to extract the user ID, and then queries the database using Prisma - * to fetch the user's details, including the profile photo, roles, and direct permissions. - * - * @param token - The JWT token containing the user's ID. - * @returns The user's data if the user exists, or null if no user is found. - * Throws an error if the token is invalid or the database query fails. - */ -export const getUserFromToken = cache(async (token: string) => { - // Decode the JWT token to extract the user ID - const decodedToken = decodeJwtToken(token) as { id: string; iat: number; }; - - // Fetch the user from the database - const user = await prisma.user.findFirst({ - include: { - photoProfile: true, - roles: true, - directPermissions: true - }, - where: { - id: decodedToken.id, - }, - }); - - return user; -}) diff --git a/src/_features/auth/contexts/AuthContext.tsx b/src/_features/auth/contexts/AuthContext.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/_features/auth/index.ts b/src/_features/auth/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/_features/auth/tools/checkMultiplePermissions.ts b/src/_features/auth/tools/checkMultiplePermissions.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/_features/auth/tools/checkPermission.ts b/src/_features/auth/tools/checkPermission.ts deleted file mode 100644 index 3dd63c2..0000000 --- a/src/_features/auth/tools/checkPermission.ts +++ /dev/null @@ -1,42 +0,0 @@ -import getCurrentUser from "./getCurrentUser"; -import "server-only"; - -/** - * Checks if the current user has the specified permissions. - * - * @param permission - The specific permission to check. If it's "guest-only", the function returns true if the user is not authenticated. If it's "authenticated-only", it returns true if the user is authenticated. For other permissions, it checks against the user's roles and direct permissions. - * @param currentUser - Optional. The current user object. If not provided, the function retrieves the current user. - * @returns true if the user has the required permission, otherwise false. - */ -export default async function checkPermission( - permission?: "guest-only" | "authenticated-only" | (string & {}), - currentUser?: Awaited> -): Promise { - // Allow if no specific permission is required. - if (!permission) return true; - - // Retrieve current user if not provided. - const user = currentUser ?? (await getCurrentUser()); - - // Handle non-authenticated users. - if (!user) { - return permission === "guest-only"; - } - - // Allow authenticated users if the permission is 'authenticated-only'. - if (permission === "authenticated-only") { - return true; - } - - // Short-circuit for super-admin role to allow all permissions. - if (user.roles.some((role) => role.code === "super-admin")) return true; - - // Aggregate all role codes and direct permissions into a set for efficient lookup. - const permissions = new Set([ - ...user.roles.map((role) => role.code), - ...user.directPermissions.map((dp) => dp.code), - ]); - - // Check if the user has the required permission. - return permissions.has(permission); -} diff --git a/src/_features/auth/tools/getCurrentUser.ts b/src/_features/auth/tools/getCurrentUser.ts deleted file mode 100644 index b196d21..0000000 --- a/src/_features/auth/tools/getCurrentUser.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { cache } from "react" -import "server-only" -import { getUserFromToken } from "../authUtils" -import { cookies } from "next/headers" - -/** - * Retrieves the current user based on the JWT token stored in cookies. - * This function is intended to run on the server side in a Next.js application. - * It reads the JWT token from the cookies, decodes it to get the user ID, - * and then fetches the corresponding user data from the database. - * - * @returns The current user's data if the user is authenticated and found in the database, otherwise null. - */ -const getCurrentUser = async () => { - // Retrieve the token from cookies - const token = cookies().get("token")?.value; - - // If no token is found, return null (no current user) - if(!token) return null; - - // Use the token to get the user from the database - const user = await getUserFromToken(token); - - // Return the user if found, otherwise return null - return user ? user : null; -} - -export default getCurrentUser; diff --git a/src/_features/auth/tools/hashPassword.ts b/src/_features/auth/tools/hashPassword.ts deleted file mode 100644 index 99e09f8..0000000 --- a/src/_features/auth/tools/hashPassword.ts +++ /dev/null @@ -1,12 +0,0 @@ -import bcrypt from "bcrypt"; -import authConfig from "../../../config/auth"; - -/** - * Hashes a plain text password using bcrypt. - * - * @param password - The plain text password to hash. - * @returns The hashed password. - */ -export default async function hashPassword(password: string): Promise { - return bcrypt.hash(password, authConfig.saltRounds); -} diff --git a/src/_features/auth/types/CrudPermissions.d.ts b/src/_features/auth/types/CrudPermissions.d.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/_features/auth/types/UserClaims.d.ts b/src/_features/auth/types/UserClaims.d.ts deleted file mode 100644 index 5997a53..0000000 --- a/src/_features/auth/types/UserClaims.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { User } from "@prisma/client" - -type UserClaims = { - id: User["id"] -} - -export default UserClaims \ No newline at end of file diff --git a/src/_features/dashboard/components/DashboardTable/DashboardTable.tsx b/src/_features/dashboard/components/DashboardTable/DashboardTable.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/_features/dashboard/components/DashboardTable/index.ts b/src/_features/dashboard/components/DashboardTable/index.ts deleted file mode 100644 index 6a3b717..0000000 --- a/src/_features/dashboard/components/DashboardTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DashboardTable"; diff --git a/src/_features/dashboard/components/index.ts b/src/_features/dashboard/components/index.ts deleted file mode 100644 index 248077e..0000000 --- a/src/_features/dashboard/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DashboardTable from "./DashboardTable"; - -export { DashboardTable }; diff --git a/src/_features/dashboard/errors/DashboardError.ts b/src/_features/dashboard/errors/DashboardError.ts deleted file mode 100644 index 3c11707..0000000 --- a/src/_features/dashboard/errors/DashboardError.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Prisma } from "@prisma/client"; - -// Use TypeScript enum for error codes to provide better autocompletion and error handling -export const DashboardErrorCodes = [ - "UNAUTHORIZED", - "NOT_FOUND", - "UNKNOWN_ERROR", - "INVALID_FORM_DATA", -] as const; - -interface ErrorOptions { - message?: string, - errorCode?: typeof DashboardErrorCodes[number] | string & {}, - formErrors?: Record -} - -/** - * Custom error class for handling errors specific to the dashboard application. - */ -export default class DashboardError extends Error { - public readonly errorCode: typeof DashboardErrorCodes[number] | string & {}; - public readonly formErrors?: Record - - constructor(options: ErrorOptions) { - super(options.message ?? "Undetermined Error"); // Pass message to the Error parent class - this.errorCode = options.errorCode ?? "UNKNOWN_ERROR"; - this.formErrors = options.formErrors; - Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain - } - - /** - * Returns a structured error response object. - */ - getErrorReponseObject(){ - return { - success: false, - dashboardError: true, - error: { - message: `${this.message}`, - errorCode: this.errorCode, - errors: this.formErrors ?? undefined - } - } as const; - } -} diff --git a/src/_features/dashboard/permissions/actions/deletePermission.ts b/src/_features/dashboard/permissions/actions/deletePermission.ts deleted file mode 100644 index 881b1ae..0000000 --- a/src/_features/dashboard/permissions/actions/deletePermission.ts +++ /dev/null @@ -1,26 +0,0 @@ -"use server"; - -import prisma from "@/db"; -import checkPermission from "@/features/auth/tools/checkPermission"; -import { handleCatch, unauthorized } from "../../errors/DashboardError"; -import ServerResponse from "@/types/Action"; -import { revalidatePath } from "next/cache"; - -export default async function deletePermission(id: string): Promise { - try { - if (!(await checkPermission("permission.delete"))) return unauthorized(); - const permission = await prisma.permission.delete({ - where: { id }, - }); - - - revalidatePath(".") - - return { - success: true, - message: "The permission has been deleted successfully", - }; - } catch (e: unknown) { - return handleCatch(e) - } -} diff --git a/src/_features/dashboard/permissions/actions/getAllPermissions.ts b/src/_features/dashboard/permissions/actions/getAllPermissions.ts deleted file mode 100644 index f4850c5..0000000 --- a/src/_features/dashboard/permissions/actions/getAllPermissions.ts +++ /dev/null @@ -1,48 +0,0 @@ -"use server" - -import checkPermission from "@/features/auth/tools/checkPermission" -import { handleCatch, notFound, unauthorized } from "../../errors/DashboardError" -import ServerResponse from "@/types/Action"; - -type PermissionData = { - code: string, - name: string, -} - -/** - * Retrieves all active permissions from the database if the user has the 'permissions.readAll' permission. - * - * @returns A structured server response containing the list of permissions or an error message. - */ -export default async function getAllPermissions(): Promise>{ - try { - // Check if the user has the required permission - if (!await checkPermission("permissions.readAll")) return unauthorized() - - // Fetch active permissions from the database - const permissions = await prisma?.permission.findMany({ - where: { - isActive: true - }, - select: { - code: true, - name: true, - } - }); - - // If no permissions are found, throw a custom 'not found' error - if (!permissions || permissions.length === 0) { - return notFound({ message: "No active permissions found." }); - } - - // Return the permissions in a structured server response - return { - success: true, - message: "Permissions fetched", - data: permissions - } - } catch (e){ - return handleCatch(e) - } - -} \ No newline at end of file diff --git a/src/_features/dashboard/permissions/actions/getPermissionById.ts b/src/_features/dashboard/permissions/actions/getPermissionById.ts deleted file mode 100644 index 37543c9..0000000 --- a/src/_features/dashboard/permissions/actions/getPermissionById.ts +++ /dev/null @@ -1,33 +0,0 @@ -"use server"; - -import { unauthorized } from "@/BaseError"; -import prisma from "@/db"; -import checkPermission from "@/features/auth/tools/checkPermission"; - -export default async function getPermissionById(id: string) { - if (!(await checkPermission("permission.read"))) unauthorized(); - - const permission = await prisma.permission.findFirst({ - where: { id }, - select: { - code: true, - description: true, - id: true, - isActive: true, - name: true, - }, - }); - - if (!permission) { - return { - success: false, - message: "Permission not found", - } as const; - } - - return { - success: true, - message: "Permission fetched successfully", - data: permission, - } as const; -} diff --git a/src/_features/dashboard/permissions/actions/upsertPermission.ts b/src/_features/dashboard/permissions/actions/upsertPermission.ts deleted file mode 100644 index e9cb39f..0000000 --- a/src/_features/dashboard/permissions/actions/upsertPermission.ts +++ /dev/null @@ -1,81 +0,0 @@ -"use server"; - -import checkPermission from "@/features/auth/tools/checkPermission"; -import permissionFormDataSchema, { PermissionFormData } from "../../../../modules/permission/formSchemas/PermissionFormData"; -import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue"; -import prisma from "@/db"; -import { revalidatePath } from "next/cache"; -import ServerResponse from "@/types/Action"; -import DashboardError, { handleCatch, unauthorized } from "../../errors/DashboardError"; - -/** - * Upserts a permission based on the provided PermissionFormData. - * If the permission already exists (determined by `id`), it updates the permission; otherwise, it creates a new permission. - * Authorization checks are performed based on whether it's a create or update operation. - * - * @param data - The data for creating or updating the permission. - * @returns An object containing the success status, message, and any errors. - */ -export default async function upsertPermission( - data: PermissionFormData -): Promise { - try { - const isInsert = !data.id; - - // Authorization check - const permissionType = isInsert ? "permission.create" : "permission.update"; - if (!(await checkPermission(permissionType))) { - return unauthorized(); - } - - // Validate form data - const validatedFields = permissionFormDataSchema.safeParse(data); - if (!validatedFields.success) { - throw new DashboardError({ - errorCode: "INVALID_FORM_DATA", - formErrors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors) - }) - } - const permissionData = { - code: validatedFields.data.code, - description: validatedFields.data.description, - name: validatedFields.data.name, - isActive: validatedFields.data.isActive, - }; - - // Database operation - if (isInsert) { - if (await prisma.permission.findFirst({ - where: { - code: permissionData.code - } - })){ - throw new DashboardError({ - errorCode: "INVALID_FORM_DATA", - formErrors: { - code: "The code is already exists" - } - }) - } - await prisma.permission.create({ data: permissionData }); - } else { - await prisma.permission.update({ - where: { id: validatedFields.data.id! }, - data: permissionData, - }); - } - - // Revalidate the cache - revalidatePath("."); - - // Return success message - return { - success: true, - message: `Permission ${validatedFields.data.name} has been successfully ${ - isInsert ? "created" : "updated" - }.`, - }; - } catch (error) { - return handleCatch(error) - } -} diff --git a/src/_features/dashboard/permissions/data/getPermissions.ts b/src/_features/dashboard/permissions/data/getPermissions.ts deleted file mode 100644 index 93c415d..0000000 --- a/src/_features/dashboard/permissions/data/getPermissions.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { unauthorized } from "@/BaseError"; -import prisma from "@/db"; -import checkPermission from "@/features/auth/tools/checkPermission"; -import "server-only"; - -/** - * Retrieves all permissions along with the count of associated permissions and users. - * Authorization check is performed for the operation. - * - * @returns An array of permission objects each including details and counts of related permissions and users. - */ -export default async function getPermissions() { - // Authorization check - if (!(await checkPermission("permissions.readAll"))) { - return unauthorized(); - } - - try { - // Fetch permissions from the database - const permissions = await prisma.permission.findMany({ - include: { - _count: { - select: { - roles: true, - directUsers: true, - }, - }, - }, - }); - - // Transform the data into the desired format - return permissions.map( - ({ id, code, name, description, isActive, _count }) => ({ - id, - code, - name, - description, - isActive, - roleCount: _count.roles, - //User count counts only direct user - userCount: _count.directUsers, - }) - ); - } catch (error) { - console.error("Error retrieving permissions", error); - throw error; - } -} diff --git a/src/_features/dashboard/permissions/modals/DeleteModal/DeleteModal.tsx b/src/_features/dashboard/permissions/modals/DeleteModal/DeleteModal.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/_features/dashboard/permissions/modals/DeleteModal/index.ts b/src/_features/dashboard/permissions/modals/DeleteModal/index.ts deleted file mode 100644 index 34d6794..0000000 --- a/src/_features/dashboard/permissions/modals/DeleteModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DeleteModal"; diff --git a/src/_features/dashboard/permissions/modals/FormModal/FormModal.tsx b/src/_features/dashboard/permissions/modals/FormModal/FormModal.tsx deleted file mode 100644 index 6f55667..0000000 --- a/src/_features/dashboard/permissions/modals/FormModal/FormModal.tsx +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import DashboardError from "@/features/dashboard/errors/DashboardError"; -import getPermissionById from "@/features/dashboard/permissions/actions/getPermissionById"; -import upsertPermission from "@/features/dashboard/permissions/actions/upsertPermission"; -import permissionFormDataSchema, { - PermissionFormData, -} from "@/features/dashboard/permissions/formSchemas/PermissionFormData"; -import withErrorHandling from "@/features/dashboard/utils/withServerAction"; -import { showNotification } from "@/utils/notifications"; -import { - Flex, - Modal, - Stack, - Switch, - TextInput, - Textarea, - Button, - ScrollArea, - Checkbox, - Skeleton, - Fieldset, - Alert, -} from "@mantine/core"; -import { useForm, zodResolver } from "@mantine/form"; -import { useRouter } from "next/navigation"; -import React, { useCallback, useEffect, useState } from "react"; -import { TbDeviceFloppy } from "react-icons/tb"; - -export interface ModalProps { - title: string; - readonly?: boolean; - id?: string; - opened: boolean; - onClose?: () => void; -} - -/** - * A component for rendering a modal with a form to create or edit a permission. - * - * @param props - The props for the component. - * @returns The rendered element. - */ -export default function FormModal(props: ModalProps) { - const router = useRouter(); - const [isSubmitting, setSubmitting] = useState(false); - const [isFetching, setFetching] = useState(false); - const [errorMessage, setErrorMessage] = useState(""); - - const form = useForm({ - initialValues: { - code: "", - description: "", - id: "", - isActive: false, - name: "", - }, - validate: zodResolver(permissionFormDataSchema), - validateInputOnChange: false, - onValuesChange: (values) => { - console.log(values); - }, - }); - - /** - * Fetches permission data by ID and populates the form if the modal is opened and an ID is provided. - */ - useEffect(() => { - if (!props.opened || !props.id) { - return; - } - - setFetching(true); - getPermissionById(props.id) - .then((response) => { - if (response.success) { - const data = response.data; - form.setValues({ - code: data.code, - description: data.description, - id: data.id, - isActive: data.isActive, - name: data.name, - }); - } - }) - .catch((e) => { - //TODO: Handle error - console.log(e); - }) - .finally(() => { - setFetching(false); - }); - }, [props.opened, props.id]); - - const closeModal = () => { - form.reset() - props.onClose ? props.onClose() : router.replace("?"); - }; - - const handleSubmit = (values: PermissionFormData) => { - setSubmitting(true); - withErrorHandling(() => upsertPermission(values)) - .then((response) => { - showNotification(response.message!, "success"); - closeModal(); - }) - .catch((e) => { - if (e instanceof DashboardError) { - if (e.errorCode === "INVALID_FORM_DATA") { - form.setErrors(e.formErrors ?? {}); - } else { - setErrorMessage(`ERROR: ${e.message} (${e.errorCode})`); - } - } else if (e instanceof Error) { - setErrorMessage(`ERROR: ${e.message}`); - } else { - setErrorMessage( - `Unkown error is occured. Please contact administrator` - ); - } - }) - .finally(() => { - setSubmitting(false); - }); - }; - - return ( - -
- - {errorMessage && {errorMessage}} - {/* ID */} - {form.values.id ? ( - - ) : ( -
- )} - - {/* Code */} - - - - - {/* Name */} - - - - - {/* Description */} - -