From a85330e8cfd03ee074043261e267d87e912c1e89047a07824132ee847956a747 Mon Sep 17 00:00:00 2001 From: aslan Date: Wed, 31 Dec 2025 15:00:18 +0100 Subject: [PATCH] Add logged user and user communities endpoint --- package.json | 2 +- src/controllers/user/routes.ts | 2 ++ src/controllers/user/types.ts | 34 ++++++++++++++++++ src/controllers/user/user.ts | 63 +++++++++++++++++++++++++++++++++- src/services/user/user.ts | 54 ++++++++++++++++++++++++++++- tests/1.user.test.js | 12 +++++++ tests/2.community.test.js | 16 +++++++++ 7 files changed, 180 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 499416f..10fabb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tether", - "version": "0.3.3", + "version": "0.3.4", "description": "Communication server using the Nexlink protocol", "repository": { "type": "git", diff --git a/src/controllers/user/routes.ts b/src/controllers/user/routes.ts index 23981fe..f95b547 100644 --- a/src/controllers/user/routes.ts +++ b/src/controllers/user/routes.ts @@ -2,11 +2,13 @@ import { type FastifyInstance } from "fastify"; import * as controller from "./user.js"; const userRoutes = async (fastify: FastifyInstance) => { + fastify.get(`/logged`, controller.getUserLogged); fastify.get(`/:id`, controller.getUser); fastify.post(`/`, controller.postCreateUser); fastify.patch(`/:id`, controller.patchUser); fastify.delete(`/:id`, controller.deleteUser); fastify.get(`/:id/sessions`, controller.getSessions); + fastify.get(`/:id/communities`, controller.getCommunities); }; export { userRoutes }; diff --git a/src/controllers/user/types.ts b/src/controllers/user/types.ts index 4e2ccbe..374a273 100644 --- a/src/controllers/user/types.ts +++ b/src/controllers/user/types.ts @@ -1,5 +1,13 @@ import type { API_ERROR } from "../errors.js"; +interface IGetLoggedUserResponseError { + error: API_ERROR; +} + +interface IGetLoggedUserResponseSuccess { + id: string; +} + interface IGetUserParams { id: string; } @@ -92,7 +100,29 @@ interface IGetSessionsResponseSession { userId: string; } +interface IGetCommunitiesParams { + id: string; +} + +interface IGetCommunitiesResponseError { + id: string; + error: API_ERROR; +} + +interface IGetCommunitiesResponseSuccess { + id: string; + communities: IGetCommunitiesResponseCommunity[]; +} + +interface IGetCommunitiesResponseCommunity { + id: string; + name: string; + description: string; +} + export { + type IGetLoggedUserResponseError, + type IGetLoggedUserResponseSuccess, type IGetUserParams, type IGetUserResponseError, type IGetUserResponseSuccess, @@ -110,4 +140,8 @@ export { type IGetSessionsResponseError, type IGetSessionsResponseSuccess, type IGetSessionsResponseSession, + type IGetCommunitiesParams, + type IGetCommunitiesResponseError, + type IGetCommunitiesResponseSuccess, + type IGetCommunitiesResponseCommunity, }; diff --git a/src/controllers/user/user.ts b/src/controllers/user/user.ts index 5f01936..38712c9 100644 --- a/src/controllers/user/user.ts +++ b/src/controllers/user/user.ts @@ -1,5 +1,7 @@ import { type FastifyReply, type FastifyRequest } from "fastify"; import type { + IGetLoggedUserResponseError, + IGetLoggedUserResponseSuccess, IGetUserParams, IGetUserResponseError, IGetUserResponseSuccess, @@ -16,6 +18,9 @@ import type { IGetSessionsParams, IGetSessionsResponseError, IGetSessionsResponseSuccess, + IGetCommunitiesParams, + IGetCommunitiesResponseError, + IGetCommunitiesResponseSuccess, } from "./types.js"; import { getUserByIdAuth, @@ -23,9 +28,27 @@ import { getUserSessionsByIdAuth, deleteUserByIdAuth, createUserAuth, + getUserCommunitiesByIdAuth, + getLoggedUserAuth, } from "../../services/user/user.js"; import { API_ERROR } from "../errors.js"; +const getUserLogged = async (request: FastifyRequest, reply: FastifyReply) => { + const authHeader = request.headers["authorization"]; + + const user = await getLoggedUserAuth(authHeader); + if (user === API_ERROR.ACCESS_DENIED) { + reply.status(404); + return { + error: API_ERROR.ACCESS_DENIED, + } as IGetLoggedUserResponseError; + } + + return { + id: user.id, + } as IGetLoggedUserResponseSuccess; +}; + const getUser = async (request: FastifyRequest, reply: FastifyReply) => { const { id } = request.params as IGetUserParams; const authHeader = request.headers["authorization"]; @@ -160,4 +183,42 @@ const getSessions = async (request: FastifyRequest, reply: FastifyReply) => { } as IGetSessionsResponseSuccess; }; -export { getUser, postCreateUser, patchUser, deleteUser, getSessions }; +const getCommunities = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IGetCommunitiesParams; + const authHeader = request.headers["authorization"]; + + const communities = await getUserCommunitiesByIdAuth(id, authHeader); + if (!communities) { + reply.status(404); + return { + id: id, + error: API_ERROR.NOT_FOUND, + } as IGetCommunitiesResponseError; + } + if (communities === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + id: id, + error: API_ERROR.ACCESS_DENIED, + } as IGetCommunitiesResponseError; + } + + return { + id: id, + communities: communities.map((community) => ({ + id: community.id, + name: community.name, + description: community.description, + })), + } as IGetCommunitiesResponseSuccess; +}; + +export { + getUserLogged, + getUser, + postCreateUser, + patchUser, + deleteUser, + getSessions, + getCommunities, +}; diff --git a/src/services/user/user.ts b/src/services/user/user.ts index 761455f..e322fd7 100644 --- a/src/services/user/user.ts +++ b/src/services/user/user.ts @@ -1,9 +1,25 @@ -import type { User, Session } from "../../generated/prisma/client.js"; +import type { + User, + Session, + Community, +} from "../../generated/prisma/client.js"; import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js"; import { getDB } from "../../store/store.js"; import { API_ERROR } from "../../controllers/errors.js"; import type { ICreateUser, IUpdateUser } from "./types.js"; +const getLoggedUserAuth = async ( + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + + if (!authUser) { + return API_ERROR.ACCESS_DENIED; + } + + return authUser; +}; + const getUserById = async (id: string): Promise => { return await getDB().user.findUnique({ where: { id: id }, @@ -133,7 +149,41 @@ const getUserSessionsByIdAuth = async ( return sessions; }; +const getUserCommunitiesById = async ( + id: string, +): Promise => { + return await getDB().community.findMany({ + where: { + members: { + some: { + id: id, + }, + }, + }, + }); +}; + +const getUserCommunitiesByIdAuth = async ( + id: string, + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + const user = await getUserById(id); + const communities = await getUserCommunitiesById(id); + + if ( + !(await isUserOwnerOrAdmin(authUser, { + user: user, + })) + ) { + return API_ERROR.ACCESS_DENIED; + } + + return communities; +}; + export { + getLoggedUserAuth, getUserById, getUserByIdAuth, createUser, @@ -144,4 +194,6 @@ export { deleteUserByIdAuth, getUserSessionsById, getUserSessionsByIdAuth, + getUserCommunitiesById, + getUserCommunitiesByIdAuth, }; diff --git a/tests/1.user.test.js b/tests/1.user.test.js index c4f65be..2e99532 100644 --- a/tests/1.user.test.js +++ b/tests/1.user.test.js @@ -23,6 +23,12 @@ test("can register", async () => { state.userId = response.id; }); +test("shouldn't be authorized to get logged user", async () => { + const response = await apiGet(`user/logged`); + + assert.equal(response.error, "ACCESS_DENIED"); +}); + test("shouldn't be able to login", async () => { const response = await apiPost(`auth/login`, { username: state.username, @@ -48,6 +54,12 @@ test("can login", async () => { state.token = response.token; }); +test("can get logged user", async () => { + const response = await apiGet(`user/logged`, state.token); + + assert.equal(response.id, state.userId); +}); + test("shouldn't be authorized to get user", async () => { const response1 = await apiGet(`user/${state.userId}`); assert.equal(response1.error, "ACCESS_DENIED"); diff --git a/tests/2.community.test.js b/tests/2.community.test.js index 2faeef5..cd9b914 100644 --- a/tests/2.community.test.js +++ b/tests/2.community.test.js @@ -121,6 +121,22 @@ test("can accept invite", async () => { assert.equal(responseGet.members.length === 2, true); }); +test("can get user communities", async () => { + const response = await apiGet( + `user/${state.userId2}/communities`, + state.token2, + ); + + assert.equal(response.id, state.userId2); + assert.equal(response.communities.length === 1, true); + assert.equal(response.communities[0].id, state.communityId); + assert.equal(response.communities[0].name, state.communityName); + assert.equal( + response.communities[0].description, + state.communityDescription, + ); +}); + test("can get invite", async () => { const response = await apiGet(`invite/${state.inviteId}`);