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

Merge branch 'routes-documentation' into v3.1.0

parents eb602fde 4dbc78f2
Branches
Tags
No related merge requests found
Pipeline #27134 passed
Showing
with 2719 additions and 678 deletions
......@@ -4,6 +4,9 @@ workspace.xml
Wiki/.idea
ExpressAPI/src/config/Version.ts
redoc.html
OpenAPI.yaml-r
############################ MacOS
# General
......
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SwaggerSettings">
<option name="defaultPreviewType" value="SWAGGER_UI" />
</component>
</project>
\ No newline at end of file
This diff is collapsed.
......@@ -9,5 +9,5 @@
"verbose": true,
"ext" : ".ts,.js",
"ignore" : [],
"exec" : "npm run lint; ts-node --files ./src/app.ts"
"exec" : "npm run lint; npm run build:openapi; ts-node --files ./src/app.ts"
}
This diff is collapsed.
......@@ -10,7 +10,9 @@
"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",
"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",
"build:project" : "npm run genversion; npx prisma generate && npx tsc --project ./ && cp -R assets dist/assets",
"build" : "npm run build:openapi; npm run build:project",
"database:migrate" : "npx prisma migrate deploy",
"database:seed" : "npm run genversion; npx prisma db seed",
"database:deploy" : "npm run database:migrate && npm run database:seed",
......@@ -43,11 +45,13 @@
"node" : "^20.5.0",
"parse-link-header" : "^2.0.0",
"semver" : "^7.5.4",
"swagger-ui-express": "^5.0.0",
"tar-stream" : "^3.1.6",
"uuid" : "^9.0.0",
"winston" : "^3.8.2"
},
"devDependencies": {
"@redocly/cli" : "^1.5.0",
"@types/compression" : "^1.7.2",
"@types/cors" : "^2.8.13",
"@types/express" : "^4.17.17",
......@@ -57,16 +61,17 @@
"@types/node" : "^20.4.7",
"@types/parse-link-header" : "^2.0.1",
"@types/semver" : "^7.5.3",
"@types/swagger-ui-express" : "^4.1.6",
"@types/tar-stream" : "^2.2.2",
"@types/uuid" : "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser" : "^6.10.0",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser" : "^6.12.0",
"dotenv-vault" : "^1.25.0",
"genversion" : "^3.1.1",
"nodemon" : "^3.0.1",
"npm" : "^9.8.1",
"prisma" : "^5.1.1",
"ts-node" : "^10.9.1",
"typescript" : "^5.1.6",
"npm" : "^9.8.1"
"typescript" : "^5.1.6"
}
}
......@@ -13,7 +13,9 @@ import logger from '../shared/logging/WinstonLogger';
import ParamsCallbackManager from '../middlewares/ParamsCallbackManager';
import ApiRoutesManager from '../routes/ApiRoutesManager';
import compression from 'compression';
import ClientVersionMiddleware from '../middlewares/ClientVersionMiddleware';
import ClientVersionCheckerMiddleware from '../middlewares/ClientVersionCheckerMiddleware';
import swaggerUi from 'swagger-ui-express';
import path from 'path';
class API implements WorkerTask {
......@@ -23,6 +25,17 @@ class API implements WorkerTask {
constructor() {
this.backend = express();
this.initSwagger();
this.initBaseMiddlewares();
this.backend.use(ClientVersionCheckerMiddleware.register());
ParamsCallbackManager.registerOnBackend(this.backend);
SessionMiddleware.registerOnBackend(this.backend);
ApiRoutesManager.registerOnBackend(this.backend);
}
private initBaseMiddlewares() {
this.backend.use(multer({
limits: { fieldSize: 100 * 1024 * 1024 }
}).none()); //Used for extract params from body with format "form-data", The none is for say that we do not wait a file in params
......@@ -30,14 +43,36 @@ 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(ClientVersionMiddleware.register());
ParamsCallbackManager.register(this.backend);
this.backend.use(SessionMiddleware.register());
private initSwagger() {
const options = {
swaggerOptions: {
url: '/docs/OpenAPI.yaml'
}
};
this.backend.get('/docs/OpenAPI.yaml', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/OpenAPI/OpenAPI.yaml')));
this.backend.use('/docs/swagger', swaggerUi.serveFiles(undefined, options), swaggerUi.setup(undefined, options));
this.backend.get('/docs/redoc.html', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/OpenAPI/redoc.html')));
ApiRoutesManager.registerOnBackend(this.backend);
this.backend.get('/docs/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<li><a href="/docs/OpenAPI.yaml">OpenAPI</a></li>
<li>GUI
<ul>
<li><a href="/docs/swagger">Swagger</a></li>
<li><a href="/docs/redoc.html">Redoc</a></li>
</ul>
</li>
</ul>
</body>
</html>
`);
});
}
run() {
......
......@@ -56,8 +56,8 @@ class GitlabManager {
}
}
async getRepository(idOrNamespace: string): Promise<GitlabRepository> {
const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(idOrNamespace)));
async getRepository(projectIdOrNamespace: string): Promise<GitlabRepository> {
const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(projectIdOrNamespace)));
return response.data;
}
......@@ -140,10 +140,10 @@ class GitlabManager {
return response.data;
}
async checkTemplateAccess(idOrNamespace: string, req: express.Request): Promise<StatusCodes> {
async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request): Promise<StatusCodes> {
// Get the Gitlab project and check if it have public or internal visibility
try {
const project: GitlabRepository = await this.getRepository(idOrNamespace);
const project: GitlabRepository = await this.getRepository(projectIdOrNamespace);
if ( [ GitlabVisibility.PUBLIC.valueOf(), GitlabVisibility.INTERNAL.valueOf() ].includes(project.visibility) ) {
return StatusCodes.OK;
......@@ -153,7 +153,7 @@ class GitlabManager {
}
// Check if the user and dojo are members (with at least reporter access) of the project
const members = await this.getRepositoryMembers(idOrNamespace);
const members = await this.getRepositoryMembers(projectIdOrNamespace);
const isUsersAtLeastReporter = {
user: false,
dojo: false
......
......@@ -6,7 +6,7 @@ import { HttpStatusCode } from 'axios';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
class ClientVersionMiddleware {
class ClientVersionCheckerMiddleware {
register(): (req: express.Request, res: express.Response, next: express.NextFunction) => void {
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if ( req.headers['client'] && req.headers['client-version'] ) {
......@@ -32,4 +32,4 @@ class ClientVersionMiddleware {
}
export default new ClientVersionMiddleware();
\ No newline at end of file
export default new ClientVersionCheckerMiddleware();
\ No newline at end of file
......@@ -33,7 +33,7 @@ class ParamsCallbackManager {
}
}
register(backend: Express) {
registerOnBackend(backend: Express) {
this.listenParam('assignmentNameOrUrl', backend, (AssignmentManager.get as GetFunction).bind(AssignmentManager), [ {
exercises: true,
staff : true
......
......@@ -31,7 +31,7 @@ class SecurityMiddleware {
isAllowed = isAllowed || (req.boundParams.assignment?.published ?? false);
break;
case SecurityCheckType.EXERCISE_SECRET:
isAllowed = isAllowed || req.headers.authorization?.replace('ExerciseSecret ', '') === req.boundParams.exercise!.secret;
isAllowed = isAllowed || (req.headers.ExerciseSecret as string | undefined) === req.boundParams.exercise!.secret;
break;
default:
break;
......
import express from 'express';
import Session from '../controllers/Session';
import { Express } from 'express-serve-static-core';
class SessionMiddleware {
register(): (req: express.Request, res: express.Response, next: express.NextFunction) => void {
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
registerOnBackend(backend: Express) {
backend.use(async (req: express.Request, res: express.Response, next: express.NextFunction) => {
req.session = new Session();
await req.session.initSession(req, res);
return next();
};
});
}
}
......
......@@ -49,8 +49,8 @@ class AssignmentRoutes implements RoutesManager {
backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment);
backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment);
backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true));
backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false));
backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.publishAssignment);
backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unpublishAssignment);
}
// Get an assignment by its name or gitlab url
......@@ -167,6 +167,14 @@ class AssignmentRoutes implements RoutesManager {
}
}
private async publishAssignment(req: express.Request, res: express.Response) {
return this.changeAssignmentPublishedStatus(true)(req, res);
}
private async unpublishAssignment(req: express.Request, res: express.Response) {
return this.changeAssignmentPublishedStatus(false)(req, res);
}
private changeAssignmentPublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> {
return async (req: express.Request, res: express.Response): Promise<void> => {
if ( publish ) {
......
......@@ -6,8 +6,16 @@ import RoutesManager from '../express/RoutesManager';
class BaseRoutes implements RoutesManager {
registerOnBackend(backend: Express) {
backend.get('/', (req: express.Request, res: express.Response) => { res.status(StatusCodes.OK).end(); });
backend.get('/health_check', (req: express.Request, res: express.Response) => { res.status(StatusCodes.OK).end(); });
backend.get('/', this.homepage);
backend.get('/health_check', this.healthCheck);
}
private async homepage(req: express.Request, res: express.Response) {
return req.session.sendResponse(res, StatusCodes.OK);
}
private async healthCheck(req: express.Request, res: express.Response) {
return req.session.sendResponse(res, StatusCodes.OK);
}
}
......
......@@ -8,13 +8,13 @@ import GitlabManager from '../managers/GitlabManager';
class GitlabRoutes implements RoutesManager {
registerOnBackend(backend: Express) {
backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess);
backend.get('/gitlab/project/:gitlabProjectIdOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess);
}
private async checkTemplateAccess(req: express.Request, res: express.Response) {
const idOrNamespace: string = req.params.idOrNamespace;
const gitlabProjectIdOrNamespace: string = req.params.gitlabProjectIdOrNamespace;
return res.status(await GitlabManager.checkTemplateAccess(idOrNamespace, req)).send();
return res.status(await GitlabManager.checkTemplateAccess(gitlabProjectIdOrNamespace, req)).send();
}
}
......
......@@ -34,7 +34,7 @@ class SessionRoutes implements RoutesManager {
registerOnBackend(backend: Express) {
backend.post('/login', ParamsValidatorMiddleware.validate(this.loginValidator), this.login);
backend.post('/refresh_tokens', ParamsValidatorMiddleware.validate(this.refreshTokensValidator), this.refreshTokens);
backend.get('/test_session', SecurityMiddleware.check(true), (req: express.Request, res: express.Response) => req.session.sendResponse(res, StatusCodes.OK));
backend.get('/test_session', SecurityMiddleware.check(true), this.testSession);
}
private async login(req: express.Request, res: express.Response) {
......@@ -71,6 +71,10 @@ class SessionRoutes implements RoutesManager {
req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, {}, 'Unknown error while refresh tokens', DojoStatusCode.REFRESH_TOKENS_FAILED);
}
}
private async testSession(req: express.Request, res: express.Response) {
req.session.sendResponse(res, StatusCodes.OK);
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment