Skip to content
Snippets Groups Projects
Commit f728df9f authored by michael.minelli's avatar michael.minelli
Browse files

Merge branch 'v3.3.0'

parents 2f8083ee 68a6d5df
No related branches found
No related tags found
No related merge requests found
Pipeline #29004 passed
Showing with 450 additions and 500 deletions
...@@ -17,7 +17,17 @@ ...@@ -17,7 +17,17 @@
- No modifications / Keep major and minors versions in sync with all parts of the project - No modifications / Keep major and minors versions in sync with all parts of the project
--> -->
## 3.2.0 (???)
## 3.3.0 (???)
### ✨ Feature
- **CLI**: Show an information message when a new version of the CLI is available (and not required)
### 🐛 Bugfix
- Fix an error when providing template for assignment creation
## 3.2.0 (2023-12-19)
### 🤏 Minor change ### 🤏 Minor change
- **Exercices**: Set names of students in exercise name in alphabetical order - **Exercices**: Set names of students in exercise name in alphabetical order
......
This diff is collapsed.
openapi: 3.1.0 openapi: 3.1.0
info: info:
title: Dojo API title: Dojo API
version: 3.2.0 version: 3.3.0
description: | description: |
**Backend API of the Dojo project.** **Backend API of the Dojo project.**
......
...@@ -18,7 +18,7 @@ dojo: ...@@ -18,7 +18,7 @@ dojo:
tags: tags:
- dojo_assignment - dojo_assignment
services: services:
- docker:dind - docker:docker:24.0.6-dind
image: image:
name: dojohesso/dojo_assignment_checker:latest name: dojohesso/dojo_assignment_checker:latest
script: script:
......
...@@ -18,7 +18,7 @@ dojo: ...@@ -18,7 +18,7 @@ dojo:
tags: tags:
- dojo_exercise - dojo_exercise
services: services:
- docker:dind - docker:24.0.6-dind
image: image:
name: dojohesso/dojo_exercise_checker:latest name: dojohesso/dojo_exercise_checker:latest
script: script:
......
This diff is collapsed.
{ {
"name" : "dojo_backend_api", "name" : "dojo_backend_api",
"description" : "Backend API of the Dojo project", "description" : "Backend API of the Dojo project",
"version" : "3.2.0", "version" : "3.3.0",
"license" : "AGPLv3", "license" : "AGPLv3",
"author" : "Michaël Minelli <dojo@minelli.me>", "author" : "Michaël Minelli <dojo@minelli.me>",
"main" : "dist/src/app.js", "main" : "dist/src/app.js",
...@@ -28,54 +28,55 @@ ...@@ -28,54 +28,55 @@
"seed": "node dist/prisma/seed" "seed": "node dist/prisma/seed"
}, },
"dependencies" : { "dependencies" : {
"@prisma/client" : "^5.6.0", "@prisma/client" : "^5.8.1",
"ajv" : "^8.12.0", "axios" : "^1.6.5",
"axios" : "^1.6.2", "compression" : "^1.7.4",
"compression" : "^1.7.4", "cors" : "^2.8.5",
"cors" : "^2.8.5", "dotenv" : "^16.3.1",
"dotenv" : "^16.3.1", "dotenv-expand" : "^10.0.0",
"dotenv-expand" : "^10.0.0", "express" : "^4.18.2",
"express" : "^4.18.2", "express-validator" : "^7.0.1",
"express-validator" : "^7.0.1", "form-data" : "^4.0.0",
"form-data" : "^4.0.0", "helmet" : "^7.1.0",
"helmet" : "^7.1.0", "http-status-codes" : "^2.3.0",
"http-status-codes" : "^2.3.0", "json5" : "^2.2.3",
"json5" : "^2.2.3", "jsonwebtoken" : "^9.0.2",
"jsonwebtoken" : "^9.0.2", "morgan" : "^1.10.0",
"morgan" : "^1.10.0", "multer" : "^1.4.5-lts.1",
"multer" : "^1.4.5-lts.1", "mysql" : "^2.18.1",
"mysql" : "^2.18.1", "node" : "^20.10.0",
"node" : "^20.10.0", "parse-link-header" : "^2.0.0",
"parse-link-header" : "^2.0.0", "semver" : "^7.5.4",
"semver" : "^7.5.4", "swagger-ui-express" : "^5.0.0",
"swagger-ui-express": "^5.0.0", "tar-stream" : "^3.1.6",
"tar-stream" : "^3.1.6", "uuid" : "^9.0.1",
"uuid" : "^9.0.1", "winston" : "^3.11.0",
"winston" : "^3.11.0" "zod" : "^3.22.4",
"zod-validation-error": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@redocly/cli" : "^1.5.0", "@redocly/cli" : "^1.6.0",
"@types/compression" : "^1.7.5", "@types/compression" : "^1.7.5",
"@types/cors" : "^2.8.17", "@types/cors" : "^2.8.17",
"@types/express" : "^4.17.21", "@types/express" : "^4.17.21",
"@types/jsonwebtoken" : "^9.0.5", "@types/jsonwebtoken" : "^9.0.5",
"@types/morgan" : "^1.9.9", "@types/morgan" : "^1.9.9",
"@types/multer" : "^1.4.11", "@types/multer" : "^1.4.11",
"@types/node" : "^20.10.3", "@types/node" : "^20.11.5",
"@types/parse-link-header" : "^2.0.3", "@types/parse-link-header" : "^2.0.3",
"@types/semver" : "^7.5.6", "@types/semver" : "^7.5.6",
"@types/swagger-ui-express" : "^4.1.6", "@types/swagger-ui-express" : "^4.1.6",
"@types/tar-stream" : "^3.1.3", "@types/tar-stream" : "^3.1.3",
"@types/uuid" : "^9.0.7", "@types/uuid" : "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser" : "^6.13.2", "@typescript-eslint/parser" : "^6.18.1",
"dotenv-cli" : "^7.3.0", "dotenv-cli" : "^7.3.0",
"dotenv-vault" : "^1.25.0", "dotenv-vault" : "^1.25.0",
"genversion" : "^3.1.1", "genversion" : "^3.2.0",
"nodemon" : "^3.0.2", "nodemon" : "^3.0.3",
"npm" : "^10.2.4", "npm" : "^10.3.0",
"prisma" : "^5.6.0", "prisma" : "^5.8.1",
"ts-node" : "^10.9.1", "ts-node" : "^10.9.2",
"typescript" : "^5.3.2" "typescript" : "^5.3.3"
} }
} }
...@@ -21,6 +21,11 @@ class Config { ...@@ -21,6 +21,11 @@ class Config {
} }
}; // { version: { CLIENT: CONDITION } } }; // { version: { CLIENT: CONDITION } }
public readonly dojoCLI: {
versionUpdatePeriodMs: number
repositoryId: number
};
public readonly jwtConfig: { public readonly jwtConfig: {
secret: string; expiresIn: number; secret: string; expiresIn: number;
}; };
...@@ -64,6 +69,11 @@ class Config { ...@@ -64,6 +69,11 @@ class Config {
this.requestClientValidation = JSON5.parse(process.env.REQUEST_CLIENT_VALIDATION || '{"version": {}}'); this.requestClientValidation = JSON5.parse(process.env.REQUEST_CLIENT_VALIDATION || '{"version": {}}');
this.dojoCLI = {
versionUpdatePeriodMs: Number(process.env.DOJO_CLI_VERSION_UPDATE_PERIOD_MS || 3600),
repositoryId : Number(process.env.DOJO_CLI_GITLAB_REPOSITORY_ID || 0)
};
this.jwtConfig = { this.jwtConfig = {
secret : process.env.JWT_SECRET_KEY || '', secret : process.env.JWT_SECRET_KEY || '',
expiresIn: Number(process.env.SESSION_TIMEOUT || 0) expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
......
...@@ -16,6 +16,7 @@ import compression from 'compression'; ...@@ -16,6 +16,7 @@ import compression from 'compression';
import ClientVersionCheckerMiddleware from '../middlewares/ClientVersionCheckerMiddleware'; import ClientVersionCheckerMiddleware from '../middlewares/ClientVersionCheckerMiddleware';
import swaggerUi from 'swagger-ui-express'; import swaggerUi from 'swagger-ui-express';
import path from 'path'; import path from 'path';
import DojoCliVersionHelper from '../helpers/DojoCliVersionHelper';
class API implements WorkerTask { class API implements WorkerTask {
...@@ -25,7 +26,7 @@ class API implements WorkerTask { ...@@ -25,7 +26,7 @@ class API implements WorkerTask {
constructor() { constructor() {
this.backend = express(); this.backend = express();
this.initSwagger(); this.initOpenAPI();
this.initBaseMiddlewares(); this.initBaseMiddlewares();
this.backend.use(ClientVersionCheckerMiddleware.register()); this.backend.use(ClientVersionCheckerMiddleware.register());
...@@ -43,9 +44,14 @@ class API implements WorkerTask { ...@@ -43,9 +44,14 @@ class API implements WorkerTask {
this.backend.use(helmet()); //Help to secure express, https://helmetjs.github.io/ this.backend.use(helmet()); //Help to secure express, https://helmetjs.github.io/
this.backend.use(cors()); //Allow CORS requests this.backend.use(cors()); //Allow CORS requests
this.backend.use(compression()); //Compress responses this.backend.use(compression()); //Compress responses
this.backend.use(async (req, res, next) => {
res.header('dojocli-latest-version', await DojoCliVersionHelper.getLatestVersion());
next();
});
} }
private initSwagger() { private initOpenAPI() {
const options = { const options = {
customSiteTitle: 'Dojo API', customSiteTitle: 'Dojo API',
explorer : false, explorer : false,
......
import Config from '../config/Config';
import GitlabRelease from '../shared/types/Gitlab/GitlabRelease';
import GitlabManager from '../managers/GitlabManager';
class DojoCliVersionHelper {
private latestUpdate: Date | undefined;
private latestVersion: string | undefined;
constructor() { }
private async updateVersion(): Promise<void> {
const releases: Array<GitlabRelease> = await GitlabManager.getRepositoryReleases(Config.dojoCLI.repositoryId);
for ( const release of releases ) {
if ( !isNaN(+release.tag_name.replace('.', '')) ) {
this.latestVersion = release.tag_name;
this.latestUpdate = new Date();
return;
}
}
}
public async getLatestVersion(): Promise<string> {
if ( !this.latestVersion || !this.latestUpdate || (new Date()).getTime() - this.latestUpdate.getTime() >= Config.dojoCLI.versionUpdatePeriodMs ) {
await this.updateVersion();
}
return this.latestVersion!;
}
}
export default new DojoCliVersionHelper();
\ No newline at end of file
...@@ -4,8 +4,9 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres ...@@ -4,8 +4,9 @@ import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'expres
import { BailOptions, ValidationChain } from 'express-validator/src/chain'; import { BailOptions, ValidationChain } from 'express-validator/src/chain';
import GitlabManager from '../managers/GitlabManager'; import GitlabManager from '../managers/GitlabManager';
import express from 'express'; import express from 'express';
import SharedExerciseHelper from '../shared/helpers/Dojo/SharedExerciseHelper';
import logger from '../shared/logging/WinstonLogger'; import logger from '../shared/logging/WinstonLogger';
import Json5FileValidator from '../shared/helpers/Json5FileValidator';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
declare type DojoMeta = Meta & { declare type DojoMeta = Meta & {
...@@ -98,7 +99,7 @@ class DojoValidators { ...@@ -98,7 +99,7 @@ class DojoValidators {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const results = this.getParamValue(req, path) as string; const results = this.getParamValue(req, path) as string;
if ( results ) { if ( results ) {
SharedExerciseHelper.validateResultFile(results, false).isValid ? resolve(true) : reject(); Json5FileValidator.validateFile(ExerciseResultsFile, results, false).isValid ? resolve(true) : reject();
} else { } else {
reject(); reject();
} }
......
...@@ -13,6 +13,7 @@ import express from 'express'; ...@@ -13,6 +13,7 @@ import express from 'express';
import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; import GitlabRoute from '../shared/types/Gitlab/GitlabRoute';
import SharedConfig from '../shared/config/SharedConfig'; import SharedConfig from '../shared/config/SharedConfig';
import GitlabProfile from '../shared/types/Gitlab/GitlabProfile'; import GitlabProfile from '../shared/types/Gitlab/GitlabProfile';
import GitlabRelease from '../shared/types/Gitlab/GitlabRelease';
class GitlabManager { class GitlabManager {
...@@ -68,6 +69,12 @@ class GitlabManager { ...@@ -68,6 +69,12 @@ class GitlabManager {
return response.data; return response.data;
} }
async getRepositoryReleases(repoId: number): Promise<Array<GitlabRelease>> {
const response = await axios.get<Array<GitlabRelease>>(this.getApiUrl(GitlabRoute.REPOSITORY_RELEASES_GET).replace('{{id}}', String(repoId)));
return response.data;
}
async createRepository(name: string, description: string, visibility: string, initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, import_url: string): Promise<GitlabRepository> { async createRepository(name: string, description: string, visibility: string, initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, import_url: string): Promise<GitlabRepository> {
const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_CREATE), { const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_CREATE), {
name : name, name : name,
...@@ -241,6 +248,18 @@ class GitlabManager { ...@@ -241,6 +248,18 @@ class GitlabManager {
async updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) { async updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) {
return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail); return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail);
} }
async deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) {
await axios.delete(this.getApiUrl(GitlabRoute.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)), {
data: {
branch : branch,
commit_message: commitMessage,
author_name : authorName,
author_email : authorMail
}
});
}
} }
......
...@@ -120,6 +120,10 @@ class AssignmentRoutes implements RoutesManager { ...@@ -120,6 +120,10 @@ class AssignmentRoutes implements RoutesManager {
return GlobalHelper.repositoryCreationError('Repo params error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository); return GlobalHelper.repositoryCreationError('Repo params error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository);
} }
try {
await GitlabManager.deleteFile(repository.id, '.gitlab-ci.yml', 'Remove .gitlab-ci.yml');
} catch ( error ) { /* empty */ }
try { try {
await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)'); await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
} catch ( error ) { } catch ( error ) {
......
Subproject commit 101cc26895eb0b5fe97e03bb96039e0cddd94391 Subproject commit 89f3579ca9009f793742170928d808ab4c35d931
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"outDir" : "dist", "outDir" : "dist",
"strict" : true, "strict" : true,
"target" : "es6", "target" : "ES2022",
"module" : "commonjs", "module" : "commonjs",
"sourceMap" : true, "sourceMap" : true,
"esModuleInterop" : true, "esModuleInterop" : true,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment