Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • jw_sonar
  • v6.0.0
  • bedran_exercise-list
  • ask-user-to-delete-exercises-on-duplicates
  • update-dependencies
  • main
  • jw_sonar_backup
  • add_route_assignments
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.1.3
  • 3.2.0
  • 3.3.0
  • 3.4.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 3.5.1
  • 3.5.2
  • 3.5.3
  • 4.0.0
  • 4.1.0
  • 5.0.0
  • 5.0.1
  • 6.0.0-dev
  • v1.0.1
32 results

Target

Select target project
  • Dojo_Project_Nguyen / Backend / DojoBackendAPI
  • Dojo Project (HES-SO) / Projects / Backend / DojoBackendAPI
2 results
Select Git revision
  • jw_sonar
  • v6.0.0
  • bedran_exercise-list
  • ask-user-to-delete-exercises-on-duplicates
  • update-dependencies
  • main
  • jw_sonar_backup
  • add_route_assignments
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.1.3
  • 3.2.0
  • 3.3.0
  • 3.4.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 3.5.1
  • 3.5.2
  • 3.5.3
  • 4.0.0
  • 4.1.0
  • 5.0.0
  • 5.0.1
  • 6.0.0-dev
  • v1.0.1
32 results
Show changes

Commits on Source 21

37 files
+ 575
668
Compare changes
  • Side-by-side
  • Inline

Files

+30 −7
Original line number Diff line number Diff line
FROM node:20-bullseye
ARG BUILD_WORKDIR=/dojo/ExpressAPI

######################################################################## Build stage
FROM node:20-bullseye AS builder
LABEL maintainer="Michaël Minelli <michael-jean.minelli@hesge.ch>"
LABEL Description="Express API for Dojo"
LABEL Description="Express API for Dojo - Build stage"

ARG BUILD_WORKDIR

ADD ExpressAPI/ /dojo/ExpressAPI/
ADD .env /dojo/ExpressAPI/.env
# Create app directory
ADD ExpressAPI/ ${BUILD_WORKDIR}/

WORKDIR /dojo/ExpressAPI/
WORKDIR ${BUILD_WORKDIR}

# Install app dependencies
RUN npm install

# Build app
RUN npm run build



######################################################################## Run stage
FROM node:20-bullseye
LABEL maintainer="Michaël Minelli <michael-jean.minelli@hesge.ch>"
LABEL Description="Express API for Dojo - Run stage"

ARG BUILD_WORKDIR

COPY --from=builder ${BUILD_WORKDIR}/node_modules ./node_modules
COPY --from=builder ${BUILD_WORKDIR}/package*.json ./
COPY --from=builder ${BUILD_WORKDIR}/dist ./dist
COPY --from=builder ${BUILD_WORKDIR}/prisma ./prisma
ADD .env ${BUILD_WORKDIR}/.env

EXPOSE 30992

ENTRYPOINT [ "npm", "run", "start:prod" ]
CMD [  "npm", "run", "start:migrate:prod" ]
#ENTRYPOINT ["tail", "-f", "/dev/null"]
Compare b736d6bf to fec06e6a
Original line number Diff line number Diff line
Subproject commit b736d6bf3853140e2f4a8950155b1ce7b3173250
Subproject commit fec06e6aeeff2083bfe82b38182f6a02c73d023f
+51 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
            "name": "dojo_backend_api",
            "version": "1.0.0",
            "dependencies": {
                "@prisma/client": "^4.16.2",
                "axios": "^1.4.0",
                "bcryptjs": "^2.4.3",
                "cors": "^2.8.5",
@@ -37,6 +38,7 @@
                "@types/uuid": "^9.0.2",
                "nodemon": "^2.0.22",
                "npm": "^9.6.7",
                "prisma": "^4.16.2",
                "ts-node": "^10.9.1",
                "typescript": "^5.0.4"
            }
@@ -96,6 +98,38 @@
                "@jridgewell/sourcemap-codec": "^1.4.10"
            }
        },
        "node_modules/@prisma/client": {
            "version": "4.16.2",
            "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz",
            "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==",
            "hasInstallScript": true,
            "dependencies": {
                "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
            },
            "engines": {
                "node": ">=14.17"
            },
            "peerDependencies": {
                "prisma": "*"
            },
            "peerDependenciesMeta": {
                "prisma": {
                    "optional": true
                }
            }
        },
        "node_modules/@prisma/engines": {
            "version": "4.16.2",
            "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz",
            "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==",
            "devOptional": true,
            "hasInstallScript": true
        },
        "node_modules/@prisma/engines-version": {
            "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
            "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz",
            "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg=="
        },
        "node_modules/@tsconfig/node10": {
            "version": "1.0.9",
            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@@ -4931,6 +4965,23 @@
                "url": "https://github.com/sponsors/jonschlinkert"
            }
        },
        "node_modules/prisma": {
            "version": "4.16.2",
            "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz",
            "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==",
            "devOptional": true,
            "hasInstallScript": true,
            "dependencies": {
                "@prisma/engines": "4.16.2"
            },
            "bin": {
                "prisma": "build/index.js",
                "prisma2": "build/index.js"
            },
            "engines": {
                "node": ">=14.17"
            }
        },
        "node_modules/process-nextick-args": {
            "version": "2.0.1",
            "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+10 −4
Original line number Diff line number Diff line
@@ -7,11 +7,16 @@
    "main"           : "app.js",
    "scripts"        : {
        "clean"             : "rm -R dist/*",
        "build"     : "npx tsc --project ./",
        "build"             : "npx prisma generate; npx tsc --project ./",
        "start:dev"         : "npx nodemon src/app.ts",
        "start:prod": "NODE_ENV=production npx node --max-http-header-size=1048576 dist/app.js"
        "start:prod"        : "NODE_ENV=production npx node --max-http-header-size=1048576 dist/app.js",
        "start:migrate:prod": "npx prisma migrate deploy && npx prisma db seed && npm run start:prod"
    },
    "prisma"         : {
        "seed": "ts-node prisma/seed.ts"
    },
    "dependencies"   : {
        "@prisma/client"   : "^4.16.2",
        "axios"            : "^1.4.0",
        "bcryptjs"         : "^2.4.3",
        "cors"             : "^2.8.5",
@@ -40,6 +45,7 @@
        "@types/node"        : "^20.2.4",
        "@types/uuid"        : "^9.0.2",
        "nodemon"            : "^2.0.22",
        "prisma"             : "^4.16.2",
        "ts-node"            : "^10.9.1",
        "typescript"         : "^5.0.4",
        "npm"                : "^9.6.7"
+87 −0
Original line number Diff line number Diff line
-- CreateTable
CREATE TABLE `User` (
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    `firstname` VARCHAR(191) NOT NULL,
    `lastname` VARCHAR(191) NULL,
    `mail` VARCHAR(191) NULL,
    `password` VARCHAR(191) NULL,
    `gitlabId` INTEGER NOT NULL,
    `role` VARCHAR(191) NULL,
    `deleted` BOOLEAN NOT NULL DEFAULT false,

    UNIQUE INDEX `User_mail_key`(`mail`),
    UNIQUE INDEX `User_gitlabId_key`(`gitlabId`),
    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Enonce` (
    `name` VARCHAR(191) NOT NULL,
    `gitlabId` INTEGER NOT NULL,
    `gitlabLink` VARCHAR(191) NOT NULL,
    `gitlabCreationInfo` JSON NOT NULL,
    `gitlabLastInfo` JSON NOT NULL,
    `gitlabLastInfoDate` DATETIME(3) NOT NULL,

    PRIMARY KEY (`name`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Exercice` (
    `id` CHAR(36) NOT NULL,
    `enonceName` VARCHAR(191) NOT NULL,
    `name` VARCHAR(191) NOT NULL,
    `gitlabId` INTEGER NOT NULL,
    `gitlabLink` VARCHAR(191) NOT NULL,
    `gitlabCreationInfo` JSON NOT NULL,
    `gitlabLastInfo` JSON NOT NULL,
    `gitlabLastInfoDate` DATETIME(3) NOT NULL,

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Result` (
    `exerciceId` CHAR(36) NOT NULL,
    `dateTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `pass` BOOLEAN NOT NULL,
    `details` TEXT NOT NULL,

    PRIMARY KEY (`exerciceId`, `dateTime`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `_EnonceToUser` (
    `A` VARCHAR(191) NOT NULL,
    `B` INTEGER NOT NULL,

    UNIQUE INDEX `_EnonceToUser_AB_unique`(`A`, `B`),
    INDEX `_EnonceToUser_B_index`(`B`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `_ExerciceToUser` (
    `A` CHAR(36) NOT NULL,
    `B` INTEGER NOT NULL,

    UNIQUE INDEX `_ExerciceToUser_AB_unique`(`A`, `B`),
    INDEX `_ExerciceToUser_B_index`(`B`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `Exercice` ADD CONSTRAINT `Exercice_enonceName_fkey` FOREIGN KEY (`enonceName`) REFERENCES `Enonce`(`name`) ON DELETE NO ACTION ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `Result` ADD CONSTRAINT `Result_exerciceId_fkey` FOREIGN KEY (`exerciceId`) REFERENCES `Exercice`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `_EnonceToUser` ADD CONSTRAINT `_EnonceToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Enonce`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `_EnonceToUser` ADD CONSTRAINT `_EnonceToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `_ExerciceToUser` ADD CONSTRAINT `_ExerciceToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Exercice`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `_ExerciceToUser` ADD CONSTRAINT `_ExerciceToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+3 −0
Original line number Diff line number Diff line
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "mysql"
 No newline at end of file
+61 −0
Original line number Diff line number Diff line
generator client {
    provider = "prisma-client-js"
}

datasource db {
    provider = "mysql"
    url      = env("DATABASE_URL")
}

model User {
    id        Int     @id @default(autoincrement())
    firstname String
    lastname  String?
    mail      String? @unique
    password  String?
    gitlabId  Int     @unique
    role      String?
    deleted   Boolean @default(false)

    enonces   Enonce[]
    exercices Exercice[]
}

model Enonce {
    name               String   @id
    gitlabId           Int
    gitlabLink         String
    gitlabCreationInfo Json     @db.Json
    gitlabLastInfo     Json     @db.Json
    gitlabLastInfoDate DateTime

    exercices Exercice[]
    staff     User[]
}

model Exercice {
    id                 String   @id @db.Char(36)
    enonceName         String
    name               String
    gitlabId           Int
    gitlabLink         String
    gitlabCreationInfo Json     @db.Json
    gitlabLastInfo     Json     @db.Json
    gitlabLastInfoDate DateTime

    enonce Enonce @relation(fields: [enonceName], references: [name], onDelete: NoAction, onUpdate: Cascade)

    members User[]
    results Result[]
}

model Result {
    exerciceId String   @db.Char(36)
    dateTime   DateTime @default(now())
    pass       Boolean
    details    String   @db.Text

    exercice Exercice @relation(fields: [exerciceId], references: [id], onDelete: Cascade, onUpdate: Cascade)

    @@id([exerciceId, dateTime])
}
+33 −0
Original line number Diff line number Diff line
require('dotenv').config(); // ATTENTION : This line MUST be the first of this file
require('../src/shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file

import Config      from '../src/config/Config';
import logger      from '../src/shared/logging/WinstonLogger';
import * as bcrypt from 'bcryptjs';
import db          from '../src/helpers/DatabaseHelper';


async function main() {
    await db.user.upsert({
                             where : { gitlabId: 142 },
                             update: {},
                             create: {
                                 id       : 1,
                                 firstname: 'Michaël',
                                 lastname : 'Minelli',
                                 mail     : 'michael@minelli.me',
                                 password : bcrypt.hashSync('123456', Config.userPasswordSaltRounds),
                                 gitlabId : 142,
                                 role     : 'colsci',
                                 deleted  : false
                             }
                         });
}

main().then(async () => {
    await db.$disconnect();
}).catch(async (e) => {
    logger.error(e);
    await db.$disconnect();
    process.exit(1);
});
 No newline at end of file
+3 −3
Original line number Diff line number Diff line
// Read from the .env file
// ATTENTION : This line MUST be the first of this file
require('dotenv').config();
require('dotenv').config(); // ATTENTION : This line MUST be the first of this file
require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file

import WorkerRole     from './process/WorkerRole';
import ClusterManager from './process/ClusterManager';
@@ -10,6 +9,7 @@ import HttpManager from './managers/HttpManager';

HttpManager.registerAxiosInterceptor();


(new ClusterManager([ {
    role         : WorkerRole.API,
    quantity     : ClusterManager.CORES,
+3 −19
Original line number Diff line number Diff line
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)
@@ -77,10 +61,10 @@ class Config {
        this.enonce = {
            default: {
                description         : process.env.ENONCE_DEFAULT_DESCRIPTION,
                initReadme          : Toolbox.strToBool(process.env.ENONCE_DEFAULT_INIT_README),
                sharedRunnersEnabled: Toolbox.strToBool(process.env.ENONCE_DEFAULT_SHARED_RUNNERS_ENABLED),
                initReadme          : process.env.ENONCE_DEFAULT_INIT_README.toBoolean(),
                sharedRunnersEnabled: process.env.ENONCE_DEFAULT_SHARED_RUNNERS_ENABLED.toBoolean(),
                visibility          : process.env.ENONCE_DEFAULT_VISIBILITY,
                wikiEnabled         : Toolbox.strToBool(process.env.ENONCE_DEFAULT_WIKI_ENABLED),
                wikiEnabled         : process.env.ENONCE_DEFAULT_WIKI_ENABLED.toBoolean(),
                template            : process.env.ENONCE_DEFAULT_TEMPLATE.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token)
            }
        };
+9 −9
Original line number Diff line number Diff line
@@ -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 = '';

+31 −7
Original line number Diff line number Diff line
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 }`));


const db = prisma.$extends(UserQueryExtension).$extends(UserResultExtension);


export default db;
 No newline at end of file
+3 −33
Original line number Diff line number Diff line
import ApiRequest                                                   from '../models/ApiRequest';
import ApiRequest                                                   from '../types/ApiRequest';
import Config                                                       from '../config/Config';
import { StatusCodes }                                              from 'http-status-codes';
import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'express-validator/src/base';
@@ -36,11 +36,7 @@ class DojoValidators {
    }

    readonly nullSanitizer = this.toValidatorSchemaOptions({
                                                               options: (value, {
                                                                   req,
                                                                   location,
                                                                   path
                                                               }) => {
                                                               options: (value) => {
                                                                   try {
                                                                       return value == 'null' || value == 'undefined' || value == '' ? null : value;
                                                                   } catch ( e ) {
@@ -50,11 +46,7 @@ class DojoValidators {
                                                           });

    readonly jsonSanitizer = this.toValidatorSchemaOptions({
                                                               options: (value, {
                                                                   req,
                                                                   location,
                                                                   path
                                                               }) => {
                                                               options: (value) => {
                                                                   try {
                                                                       return JSON.parse(value);
                                                                   } catch ( e ) {
@@ -68,7 +60,6 @@ class DojoValidators {
                                                                      errorMessage: 'Template doesn\'t exist or you don\'t have access to it',
                                                                      options     : (value, {
                                                                          req,
                                                                          location,
                                                                          path
                                                                      }) => {
                                                                          return new Promise((resolve, reject) => {
@@ -86,7 +77,6 @@ class DojoValidators {
    readonly templateUrlSanitizer = this.toValidatorSchemaOptions({
                                                                      options: (value, {
                                                                          req,
                                                                          location,
                                                                          path
                                                                      }) => {
                                                                          try {
@@ -101,26 +91,6 @@ class DojoValidators {
                                                                          return value;
                                                                      }
                                                                  });

    readonly enonceValidator = this.toValidatorSchemaOptions({
                                                                 bail        : true,
                                                                 errorMessage: 'Template doesn\'t exist or you don\'t have access to it',
                                                                 options     : (value, {
                                                                     req,
                                                                     location,
                                                                     path
                                                                 }) => {
                                                                     return new Promise((resolve, reject) => {
                                                                         const template = this.getParamValue(req, path);
                                                                         if ( template ) {
                                                                             GitlabManager.checkTemplateAccess(template, req).then((templateAccess) => {
                                                                                 templateAccess !== StatusCodes.OK ? reject() : resolve(true);
                                                                             });
                                                                         }
                                                                         resolve(true);
                                                                     });
                                                                 }
                                                             });
}


+31 −0
Original line number Diff line number Diff line
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
+33 −0
Original line number Diff line number Diff line
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
+14 −19
Original line number Diff line number Diff line
import { Prisma } from '@prisma/client';
import { Enonce } from '../types/DatabaseTypes';
import db         from '../helpers/DatabaseHelper';
import Enonce from '../models/Enonce';


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);
    }
}

+0 −28
Original line number Diff line number Diff line
import Exercice from '../models/Exercice';


class ExerciceManager {
    private static _instance: ExerciceManager;

    private constructor() { }

    public static get instance(): ExerciceManager {
        if ( !ExerciceManager._instance ) {
            ExerciceManager._instance = new ExerciceManager();
        }

        return ExerciceManager._instance;
    }

    createObjectFromRawSql(raw: any): Exercice {
        const exercice = Exercice.createFromSql(raw);

        exercice.exerciceGitlabCreationInfo = raw.exerciceGitlabCreationInfo;
        exercice.exerciceGitlabLastInfo = raw.exerciceGitlabLastInfo;

        return exercice;
    }
}


export default ExerciceManager.instance;
+3 −3
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import GitlabMember      from '../shared/types/Gitlab/GitlabMember';
import { StatusCodes }   from 'http-status-codes';
import GitlabVisibility  from '../shared/types/Gitlab/GitlabVisibility';
import ApiRequest        from '../models/ApiRequest';
import ApiRequest        from '../types/ApiRequest';
import GitlabUser        from '../shared/types/Gitlab/GitlabUser';
import GitlabRoutes      from '../shared/types/Gitlab/GitlabRoutes';

@@ -112,8 +112,8 @@ class GitlabManager {
            dojo: false
        };
        members.forEach(member => {
            if ( member.access_level >= GitlabAccessLevel.Reporter ) {
                if ( member.id === req.session.profile.userGitlabId ) {
            if ( member.access_level >= GitlabAccessLevel.REPORTER ) {
                if ( member.id === req.session.profile.gitlabId ) {
                    isUsersAtLeastReporter.user = true;
                } else if ( member.id === Config.gitlab.account.id ) {
                    isUsersAtLeastReporter.dojo = true;
+35 −44
Original line number Diff line number Diff line
import User        from '../models/User';
import db          from '../helpers/DatabaseHelper';
import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import EnonceStaff from '../models/EnonceStaff';
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): Promise<User | undefined> {
        const raw = await db<User>(User.tableName).where('userMail', mail).first();

        return raw ? this.createObjectFromRawSql(raw) : undefined;
    async getByMail(mail: string, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> {
        return db.user.findUnique({
                                      where  : {
                                          mail: mail
                                      },
                                      include: include
                                  });
    }

    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 getById(id: number, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> {
        return await db.user.findUnique({
                                            where  : {
                                                id: id
                                            },
                                            include: include
                                        });
    }

    async getByIds(ids: Array<number>): Promise<Array<User>> {
        return Promise.all(ids.map(userId => this.getById(userId)));
    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 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 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
                                             }

        return user;
                                         })).id;
        }

    async getFromGitlabUsers(gitlabUsers: Array<GitlabUser>, createIfNotExist: boolean = false): Promise<Array<User | number>> {
        return Promise.all(gitlabUsers.map(gitlabUser => this.getFromGitlabUser(gitlabUser, createIfNotExist)));
        return user;
    }

    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)));
    }
}

+12 −4
Original line number Diff line number Diff line
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'
        );
    }
}

+1 −1
Original line number Diff line number Diff line
import * as ExpressValidator from 'express-validator';
import express               from 'express';
import { StatusCodes }       from 'http-status-codes';
import ApiRequest            from '../models/ApiRequest';
import ApiRequest            from '../types/ApiRequest';


class ParamsValidatorMiddleware {
+2 −2
Original line number Diff line number Diff line
@@ -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);
                }
            }
+1 −1
Original line number Diff line number Diff line
import express    from 'express';
import ApiRequest from '../models/ApiRequest';
import ApiRequest from '../types/ApiRequest';
import Session    from '../controllers/Session';


ExpressAPI/src/models/Enonce.ts

deleted100644 → 0
+0 −97
Original line number Diff line number Diff line
import Model            from './Model';
import db               from '../helpers/DatabaseHelper';
import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';
import LazyVal          from '../shared/helpers/LazyVal';
import UserManager      from '../managers/UserManager';
import User             from './User';


class Enonce extends Model {
    static tableName: string = 'Enonce';

    enonceName: string = '';
    enonceGitlabId: number = null;
    enonceGitlabLink: string = '';
    private _enonceGitlabCreationInfo: string = '{}';
    private _enonceGitlabLastInfo: string = '{}';
    enonceGitlabLastInfoTs: number = null;

    get enonceGitlabCreationInfo(): GitlabRepository {
        return JSON.parse(this._enonceGitlabCreationInfo);
    }

    set enonceGitlabCreationInfo(value: any) {
        if ( typeof value === 'string' ) {
            this._enonceGitlabCreationInfo = value;
            return;
        }

        this._enonceGitlabCreationInfo = JSON.stringify(value);
    }

    get enonceGitlabLastInfo(): GitlabRepository {
        return JSON.parse(this._enonceGitlabLastInfo);
    }

    set enonceGitlabLastInfo(value: any) {
        if ( typeof value === 'string' ) {
            this._enonceGitlabLastInfo = value;
            return;
        }

        this._enonceGitlabLastInfo = JSON.stringify(value);
    }

    staff = new LazyVal<Array<User>>(() => {
        return UserManager.getStaffOfEnonce(this.enonceName);
    });

    public async toJsonObject(): Promise<Object> {
        const result = {
            'name'              : this.enonceName,
            'gitlabId'          : this.enonceGitlabId,
            'gitlabLink'        : this.enonceGitlabLink,
            'gitlabCreationInfo': this.enonceGitlabCreationInfo,
            'gitlabLastInfo'    : this.enonceGitlabLastInfo,
            'gitlabLastInfoTs'  : this.enonceGitlabLastInfoTs
        };

        return result;
    };

    public importFromJsonObject(jsonObject: any) {
        this.enonceName = jsonObject.name;
        this.enonceGitlabId = jsonObject.gitlabId;
        this.enonceGitlabLink = jsonObject.gitlabLink;
        this.enonceGitlabCreationInfo = jsonObject.gitlabCreationInfo;
        this.enonceGitlabLastInfo = jsonObject.gitlabLastInfo;
        this.enonceGitlabLastInfoTs = jsonObject.gitlabLastInfoTs;
    }

    public toDb(): any {
        return {
            enonceName              : this.enonceName,
            enonceGitlabId          : this.enonceGitlabId,
            enonceGitlabLink        : this.enonceGitlabLink,
            enonceGitlabCreationInfo: this._enonceGitlabCreationInfo,
            enonceGitlabLastInfo    : this._enonceGitlabLastInfo,
            enonceGitlabLastInfoTs  : this.enonceGitlabLastInfoTs
        };
    }

    async create(): Promise<Enonce> {
        await db(Enonce.tableName).insert(this.toDb());
        return this;
    }

    update(): Promise<void> {
        return db(Enonce.tableName).where('enonceName', this.enonceName).update(this.toDb());
    }

    del(): Promise<void> {
        return db(Enonce.tableName).where('enonceName', this.enonceName).del();
    }
}


export default Enonce;
+0 −43
Original line number Diff line number Diff line
import Model from './Model';
import db    from '../helpers/DatabaseHelper';


class EnonceStaff extends Model {
    static tableName: string = 'EnonceStaff';

    enonceName: string = null;
    userId: number = null;

    public async toJsonObject(): Promise<Object> {
        const result = {
            'enonceName': this.enonceName,
            'userId'    : this.userId
        };

        return result;
    };

    public importFromJsonObject(jsonObject: any) {
        this.enonceName = jsonObject.enonceName;
        this.userId = jsonObject.userId;
    }

    public toDb(): any {
        return {
            enonceName: this.enonceName,
            userId    : this.userId
        };
    }

    async create(): Promise<EnonceStaff> {
        await db(EnonceStaff.tableName).insert(this.toDb());
        return this;
    }

    del(): Promise<void> {
        return db(EnonceStaff.tableName).where('enonceName', this.enonceName).andWhere('userId', this.userId).del();
    }
}


export default EnonceStaff;

ExpressAPI/src/models/Exercice.ts

deleted100644 → 0
+0 −98
Original line number Diff line number Diff line
import Model            from './Model';
import db               from '../helpers/DatabaseHelper';
import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';


class Exercice extends Model {
    static tableName: string = 'Exercice';

    exerciceId: string = '';
    exerciceEnonceName: string = '';
    exerciceName: string = '';
    exerciceGitlabId: number = null;
    exerciceGitlabLink: string = '';
    private _exerciceGitlabCreationInfo: string = '{}';
    private _exerciceGitlabLastInfo: string = '{}';
    exerciceGitlabLastInfoTs: number = null;

    get exerciceGitlabCreationInfo(): GitlabRepository {
        return JSON.parse(this._exerciceGitlabCreationInfo);
    }

    set exerciceGitlabCreationInfo(value: any) {
        if ( typeof value === 'string' ) {
            this._exerciceGitlabCreationInfo = value;
            return;
        }

        this._exerciceGitlabCreationInfo = JSON.stringify(value);
    }

    get exerciceGitlabLastInfo(): GitlabRepository {
        return JSON.parse(this._exerciceGitlabLastInfo);
    }

    set exerciceGitlabLastInfo(value: any) {
        if ( typeof value === 'string' ) {
            this._exerciceGitlabLastInfo = value;
            return;
        }

        this._exerciceGitlabLastInfo = JSON.stringify(value);
    }

    public async toJsonObject(): Promise<Object> {
        const result = {
            'id'                : this.exerciceId,
            'enonceName'        : this.exerciceEnonceName,
            'name'              : this.exerciceName,
            'gitlabId'          : this.exerciceGitlabId,
            'gitlabLink'        : this.exerciceGitlabLink,
            'gitlabCreationInfo': this.exerciceGitlabCreationInfo,
            'gitlabLastInfo'    : this.exerciceGitlabLastInfo,
            'gitlabLastInfoTs'  : this.exerciceGitlabLastInfoTs
        };

        return result;
    };

    public importFromJsonObject(jsonObject: any) {
        this.exerciceId = jsonObject.id;
        this.exerciceEnonceName = jsonObject.enonceName;
        this.exerciceName = jsonObject.name;
        this.exerciceGitlabId = jsonObject.gitlabId;
        this.exerciceGitlabLink = jsonObject.gitlabLink;
        this.exerciceGitlabCreationInfo = jsonObject.gitlabCreationInfo;
        this.exerciceGitlabLastInfo = jsonObject.gitlabLastInfo;
        this.exerciceGitlabLastInfoTs = jsonObject.gitlabLastInfoTs;
    }

    public toDb(): any {
        return {
            exerciceId                : this.exerciceId,
            exerciceEnonceName        : this.exerciceEnonceName,
            exerciceName              : this.exerciceName,
            exerciceGitlabId          : this.exerciceGitlabId,
            exerciceGitlabLink        : this.exerciceGitlabLink,
            exerciceGitlabCreationInfo: this._exerciceGitlabCreationInfo,
            exerciceGitlabLastInfo    : this._exerciceGitlabLastInfo,
            exerciceGitlabLastInfoTs  : this.exerciceGitlabLastInfoTs
        };
    }

    async create(): Promise<Exercice> {
        await db(Exercice.tableName).insert(this.toDb());
        return this;
    }

    update(): Promise<void> {
        return db(Exercice.tableName).where('exerciceId', this.exerciceId).update(this.toDb());
    }

    del(): Promise<void> {
        return db(Exercice.tableName).where('exerciceId', this.exerciceId).del();
    }
}


export default Exercice;
+0 −43
Original line number Diff line number Diff line
import Model from './Model';
import db    from '../helpers/DatabaseHelper';


class ExerciceMember extends Model {
    static tableName: string = 'ExerciceMember';

    exerciceId: string = '';
    userId: number = null;

    public async toJsonObject(): Promise<Object> {
        const result = {
            'exerciceId': this.exerciceId,
            'userId'    : this.userId
        };

        return result;
    };

    public importFromJsonObject(jsonObject: any) {
        this.exerciceId = jsonObject.exerciceId;
        this.userId = jsonObject.userId;
    }

    public toDb(): any {
        return {
            exerciceId: this.exerciceId,
            userId    : this.userId
        };
    }

    async create(): Promise<ExerciceMember> {
        await db(ExerciceMember.tableName).insert(this.toDb());
        return this;
    }

    del(): Promise<void> {
        return db(ExerciceMember.tableName).where('exerciceId', this.exerciceId).andWhere('userId', this.userId).del();
    }
}


export default ExerciceMember;

ExpressAPI/src/models/Model.ts

deleted100644 → 0
+0 −22
Original line number Diff line number Diff line
type Constructor<T> = new (...args: any[]) => T;


abstract class Model extends Object {
    static tableName: string = null;

    static createFromSql<T extends Object>(this: Constructor<T>, obj: any): T {
        const result = new this();

        Object.getOwnPropertyNames(obj).forEach(property => {
            if ( result.hasOwnProperty(property) ) {
                (result as any)[property] = obj[property];
            }
        });
        return result;
    }

    public abstract toJsonObject(): Promise<Object>
}


export default Model;

ExpressAPI/src/models/User.ts

deleted100644 → 0
+0 −112
Original line number Diff line number Diff line
import Session       from '../controllers/Session';
import Config        from '../config/Config';
import Toolbox       from '../shared/helpers/Toolbox';
import * as bcrypt   from 'bcryptjs';
import Model         from './Model';
import db            from '../helpers/DatabaseHelper';
import LazyVal       from '../shared/helpers/LazyVal';
import GitlabUser    from '../shared/types/Gitlab/GitlabUser';
import GitlabManager from '../managers/GitlabManager';


class User extends Model {
    static tableName: string = 'User';

    userId: number = null;
    userFirstName: string = '';
    userLastName: string = '';
    userMail: string = '';
    userGitlabId: number = -1;
    userRole: string = 'student';
    userDeleted: boolean = false;

    userPassword: string = null;

    unencryptedPassword: string = null; // This value is not set from the db. It's a value that is not null only if we have called createPassword function

    currentSession: Session = null;

    gitlabProfile = new LazyVal<GitlabUser>(() => {
        return GitlabManager.getUserById(this.userGitlabId);
    });

    public async toJsonObject(): Promise<Object> {
        const result = {
            'id'             : this.userId,
            'firstName'      : this.userFirstName,
            'lastName'       : this.userLastName,
            'mail'           : this.userMail,
            'gitlabId'       : this.userGitlabId,
            'role'           : this.userRole,
            'isTeachingStaff': this.isTeachingStaff,
            'deleted'        : this.userDeleted
        };

        return result;
    };

    get fullName(): string {
        return this.userLastName.toUpperCase() + ' ' + this.userFirstName;
    }

    get isTeachingStaff(): boolean {
        return Config.permissions.teachingStaff.includes(this.userRole);
    }

    public importFromJsonObject(jsonObject: any) {
        this.userId = jsonObject.id;
        this.userFirstName = jsonObject.firstName;
        this.userLastName = jsonObject.lastName;
        this.userMail = jsonObject.mail;
        this.userGitlabId = jsonObject.gitlabId;
        this.userRole = jsonObject.role;
        this.userDeleted = jsonObject.deleted;
    }

    public generateHashedPassword() {
        this.userPassword = bcrypt.hashSync(this.unencryptedPassword, Config.userPasswordSaltRounds);
    }

    public replacePassword(password: string) {
        this.unencryptedPassword = password;
        this.generateHashedPassword();
    }

    public createPassword() {
        this.unencryptedPassword = Toolbox.randomString(Config.userPasswordLength);
        this.generateHashedPassword();
    }

    public toDb(): any {
        return {
            userFirstName: Toolbox.capitalizeName(this.userFirstName),
            userLastName : Toolbox.capitalizeName(this.userLastName),
            userRole     : this.userRole,
            userMail     : this.userMail,
            userGitlabId : this.userGitlabId,
            userPassword : this.userPassword
        };
    }

    async create(): Promise<User> {
        const id = await db(User.tableName).insert(this.toDb());
        this.userId = id[0];

        return this;
    }

    update(): Promise<void> {
        return db(User.tableName).where('userId', this.userId).update(this.toDb());
    }

    updatePassword(): Promise<void> {
        return db(User.tableName).where('userId', this.userId).update({ 'userPassword': this.userPassword });
    }

    del(): Promise<void> {
        return db(User.tableName).where('userId', this.userId).update({ 'userDeleted': true });
    }
}


export default User;
+1 −1
Original line number Diff line number Diff line
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 RoutesManager   from '../express/RoutesManager';
+35 −26
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ import * as ExpressValidator from 'express-validator';
import { StatusCodes }           from 'http-status-codes';
import RoutesManager             from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import ApiRequest                from '../models/ApiRequest';
import ApiRequest                from '../types/ApiRequest';
import SecurityMiddleware        from '../middlewares/SecurityMiddleware';
import SecurityCheckType         from '../types/SecurityCheckType';
import GitlabUser                from '../shared/types/Gitlab/GitlabUser';
@@ -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();
+40 −33
Original line number Diff line number Diff line
@@ -4,24 +4,21 @@ import * as ExpressValidator from 'express-validator';
import { StatusCodes }           from 'http-status-codes';
import RoutesManager             from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import ApiRequest                from '../models/ApiRequest';
import ApiRequest                from '../types/ApiRequest';
import SecurityMiddleware        from '../middlewares/SecurityMiddleware';
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();
+1 −1
Original line number Diff line number Diff line
import { Express }        from 'express-serve-static-core';
import express            from 'express';
import RoutesManager      from '../express/RoutesManager';
import ApiRequest         from '../models/ApiRequest';
import ApiRequest         from '../types/ApiRequest';
import SecurityMiddleware from '../middlewares/SecurityMiddleware';
import SecurityCheckType  from '../types/SecurityCheckType';
import GitlabManager      from '../managers/GitlabManager';
+3 −3
Original line number Diff line number Diff line
@@ -5,10 +5,10 @@ import { StatusCodes } from 'http-status-codes';
import * as bcrypt               from 'bcryptjs';
import RoutesManager             from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import ApiRequest                from '../models/ApiRequest';
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);
Compare da42b3d4 to 0328c67f
Original line number Diff line number Diff line
Subproject commit da42b3d49b9b624c2b4c26ba15b6459109696a41
Subproject commit 0328c67fd0cade4b51fbf82afc7a07a7d16abe5d
+3 −3
Original line number Diff line number Diff line
import express    from 'express';
import Session    from '../controllers/Session';
import Enonce  from './Enonce';
import { Enonce } from './DatabaseTypes';


type ApiRequest = express.Request & {
+34 −0
Original line number Diff line number Diff line
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