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 13

9 files
+ 897
1701
Compare changes
  • Side-by-side
  • Inline

Files

+133 −50
Original line number Diff line number Diff line
@@ -73,17 +73,7 @@ paths:
                                - refreshToken
            responses:
                '200':
                    description: OK
                    content:
                        application/json:
                            schema:
                                allOf:
                                    -   $ref: '#/components/schemas/DojoBackendResponse'
                                    -   type: object
                                        properties:
                                            data:
                                                type: object
                                                nullable: true
                    $ref: '#/components/responses/OK'
                '404':
                    $ref: '#/components/responses/NOT_FOUND'
                default:
@@ -306,17 +296,7 @@ paths:
                -   $ref: '#/components/parameters/assignmentNameOrUrl'
            responses:
                '200':
                    description: OK
                    content:
                        application/json:
                            schema:
                                allOf:
                                    -   $ref: '#/components/schemas/DojoBackendResponse'
                                    -   type: object
                                        properties:
                                            data:
                                                type: object
                                                nullable: true
                    $ref: '#/components/responses/OK'
                '401':
                    $ref: '#/components/responses/UNAUTHORIZED'
                '404':
@@ -336,17 +316,93 @@ paths:
                -   $ref: '#/components/parameters/assignmentNameOrUrl'
            responses:
                '200':
                    description: OK
                    $ref: '#/components/responses/OK'
                '401':
                    $ref: '#/components/responses/UNAUTHORIZED'
                '404':
                    $ref: '#/components/responses/NOT_FOUND'
                default:
                    $ref: '#/components/responses/ERROR'
    /assignments/{assignmentNameOrUrl}/corrections:
        post:
            tags:
                - Assignment
            summary: Link a exercise to an assignment as a correction
            description: |
                **🔒 Security needs:** TeachingStaff of the assignment or Admin role
            security:
                -   Clients_Token: [ ]
            parameters:
                -   $ref: '#/components/parameters/assignmentNameOrUrl'
            requestBody:
                content:
                        application/json:
                    multipart/form-data:
                        schema:
                            allOf:
                                    -   $ref: '#/components/schemas/DojoBackendResponse'
                                -   type: object
                                    properties:
                                            data:
                                                type: object
                                                nullable: true
                                        exerciseIdOrUrl:
                                            type: string
                                            format: uuidv4
                                            description: The id of the exercise to link
                                -   $ref: '#/components/schemas/CorrectionsRequestBody'
                            required:
                                - exerciseIdOrUrl
            responses:
                '200':
                    $ref: '#/components/responses/OK'
                '400':
                    $ref: '#/components/responses/BAD_REQUEST'
                '401':
                    $ref: '#/components/responses/UNAUTHORIZED'
                '404':
                    $ref: '#/components/responses/NOT_FOUND'
                default:
                    $ref: '#/components/responses/ERROR'
    /assignments/{assignmentNameOrUrl}/corrections/{exerciseIdOrUrl}:
        patch:
            tags:
                - Assignment
            summary: Update the correction link (f.e. commit SHA, description, etc.)
            description: |
                **🔒 Security needs:** TeachingStaff of the assignment or Admin role
            security:
                -   Clients_Token: [ ]
            parameters:
                -   $ref: '#/components/parameters/assignmentNameOrUrl'
                -   $ref: '#/components/parameters/exerciseIdOrUrl'
            requestBody:
                content:
                    multipart/form-data:
                        schema:
                            $ref: '#/components/schemas/CorrectionsRequestBody'
            responses:
                '200':
                    $ref: '#/components/responses/OK'
                '400':
                    $ref: '#/components/responses/BAD_REQUEST'
                '401':
                    $ref: '#/components/responses/UNAUTHORIZED'
                '404':
                    $ref: '#/components/responses/NOT_FOUND'
                default:
                    $ref: '#/components/responses/ERROR'
        delete:
            tags:
                - Assignment
            summary: Unlink a correction from an assignment
            description: |
                **🔒 Security needs:** TeachingStaff of the assignment or Admin role
            security:
                -   Clients_Token: [ ]
            parameters:
                -   $ref: '#/components/parameters/assignmentNameOrUrl'
                -   $ref: '#/components/parameters/exerciseIdOrUrl'
            responses:
                '200':
                    $ref: '#/components/responses/OK'
                '400':
                    $ref: '#/components/responses/BAD_REQUEST'
                '401':
                    $ref: '#/components/responses/UNAUTHORIZED'
                '404':
@@ -415,7 +471,7 @@ paths:
                                                    $ref: '#/components/schemas/User'
                default:
                    $ref: '#/components/responses/ERROR'
    /exercises/{exerciseId}/assignment:
    /exercises/{exerciseIdOrUrl}/assignment:
        get:
            tags:
                - Exercise
@@ -424,7 +480,7 @@ paths:
            security:
                -   ExerciseChecker_Secret: [ ]
            parameters:
                -   $ref: '#/components/parameters/exerciseId'
                -   $ref: '#/components/parameters/exerciseIdOrUrl'
            responses:
                '200':
                    description: OK
@@ -456,7 +512,7 @@ paths:
                    $ref: '#/components/responses/NOT_FOUND'
                default:
                    $ref: '#/components/responses/ERROR'
    /exercises/{exerciseId}/results:
    /exercises/{exerciseIdOrUrl}/results:
        post:
            tags:
                - Exercise
@@ -464,7 +520,7 @@ paths:
            security:
                -   ExerciseChecker_Secret: [ ]
            parameters:
                -   $ref: '#/components/parameters/exerciseId'
                -   $ref: '#/components/parameters/exerciseIdOrUrl'
            requestBody:
                content:
                    multipart/form-data:
@@ -504,17 +560,7 @@ paths:
                                - archiveBase64
            responses:
                '200':
                    description: OK
                    content:
                        application/json:
                            schema:
                                allOf:
                                    -   $ref: '#/components/schemas/DojoBackendResponse'
                                    -   type: object
                                        properties:
                                            data:
                                                type: object
                                                nullable: true
                    $ref: '#/components/responses/OK'
                '401':
                    $ref: '#/components/responses/UNAUTHORIZED'
                '404':
@@ -548,8 +594,8 @@ components:
            required: true
            schema:
                type: string
        exerciseId:
            name: exerciseId
        exerciseIdOrUrl:
            name: exerciseIdOrUrl
            description: The id of an exercise.
            in: path
            required: true
@@ -557,6 +603,19 @@ components:
                type: string
                format: uuidv4
    schemas:
        CorrectionsRequestBody:
            type: object
            properties:
                description:
                    type: string
                    description: Short (max. 80 characters) description of the correction
                commit:
                    type: string
                    format: Commit SHA
                    description: Long or short commit id (if not set, take the last commit)
                    externalDocs:
                        description: Commit SHA
                        url: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits
        DojoBackendResponse:
            type: object
            properties:
@@ -848,6 +907,30 @@ components:
                sessionToken: JWT token (for content, see schema named 'SessionTokenJWT')
                data: null
    responses:
        OK:
            description: OK
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/DojoBackendResponse'
                    example:
                        timestamp: '1992-09-30T19:00:00.000Z'
                        code: 200
                        description: OK
                        sessionToken: JWT token (for content, see schema named 'SessionTokenJWT')
                        data: null
        BAD_REQUEST:
            description: BAD_REQUEST
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/DojoBackendResponse'
                    example:
                        timestamp: '1992-09-30T19:00:00.000Z'
                        code: 400
                        description: BAD_REQUEST
                        sessionToken: JWT token (for content, see schema named 'SessionTokenJWT')
                        data: null
        UNAUTHORIZED:
            description: UNAUTHORIZED
            content:
+631 −1603

File changed.

Preview size limit exceeded, changes collapsed.

+16 −16
Original line number Diff line number Diff line
@@ -28,14 +28,14 @@
        "seed": "node dist/prisma/seed"
    },
    "dependencies"   : {
        "@dotenvx/dotenvx"    : "^0.27.1",
        "@gitbeaker/rest"     : "^40.0.2",
        "@prisma/client"      : "^5.11.0",
        "axios"               : "^1.6.8",
        "@dotenvx/dotenvx"    : "^0.44.1",
        "@gitbeaker/rest"     : "^40.0.3",
        "@prisma/client"      : "^5.14.0",
        "axios"               : "^1.7.2",
        "compression"         : "^1.7.4",
        "cors"                : "^2.8.5",
        "express"             : "^4.19.2",
        "express-validator"   : "^7.0.1",
        "express-validator"   : "^7.1.0",
        "form-data"           : "^4.0.0",
        "helmet"              : "^7.1.0",
        "http-status-codes"   : "^2.3.0",
@@ -46,23 +46,23 @@
        "mysql"               : "^2.18.1",
        "node"                : "^20.11.0",
        "parse-link-header"   : "^2.0.0",
        "semver"              : "^7.6.0",
        "semver"              : "^7.6.2",
        "swagger-ui-express"  : "^5.0.0",
        "tar-stream"          : "^3.1.7",
        "uuid"                : "^9.0.1",
        "winston"             : "^3.13.0",
        "zod"                 : "^3.22.4",
        "zod-validation-error": "^3.0.3"
        "zod"                 : "^3.23.8",
        "zod-validation-error": "^3.3.0"
    },
    "devDependencies": {
        "@redocly/cli"             : "^1.10.6",
        "@redocly/cli"             : "^1.13.0",
        "@types/compression"       : "^1.7.5",
        "@types/cors"              : "^2.8.17",
        "@types/express"           : "^4.17.21",
        "@types/jsonwebtoken"      : "^9.0.6",
        "@types/morgan"            : "^1.9.9",
        "@types/multer"            : "^1.4.11",
        "@types/node"              : "^20.11.30",
        "@types/node"              : "^20.12.12",
        "@types/parse-link-header" : "^2.0.3",
        "@types/semver"            : "^7.5.8",
        "@types/swagger-ui-express": "^4.1.6",
@@ -70,11 +70,11 @@
        "@types/uuid"              : "^9.0.8",
        "eslint"                   : "^8.57.0",
        "genversion"               : "^3.2.0",
        "nodemon"                  : "^3.1.0",
        "npm"                      : "^10.5.0",
        "prisma"                   : "^5.11.0",
        "tsx"                      : "^4.7.1",
        "typescript"               : "^5.4.3",
        "typescript-eslint"        : "^7.4.0"
        "nodemon"                  : "^3.1.1",
        "npm"                      : "^10.8.0",
        "prisma"                   : "^5.14.0",
        "tsx"                      : "^4.11.0",
        "typescript"               : "^5.4.5",
        "typescript-eslint"        : "^7.11.0"
    }
}
+2 −0
Original line number Diff line number Diff line
-- AlterTable
ALTER TABLE `Exercise` ADD COLUMN `correctionDescription` VARCHAR(80) NULL;
+2 −1
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ model Exercise {
    gitlabLastInfoDate DateTime

    correctionCommit      Json?   @db.Json
    correctionDescription String? @db.VarChar(80)

    assignment Assignment @relation(fields: [assignmentName], references: [name], onDelete: NoAction, onUpdate: Cascade)

+12 −11
Original line number Diff line number Diff line
import Config                                                          from '../config/Config.js';
import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'express-validator/src/base';
import { BailOptions, ValidationChain }                             from 'express-validator/src/chain';
import GitlabManager                                                   from '../managers/GitlabManager.js';
import express                                                         from 'express';
import logger                                                          from '../shared/logging/WinstonLogger.js';
@@ -9,6 +7,9 @@ import ExerciseResultsFile from '../sha
import ParamsCallbackManager                                           from '../middlewares/ParamsCallbackManager.js';
import ExerciseManager                                                 from '../managers/ExerciseManager.js';
import Toolbox                                                         from '../shared/helpers/Toolbox.js';
import { CustomValidator, FieldMessageFactory, Meta, ValidationChain } from 'express-validator/lib/index.js';
import { ErrorMessage }                                                from 'express-validator/lib/base.js';
import { BailOptions }                                                 from 'express-validator/lib/chain/index.js';


declare type DojoMeta = Meta & {
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ export default Prisma.defineExtension(client => {
                                   assignment: {
                                       corrections: {
                                           compute(assignment) {
                                               return new LazyVal<Array<Partial<Exercise>> | undefined>(() => getCorrections(assignment));
                                               return new LazyVal<Array<Partial<Exercise>> | undefined>(() => assignment.published ? getCorrections(assignment) : []);
                                           }
                                       }
                                   }
+9 −0
Original line number Diff line number Diff line
@@ -62,6 +62,15 @@ class GitlabManager extends SharedGitlabManager {
        }
    }

    async getRepositoryCommit(repoId: number, commitSha: string): Promise<CommitSchema | undefined> {
        try {
            return await this.api.Commits.show(repoId, commitSha);
        } catch ( e ) {
            logger.error(JSON.stringify(e));
            return undefined;
        }
    }

    async createRepository(name: string, description: string, visibility: 'public' | 'internal' | 'private', initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, importUrl: string): Promise<ProjectSchema> {
        try {
            return await this.api.Projects.create({
+91 −19
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ import logger from '../shared/logging/WinstonLogger.js';
import DojoValidators              from '../helpers/DojoValidators.js';
import { Prisma }                  from '@prisma/client';
import db                          from '../helpers/DatabaseHelper.js';
import { Assignment }              from '../types/DatabaseTypes.js';
import { Assignment, Exercise }    from '../types/DatabaseTypes.js';
import AssignmentManager           from '../managers/AssignmentManager.js';
import fs                          from 'fs';
import path                        from 'path';
@@ -48,6 +48,25 @@ class AssignmentRoutes implements RoutesManager {
            trim    : true,
            notEmpty: true,
            custom  : DojoValidators.exerciseIdOrUrlValidator
        },
        commit         : {
            trim    : true,
            notEmpty: false
        },
        description    : {
            trim    : true,
            notEmpty: false
        }
    };

    private readonly assignmentUpdateCorrigeValidator: ExpressValidator.Schema = {
        commit     : {
            trim    : true,
            notEmpty: false
        },
        description: {
            trim    : true,
            notEmpty: false
        }
    };

@@ -59,14 +78,16 @@ class AssignmentRoutes implements RoutesManager {
        backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false).bind(this) as RequestHandler);

        backend.post('/assignments/:assignmentNameOrUrl/corrections', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), ParamsValidatorMiddleware.validate(this.assignmentAddCorrigeValidator), this.linkUpdateAssignmentCorrection(false).bind(this) as RequestHandler);
        backend.patch('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.linkUpdateAssignmentCorrection(true).bind(this) as RequestHandler);
        backend.patch('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), ParamsValidatorMiddleware.validate(this.assignmentUpdateCorrigeValidator), this.linkUpdateAssignmentCorrection(true).bind(this) as RequestHandler);
        backend.delete('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unlinkAssignmentCorrection.bind(this) as RequestHandler);
    }

    // Get an assignment by its name or gitlab url
    private async getAssignment(req: express.Request, res: express.Response) {
        const assignment: Partial<Assignment> | undefined = req.boundParams.assignment;

        if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment as Assignment, req.session.profile) ) {
        if ( assignment ) {
            if ( !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment as Assignment, req.session.profile) ) {
                delete assignment.gitlabId;
                delete assignment.gitlabLink;
                delete assignment.gitlabCreationInfo;
@@ -76,7 +97,30 @@ class AssignmentRoutes implements RoutesManager {
                delete assignment.exercises;
            }

        return assignment ? req.session.sendResponse(res, StatusCodes.OK, DojoModelsHelper.getFullSerializableObject(assignment)) : res.status(StatusCodes.NOT_FOUND).send();
            const getExercises = req.query.getMyExercises;
            let exercises: Array<Omit<Exercise, 'assignment'>> = [];
            if ( getExercises ) {
                exercises = await db.exercise.findMany({
                                                           where  : {
                                                               assignmentName: assignment.name,
                                                               members       : {
                                                                   some: {
                                                                       id: req.session.profile.id
                                                                   }
                                                               }
                                                           },
                                                           include: {
                                                               assignment: false,
                                                               members   : true,
                                                               results   : true
                                                           }
                                                       });
            }

            return req.session.sendResponse(res, StatusCodes.OK, DojoModelsHelper.getFullSerializableObject(Object.assign(assignment, { myExercises: exercises })));
        } else {
            return res.status(StatusCodes.NOT_FOUND).send();
        }
    }

    private async createAssignment(req: express.Request, res: express.Response) {
@@ -205,9 +249,9 @@ class AssignmentRoutes implements RoutesManager {
                return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST);
            }

            const lastCommit = await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId);
            const commit: Gitlab.CommitSchema | undefined = req.body.commit ? await GitlabManager.getRepositoryCommit(req.boundParams.exercise!.gitlabId, req.body.commit as string) : await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId);

            if ( lastCommit ) {
            if ( commit ) {
                if ( !isUpdate && SharedConfig.production ) { //Disable in dev env because gitlab dev group is private and we can't change visibility of sub projects
                    await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise!.gitlabId, 'internal');
                }
@@ -216,17 +260,45 @@ class AssignmentRoutes implements RoutesManager {
                                             where: {
                                                 id: req.boundParams.exercise!.id
                                             },
                                             data : {
                                                 correctionCommit: lastCommit
                                             }
                                             data : Object.assign({
                                                                      correctionCommit: commit
                                                                  }, isUpdate && req.body.description === undefined ? {} : {
                                                 correctionDescription: req.body.description
                                             })
                                         });

                return req.session.sendResponse(res, StatusCodes.OK);
            } else {
                return req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'No last commit found');
                return req.session.sendResponse(res, StatusCodes.NOT_FOUND, undefined, 'Commit not found');
            }
        };
    }

    private async unlinkAssignmentCorrection(req: express.Request, res: express.Response) {
        if ( req.boundParams.exercise?.assignmentName !== req.boundParams.assignment?.name ) {
            return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'The exercise does not belong to the assignment', DojoStatusCode.ASSIGNMENT_EXERCISE_NOT_RELATED);
        }

        if ( !req.boundParams.exercise?.isCorrection ) {
            return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST);
        }

        if ( SharedConfig.production ) { //Disable in dev env because gitlab dev group is private and we can't change visibility of sub projects
            await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise.gitlabId, 'private');
        }

        await db.exercise.update({
                                     where: {
                                         id: req.boundParams.exercise.id
                                     },
                                     data : {
                                         correctionCommit     : Prisma.DbNull,
                                         correctionDescription: null
                                     }
                                 });

        return req.session.sendResponse(res, StatusCodes.OK);
    }
}