Added perission check and permission management
This commit is contained in:
parent
152d444067
commit
7800fb471c
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import DashboardError from "@/features/dashboard/errors/DashboardError";
|
import DashboardError from "@/features/dashboard/errors/DashboardError";
|
||||||
|
import getAllPermissions from "@/features/dashboard/permissions/actions/getAllPermissions";
|
||||||
import getRoleById from "@/features/dashboard/roles/actions/getRoleById";
|
import getRoleById from "@/features/dashboard/roles/actions/getRoleById";
|
||||||
import upsertRole from "@/features/dashboard/roles/actions/upsertRole";
|
import upsertRole from "@/features/dashboard/roles/actions/upsertRole";
|
||||||
import roleFormDataSchema, {
|
import roleFormDataSchema, {
|
||||||
|
|
@ -20,11 +21,13 @@ import {
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Fieldset,
|
Fieldset,
|
||||||
Alert,
|
Alert,
|
||||||
|
Chip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useForm, zodResolver } from "@mantine/form";
|
import { useForm, zodResolver } from "@mantine/form";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { TbDeviceFloppy } from "react-icons/tb";
|
import { TbDeviceFloppy } from "react-icons/tb";
|
||||||
|
import { string } from "zod";
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -45,6 +48,9 @@ export default function FormModal(props: ModalProps) {
|
||||||
const [isSubmitting, setSubmitting] = useState(false);
|
const [isSubmitting, setSubmitting] = useState(false);
|
||||||
const [isFetching, setFetching] = useState(false);
|
const [isFetching, setFetching] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
const [allPermissions, setAllPermissions] = useState<
|
||||||
|
{ code: string; name: string }[] | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
const form = useForm<RoleFormData>({
|
const form = useForm<RoleFormData>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
|
@ -53,6 +59,7 @@ export default function FormModal(props: ModalProps) {
|
||||||
id: "",
|
id: "",
|
||||||
isActive: false,
|
isActive: false,
|
||||||
name: "",
|
name: "",
|
||||||
|
permissions: [],
|
||||||
},
|
},
|
||||||
validate: zodResolver(roleFormDataSchema),
|
validate: zodResolver(roleFormDataSchema),
|
||||||
validateInputOnChange: false,
|
validateInputOnChange: false,
|
||||||
|
|
@ -61,6 +68,29 @@ export default function FormModal(props: ModalProps) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Fetch Permissions
|
||||||
|
useEffect(() => {
|
||||||
|
setFetching(true);
|
||||||
|
withErrorHandling(getAllPermissions)
|
||||||
|
.then((response) => {
|
||||||
|
setAllPermissions(response.data);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e instanceof DashboardError) {
|
||||||
|
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(() => {
|
||||||
|
setFetching(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches role data by ID and populates the form if the modal is opened and an ID is provided.
|
* Fetches role data by ID and populates the form if the modal is opened and an ID is provided.
|
||||||
*/
|
*/
|
||||||
|
|
@ -70,9 +100,8 @@ export default function FormModal(props: ModalProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setFetching(true);
|
setFetching(true);
|
||||||
getRoleById(props.id)
|
withErrorHandling(getRoleById, props.id)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.success) {
|
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
form.setValues({
|
form.setValues({
|
||||||
code: data.code,
|
code: data.code,
|
||||||
|
|
@ -80,12 +109,21 @@ export default function FormModal(props: ModalProps) {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
isActive: data.isActive,
|
isActive: data.isActive,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
permissions: data.permissions.map(
|
||||||
|
(permission) => permission.code
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
//TODO: Handle error
|
if (e instanceof DashboardError) {
|
||||||
console.log(e);
|
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(() => {
|
.finally(() => {
|
||||||
setFetching(false);
|
setFetching(false);
|
||||||
|
|
@ -94,11 +132,12 @@ export default function FormModal(props: ModalProps) {
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
props.onClose ? props.onClose() : router.replace("?");
|
props.onClose ? props.onClose() : router.replace("?");
|
||||||
|
form.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (values: RoleFormData) => {
|
const handleSubmit = (values: RoleFormData) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
withErrorHandling(() => upsertRole(values))
|
withErrorHandling(upsertRole, values)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
showNotification(response.message!, "success");
|
showNotification(response.message!, "success");
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|
@ -187,6 +226,30 @@ export default function FormModal(props: ModalProps) {
|
||||||
/>
|
/>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
||||||
|
<Fieldset legend="Permissions">
|
||||||
|
<Chip.Group
|
||||||
|
multiple
|
||||||
|
value={form.values.permissions}
|
||||||
|
onChange={(values) =>
|
||||||
|
!props.readonly &&
|
||||||
|
form.setFieldValue("permissions", values)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex wrap="wrap" gap="md">
|
||||||
|
{allPermissions?.map((permission) => (
|
||||||
|
<div key={permission.code}>
|
||||||
|
<Chip
|
||||||
|
disabled={isSubmitting}
|
||||||
|
value={permission.code}
|
||||||
|
>
|
||||||
|
{permission.code}
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Chip.Group>
|
||||||
|
</Fieldset>
|
||||||
|
|
||||||
{/* Buttons */}
|
{/* Buttons */}
|
||||||
<Flex justify="flex-end" align="center" gap="lg" mt="lg">
|
<Flex justify="flex-end" align="center" gap="lg" mt="lg">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import prisma from "@/db";
|
||||||
import AuthError, { AuthErrorCode } from "./AuthError";
|
import AuthError, { AuthErrorCode } from "./AuthError";
|
||||||
import authConfig from "@/config/auth";
|
import authConfig from "@/config/auth";
|
||||||
import UserClaims from "./types/UserClaims";
|
import UserClaims from "./types/UserClaims";
|
||||||
|
import { cache } from "react";
|
||||||
|
import BaseError from "@/BaseError";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hashes a plain text password using bcrypt.
|
* Hashes a plain text password using bcrypt.
|
||||||
|
|
@ -64,12 +66,21 @@ export function decodeJwtToken(token: string): JwtPayload | string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserFromToken(token: string) {
|
/**
|
||||||
const decodedToken = decodeJwtToken(token) as {
|
* Retrieves user data from the database based on the provided JWT token.
|
||||||
id: string;
|
*
|
||||||
iat: number;
|
* 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({
|
const user = await prisma.user.findFirst({
|
||||||
include: {
|
include: {
|
||||||
photoProfile: true,
|
photoProfile: true,
|
||||||
|
|
@ -82,4 +93,4 @@ export async function getUserFromToken(token: string) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,26 @@ import "server-only"
|
||||||
import { getUserFromToken } from "../authUtils"
|
import { getUserFromToken } from "../authUtils"
|
||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
|
|
||||||
const getCurrentUser = cache(async () => {
|
/**
|
||||||
|
* 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;
|
const token = cookies().get("token")?.value;
|
||||||
|
|
||||||
|
// If no token is found, return null (no current user)
|
||||||
if(!token) return null;
|
if(!token) return null;
|
||||||
|
|
||||||
|
// Use the token to get the user from the database
|
||||||
const user = await getUserFromToken(token);
|
const user = await getUserFromToken(token);
|
||||||
|
|
||||||
if (!user) return null;
|
// Return the user if found, otherwise return null
|
||||||
|
return user ? user : null;
|
||||||
return user;
|
}
|
||||||
})
|
|
||||||
|
|
||||||
export default getCurrentUser;
|
export default getCurrentUser;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
// Use TypeScript enum for error codes to provide better autocompletion and error handling
|
||||||
export const DashboardErrorCodes = [
|
export const DashboardErrorCodes = [
|
||||||
"UNAUTHORIZED",
|
"UNAUTHORIZED",
|
||||||
"NOT_FOUND",
|
"NOT_FOUND",
|
||||||
|
|
@ -10,22 +11,26 @@ export const DashboardErrorCodes = [
|
||||||
interface ErrorOptions {
|
interface ErrorOptions {
|
||||||
message?: string,
|
message?: string,
|
||||||
errorCode?: typeof DashboardErrorCodes[number] | string & {},
|
errorCode?: typeof DashboardErrorCodes[number] | string & {},
|
||||||
formErrors?: {[k: string]: string}
|
formErrors?: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom error class for handling errors specific to the dashboard application.
|
||||||
|
*/
|
||||||
export default class DashboardError extends Error {
|
export default class DashboardError extends Error {
|
||||||
public readonly errorCode: typeof DashboardErrorCodes[number] | string & {};
|
public readonly errorCode: typeof DashboardErrorCodes[number] | string & {};
|
||||||
public readonly formErrors?: {[k: string]: string}
|
public readonly formErrors?: Record<string, string>
|
||||||
// public readonly data: object;
|
|
||||||
|
|
||||||
constructor(options: ErrorOptions) {
|
constructor(options: ErrorOptions) {
|
||||||
super(options.message ?? "Undetermined Error"); // Pass message to the Error parent class
|
super(options.message ?? "Undetermined Error"); // Pass message to the Error parent class
|
||||||
this.errorCode = options.errorCode ?? "UNKNOWN_ERROR";
|
this.errorCode = options.errorCode ?? "UNKNOWN_ERROR";
|
||||||
this.formErrors = options.formErrors;
|
this.formErrors = options.formErrors;
|
||||||
// this.data = data;
|
|
||||||
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
|
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a structured error response object.
|
||||||
|
*/
|
||||||
getErrorReponseObject(){
|
getErrorReponseObject(){
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -35,10 +40,14 @@ export default class DashboardError extends Error {
|
||||||
errorCode: this.errorCode,
|
errorCode: this.errorCode,
|
||||||
errors: this.formErrors ?? undefined
|
errors: this.formErrors ?? undefined
|
||||||
}
|
}
|
||||||
} as const
|
} as const;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles exceptions and converts them into a structured error response.
|
||||||
|
* @param e The caught error or exception.
|
||||||
|
*/
|
||||||
export const handleCatch = (e: unknown) => {
|
export const handleCatch = (e: unknown) => {
|
||||||
if (e instanceof DashboardError){
|
if (e instanceof DashboardError){
|
||||||
return e.getErrorReponseObject()
|
return e.getErrorReponseObject()
|
||||||
|
|
@ -65,9 +74,23 @@ export const handleCatch = (e: unknown) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws a 'UNAUTHORIZED' DashboardError.
|
||||||
|
*/
|
||||||
export const unauthorized = () => {
|
export const unauthorized = () => {
|
||||||
throw new DashboardError({
|
throw new DashboardError({
|
||||||
errorCode: "UNAUTHORIZED",
|
errorCode: "UNAUTHORIZED",
|
||||||
message: "You are unauthorized to do this action"
|
message: "You are unauthorized to do this action"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws a 'NOT_FOUND' DashboardError with a custom or default message.
|
||||||
|
* @param message Optional custom message for the error.
|
||||||
|
*/
|
||||||
|
export const notFound = ({message}: {message?: string}) => {
|
||||||
|
throw new DashboardError({
|
||||||
|
errorCode: "NOT_FOUND",
|
||||||
|
message: message ?? "The requested data could not be located. It may have been deleted or relocated. Please verify the information or try a different request."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
"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<ServerResponse<PermissionData[]>>{
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ import "server-only";
|
||||||
*/
|
*/
|
||||||
export default async function getPermissions() {
|
export default async function getPermissions() {
|
||||||
// Authorization check
|
// Authorization check
|
||||||
if (!(await checkPermission("permissions.getAll"))) {
|
if (!(await checkPermission("permissions.readAll"))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,27 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { unauthorized } from "@/BaseError";
|
|
||||||
import prisma from "@/db";
|
import prisma from "@/db";
|
||||||
import checkPermission from "@/features/auth/tools/checkPermission";
|
import checkPermission from "@/features/auth/tools/checkPermission";
|
||||||
|
import { handleCatch, notFound, unauthorized } from "../../errors/DashboardError";
|
||||||
|
import ServerResponse from "@/types/Action";
|
||||||
|
|
||||||
export default async function getRoleById(id: string) {
|
type RoleData = {
|
||||||
if (!(await checkPermission("role.read"))) unauthorized();
|
id: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
permissions: {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function getRoleById(id: string): Promise<ServerResponse<RoleData>>{
|
||||||
|
try{
|
||||||
|
|
||||||
|
if (!(await checkPermission("role.read"))) return unauthorized();
|
||||||
|
|
||||||
const role = await prisma.role.findFirst({
|
const role = await prisma.role.findFirst({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|
@ -26,15 +42,15 @@ export default async function getRoleById(id: string) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!role) {
|
if (!role) {
|
||||||
return {
|
throw new Error("Permission not found")
|
||||||
success: false,
|
|
||||||
message: "Role not found",
|
|
||||||
} as const;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Role fetched successfully",
|
message: "Role fetched successfully",
|
||||||
data: role,
|
data: role,
|
||||||
} as const;
|
};
|
||||||
|
} catch (e){
|
||||||
|
return handleCatch(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
|
||||||
import prisma from "@/db";
|
import prisma from "@/db";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import ServerResponse from "@/types/Action";
|
import ServerResponse from "@/types/Action";
|
||||||
import DashboardError, { handleCatch, unauthorized } from "../../errors/DashboardError";
|
import DashboardError, {
|
||||||
|
handleCatch,
|
||||||
|
unauthorized,
|
||||||
|
} from "../../errors/DashboardError";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upserts a role based on the provided RoleFormData.
|
* Upserts a role based on the provided RoleFormData.
|
||||||
|
|
@ -33,8 +36,10 @@ export default async function upsertRole(
|
||||||
if (!validatedFields.success) {
|
if (!validatedFields.success) {
|
||||||
throw new DashboardError({
|
throw new DashboardError({
|
||||||
errorCode: "INVALID_FORM_DATA",
|
errorCode: "INVALID_FORM_DATA",
|
||||||
formErrors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors)
|
formErrors: mapObjectToFirstValue(
|
||||||
})
|
validatedFields.error.flatten().fieldErrors
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const roleData = {
|
const roleData = {
|
||||||
code: validatedFields.data.code,
|
code: validatedFields.data.code,
|
||||||
|
|
@ -43,25 +48,38 @@ export default async function upsertRole(
|
||||||
isActive: validatedFields.data.isActive,
|
isActive: validatedFields.data.isActive,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const permissionIds = validatedFields.data.permissions.map(
|
||||||
|
(permission) => ({ code: permission })
|
||||||
|
);
|
||||||
|
|
||||||
// Database operation
|
// Database operation
|
||||||
if (isInsert) {
|
if (isInsert) {
|
||||||
if (await prisma.role.findFirst({
|
if (
|
||||||
|
await prisma.role.findFirst({
|
||||||
where: {
|
where: {
|
||||||
code: roleData.code
|
code: roleData.code,
|
||||||
}
|
},
|
||||||
})){
|
})
|
||||||
|
) {
|
||||||
throw new DashboardError({
|
throw new DashboardError({
|
||||||
errorCode: "INVALID_FORM_DATA",
|
errorCode: "INVALID_FORM_DATA",
|
||||||
formErrors: {
|
formErrors: {
|
||||||
code: "The code is already exists"
|
code: "The code is already exists",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
await prisma.role.create({
|
||||||
}
|
data: {
|
||||||
await prisma.role.create({ data: roleData });
|
...roleData,
|
||||||
|
permissions: {
|
||||||
|
connect: permissionIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.role.update({
|
await prisma.role.update({
|
||||||
where: { id: validatedFields.data.id! },
|
where: { id: validatedFields.data.id! },
|
||||||
data: roleData,
|
data: { ...roleData, permissions: { connect: permissionIds } },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +94,6 @@ export default async function upsertRole(
|
||||||
}.`,
|
}.`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return handleCatch(error)
|
return handleCatch(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { unauthorized } from "@/BaseError";
|
|
||||||
import prisma from "@/db";
|
import prisma from "@/db";
|
||||||
import checkPermission from "@/features/auth/tools/checkPermission";
|
import checkPermission from "@/features/auth/tools/checkPermission";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
|
import { unauthorized } from "@/features/dashboard/errors/DashboardError";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all roles along with the count of associated permissions and users.
|
* Retrieves all roles along with the count of associated permissions and users.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export interface RoleFormData {
|
||||||
code: string;
|
code: string;
|
||||||
description: string;
|
description: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
permissions: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleFormDataSchema = z.object({
|
const roleFormDataSchema = z.object({
|
||||||
|
|
@ -14,6 +15,7 @@ const roleFormDataSchema = z.object({
|
||||||
code: z.string().min(1),
|
code: z.string().min(1),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
|
permissions: z.array(z.string()).optional().default([]),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default roleFormDataSchema;
|
export default roleFormDataSchema;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
import ServerResponse from "@/types/Action";
|
import ServerResponse, { SuccessResponse } from "@/types/Action";
|
||||||
import DashboardError from "../errors/DashboardError";
|
import DashboardError from "../errors/DashboardError";
|
||||||
|
|
||||||
async function withErrorHandling<T extends ServerResponse>(
|
/**
|
||||||
asyncFunction: () => Promise<T>
|
* A higher-order function that wraps an async function and provides structured error handling.
|
||||||
): Promise<T> {
|
* If the wrapped function returns a successful response, it's returned directly.
|
||||||
const result = await asyncFunction();
|
* If an error occurs, it throws a DashboardError for dashboard-related errors or a generic Error otherwise.
|
||||||
if (result.success) {
|
*
|
||||||
|
* @param asyncFunction - The async function to wrap.
|
||||||
|
* @param args - The arguments to pass to the async function.
|
||||||
|
* @returns The successful response from the async function.
|
||||||
|
* @throws DashboardError for dashboard-related errors or Error for other errors.
|
||||||
|
*/
|
||||||
|
async function withErrorHandling<T, Args extends unknown[] = []>(
|
||||||
|
asyncFunction: (...args: Args) => Promise<ServerResponse<T>>,
|
||||||
|
...args: Args
|
||||||
|
){
|
||||||
|
const result = await asyncFunction(...args);
|
||||||
|
if (result.success === true) {
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
if (result.dashboardError && result.error) {
|
if (result.dashboardError && result.error) {
|
||||||
|
|
|
||||||
14
src/types/Action.d.ts
vendored
14
src/types/Action.d.ts
vendored
|
|
@ -1,5 +1,4 @@
|
||||||
type ServerResponse<T = undefined> =
|
export type ErrorResponse = {
|
||||||
| {
|
|
||||||
success: false;
|
success: false;
|
||||||
dashboardError?: boolean;
|
dashboardError?: boolean;
|
||||||
error?: {
|
error?: {
|
||||||
|
|
@ -9,10 +8,15 @@ type ServerResponse<T = undefined> =
|
||||||
};
|
};
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
|
export type SuccessResponse<T = undefined> = T extends undefined ? {success: true; message?: string} : {
|
||||||
success: true;
|
success: true;
|
||||||
message?: string;
|
message?: string;
|
||||||
data?: T;
|
data: T;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
type ServerResponse<T = undefined> =
|
||||||
|
| ErrorResponse
|
||||||
|
| SuccessResponse<T>
|
||||||
|
|
||||||
export default ServerResponse;
|
export default ServerResponse;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user