diff --git a/src/app.ts b/src/app.ts index 099f8a4..45dd2ee 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,6 +6,7 @@ import { StatusCodes } from 'http-status-codes'; import authRouter from './routes/auth'; import algorithmRouter from './routes/algorithm'; import userRouter from './routes/user'; +import projectsRouter from './routes/projects'; import cookieParser from 'cookie-parser'; const app = express(); const port = 3001; @@ -42,6 +43,7 @@ app.get('/', (req: Request, res: Response) => { app.use('/auth', authRouter); app.use('/algorithm', algorithmRouter); app.use('/user', userRouter); +app.use('/projects', projectsRouter); if (process.env.NODE_ENV !== 'test') { app.listen(port, () => { diff --git a/src/routes/projects/index.ts b/src/routes/projects/index.ts new file mode 100644 index 0000000..3266274 --- /dev/null +++ b/src/routes/projects/index.ts @@ -0,0 +1,87 @@ +import express, { Router, Request, Response } from 'express'; +import { validateAndExtractAuthToken } from '../../utils/middleware/authToken.middleware'; +import projectsManager from '../../utils/services/projects.manager'; +import { validateData } from '../../utils/middleware/validation.middleware'; +import { + projectDeleteSchema, + projectGetSchema, + projectUpdateData, + projectUpdateSchema, +} from './types'; + +const router: Router = express.Router(); + +router.post( + '/create', + validateAndExtractAuthToken(), + async (req: Request, res: Response) => { + const userId = req.userId; + + const { status, response } = await projectsManager.createProject( + userId ?? '' + ); + res.status(status).send({ response }); + } +); + +router.get( + '/get-all', + validateAndExtractAuthToken(), + async (req: Request, res: Response) => { + const userId = req.userId; + + const { status, response } = await projectsManager.getAllProjects( + userId ?? '' + ); + res.status(status).send({ response }); + } +); + +router.delete( + '/delete/:projectId', + validateAndExtractAuthToken(), + validateData(projectDeleteSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const projectId = req.params.projectId; + + const { status, response } = await projectsManager.deleteProject( + userId ?? '', + projectId + ); + res.status(status).send({ response }); + } +); + +router.get( + '/get/:projectId', + validateAndExtractAuthToken(), + validateData(projectGetSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const projectId = req.params.projectId; + + const { status, response } = await projectsManager.getProject( + userId ?? '', + projectId + ); + res.status(status).send({ response }); + } +); + +router.put( + '/update', + validateAndExtractAuthToken(), + validateData(projectUpdateSchema), + async (req: Request, res: Response) => { + const userId = req.userId; + const data: projectUpdateData = req.body; + + const { status, response } = await projectsManager.updateProject( + userId ?? '', + data + ); + res.status(status).send({ response }); + } +); +export default router; diff --git a/src/routes/projects/types.ts b/src/routes/projects/types.ts new file mode 100644 index 0000000..bc5979b --- /dev/null +++ b/src/routes/projects/types.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; + +export const projectDeleteSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectDeleteData = z.infer; + +export const projectGetSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectGetData = z.infer; + +export const projectUpdateSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), + + name: z.string().min(1).max(100).optional(), + participants: z.number().min(1).optional(), + group_size: z.number().min(1).optional(), +}); + +export type projectUpdateData = z.infer; diff --git a/src/utils/services/projects.manager.ts b/src/utils/services/projects.manager.ts new file mode 100644 index 0000000..df67f97 --- /dev/null +++ b/src/utils/services/projects.manager.ts @@ -0,0 +1,200 @@ +import { StatusCodes } from 'http-status-codes'; +import { DatabaseManager } from './database.manager'; +import { Document, ObjectId, WithId } from 'mongodb'; +import { projectUpdateData } from '../../routes/projects/types'; + +class ProjectsManager { + private static instance: ProjectsManager; + private userDatabaseManager = new DatabaseManager('Users'); + private projectsDatabaseManager = new DatabaseManager('Projects'); + + private constructor() {} + + public static getInstance(): ProjectsManager { + if (!ProjectsManager.instance) { + ProjectsManager.instance = new ProjectsManager(); + } + return ProjectsManager.instance; + } + + public async createProject( + userId: string + ): Promise<{ status: number; response: string }> { + try { + const user = await this.userDatabaseManager.findOne({ + _id: new ObjectId(userId), + }); + if (!user) { + return { status: StatusCodes.NOT_FOUND, response: 'User not found' }; + } + const projects = await this.projectsDatabaseManager.find({ + user: new ObjectId(userId), + }); + const result = await this.projectsDatabaseManager.create({ + user: new ObjectId(userId), + name: `New Project ${projects.length + 1}`, + participants: 10, + registrants: 0, + group_size: 2, + preferences: 0, + }); + if (result.acknowledged) { + return { status: StatusCodes.OK, response: 'Created project' }; + } + } catch (err) { + console.error('Failed to create project:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to created project', + }; + } + + public async getAllProjects( + userId: string + ): Promise<{ status: number; response: string | WithId[] }> { + try { + const user = await this.userDatabaseManager.findOne({ + _id: new ObjectId(userId), + }); + if (!user) { + return { status: StatusCodes.NOT_FOUND, response: 'User not found' }; + } + const projects = await this.projectsDatabaseManager.find({ + user: new ObjectId(userId), + }); + return { status: StatusCodes.OK, response: projects }; + } catch (err) { + console.error('Failed to get all projects:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to get all projects', + }; + } + + public async deleteProject( + userId: string, + projectId: string + ): Promise<{ status: number; response: string }> { + try { + const user = await this.userDatabaseManager.findOne({ + _id: new ObjectId(userId), + }); + if (!user) { + return { status: StatusCodes.NOT_FOUND, response: 'User not found' }; + } + const project = await this.projectsDatabaseManager.findOne({ + _id: new ObjectId(projectId), + }); + if (!project) { + return { status: StatusCodes.NOT_FOUND, response: 'Project not found' }; + } + if (project.user.toString() !== user._id.toString()) { + return { + status: StatusCodes.FORBIDDEN, + response: 'The user dosnt own the project', + }; + } + const result = await this.projectsDatabaseManager.delete({ + _id: project._id, + }); + if (result.acknowledged) { + return { status: StatusCodes.OK, response: 'Project deleted' }; + } + } catch (err) { + console.error('Failed to delete project:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to delete project', + }; + } + + public async getProject( + userId: string, + projectId: string + ): Promise<{ status: number; response: string | WithId }> { + try { + const user = await this.userDatabaseManager.findOne({ + _id: new ObjectId(userId), + }); + if (!user) { + return { status: StatusCodes.NOT_FOUND, response: 'User not found' }; + } + const project = await this.projectsDatabaseManager.findOne({ + _id: new ObjectId(projectId), + }); + if (!project) { + return { status: StatusCodes.NOT_FOUND, response: 'Project not found' }; + } + if (project.user.toString() !== user._id.toString()) { + return { + status: StatusCodes.FORBIDDEN, + response: 'The user dosnt own the project', + }; + } + return { status: StatusCodes.OK, response: project }; + } catch (err) { + console.error('Failed to get project:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to get project', + }; + } + + public async updateProject( + userId: string, + data: projectUpdateData + ): Promise<{ status: number; response: string }> { + try { + const user = await this.userDatabaseManager.findOne({ + _id: new ObjectId(userId), + }); + if (!user) { + return { status: StatusCodes.NOT_FOUND, response: 'User not found' }; + } + const project = await this.projectsDatabaseManager.findOne({ + _id: new ObjectId(data.projectId), + }); + if (!project) { + return { status: StatusCodes.NOT_FOUND, response: 'Project not found' }; + } + if (project.user.toString() !== user._id.toString()) { + return { + status: StatusCodes.FORBIDDEN, + response: 'The user dosnt own the project', + }; + } + const result = await this.projectsDatabaseManager.update( + { + _id: project._id, + }, + { + $set: { + ...(data.name !== undefined && { name: data.name }), + ...(data.participants !== undefined && { + participants: data.participants, + }), + ...(data.group_size !== undefined && { + group_size: data.group_size, + }), + }, + } + ); + if (result.acknowledged) { + return { status: StatusCodes.OK, response: 'Project updated' }; + } + } catch (err) { + console.error('Failed to update project:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to update project', + }; + } +} + +const projectsManager = ProjectsManager.getInstance(); +export default projectsManager;