From a2be64f054bf3c5c0b4a2c1886ce28354b5f9671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <michael@minelli.me> Date: Wed, 5 Jul 2023 17:50:50 +0200 Subject: [PATCH] Migrate to Prisma --- ExpressAPI/src/config/Config.ts | 16 ---- ExpressAPI/src/controllers/Session.ts | 18 ++--- ExpressAPI/src/helpers/DatabaseHelper.ts | 38 +++++++-- .../Prisma/Extensions/UserQueryExtension.ts | 31 ++++++++ .../Prisma/Extensions/UserResultExtension.ts | 33 ++++++++ ExpressAPI/src/managers/EnonceManager.ts | 33 ++++---- ExpressAPI/src/managers/GitlabManager.ts | 2 +- ExpressAPI/src/managers/UserManager.ts | 79 ++++++++----------- .../src/middlewares/ParamsCallbackManager.ts | 16 +++- .../src/middlewares/SecurityMiddleware.ts | 4 +- ExpressAPI/src/routes/EnonceRoutes.ts | 59 ++++++++------ ExpressAPI/src/routes/ExerciceRoutes.ts | 71 +++++++++-------- ExpressAPI/src/routes/SessionRoutes.ts | 4 +- ExpressAPI/src/types/DatabaseTypes.ts | 34 ++++++++ 14 files changed, 277 insertions(+), 161 deletions(-) create mode 100644 ExpressAPI/src/helpers/Prisma/Extensions/UserQueryExtension.ts create mode 100644 ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts create mode 100644 ExpressAPI/src/types/DatabaseTypes.ts diff --git a/ExpressAPI/src/config/Config.ts b/ExpressAPI/src/config/Config.ts index ae50eb1..1473320 100644 --- a/ExpressAPI/src/config/Config.ts +++ b/ExpressAPI/src/config/Config.ts @@ -1,6 +1,3 @@ -import Toolbox from '../shared/helpers/Toolbox'; - - class Config { private static _instance: Config; @@ -8,10 +5,6 @@ class Config { port: number }; - public readonly database: { - type: string, host: string, port: number, user: string, password: string, database: string - }; - public jwtConfig: { secret: string; expiresIn: number; }; @@ -40,15 +33,6 @@ class Config { port: Number(process.env.API_PORT) }; - this.database = { - type : process.env.DATABASE_TYPE, - host : process.env.DATABASE_HOST, - port : Number(process.env.DATABASE_PORT), - user : process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_NAME - }; - this.jwtConfig = { secret : process.env.JWT_SECRET_KEY, expiresIn: Number(process.env.SESSION_TIMEOUT) diff --git a/ExpressAPI/src/controllers/Session.ts b/ExpressAPI/src/controllers/Session.ts index aa6fdd3..3abcc18 100644 --- a/ExpressAPI/src/controllers/Session.ts +++ b/ExpressAPI/src/controllers/Session.ts @@ -3,20 +3,21 @@ import * as jwt from 'jsonwebtoken'; import { JwtPayload } from 'jsonwebtoken'; import Config from '../config/Config'; import express from 'express'; -import ApiRequest from '../models/ApiRequest'; -import User from '../models/User'; +import ApiRequest from '../types/ApiRequest'; import UserManager from '../managers/UserManager'; +import DojoResponse from '../shared/types/DojoResponse'; +import { User } from '../types/DatabaseTypes'; class Session { - private _profile: User = new User(); + private _profile: User; get profile(): User { return this._profile; } set profile(newProfile: User) { - newProfile.currentSession = this; + delete newProfile.password; this._profile = newProfile; } @@ -31,9 +32,8 @@ class Session { const jwtData = jwt.verify(jwtToken, Config.jwtConfig.secret) as JwtPayload; if ( jwtData.profile ) { - this.profile.importFromJsonObject(jwtData.profile); - this.profile = await UserManager.getById(this.profile.userId); - this.profile.currentSession = this; + this.profile = jwtData.profile; + this.profile = await UserManager.getById(this.profile.id); } } catch ( err ) { } } @@ -43,8 +43,8 @@ class Session { return profileJson.id === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {}); } - private async getResponse(code: number, data: any, descriptionOverride?: string): Promise<any> { - const profileJson = await this.profile.toJsonObject(); + private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoResponse<T>> { + const profileJson = this.profile; let reasonPhrase = ''; diff --git a/ExpressAPI/src/helpers/DatabaseHelper.ts b/ExpressAPI/src/helpers/DatabaseHelper.ts index 9715876..89ec9c7 100644 --- a/ExpressAPI/src/helpers/DatabaseHelper.ts +++ b/ExpressAPI/src/helpers/DatabaseHelper.ts @@ -1,11 +1,35 @@ -import knex from 'knex'; -import Config from '../config/Config'; +import { PrismaClient } from '@prisma/client'; +import logger from '../shared/logging/WinstonLogger'; +import UserQueryExtension from './Prisma/Extensions/UserQueryExtension'; +import UserResultExtension from './Prisma/Extensions/UserResultExtension'; -const db = knex({ - client : Config.database.type, - connection: Config.database - }); +const prisma = new PrismaClient({ + log: [ { + emit : 'event', + level: 'query' + }, { + emit : 'event', + level: 'info' + }, { + emit : 'event', + level: 'warn' + }, { + emit : 'event', + level: 'error' + } ] + }); +prisma.$on('query', e => { + logger.debug(`Prisma => Query (${ e.duration }ms): ${ e.query }`); + logger.debug(`Prisma => Params: ${ e.params }\n`); +}); +prisma.$on('info', e => logger.info(`Prisma => ${ e.message }`)); +prisma.$on('warn', e => logger.warn(`Prisma => ${ e.message }`)); +prisma.$on('error', e => logger.error(`Prisma => ${ e.message }`)); -export default db; + +const db = prisma.$extends(UserQueryExtension).$extends(UserResultExtension); + + +export default db; \ No newline at end of file diff --git a/ExpressAPI/src/helpers/Prisma/Extensions/UserQueryExtension.ts b/ExpressAPI/src/helpers/Prisma/Extensions/UserQueryExtension.ts new file mode 100644 index 0000000..0627c77 --- /dev/null +++ b/ExpressAPI/src/helpers/Prisma/Extensions/UserQueryExtension.ts @@ -0,0 +1,31 @@ +import { Prisma } from '@prisma/client'; + + +export default Prisma.defineExtension(client => { + return client.$extends({ + query: { + user: { + async delete({ + args + }) { + + return client.user.update(Object.assign(args, { + data: { + deleted: true + } + })); + }, + async deleteMany({ + args + }) { + + return client.user.updateMany(Object.assign(args, { + data: { + deleted: true + } + })); + } + } + } + }); +}); \ No newline at end of file diff --git a/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts b/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts new file mode 100644 index 0000000..604b6de --- /dev/null +++ b/ExpressAPI/src/helpers/Prisma/Extensions/UserResultExtension.ts @@ -0,0 +1,33 @@ +import { Prisma } from '@prisma/client'; +import Config from '../../../config/Config'; +import LazyVal from '../../../shared/helpers/LazyVal'; +import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; +import GitlabManager from '../../../managers/GitlabManager'; + + +export default Prisma.defineExtension(client => { + return client.$extends({ + result: { + user: { + isTeachingStaff: { + needs: { + role: true + }, + compute(user) { + return Config.permissions.teachingStaff.includes(user.role); + } + }, + gitlabProfile : { + needs: { + gitlabId: true + }, + compute(user) { + return new LazyVal<GitlabUser>(() => { + return GitlabManager.getUserById(user.gitlabId); + }); + } + } + } + } + }); +}); \ No newline at end of file diff --git a/ExpressAPI/src/managers/EnonceManager.ts b/ExpressAPI/src/managers/EnonceManager.ts index 03022d8..1264464 100644 --- a/ExpressAPI/src/managers/EnonceManager.ts +++ b/ExpressAPI/src/managers/EnonceManager.ts @@ -1,5 +1,6 @@ -import db from '../helpers/DatabaseHelper'; -import Enonce from '../models/Enonce'; +import { Prisma } from '@prisma/client'; +import { Enonce } from '../types/DatabaseTypes'; +import db from '../helpers/DatabaseHelper'; class EnonceManager { @@ -15,30 +16,24 @@ class EnonceManager { return EnonceManager._instance; } - createObjectFromRawSql(raw: any): Enonce { - const enonce = Enonce.createFromSql(raw); - - enonce.enonceGitlabCreationInfo = raw.enonceGitlabCreationInfo; - enonce.enonceGitlabLastInfo = raw.enonceGitlabLastInfo; - - return enonce; - } - - async getByName(name: string): Promise<Enonce | undefined> { - const raw = await db<Enonce>(Enonce.tableName).where('enonceName', name).first(); - - return raw ? this.createObjectFromRawSql(raw) : undefined; + async getByName(name: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> { + return db.enonce.findUnique({ + where : { + name: name + }, + include: include + }); } - getByGitlabLink(gitlabLink: string): Promise<Enonce | undefined> { + getByGitlabLink(gitlabLink: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> { const name = gitlabLink.replace('.git', '').split('/').pop(); - return this.getByName(name); + return this.getByName(name, include); } - get(nameOrUrl: string): Promise<Enonce | undefined> { + get(nameOrUrl: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> { // We can use the same function for both name and url because the name is the last part of the url and the name extraction from the url doesn't corrupt the name - return this.getByGitlabLink(nameOrUrl); + return this.getByGitlabLink(nameOrUrl, include); } } diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index d1a6d5d..8474c7d 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -113,7 +113,7 @@ class GitlabManager { }; members.forEach(member => { if ( member.access_level >= GitlabAccessLevel.REPORTER ) { - if ( member.id === req.session.profile.userGitlabId ) { + if ( member.id === req.session.profile.gitlabId ) { isUsersAtLeastReporter.user = true; } else if ( member.id === Config.gitlab.account.id ) { isUsersAtLeastReporter.dojo = true; diff --git a/ExpressAPI/src/managers/UserManager.ts b/ExpressAPI/src/managers/UserManager.ts index 748ca4c..5456838 100644 --- a/ExpressAPI/src/managers/UserManager.ts +++ b/ExpressAPI/src/managers/UserManager.ts @@ -1,7 +1,7 @@ -import User from '../models/User'; -import db from '../helpers/DatabaseHelper'; -import GitlabUser from '../shared/types/Gitlab/GitlabUser'; -import EnonceStaff from '../models/EnonceStaff'; +import GitlabUser from '../shared/types/Gitlab/GitlabUser'; +import { Prisma } from '@prisma/client'; +import db from '../helpers/DatabaseHelper'; +import { User } from '../types/DatabaseTypes'; class UserManager { @@ -17,59 +17,50 @@ class UserManager { return UserManager._instance; } - createObjectFromRawSql(raw: any): User { - return User.createFromSql(raw); + async getByMail(mail: string, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> { + return db.user.findUnique({ + where : { + mail: mail + }, + include: include + }); } - async getByMail(mail: string): Promise<User | undefined> { - const raw = await db<User>(User.tableName).where('userMail', mail).first(); - - return raw ? this.createObjectFromRawSql(raw) : undefined; - } - - async getById(id: number): Promise<User | undefined> { - const raw = await db<User>(User.tableName).where('userId', id).first(); - - return raw ? this.createObjectFromRawSql(raw) : undefined; - } - - async getByIds(ids: Array<number>): Promise<Array<User>> { - return Promise.all(ids.map(userId => this.getById(userId))); + async getById(id: number, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> { + return await db.user.findUnique({ + where : { + id: id + }, + include: include + }); } - async getByGitlabId(gitlabId: number): Promise<User | number> { - const raw = await db<User>(User.tableName).where('userGitlabId', gitlabId).first(); - - return raw ? this.createObjectFromRawSql(raw) : gitlabId; - } - - async getByGitlabIds(gitlabIds: Array<number>): Promise<Array<User | number>> { - return Promise.all(gitlabIds.map(gitlabId => this.getByGitlabId(gitlabId))); + async getByGitlabId(gitlabId: number, returnIdIfUndefined: boolean = true, include: Prisma.UserInclude | undefined = undefined): Promise<User | number | undefined> { + return (await db.user.findUnique({ + where : { + gitlabId: gitlabId + }, + include: include + })) ?? (returnIdIfUndefined ? gitlabId : undefined); } - async getFromGitlabUser(gitlabUser: GitlabUser, createIfNotExist: boolean = false): Promise<User | number> { - let user = await this.getByGitlabId(gitlabUser.id); + async getFromGitlabUser(gitlabUser: GitlabUser, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<User | number> { + let user = await this.getByGitlabId(gitlabUser.id, true, include); if ( typeof user === 'number' && createIfNotExist ) { - user = await User.createFromSql({ - userFirstName: gitlabUser.name, - userGitlabId : gitlabUser.id - }).create(); + user = (await db.user.create({ + data: { + firstname: gitlabUser.name, + gitlabId : gitlabUser.id + } + })).id; } return user; } - async getFromGitlabUsers(gitlabUsers: Array<GitlabUser>, createIfNotExist: boolean = false): Promise<Array<User | number>> { - return Promise.all(gitlabUsers.map(gitlabUser => this.getFromGitlabUser(gitlabUser, createIfNotExist))); - } - - async getStaffOfEnonce(enonceName: string): Promise<Array<User>> { - const raw = await db<User>(User.tableName) - .innerJoin(EnonceStaff.tableName, `${ EnonceStaff.tableName }.userId`, `${ User.tableName }.userId`) - .where('enonceName', enonceName); - - return raw ? raw.map(user => this.createObjectFromRawSql(user)) : []; + async getFromGitlabUsers(gitlabUsers: Array<GitlabUser>, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User | number>> { + return Promise.all(gitlabUsers.map(gitlabUser => this.getFromGitlabUser(gitlabUser, createIfNotExist, include))); } } diff --git a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts index 7626cde..4c09baa 100644 --- a/ExpressAPI/src/middlewares/ParamsCallbackManager.ts +++ b/ExpressAPI/src/middlewares/ParamsCallbackManager.ts @@ -1,5 +1,5 @@ import { Express } from 'express-serve-static-core'; -import ApiRequest from '../models/ApiRequest'; +import ApiRequest from '../types/ApiRequest'; import express from 'express'; import { StatusCodes } from 'http-status-codes'; import EnonceManager from '../managers/EnonceManager'; @@ -18,9 +18,9 @@ class ParamsCallbackManager { return ParamsCallbackManager._instance; } - protected listenParam(paramName: string, backend: Express, context: any, functionName: string, indexName: string) { + protected listenParam(paramName: string, backend: Express, getFunction: (id: string | number, ...args: Array<any>) => Promise<any>, args: Array<any>, indexName: string) { backend.param(paramName, (req: ApiRequest, res: express.Response, next: express.NextFunction, id: string | number) => { - (context[functionName] as (id: string | number) => Promise<any>)(id).then(result => { + getFunction(id, ...args).then(result => { if ( result ) { this.initBoundParams(req); (req.boundParams as any)[indexName] = result; @@ -42,7 +42,15 @@ class ParamsCallbackManager { } register(backend: Express) { - this.listenParam('enonceNameOrUrl', backend, EnonceManager, 'get', 'enonce'); + this.listenParam('enonceNameOrUrl', + backend, + EnonceManager.get.bind(EnonceManager), + [ { + exercices: true, + staff: true + } ], + 'enonce' + ); } } diff --git a/ExpressAPI/src/middlewares/SecurityMiddleware.ts b/ExpressAPI/src/middlewares/SecurityMiddleware.ts index cce4344..a488037 100644 --- a/ExpressAPI/src/middlewares/SecurityMiddleware.ts +++ b/ExpressAPI/src/middlewares/SecurityMiddleware.ts @@ -2,7 +2,7 @@ import express from 'express'; import { StatusCodes } from 'http-status-codes'; import SecurityCheckType from '../types/SecurityCheckType'; import logger from '../shared/logging/WinstonLogger'; -import ApiRequest from '../models/ApiRequest'; +import ApiRequest from '../types/ApiRequest'; class SecurityMiddleware { @@ -22,7 +22,7 @@ class SecurityMiddleware { check(checkIfConnected: boolean, ...checkTypes: Array<SecurityCheckType>): (req: ApiRequest, res: express.Response, next: express.NextFunction) => void { return async (req: ApiRequest, res: express.Response, next: express.NextFunction) => { if ( checkIfConnected ) { - if ( req.session.profile.userId === null ) { + if ( req.session.profile.id === null ) { return req.session.sendResponse(res, StatusCodes.UNAUTHORIZED); } } diff --git a/ExpressAPI/src/routes/EnonceRoutes.ts b/ExpressAPI/src/routes/EnonceRoutes.ts index bd4e1db..1a58437 100644 --- a/ExpressAPI/src/routes/EnonceRoutes.ts +++ b/ExpressAPI/src/routes/EnonceRoutes.ts @@ -13,14 +13,12 @@ import Config from '../config/Config'; import GitlabMember from '../shared/types/Gitlab/GitlabMember'; import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'; import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; -import UserManager from '../managers/UserManager'; -import User from '../models/User'; -import Enonce from '../models/Enonce'; -import EnonceStaff from '../models/EnonceStaff'; import { AxiosError } from 'axios'; import logger from '../shared/logging/WinstonLogger'; import DojoValidators from '../helpers/DojoValidators'; -import EnonceManager from '../managers/EnonceManager'; +import { Prisma } from '@prisma/client'; +import db from '../helpers/DatabaseHelper'; +import { Enonce } from '../types/DatabaseTypes'; class EnonceRoutes implements RoutesManager { @@ -60,11 +58,14 @@ class EnonceRoutes implements RoutesManager { // Get an enonce by its name or gitlab url private async getEnonce(req: ApiRequest, res: express.Response) { - return req.boundParams.enonce ? req.session.sendResponse(res, StatusCodes.OK, req.boundParams.enonce.toJsonObject()) : res.status(StatusCodes.NOT_FOUND).send(); + return req.boundParams.enonce ? req.session.sendResponse(res, StatusCodes.OK, req.boundParams.enonce) : res.status(StatusCodes.NOT_FOUND).send(); } private async createEnonce(req: ApiRequest, res: express.Response) { const params: { name: string, members: Array<GitlabUser>, template: string } = req.body; + params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ]; + params.members = params.members.removeObjectDuplicates(gitlabUser => gitlabUser.id); + let repository: GitlabRepository; try { @@ -83,31 +84,39 @@ class EnonceRoutes implements RoutesManager { } try { - const members: Array<GitlabMember | false> = await Promise.all([ req.session.profile.userGitlabId, ...params.members.map(member => member.id) ].map(async (memberId: number): Promise<GitlabMember | false> => { + await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<GitlabMember | false> => { try { - return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.Maintainer); + return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER); } catch ( e ) { return false; } })); - const enonce: Enonce = await EnonceManager.createObjectFromRawSql({ - enonceName : repository.name, - enonceGitlabId : repository.id, - enonceGitlabLink : repository.web_url, - enonceGitlabCreationInfo: JSON.stringify(repository), - enonceGitlabLastInfo : JSON.stringify(repository), - enonceGitlabLastInfoTs : new Date().getTime() - }).create(); - - let dojoUsers: Array<User> = [ req.session.profile, ...(await UserManager.getFromGitlabUsers(params.members, true) as Array<User>) ]; - dojoUsers = dojoUsers.reduce((unique, user) => (unique.findIndex(uniqueUser => uniqueUser.userId === user.userId) !== -1 ? unique : [ ...unique, user ]), Array<User>()); - await Promise.all(dojoUsers.map(dojoUser => EnonceStaff.createFromSql({ - enonceName: enonce.enonceName, - userId : dojoUser.userId - }).create())); - - return req.session.sendResponse(res, StatusCodes.OK, enonce.toJsonObject()); + const enonce: Enonce = await db.enonce.create({ + data: { + name : repository.name, + gitlabId : repository.id, + gitlabLink : repository.web_url, + gitlabCreationInfo: repository as unknown as Prisma.JsonObject, + gitlabLastInfo : repository as unknown as Prisma.JsonObject, + gitlabLastInfoDate: new Date(), + staff : { + connectOrCreate: [ ...params.members.map(gitlabUser => { + return { + create: { + gitlabId : gitlabUser.id, + firstname: gitlabUser.name + }, + where : { + gitlabId: gitlabUser.id + } + }; + }) ] + } + } + }); + + return req.session.sendResponse(res, StatusCodes.OK, enonce); } catch ( error ) { if ( error instanceof AxiosError ) { return res.status(error.response.status).send(); diff --git a/ExpressAPI/src/routes/ExerciceRoutes.ts b/ExpressAPI/src/routes/ExerciceRoutes.ts index 3f78320..840f4ed 100644 --- a/ExpressAPI/src/routes/ExerciceRoutes.ts +++ b/ExpressAPI/src/routes/ExerciceRoutes.ts @@ -10,18 +10,15 @@ import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import GitlabManager from '../managers/GitlabManager'; import Config from '../config/Config'; import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; -import Enonce from '../models/Enonce'; import { AxiosError } from 'axios'; import logger from '../shared/logging/WinstonLogger'; import DojoValidators from '../helpers/DojoValidators'; import { v4 as uuidv4 } from 'uuid'; import GitlabMember from '../shared/types/Gitlab/GitlabMember'; import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'; -import User from '../models/User'; -import UserManager from '../managers/UserManager'; -import Exercice from '../models/Exercice'; -import ExerciceMember from '../models/ExerciceMember'; -import ExerciceManager from '../managers/ExerciceManager'; +import { Prisma } from '@prisma/client'; +import { Enonce, Exercice } from '../types/DatabaseTypes'; +import db from '../helpers/DatabaseHelper'; class ExerciceRoutes implements RoutesManager { @@ -50,24 +47,26 @@ class ExerciceRoutes implements RoutesManager { } private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string { - return `DojoEx - ${ enonce.enonceName } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`; + return `DojoEx - ${ enonce.name } - ${ members.map(member => member.username).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`; } private getExercicePath(enonce: Enonce, exerciceId: string): string { - return `dojo-ex_${ enonce.enonceGitlabLastInfo.path }_${ exerciceId }`; + return `dojo-ex_${ (enonce.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciceId }`; } private async createExercice(req: ApiRequest, res: express.Response) { const params: { members: Array<GitlabUser> } = req.body; - params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ]; + params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id); + const enonce: Enonce = req.boundParams.enonce; - const exerciceId: string = uuidv4(); + const exerciceId: string = uuidv4(); let repository: GitlabRepository; + let suffix: number = 0; do { try { - repository = await GitlabManager.forkRepository(req.boundParams.enonce.enonceGitlabCreationInfo.id, this.getExerciceName(req.boundParams.enonce, params.members, suffix), this.getExercicePath(req.boundParams.enonce, exerciceId), Config.exercice.default.description.replace('{{ENONCE_NAME}}', req.boundParams.enonce.enonceName), Config.exercice.default.visibility, Config.gitlab.group.exercices); + repository = await GitlabManager.forkRepository((enonce.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciceName(enonce, params.members, suffix), this.getExercicePath(req.boundParams.enonce, exerciceId), Config.exercice.default.description.replace('{{ENONCE_NAME}}', enonce.name), Config.exercice.default.visibility, Config.gitlab.group.exercices); break; } catch ( error ) { if ( error instanceof AxiosError ) { @@ -87,33 +86,41 @@ class ExerciceRoutes implements RoutesManager { } try { - const members: Array<GitlabMember | false> = await Promise.all([ ...new Set([ ...(await req.boundParams.enonce.staff.value).map(member => member.userGitlabId), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => { + await Promise.all([ ...new Set([ ...enonce.staff.map(user => user.gitlabId), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => { try { - return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.Maintainer); + return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER); } catch ( e ) { return false; } })); - const exercice: Exercice = await ExerciceManager.createObjectFromRawSql({ - exerciceId : exerciceId, - exerciceEnonceName : req.boundParams.enonce.enonceName, - exerciceName : repository.name, - exerciceGitlabId : repository.id, - exerciceGitlabLink : repository.web_url, - exerciceGitlabCreationInfo: JSON.stringify(repository), - exerciceGitlabLastInfo : JSON.stringify(repository), - exerciceGitlabLastInfoTs : new Date().getTime() - }).create(); - - let dojoUsers: Array<User> = await UserManager.getFromGitlabUsers(params.members, true) as Array<User>; - dojoUsers = dojoUsers.reduce((unique, user) => (unique.findIndex(uniqueUser => uniqueUser.userId === user.userId) !== -1 ? unique : [ ...unique, user ]), Array<User>()); - await Promise.all(dojoUsers.map(dojoUser => ExerciceMember.createFromSql({ - exerciceId: exercice.exerciceId, - userId : dojoUser.userId - }).create())); - - return req.session.sendResponse(res, StatusCodes.OK, exercice.toJsonObject()); + const exercice: Exercice = await db.exercice.create({ + data: { + id : exerciceId, + enonceName : enonce.name, + name : repository.name, + gitlabId : repository.id, + gitlabLink : repository.web_url, + gitlabCreationInfo: repository as unknown as Prisma.JsonObject, + gitlabLastInfo : repository as unknown as Prisma.JsonObject, + gitlabLastInfoDate: new Date(), + members : { + connectOrCreate: [ ...params.members.map(gitlabUser => { + return { + create: { + gitlabId : gitlabUser.id, + firstname: gitlabUser.name + }, + where : { + gitlabId: gitlabUser.id + } + }; + }) ] + } + } + }); + + return req.session.sendResponse(res, StatusCodes.OK, exercice); } catch ( error ) { if ( error instanceof AxiosError ) { return res.status(error.response.status).send(); diff --git a/ExpressAPI/src/routes/SessionRoutes.ts b/ExpressAPI/src/routes/SessionRoutes.ts index b74019b..08c3f35 100644 --- a/ExpressAPI/src/routes/SessionRoutes.ts +++ b/ExpressAPI/src/routes/SessionRoutes.ts @@ -7,8 +7,8 @@ import RoutesManager from '../express/RoutesManager'; import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware'; import ApiRequest from '../types/ApiRequest'; import UserManager from '../managers/UserManager'; -import User from '../models/User'; import SecurityMiddleware from '../middlewares/SecurityMiddleware'; +import { User } from '../types/DatabaseTypes'; class SessionRoutes implements RoutesManager { @@ -46,7 +46,7 @@ class SessionRoutes implements RoutesManager { const user: User | undefined = await UserManager.getByMail(params.user); if ( user ) { - if ( bcrypt.compareSync(params.password, user.userPassword) ) { + if ( bcrypt.compareSync(params.password, user.password) ) { req.session.profile = user; req.session.sendResponse(res, StatusCodes.OK); diff --git a/ExpressAPI/src/types/DatabaseTypes.ts b/ExpressAPI/src/types/DatabaseTypes.ts new file mode 100644 index 0000000..a782d9a --- /dev/null +++ b/ExpressAPI/src/types/DatabaseTypes.ts @@ -0,0 +1,34 @@ +import { Prisma } from '@prisma/client'; +import LazyVal from '../shared/helpers/LazyVal'; +import GitlabUser from '../shared/types/Gitlab/GitlabUser'; + + +const userBase = Prisma.validator<Prisma.UserArgs>()({ + include: { exercices: true } + }); +const enonceBase = Prisma.validator<Prisma.EnonceArgs>()({ + include: { + exercices: true, + staff : true + } + }); +const exerciceBase = Prisma.validator<Prisma.ExerciceArgs>()({ + include: { + members: true, + results: true + } + }); +const resultBase = Prisma.validator<Prisma.ResultArgs>()({ + include: { + exercice: true + } + }); + + +export type User = Partial<Prisma.UserGetPayload<typeof userBase> & { + isTeachingStaff: boolean + gitlabProfile: LazyVal<GitlabUser> +}> +export type Enonce = Partial<Prisma.EnonceGetPayload<typeof enonceBase>> +export type Exercice = Partial<Prisma.ExerciceGetPayload<typeof exerciceBase>> +export type Result = Partial<Prisma.ResultGetPayload<typeof resultBase>> \ No newline at end of file -- GitLab