diff --git a/src/app/dashboard/users/page.tsx b/src/app/dashboard/users/page.tsx
index 8b9c747..ed2f9cd 100644
--- a/src/app/dashboard/users/page.tsx
+++ b/src/app/dashboard/users/page.tsx
@@ -1,6 +1,6 @@
import { Card, Stack, Title } from "@mantine/core";
import React from "react";
-import getUsers from "@/modules/userManagement/actions/getAllUsers";
+import getUsers from "@/modules/userManagement/services/getAllUsers";
import { Metadata } from "next";
import UsersTable from "@/modules/userManagement/tables/UsersTable/UsersTable";
import checkMultiplePermissions from "@/modules/auth/utils/checkMultiplePermissions";
@@ -25,7 +25,7 @@ export default async function UsersPage() {
Users
-
+
);
diff --git a/src/modules/dashboard/errors/DashboardError.ts b/src/modules/dashboard/errors/DashboardError.ts
index af3f052..c269fdc 100644
--- a/src/modules/dashboard/errors/DashboardError.ts
+++ b/src/modules/dashboard/errors/DashboardError.ts
@@ -14,6 +14,7 @@ interface DashboardErrorOptions {
message?: string;
errorCode: (typeof DashboardErrorCodes)[number] | (string & {});
formErrors?: Record
+ statusCode?: number;
}
export default class DashboardError extends BaseError {
@@ -24,6 +25,7 @@ export default class DashboardError extends BaseError {
super({
errorCode: options.errorCode,
message: options.message,
+ statusCode: options.statusCode,
});
this.errorCode = options.errorCode;
diff --git a/src/modules/dashboard/utils/notFound.ts b/src/modules/dashboard/utils/notFound.ts
index 3fa9b71..1819010 100644
--- a/src/modules/dashboard/utils/notFound.ts
+++ b/src/modules/dashboard/utils/notFound.ts
@@ -1,15 +1,16 @@
-import DashboardError from "../errors/DashboardError";
+import BaseError from "@/core/error/BaseError";
/**
* Throws a 'NOT_FOUND' DashboardError with a custom or default message.
* @param message Optional custom message for the error.
*/
const notFound = ({ message }: { message?: string }) => {
- throw new DashboardError({
+ throw new BaseError({
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.",
+ statusCode: 404
});
};
diff --git a/src/modules/userManagement/actions/deleteUser.ts b/src/modules/userManagement/actions/deleteUser.ts
deleted file mode 100644
index 81320ec..0000000
--- a/src/modules/userManagement/actions/deleteUser.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-"use server";
-
-import prisma from "@/db";
-import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
-import checkPermission from "@/modules/dashboard/services/checkPermission";
-import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
-import handleCatch from "@/modules/dashboard/utils/handleCatch";
-import notFound from "@/modules/dashboard/utils/notFound";
-import unauthorized from "@/modules/dashboard/utils/unauthorized";
-import { revalidatePath } from "next/cache";
-import UserManagementError from "../errors/UserManagementError";
-import db from "@/core/db";
-
-export default async function deleteUser(
- id: string
-): Promise {
- try {
- const currentUser = await getCurrentUser();
-
- if (!(await checkPermission("users.delete")) || !currentUser)
- return unauthorized();
-
- //prevents self delete
- if (currentUser.id === id) {
- throw new UserManagementError({
- errorCode: "CANNOT_DELETE_SELF",
- message: "You cannot delete yourself",
- });
- }
-
- const user = await db.user.delete({
- where: { id },
- });
-
- if (!user) notFound({ message: "The user does not exists" });
-
- revalidatePath(".");
-
- return {
- success: true,
- message: "The user has been deleted successfully",
- };
- } catch (e: unknown) {
- return handleCatch(e);
- }
-}
diff --git a/src/modules/userManagement/actions/deleteUserAction.ts b/src/modules/userManagement/actions/deleteUserAction.ts
new file mode 100644
index 0000000..ae512f4
--- /dev/null
+++ b/src/modules/userManagement/actions/deleteUserAction.ts
@@ -0,0 +1,29 @@
+"use server";
+
+import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
+import handleCatch from "@/modules/dashboard/utils/handleCatch";
+import unauthorized from "@/modules/dashboard/utils/unauthorized";
+import { revalidatePath } from "next/cache";
+import deleteUser from "../services/deleteUser";
+import checkPermission from "@/modules/auth/utils/checkPermission";
+
+export default async function deleteUserAction(
+ id: string
+): Promise {
+ try {
+
+ if (!(await checkPermission("users.delete")))
+ return unauthorized();
+
+ await deleteUser(id);
+
+ revalidatePath(".");
+
+ return {
+ success: true,
+ message: "The user has been deleted successfully",
+ };
+ } catch (e: unknown) {
+ return handleCatch(e);
+ }
+}
diff --git a/src/modules/userManagement/actions/getAllUsers.ts b/src/modules/userManagement/actions/getAllUsers.ts
deleted file mode 100644
index a62f592..0000000
--- a/src/modules/userManagement/actions/getAllUsers.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import db from "@/core/db";
-import prisma from "@/db";
-import checkPermission from "@/modules/dashboard/services/checkPermission";
-import unauthorized from "@/modules/dashboard/utils/unauthorized";
-import "server-only";
-
-const getAllUsers = async () => {
- if (!(await checkPermission("users.readAll"))) unauthorized();
-
- try {
- const users = await db.user.findMany({
- select: {
- id: true,
- email: true,
- photoProfile: true,
- name: true,
- roles: {
- select: {
- name: true,
- code: true,
- },
- },
- },
- });
-
- const result = users.map((user) => ({
- ...user,
- photoUrl: user.photoProfile ?? null,
- photoProfile: undefined,
- }));
-
- return result;
- } catch (e) {
- throw e;
- }
-};
-
-export default getAllUsers;
diff --git a/src/modules/userManagement/actions/getAllUsersAction.ts b/src/modules/userManagement/actions/getAllUsersAction.ts
new file mode 100644
index 0000000..e1c78c6
--- /dev/null
+++ b/src/modules/userManagement/actions/getAllUsersAction.ts
@@ -0,0 +1,24 @@
+"use server";
+
+import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
+import getAllUsers from "../services/getAllUsers";
+import handleCatch from "@/modules/dashboard/utils/handleCatch";
+import checkPermission from "@/modules/auth/utils/checkPermission";
+import unauthorized from "@/modules/dashboard/utils/unauthorized";
+
+export default async function getAllUsersAction(): Promise<
+ ServerResponseAction>>
+> {
+ try {
+ if (!(await checkPermission("users.readAll"))) unauthorized();
+
+ const users = await getAllUsers();
+
+ return {
+ success: true,
+ data: users,
+ };
+ } catch (e) {
+ return handleCatch(e);
+ }
+}
diff --git a/src/modules/userManagement/actions/getUserDetailById.ts b/src/modules/userManagement/actions/getUserDetailByIdAction.ts
similarity index 61%
rename from src/modules/userManagement/actions/getUserDetailById.ts
rename to src/modules/userManagement/actions/getUserDetailByIdAction.ts
index ff55d52..dc72e5b 100644
--- a/src/modules/userManagement/actions/getUserDetailById.ts
+++ b/src/modules/userManagement/actions/getUserDetailByIdAction.ts
@@ -1,10 +1,8 @@
"use server";
-import "server-only";
-import prisma from "@/db";
-import checkPermission from "@/modules/dashboard/services/checkPermission";
import unauthorized from "@/modules/dashboard/utils/unauthorized";
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
-import db from "@/core/db";
+import getUserById from "../services/getUserById";
+import checkPermission from "@/modules/auth/utils/checkPermission";
type UserData = {
id: string;
@@ -23,35 +21,14 @@ type UserData = {
* @param id The unique identifier of the user.
* @returns The user's detailed information or an error response.
*/
-export default async function getUserDetailById(
+export default async function getUserDetailByIdAction(
id: string
): Promise> {
// Check user permission
if (!checkPermission("users.read")) return unauthorized();
// Retrieve user data from the database
- const user = await db.user.findFirst({
- where: { id },
- select: {
- id: true,
- email: true,
- name: true,
- photoProfile: true,
- roles: {
- select: {
- code: true,
- name: true,
- },
- },
- },
- });
-
- // Check if user exists
- if (!user)
- return {
- success: false,
- message: "User not found",
- } as const;
+ const user = await getUserById(id)
// Format user data
const formattedUser = {
@@ -64,7 +41,6 @@ export default async function getUserDetailById(
return {
success: true,
- message: "Permission fetched successfully",
data: formattedUser,
} as const;
}
diff --git a/src/modules/userManagement/actions/upsertUser.ts b/src/modules/userManagement/actions/upsertUser.ts
deleted file mode 100644
index be0977c..0000000
--- a/src/modules/userManagement/actions/upsertUser.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-"use server";
-
-import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
-import prisma from "@/db";
-import { revalidatePath } from "next/cache";
-import userFormDataSchema, {
- UserFormData,
-} from "../formSchemas/userFormSchema";
-import checkPermission from "@/modules/dashboard/services/checkPermission";
-import unauthorized from "@/modules/dashboard/utils/unauthorized";
-import DashboardError from "@/modules/dashboard/errors/DashboardError";
-import handleCatch from "@/modules/dashboard/utils/handleCatch";
-import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
-import hashPassword from "@/modules/auth/utils/hashPassword";
-import db from "@/core/db";
-
-/**
- * Upserts a user based on the provided UserFormData.
- * If the user already exists (determined by `id`), it updates the user; otherwise, it creates a new user.
- * Authorization checks are performed based on whether it's a create or update operation.
- *
- * @param data - The data for creating or updating the user.
- * @returns An object containing the success status, message, and any errors.
- */
-export default async function upsertUser(
- data: UserFormData
-): Promise {
- try {
- const isInsert = !data.id;
-
- // Authorization check
- const permissionType = isInsert ? "users.create" : "users.update";
- if (!(await checkPermission(permissionType))) {
- return unauthorized();
- }
-
- // Validate form data
- const validatedFields = userFormDataSchema.safeParse(data);
- if (!validatedFields.success) {
- throw new DashboardError({
- errorCode: "INVALID_FORM_DATA",
- formErrors: mapObjectToFirstValue(
- validatedFields.error.flatten().fieldErrors
- ),
- });
- }
- const userData = {
- id: validatedFields.data.id ? validatedFields.data.id : undefined,
- name: validatedFields.data.name,
- photoProfile: validatedFields.data.photoProfileUrl ?? "",
- email: validatedFields.data.email,
- };
-
- const passwordHash = await hashPassword(validatedFields.data.password!);
-
- const roles = await db.role.findMany({
- where: {
- code: {
- in: validatedFields.data.roles,
- },
- },
- select: {
- id: true, // Only select the id field
- },
- });
-
- // Database operation
- if (isInsert) {
- if (
- await db.user.findFirst({
- where: {
- email: userData.email,
- },
- })
- ) {
- throw new DashboardError({
- errorCode: "INVALID_FORM_DATA",
- formErrors: {
- email: "The user is already exists",
- },
- });
- }
- await db.user.create({
- data: {
- ...userData,
- passwordHash,
- roles: {
- connect: roles.map((role) => ({ id: role.id })),
- },
- },
- });
- } else {
- await db.user.update({
- where: { id: validatedFields.data.id! },
- data: {
- ...userData,
- roles: {
- set: roles.map((role) => ({ id: role.id })),
- },
- },
- });
- }
-
- // Revalidate the cache
- revalidatePath(".");
-
- // Return success message
- return {
- success: true,
- message: `User ${validatedFields.data.name} has been successfully ${
- isInsert ? "created" : "updated"
- }.`,
- };
- } catch (error) {
- return handleCatch(error);
- }
-}
diff --git a/src/modules/userManagement/actions/upsertUserAction.ts b/src/modules/userManagement/actions/upsertUserAction.ts
new file mode 100644
index 0000000..ab1d076
--- /dev/null
+++ b/src/modules/userManagement/actions/upsertUserAction.ts
@@ -0,0 +1,46 @@
+"use server";
+
+import { revalidatePath } from "next/cache";
+import { UserFormData } from "../formSchemas/userFormSchema";
+import unauthorized from "@/modules/dashboard/utils/unauthorized";
+import handleCatch from "@/modules/dashboard/utils/handleCatch";
+import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
+import checkPermission from "@/modules/auth/utils/checkPermission";
+import upsertUser from "../services/upsertUser";
+
+/**
+ * Upserts a user based on the provided UserFormData.
+ * If the user already exists (determined by `id`), it updates the user; otherwise, it creates a new user.
+ * Authorization checks are performed based on whether it's a create or update operation.
+ *
+ * @param data - The data for creating or updating the user.
+ * @returns An object containing the success status, message, and any errors.
+ */
+export default async function upsertUserAction(
+ data: UserFormData
+): Promise {
+ try {
+ const isInsert = !data.id;
+
+ // Authorization check
+ const permissionType = isInsert ? "users.create" : "users.update";
+ if (!(await checkPermission(permissionType))) {
+ return unauthorized();
+ }
+
+ const user = await upsertUser(data);
+
+ // Revalidate the cache
+ revalidatePath(".");
+
+ // Return success message
+ return {
+ success: true,
+ message: `User ${user.name} has been successfully ${
+ isInsert ? "created" : "updated"
+ }.`,
+ };
+ } catch (error) {
+ return handleCatch(error);
+ }
+}
diff --git a/src/modules/userManagement/errors/UserManagementError.ts b/src/modules/userManagement/errors/UserManagementError.ts
index 269ef27..cf916b2 100644
--- a/src/modules/userManagement/errors/UserManagementError.ts
+++ b/src/modules/userManagement/errors/UserManagementError.ts
@@ -8,6 +8,7 @@ interface UserManagementErrorOptions {
message?: string;
errorCode: (typeof UserManagementErrorCodes)[number] | (string & {});
formErrors?: Record
+ statusCode?: number;
}
export default class UserManagementError extends DashboardError {
@@ -18,6 +19,7 @@ export default class UserManagementError extends DashboardError {
super({
errorCode: options.errorCode,
message: options.message,
+ statusCode: options.statusCode,
});
this.errorCode = options.errorCode;
diff --git a/src/modules/userManagement/modals/UserDeleteModal.tsx b/src/modules/userManagement/modals/UserDeleteModal.tsx
index 56ec82a..0f23463 100644
--- a/src/modules/userManagement/modals/UserDeleteModal.tsx
+++ b/src/modules/userManagement/modals/UserDeleteModal.tsx
@@ -9,7 +9,7 @@ import {
} from "@mantine/core";
import { showNotification } from "@/utils/notifications";
import withServerAction from "@/modules/dashboard/utils/withServerAction";
-import deleteUser from "../actions/deleteUser";
+import deleteUserAction from "../actions/deleteUserAction";
import ClientError from "@/core/error/ClientError";
export interface DeleteModalProps {
@@ -38,7 +38,7 @@ export default function UserDeleteModal(props: DeleteModalProps) {
if (!props.data?.id) return;
setSubmitting(true);
- withServerAction(() => deleteUser(props.data!.id))
+ withServerAction(deleteUserAction, props.data!.id)
.then((response) => {
showNotification(
response.message ?? "User deleted successfully"
diff --git a/src/modules/userManagement/modals/UserFormModal.tsx b/src/modules/userManagement/modals/UserFormModal.tsx
index 582f096..16980ed 100644
--- a/src/modules/userManagement/modals/UserFormModal.tsx
+++ b/src/modules/userManagement/modals/UserFormModal.tsx
@@ -21,9 +21,9 @@ import { TbDeviceFloppy } from "react-icons/tb";
import userFormDataSchema, {
UserFormData,
} from "../formSchemas/userFormSchema";
-import getUserDetailById from "../actions/getUserDetailById";
+import getUserDetailById from "../actions/getUserDetailByIdAction";
import withServerAction from "@/modules/dashboard/utils/withServerAction";
-import upsertUser from "../actions/upsertUser";
+import upsertUserAction from "../actions/upsertUserAction";
import ClientError from "@/core/error/ClientError";
import stringToColorHex from "@/core/utils/stringToColorHex";
import getAllRoles from "@/modules/role/actions/getAllRoles";
@@ -112,7 +112,7 @@ export default function UserFormModal(props: ModalProps) {
const handleSubmit = (values: UserFormData) => {
setSubmitting(true);
- withServerAction(upsertUser, values)
+ withServerAction(upsertUserAction, values)
.then((response) => {
showNotification(response.message!, "success");
closeModal();
diff --git a/src/modules/userManagement/services/deleteUser.ts b/src/modules/userManagement/services/deleteUser.ts
new file mode 100644
index 0000000..8d9e9f8
--- /dev/null
+++ b/src/modules/userManagement/services/deleteUser.ts
@@ -0,0 +1,26 @@
+import getCurrentUser from "@/modules/auth/services/getCurrentUser"
+import unauthorized from "@/modules/dashboard/utils/unauthorized";
+import "server-only"
+import UserManagementError from "../errors/UserManagementError";
+import db from "@/core/db";
+import notFound from "@/modules/dashboard/utils/notFound";
+
+export default async function deleteUser(id: string){
+ const currentUser = await getCurrentUser();
+
+ if (!currentUser) return unauthorized();
+
+ if (currentUser.id !== id) throw new UserManagementError({
+ errorCode: "CANNOT_DELETE_SELF",
+ message: "You cannot delete yourself",
+ statusCode: 403,
+ });
+
+ const user = await db.user.delete({
+ where: { id },
+ });
+
+ if (!user) return notFound({message: "The user does not exists"})
+
+ return true as const;
+}
\ No newline at end of file
diff --git a/src/modules/userManagement/services/getAllUsers.ts b/src/modules/userManagement/services/getAllUsers.ts
new file mode 100644
index 0000000..28bf3be
--- /dev/null
+++ b/src/modules/userManagement/services/getAllUsers.ts
@@ -0,0 +1,29 @@
+import db from "@/core/db";
+import "server-only";
+
+const getAllUsers = async () => {
+ const users = await db.user.findMany({
+ select: {
+ id: true,
+ email: true,
+ photoProfile: true,
+ name: true,
+ roles: {
+ select: {
+ name: true,
+ code: true,
+ },
+ },
+ },
+ });
+
+ const result = users.map((user) => ({
+ ...user,
+ photoUrl: user.photoProfile ?? null,
+ photoProfile: undefined,
+ }));
+
+ return result;
+};
+
+export default getAllUsers;
diff --git a/src/modules/userManagement/services/getUserById.ts b/src/modules/userManagement/services/getUserById.ts
new file mode 100644
index 0000000..ebb7040
--- /dev/null
+++ b/src/modules/userManagement/services/getUserById.ts
@@ -0,0 +1,24 @@
+import db from "@/core/db";
+import notFound from "@/modules/dashboard/utils/notFound";
+
+export default async function getUserById(id: string) {
+ const user = await db.user.findFirst({
+ where: { id },
+ select: {
+ id: true,
+ email: true,
+ name: true,
+ photoProfile: true,
+ roles: {
+ select: {
+ code: true,
+ name: true,
+ },
+ },
+ },
+ });
+
+ if (!user) return notFound({message: "The user does not exists"})
+
+ return user;
+}
diff --git a/src/modules/userManagement/services/upsertUser.ts b/src/modules/userManagement/services/upsertUser.ts
new file mode 100644
index 0000000..2043644
--- /dev/null
+++ b/src/modules/userManagement/services/upsertUser.ts
@@ -0,0 +1,80 @@
+import DashboardError from "@/modules/dashboard/errors/DashboardError";
+import userFormDataSchema, {
+ UserFormData,
+} from "../formSchemas/userFormSchema";
+import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
+import hashPassword from "@/modules/auth/utils/hashPassword";
+import db from "@/core/db";
+import "server-only"
+
+export default async function upsertUser(data: UserFormData) {
+ const isInsert = !data.id;
+
+ // Validate form data
+ const validatedFields = userFormDataSchema.safeParse(data);
+ if (!validatedFields.success) {
+ throw new DashboardError({
+ errorCode: "INVALID_FORM_DATA",
+ formErrors: mapObjectToFirstValue(
+ validatedFields.error.flatten().fieldErrors
+ ),
+ });
+ }
+ const userData = {
+ id: validatedFields.data.id ? validatedFields.data.id : undefined,
+ name: validatedFields.data.name,
+ photoProfile: validatedFields.data.photoProfileUrl ?? "",
+ email: validatedFields.data.email,
+ };
+
+ const passwordHash = await hashPassword(validatedFields.data.password!);
+
+ const roles = await db.role.findMany({
+ where: {
+ code: {
+ in: validatedFields.data.roles,
+ },
+ },
+ select: {
+ id: true, // Only select the id field
+ },
+ });
+
+ // Database operation
+ if (isInsert) {
+ if (
+ await db.user.findFirst({
+ where: {
+ email: userData.email,
+ },
+ })
+ ) {
+ throw new DashboardError({
+ errorCode: "INVALID_FORM_DATA",
+ formErrors: {
+ email: "The user is already exists",
+ },
+ });
+ }
+
+ return await db.user.create({
+ data: {
+ ...userData,
+ passwordHash,
+ roles: {
+ connect: roles.map((role) => ({ id: role.id })),
+ },
+ },
+ });
+ } else {
+ return await db.user.update({
+ where: { id: validatedFields.data.id! },
+ data: {
+ ...userData,
+ roles: {
+ set: roles.map((role) => ({ id: role.id })),
+ },
+ },
+ });
+ }
+}
diff --git a/src/modules/userManagement/tables/UsersTable/UsersTable.tsx b/src/modules/userManagement/tables/UsersTable/UsersTable.tsx
index d3b44c6..9cdd268 100644
--- a/src/modules/userManagement/tables/UsersTable/UsersTable.tsx
+++ b/src/modules/userManagement/tables/UsersTable/UsersTable.tsx
@@ -9,12 +9,12 @@ import UserDeleteModal, {
DeleteModalProps,
} from "../../modals/UserDeleteModal";
import createColumns from "./columns";
-import getAllUsers from "../../actions/getAllUsers";
+import getAllUsers from "../../services/getAllUsers";
import DashboardTable from "@/modules/dashboard/components/DashboardTable";
interface Props {
permissions: Partial;
- userData: Awaited>;
+ data: Awaited>;
}
export default function UsersTable(props: Props) {
@@ -32,11 +32,11 @@ export default function UsersTable(props: Props) {
});
const userData = useMemo(
- () => props.userData.map((data) => ({
+ () => props.data.map((data) => ({
...data,
roles: data.roles.map((x) => x.name),
})),
- [props.userData]
+ [props.data]
);
const table = useReactTable({