diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b58b603fea78041071d125a30db58d79b3d49217 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/dojobackendapi.iml b/.idea/dojobackendapi.iml new file mode 100644 index 0000000000000000000000000000000000000000..24643cc37449b4bde54411a80b8ed61258225e34 --- /dev/null +++ b/.idea/dojobackendapi.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="WEB_MODULE" version="4"> + <component name="NewModuleRootManager"> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/.tmp" /> + <excludeFolder url="file://$MODULE_DIR$/temp" /> + <excludeFolder url="file://$MODULE_DIR$/tmp" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..53aaca2013eb5b498566e71713e4c4feca197ee3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/dojobackendapi.iml" filepath="$PROJECT_DIR$/.idea/dojobackendapi.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/ExpressAPI/package.json b/ExpressAPI/package.json index 741cb443fe7d7a24293f741254b944dc811d8265..a49a79d592d0815d80dcb4c082595bc5cec88f49 100644 --- a/ExpressAPI/package.json +++ b/ExpressAPI/package.json @@ -14,17 +14,17 @@ "build:project" : "npm run genversion; npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets", "build" : "npm run build:openapi; npm run build:project", "database:migrate:create": "npx dotenv -e .env.development -- npx prisma migrate dev", - "database:migrate:dev" : "npx dotenv -e .env.development -- npx prisma migrate deploy", - "database:migrate:prod" : "npx prisma migrate deploy", - "database:seed:dev" : "npm run genversion; npm run build; npx dotenv -e .env.development -- npx prisma db seed", - "database:seed:prod" : "npm run genversion; npm run build; NODE_ENV=production npx prisma db seed", - "database:deploy:dev" : "npm run database:migrate:dev && npm run database:seed:dev", - "database:deploy:prod" : "npm run database:migrate:prod && npm run database:seed:prod", - "start:dev" : "npm run genversion; npx nodemon src/app.ts", - "start:prod" : "npm run genversion; NODE_ENV=production npx node --max-http-header-size=1048576 dist/src/app.js", - "start:migrate:prod" : "npm run genversion; npm run database:deploy:prod && npm run start:prod" + "database:migrate:dev": "npx dotenv -e .env.development -- npx prisma migrate deploy", + "database:migrate:prod": "npx prisma migrate deploy", + "database:seed:dev": "npm run genversion; npm run build; npx dotenv -e .env.development -- npx prisma db seed", + "database:seed:prod": "npm run genversion; npm run build; NODE_ENV=production npx prisma db seed", + "database:deploy:dev": "npm run database:migrate:dev && npm run database:seed:dev", + "database:deploy:prod": "npm run database:migrate:prod && npm run database:seed:prod", + "start:dev": "npm run genversion; npx nodemon src/app.ts", + "start:prod": "npm run genversion; NODE_ENV=production npx node --max-http-header-size=1048576 dist/src/app.js", + "start:migrate:prod": "npm run genversion; npm run database:deploy:prod && npm run start:prod" }, - "prisma" : { + "prisma": { "seed": "node dist/prisma/seed" }, "dependencies" : { diff --git a/ExpressAPI/prisma/migrations/20240523212938_add_deleted_to_exercise/migration.sql b/ExpressAPI/prisma/migrations/20240523212938_add_deleted_to_exercise/migration.sql new file mode 100644 index 0000000000000000000000000000000000000000..4d82e3a5c8054818e93ff3fdece07ad890f4cb64 --- /dev/null +++ b/ExpressAPI/prisma/migrations/20240523212938_add_deleted_to_exercise/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `Exercise` ADD COLUMN `deleted` BOOLEAN NOT NULL DEFAULT false; diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma index 69e3b2678b3e6e6548bc86c6f713b5560ccc31f0..706daad05ce5bee9cbfad57265357b94c50fba32 100644 --- a/ExpressAPI/prisma/schema.prisma +++ b/ExpressAPI/prisma/schema.prisma @@ -57,6 +57,8 @@ model Exercise { gitlabCreationInfo Json @db.Json gitlabLastInfo Json @db.Json gitlabLastInfoDate DateTime + deleted Boolean @default(false) + correctionCommit Json? @db.Json correctionDescription String? @db.VarChar(80) diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index bd2746ab4323f39d16b3ab7cee410c463b42ddfe..5ad9dca9dd1bad96492bdb4dd32040c040442aaa 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -29,9 +29,9 @@ class GitlabManager extends SharedGitlabManager { } } - async getRepositoryMembers(idOrNamespace: string): Promise<Array<MemberSchema>> { + async getRepositoryMembers(idOrNamespace: string, includeInherited: boolean = true): Promise<Array<MemberSchema>> { try { - return await this.api.ProjectMembers.all(idOrNamespace, { includeInherited: true }); + return await this.api.ProjectMembers.all(idOrNamespace, { includeInherited: includeInherited }); } catch ( e ) { logger.error(JSON.stringify(e)); return Promise.reject(e); @@ -122,6 +122,27 @@ class GitlabManager extends SharedGitlabManager { } } + async deleteRepositoryMember(repoId: number, userId: number, skipSubresources: boolean = false, unassignIssuables: boolean = false): Promise<void> { + try { + return await this.api.ProjectMembers.remove(repoId, userId, { + skipSubresourceS: skipSubresources, + unassignIssuables + }); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } + } + + async moveRepository(repoId: number, newRepoId: number): Promise<ProjectSchema> { + try { + return await this.api.Projects.transfer(repoId, newRepoId); + } catch ( e ) { + logger.error(JSON.stringify(e)); + return Promise.reject(e); + } + } + changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<ProjectSchema> { return this.editRepository(repoId, { visibility: visibility }); } diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index 0f06d74728a684153763d8b7d710809ff6f6e9f4..40dfbaf272174c79228d4b7733ae9e091771e969 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -82,6 +82,19 @@ class AssignmentRoutes implements RoutesManager { backend.delete('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unlinkAssignmentCorrection.bind(this) as RequestHandler); } + // private async optionsAssignments(req: express.Request, res: express.Response) { + // try { + // const assignments = await db.assignment.findMany(); + // res.setHeader('Access-Control-Allow-Origin', '*'); + // res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); + // res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + // res.status(200).json(assignments); + // } catch (error) { + // console.error('Erreur lors de la récupération de tous les devoirs :', error); + // res.status(500).send('Erreur lors de la récupération de tous les devoirs'); + // } + // } + // Get an assignment by its name or gitlab url private async getAssignment(req: express.Request, res: express.Response) { const assignment: Partial<Assignment> | undefined = req.boundParams.assignment; @@ -124,6 +137,18 @@ class AssignmentRoutes implements RoutesManager { } } + private async getAllAssignments(req: express.Request, res: express.Response) { + console.log('Test Get ') + try { + const assignments = await db.assignment.findMany(); + console.log(assignments); + res.status(StatusCodes.OK).json(assignments); + } catch (error) { + console.error('Erreur lors de la récupération de tous les devoirs :', error); + res.status(StatusCodes.INTERNAL_SERVER_ERROR).send('Erreur lors de la récupération de tous les devoirs'); + } + } + private async createAssignment(req: express.Request, res: express.Response) { const params: { name: string, members: Array<Gitlab.UserSchema>, template: string diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts index b32ab8b22983feed2cef9d06768bedfc0b70b2c6..e1fef15b11a4d3d91b3dd7d364d3775d372a3552 100644 --- a/ExpressAPI/src/routes/ExerciseRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -67,7 +67,13 @@ class ExerciseRoutes implements RoutesManager { registerOnBackend(backend: Express) { backend.post('/assignments/:assignmentNameOrUrl/exercises', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciseValidator), this.createExercise.bind(this) as RequestHandler); + backend.get('/exercises', this.getAllExercises.bind(this)); backend.get('/exercises/:exerciseIdOrUrl/assignment', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), this.getAssignment.bind(this) as RequestHandler); + backend.get('/exercises/:exerciseId/members', SecurityMiddleware.check(true), this.getExerciseMembers.bind(this)); + backend.get('/exercises/:exerciseId/results', SecurityMiddleware.check(true), this.getExerciseResults.bind(this)); + backend.get('/exercises/:idOrLink/results', SecurityMiddleware.check(true), this.getExerciseResultsByIdOrLink.bind(this)); + + backend.patch('/exercises/:exerciseId/deleted', SecurityMiddleware.check(true), this.deleteExercise.bind(this)); backend.post('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this) as RequestHandler); } @@ -78,10 +84,137 @@ class ExerciseRoutes implements RoutesManager { return `DojoEx - ${ assignment.name } - ${ memberNames }${ suffixString }`; } + private async getExerciseResultsByIdOrLink(req: express.Request, res: express.Response) { + const idOrLink = req.params.idOrLink; + + console.log(`Fetching results for exercise with ID or link: ${ idOrLink }`); + + // Chercher l'exercice par ID ou lien GitLab + const exercise = await db.exercise.findFirst({ + where: { + OR: [ { id: idOrLink }, { gitlabLink: idOrLink } ] + } + }); + + if ( !exercise ) { + console.log(`Exercise with ID or link ${ idOrLink } not found`); + return res.status(StatusCodes.NOT_FOUND).send('Exercise not found'); + } + + console.log(`Exercise found: ${ exercise.id }`); + + const results = await db.result.findMany({ + where: { exerciseId: exercise.id } + }); + + console.log(`Results fetched:`, results); + + return res.status(StatusCodes.OK).json(results); + } + + private async getExerciseMembers(req: express.Request, res: express.Response) { + const exerciseId = req.params.exerciseId; + + // Retrieve exercise details from the database + const repo = await db.exercise.findUnique({ + where: { id: exerciseId } + }); + + if ( !repo ) { + return res.status(StatusCodes.NOT_FOUND).send('Exercise not found'); + } + + const repoId = repo.gitlabId; + console.log(`Fetching members for repo ID: ${ repoId }`); + + const members = await GitlabManager.getRepositoryMembers(String(repoId)); + console.log('Member IDs and names of the repository:'); + members.forEach(async member => { + console.log(`ID: ${ member.id }, Name: ${ member.name }`); + }); + return req.session.sendResponse(res, StatusCodes.OK, members); + } + + private async getExerciseResults(req: express.Request, res: express.Response) { + const exerciseId = req.params.exerciseId; + + // Chercher l'exercice par ID ou lien GitLab + const exercise = await db.exercise.findFirst({ + where: { + OR: [ { id: exerciseId } ] + } + }); + + if ( exercise ) { + console.log(exercise); + } + + if ( !exercise ) { + return res.status(StatusCodes.NOT_FOUND).send('Exercise not found'); + } + + // Récupérer les résultats de l'exercice + const results = await db.result.findMany({ + where : { exerciseId: exercise.id }, + orderBy: { dateTime: 'desc' } // Trier les résultats par date décroissante + }); + + return res.status(StatusCodes.OK).json(results); + } + + private async deleteExercise(req: express.Request, res: express.Response) { + const exerciseId = req.params.exerciseId; + + // Retrieve exercise details from the database + const repo = await db.exercise.findUnique({ + where: { id: exerciseId } + }); + + if ( !repo ) { + return res.status(StatusCodes.NOT_FOUND).send('Exercise not found'); + } + console.log(repo.name); + + + //const repoId = 14232; + const repoId = repo.gitlabId; + console.log(repoId); + + const newRepoId = 14192; + + + const members = await GitlabManager.getRepositoryMembers(String(repoId), false); + console.log('Member IDs and names of the repository:'); + members.forEach(async member => { + console.log(`ID: ${ member.id }, Name: ${ member.name }`); + if ( member.id !== Config.gitlab.account.id ) { + await GitlabManager.deleteRepositoryMember(repoId, member.id); + } + }); + + await GitlabManager.moveRepository(repoId, newRepoId); + + + await db.exercise.update({ + where: { id: exerciseId }, + data : { deleted: true } + }); + + return res.status(StatusCodes.OK).send('Exercise deleted successfully'); + } + private getExercisePath(assignment: Assignment, exerciseId: string): string { return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as Gitlab.ProjectSchema).path }_${ exerciseId }`; } + // Get all exercise + private async getAllExercises(req: express.Request, res: express.Response) { + const exo = await db.exercise.findMany(); + console.log(exo); + return req.session.sendResponse(res, StatusCodes.OK, exo); + } + + private async checkExerciseLimit(assignment: Assignment, members: Array<Gitlab.UserSchema>): Promise<Array<Gitlab.UserSchema>> { const exercises: Array<Exercise> | undefined = await ExerciseManager.getFromAssignment(assignment.name, { members: true }); const reachedLimitUsers: Array<Gitlab.UserSchema> = []; @@ -259,3 +392,5 @@ class ExerciseRoutes implements RoutesManager { export default new ExerciseRoutes(); + +