From cae53fab6192023f57728d359d4beb9dab5e79627aafc81ba617fd0344b27841 Mon Sep 17 00:00:00 2001 From: aslan Date: Sat, 27 Dec 2025 00:18:15 +0100 Subject: [PATCH] Add more services and auth --- src/controllers/community/community.ts | 61 ++++++++++++++--- src/controllers/community/routes.ts | 1 + src/controllers/community/types.ts | 36 ++++++++-- src/controllers/user/routes.ts | 1 + src/controllers/user/types.ts | 55 +++++++++++---- src/controllers/user/user.ts | 95 ++++++++++++++++++++------ src/services/community/community.ts | 44 +++++++++++- src/services/community/index.ts | 1 + src/services/community/types.ts | 6 ++ src/services/session/session.ts | 4 +- src/services/user/index.ts | 1 + src/services/user/types.ts | 6 ++ src/services/user/user.ts | 77 +++++++++++++++++++-- 13 files changed, 331 insertions(+), 57 deletions(-) create mode 100644 src/services/community/types.ts create mode 100644 src/services/user/types.ts diff --git a/src/controllers/community/community.ts b/src/controllers/community/community.ts index 8f1188d..8ad2732 100644 --- a/src/controllers/community/community.ts +++ b/src/controllers/community/community.ts @@ -1,20 +1,29 @@ import { type FastifyReply, type FastifyRequest } from "fastify"; import type { - ICommunityParams, - ICommunityResponseError, - ICommunityResponseSuccess, + IGetCommunityParams, + IGetCommunityResponseError, + IGetCommunityResponseSuccess, + IPatchCommunityParams, + IPatchCommunityRequest, + IPatchCommunityResponseError, + IPatchCommunityResponseSuccess, } from "./types.js"; -import { getCommunityById } from "../../services/community/community.js"; +import { + getCommunityById, + updateCommunityByIdAuth, +} from "../../services/community/community.js"; +import { API_ERROR } from "../errors.js"; -const getCommunity = async (request: FastifyRequest, _reply: FastifyReply) => { - const { id } = request.params as ICommunityParams; +const getCommunity = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IGetCommunityParams; const community = await getCommunityById(id); if (!community) { + reply.status(404); return { id: id, - error: "community does not exist", - } as ICommunityResponseError; + error: API_ERROR.NOT_FOUND, + } as IGetCommunityResponseError; } return { @@ -22,7 +31,39 @@ const getCommunity = async (request: FastifyRequest, _reply: FastifyReply) => { name: community.name, description: community.description, creationDate: community.creationDate.getTime(), - } as ICommunityResponseSuccess; + } as IGetCommunityResponseSuccess; }; -export { getCommunity }; +const patchCommunity = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IPatchCommunityParams; + const patchCommunityRequest = request.body as IPatchCommunityRequest; + const authHeader = request.headers["authorization"]; + + const community = await updateCommunityByIdAuth( + id, + patchCommunityRequest, + authHeader, + ); + if (!community) { + reply.status(404); + return { + id: id, + error: API_ERROR.NOT_FOUND, + } as IPatchCommunityResponseError; + } + if (community === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + id: id, + error: API_ERROR.ACCESS_DENIED, + } as IPatchCommunityResponseError; + } + + return { + id: community.id, + name: community.name, + description: community.description, + } as IPatchCommunityResponseSuccess; +}; + +export { getCommunity, patchCommunity }; diff --git a/src/controllers/community/routes.ts b/src/controllers/community/routes.ts index 8ab3b2f..f5ee5fc 100644 --- a/src/controllers/community/routes.ts +++ b/src/controllers/community/routes.ts @@ -3,6 +3,7 @@ import * as controller from "./community.js"; const communityRoutes = async (fastify: FastifyInstance) => { fastify.get(`/:id`, controller.getCommunity); + fastify.patch(`/:id`, controller.patchCommunity); }; export { communityRoutes }; diff --git a/src/controllers/community/types.ts b/src/controllers/community/types.ts index 2fda38a..462c9f8 100644 --- a/src/controllers/community/types.ts +++ b/src/controllers/community/types.ts @@ -1,21 +1,45 @@ -interface ICommunityParams { +interface IGetCommunityParams { id: string; } -interface ICommunityResponseError { +interface IGetCommunityResponseError { id: string; error: string; } -interface ICommunityResponseSuccess { +interface IGetCommunityResponseSuccess { id: string; name: string; description: string; creationDate: number; } +interface IPatchCommunityParams { + id: string; +} + +interface IPatchCommunityRequest { + name?: string; + description?: string; +} + +interface IPatchCommunityResponseError { + id: string; + error: string; +} + +interface IPatchCommunityResponseSuccess { + id: string; + name: string; + description: string; +} + export { - type ICommunityParams, - type ICommunityResponseError, - type ICommunityResponseSuccess, + type IGetCommunityParams, + type IGetCommunityResponseError, + type IGetCommunityResponseSuccess, + type IPatchCommunityParams, + type IPatchCommunityRequest, + type IPatchCommunityResponseError, + type IPatchCommunityResponseSuccess, }; diff --git a/src/controllers/user/routes.ts b/src/controllers/user/routes.ts index ed16350..79a1750 100644 --- a/src/controllers/user/routes.ts +++ b/src/controllers/user/routes.ts @@ -3,6 +3,7 @@ import * as controller from "./user.js"; const userRoutes = async (fastify: FastifyInstance) => { fastify.get(`/:id`, controller.getUser); + fastify.patch(`/:id`, controller.patchUser); fastify.get(`/:id/sessions`, controller.getSessions); }; diff --git a/src/controllers/user/types.ts b/src/controllers/user/types.ts index a0e3c79..c30d26b 100644 --- a/src/controllers/user/types.ts +++ b/src/controllers/user/types.ts @@ -1,13 +1,13 @@ -interface IUserParams { +interface IGetUserParams { id: string; } -interface IUserResponseError { +interface IGetUserResponseError { id: string; error: string; } -interface IUserResponseSuccess { +interface IGetUserResponseSuccess { id: string; username: string; email: string; @@ -17,25 +17,54 @@ interface IUserResponseSuccess { lastLogin: number; } -interface ISessionsResponseError { +interface IPatchUserParams { + id: string; +} + +interface IPatchUserRequest { + email?: string; + description?: string; +} + +interface IPatchUserResponseError { id: string; error: string; } -interface ISessionsResponseSuccess { - sessions: ISessionsResponseSession[]; +interface IPatchUserResponseSuccess { + id: string; + email: string; + description: string; } -interface ISessionsResponseSession { +interface IGetSessionsParams { + id: string; +} + +interface IGetSessionsResponseError { + id: string; + error: string; +} + +interface IGetSessionsResponseSuccess { + sessions: IGetSessionsResponseSession[]; +} + +interface IGetSessionsResponseSession { id: string; userId: string; } export { - type IUserParams, - type IUserResponseError, - type IUserResponseSuccess, - type ISessionsResponseError, - type ISessionsResponseSuccess, - type ISessionsResponseSession, + type IGetUserParams, + type IGetUserResponseError, + type IGetUserResponseSuccess, + type IPatchUserParams, + type IPatchUserRequest, + type IPatchUserResponseError, + type IPatchUserResponseSuccess, + type IGetSessionsParams, + type IGetSessionsResponseError, + type IGetSessionsResponseSuccess, + type IGetSessionsResponseSession, }; diff --git a/src/controllers/user/user.ts b/src/controllers/user/user.ts index d573f43..e677d0a 100644 --- a/src/controllers/user/user.ts +++ b/src/controllers/user/user.ts @@ -1,22 +1,41 @@ import { type FastifyReply, type FastifyRequest } from "fastify"; import type { - IUserParams, - IUserResponseError, - IUserResponseSuccess, - ISessionsResponseError, - ISessionsResponseSuccess, + IGetUserParams, + IGetUserResponseError, + IGetUserResponseSuccess, + IPatchUserParams, + IPatchUserRequest, + IPatchUserResponseError, + IPatchUserResponseSuccess, + IGetSessionsParams, + IGetSessionsResponseError, + IGetSessionsResponseSuccess, } from "./types.js"; -import { getUserById, getUserSessionsById } from "../../services/user/user.js"; +import { + getUserByIdAuth, + updateUserByIdAuth, + getUserSessionsByIdAuth, +} from "../../services/user/user.js"; +import { API_ERROR } from "../errors.js"; -const getUser = async (request: FastifyRequest, _reply: FastifyReply) => { - const { id } = request.params as IUserParams; +const getUser = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IGetUserParams; + const authHeader = request.headers["authorization"]; - const user = await getUserById(id); + const user = await getUserByIdAuth(id, authHeader); if (!user) { + reply.status(404); return { id: id, - error: "user does not exist", - } as IUserResponseError; + error: API_ERROR.NOT_FOUND, + } as IGetUserResponseError; + } + if (user === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + id: id, + error: API_ERROR.ACCESS_DENIED, + } as IGetUserResponseError; } return { @@ -27,19 +46,55 @@ const getUser = async (request: FastifyRequest, _reply: FastifyReply) => { admin: user.admin, registerDate: user.registerDate.getTime(), lastLogin: user.lastLogin?.getTime() ?? 0, - } as IUserResponseSuccess; + } as IGetUserResponseSuccess; }; -const getSessions = async (request: FastifyRequest, _reply: FastifyReply) => { - const { id } = request.params as IUserParams; +const patchUser = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IPatchUserParams; + const patchUserRequest = request.body as IPatchUserRequest; const authHeader = request.headers["authorization"]; - const sessions = await getUserSessionsById(id, authHeader); - if (!sessions) { + const user = await updateUserByIdAuth(id, patchUserRequest, authHeader); + if (!user) { + reply.status(404); return { id: id, - error: "user does not exist or you have no access", - } as ISessionsResponseError; + error: API_ERROR.NOT_FOUND, + } as IPatchUserResponseError; + } + if (user === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + id: id, + error: API_ERROR.ACCESS_DENIED, + } as IPatchUserResponseError; + } + + return { + id: user.id, + email: user.email, + description: user.description, + } as IPatchUserResponseSuccess; +}; + +const getSessions = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as IGetSessionsParams; + const authHeader = request.headers["authorization"]; + + const sessions = await getUserSessionsByIdAuth(id, authHeader); + if (!sessions) { + reply.status(404); + return { + id: id, + error: API_ERROR.NOT_FOUND, + } as IGetSessionsResponseError; + } + if (sessions === API_ERROR.ACCESS_DENIED) { + reply.status(403); + return { + id: id, + error: API_ERROR.ACCESS_DENIED, + } as IGetSessionsResponseError; } return { @@ -47,7 +102,7 @@ const getSessions = async (request: FastifyRequest, _reply: FastifyReply) => { id: session.id, userId: session.userId, })), - } as ISessionsResponseSuccess; + } as IGetSessionsResponseSuccess; }; -export { getUser, getSessions }; +export { getUser, patchUser, getSessions }; diff --git a/src/services/community/community.ts b/src/services/community/community.ts index 086c7f2..6bddbae 100644 --- a/src/services/community/community.ts +++ b/src/services/community/community.ts @@ -1,5 +1,9 @@ +import { API_ERROR } from "../../controllers/errors.js"; import type { Community } from "../../generated/prisma/client.js"; import { getDB } from "../../store/store.js"; +import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js"; +import { PERMISSION } from "../auth/permission.js"; +import type { IUpdateCommunity } from "./types.js"; const getCommunityById = async (id: string): Promise => { return await getDB().community.findUnique({ @@ -7,4 +11,42 @@ const getCommunityById = async (id: string): Promise => { }); }; -export { getCommunityById }; +const updateCommunityById = async ( + id: string, + update: IUpdateCommunity, +): Promise => { + return await getDB().community.update({ + where: { + id: id, + }, + data: { + ...update, + }, + }); +}; + +const updateCommunityByIdAuth = async ( + id: string, + update: IUpdateCommunity, + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + const community = await getCommunityById(id); + + if ( + !(await isUserAllowed( + authUser, + { + community: community, + }, + community, + [PERMISSION.COMMUNITY_MANAGE], + )) + ) { + return API_ERROR.ACCESS_DENIED; + } + + return await updateCommunityById(id, update); +}; + +export { getCommunityById, updateCommunityById, updateCommunityByIdAuth }; diff --git a/src/services/community/index.ts b/src/services/community/index.ts index 893e64d..71f7299 100644 --- a/src/services/community/index.ts +++ b/src/services/community/index.ts @@ -1 +1,2 @@ export * from "./community.js"; +export * from "./types.js"; diff --git a/src/services/community/types.ts b/src/services/community/types.ts new file mode 100644 index 0000000..4ad8446 --- /dev/null +++ b/src/services/community/types.ts @@ -0,0 +1,6 @@ +interface IUpdateCommunity { + name?: string; + description?: string; +} + +export { type IUpdateCommunity }; diff --git a/src/services/session/session.ts b/src/services/session/session.ts index d594df2..62e301d 100644 --- a/src/services/session/session.ts +++ b/src/services/session/session.ts @@ -38,7 +38,7 @@ const deleteSessionByIdAuth = async ( authHeader: string | undefined, ): Promise => { const authUser = await getUserFromAuth(authHeader); - const session = await deleteSessionById(id); + const session = await getSessionById(id); if ( !(await isUserOwnerOrAdmin(authUser, { @@ -48,7 +48,7 @@ const deleteSessionByIdAuth = async ( return API_ERROR.ACCESS_DENIED; } - return session; + return await deleteSessionById(id); }; export { diff --git a/src/services/user/index.ts b/src/services/user/index.ts index fd3004f..110bb57 100644 --- a/src/services/user/index.ts +++ b/src/services/user/index.ts @@ -1 +1,2 @@ export * from "./user.js"; +export * from "./types.js"; diff --git a/src/services/user/types.ts b/src/services/user/types.ts new file mode 100644 index 0000000..fee3b16 --- /dev/null +++ b/src/services/user/types.ts @@ -0,0 +1,6 @@ +interface IUpdateUser { + email?: string; + description?: string; +} + +export { type IUpdateUser }; diff --git a/src/services/user/user.ts b/src/services/user/user.ts index e3e57fd..2abe0ad 100644 --- a/src/services/user/user.ts +++ b/src/services/user/user.ts @@ -1,6 +1,8 @@ import type { User, Session } 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 { IUpdateUser } from "./types.js"; const getUserById = async (id: string): Promise => { return await getDB().user.findUnique({ @@ -8,19 +10,58 @@ const getUserById = async (id: string): Promise => { }); }; -const getUserSessionsById = async ( +const getUserByIdAuth = async ( id: string, authHeader: string | undefined, -): Promise => { +): Promise => { const authUser = await getUserFromAuth(authHeader); + const user = await getUserById(id); + if ( !(await isUserOwnerOrAdmin(authUser, { - user: await getUserById(id), + user: user, })) ) { - return null; + return API_ERROR.ACCESS_DENIED; } + return user; +}; + +const updateUserById = async ( + id: string, + update: IUpdateUser, +): Promise => { + return await getDB().user.update({ + where: { + id: id, + }, + data: { + ...update, + }, + }); +}; + +const updateUserByIdAuth = async ( + id: string, + update: IUpdateUser, + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + const user = await getUserById(id); + + if ( + !(await isUserOwnerOrAdmin(authUser, { + user: user, + })) + ) { + return API_ERROR.ACCESS_DENIED; + } + + return await updateUserById(id, update); +}; + +const getUserSessionsById = async (id: string): Promise => { return await getDB().session.findMany({ where: { userId: id, @@ -28,4 +69,30 @@ const getUserSessionsById = async ( }); }; -export { getUserById, getUserSessionsById }; +const getUserSessionsByIdAuth = async ( + id: string, + authHeader: string | undefined, +): Promise => { + const authUser = await getUserFromAuth(authHeader); + const user = await getUserById(id); + const sessions = await getUserSessionsById(id); + + if ( + !(await isUserOwnerOrAdmin(authUser, { + user: user, + })) + ) { + return API_ERROR.ACCESS_DENIED; + } + + return sessions; +}; + +export { + getUserById, + getUserByIdAuth, + updateUserById, + updateUserByIdAuth, + getUserSessionsById, + getUserSessionsByIdAuth, +};