Pārlūkot izejas kodu

poc new entityManager (ongoing)

Olivier Massot 3 gadi atpakaļ
vecāks
revīzija
39ba37aaf6

+ 3 - 0
.env.local

@@ -2,6 +2,9 @@
 ENV=dev
 DEBUG=1
 
+HOST=0
+PORT=3000
+
 ## API Base Url
 NUXT_BASE_URL=http://nginx_new
 NUXT_PUBLIC_BASE_URL=https://local.ap2i.opentalent.fr

+ 29 - 19
composables/data/useAp2iFetch.ts → composables/data/useAp2iRequestService.ts

@@ -2,13 +2,16 @@ import {useProfileAccessStore} from "~/store/profile/access";
 import {FetchContext, FetchOptions} from "ohmyfetch";
 import Page from "~/services/store/page";
 import {TYPE_ALERT} from "~/types/enums";
+import {useRuntimeConfig} from "#app";
+import OhMyFetchConnector from "~/services/data/connector/ohMyFetchConnector";
+import ApiRequestService from "~/services/data/apiRequestService";
 
 /**
- * Utilise la fonction `create` de ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
+ * Retourne une instance de ApiRequestService configurée pour interroger l'api Ap2i
  *
  * @see https://github.com/unjs/ohmyfetch/blob/main/README.md#%EF%B8%8F-create-fetch-with-default-options
  */
-export const useAp2iFetch = () => {
+export const useAp2iRequestService = () => {
     const runtimeConfig = useRuntimeConfig()
 
     /**
@@ -17,22 +20,24 @@ export const useAp2iFetch = () => {
      * @param request
      * @param options
      */
-    const onRequest = async function ({ request, options }: FetchContext) {
-        // @ts-ignore
-        if(options ?. params.noXaccessId) {
-            return
-        }
-
-        request = request as Request
-
-        const profileAccessStore = useProfileAccessStore()
-        request.headers.set('x-accessid', String(profileAccessStore.id))
-        request.headers.set('Authorization', 'BEARER ' + profileAccessStore.bearer)
-
-        if (profileAccessStore.switchId) {
-            request.headers.set('x-switch-user', String(profileAccessStore.switchId))
-        }
-    }
+    // const onRequest = async function ({ request, options }: FetchContext) {
+    //     // @ts-ignore
+    //     if(options && options.noXaccessId) {
+    //         return
+    //     }
+    //
+    //     request = request as Request
+    //
+    //     console.log(request, options)
+    //
+    //     const profileAccessStore = useProfileAccessStore()
+    //     request.headers.set('x-accessid', String(profileAccessStore.id))
+    //     request.headers.set('Authorization', 'BEARER ' + profileAccessStore.bearer)
+    //
+    //     if (profileAccessStore.switchId) {
+    //         request.headers.set('x-switch-user', String(profileAccessStore.switchId))
+    //     }
+    // }
 
     /**
      * Gère les erreurs retournées par l'api
@@ -59,5 +64,10 @@ export const useAp2iFetch = () => {
         onRequest,
         onResponseError
     }
-    return $fetch.create(config)
+
+    // Utilise la fonction `create` de ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
+    const fetcher = $fetch.create(config)
+
+    const connector = new OhMyFetchConnector(fetcher)
+    return new ApiRequestService(connector)
 }

+ 0 - 7
composables/data/useDataDeleter.ts

@@ -1,7 +0,0 @@
-import DataDeleter from "~/services/data/dataDeleter";
-
-export const useDataDeleter = () => {
-    // const dataDeleter = new DataDeleter()
-    // dataDeleter.initCtx(useNuxtApp())
-    return new DataDeleter()
-}

+ 0 - 7
composables/data/useDataPersister.ts

@@ -1,7 +0,0 @@
-import DataPersister from "~/services/data/dataPersister";
-
-export const useDataPersister = () => {
-    // const dataPersister = new DataPersister()
-    // dataPersister.initCtx(useNuxtApp())
-    return new DataPersister()
-}

+ 0 - 7
composables/data/useDataProvider.ts

@@ -1,7 +0,0 @@
-import DataProvider from "~/services/data/dataProvider";
-
-export const useDataProvider = () => {
-    // const dataProvider = new DataProvider()
-    // dataProvider.initCtx(useNuxtApp())
-    return new DataProvider()
-}

+ 7 - 0
composables/data/useEntityManager.ts

@@ -0,0 +1,7 @@
+import EntityManager from "~/services/data/entityManager";
+import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
+
+export const useEntityManager = () => {
+    const apiRequestService = useAp2iRequestService()
+    return new EntityManager(apiRequestService)
+}

+ 1 - 1
models/Access/Access.ts

@@ -1,7 +1,7 @@
-import {Attr, Num, Model, Uid, HasOne} from 'pinia-orm'
 import { Historical } from '~/types/interfaces'
 import {Person} from "~/models/Person/Person";
 import ApiModel from "~/models/ApiModel";
+import {HasOne, Num} from "pinia-orm/dist/decorators";
 
 export class Access extends ApiModel {
   static entity = 'accesses'

+ 1 - 1
models/ApiModel.ts

@@ -1,5 +1,5 @@
 import { Model } from "pinia-orm";
-import { Uid } from "pinia-orm/dist/nanoid";
+import {Uid} from "pinia-orm/dist/decorators";
 
 export class ApiModel extends Model {
     @Uid()

+ 40 - 45
models/Organization/Organization.ts

@@ -1,125 +1,120 @@
-import {Attr, Str, Bool, Num, Model, Uid} from 'pinia-orm'
+import ApiModel from "~/models/ApiModel";
+import {Bool, Str, Attr, Num} from "pinia-orm/dist/decorators";
 
-export class Organization extends Model {
+export class Organization extends ApiModel {
   static entity = 'organizations'
 
-  @Uid()
-  id!: number | string | null
-
-  @Attr({})
-  originalState!: object | null
-
-  @Str(null, { nullable: true })
+  @Str(null)
   name!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   acronym!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   siretNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   apeNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   waldecNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   identifier!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   ffecApproval!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   description!: string|null
 
   @Attr([])
   typeOfPractices!: []
 
-  @Str(null, { nullable: true })
+  @Str(null)
   otherPractice!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   legalStatus!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   principalType!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   youngApproval!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   trainingApproval!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   otherApproval!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   collectiveAgreement!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   opca!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   icomNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   urssafNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   twitter!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   youtube!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   facebook!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   instagram!: string|null
 
-  @Bool(true, { nullable: false })
+  @Bool(true, { notNullable: true })
   portailVisibility!: boolean
 
-  @Str(null, { nullable: true })
+  @Str(null)
   image!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   creationDate!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   prefectureName!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   prefectureNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   declarationDate!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   tvaNumber!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   schoolCategory!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   typeEstablishment!: string|null
 
-  @Str(null, { nullable: true })
+  @Str(null)
   typeEstablishmentDetail!: string|null
 
-  @Bool(false, { nullable: false })
+  @Bool(false, { notNullable: true })
   isPerformanceContractor!: boolean
 
-  @Num(0, { nullable: true })
+  @Num(0)
   budget!: number
 
-  @Bool(false, { nullable: false })
+  @Bool(false, { notNullable: true })
   isPedagogicIsPrincipalActivity!: boolean
 
-  @Num(0, { nullable: true })
+  @Num(0)
   pedagogicBudget!: number
 
-  @Str(null, { nullable: true })
+  @Str(null)
   logo!: string|null
 }

+ 5 - 3
nuxt.config.ts

@@ -1,9 +1,8 @@
-import { defineNuxtConfig } from 'nuxt'
 import fs from 'fs';
 
 // https://v3.nuxtjs.org/api/configuration/nuxt.config
 // @ts-ignore
-export default defineNuxtConfig({
+export default {
     vuetify: {
         customVariables: ['~/assets/css/variables.scss'],
         treeShake: true,
@@ -99,6 +98,9 @@ export default defineNuxtConfig({
     build: {
         transpile: ['vuetify'],
     },
+    server: {
+        host: "0.0.0.0"
+    },
     vite: {
         define: {
             'process.env.DEBUG': false,
@@ -115,4 +117,4 @@ export default defineNuxtConfig({
             }
         }
     }
-})
+}

+ 1 - 1
package.json

@@ -3,7 +3,7 @@
   "version": "2.3.0",
   "type": "module",
   "scripts": {
-    "dev": "nuxt dev --hostname '0.0.0.0' --port 3003",
+    "dev": "nuxt dev",
     "dev:local": "yarn dev --dotenv .env.local",
     "dev:preprod": "yarn dev --dotenv .env.preprod",
     "dev:prod": "yarn dev --dotenv .env.prod",

+ 7 - 1
pages/index.vue

@@ -1,10 +1,16 @@
 <template>
   <main>
-    Test
+    Test : {{ organization }}
   </main>
 </template>
 
 <script setup lang="ts">
+  import {useEntityManager} from "~/composables/data/useEntityManager";
+  import {Organization} from "~/models/Organization/Organization";
+
+  const em = useEntityManager()
+
+  const organization = await em.fetch(Organization, 498)
 
 </script>
 

+ 11 - 18
plugins/init.server.ts

@@ -1,25 +1,18 @@
-import {defineNuxtPlugin} from "nuxt/app";
+import {defineNuxtPlugin, useCookie} from "nuxt/app";
 import {useProfileAccessStore} from "~/store/profile/access";
-import {QUERY_TYPE} from "~/types/enums";
-import {useDataProvider} from "~/composables/data/useDataProvider";
+import {useEntityManager} from "~/composables/data/useEntityManager";
 
 export default defineNuxtPlugin(async ({ssrContext}) => {
     const profileAccessStore = useProfileAccessStore()
 
-    const arraysCookies = ssrContext?.req?.headers.cookie?.split('; ').map((a:string) => a.split('='))
-    if(arraysCookies) {
-        const cookies = Object.fromEntries(arraysCookies)
+    const bearer = useCookie('BEARER')
+    const accessId = useCookie('AccessId')
 
-        profileAccessStore.$patch({
-            bearer: cookies['BEARER'],
-            id: cookies['AccessId']
-        })
+    profileAccessStore.$patch({
+        bearer: bearer.value,
+        id: parseInt(accessId.value)
+    })
 
-        const dataProvider = useDataProvider()
-        const myProfile = await dataProvider.invoke({
-            type: QUERY_TYPE.DEFAULT,
-            url: '/api/my_profile'
-        })
-        profileAccessStore.setProfile(myProfile.data)
-    }
-})
+    const em = useEntityManager()
+    await em.refreshProfile()
+})

+ 1 - 1
services/data/apiRequestService.ts

@@ -1,4 +1,4 @@
-import {AssociativeArray, Connector, HTTP_METHOD} from "./data";
+import {AssociativeArray, Connector, HTTP_METHOD} from "./data.d";
 
 /**
  * A basic api request service

+ 0 - 6
services/data/data.d.ts

@@ -5,11 +5,6 @@ export const enum HTTP_METHOD {
     DELETE = 'DELETE'
 }
 
-export const enum FORMAT {
-    HYDRA,
-    YAML
-}
-
 interface AssociativeArray {
     [key: string]: any;
 }
@@ -23,4 +18,3 @@ interface Connector {
         query: null | AssociativeArray
     )
 }
-

+ 20 - 5
services/data/entityManager.ts

@@ -5,6 +5,7 @@ import UrlBuilder from "~/services/utils/urlBuilder";
 import ModelNormalizer from "./serializer/normalizer/modelNormalizer";
 import HydraDenormalizer from "./serializer/denormalizer/hydraDenormalizer";
 import ApiModel from "~/models/ApiModel";
+import {useProfileAccessStore} from "~/store/profile/access";
 
 /**
  * Entity manager: make operations on the models defined with the Pinia-Orm library
@@ -47,7 +48,7 @@ class EntityManager  {
         }
 
         // Else, get the object from the API
-        const url = UrlBuilder.concat('api', model.entity, String(id))
+        const url = UrlBuilder.join('api', model.entity, String(id))
 
         const response = await this.apiRequestService.get(url)
 
@@ -73,11 +74,11 @@ class EntityManager  {
 
     public async persist(model: typeof ApiModel, entity: ApiModel) {
         const data = ModelNormalizer.normalize(entity)
-        let url = UrlBuilder.concat('api', model.entity)
+        let url = UrlBuilder.join('api', model.entity)
         let response = null
 
         if (entity.persisted) {
-            url = UrlBuilder.concat(url, String(entity.id))
+            url = UrlBuilder.join(url, String(entity.id))
             response = await this.apiRequestService.put(url, data)
         } else {
             response = await this.apiRequestService.post(url, data)
@@ -90,6 +91,10 @@ class EntityManager  {
         const repository = this.getRepository(model)
         repository.save(fetchedEntity)
 
+        if (['accesses', 'organizations', 'parameters', 'subdomains'].includes(model.entity)) {
+            await this.refreshProfile()
+        }
+
         return fetchedEntity
     }
 
@@ -103,7 +108,7 @@ class EntityManager  {
 
         // If object has been persisted to the datasource, send a delete request
         if (entity.persisted) {
-            const url = UrlBuilder.concat('api', model.entity, String(id))
+            const url = UrlBuilder.join('api', model.entity, String(id))
             await this.apiRequestService.delete(url)
         }
 
@@ -132,9 +137,19 @@ class EntityManager  {
         entity = entity.initialState as ApiModel
         entity.initialState = structuredClone(entity)
 
-        const repository = this.getRepository(this.getEntityModel(entity))
+        const repository = this.getRepository(EntityManager.getEntityModel(entity))
         repository.save(entity)
     }
+
+    public async refreshProfile() {
+        const response = await this.apiRequestService.get('api/my_profile')
+
+        // deserialize the response
+        const profile = await HydraDenormalizer.denormalize(response)
+
+        const profileAccessStore = useProfileAccessStore()
+        profileAccessStore.setProfile(profile.data)
+    }
 }
 
 export default EntityManager

+ 14 - 1
services/data/enumManager.ts

@@ -1,4 +1,7 @@
 import ApiRequestService from "./apiRequestService";
+import UrlBuilder from "~/services/utils/urlBuilder";
+import HydraDenormalizer from "~/services/data/serializer/denormalizer/hydraDenormalizer";
+import {AssociativeArray} from "~/services/data/data";
 
 class EnumManager {
     private apiRequestService: ApiRequestService;
@@ -7,8 +10,18 @@ class EnumManager {
         this.apiRequestService = apiRequestService
     }
 
-    public fetch() {
+    public async fetch(enumName: string): Promise<Array<AssociativeArray>> {
+        const url = UrlBuilder.join('api', 'enum', enumName)
 
+        const response = await this.apiRequestService.get(url)
+
+        const data = await HydraDenormalizer.denormalize(response)
+
+        return data.items.map(
+            (v: string, k: string | number) => {
+                return {value: k, label: v}
+            }
+        )
     }
 }
 

+ 3 - 4
services/rights/abilitiesUtils.ts

@@ -3,11 +3,10 @@ import {$organizationProfile} from '~/services/profile/organizationProfile'
 import {Ability} from '@casl/ability'
 import {$roleUtils} from '~/services/rights/roleUtils'
 import {AbilitiesType, AnyJson} from '~/types/interfaces'
-import Serializer from '~/services/serializer/serializer'
 import {DENORMALIZER_TYPE} from '~/types/enums'
-import {Pinia} from "pinia";
 import {useProfileAccessStore} from "~/store/profile/access";
 import {useProfileOrganizationStore} from "~/store/profile/organization";
+import YamlDenormalizer from "~/services/data/serializer/denormalizer/yamlDenormalizer";
 
 /**
  * Classe permettant de mener des opérations sur les habilités
@@ -119,7 +118,7 @@ class AbilitiesUtils {
     getAbilitiesByConfig(configPath: string): Array<AbilitiesType> {
         let abilitiesByConfig: Array<AbilitiesType> = []
         try {
-            const doc = Serializer.denormalize({path: configPath}, DENORMALIZER_TYPE.YAML)
+            const doc = YamlDenormalizer.denormalize({path: configPath})
             const abilitiesAvailable = doc.abilities
             const abilitiesFiltered = this.abilitiesAvailableFilter(abilitiesAvailable)
             abilitiesByConfig = this.transformAbilitiesConfigToAbility(abilitiesFiltered)
@@ -187,4 +186,4 @@ class AbilitiesUtils {
     }
 }
 
-export const $abilitiesUtils = (ability: Ability) => new AbilitiesUtils(ability)
+export const $abilitiesUtils = (ability: Ability) => new AbilitiesUtils(ability)

+ 40 - 0
services/utils/i18nUtils.ts

@@ -0,0 +1,40 @@
+import {VueI18n} from "vue-i18n";
+import {EnumChoice, EnumChoices} from "~/types/interfaces";
+
+
+class I18nUtils {
+    private i18n!: VueI18n
+
+    public constructor(i18n: VueI18n) {
+        this.i18n = i18n
+    }
+
+    /**
+     * Return the given enum with its labels translated
+     *
+     * If sort is true, the enum is also sorted by its newly translated labels
+     *
+     * @param enum_
+     * @param sort
+     */
+    public translateEnum(enum_: EnumChoices, sort: boolean = true): EnumChoices {
+        enum_ = enum_.map(
+            (item: EnumChoice) => {
+                return {value: item.value, label: this.i18n.t(item.value) as string}
+            }
+        )
+
+        if (sort) {
+            enum_ = enum_.sort((a, b) => {
+                if (a.label > b.label) {
+                    return 1
+                }
+                if (a.label < b.label) {
+                    return -1
+                }
+                return 0
+            })
+        }
+        return enum_
+    }
+}

+ 17 - 123
services/utils/urlBuilder.ts

@@ -1,9 +1,6 @@
-import {Model} from '@vuex-orm/core'
 import {FileArgs, ImageArgs, ListArgs, UrlArgs} from '~/types/interfaces'
 import {QUERY_TYPE} from '~/types/enums'
-import {repositoryHelper} from '~/services/store/repository'
 import TypesTesting from "~/services/utils/typesTesting";
-import UrlOptionsBuilder from "~/services/connection/urlOptionsBuilder";
 
 /**
  * Classe permettant de construire une URL pour l'interrogation d'une API externe
@@ -12,105 +9,29 @@ class UrlBuilder {
   static ROOT = '/api/'
 
   /**
-   * Main méthode qui appellera les méthode privées correspondantes (getEnumUrl, getModelUrl, getImageUrl)
-   * @param {UrlArgs} args
-   * @return {string}
-   */
-  public static build (args: UrlArgs): string {
-    let url: string = ''
-    switch (args.type) {
-      case QUERY_TYPE.DEFAULT:
-        url = UrlBuilder.getDefaultUrl( args.url, args.baseUrl)
-        break;
-
-      case QUERY_TYPE.ENUM:
-        url = UrlBuilder.getEnumUrl(args.enumType)
-        break;
-
-      case QUERY_TYPE.MODEL:
-        url = UrlBuilder.getModelUrl(args.model, args.rootModel, args.rootId)
-        break;
-
-      case QUERY_TYPE.IMAGE:
-        if (!TypesTesting.isDataProviderArgs(args)) {
-          throw new Error('*args* is not a dataProviderArgs')
-        }
-        if (!args.imgArgs) {
-          throw new Error('*args* has no imgArgs')
-        }
-        url = UrlBuilder.getImageUrl(args.imgArgs, args.baseUrl)
-        break;
-
-      case QUERY_TYPE.FILE:
-        if (!TypesTesting.isDataProviderArgs(args)) {
-          throw new Error('*args* is not a dataProviderArgs')
-        }
-        if (!args.fileArgs) {
-          throw new Error('*args* has no fileArgs')
-        }
-        url = UrlBuilder.getFileUrl(args.fileArgs, args.baseUrl)
-        break;
-
-      default:
-        throw new Error('url, model, image or enum must be defined')
-        break;
-    }
-
-    const options = UrlBuilder.buildOptions(args)
-    return options.length > 0 ? `${url}?${UrlBuilder.buildOptions(args).join('&')}` : url
-  }
-
-  /**
-   * Construction d'une URL qui ira concaténer la base URL avec l'url
-   * @param url
-   * @param baseUrl
+   * Concatenate a base url and a tail
+   * @param base
+   * @param tails
    * @private
    */
-  private static getDefaultUrl (url?: string, baseUrl: string|null = null): string {
-    if (!url) {
-      throw new Error('no url')
-    }
-    return baseUrl ? UrlBuilder.concat(baseUrl, url) : url
-  }
-
-  /**
-   * Construction d'une URL Type Enum qui ira concaténer le type enum passé en paramètre avec la ROOT Url définie
-   * @param {string} enumType
-   * @return {string}
-   */
-  private static getEnumUrl (enumType?: string): string {
-    if (typeof enumType === 'undefined') {
-      throw new Error('enumType must be defined')
-    }
-    return UrlBuilder.concat(UrlBuilder.ROOT, 'enum', enumType)
+  public static join (base: string, ...tails: string[]): string {
+    let url = base
+    tails.forEach((tail: string) => {
+      url = url.replace(/^|\/$/g, '') + '/' + tail.replace(/^\/?|$/g, '')
+    })
+    return url
   }
 
   /**
-   * Construction d'une URL Type Model qui ira concaténer le nom de l'entité du model passé en paramètre
-   * avec la ROOT Url définie (possibilité de récursivité si le root model est défini)
+   * Prepend the 'https://' part if neither 'http://' of 'https://' is present, else: does nothing
    *
-   * @param {Model} model roles à tester
-   * @param {Model} rootModel roles à tester
-   * @param {number} rootId roles à tester
-   * @return {string}
+   * @param url
    */
-  private static getModelUrl (model?: typeof Model, rootModel?: typeof Model, rootId?: number): string {
-    if (typeof model === 'undefined') {
-      throw new Error('model must be defined')
-    }
-
-    const entity = repositoryHelper.getEntity(model)
-
-    if (typeof rootModel !== 'undefined') {
-      if (typeof rootId === 'undefined') {
-        throw new Error('Root ID must be defined')
-      }
-
-      const rootUrl = UrlBuilder.getModelUrl(rootModel) as string
-      return String(`${rootUrl}/${rootId}/${entity}`).toString()
+  public static prependHttps (url: string): string {
+    if (!url.match(/^https?:\/\/.*/)) {
+      url = 'https://' + url;
     }
-
-    return UrlBuilder.concat(UrlBuilder.ROOT, entity)
+    return url;
   }
 
   /**
@@ -121,7 +42,7 @@ class UrlBuilder {
    */
   private static getImageUrl (imgArgs: ImageArgs, baseUrl: string = ''): string {
     const downloadUrl = `files/${imgArgs.id}/download/${imgArgs.height}x${imgArgs.width}`
-    return UrlBuilder.concat(baseUrl, UrlBuilder.ROOT, downloadUrl)
+    return UrlBuilder.join(baseUrl, UrlBuilder.ROOT, downloadUrl)
   }
 
   /**
@@ -131,36 +52,9 @@ class UrlBuilder {
    * @private
    */
   private static getFileUrl (args: FileArgs, baseUrl: string = ''): string {
-    return UrlBuilder.concat(baseUrl, UrlBuilder.ROOT, `download/${args.fileId}`)
-  }
-
-  /**
-   * Concatenate a base url and a tail
-   * @param base
-   * @param tails
-   * @private
-   */
-  public static concat (base: string, ...tails: string[]): string {
-    let url = base
-    tails.forEach((tail: string) => {
-      url = url.replace(/^|\/$/g, '') + '/' + tail.replace(/^\/?|$/g, '')
-    })
-    return url
-  }
-
-  /**
-   * Prepend the 'https://' part if neither 'http://' of 'https://' is present, else: does nothing
-   *
-   * @param url
-   */
-  public static prependHttps (url: string): string {
-    if (!url.match(/^https?:\/\/.*/)) {
-      url = 'https://' + url;
-    }
-    return url;
+    return UrlBuilder.join(baseUrl, UrlBuilder.ROOT, `download/${args.fileId}`)
   }
 
-
   /**
    * Main méthode qui appellera les méthode privées correspondantes (getUrlOptionsImage, getUrlOptionsLists)
    * @param {UrlArgs} args