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 (5)
Showing
with 1322 additions and 71 deletions
......@@ -55,12 +55,25 @@ variables:
stages:
- code_quality
- test
- clean
- upload
- release
code_quality:lint:
stage: code_quality
tags:
- code_quality
image: node:latest
script:
- cd "${PROJECT_FOLDER}"
- npm install
- npm run lint
test:build:
stage: test
image: node:latest
......
......@@ -17,7 +17,17 @@
- No modifications / Keep major and minors versions in sync with all parts of the project
-->
## 3.0.0 (?)
## 3.1.0 (???)
### 🔨 Internal / Developers
- **Typescript**: Add linter (ESLint)
### 📚 Documentation
- **API**: Routes documentation with Swagger
## 3.0.0 (2023-11-03)
### ✨ Feature
- Login to Dojo app via Gitlab OAuth
......
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
Subproject commit 4d703a2dd39ec0c2b71bbbbda8900588c4e360bd
Subproject commit ffc5d65f9f0f0e825688177425e526131aa84631
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>
\ No newline at end of file
......@@ -9,5 +9,5 @@
"verbose": true,
"ext" : ".ts,.js",
"ignore" : [],
"exec" : "ts-node --files ./src/app.ts"
"exec" : "npm run lint; ts-node --files ./src/app.ts"
}
This diff is collapsed.
{
"name" : "dojo_backend_api",
"description" : "Backend API of the Dojo project",
"version" : "3.0.1",
"version" : "3.1.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",
"lint" : "npx eslint .",
"genversion" : "npx genversion -s -e src/config/Version.ts",
"build" : "npm run genversion; npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets",
"database:migrate" : "npx prisma migrate deploy",
......@@ -58,6 +59,8 @@
"@types/semver" : "^7.5.3",
"@types/tar-stream" : "^2.2.2",
"@types/uuid" : "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser" : "^6.10.0",
"dotenv-vault" : "^1.25.0",
"genversion" : "^3.1.1",
"nodemon" : "^3.0.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' ) {
const myEnv = require('dotenv').config();
require('dotenv-expand').expand(myEnv);
dotenvExpand.expand(myEnv.config());
} else {
require('dotenv').config({ path: path.join(__dirname, '../.env.keys') });
const myEnv = require('dotenv').config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT });
require('dotenv-expand').expand(myEnv);
myEnv.config({ path: path.join(__dirname, '../.env.keys') });
dotenvExpand.expand(myEnv.config({ DOTENV_KEY: process.env.DOTENV_KEY_DEVELOPMENT }));
}
}
......
......@@ -41,7 +41,7 @@ class Session {
}
}
private static getToken(profileJson: any): string | null {
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 } : {});
}
......@@ -52,7 +52,7 @@ class Session {
try {
reasonPhrase = getReasonPhrase(code);
} catch {}
} catch { /* empty */ }
return {
timestamp : (new Date()).toISOString(),
......@@ -67,8 +67,8 @@ 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?: any, descriptionOverride?: string, internalCode?: number) {
Promise.resolve(data).then((toReturn: any) => {
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 => {
res.status(code).json(response);
});
......
......@@ -5,13 +5,14 @@ import { BailOptions, ValidationChain } from 'expres
import GitlabManager from '../managers/GitlabManager';
import express from 'express';
import SharedExerciseHelper from '../shared/helpers/Dojo/SharedExerciseHelper';
import logger from '../shared/logging/WinstonLogger';
declare type DojoMeta = Meta & {
req: express.Request
};
declare type DojoCustomValidator = (input: any, meta: DojoMeta) => any;
declare type DojoCustomValidator = (input: unknown, meta: DojoMeta) => unknown;
declare type DojoCustomValidatorSchemaOptions = { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: CustomValidator | ValidationChain, options?: CustomValidator }
......@@ -22,7 +23,7 @@ class DojoValidators {
return arg as unknown as DojoCustomValidatorSchemaOptions;
}
private getParamValue(req: express.Request, path: string): any {
private getParamValue(req: express.Request, path: string): unknown {
return 'body' in req && path in req.body ? req.body[path] : req.query[path];
}
......@@ -30,7 +31,9 @@ class DojoValidators {
options: (value) => {
try {
return value == 'null' || value == 'undefined' || value == '' ? null : value;
} catch ( e ) {
} catch ( error ) {
logger.error(`null sanitizer error: ${ error }`);
return value;
}
}
......@@ -39,7 +42,7 @@ class DojoValidators {
readonly jsonSanitizer = this.toValidatorSchemaOptions({
options: (value) => {
try {
return JSON.parse(value);
return JSON.parse(value as string);
} catch ( e ) {
return value;
}
......@@ -54,7 +57,7 @@ class DojoValidators {
path
}) => {
return new Promise((resolve, reject) => {
const template = this.getParamValue(req, path);
const template = this.getParamValue(req, path) as string;
if ( template ) {
GitlabManager.checkTemplateAccess(template, req).then((templateAccess) => {
templateAccess !== StatusCodes.OK ? reject() : resolve(true);
......@@ -77,10 +80,12 @@ class DojoValidators {
} else {
return Config.assignment.default.template;
}
} catch ( e ) { }
} catch ( error ) {
logger.error(`Template url sanitizer error: ${ error }`);
return value;
}
}
});
readonly exerciseResultsValidator = this.toValidatorSchemaOptions({
......@@ -91,7 +96,7 @@ class DojoValidators {
path
}) => {
return new Promise((resolve, reject) => {
const results = this.getParamValue(req, path);
const results = this.getParamValue(req, path) as string;
if ( results ) {
SharedExerciseHelper.validateResultFile(results, false).isValid ? resolve(true) : reject();
} else {
......
......@@ -8,7 +8,7 @@ import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
class GlobalHelper {
async repositoryCreationError(message: string, error: any, req: express.Request, res: express.Response, gitlabError: DojoStatusCode, internalError: DojoStatusCode, repositoryToRemove?: GitlabRepository): Promise<void> {
async repositoryCreationError(message: string, error: unknown, req: express.Request, res: express.Response, gitlabError: DojoStatusCode, internalError: DojoStatusCode, repositoryToRemove?: GitlabRepository): Promise<void> {
logger.error(message);
logger.error(error);
......@@ -26,7 +26,7 @@ class GlobalHelper {
}
return req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, {}, `Unknown error: ${ message }`, internalError);
};
}
}
......
......@@ -29,33 +29,32 @@ class GitlabManager {
DojoAuthorizationValue : `Bearer ${ token }`
}
})).data;
} catch ( e ) { }
} catch ( e ) {
return undefined;
}
}
public async getUserById(id: number): Promise<GitlabUser | undefined> {
try {
const params: any = {};
const user = (await axios.get<GitlabUser>(`${ this.getApiUrl(GitlabRoute.USERS_GET) }/${ String(id) }`, { params: params })).data;
const user = (await axios.get<GitlabUser>(`${ this.getApiUrl(GitlabRoute.USERS_GET) }/${ String(id) }`)).data;
return user.id === id ? user : undefined;
} catch ( e ) { }
} catch ( e ) {
return undefined;
}
}
public async getUserByUsername(username: string): Promise<GitlabUser | undefined> {
try {
const params: any = {};
const params: Record<string, string> = {};
params['search'] = username;
const user = (await axios.get<Array<GitlabUser>>(this.getApiUrl(GitlabRoute.USERS_GET), { params: params })).data[0];
return user.username === username ? user : undefined;
} catch ( e ) { }
} catch ( e ) {
return undefined;
}
}
async getRepository(idOrNamespace: string): Promise<GitlabRepository> {
const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(idOrNamespace)));
......@@ -185,15 +184,15 @@ class GitlabManager {
}
async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<GitlabTreeFile>> {
let address: string | undefined = this.getApiUrl(GitlabRoute.REPOSITORY_TREE).replace('{{id}}', String(repoId));
let params: any = {
const address: string | undefined = this.getApiUrl(GitlabRoute.REPOSITORY_TREE).replace('{{id}}', String(repoId));
let params: Partial<parseLinkHeader.Link | { recursive: boolean, per_page: number }> | undefined = {
pagination: 'keyset',
recursive : recursive,
per_page : 100,
ref : branch
};
let results: Array<GitlabTreeFile> = [];
const results: Array<GitlabTreeFile> = [];
while ( params !== undefined ) {
const response = await axios.get<Array<GitlabTreeFile>>(address, {
......
......@@ -24,7 +24,7 @@ class UserManager {
}) as unknown as User ?? undefined;
}
async getUpdateFromGitlabProfile(gitlabProfile: GitlabProfile, refreshToken: string): Promise<User> {
async getUpdateFromGitlabProfile(gitlabProfile: GitlabProfile): Promise<User> {
await db.user.upsert({
where : {
id: gitlabProfile.id
......
......@@ -5,16 +5,16 @@ import ExerciseManager from '../managers/ExerciseManager';
import AssignmentManager from '../managers/AssignmentManager';
type GetFunction = (id: string | number, ...args: Array<any>) => Promise<any>
type GetFunction = (id: string | number, ...args: Array<unknown>) => Promise<unknown>
class ParamsCallbackManager {
protected listenParam(paramName: string, backend: Express, getFunction: GetFunction, args: Array<any>, indexName: string) {
protected listenParam(paramName: string, backend: Express, getFunction: GetFunction, args: Array<unknown>, indexName: string) {
backend.param(paramName, (req: express.Request, res: express.Response, next: express.NextFunction, id: string | number) => {
getFunction(id, ...args).then(result => {
if ( result ) {
this.initBoundParams(req);
(req.boundParams as any)[indexName] = result;
(req.boundParams as Record<string, unknown>)[indexName] = result;
next();
} else {
......
......@@ -58,18 +58,25 @@ class AssignmentRoutes implements RoutesManager {
const assignment: Assignment | undefined = req.boundParams.assignment;
if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment, req.session.profile) ) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.gitlabId;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.gitlabLink;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.gitlabCreationInfo;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.gitlabLastInfo;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.gitlabLastInfoDate;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.staff;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete assignment.exercises;
}
......
......@@ -29,6 +29,7 @@ import AssignmentFile from '../shared/types/Dojo/AssignmentFile';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
import GlobalHelper from '../helpers/GlobalHelper';
import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats';
class ExerciseRoutes implements RoutesManager {
......@@ -187,8 +188,8 @@ class ExerciseRoutes implements RoutesManager {
const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId);
let assignmentHjsonFile!: GitlabFile;
let immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => {
let file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile);
const immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => {
const file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile);
if ( baseFile === Config.assignment.filename ) {
assignmentHjsonFile = file;
......@@ -220,7 +221,7 @@ class ExerciseRoutes implements RoutesManager {
}
private async createResult(req: express.Request, res: express.Response) {
const params: { exitCode: number, commit: any, results: ExerciseResultsFile, files: any, archiveBase64: string } = req.body;
const params: { exitCode: number, commit: Record<string, string>, results: ExerciseResultsFile, files: Array<IFileDirStat>, archiveBase64: string } = req.body;
const exercise: Exercise = req.boundParams.exercise!;
const result = await db.result.create({
......
......@@ -46,7 +46,7 @@ class SessionRoutes implements RoutesManager {
const gitlabUser = await GitlabManager.getUserProfile(params.accessToken);
if ( gitlabUser ) {
req.session.profile = await UserManager.getUpdateFromGitlabProfile(gitlabUser, params.refreshToken);
req.session.profile = await UserManager.getUpdateFromGitlabProfile(gitlabUser);
req.session.sendResponse(res, StatusCodes.OK);
return;
......