diff --git a/api/src/engine/connectors/csv/main.connector.ts b/api/src/engine/connectors/csv/main.connector.ts index eedb7ae6b31074fbe1e5c66970aa008f62859004..05864adb55c7141de3ec49bd6854b5fdcdb793ee 100644 --- a/api/src/engine/connectors/csv/main.connector.ts +++ b/api/src/engine/connectors/csv/main.connector.ts @@ -19,6 +19,10 @@ export default class CSVService implements IEngineService { private readonly httpService: HttpService, ) {} + logout() { + throw new Error('Method not implemented.'); + } + getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { throw new Error('Method not implemented.'); } diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts index 829147fe9b985703be4868f6e12b8889884d8abe..4df8aa14bf1fda0bdec3a4d21c98cc79277cdd83 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/main.connector.ts @@ -11,6 +11,10 @@ import { ExperimentEditInput } from 'src/engine/models/experiment/input/experime import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; export default class DataShieldService implements IEngineService { + logout(): void { + throw new Error('Method not implemented.'); + } + getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { throw new Error('Method not implemented.'); } diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts index ab840843a6c698c24142eb228343d819584ee8a6..1e43bfbe9973f0011e2e883ae7e417c2645ce310 100644 --- a/api/src/engine/connectors/exareme/converters.ts +++ b/api/src/engine/connectors/exareme/converters.ts @@ -59,28 +59,33 @@ export const dataToVariable = (data: VariableEntity): Variable => { const algoParamInputToData = (param: AlgorithmParameter) => { return { name: param.name, + label: param.name, value: param.value.join(','), }; }; export const experimentInputToData = (data: ExperimentCreateInput) => { - return { + const params = { algorithm: { parameters: [ { name: 'dataset', + label: 'dataset', value: data.datasets.join(','), }, { name: 'filter', + label: 'filter', value: data.filter, }, { name: 'pathology', + label: 'pathology', value: data.domain, }, { name: 'y', + label: 'y', value: data.variables.join(','), }, ].concat(data.algorithm.parameters.map(algoParamInputToData)), @@ -89,6 +94,8 @@ export const experimentInputToData = (data: ExperimentCreateInput) => { }, name: data.name, }; + + return params; }; export const descriptiveDataToTableResult = ( diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts index 11f2ed6c18118d813a830ad2cb34c808203be43b..dc9b74ea9107c40f69719813d4c62ba1d41c8ad1 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/main.connector.ts @@ -1,7 +1,15 @@ import { HttpService } from '@nestjs/axios'; -import { BadRequestException, HttpException, HttpStatus } from '@nestjs/common'; +import { + BadRequestException, + HttpException, + HttpStatus, + Inject, + Injectable, +} from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; import { Request } from 'express'; import { firstValueFrom, map, Observable } from 'rxjs'; +import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; import { IEngineOptions, IEngineService } from 'src/engine/engine.interfaces'; import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; @@ -27,12 +35,20 @@ import { ExperimentsData } from './interfaces/experiment/experiments.interface'; import { Hierarchy } from './interfaces/hierarchy.interface'; import { Pathology } from './interfaces/pathology.interface'; +@Injectable() export default class ExaremeService implements IEngineService { constructor( - private readonly options: IEngineOptions, + @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions, private readonly httpService: HttpService, + @Inject(REQUEST) private readonly req: Request, ) {} + async logout() { + const path = `${this.options.baseurl}logout`; + + await firstValueFrom(this.httpService.get(path)); + } + async createExperiment( data: ExperimentCreateInput, isTransient = false, @@ -146,11 +162,11 @@ export default class ExaremeService implements IEngineService { .pipe(map((response) => response.data)); } - editActiveUser(request: Request): Observable<string> { + editActiveUser(): Observable<string> { const path = this.options.baseurl + 'activeUser/agreeNDA'; return this.httpService - .post<string>(path, request.body) + .post<string>(path, this.req.body) .pipe(map((response) => response.data)); } @@ -168,43 +184,43 @@ export default class ExaremeService implements IEngineService { return this.httpService.delete(path).pipe(map((response) => response.data)); } - editExperimentREST(uuid: string, request: Request): Observable<string> { + editExperimentREST(uuid: string): Observable<string> { const path = this.options.baseurl + `experiments/${uuid}`; return this.httpService - .patch(path, request.body) + .patch(path, this.req.body) .pipe(map((response) => response.data)); } - startExperimentTransient(request: Request): Observable<string> { + startExperimentTransient(): Observable<string> { const path = this.options.baseurl + 'experiments/transient'; return this.httpService - .post(path, request.body) + .post(path, this.req.body) .pipe(map((response) => response.data)); } - startExperiment(request: Request): Observable<string> { + startExperiment(): Observable<string> { const path = this.options.baseurl + 'experiments'; return this.httpService - .post(path, request.body) + .post(path, this.req.body) .pipe(map((response) => response.data)); } - getExperiments(request: Request): Observable<string> { + getExperiments(): Observable<string> { const path = this.options.baseurl + 'experiments'; return this.httpService - .get<string>(path, { params: request.query }) + .get<string>(path, { params: this.req.query }) .pipe(map((response) => response.data)); } - getAlgorithmsREST(request: Request): Observable<string> { + getAlgorithmsREST(): Observable<string> { const path = this.options.baseurl + 'algorithms'; return this.httpService - .get<string>(path, { params: request.query }) + .get<string>(path, { params: this.req.query }) .pipe(map((response) => response.data)); } diff --git a/api/src/engine/connectors/local/main.connector.ts b/api/src/engine/connectors/local/main.connector.ts index 2ff1a44faa75d436313c152256cfdcf46299121d..cb472d927f571c9553c0115624a9981f2be653c6 100644 --- a/api/src/engine/connectors/local/main.connector.ts +++ b/api/src/engine/connectors/local/main.connector.ts @@ -11,6 +11,10 @@ import { ExperimentEditInput } from 'src/engine/models/experiment/input/experime import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; export default class LocalService implements IEngineService { + logout(): void { + throw new Error('Method not implemented.'); + } + getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { throw new Error('Method not implemented.'); } diff --git a/api/src/engine/engine.controller.ts b/api/src/engine/engine.controller.ts index fe65f352e83e325879d414855a582ad5fec8619a..d7f34d493e03ae0f08401baf9aab1b888851b9f9 100644 --- a/api/src/engine/engine.controller.ts +++ b/api/src/engine/engine.controller.ts @@ -6,13 +6,14 @@ import { Param, Patch, Post, - Req, + UseInterceptors, } from '@nestjs/common'; -import { Request } from 'express'; import { Observable } from 'rxjs'; import { ENGINE_SERVICE } from './engine.constants'; import { IEngineService } from './engine.interfaces'; +import { HeadersInterceptor } from './interceptors/headers.interceptor'; +@UseInterceptors(HeadersInterceptor) @Controller() export class EngineController { constructor( @@ -20,13 +21,13 @@ export class EngineController { ) {} @Get('/algorithms') - getAlgorithms(@Req() request: Request): Observable<string> | string { - return this.engineService.getAlgorithmsREST(request); + getAlgorithms(): Observable<string> | string { + return this.engineService.getAlgorithmsREST(); } @Get('/experiments') - getExperiments(@Req() request: Request): Observable<string> | string { - return this.engineService.getExperiments(request); + getExperiments(): Observable<string> | string { + return this.engineService.getExperiments(); } @Get('/experiments/:uuid') @@ -35,40 +36,37 @@ export class EngineController { } @Delete('/experiments/:uuid') - deleteExperiment( - @Param('uuid') uuid: string, - @Req() request: Request, - ): Observable<string> | string { - return this.engineService.deleteExperiment(uuid, request); + deleteExperiment(@Param('uuid') uuid: string): Observable<string> | string { + return this.engineService.deleteExperiment(uuid); } @Patch('/experiments/:uuid') - editExperiment( - @Param('uuid') uuid: string, - @Req() request: Request, - ): Observable<string> | string { - return this.engineService.editExperimentREST(uuid, request); + editExperiment(@Param('uuid') uuid: string): Observable<string> | string { + return this.engineService.editExperimentREST(uuid); } @Post('experiments/transient') - startExperimentTransient( - @Req() request: Request, - ): Observable<string> | string { - return this.engineService.startExperimentTransient(request); + startExperimentTransient(): Observable<string> | string { + return this.engineService.startExperimentTransient(); } @Post('experiments') - startExperiment(@Req() request: Request): Observable<string> | string { - return this.engineService.startExperiment(request); + startExperiment(): Observable<string> | string { + return this.engineService.startExperiment(); } @Get('activeUser') - getActiveUser(@Req() request: Request): Observable<string> | string { - return this.engineService.getActiveUser(request); + getActiveUser(): Observable<string> | string { + return this.engineService.getActiveUser(); } @Post('activeUser/agreeNDA') - agreeNDA(@Req() request: Request): Observable<string> | string { - return this.engineService.editActiveUser(request); + agreeNDA(): Observable<string> | string { + return this.engineService.editActiveUser(); + } + + @Get('logout') + logout(): void { + this.engineService.logout(); } } diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts index e3f68aa1b75967c8d9cca9b8c52c4a120f0d7a43..29e57f161a8dc2a69c747d415fa529b4c986002a 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/engine.interfaces.ts @@ -1,4 +1,3 @@ -import { Request } from 'express'; import { Observable } from 'rxjs'; import { Domain } from './models/domain.model'; import { Algorithm } from './models/experiment/algorithm.model'; @@ -43,24 +42,23 @@ export interface IEngineService { getAlgorithms(): Promise<Algorithm[]> | Algorithm[]; // Standard REST API call - getAlgorithmsREST(request: Request): Observable<string> | string; + getAlgorithmsREST(): Observable<string> | string; - getExperiments(request: Request): Observable<string> | string; + getExperiments(): Observable<string> | string; getExperimentREST(uuid: string): Observable<string> | string; - deleteExperiment(uuid: string, request: Request): Observable<string> | string; + deleteExperiment(uuid: string): Observable<string> | string; - editExperimentREST( - uuid: string, - request: Request, - ): Observable<string> | string; + editExperimentREST(uuid: string): Observable<string> | string; + + startExperimentTransient(): Observable<string> | string; - startExperimentTransient(request: Request): Observable<string> | string; + startExperiment(): Observable<string> | string; - startExperiment(request: Request): Observable<string> | string; + getActiveUser(): Observable<string> | string; - getActiveUser(request: Request): Observable<string> | string; + editActiveUser(): Observable<string> | string; - editActiveUser(request: Request): Observable<string> | string; + logout(): void; } diff --git a/api/src/engine/engine.module.ts b/api/src/engine/engine.module.ts index 182cecf3e7d8239e734071c07a797dcaefb86961..48442588a8d1a2c5120e99fa15d60e7da3c29739 100644 --- a/api/src/engine/engine.module.ts +++ b/api/src/engine/engine.module.ts @@ -1,5 +1,6 @@ import { HttpModule, HttpService } from '@nestjs/axios'; import { DynamicModule, Global, Logger, Module } from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; import { GraphQLModule } from '@nestjs/graphql'; import { join } from 'path'; import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; @@ -20,10 +21,10 @@ export class EngineModule { const engineProvider = { provide: ENGINE_SERVICE, - useFactory: async (httpService: HttpService) => { - return await this.createEngineConnection(options, httpService); + useFactory: async (httpService: HttpService, req: Request) => { + return await this.createEngineConnection(options, httpService, req); }, - inject: [HttpService], + inject: [HttpService, REQUEST], }; return { @@ -43,14 +44,13 @@ export class EngineModule { private static async createEngineConnection( options: IEngineOptions, httpService: HttpService, + req: Request, ): Promise<IEngineService> { try { const service = await import( `./connectors/${options.type}/main.connector` ); - const engine = new service.default(options, httpService); - - this.logger.log(`The connector '${options.type}' has been loaded`); + const engine = new service.default(options, httpService, req); return engine; } catch { diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index 0cfac3a5b1fdc02c82b74cd31928a554c45bc550..6e23ba38ca26d1129e51abb7cbba8d311cad67b9 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -1,7 +1,8 @@ -import { Inject } from '@nestjs/common'; +import { Inject, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { ENGINE_SERVICE } from './engine.constants'; import { IEngineService } from './engine.interfaces'; +import { HeadersInterceptor } from './interceptors/headers.interceptor'; import { Domain } from './models/domain.model'; import { Algorithm } from './models/experiment/algorithm.model'; import { @@ -12,6 +13,7 @@ import { ExperimentCreateInput } from './models/experiment/input/experiment-crea import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input'; import { ListExperiments } from './models/experiment/list-experiments.model'; +@UseInterceptors(HeadersInterceptor) @Resolver() export class EngineResolver { constructor( diff --git a/api/src/engine/interceptors/headers.interceptor.ts b/api/src/engine/interceptors/headers.interceptor.ts new file mode 100644 index 0000000000000000000000000000000000000000..9b28e4953456de5d9bc66a390c9cf452cf45306f --- /dev/null +++ b/api/src/engine/interceptors/headers.interceptor.ts @@ -0,0 +1,45 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable, NestInterceptor, CallHandler } from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; +import { IncomingMessage } from 'http'; +import { Observable, tap } from 'rxjs'; + +@Injectable() +export class HeadersInterceptor implements NestInterceptor { + constructor(private httpService: HttpService) {} + + intercept(context: GqlExecutionContext, next: CallHandler): Observable<any> { + // cleaner : add only the auth header (should find the name) + + const keys = ['cookie', 'x-xsrf-token']; + let headers = {}; + + switch (context.getType()) { + case 'http': { + const ctx = context.switchToHttp(); + const request = ctx.getRequest<Request>(); + headers = request.headers; + break; + } + case 'graphql': { + const ctx = GqlExecutionContext.create(context); + const req: IncomingMessage = ctx.getContext().req; + headers = req.headers; + break; + } + } + + Object.keys(headers) + .filter((key) => keys.includes(key)) + .map((key) => key.toLowerCase()) + .forEach((key) => { + this.httpService.axiosRef.defaults.headers.common[key] = headers[key]; + }); + + return next.handle().pipe( + tap(() => { + this.httpService.axiosRef.defaults.headers.common = {}; //cleaning request + }), + ); + } +}