diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js index 7af1e99c1..db017c890 100644 --- a/backend/controllers/project.controller.js +++ b/backend/controllers/project.controller.js @@ -1,4 +1,4 @@ -const { Project } = require('../models'); +const { Project, User } = require('../models'); const ProjectController = {}; @@ -66,4 +66,40 @@ ProjectController.destroy = async function (req, res) { } }; +ProjectController.updateManagedByUsers = async function (req, res) { + const { ProjectId } = req.params; + const { action, userId } = req.body; // action - 'add' or 'remove' + + try { + // Update project's managedByUsers and the user's managedProjects + const project = await Project.findById(ProjectId); + let managedByUsers = project.managedByUsers || []; + + const user = await User.findById(userId); + let managedProjects = user.managedProjects || []; + + if (action === 'add') { + managedByUsers = [...new Set([...managedByUsers, userId])]; + managedProjects = [...new Set([...managedProjects, ProjectId])]; + } else { + // remove case + managedByUsers = managedByUsers.filter((id) => id !== userId); + managedProjects = managedProjects.filter((id) => id !== ProjectId); + } + + // Update project's managedByUsers + project.managedByUsers = managedByUsers; + await project.save({ validateBeforeSave: false }); + + // Update user's managedProjects + user.managedProjects = managedProjects; + await user.save({ validateBeforeSave: false }); + + return res.status(200).send({ project, user }); + } catch (err) { + console.log(err); + return res.sendStatus(400); + } +}; + module.exports = ProjectController; diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js index 2543b3ad2..267ab5e68 100644 --- a/backend/controllers/user.controller.js +++ b/backend/controllers/user.controller.js @@ -266,4 +266,47 @@ UserController.logout = async function (req, res) { return res.clearCookie('token').status(200).send('Successfully logged out.'); }; +// Update user's managedProjects +UserController.updateManagedProjects = async function (req, res) { + const { headers } = req; + const { UserId } = req.params; + const { action, projectId } = req.body; // action - 'add' or 'remove' + // console.log('action:', action, 'projectId:', projectId); + + if (headers['x-customrequired-header'] !== expectedHeader) { + return res.sendStatus(403); + } + + try { + // Update user's managedProjects and the project's managedByUsers + const user = await User.findById(UserId); + let managedProjects = user.managedProjects || []; + + const project = await Project.findById(projectId); + let managedByUsers = project.managedByUsers || []; + + if (action === 'add') { + managedProjects = [...managedProjects, projectId]; + managedByUsers = [...managedByUsers, UserId]; + } else { + // remove case + managedProjects = managedProjects.filter((id) => id !== projectId); + managedByUsers = managedByUsers.filter((id) => id !== UserId); + } + + // Update user's managedProjects + user.managedProjects = managedProjects; + await user.save({ validateBeforeSave: false }); + + // Update project's managedByUsers + project.managedByUsers = managedByUsers; + await project.save({ validateBeforeSave: false }); + + return res.status(200).send({ user, project }); + } catch (err) { + console.log(err); + return res.sendStatus(400); + } +}; + module.exports = UserController; diff --git a/backend/routers/projects.router.js b/backend/routers/projects.router.js index 16aed3728..19acaa43d 100644 --- a/backend/routers/projects.router.js +++ b/backend/routers/projects.router.js @@ -16,6 +16,7 @@ router.get('/:ProjectId', ProjectController.project_by_id); router.put('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update); -router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update); +// Update project's managedByUsers in db +router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.updateManagedByUsers); module.exports = router; diff --git a/backend/routers/projects.router.test.js b/backend/routers/projects.router.test.js index c5c9f30c0..100e59635 100644 --- a/backend/routers/projects.router.test.js +++ b/backend/routers/projects.router.test.js @@ -158,7 +158,7 @@ describe('Unit testing for Projects router', () => { // Tests expect(ProjectController.create).toHaveBeenCalledWith( - expect.objectContaining({ body: newProject }), // Check if newProject in body is parsed + expect.objectContaining({ body: newProject }), // Check if newProject in body is parsed expect.anything(), // Mock response expect.anything(), // Mock next ); @@ -234,7 +234,7 @@ describe('Unit testing for Projects router', () => { }); const updatedProject = { - id: '1', + id: 'projectId1', name: 'updated project1', description: 'updated testing', githubIdentifier: 'gitHubTest3', @@ -251,7 +251,7 @@ describe('Unit testing for Projects router', () => { lookingDescription: 'n/a', recruitingCategories: ['n/a'], partners: ['n/a'], - managedByUsers: ['n/a'], + managedByUsers: ['userId1'], }; const ProjectId = updatedProject.id; @@ -274,7 +274,7 @@ describe('Unit testing for Projects router', () => { // Tests expect(ProjectController.update).toHaveBeenCalledWith( - expect.objectContaining({ params: { ProjectId }}), // Check if ProjectId is parsed from params + expect.objectContaining({ params: { ProjectId } }), // Check if ProjectId is parsed from params expect.anything(), // Mock response expect.anything(), // Mock next ); @@ -284,5 +284,85 @@ describe('Unit testing for Projects router', () => { // Marks completion of tests done(); }); + + const updatedUser = { + id: 'userId1', + name: 'Updated User', + email: 'mockuser@example.com', + managedProjects: ['projectId1'], + }; + + const userId = updatedUser.id; + + it("should add to the project's managedByUsers and the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { + // Mock ProjectController.updateManagedByUsers method when this route is called + ProjectController.updateManagedByUsers.mockImplementationOnce((req, res) => { + res.status(200).send({ project: updatedProject, user: updatedUser }); + }); + + // Mock PUT API call + const response = await request + .patch(`/api/projects/${ProjectId}`) + .send({ action: 'add', userId }); + + // Middlware assertions + expect(mockVerifyCookie).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + expect.any(Function), + ); + + // Tests + expect(ProjectController.updateManagedByUsers).toHaveBeenCalledWith( + expect.objectContaining({ + params: { ProjectId }, + body: { action: 'add', userId }, + }), // Check if ProjectId is parsed from params + expect.anything(), // Mock response + expect.anything(), // Mock next + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ project: updatedProject, user: updatedUser }); + + // Marks completion of tests + done(); + }); + + it("should remove user from the project's managedByUsers and remove project from the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { + updatedProject.managedByUsers = []; + updatedUser.managedProjects = []; + + // Mock ProjectController.updateManagedByUsers method when this route is called + ProjectController.updateManagedByUsers.mockImplementationOnce((req, res) => { + res.status(200).send({ project: updatedProject, user: updatedUser }); + }); + + // Mock PUT API call + const response = await request + .patch(`/api/projects/${ProjectId}`) + .send({ action: 'remove', userId }); + + // Middlware assertions + expect(mockVerifyCookie).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + expect.any(Function), + ); + + // Tests + expect(ProjectController.updateManagedByUsers).toHaveBeenCalledWith( + expect.objectContaining({ + params: { ProjectId }, + body: { action: 'remove', userId }, + }), // Check if ProjectId is parsed from params + expect.anything(), // Mock response + expect.anything(), // Mock next + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ project: updatedProject, user: updatedUser }); + + // Marks completion of tests + done(); + }); }); }); diff --git a/backend/routers/users.router.js b/backend/routers/users.router.js index a20da8f5a..832c9edf0 100644 --- a/backend/routers/users.router.js +++ b/backend/routers/users.router.js @@ -16,6 +16,9 @@ router.get('/:UserId', UserController.user_by_id); router.patch('/:UserId', UserController.update); +// Update user projects in db +router.patch('/:UserId/managedProjects', UserController.updateManagedProjects); + router.delete('/:UserId', UserController.delete); module.exports = router; diff --git a/backend/routers/users.router.test.js b/backend/routers/users.router.test.js index b8f65e201..9fb96062f 100644 --- a/backend/routers/users.router.test.js +++ b/backend/routers/users.router.test.js @@ -15,206 +15,254 @@ testapp.use('/api/users', usersRouter); const request = supertest(testapp); describe('Unit Tests for userRouter', () => { - // Mocked user data - const mockUser = { - name: { - firstName: 'test', - lastName: 'user', - }, - email: 'newtest@test.com', - }; - const mockId = '12345'; - const mockUpdatedEmail = { - email: 'newtest@test.com', - }; + // Mocked user data + const mockUser = { + id: 'userId1', + name: { + firstName: 'test', + lastName: 'user', + }, + email: 'newtest@test.com', + accessLevel: 'admin', + managedProjects: ['projectId1'], + }; + const mockUserId = mockUser.id; + const mockUpdatedEmail = mockUser.email; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('CREATE', () => { + it('should create a User through the UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.create.mockImplementationOnce((req, res) => { + return res.status(201).send(mockUser); + }); - afterEach(() => { - jest.clearAllMocks(); + //Functionality + //Post mockUser to CREATE API Endpoint + const response = await request.post('/api/users/').send(mockUser); + + //Test + expect(UserController.create).toHaveBeenCalledWith( + expect.objectContaining({ body: mockUser }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(201); + expect(response.body).toEqual(mockUser); + + done(); }); + }); + + describe('READ', () => { + it('should get a list of Users with with GET to /api/users/ through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.user_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); - describe('CREATE', () => { - it('should create a User through the UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.create.mockImplementationOnce( - (req, res) => { return res.status(201).send(mockUser) } - ); - - //Functionality - //Post mockUser to CREATE API Endpoint - const response = await request - .post('/api/users/') - .send(mockUser); - - //Test - expect(UserController.create).toHaveBeenCalledWith( - expect.objectContaining({body: mockUser}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(201); - expect(response.body).toEqual(mockUser); - - done(); - }); + //Functionality + //Get list of all users from READ API Endpoint + const response = await request.get('/api/users/'); + + //Test + expect(UserController.user_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); }); - - describe('READ', () => { - it('should get a list of Users with with GET to /api/users/ through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get list of all users from READ API Endpoint - const response = await request - .get('/api/users/'); - - //Test - expect(UserController.user_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a specific User by param with GET to /api/users?email= through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a user with a specific email using a query param to READ API Endpoint - const response = await request - .get('/api/users?email=newtest@test.com'); - - //Test - expect(UserController.user_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a list of Users with accessLevel of admin or superadmin with GET to /api/users/admins through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.admin_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a list of admins and superadmins from READ API Endpoint for admins - const response = await request - .get('/api/users/admins'); - - //Test - expect(UserController.admin_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a list of Users with the ability to manage projects with GET to /api/users/projectManagers through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.projectLead_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a list of project leads and admins from READ API Endpoint for project leads - const response = await request - .get('/api/users/projectManagers'); - - //Test - expect(UserController.projectLead_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a specific User by UserId with GET to /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_by_id.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Functionality - //Get a specific user from READ API Endpoint for specific UUIDs - const response = await request - .get(`/api/users/${mockId}`); - - //Test - expect(UserController.user_by_id).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a specific User by param with GET to /api/users?email= through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.user_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a user with a specific email using a query param to READ API Endpoint + const response = await request.get('/api/users?email=newtest@test.com'); + + //Test + expect(UserController.user_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); }); - - describe('UPDATE', () => { - it('should update a User with PATCH to /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.update.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Functionality - //Patch a user with a specific id by sending new user data to UPDATE API Endpoint - const response = await request - .patch(`/api/users/${mockId}`) - .send(mockUpdatedEmail); - - //Test - expect(UserController.update).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a list of Users with accessLevel of admin or superadmin with GET to /api/users/admins through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.admin_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a list of admins and superadmins from READ API Endpoint for admins + const response = await request.get('/api/users/admins'); + + //Test + expect(UserController.admin_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); + }); + + it('should get a list of Users with the ability to manage projects with GET to /api/users/projectManagers through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.projectManager_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a list of project leads and admins from READ API Endpoint for project leads + const response = await request.get('/api/users/projectManagers'); + + //Test + expect(UserController.projectManager_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); }); - - describe('DELETE', () => { - it('should delete a specific user by Id with DELETE /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.delete.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Delete user with a specific id via a request to DELETE API Endpoint - const response = await request - .delete(`/api/users/${mockId}`) - .send(mockUpdatedEmail); - - //Test - expect(UserController.delete).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a specific User by UserId with GET to /api/users/:UserId through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.user_by_id.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Functionality + //Get a specific user from READ API Endpoint for specific UUIDs + const response = await request.get(`/api/users/${mockUserId}`); + + //Test + expect(UserController.user_by_id).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockUserId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); + }); + }); + + describe('UPDATE', () => { + it('should update a User with PATCH to /api/users/:UserId through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.update.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Functionality + //Patch a user with a specific id by sending new user data to UPDATE API Endpoint + const response = await request.patch(`/api/users/${mockUserId}`).send(mockUpdatedEmail); + + //Test + expect(UserController.update).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockUserId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); + }); + + // Create mock project and add userId to managedByUsers + const mockProject = { + id: 'projectId1', + name: 'Test Project', + managedByUsers: [mockUserId], + }; + const projectId = mockProject.id; + + it("should add projectId to user's managedProjects and userId to project's managedByUsers with PATCH /api/users/:UserId/managedProjects", async (done) => { + // Mock the response of UserController.updateManagedProjects + UserController.updateManagedProjects.mockImplementationOnce((req, res) => { + return res.status(200).send({ user: mockUser, project: mockProject }); + }); + + // Send PATCH request to update managedProjects + const response = await request.patch(`/api/users/${mockUserId}/managedProjects`).send({ + action: 'add', + projectId: projectId, + }); + + // Tests + expect(UserController.updateManagedProjects).toHaveBeenCalledWith( + expect.objectContaining({ + params: { UserId: mockUserId }, + body: { action: 'add', projectId }, + }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ user: mockUser, project: mockProject }); + + done(); + }); + + it("should remove projectId in user's managedProjects and userId in project's managedByUsers with PATCH /api/users/:UserId/managedProjects", async (done) => { + // Remove projectId and userId from fields + mockProject.managedByUsers = []; + mockUser.managedProjects = []; + + // Mock the response of UserController.updateManagedProjects + UserController.updateManagedProjects.mockImplementationOnce((req, res) => { + return res.status(200).send({ user: mockUser, project: mockProject }); + }); + + // Send PATCH request to update managedProjects + const response = await request.patch(`/api/users/${mockUserId}/managedProjects`).send({ + action: 'remove', + projectId: projectId, + }); + + // Tests + expect(UserController.updateManagedProjects).toHaveBeenCalledWith( + expect.objectContaining({ + params: { UserId: mockUserId }, + body: { action: 'remove', projectId }, + }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ user: mockUser, project: mockProject }); + + done(); + }); + }); + + describe('DELETE', () => { + it('should delete a specific user by Id with DELETE /api/users/:UserId through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.delete.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Delete user with a specific id via a request to DELETE API Endpoint + const response = await request.delete(`/api/users/${mockUserId}`).send(mockUpdatedEmail); + + //Test + expect(UserController.delete).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockUserId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); }); -}); \ No newline at end of file + }); +}); diff --git a/client/src/api/ProjectApiService.js b/client/src/api/ProjectApiService.js index b2e692462..80dfc8523 100644 --- a/client/src/api/ProjectApiService.js +++ b/client/src/api/ProjectApiService.js @@ -84,6 +84,27 @@ class ProjectApiService { } } + // update managedByUsers in Project + async updateManagedByUsers(projectId, userId, action) { + const url = `${this.baseProjectUrl}${projectId}`; + const requestOptions = { + method: 'PATCH', + headers: this.headers, + body: JSON.stringify({ action, userId }), + }; + + try { + const response = await fetch(url, requestOptions); + const resJson = await response.json(); + console.log(resJson); + return resJson; + } catch (error) { + console.log(`update project error: `, error); + alert('Server not responding. Please try again.'); + return undefined; + } + } + async fetchPMProjects(projects) { const requestOptions = { headers: this.headers, diff --git a/client/src/api/UserApiService.js b/client/src/api/UserApiService.js index 3ef938f02..0c4e115d4 100644 --- a/client/src/api/UserApiService.js +++ b/client/src/api/UserApiService.js @@ -51,13 +51,13 @@ class UserApiService { } // Updates user projects in db - async updateUserDbProjects(userToEdit, managedProjects) { + async updateUserDbProjects(userToEdit, projectId, action) { // eslint-disable-next-line no-underscore-dangle - const url = `${this.baseUserUrl}${userToEdit._id}`; + const url = `${this.baseUserUrl}${userToEdit._id}/managedProjects`; const requestOptions = { method: 'PATCH', headers: this.headers, - body: JSON.stringify({ managedProjects }), + body: JSON.stringify({ action, projectId }), }; try { diff --git a/client/src/components/user-admin/EditUsers.jsx b/client/src/components/user-admin/EditUsers.jsx index 825ee23dd..e5d48075d 100644 --- a/client/src/components/user-admin/EditUsers.jsx +++ b/client/src/components/user-admin/EditUsers.jsx @@ -61,7 +61,8 @@ const EditUsers = ({ !userManagedProjects.includes(projectValue) ) { const newProjects = [...userManagedProjects, projectValue]; - updateUserDb(userToEdit, newProjects); + updateUserDb(userToEdit, projectValue, 'add'); + // updateUserDb(userToEdit, newProjects); setUserManagedProjects(newProjects); setProjectValue(''); } else { @@ -74,7 +75,8 @@ const EditUsers = ({ const newProjects = userManagedProjects.filter( (p) => p !== projectToRemove ); - updateUserDb(userToEdit, newProjects); + updateUserDb(userToEdit, projectToRemove, 'remove'); + // updateUserDb(userToEdit, newProjects); setUserManagedProjects(newProjects); } }; diff --git a/client/src/pages/UserAdmin.jsx b/client/src/pages/UserAdmin.jsx index 460f53aa7..586f3252e 100644 --- a/client/src/pages/UserAdmin.jsx +++ b/client/src/pages/UserAdmin.jsx @@ -20,8 +20,8 @@ const UserAdmin = () => { }, [userApiService]); const updateUserDb = useCallback( - async (user, managedProjects) => { - await userApiService.updateUserDbProjects(user, managedProjects); + async (user, projectId, action) => { + await userApiService.updateUserDbProjects(user, projectId, action); fetchUsers(); }, [userApiService, fetchUsers]