Add logged user and user communities endpoint

This commit is contained in:
Aslan 2025-12-31 15:00:18 +01:00
parent ddcc591d12
commit a85330e8cf
7 changed files with 180 additions and 3 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "tether", "name": "tether",
"version": "0.3.3", "version": "0.3.4",
"description": "Communication server using the Nexlink protocol", "description": "Communication server using the Nexlink protocol",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -2,11 +2,13 @@ import { type FastifyInstance } from "fastify";
import * as controller from "./user.js"; import * as controller from "./user.js";
const userRoutes = async (fastify: FastifyInstance) => { const userRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/logged`, controller.getUserLogged);
fastify.get(`/:id`, controller.getUser); fastify.get(`/:id`, controller.getUser);
fastify.post(`/`, controller.postCreateUser); fastify.post(`/`, controller.postCreateUser);
fastify.patch(`/:id`, controller.patchUser); fastify.patch(`/:id`, controller.patchUser);
fastify.delete(`/:id`, controller.deleteUser); fastify.delete(`/:id`, controller.deleteUser);
fastify.get(`/:id/sessions`, controller.getSessions); fastify.get(`/:id/sessions`, controller.getSessions);
fastify.get(`/:id/communities`, controller.getCommunities);
}; };
export { userRoutes }; export { userRoutes };

View file

@ -1,5 +1,13 @@
import type { API_ERROR } from "../errors.js"; import type { API_ERROR } from "../errors.js";
interface IGetLoggedUserResponseError {
error: API_ERROR;
}
interface IGetLoggedUserResponseSuccess {
id: string;
}
interface IGetUserParams { interface IGetUserParams {
id: string; id: string;
} }
@ -92,7 +100,29 @@ interface IGetSessionsResponseSession {
userId: string; 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 { export {
type IGetLoggedUserResponseError,
type IGetLoggedUserResponseSuccess,
type IGetUserParams, type IGetUserParams,
type IGetUserResponseError, type IGetUserResponseError,
type IGetUserResponseSuccess, type IGetUserResponseSuccess,
@ -110,4 +140,8 @@ export {
type IGetSessionsResponseError, type IGetSessionsResponseError,
type IGetSessionsResponseSuccess, type IGetSessionsResponseSuccess,
type IGetSessionsResponseSession, type IGetSessionsResponseSession,
type IGetCommunitiesParams,
type IGetCommunitiesResponseError,
type IGetCommunitiesResponseSuccess,
type IGetCommunitiesResponseCommunity,
}; };

View file

@ -1,5 +1,7 @@
import { type FastifyReply, type FastifyRequest } from "fastify"; import { type FastifyReply, type FastifyRequest } from "fastify";
import type { import type {
IGetLoggedUserResponseError,
IGetLoggedUserResponseSuccess,
IGetUserParams, IGetUserParams,
IGetUserResponseError, IGetUserResponseError,
IGetUserResponseSuccess, IGetUserResponseSuccess,
@ -16,6 +18,9 @@ import type {
IGetSessionsParams, IGetSessionsParams,
IGetSessionsResponseError, IGetSessionsResponseError,
IGetSessionsResponseSuccess, IGetSessionsResponseSuccess,
IGetCommunitiesParams,
IGetCommunitiesResponseError,
IGetCommunitiesResponseSuccess,
} from "./types.js"; } from "./types.js";
import { import {
getUserByIdAuth, getUserByIdAuth,
@ -23,9 +28,27 @@ import {
getUserSessionsByIdAuth, getUserSessionsByIdAuth,
deleteUserByIdAuth, deleteUserByIdAuth,
createUserAuth, createUserAuth,
getUserCommunitiesByIdAuth,
getLoggedUserAuth,
} from "../../services/user/user.js"; } from "../../services/user/user.js";
import { API_ERROR } from "../errors.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 getUser = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IGetUserParams; const { id } = request.params as IGetUserParams;
const authHeader = request.headers["authorization"]; const authHeader = request.headers["authorization"];
@ -160,4 +183,42 @@ const getSessions = async (request: FastifyRequest, reply: FastifyReply) => {
} as IGetSessionsResponseSuccess; } 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,
};

View file

@ -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 { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
import { getDB } from "../../store/store.js"; import { getDB } from "../../store/store.js";
import { API_ERROR } from "../../controllers/errors.js"; import { API_ERROR } from "../../controllers/errors.js";
import type { ICreateUser, IUpdateUser } from "./types.js"; import type { ICreateUser, IUpdateUser } from "./types.js";
const getLoggedUserAuth = async (
authHeader: string | undefined,
): Promise<User | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
if (!authUser) {
return API_ERROR.ACCESS_DENIED;
}
return authUser;
};
const getUserById = async (id: string): Promise<User | null> => { const getUserById = async (id: string): Promise<User | null> => {
return await getDB().user.findUnique({ return await getDB().user.findUnique({
where: { id: id }, where: { id: id },
@ -133,7 +149,41 @@ const getUserSessionsByIdAuth = async (
return sessions; return sessions;
}; };
const getUserCommunitiesById = async (
id: string,
): Promise<Community[] | null> => {
return await getDB().community.findMany({
where: {
members: {
some: {
id: id,
},
},
},
});
};
const getUserCommunitiesByIdAuth = async (
id: string,
authHeader: string | undefined,
): Promise<Community[] | null | API_ERROR.ACCESS_DENIED> => {
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 { export {
getLoggedUserAuth,
getUserById, getUserById,
getUserByIdAuth, getUserByIdAuth,
createUser, createUser,
@ -144,4 +194,6 @@ export {
deleteUserByIdAuth, deleteUserByIdAuth,
getUserSessionsById, getUserSessionsById,
getUserSessionsByIdAuth, getUserSessionsByIdAuth,
getUserCommunitiesById,
getUserCommunitiesByIdAuth,
}; };

View file

@ -23,6 +23,12 @@ test("can register", async () => {
state.userId = response.id; 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 () => { test("shouldn't be able to login", async () => {
const response = await apiPost(`auth/login`, { const response = await apiPost(`auth/login`, {
username: state.username, username: state.username,
@ -48,6 +54,12 @@ test("can login", async () => {
state.token = response.token; 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 () => { test("shouldn't be authorized to get user", async () => {
const response1 = await apiGet(`user/${state.userId}`); const response1 = await apiGet(`user/${state.userId}`);
assert.equal(response1.error, "ACCESS_DENIED"); assert.equal(response1.error, "ACCESS_DENIED");

View file

@ -121,6 +121,22 @@ test("can accept invite", async () => {
assert.equal(responseGet.members.length === 2, true); 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 () => { test("can get invite", async () => {
const response = await apiGet(`invite/${state.inviteId}`); const response = await apiGet(`invite/${state.inviteId}`);