diff --git a/ExpressAPI/src/managers/UserManager.ts b/ExpressAPI/src/managers/UserManager.ts index 871ea380c53163bc518f752c0373d97a02ef6374..d4820af48ed073f3cf5d4013b3de1e08f18a4ce9 100644 --- a/ExpressAPI/src/managers/UserManager.ts +++ b/ExpressAPI/src/managers/UserManager.ts @@ -5,6 +5,13 @@ import * as Gitlab from '@gitbeaker/rest'; class UserManager { + async getFiltered(filters: Prisma.UserWhereInput | undefined, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User> | undefined> { + return await db.user.findMany({ + where : filters, + include: include + }) as unknown as Array<User> ?? undefined; + } + async getByMail(mail: string, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> { return await db.user.findUnique({ where : { @@ -14,10 +21,10 @@ class UserManager { }) as unknown as User ?? undefined; } - async getById(id: number, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> { + async getById(id: string | number, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> { return await db.user.findUnique({ where : { - id: id + id: Number(id) }, include: include }) as unknown as User ?? undefined; diff --git a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts index 1dffaef8a782bb06c01e485705afc830e563f441..c3355a6d47215f7c236888d5cb9d10c799e72675 100644 --- a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts +++ b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts @@ -5,6 +5,7 @@ import ExerciseManager from '../managers/ExerciseManager'; import AssignmentManager from '../managers/AssignmentManager'; import TagManager from '../managers/TagManager'; import TagProposalManager from '../managers/TagProposalManager'; +import UserManager from '../managers/UserManager'; type GetFunction = (id: string | number, ...args: Array<unknown>) => Promise<unknown> @@ -29,6 +30,7 @@ class ParamsCallbackManager { initBoundParams(req: express.Request) { if ( !req.boundParams ) { req.boundParams = { + user : undefined, assignment : undefined, exercise : undefined, tag : undefined, @@ -38,13 +40,31 @@ class ParamsCallbackManager { } registerOnBackend(backend: Express) { + this.listenParam('userId', backend, (UserManager.getById as GetFunction).bind(UserManager), [ { + assignments: true, + exercises : { + include: { + members : true, + assignment: { + include: { + staff: true + } + } + } + } + } ], 'user'); + this.listenParam('assignmentNameOrUrl', backend, (AssignmentManager.get as GetFunction).bind(AssignmentManager), [ { exercises: true, staff : true } ], 'assignment'); this.listenParam('exerciseIdOrUrl', backend, (ExerciseManager.get as GetFunction).bind(ExerciseManager), [ { - assignment: true, + assignment: { + include: { + staff: true + } + }, members : true, results : true } ], 'exercise'); diff --git a/ExpressAPI/src/routes/ApiRoutesManager.ts b/ExpressAPI/src/routes/ApiRoutesManager.ts index 1508de4124baa104b7aac892da229685ecb9b998..1110c107f068052a254b5c3e1c55c681c2b9f461 100644 --- a/ExpressAPI/src/routes/ApiRoutesManager.ts +++ b/ExpressAPI/src/routes/ApiRoutesManager.ts @@ -6,6 +6,7 @@ import AssignmentRoutes from './AssignmentRoutes.js'; import GitlabRoutes from './GitlabRoutes.js'; import ExerciseRoutes from './ExerciseRoutes.js'; import TagsRoutes from './TagRoutes'; +import UserRoutes from './UserRoutes'; class AdminRoutesManager implements RoutesManager { @@ -16,6 +17,7 @@ class AdminRoutesManager implements RoutesManager { AssignmentRoutes.registerOnBackend(backend); ExerciseRoutes.registerOnBackend(backend); TagsRoutes.registerOnBackend(backend); + UserRoutes.registerOnBackend(backend); } } diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts index ae3d4b9efc6e775f5a5887c16de6165d33bd5d28..8f73c0eeb12d884b253106ac0b4bba8d04e126e7 100644 --- a/ExpressAPI/src/routes/ExerciseRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -70,20 +70,68 @@ class ExerciseRoutes implements RoutesManager { backend.get('/exercises', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.getAllExercises.bind(this) as RequestHandler); backend.get('/exercises/:exerciseIdOrUrl/assignment', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), this.getAssignment.bind(this) as RequestHandler); + backend.get('/exercises/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.getExercise.bind(this) as RequestHandler); backend.get('/exercises/:exerciseIdOrUrl/members', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.getExerciseMembers.bind(this) as RequestHandler); backend.get('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.getExerciseResults.bind(this) as RequestHandler); backend.delete('/exercises/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.deleteExercise.bind(this) as RequestHandler); + backend.get('/users/:userId/exercises', SecurityMiddleware.check(true), this.getUserExercises.bind(this) as RequestHandler); + + backend.get('/exercises/:exerciseIdOrLink/results', SecurityMiddleware.check(true), this.getExerciseResultsByIdOrLink.bind(this) as RequestHandler); backend.post('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this) as RequestHandler); } + private async getExerciseResultsByIdOrLink(req: express.Request, res: express.Response) { + const exerciseIdOrLink = req.params.exerciseIdOrLink; + + + const exercise = await db.exercise.findFirst({ + where: { + OR: [ { id: exerciseIdOrLink }, { gitlabLink: exerciseIdOrLink } ] + } + }); + + if ( !exercise ) { + return res.status(StatusCodes.NOT_FOUND).send('Exercise not found'); + } + + + const results = await db.result.findMany({ + where: { exerciseId: exercise.id } + }); + + return res.status(StatusCodes.OK).json(results); + } + + private async getAllExercises(req: express.Request, res: express.Response) { + const exos = await db.exercise.findMany(); + + return req.session.sendResponse(res, StatusCodes.OK, exos); + } + + private async getUserExercises(req: express.Request, res: express.Response) { + if ( req.boundParams.user ) { + if ( req.session.profile.isAdmin || req.session.profile.id === req.boundParams.user.id ) { + return req.session.sendResponse(res, StatusCodes.OK, req.boundParams.user.exercises.filter(exercise => !exercise.deleted)); + } else { + return req.session.sendResponse(res, StatusCodes.FORBIDDEN); + } + } else { + return req.session.sendResponse(res, StatusCodes.NOT_FOUND); + } + } + private getExerciseName(assignment: Assignment, members: Array<Gitlab.UserSchema>, suffix: number): string { const memberNames: string = members.map(member => member.username).sort((a, b) => a.localeCompare(b)).join(' + '); const suffixString: string = suffix > 0 ? ` - ${ suffix }` : ''; return `DojoEx - ${ assignment.name } - ${ memberNames }${ suffixString }`; } + private async getExercise(req: express.Request, res: express.Response) { + return req.session.sendResponse(res, StatusCodes.OK, req.boundParams.exercise!); + } + private async getExerciseMembers(req: express.Request, res: express.Response) { const repoId = req.boundParams.exercise!.gitlabId; @@ -133,13 +181,6 @@ class ExerciseRoutes implements RoutesManager { 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 exos = await db.exercise.findMany(); - - return req.session.sendResponse(res, StatusCodes.OK, exos); - } - private async checkExerciseLimit(assignment: Assignment, members: Array<Gitlab.UserSchema>): Promise<Array<Gitlab.UserSchema>> { const exercises: Array<Exercise> | undefined = await ExerciseManager.getFromAssignment(assignment.name, false, { members: true }); @@ -225,33 +266,42 @@ class ExerciseRoutes implements RoutesManager { await repoCreationFnExec(async () => Promise.all([ ...new Set([ ...assignment.staff, ...params.members ].map(member => member.id)) ].map(GlobalHelper.addRepoMember(repository.id))), 'Add repository members error'); - const exercise: Exercise = await repoCreationFnExec(() => db.exercise.create({ - data: { - id : exerciseId, - assignmentName : assignment.name, - name : repository.name, - secret : secret, - gitlabId : repository.id, - gitlabLink : repository.web_url, - gitlabCreationInfo: repository as unknown as Prisma.JsonObject, - gitlabCreationDate: new Date(), - gitlabLastInfo : repository as unknown as Prisma.JsonObject, - gitlabLastInfoDate: new Date(), - members : { - connectOrCreate: [ ...params.members.map(gitlabUser => { - return { - create: { - id : gitlabUser.id, - gitlabUsername: gitlabUser.name - }, - where : { - id: gitlabUser.id - } - }; - }) ] - } - } - })) as Exercise; + let exercise: Exercise = await repoCreationFnExec(() => db.exercise.create({ + data: { + id : exerciseId, + assignmentName : assignment.name, + name : repository.name, + secret : secret, + gitlabId : repository.id, + gitlabLink : repository.web_url, + gitlabCreationInfo: repository as unknown as Prisma.JsonObject, + gitlabCreationDate: new Date(), + gitlabLastInfo : repository as unknown as Prisma.JsonObject, + gitlabLastInfoDate: new Date(), + members : { + connectOrCreate: [ ...params.members.map(gitlabUser => { + return { + create: { + id : gitlabUser.id, + gitlabUsername: gitlabUser.name + }, + where : { + id: gitlabUser.id + } + }; + }) ] + } + } + })) as Exercise; + + exercise = await ExerciseManager.get(exercise.id, { + members : true, + assignment: { + include: { + staff: true + } + } + }) as Exercise; req.session.sendResponse(res, StatusCodes.OK, exercise); return; diff --git a/ExpressAPI/src/routes/UserRoutes.ts b/ExpressAPI/src/routes/UserRoutes.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f729fe14f25c4ab8ff97ef118062737dbd6b06c --- /dev/null +++ b/ExpressAPI/src/routes/UserRoutes.ts @@ -0,0 +1,58 @@ +import { Express } from 'express-serve-static-core'; +import express, { RequestHandler } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import RoutesManager from '../express/RoutesManager.js'; +import SecurityMiddleware from '../middlewares/SecurityMiddleware'; +import * as ExpressValidator from 'express-validator'; +import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware'; +import { Prisma, UserRole } from '@prisma/client'; +import UserManager from '../managers/UserManager'; + + +class UserRoutes implements RoutesManager { + private readonly usersGetValidator: ExpressValidator.Schema = { + role: { + trim : true, + notEmpty: false, + optional: true + } + }; + + registerOnBackend(backend: Express) { + backend.get('/users', SecurityMiddleware.check(true), ParamsValidatorMiddleware.validate(this.usersGetValidator), this.getUsers.bind(this) as RequestHandler); + } + + private async getUsers(req: express.Request, res: express.Response) { + + let roleFilter: Prisma.UserWhereInput | undefined = undefined; + + if ( req.query.role ) { + if ( req.query.role === UserRole.ADMIN ) { + roleFilter = { + role: UserRole.ADMIN + }; + } else if ( req.query.role === UserRole.TEACHING_STAFF ) { + roleFilter = { + OR: [ { + role: UserRole.ADMIN + }, { + role: UserRole.TEACHING_STAFF + } ] + }; + } else if ( req.query.role === UserRole.STUDENT ) { + roleFilter = { + role: UserRole.STUDENT + }; + } else { + return req.session.sendResponse(res, StatusCodes.FORBIDDEN); + } + } else if ( !req.session.profile.isAdmin ) { + return req.session.sendResponse(res, StatusCodes.FORBIDDEN); + } + + return req.session.sendResponse(res, StatusCodes.OK, await UserManager.getFiltered(roleFilter)); + } +} + + +export default new UserRoutes(); diff --git a/ExpressAPI/src/types/express/index.d.ts b/ExpressAPI/src/types/express/index.d.ts index 59a5e0056b7d85185e11d49a66d82153cab01192..0c90584540c7e56c6ae4e0db55008d0c89dc9f74 100644 --- a/ExpressAPI/src/types/express/index.d.ts +++ b/ExpressAPI/src/types/express/index.d.ts @@ -1,6 +1,6 @@ -import Session from '../../controllers/Session.js'; -import { Assignment, Exercise, Tag } from '../DatabaseTypes'; -import { TagProposal } from '@prisma/client'; +import Session from '../../controllers/Session.js'; +import { Assignment, Exercise, Tag, User } from '../DatabaseTypes'; +import { TagProposal } from '@prisma/client'; // to make the file a module and avoid the TypeScript error export {}; @@ -10,7 +10,7 @@ declare global { export interface Request { session: Session, boundParams: { - assignment: Assignment | undefined, exercise: Exercise | undefined, tag: Tag | undefined, tagProposal: TagProposal | undefined + user: User | undefined, assignment: Assignment | undefined, exercise: Exercise | undefined, tag: Tag | undefined, tagProposal: TagProposal | undefined } } }