Skip to main content
Glama
project.controller.ts17.8 kB
import { logger } from '@logger'; import type { ResponseWithSession } from '@middlewares/sessionAuth.middleware'; import { SessionModel } from '@models/session.model'; import * as projectService from '@services/project.service'; import * as userService from '@services/user.service'; import { type AppError, ErrorHandler } from '@utils/errors'; import type { FiltersAndPagination } from '@utils/filtersAndPagination/getFiltersAndPaginationFromBody'; import { getProjectFiltersAndPagination, type ProjectFiltersParams, } from '@utils/filtersAndPagination/getProjectFiltersAndPagination'; import { mapProjectsToAPI, mapProjectToAPI } from '@utils/mapper/project'; import { hasPermission } from '@utils/permissions'; import { getPlanDetails } from '@utils/plan'; import { formatPaginatedResponse, formatResponse, type PaginatedResponse, type ResponseData, } from '@utils/responseData'; import type { NextFunction, Request } from 'express'; import { t } from 'express-intlayer'; import type { Types } from 'mongoose'; import type { ProjectAPI, ProjectConfiguration, ProjectCreationData, ProjectData, } from '@/types/project.types'; import type { User } from '@/types/user.types'; export type GetProjectsParams = FiltersAndPagination<ProjectFiltersParams>; export type GetProjectsResult = PaginatedResponse<ProjectAPI>; /** * Retrieves a list of projects based on filters and pagination. */ export const getProjects = async ( req: Request<GetProjectsParams>, res: ResponseWithSession<GetProjectsResult>, _next: NextFunction ): Promise<void> => { const { user, organization, roles } = res.locals; const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getProjectFiltersAndPagination(req, res); if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED'); return; } if (!organization && !roles.includes('admin')) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED'); return; } try { const projects = await projectService.findProjects( filters, skip, pageSize, sortOptions ); if ( !hasPermission( roles, 'project:read' )({ ...res.locals, targetProjects: projects, }) ) { ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED'); return; } const totalItems = await projectService.countProjects(filters); const formattedProjects = mapProjectsToAPI(projects); const responseData = formatPaginatedResponse<ProjectAPI>({ data: formattedProjects, page, pageSize, totalPages: getNumberOfPages(totalItems), totalItems, }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; export type AddProjectBody = ProjectCreationData; export type AddProjectResult = ResponseData<ProjectAPI>; /** * Adds a new project to the database. */ export const addProject = async ( req: Request<any, any, AddProjectBody>, res: ResponseWithSession<AddProjectResult>, _next: NextFunction ): Promise<void> => { const { organization, user, roles } = res.locals; const projectData = req.body; if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED'); return; } if (!organization) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED'); return; } if (!projectData) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_DATA_NOT_FOUND'); } if ( !hasPermission( roles, 'organization:admin' )({ ...res.locals, targetOrganizations: [organization], }) ) { ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED'); return; } const { plan } = organization; const planType = getPlanDetails(plan); if (planType.numberOfProjects) { const projectCount = await projectService.countProjects({ organizationId: organization.id, }); if (projectCount >= planType.numberOfProjects) { ErrorHandler.handleGenericErrorResponse( res, 'PLAN_PROJECT_LIMIT_REACHED', { organizationId: organization.id, } ); return; } } const project: ProjectData = { membersIds: [user.id], adminsIds: [user.id], creatorId: user.id, organizationId: organization.id, ...projectData, }; try { const newProject = await projectService.createProject(project); const formattedProject = mapProjectToAPI(newProject); const responseData = formatResponse<ProjectAPI>({ message: t({ en: 'Project created successfully', fr: 'Projet créé avec succès', es: 'Proyecto creado con éxito', }), description: t({ en: 'Your project has been created successfully', fr: 'Votre projet a été créé avec succès', es: 'Su proyecto ha sido creado con éxito', }), data: formattedProject, }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; export type UpdateProjectBody = Partial<ProjectAPI>; export type UpdateProjectResult = ResponseData<ProjectAPI>; /** * Updates an existing project in the database. */ export const updateProject = async ( req: Request<any, any, UpdateProjectBody>, res: ResponseWithSession<UpdateProjectResult>, _next: NextFunction ): Promise<void> => { const { organization, project, user, roles } = res.locals; const projectData = req.body; if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED'); return; } if (!project) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_DATA_NOT_FOUND'); return; } if (!organization) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED'); return; } if (String(project.organizationId) !== String(organization.id)) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_IN_ORGANIZATION'); return; } if ( !hasPermission( roles, 'project:write' )({ ...res.locals, targetProjectIds: [String(project.id)], }) ) { ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED'); return; } try { const updatedProject = await projectService.updateProjectById( project.id, projectData ); const formattedProject = mapProjectToAPI(updatedProject); const responseData = formatResponse<ProjectAPI>({ message: t({ en: 'Project updated successfully', fr: 'Projet mis à jour avec succès', es: 'Proyecto actualizado con éxito', }), description: t({ en: 'Your project has been updated successfully', fr: 'Votre projet a été mis à jour avec succès', es: 'Su proyecto ha sido actualizado con éxito', }), data: formattedProject, }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; type UserAndAdmin = { user: User; isAdmin: boolean }; export type ProjectMemberByIdOption = { userId: string | Types.ObjectId; isAdmin?: boolean; }; export type UpdateProjectMembersBody = Partial<{ membersIds: ProjectMemberByIdOption[]; }>; export type UpdateProjectMembersResult = ResponseData<ProjectAPI>; /** * Update members to the dictionary in the database. */ export const updateProjectMembers = async ( req: Request<any, any, UpdateProjectMembersBody>, res: ResponseWithSession<UpdateProjectMembersResult>, _next: NextFunction ): Promise<void> => { const { user, project, organization, roles } = res.locals; const { membersIds } = req.body; if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED'); return; } if (!project) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED'); return; } if (!organization) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED'); return; } if (membersIds?.length === 0) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_MUST_HAVE_MEMBER'); return; } if (membersIds?.map((el) => el.isAdmin)?.length === 0) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_MUST_HAVE_ADMIN'); return; } if ( !hasPermission( roles, 'project:write' )({ ...res.locals, targetProjectIds: [String(project.id)], }) ) { ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED'); return; } try { const existingUsers: UserAndAdmin[] = []; if (membersIds) { const userIdList = membersIds ?.filter( (member) => // Remove members that are not in the organization !organization?.membersIds.includes(member.userId as Types.ObjectId) ) .map((member) => member.userId); const users = await userService.getUsersByIds(userIdList); if (users) { const userMap: UserAndAdmin[] = users.map((user) => ({ user, isAdmin: membersIds.find( (member) => String(member.userId) === String(user.id) )?.isAdmin ?? false, })); existingUsers.push(...userMap); } } const formattedMembers: Types.ObjectId[] = existingUsers.map( (user) => user.user.id ); const formattedAdmin: Types.ObjectId[] = existingUsers .filter((el) => el.isAdmin) .map((user) => user.user.id); const updatedOrganization = await projectService.updateProjectById( project.id, { ...project, membersIds: formattedMembers, adminsIds: formattedAdmin, } ); const formattedProject = mapProjectToAPI(updatedOrganization); const responseData = formatResponse<ProjectAPI>({ message: t({ en: 'Project members updated successfully', fr: 'Membres du projet mis à jour avec succès', es: 'Miembros del proyecto actualizados con éxito', }), description: t({ en: 'Your project members have been updated successfully', fr: 'Les membres de votre projet ont été mis à jour avec succès', es: 'Los miembros de su proyecto han sido actualizados con éxito', }), data: formattedProject, }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; export type PushProjectConfigurationBody = ProjectConfiguration; export type PushProjectConfigurationResult = ResponseData<ProjectConfiguration>; /** * Pushes a project configuration to the database. * @param req - Express request object. * @param res - Express response object. * @returns Response confirming the deletion. */ export const pushProjectConfiguration = async ( req: Request<any, any, PushProjectConfigurationBody>, res: ResponseWithSession<PushProjectConfigurationResult>, _next: NextFunction ): Promise<void> => { const { user, project, roles } = res.locals; const projectConfiguration = req.body; if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED'); return; } if (!project) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED'); return; } if ( !hasPermission( roles, 'project:write' )({ ...res.locals, targetProjectIds: [String(project.id)], }) ) { ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED'); return; } try { const projectObject = await projectService.getProjectById(project.id); projectObject.configuration = projectConfiguration; projectObject.save(); if (!projectObject.configuration) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_UPDATE_FAILED', { projectId: project.id, }); return; } const responseData = formatResponse<ProjectConfiguration>({ message: t({ en: 'Project configuration updated successfully', fr: 'Configuration du projet mise à jour avec succès', es: 'Configuración del proyecto actualizada con éxito', }), description: t({ en: 'Your project configuration has been updated successfully', fr: 'La configuration du projet a été mise à jour avec succès', es: 'Su configuración del proyecto ha sido actualizada con éxito', }), data: projectObject.configuration, }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; export type DeleteProjectResult = ResponseData<ProjectAPI>; /** * Deletes a project from the database by its ID. * @param req - Express request object. * @param res - Express response object. * @returns Response confirming the deletion. */ export const deleteProject = async ( _req: Request, res: ResponseWithSession<DeleteProjectResult>, _next: NextFunction ): Promise<void> => { const { user, organization, project, session, roles } = res.locals; if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED'); return; } if (!organization) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_DEFINED'); return; } if (!project) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED'); return; } if (typeof session === 'undefined') { ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED'); return; } if ( !hasPermission( roles, 'project:admin' )({ ...res.locals, targetProjectIds: [String(project.id)], }) ) { ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED'); return; } try { const projectToDelete = await projectService.getProjectById(project.id); if (String(projectToDelete.organizationId) !== String(organization.id)) { ErrorHandler.handleGenericErrorResponse( res, 'PROJECT_NOT_IN_ORGANIZATION' ); return; } const deletedProject = await projectService.deleteProjectById(project.id); if (!deletedProject) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_NOT_DEFINED', { projectId: project.id, }); return; } logger.info(`Project deleted: ${String(deletedProject.id)}`); const responseData = formatResponse<ProjectAPI>({ message: t({ en: 'Project deleted successfully', fr: 'Projet supprimé avec succès', es: 'Proyecto eliminado con éxito', }), description: t({ en: 'Your project has been deleted successfully', fr: 'Votre projet a été supprimé avec succès', es: 'Su proyecto ha sido eliminado con éxito', }), data: mapProjectToAPI(deletedProject), }); await SessionModel.updateOne( { _id: session.id }, { $set: { activeProjectId: null } } ); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; export type SelectProjectParam = { projectId: string | Types.ObjectId }; export type SelectProjectResult = ResponseData<ProjectAPI>; /** * Select a project. */ export const selectProject = async ( req: Request<SelectProjectParam>, res: ResponseWithSession<SelectProjectResult>, _next: NextFunction ) => { const { projectId } = req.params; const { session } = res.locals; if (!projectId) { ErrorHandler.handleGenericErrorResponse(res, 'PROJECT_ID_NOT_FOUND'); return; } if (typeof session === 'undefined') { ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED'); return; } try { const project = await projectService.getProjectById(projectId); await SessionModel.updateOne( { _id: session.id }, { $set: { activeProjectId: String(projectId) } } ); const responseData = formatResponse<ProjectAPI>({ message: t({ en: 'Project selected successfully', fr: 'Projet sélectionné avec succès', es: 'Proyecto seleccionado con éxito', }), description: t({ en: 'Your project has been selected successfully', fr: 'Votre projet a été sélectionné avec succès', es: 'Su proyecto ha sido seleccionado con éxito', }), data: mapProjectToAPI(project), }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; export type UnselectProjectResult = ResponseData<null>; /** * Unselect a project. */ export const unselectProject = async ( _req: Request, res: ResponseWithSession<UnselectProjectResult>, _next: NextFunction ) => { const { session } = res.locals; if (typeof session === 'undefined') { ErrorHandler.handleGenericErrorResponse(res, 'SESSION_NOT_DEFINED'); return; } try { await SessionModel.updateOne( { _id: session.id }, { $set: { activeProjectId: null } } ); const responseData = formatResponse<null>({ message: t({ en: 'Project unselected successfully', fr: 'Projet désélectionné avec succès', es: 'Proyecto deseleccionado con éxito', }), description: t({ en: 'Your project has been unselected successfully', fr: 'Votre projet a été désélectionné avec succès', es: 'Su proyecto ha sido deseleccionado con éxito', }), data: null, }); res.json(responseData); return; } catch (error) { ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server