From 14e5e99c650a4b0f42a27b640d718208c9db0c6f Mon Sep 17 00:00:00 2001
From: Joel von der Weid <joel.von-der-weid@hesge.ch>
Date: Tue, 12 Mar 2024 14:20:58 +0100
Subject: [PATCH] Add languages route and language in assignment creation

---
 ExpressAPI/assets/OpenAPI/OpenAPI.yaml        | 41 +++++++++++++++++++
 .../migration.sql                             |  2 +-
 ExpressAPI/prisma/schema.prisma               |  1 +
 ExpressAPI/src/helpers/DojoValidators.ts      | 23 ++++++++++-
 ExpressAPI/src/routes/AssignmentRoutes.ts     | 20 +++++++--
 5 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml
index 72494a9..43d8d1e 100644
--- a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml
+++ b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml
@@ -333,9 +333,21 @@ paths:
                                     type: boolean
                                     default: false
                                     description: Whether or not to use the Sonar integration to validate the assignment and exercise solutions
+                                language:
+                                    type: string
+                                    default: other
+                                    description: Main programming language of the assignment
+                                    examples:
+                                        - java
+                                        - python
+                                        - csharp
+                                        - js
+                                        - c
+                                        - other
 
                             required:
                                 - name
+                                - language
                                 - members
             responses:
                 '200':
@@ -541,6 +553,35 @@ paths:
                                                     $ref: '#/components/schemas/User'
                 default:
                     $ref: '#/components/responses/ERROR'
+    /assignments/languages:
+        get:
+            tags:
+                - Assignment
+            summary: Get all supported languages for assignments
+            description: |
+                This route can be used to get the list of all supported programming languages for which an assignment can be created.
+            security:
+                -   Clients_Token: [ ]
+            responses:
+                '200':
+                    description: OK
+                    content:
+                        application/json:
+                            schema:
+                                allOf:
+                                    -   $ref: '#/components/schemas/DojoBackendResponse'
+                                    -   type: object
+                                        properties:
+                                            data:
+                                                type: array
+                                                items:
+                                                    type: string
+                '401':
+                    $ref: '#/components/responses/UNAUTHORIZED'
+                '404':
+                    $ref: '#/components/responses/NOT_FOUND'
+                default:
+                    $ref: '#/components/responses/ERROR'
     /exercises/{exerciseIdOrUrl}/assignment:
         get:
             tags:
diff --git a/ExpressAPI/prisma/migrations/20240311145203_add_language_to_assignment/migration.sql b/ExpressAPI/prisma/migrations/20240311145203_add_language_to_assignment/migration.sql
index 94b1ce8..e0ed08c 100644
--- a/ExpressAPI/prisma/migrations/20240311145203_add_language_to_assignment/migration.sql
+++ b/ExpressAPI/prisma/migrations/20240311145203_add_language_to_assignment/migration.sql
@@ -1,2 +1,2 @@
 -- AlterTable
-ALTER TABLE `Assignment` ADD COLUMN `language` ENUM('abap', 'ada', 'asm', 'bash', 'bqn', 'c', 'caml', 'cloudformation', 'csharp', 'css', 'cuda', 'dart', 'delphi', 'docker', 'erlang', 'f', 'fsharp', 'flex', 'fortran', 'futhark', 'go', 'groovy', 'haskell', 'hepial', 'json', 'jsp', 'java', 'js', 'julia', 'kotlin', 'kubernetes', 'latex', 'lisp', 'lua', 'matlab', 'objc', 'ocaml', 'pascal', 'pearl', 'perl', 'php', 'postscript', 'powershell', 'prolog', 'promela', 'python', 'r', 'ruby', 'rust', 'scala', 'sql', 'smalltalk', 'swift', 'terraform', 'text', 'ts', 'tsql', 'typst', 'vba', 'vbnet', 'web', 'xml', 'yaml', 'other') NOT NULL DEFAULT 'other';
+ALTER TABLE `Assignment` ADD COLUMN `language` ENUM('abap', 'ada', 'asm', 'bash', 'bqn', 'c', 'caml', 'cloudformation', 'cpp', 'csharp', 'css', 'cuda', 'dart', 'delphi', 'docker', 'erlang', 'f', 'fsharp', 'flex', 'fortran', 'futhark', 'go', 'groovy', 'haskell', 'hepial', 'json', 'jsp', 'java', 'js', 'julia', 'kotlin', 'kubernetes', 'latex', 'lisp', 'lua', 'matlab', 'objc', 'ocaml', 'pascal', 'pearl', 'perl', 'php', 'postscript', 'powershell', 'prolog', 'promela', 'python', 'r', 'ruby', 'rust', 'scala', 'sql', 'smalltalk', 'swift', 'terraform', 'text', 'ts', 'tsql', 'typst', 'vba', 'vbnet', 'web', 'xml', 'yaml', 'other') NOT NULL DEFAULT 'other';
diff --git a/ExpressAPI/prisma/schema.prisma b/ExpressAPI/prisma/schema.prisma
index 95cc37e..5f2f6e8 100644
--- a/ExpressAPI/prisma/schema.prisma
+++ b/ExpressAPI/prisma/schema.prisma
@@ -111,6 +111,7 @@ enum Language {
     c
     caml
     cloudformation
+    cpp
     csharp
     css
     cuda
diff --git a/ExpressAPI/src/helpers/DojoValidators.ts b/ExpressAPI/src/helpers/DojoValidators.ts
index 652c600..d3a72b5 100644
--- a/ExpressAPI/src/helpers/DojoValidators.ts
+++ b/ExpressAPI/src/helpers/DojoValidators.ts
@@ -7,9 +7,11 @@ import ExerciseResultsFile                                             from '../
 import ParamsCallbackManager                                           from '../middlewares/ParamsCallbackManager.js';
 import ExerciseManager                                                 from '../managers/ExerciseManager.js';
 import Toolbox                                                         from '../shared/helpers/Toolbox.js';
-import { CustomValidator, FieldMessageFactory, Meta, ValidationChain } from 'express-validator/lib/index.js';
+import { CustomValidator, FieldMessageFactory, Meta, ValidationChain, ErrorMessage } from 'express-validator/lib/index.js';
 import { ErrorMessage }                                                from 'express-validator/lib/base.js';
-import { BailOptions }                                                 from 'express-validator/lib/chain/index.js';
+import { StatusCodes }                                              from 'http-status-codes';
+import { BailOptions, ValidationChain }                             from 'express-validator/src/chain';
+import { Language }                                                 from '@prisma/client';
 
 
 declare type DojoMeta = Meta & {
@@ -136,6 +138,23 @@ class DojoValidators {
                                                                               });
                                                                           }
                                                                       });
+
+    readonly supportedLanguageValidator = this.toValidatorSchemaOptions({
+                                                                            bail        : true,
+                                                                            errorMessage: 'Unsupported language',
+                                                                            options     : (_value, {
+                                                                                req,
+                                                                                path
+                                                                            }) => {
+                                                                                return new Promise((resolve, reject) => {
+                                                                                    const language = this.getParamValue(req, path) as string;
+                                                                                    if ( language ) {
+                                                                                        (Object.values(Language).includes(language as keyof typeof Language) ? resolve(true) : reject());
+                                                                                    }
+                                                                                    reject();
+                                                                                });
+                                                                            }
+                                                                        });
 }
 
 
diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts
index 84de91f..93faa05 100644
--- a/ExpressAPI/src/routes/AssignmentRoutes.ts
+++ b/ExpressAPI/src/routes/AssignmentRoutes.ts
@@ -23,6 +23,8 @@ import DojoModelsHelper            from '../helpers/DojoModelsHelper.js';
 import * as Gitlab                 from '@gitbeaker/rest';
 import { GitbeakerRequestError }   from '@gitbeaker/requester-utils';
 import SharedConfig                from '../shared/config/SharedConfig.js';
+import { AxiosError, HttpStatusCode } from 'axios';
+import { Language, Prisma }           from '@prisma/client';
 
 
 class AssignmentRoutes implements RoutesManager {
@@ -46,6 +48,11 @@ class AssignmentRoutes implements RoutesManager {
             notEmpty : true,
             isBoolean: true,
         },
+        language : {
+            trim    : true,
+            notEmpty: true,
+            custom  : DojoValidators.supportedLanguageValidator
+        }
     };
 
     private readonly assignmentAddCorrigeValidator: ExpressValidator.Schema = {
@@ -79,6 +86,8 @@ class AssignmentRoutes implements RoutesManager {
         backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment.bind(this) as RequestHandler);
         backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment.bind(this) as RequestHandler);
 
+        backend.get('/assignments/languages', this.getLanguages.bind(this) as RequestHandler);
+
         backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true).bind(this) as RequestHandler);
         backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false).bind(this) as RequestHandler);
 
@@ -133,7 +142,7 @@ class AssignmentRoutes implements RoutesManager {
 
     private async createAssignment(req: express.Request, res: express.Response) {
         const params: {
-            name: string, members: Array<Gitlab.UserSchema>, template: string, useSonar: string
+            name: string, members: Array<Gitlab.UserSchema>, template: string, useSonar: string, language: string
         } = req.body;
         const useSonar = params.useSonar === 'true';
 
@@ -190,6 +199,7 @@ class AssignmentRoutes implements RoutesManager {
                                                                                                        gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
                                                                                                        gitlabLastInfoDate: new Date(),
                                                                                                        useSonar          : useSonar,
+                                                                                                       language          : Language[params.language as keyof typeof Language],
                                                                                                        staff             : {
                                                                                                            connectOrCreate: [ ...params.members.map(gitlabUser => {
                                                                                                                return {
@@ -205,8 +215,8 @@ class AssignmentRoutes implements RoutesManager {
                                                                                                        }
                                                                                                    }
                                                                                                }), 'Database error') as Assignment;
-
-            req.session.sendResponse(res, StatusCodes.OK, assignment);
+                                                                        
+            req.session.sendResponse(res, StatusCodes.OK, assignment);                                                                        
         } catch ( error ) {
             /* Empty */
         }
@@ -314,6 +324,10 @@ class AssignmentRoutes implements RoutesManager {
 
         return req.session.sendResponse(res, StatusCodes.OK);
     }
+
+    private async getLanguages(req: express.Request, res: express.Response) {
+        req.session.sendResponse(res, StatusCodes.OK, Object.values(Language));
+    }
 }
 
 
-- 
GitLab