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

Target

Select target project
  • Dojo_Project_Nguyen/backend/dojobackendapi
  • dojo_project/projects/backend/dojobackendapi
2 results
Select Git revision
Show changes
Commits on Source (13)
......@@ -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:
......
This diff is collapsed.
......@@ -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"
}
}
-- AlterTable
ALTER TABLE `Exercise` ADD COLUMN `correctionDescription` VARCHAR(80) NULL;
......@@ -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)
......
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 & {
......
......@@ -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) : []);
}
}
}
......
......@@ -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({
......
......@@ -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);
}
}
......