From 50348cc4942dd0131bcc2961a23d92d340ca1e5cc732b0a7a370a3a5acdbbbdd Mon Sep 17 00:00:00 2001 From: aslan Date: Sun, 28 Dec 2025 13:35:12 +0100 Subject: [PATCH] Add role assign/unassign --- package.json | 2 +- src/controllers/role/role.ts | 62 ++++++++++++++++++++++- src/controllers/role/routes.ts | 2 + src/controllers/role/types.ts | 48 ++++++++++++++++++ src/services/role/role.ts | 91 +++++++++++++++++++++++++++++++++- tests/2.community.test.js | 40 ++++++++++++--- 6 files changed, 235 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5802d67..3946f53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tether", - "version": "0.3.0", + "version": "0.3.1", "description": "Communication server using the Nexlink protocol", "repository": { "type": "git", diff --git a/src/controllers/role/role.ts b/src/controllers/role/role.ts index ccab7f6..2281cce 100644 --- a/src/controllers/role/role.ts +++ b/src/controllers/role/role.ts @@ -6,8 +6,21 @@ import type { IPostCreateRoleRequest, IPostCreateRoleResponseError, IPostCreateRoleResponseSuccess, + IPostAssignRoleParams, + IPostAssignRoleRequest, + IPostAssignRoleResponseError, + IPostAssignRoleResponseSuccess, + IPostUnassignRoleParams, + IPostUnassignRoleRequest, + IPostUnassignRoleResponseError, + IPostUnassignRoleResponseSuccess, } from "./types.js"; -import { createRoleAuth, getRoleByIdAuth } from "../../services/role/role.js"; +import { + assignRoleByIdAuth, + createRoleAuth, + getRoleByIdAuth, + unassignRoleByIdAuth, +} from "../../services/role/role.js"; import { API_ERROR } from "../errors.js"; const getRole = async (request: FastifyRequest, reply: FastifyReply) => { @@ -57,4 +70,49 @@ const postCreateRole = async (request: FastifyRequest, reply: FastifyReply) => { } as IPostCreateRoleResponseSuccess; }; -export { getRole, postCreateRole }; +const postAssignRole = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IPostAssignRoleParams; + const { userId } = request.body as IPostAssignRoleRequest; + const authHeader = request.headers["authorization"]; + + const role = await assignRoleByIdAuth(id, userId, authHeader); + if (role === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + error: API_ERROR.ACCESS_DENIED, + } as IPostAssignRoleResponseError; + } + + return { + id: role.id, + name: role.name, + communityId: role.communityId, + userId: userId, + } as IPostAssignRoleResponseSuccess; +}; + +const postUnassignRole = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const { id } = request.params as IPostUnassignRoleParams; + const { userId } = request.body as IPostUnassignRoleRequest; + const authHeader = request.headers["authorization"]; + + const role = await unassignRoleByIdAuth(id, userId, authHeader); + if (role === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + error: API_ERROR.ACCESS_DENIED, + } as IPostUnassignRoleResponseError; + } + + return { + id: role.id, + name: role.name, + communityId: role.communityId, + userId: userId, + } as IPostUnassignRoleResponseSuccess; +}; + +export { getRole, postCreateRole, postAssignRole, postUnassignRole }; diff --git a/src/controllers/role/routes.ts b/src/controllers/role/routes.ts index 8cbafad..3a57146 100644 --- a/src/controllers/role/routes.ts +++ b/src/controllers/role/routes.ts @@ -4,6 +4,8 @@ import * as controller from "./role.js"; const roleRoutes = async (fastify: FastifyInstance) => { fastify.get(`/:id`, controller.getRole); fastify.post(`/`, controller.postCreateRole); + fastify.post(`/:id/assign`, controller.postAssignRole); + fastify.post(`/:id/unassign`, controller.postUnassignRole); }; export { roleRoutes }; diff --git a/src/controllers/role/types.ts b/src/controllers/role/types.ts index 4c352f5..a9a0e2e 100644 --- a/src/controllers/role/types.ts +++ b/src/controllers/role/types.ts @@ -34,6 +34,46 @@ interface IPostCreateRoleResponseSuccess { communityId: string; } +interface IPostAssignRoleParams { + id: string; +} + +interface IPostAssignRoleRequest { + userId: string; +} + +interface IPostAssignRoleResponseError { + id: string; + error: API_ERROR; +} + +interface IPostAssignRoleResponseSuccess { + id: string; + name: string; + communityId: string; + userId: string; +} + +interface IPostUnassignRoleParams { + id: string; +} + +interface IPostUnassignRoleRequest { + userId: string; +} + +interface IPostUnassignRoleResponseError { + id: string; + error: API_ERROR; +} + +interface IPostUnassignRoleResponseSuccess { + id: string; + name: string; + communityId: string; + userId: string; +} + export { type IGetRoleParams, type IGetRoleResponseError, @@ -41,4 +81,12 @@ export { type IPostCreateRoleRequest, type IPostCreateRoleResponseError, type IPostCreateRoleResponseSuccess, + type IPostAssignRoleParams, + type IPostAssignRoleRequest, + type IPostAssignRoleResponseError, + type IPostAssignRoleResponseSuccess, + type IPostUnassignRoleParams, + type IPostUnassignRoleRequest, + type IPostUnassignRoleResponseError, + type IPostUnassignRoleResponseSuccess, }; diff --git a/src/services/role/role.ts b/src/services/role/role.ts index 67d6553..ec1267a 100644 --- a/src/services/role/role.ts +++ b/src/services/role/role.ts @@ -67,4 +67,93 @@ const createRoleAuth = async ( return await createRole(create); }; -export { getRoleById, getRoleByIdAuth, createRole, createRoleAuth }; +const assignRoleById = async (id: string, userId: string): Promise => { + return await getDB().role.update({ + where: { + id: id, + }, + data: { + users: { + connect: { + id: userId, + }, + }, + }, + }); +}; + +const assignRoleByIdAuth = async ( + id: string, + userId: string, + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + const role = await getRoleById(id); + const community = await getCommunityById(role?.communityId ?? ""); + + if ( + !(await isUserAllowed( + authUser, + { + community: community, + }, + community, + [PERMISSION.ROLES_MANAGE], + )) + ) { + return API_ERROR.ACCESS_DENIED; + } + + return await assignRoleById(id, userId); +}; + +const unassignRoleById = async (id: string, userId: string): Promise => { + return await getDB().role.update({ + where: { + id: id, + }, + data: { + users: { + disconnect: { + id: userId, + }, + }, + }, + }); +}; + +const unassignRoleByIdAuth = async ( + id: string, + userId: string, + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + const role = await getRoleById(id); + const community = await getCommunityById(role?.communityId ?? ""); + + if ( + !(await isUserAllowed( + authUser, + { + community: community, + }, + community, + [PERMISSION.ROLES_MANAGE], + )) + ) { + return API_ERROR.ACCESS_DENIED; + } + + return await unassignRoleById(id, userId); +}; + +export { + getRoleById, + getRoleByIdAuth, + createRole, + createRoleAuth, + assignRoleById, + assignRoleByIdAuth, + unassignRoleById, + unassignRoleByIdAuth, +}; diff --git a/tests/2.community.test.js b/tests/2.community.test.js index 1044063..b35f41a 100644 --- a/tests/2.community.test.js +++ b/tests/2.community.test.js @@ -200,14 +200,18 @@ test("can create role", async () => { }); test("can assign role to user", async () => { - const response = await apiGet( - `community/${state.communityId}/members`, - state.token2, + const response = await apiPost( + `role/${state.roleId}/assign`, + { + userId: state.userId2, + }, + state.token1, ); - assert.equal(response.id, state.communityId); - assert.equal(response.name, state.communityName); - assert.equal(response.members.length === 2, true); + assert.equal(response.id, state.roleId); + assert.equal(response.name, state.roleName); + assert.equal(response.communityId, state.communityId); + assert.equal(response.userId, state.userId2); }); test("can get members", async () => { @@ -242,3 +246,27 @@ test("can get roles", async () => { assert.equal(response.name, state.communityName); assert.equal(response.roles.length === 1, true); }); + +test("can unassign role from user", async () => { + const response = await apiPost( + `role/${state.roleId}/unassign`, + { + userId: state.userId2, + }, + state.token1, + ); + + assert.equal(response.id, state.roleId); + assert.equal(response.name, state.roleName); + assert.equal(response.communityId, state.communityId); + assert.equal(response.userId, state.userId2); +}); + +test("shouldn't be able to get channels 2", async () => { + const response = await apiGet( + `community/${state.communityId}/channels`, + state.token2, + ); + + assert.equal(response.error, "ACCESS_DENIED"); +});