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 (50)
Showing
with 2823 additions and 1795 deletions
......@@ -7,6 +7,8 @@ ExpressAPI/src/config/Version.ts
redoc.html
OpenAPI.yaml-r
sonarlint.xml
sonarlint/
############################ MacOS
# General
......
......@@ -16,6 +16,7 @@ variables:
WIKI_FOLDER: Wiki
.get_version:
script:
- IS_DEV=$([[ $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ]] && echo false || echo true)
......@@ -72,6 +73,36 @@ code_quality:lint:
- npm install
- npm run lint
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- when: on_success
code_quality:sonarqube:
stage: code_quality
tags:
- code_quality
image:
name: leadrien/isc-sonar-scanner-cli
entrypoint: [ "" ]
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- sonar-scanner
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- when: on_success
test:build:
......@@ -84,7 +115,11 @@ test:build:
- npm install
- npm run build
rules:
- if: '$CI_COMMIT_TAG =~ "/^$/"'
- if: $CI_COMMIT_TAG
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- when: on_success
clean:release:
......@@ -96,7 +131,7 @@ clean:release:
- !reference [.get_version, script]
- !reference [.clean_release, script]
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
- if: $CI_COMMIT_REF_PROTECTED == "true"
clean:packages:
......@@ -108,10 +143,10 @@ clean:packages:
- !reference [.get_version, script]
- !reference [.clean_packages, script]
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
- if: $CI_COMMIT_REF_PROTECTED == "true"
clean:dev:release:
clean:release:dev:
stage: clean
tags:
- gitlab_clean
......@@ -121,10 +156,10 @@ clean:dev:release:
- VERSION="${VERSION}${VERSION_DEV_SUFFIX}"
- !reference [.clean_release, script]
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
clean:dev:packages:
clean:packages:dev:
stage: clean
tags:
- gitlab_clean
......@@ -134,10 +169,10 @@ clean:dev:packages:
- VERSION="${VERSION}${VERSION_DEV_SUFFIX}"
- !reference [.clean_packages, script]
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
upload:packages:wiki:
upload:packages:doc:wiki:
stage: upload
tags:
- gitlab_package
......@@ -157,10 +192,10 @@ upload:packages:wiki:
# Send package
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${WIKI_ARCHIVE_PATH} "${PACKAGE_URL_WIKI}";'
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
- if: $CI_COMMIT_REF_PROTECTED == "true"
release:wiki:
release:doc:wiki:
stage: release
tags:
- release
......@@ -205,7 +240,7 @@ release:wiki:
# Push the change back to the master branch of the wiki
- git push origin "HEAD:main"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
release:gitlab:
......@@ -245,4 +280,4 @@ release:gitlab:
--header "JOB-TOKEN: $CI_JOB_TOKEN" \
--request POST "${GITLAB_API_PROJECT_URL}/releases"
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
- if: $CI_COMMIT_REF_PROTECTED == "true"
......@@ -18,6 +18,24 @@
-->
## 4.0.0 (???)
### ✨ Feature
- Add features related to corrige (commentary, commit specific link / update, delete link)
### 🔨 Internal / Developers
- Migration to GitBreaker library for all Gitlab API calls
- SonarQube integration
- Multi-process start is disabled where it is not in a production environment
### 🐛 Bugfix
- Fix no response when Authorization header is missing
- Fix get assignment by url with assignment containing spaces
### 📚 Documentation
- Corrige routes documentation
## 3.5.3 (2024-02-26)
### 🐛 Bugfix
......
This diff is collapsed.
dist
node_modules
logs
prisma
\ No newline at end of file
{
"root" : true,
"parser" : "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
]
}
\ No newline at end of file
......@@ -6,3 +6,5 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions
......@@ -15,4 +15,7 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="SonarLintModuleSettings">
<option name="uniqueId" value="2749ea0f-74a8-42c0-9fd6-d6a4b4cd75a4" />
</component>
</module>
\ No newline at end of file
Subproject commit ffc5d65f9f0f0e825688177425e526131aa84631
Subproject commit ef5c7bd49a57bc28db77bad797de4980133d6523
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<custom-configuration-file used="false" path="eslint.config.mjs" />
<option name="fix-on-save" value="true" />
</component>
</project>
\ No newline at end of file
openapi: 3.1.0
info:
title: Dojo API
version: 3.5.3
version: 4.0.0
description: |
**Backend API of the Dojo project.**
......
// @ts-check
// @formatter:off
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config({
ignores: [ 'dist/*', 'node_modules/*', 'logs/*', 'prisma/*', 'eslint.config.mjs' ]
}, eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, {
languageOptions: {
parserOptions: {
project: true, tsconfigRootDir: import.meta.dirname
}
}
}, {
plugins: {
'@typescript-eslint': tseslint.plugin
}, rules: {
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
}
});
\ No newline at end of file
......@@ -9,5 +9,5 @@
"verbose": true,
"ext" : ".ts,.js",
"ignore" : [],
"exec" : "npm run lint; npm run build:openapi; ts-node --files ./src/app.ts"
"exec" : "npm run lint; npm run build:openapi; tsc --noEmit && npx tsx src/app.ts"
}
This diff is collapsed.
{
"name" : "dojo_backend_api",
"description" : "Backend API of the Dojo project",
"version" : "3.5.3",
"version" : "4.0.0",
"license" : "AGPLv3",
"author" : "Michaël Minelli <dojo@minelli.me>",
"main" : "dist/src/app.js",
"scripts" : {
"clean" : "rm -R dist/*",
"dotenv:build" : "npx dotenv-vault local build",
"dotenv:build" : "npx dotenvx encrypt",
"lint" : "npx eslint .",
"genversion" : "npx genversion -s -e src/config/Version.ts",
"build:openapi" : "sed -i -r \"1,20 s/^\\([ ]*version:\\).*$/\\1 $(jq -r .version package.json)/\" assets/OpenAPI/OpenAPI.yaml; npx @redocly/cli build-docs assets/OpenAPI/OpenAPI.yaml --output=assets/OpenAPI/redoc.html",
......@@ -28,14 +28,13 @@
"seed": "node dist/prisma/seed"
},
"dependencies" : {
"@gitbeaker/rest" : "^39.34.2",
"@prisma/client" : "^5.9.1",
"axios" : "^1.6.7",
"@dotenvx/dotenvx" : "^0.27.1",
"@gitbeaker/rest" : "^40.0.2",
"@prisma/client" : "^5.11.0",
"axios" : "^1.6.8",
"compression" : "^1.7.4",
"cors" : "^2.8.5",
"dotenv" : "^16.4.1",
"dotenv-expand" : "^10.0.0",
"express" : "^4.18.2",
"express" : "^4.19.2",
"express-validator" : "^7.0.1",
"form-data" : "^4.0.0",
"helmet" : "^7.1.0",
......@@ -51,33 +50,31 @@
"swagger-ui-express" : "^5.0.0",
"tar-stream" : "^3.1.7",
"uuid" : "^9.0.1",
"winston" : "^3.11.0",
"winston" : "^3.13.0",
"zod" : "^3.22.4",
"zod-validation-error": "^3.0.0"
"zod-validation-error": "^3.0.3"
},
"devDependencies": {
"@redocly/cli" : "^1.8.2",
"@redocly/cli" : "^1.10.6",
"@types/compression" : "^1.7.5",
"@types/cors" : "^2.8.17",
"@types/express" : "^4.17.21",
"@types/jsonwebtoken" : "^9.0.5",
"@types/jsonwebtoken" : "^9.0.6",
"@types/morgan" : "^1.9.9",
"@types/multer" : "^1.4.11",
"@types/node" : "^20.11.17",
"@types/node" : "^20.11.30",
"@types/parse-link-header" : "^2.0.3",
"@types/semver" : "^7.5.6",
"@types/semver" : "^7.5.8",
"@types/swagger-ui-express": "^4.1.6",
"@types/tar-stream" : "^3.1.3",
"@types/uuid" : "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser" : "^6.21.0",
"dotenv-cli" : "^7.3.0",
"dotenv-vault" : "^1.26.0",
"eslint" : "^8.57.0",
"genversion" : "^3.2.0",
"nodemon" : "^3.0.3",
"npm" : "^10.4.0",
"prisma" : "^5.9.1",
"ts-node" : "^10.9.2",
"typescript" : "^5.3.3"
"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"
}
}
require('../src/InitialImports'); // ATTENTION : These lines MUST be the first of this file
// ATTENTION : This line MUST be the first of this file
import '../src/init.js';
import * as process from 'process';
import SharedConfig from '../src/shared/config/SharedConfig';
import SharedConfig from '../src/shared/config/SharedConfig.js';
import { UserRole } from '@prisma/client';
import logger from '../src/shared/logging/WinstonLogger';
import db from '../src/helpers/DatabaseHelper';
import logger from '../src/shared/logging/WinstonLogger.js';
import db from '../src/helpers/DatabaseHelper.js';
async function main() {
......@@ -16,8 +17,8 @@ async function main() {
main().then(async () => {
await db.$disconnect();
}).catch(async (e) => {
logger.error(e);
}).catch(async e => {
logger.error(JSON.stringify(e));
await db.$disconnect();
process.exit(1);
});
......
import path from 'node:path';
import cluster from 'node:cluster';
import myEnv = require('dotenv');
import dotenvExpand = require('dotenv-expand');
if ( cluster.isPrimary ) {
if ( process.env.NODE_ENV && process.env.NODE_ENV === 'production' ) {
dotenvExpand.expand(myEnv.config());
} else {
myEnv.config({ path: path.join(__dirname, '../.env.keys') });
dotenvExpand.expand(myEnv.config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT }));
}
}
require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be after the dotenv.config() calls
require('./InitialImports'); // ATTENTION : These lines MUST be the first of this file
// ATTENTION : This line MUST be the first of this file
import './init.js';
import WorkerRole from './process/WorkerRole';
import ClusterManager from './process/ClusterManager';
import API from './express/API';
import HttpManager from './managers/HttpManager';
import SharedConfig from './shared/config/SharedConfig.js';
import WorkerRole from './process/WorkerRole.js';
import ClusterManager from './process/ClusterManager.js';
import API from './express/API.js';
import HttpManager from './managers/HttpManager.js';
HttpManager.registerAxiosInterceptor();
if ( SharedConfig.production ) {
(new ClusterManager([ {
role : WorkerRole.API,
quantity : ClusterManager.CORES,
restartOnFail: true,
loadTask : () => {
return new API();
}
loadTask : () => new API()
} ])).run();
} else {
(new API()).run();
}
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import path from 'path';
import fs from 'fs';
import { Exercise } from '../types/DatabaseTypes';
import { Exercise } from '../types/DatabaseTypes.js';
import JSON5 from 'json5';
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility.js';
type ConfigGitlabBadge = {
......@@ -19,7 +19,7 @@ class Config {
version: {
[client: string]: string
}
}; // { version: { CLIENT: CONDITION } }
};
public readonly dojoCLI: {
versionUpdatePeriodMs: number
......@@ -52,13 +52,13 @@ class Config {
public readonly assignment: {
default: {
description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: string; wikiEnabled: boolean; template: string
description: string; initReadme: boolean; sharedRunnersEnabled: boolean; visibility: GitlabVisibility; wikiEnabled: boolean; template: string
}; baseFiles: Array<string>; filename: string
};
public readonly exercise: {
maxSameName: number; maxPerAssignment: number; resultsFolder: string, pipelineResultsFolder: string; default: {
description: string; visibility: string;
description: string; visibility: GitlabVisibility;
};
};
......@@ -116,7 +116,7 @@ class Config {
description : process.env.ASSIGNMENT_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
initReadme : process.env.ASSIGNMENT_DEFAULT_INIT_README?.toBoolean() ?? false,
sharedRunnersEnabled: process.env.ASSIGNMENT_DEFAULT_SHARED_RUNNERS_ENABLED?.toBoolean() ?? true,
visibility : process.env.ASSIGNMENT_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE,
visibility : process.env.ASSIGNMENT_DEFAULT_VISIBILITY as GitlabVisibility || 'private',
wikiEnabled : process.env.ASSIGNMENT_DEFAULT_WIKI_ENABLED?.toBoolean() ?? false,
template : process.env.ASSIGNMENT_DEFAULT_TEMPLATE?.replace('{{USERNAME}}', this.gitlab.account.username).replace('{{TOKEN}}', this.gitlab.account.token) ?? ''
},
......@@ -131,7 +131,7 @@ class Config {
pipelineResultsFolder: process.env.EXERCISE_PIPELINE_RESULTS_FOLDER ?? '', //Do not use convertWithEnvVars() because it is used in the exercise creation and muste be interpreted at exercise runtime
default : {
description: process.env.EXERCISE_DEFAULT_DESCRIPTION?.convertWithEnvVars() ?? '',
visibility : process.env.EXERCISE_DEFAULT_VISIBILITY || GitlabVisibility.PRIVATE
visibility : process.env.EXERCISE_DEFAULT_VISIBILITY as GitlabVisibility || 'private'
}
};
}
......
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import * as jwt from 'jsonwebtoken';
import { JwtPayload } from 'jsonwebtoken';
import Config from '../config/Config';
import Config from '../config/Config.js';
import express from 'express';
import UserManager from '../managers/UserManager';
import { User } from '../types/DatabaseTypes';
import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse';
import UserManager from '../managers/UserManager.js';
import { User } from '../types/DatabaseTypes.js';
import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse.js';
class Session {
......@@ -19,12 +19,9 @@ class Session {
this._profile = newProfile;
}
constructor() { }
async initSession(req: express.Request, res: express.Response) {
const authorization = req.headers.authorization;
if ( authorization ) {
if ( authorization.startsWith('Bearer ') ) {
if ( authorization && authorization.startsWith('Bearer ') ) {
const jwtToken = authorization.replace('Bearer ', '');
try {
......@@ -32,17 +29,17 @@ class Session {
if ( jwtData.profile ) {
this.profile = jwtData.profile;
this.profile = await UserManager.getById(this.profile.id!) ?? this.profile;
this.profile = await UserManager.getById(this.profile.id) ?? this.profile;
}
} catch ( err ) {
res.sendStatus(StatusCodes.UNAUTHORIZED).end();
}
}
}
}
private static getToken(profileJson: unknown): string | null {
return profileJson === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {});
const options = Config.jwtConfig.expiresIn > 0 ? { expiresIn: Config.jwtConfig.expiresIn } : {};
return profileJson === null ? null : jwt.sign({ profile: profileJson }, Config.jwtConfig.secret, options);
}
private async getResponse<T>(code: number, data: T, descriptionOverride?: string): Promise<DojoBackendResponse<T>> {
......@@ -67,14 +64,16 @@ class Session {
Send a response to the client
Information: Data could be a promise or an object. If it's a promise, we wait on the data to be resolved before sending the response
*/
sendResponse(res: express.Response, code: number, data?: unknown, descriptionOverride?: string, internalCode?: number) {
Promise.resolve(data).then((toReturn: unknown) => {
this.getResponse(internalCode ?? code, toReturn, descriptionOverride).then(response => {
sendResponse(res: express.Response | undefined, code: number, data?: unknown, descriptionOverride?: string, internalCode?: number) {
if ( res ) {
void Promise.resolve(data).then((toReturn: unknown) => {
void this.getResponse(internalCode ?? code, toReturn, descriptionOverride).then(response => {
res.status(code).json(response);
});
});
}
}
}
export default Session;