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 @@
- 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
- **Exercices**: Set names of students in exercise name in alphabetical order
......
This diff is collapsed.
openapi: 3.1.0
info:
title: Dojo API
version: 3.2.0
version: 3.3.0
description: |
**Backend API of the Dojo project.**
......
......@@ -18,7 +18,7 @@ dojo:
tags:
- dojo_assignment
services:
- docker:dind
- docker:docker:24.0.6-dind
image:
name: dojohesso/dojo_assignment_checker:latest
script:
......
......@@ -18,7 +18,7 @@ dojo:
tags:
- dojo_exercise
services:
- docker:dind
- docker:24.0.6-dind
image:
name: dojohesso/dojo_exercise_checker:latest
script:
......
This diff is collapsed.
{
"name" : "dojo_backend_api",
"description" : "Backend API of the Dojo project",
"version" : "3.2.0",
"version" : "3.3.0",
"license" : "AGPLv3",
"author" : "Michaël Minelli <dojo@minelli.me>",
"main" : "dist/src/app.js",
......@@ -28,9 +28,8 @@
"seed": "node dist/prisma/seed"
},
"dependencies" : {
"@prisma/client" : "^5.6.0",
"ajv" : "^8.12.0",
"axios" : "^1.6.2",
"@prisma/client" : "^5.8.1",
"axios" : "^1.6.5",
"compression" : "^1.7.4",
"cors" : "^2.8.5",
"dotenv" : "^16.3.1",
......@@ -51,31 +50,33 @@
"swagger-ui-express" : "^5.0.0",
"tar-stream" : "^3.1.6",
"uuid" : "^9.0.1",
"winston" : "^3.11.0"
"winston" : "^3.11.0",
"zod" : "^3.22.4",
"zod-validation-error": "^3.0.0"
},
"devDependencies": {
"@redocly/cli" : "^1.5.0",
"@redocly/cli" : "^1.6.0",
"@types/compression" : "^1.7.5",
"@types/cors" : "^2.8.17",
"@types/express" : "^4.17.21",
"@types/jsonwebtoken" : "^9.0.5",
"@types/morgan" : "^1.9.9",
"@types/multer" : "^1.4.11",
"@types/node" : "^20.10.3",
"@types/node" : "^20.11.5",
"@types/parse-link-header" : "^2.0.3",
"@types/semver" : "^7.5.6",
"@types/swagger-ui-express" : "^4.1.6",
"@types/tar-stream" : "^3.1.3",
"@types/uuid" : "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser" : "^6.13.2",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser" : "^6.18.1",
"dotenv-cli" : "^7.3.0",
"dotenv-vault" : "^1.25.0",
"genversion" : "^3.1.1",
"nodemon" : "^3.0.2",
"npm" : "^10.2.4",
"prisma" : "^5.6.0",
"ts-node" : "^10.9.1",
"typescript" : "^5.3.2"
"genversion" : "^3.2.0",
"nodemon" : "^3.0.3",
"npm" : "^10.3.0",
"prisma" : "^5.8.1",
"ts-node" : "^10.9.2",
"typescript" : "^5.3.3"
}
}
......@@ -21,6 +21,11 @@ class Config {
}
}; // { version: { CLIENT: CONDITION } }
public readonly dojoCLI: {
versionUpdatePeriodMs: number
repositoryId: number
};
public readonly jwtConfig: {
secret: string; expiresIn: number;
};
......@@ -64,6 +69,11 @@ class Config {
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 = {
secret : process.env.JWT_SECRET_KEY || '',
expiresIn: Number(process.env.SESSION_TIMEOUT || 0)
......
......@@ -16,6 +16,7 @@ import compression from 'compression';
import ClientVersionCheckerMiddleware from '../middlewares/ClientVersionCheckerMiddleware';
import swaggerUi from 'swagger-ui-express';
import path from 'path';
import DojoCliVersionHelper from '../helpers/DojoCliVersionHelper';
class API implements WorkerTask {
......@@ -25,7 +26,7 @@ class API implements WorkerTask {
constructor() {
this.backend = express();
this.initSwagger();
this.initOpenAPI();
this.initBaseMiddlewares();
this.backend.use(ClientVersionCheckerMiddleware.register());
......@@ -43,9 +44,14 @@ class API implements WorkerTask {
this.backend.use(helmet()); //Help to secure express, https://helmetjs.github.io/
this.backend.use(cors()); //Allow CORS requests
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 = {
customSiteTitle: 'Dojo API',
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
import { BailOptions, ValidationChain } from 'express-validator/src/chain';
import GitlabManager from '../managers/GitlabManager';
import express from 'express';
import SharedExerciseHelper from '../shared/helpers/Dojo/SharedExerciseHelper';
import logger from '../shared/logging/WinstonLogger';
import Json5FileValidator from '../shared/helpers/Json5FileValidator';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
declare type DojoMeta = Meta & {
......@@ -98,7 +99,7 @@ class DojoValidators {
return new Promise((resolve, reject) => {
const results = this.getParamValue(req, path) as string;
if ( results ) {
SharedExerciseHelper.validateResultFile(results, false).isValid ? resolve(true) : reject();
Json5FileValidator.validateFile(ExerciseResultsFile, results, false).isValid ? resolve(true) : reject();
} else {
reject();
}
......
......@@ -13,6 +13,7 @@ import express from 'express';
import GitlabRoute from '../shared/types/Gitlab/GitlabRoute';
import SharedConfig from '../shared/config/SharedConfig';
import GitlabProfile from '../shared/types/Gitlab/GitlabProfile';
import GitlabRelease from '../shared/types/Gitlab/GitlabRelease';
class GitlabManager {
......@@ -68,6 +69,12 @@ class GitlabManager {
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> {
const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_CREATE), {
name : name,
......@@ -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) {
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 {
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 {
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 ) {
......
Subproject commit 101cc26895eb0b5fe97e03bb96039e0cddd94391
Subproject commit 89f3579ca9009f793742170928d808ab4c35d931
......@@ -2,7 +2,7 @@
"compilerOptions": {
"outDir" : "dist",
"strict" : true,
"target" : "es6",
"target" : "ES2022",
"module" : "commonjs",
"sourceMap" : true,
"esModuleInterop" : true,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment