diff --git a/src/routes/projects/criteria/index.ts b/src/routes/projects/criteria/index.ts new file mode 100644 index 0000000..9540928 --- /dev/null +++ b/src/routes/projects/criteria/index.ts @@ -0,0 +1,80 @@ +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 { + projectAddCriterionSchema, + projectDeleteCriterionSchema, + projectGetAllCriteriaSchema, + projectUpdateCriterionData, + projectUpdateCriterionSchema, +} from './types'; + +const router: Router = express.Router(); + +router.post( + '/add/:projectId', + validateAndExtractAuthToken(), + validateData(projectAddCriterionSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const projectId = req.params.projectId; + + const { status, response } = await projectsManager.addCriterion( + userId ?? '', + projectId + ); + res.status(status).send({ response }); + } +); + +router.get( + '/get-all/:projectId', + validateAndExtractAuthToken(), + validateData(projectGetAllCriteriaSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const projectId = req.params.projectId; + + const { status, response } = await projectsManager.getAllCriteria( + userId ?? '', + projectId + ); + + res.status(status).send({ response }); + } +); + +router.put( + '/update', + validateAndExtractAuthToken(), + validateData(projectUpdateCriterionSchema), + async (req: Request, res: Response) => { + const userId = req.userId; + const data: projectUpdateCriterionData = req.body; + + const { status, response } = await projectsManager.updateCriterion( + userId ?? '', + data + ); + res.status(status).send({ response }); + } +); + +router.delete( + '/delete/:criterionId', + validateAndExtractAuthToken(), + validateData(projectDeleteCriterionSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const criterionId = req.params.criterionId; + + const { status, response } = await projectsManager.deleteCriterion( + userId ?? '', + criterionId + ); + res.status(status).send({ response }); + } +); + +export default router; diff --git a/src/routes/projects/criteria/types.ts b/src/routes/projects/criteria/types.ts new file mode 100644 index 0000000..bdc983c --- /dev/null +++ b/src/routes/projects/criteria/types.ts @@ -0,0 +1,56 @@ +import { z } from 'zod'; + +export const projectAddCriterionSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectAddCriterionData = z.infer; + +export const projectGetAllCriteriaSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectGetAllCriteriaData = z.infer< + typeof projectGetAllCriteriaSchema +>; + +export const projectUpdateCriterionSchema = z.object({ + criterionId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), + name: z.string().min(1).max(100).optional(), + range: z.number().min(1).max(1000).optional(), +}); + +export type projectUpdateCriterionData = z.infer< + typeof projectUpdateCriterionSchema +>; + +export const projectDeleteCriterionSchema = z.object({ + criterionId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectDeleteCriterionData = z.infer< + typeof projectDeleteCriterionSchema +>; + +export const projectGetCriteriaByProjectSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectGetCriteriaByProjectData = z.infer< + typeof projectGetCriteriaByProjectSchema +>; diff --git a/src/routes/projects/index.ts b/src/routes/projects/index.ts index 3266274..4f048ea 100644 --- a/src/routes/projects/index.ts +++ b/src/routes/projects/index.ts @@ -5,12 +5,20 @@ import { validateData } from '../../utils/middleware/validation.middleware'; import { projectDeleteSchema, projectGetSchema, + projectPreferencesSaveData, + projectPreferencesSaveSchema, + projectSearchSchema, projectUpdateData, projectUpdateSchema, } from './types'; +import criteriaRouter from './criteria'; +import participantsRouter from './participants'; const router: Router = express.Router(); +router.use('/criteria', criteriaRouter); +router.use('/participants', participantsRouter); + router.post( '/create', validateAndExtractAuthToken(), @@ -84,4 +92,27 @@ router.put( res.status(status).send({ response }); } ); + +router.get( + '/search/:code', + validateData(projectSearchSchema, 'params'), + async (req: Request, res: Response) => { + const code = req.params.code; + + const { status, response } = await projectsManager.searchProject(code); + res.status(status).send({ response }); + } +); + +router.post( + '/preferences/save', + validateData(projectPreferencesSaveSchema), + async (req: Request, res: Response) => { + const data: projectPreferencesSaveData = req.body; + + const { status, response } = await projectsManager.savePreferences(data); + res.status(status).send({ response }); + } +); + export default router; diff --git a/src/routes/projects/participants/index.ts b/src/routes/projects/participants/index.ts new file mode 100644 index 0000000..6fdfb62 --- /dev/null +++ b/src/routes/projects/participants/index.ts @@ -0,0 +1,120 @@ +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 { + projectAddParticipantSchema, + projectAddParticipantData, + projectGetAllParticipantSchema, + projectGetParticipantIdSchema, + projectUpdateParticipantCriteriaSchema, + projectUpdateParticipantCriteriaData, +} from './types'; + +const router: Router = express.Router(); + +router.post( + '/add', + validateAndExtractAuthToken(), + validateData(projectAddParticipantSchema), + async (req: Request, res: Response) => { + const userId = req.userId; + const data: projectAddParticipantData = req.body; + + const { status, response } = await projectsManager.addParticipant( + userId ?? '', + data + ); + res.status(status).send({ response }); + } +); + +router.get( + '/get-all/:projectId', + validateAndExtractAuthToken(), + validateData(projectGetAllParticipantSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const projectId = req.params.projectId; + + const { status, response } = await projectsManager.getAllParticipants( + userId ?? '', + projectId + ); + res.status(status).send({ response }); + } +); + +router.get( + '/criteria/get/:participantId', + validateAndExtractAuthToken(), + validateData(projectGetParticipantIdSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const participantId = req.params.participantId; + + const { status, response } = await projectsManager.getParticipantCriteria( + userId ?? '', + participantId + ); + res.status(status).send({ response }); + } +); + +router.put( + '/criteria/update/:participantId', + validateAndExtractAuthToken(), + validateData(projectUpdateParticipantCriteriaSchema), + async (req: Request, res: Response) => { + const userId = req.userId; + const participantId = req.params.participantId; + const data: projectUpdateParticipantCriteriaData = req.body; + + const { status, response } = + await projectsManager.updateParticipantCriteria( + userId ?? '', + participantId, + data.criteria + ); + + res.status(status).send({ response }); + } +); + +router.delete( + '/delete/:projectId/:participantId', + validateAndExtractAuthToken(), + validateData(projectGetParticipantIdSchema, 'params'), + async (req: Request, res: Response) => { + const userId = req.userId; + const { projectId, participantId } = req.params; + + const { status, response } = await projectsManager.deleteParticipant( + userId ?? '', + projectId, + participantId + ); + + res.status(status).send({ response }); + } +); + +router.put( + '/update-all/:projectId', + validateAndExtractAuthToken(), + async (req: Request, res: Response) => { + const userId = req.userId; + const { projectId } = req.params; + const { participants } = req.body; + + const { status, response } = await projectsManager.updateAllParticipants( + userId ?? '', + projectId, + participants + ); + + res.status(status).send({ response }); + } +); + +export default router; diff --git a/src/routes/projects/participants/types.ts b/src/routes/projects/participants/types.ts new file mode 100644 index 0000000..165d0e9 --- /dev/null +++ b/src/routes/projects/participants/types.ts @@ -0,0 +1,57 @@ +import { z } from 'zod'; + +export const projectAddParticipantSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), + firstName: z + .string() + .min(1) + .regex(/^[A-Za-z-]+$/, 'First name can only contain letters or dashes') + .optional(), + lastName: z + .string() + .min(1) + .regex(/^[A-Za-z-]+$/, 'Last name can only contain letters or dashes') + .optional(), + tz: z + .string() + .min(9) + .regex(/^\d{9}$/, 'ID must contain 9 digits') + .optional(), +}); + +export type projectAddParticipantData = z.infer< + typeof projectAddParticipantSchema +>; + +export const projectGetAllParticipantSchema = z.object({ + projectId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectGetAllParticipantData = z.infer< + typeof projectGetAllParticipantSchema +>; + +export const projectGetParticipantIdSchema = z.object({ + participantId: z + .string() + .min(1) + .regex(/^[0-9a-fA-F]{24}$/, 'Invalid ObjectId'), +}); + +export type projectGetParticipantIdData = z.infer< + typeof projectGetParticipantIdSchema +>; + +export const projectUpdateParticipantCriteriaSchema = z.object({ + criteria: z.record(z.string(), z.number().min(0).max(1000)), +}); + +export type projectUpdateParticipantCriteriaData = z.infer< + typeof projectUpdateParticipantCriteriaSchema +>; diff --git a/src/routes/projects/types.ts b/src/routes/projects/types.ts index bc5979b..4ccb2e9 100644 --- a/src/routes/projects/types.ts +++ b/src/routes/projects/types.ts @@ -30,3 +30,19 @@ export const projectUpdateSchema = z.object({ }); export type projectUpdateData = z.infer; + +export const projectSearchSchema = z.object({ + code: z.string().length(8), +}); + +export type projectSearchData = z.infer; + +export const projectPreferencesSaveSchema = z.object({ + selectedParticipant: z.string().min(1), + participantId: z.string().min(1), + preferences: z.array(z.string().min(1)), +}); + +export type projectPreferencesSaveData = z.infer< + typeof projectPreferencesSaveSchema +>; diff --git a/src/utils/services/projects.manager.ts b/src/utils/services/projects.manager.ts index df67f97..dd8dc99 100644 --- a/src/utils/services/projects.manager.ts +++ b/src/utils/services/projects.manager.ts @@ -1,12 +1,22 @@ import { StatusCodes } from 'http-status-codes'; import { DatabaseManager } from './database.manager'; import { Document, ObjectId, WithId } from 'mongodb'; -import { projectUpdateData } from '../../routes/projects/types'; +import { + projectPreferencesSaveData, + projectUpdateData, +} from '../../routes/projects/types'; +import { projectUpdateCriterionData } from '../../routes/projects/criteria/types'; +import { projectAddParticipantData } from '../../routes/projects/participants/types'; class ProjectsManager { private static instance: ProjectsManager; private userDatabaseManager = new DatabaseManager('Users'); private projectsDatabaseManager = new DatabaseManager('Projects'); + private criteriaDatabaseManager = new DatabaseManager('Criteria'); + private participantsDatabaseManager = new DatabaseManager('Participants'); + private participantCriteriaDatabaseManager = new DatabaseManager( + 'Participants_Criteria' + ); private constructor() {} @@ -30,6 +40,11 @@ class ProjectsManager { const projects = await this.projectsDatabaseManager.find({ user: new ObjectId(userId), }); + + let code = makeCode(); + while (await this.projectsDatabaseManager.findOne({ code })) { + code = makeCode(); + } const result = await this.projectsDatabaseManager.create({ user: new ObjectId(userId), name: `New Project ${projects.length + 1}`, @@ -37,6 +52,7 @@ class ProjectsManager { registrants: 0, group_size: 2, preferences: 0, + code: code, }); if (result.acknowledged) { return { status: StatusCodes.OK, response: 'Created project' }; @@ -100,6 +116,15 @@ class ProjectsManager { _id: project._id, }); if (result.acknowledged) { + await this.criteriaDatabaseManager.delete({ + project: project._id, + }); + await this.participantsDatabaseManager.delete({ + projectId: project._id, + }); + await this.participantCriteriaDatabaseManager.delete({ + participant: project._id, + }); return { status: StatusCodes.OK, response: 'Project deleted' }; } } catch (err) { @@ -194,6 +219,682 @@ class ProjectsManager { response: 'Failed to update project', }; } + + public async addCriterion( + 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 criteria = await this.criteriaDatabaseManager.find({ + project: new ObjectId(projectId), + }); + const result = await this.criteriaDatabaseManager.create({ + project: new ObjectId(projectId), + name: `Criterion ${criteria.length + 1}`, + range: 100, + }); + if (result.acknowledged) { + const participants = await this.participantsDatabaseManager.find({ + projectId: project._id, + }); + for (const participant of participants) { + await this.participantCriteriaDatabaseManager.create({ + participant: participant._id, + criterion: result.insertedId, + value: 0, + }); + } + return { + status: StatusCodes.OK, + response: 'Criterion added', + }; + } + } catch (err) { + console.error('Failed to add criterion:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to add criterion', + }; + } + + public async getAllCriteria( + 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', + }; + } + const criteria = await this.criteriaDatabaseManager.find({ + project: new ObjectId(projectId), + }); + return { status: StatusCodes.OK, response: criteria }; + } catch (err) { + console.error('Failed to get all criteria:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to get all criteria', + }; + } + + public async updateCriterion( + userId: string, + data: projectUpdateCriterionData + ): 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 criterion = await this.criteriaDatabaseManager.findOne({ + _id: new ObjectId(data.criterionId), + }); + if (!criterion) { + return { + status: StatusCodes.NOT_FOUND, + response: 'Criterion not found', + }; + } + const project = await this.projectsDatabaseManager.findOne({ + _id: criterion.project, + }); + 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.criteriaDatabaseManager.update( + { + _id: criterion._id, + }, + { + $set: { + ...(data.name !== undefined && { name: data.name }), + ...(data.range !== undefined && { range: data.range }), + }, + } + ); + if (result.acknowledged) { + if (data.range !== undefined) { + const participantsCriteria = + await this.participantCriteriaDatabaseManager.find({ + criterion: criterion._id, + }); + for (const participantCriterion of participantsCriteria) { + if (participantCriterion.value > data.range) { + await this.participantCriteriaDatabaseManager.update( + { _id: participantCriterion._id }, + { $set: { value: data.range } } + ); + } + } + } + return { status: StatusCodes.OK, response: 'Criterion updated' }; + } + } catch (err) { + console.error('Failed to update criterion:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to update criterion', + }; + } + + public async deleteCriterion( + userId: string, + criterionId: 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 criterion = await this.criteriaDatabaseManager.findOne({ + _id: new ObjectId(criterionId), + }); + if (!criterion) { + return { + status: StatusCodes.NOT_FOUND, + response: 'Criterion not found', + }; + } + const project = await this.projectsDatabaseManager.findOne({ + _id: criterion.project, + }); + 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.criteriaDatabaseManager.delete({ + _id: criterion._id, + }); + if (result.acknowledged) { + await this.participantCriteriaDatabaseManager.delete({ + criterion: criterion._id, + }); + return { status: StatusCodes.OK, response: 'Criterion deleted' }; + } + } catch (err) { + console.error('Failed to delete criterion:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to delete criterion', + }; + } + + public async addParticipant( + userId: string, + data: projectAddParticipantData + ): 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(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 existing = await this.participantsDatabaseManager.find({ + tz: data.tz, + }); + if (existing.length !== 0) { + return { + status: StatusCodes.CONFLICT, + response: 'Participant already exists', + }; + } + + const result = await this.participantsDatabaseManager.create({ + projectId: new ObjectId(data.projectId), + firstName: data.firstName, + lastName: data.lastName, + tz: data.tz, + }); + + if (result.acknowledged) { + const createdParticipant = + await this.participantsDatabaseManager.findOne({ + _id: result.insertedId, + }); // 👈 שליפה מהדאטהבייס + + const criteria = await this.criteriaDatabaseManager.find({ + project: project._id, + }); + + for (const criterion of criteria) { + await this.participantCriteriaDatabaseManager.create({ + participant: result.insertedId, + criterion: criterion._id, + value: 0, + }); + } + if (createdParticipant) { + return { + status: StatusCodes.OK, + response: createdParticipant, // 👈 שולחים לפרונט את כל האובייקט + }; + } + } + } catch (err) { + console.error('Failed to add participant:', err); + } + + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to add participant', + }; + } + + public async getAllParticipants( + 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', + }; + } + const participants = await this.participantsDatabaseManager.find({ + projectId: project._id, + }); + participants.forEach((participant) => { + if (participant.preferences && participant.preferences.length > 0) { + participant.preferences = true; + } else { + participant.preferences = false; + } + }); + return { + status: StatusCodes.OK, + response: participants, + }; + } catch (err) { + console.error('Failed to add participant:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to add participant', + }; + } + + public async getParticipantCriteria( + userId: string, + participantId: 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 participant = await this.participantsDatabaseManager.findOne({ + _id: new ObjectId(participantId), + }); + if (!participant) { + return { + status: StatusCodes.NOT_FOUND, + response: 'Participant not found', + }; + } + const project = await this.projectsDatabaseManager.findOne({ + _id: participant.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 criteria = await this.participantCriteriaDatabaseManager.find({ + participant: participant._id, + }); + return { + status: StatusCodes.OK, + response: criteria, + }; + } catch (err) { + console.error('Failed to get participant criteria:', err); + } + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to get participant criteria', + }; + } + + public async deleteParticipant( + userId: string, + projectId: string, + participantId: 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 does not own the project', + }; + } + + const participant = await this.participantsDatabaseManager.findOne({ + _id: new ObjectId(participantId), + }); + if (!participant) { + return { + status: StatusCodes.NOT_FOUND, + response: 'Participant not found', + }; + } + + const result = await this.participantsDatabaseManager.delete({ + _id: participant._id, + }); + + if (result.acknowledged) { + await this.participantCriteriaDatabaseManager.delete({ + participant: participant._id, + }); + + return { + status: StatusCodes.OK, + response: 'Participant deleted successfully', + }; + } + } catch (err) { + console.error('Failed to delete participant:', err); + } + + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to delete participant', + }; + } + + public async updateParticipantCriteria( + userId: string, + participantId: string, + criteria: Record + ): 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 participant = await this.participantsDatabaseManager.findOne({ + _id: new ObjectId(participantId), + }); + if (!participant) { + return { + status: StatusCodes.NOT_FOUND, + response: 'Participant not found', + }; + } + + const project = await this.projectsDatabaseManager.findOne({ + _id: participant.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 does not own the project', + }; + } + + // נעדכן כל קריטריון שנשלח לפי participant + criterionId + for (const [criterionId, value] of Object.entries(criteria)) { + const participantObjectId = new ObjectId(participantId); + const criterionObjectId = new ObjectId(criterionId); + + const filter = { + participant: participantObjectId, + criterion: criterionObjectId, + }; + const update = { $set: { value } }; + + const result = await this.participantCriteriaDatabaseManager.update( + filter, + update + ); + if (!result.acknowledged) { + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to update participant criteria', + }; + } + } + + return { + status: StatusCodes.OK, + response: 'Criteria updated successfully', + }; + } catch (error) { + console.error('Failed to update participant criteria:', error); + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to update participant criteria', + }; + } + } + + public async updateAllParticipants( + userId: string, + projectId: string, + participants: { + _id: string; + firstName: string; + lastName: string; + tz: 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 || project.user.toString() !== user._id.toString()) { + return { + status: StatusCodes.FORBIDDEN, + response: 'Unauthorized project access', + }; + } + + for (const p of participants) { + await this.participantsDatabaseManager.update( + { _id: new ObjectId(p._id) }, + { + $set: { + firstName: p.firstName, + lastName: p.lastName, + tz: p.tz, + }, + } + ); + } + + return { + status: StatusCodes.OK, + response: 'Participants updated successfully', + }; + } catch (err) { + console.error('❌ Failed to update participants:', err); + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to update participants', + }; + } + } + + public async searchProject(code: string): Promise<{ + status: number; + response: + | { + participants: { + _id: string; + firstName: string; + lastName: string; + }[]; + maxPreferences: number; + } + | string; + }> { + try { + const project = await this.projectsDatabaseManager.findOne({ + code: code, + }); + if (!project) { + return { status: StatusCodes.NOT_FOUND, response: 'Project not found' }; + } + + const participants = await this.participantsDatabaseManager.find({ + projectId: project._id, + }); + + const participantsArray = participants.map((participant) => ({ + _id: participant._id.toString(), + firstName: participant.firstName, + lastName: participant.lastName, + })); + + return { + status: StatusCodes.OK, + response: { + participants: participantsArray, + maxPreferences: project.preferences, + }, + }; + } catch (err) { + console.error('Failed to search project:', err); + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to search project', + }; + } + } + + public async savePreferences( + data: projectPreferencesSaveData + ): Promise<{ status: number; response: string }> { + try { + const participant = await this.participantsDatabaseManager.findOne({ + _id: new ObjectId(data.selectedParticipant), + tz: data.participantId, + }); + if (!participant) { + return { + status: StatusCodes.NOT_FOUND, + response: 'Participant not found or not authorized', + }; + } + + const project = await this.projectsDatabaseManager.findOne({ + _id: participant.projectId, + }); + if (!project) { + return { + status: StatusCodes.FORBIDDEN, + response: 'Unauthorized project access', + }; + } + + const preferences = await this.participantsDatabaseManager.update( + { _id: participant._id }, + { $set: { preferences: data.preferences.map((p) => new ObjectId(p)) } } + ); + + if (!preferences.acknowledged) { + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to save preferences', + }; + } + + return { + status: StatusCodes.OK, + response: 'Preferences saved successfully', + }; + } catch (err) { + console.error('Failed to save preferences:', err); + return { + status: StatusCodes.INTERNAL_SERVER_ERROR, + response: 'Failed to save preferences', + }; + } + } +} + +function makeCode() { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < 8; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; } const projectsManager = ProjectsManager.getInstance();