فهرست منبع

v4027 Review todos, .env.local becomes .env.docker

Olivier Massot 2 سال پیش
والد
کامیت
a353618569

+ 3 - 0
.env.local → .env.docker

@@ -25,4 +25,7 @@ NUXT_PUBLIC_BASE_URL_TYPO3=https://local.sub.opentalent.fr/###subDomain###
 NUXT_BASE_URL_MERCURE=https://mercure/.well-known/mercure
 NUXT_PUBLIC_BASE_URL_MERCURE=https://local.mercure.opentalent.fr/.well-known/mercure
 
+# Other links
+NUXT_SUPPORT_URL=https://support.opentalent.fr/
+
 MERCURE_SUBSCRIBER_JWT_KEY=NQEupdREijrfYvCmF2mnvZQFL9zLKDH9RCYter6tUWzjemPqzicffhc2fSf0yEmM

+ 3 - 0
.env.preprod

@@ -17,3 +17,6 @@ NUXT_PUBLIC_BASE_URL_ADMIN_LEGACY=https://admin.preprod.opentalent.fr/#
 # Typo3 Base Url
 NUXT_BASE_URL_TYPO3=https://preprod.opentalent.fr/###subDomain###
 NUXT_PUBLIC_BASE_URL_TYPO3=https://preprod.opentalent.fr/###subDomain###
+
+# Other links
+NUXT_SUPPORT_URL=https://support.opentalent.fr/

+ 3 - 0
.env.prod

@@ -18,3 +18,6 @@ NUXT_PUBLIC_BASE_URL_ADMIN_LEGACY=https://admin.opentalent.fr/#
 # Typo3 Base Url
 NUXT_BASE_URL_TYPO3=https://###subDomain###.opentalent.fr
 NUXT_PUBLIC_BASE_URL_TYPO3=https://###subDomain###.opentalent.fr
+
+# Other links
+NUXT_SUPPORT_URL=https://support.opentalent.fr/

+ 3 - 0
.env.test

@@ -26,3 +26,6 @@ NUXT_BASE_URL_MERCURE=https://mercure.test.opentalent.fr/.well-known/mercure
 NUXT_PUBLIC_BASE_URL_MERCURE=https://mercure.test.opentalent.fr/.well-known/mercure
 
 MERCURE_SUBSCRIBER_JWT_KEY=NQEupdREijrfYvCmF2mnvZQFL9zLKDH9RCYter6tUWzjemPqzicffhc2fSf0yEmM
+
+# Other links
+NUXT_SUPPORT_URL=https://support.opentalent.fr/

+ 2 - 2
components/Layout/Header.vue

@@ -37,11 +37,10 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
     <LayoutHeaderMenu name="Account" />
 
     <a
-        href="https://support.opentalent.fr/"
+        :href="runtimeConfig.supportUrl"
         class="text-body pa-3 ml-2 bg-ot-dark-grey text-ot-white text-decoration-none"
         target="_blank"
     >
-      <!-- TODO: mettre le lien vers le support dans les .env ou dans la conf -->
       <span class="d-none d-sm-none d-md-flex">{{ $t('help_access') }}</span>
       <v-icon icon="fas fa-question-circle" class="d-sm-flex d-md-none" color="white" />
     </a>
@@ -57,6 +56,7 @@ import { useDisplay } from 'vuetify'
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
 
 const organizationProfile = useOrganizationProfileStore()
+const runtimeConfig = useRuntimeConfig()
 
 const title: ComputedRef<string> = computed(() => organizationProfile.name ?? 'Opentalent')
 

+ 3 - 2
components/Layout/Header/Notification.vue

@@ -122,8 +122,9 @@ let { data: collection, pending, refresh } = await fetchCollection(Notification)
  * On récupère les Notifications via le store
  */
 const notifications: ComputedRef = computed(() => {
-  // TODO: revoir pour reprendre le order by et tout
-  return collection.value !== null ? collection.value.items : []
+  const items = collection.value !== null ? collection.value.items : []
+  items.sort((a, b) => b.id - a.id ) // reverse sort by id
+  return items
 })
 
 /**

+ 4 - 2
components/Layout/SubHeader/ActivityYear.vue

@@ -58,9 +58,11 @@ const setActivityYear = async (activityYear: number) => {
   access.activityYear = activityYear
   await em.persist(Access, access)
 
-  accessProfileStore.$patch({ activityYear: activityYear }) // TODO: est-ce nécessaire, sachant que l'EM met déjà à jour le profil?
+  // Update the store
+  // TODO: voir si mieux d'automatiser ces maj du profil, ou de les faire à la main au cas par cas?
+  accessProfileStore.$patch({ activityYear: activityYear })
 
-  window.location.reload() // TODO: est-ce vraiment nécessaire?
+  window.location.reload()
 }
 
 </script>

+ 0 - 1
components/Layout/SubHeader/DataTiming.vue

@@ -71,7 +71,6 @@ const onUpdate = async (newValue: Array<string>) => {
       {'historical': accessProfileStore.historical}
   )
 
-  // TODO: voir si c'est indispensable de reload la page entière
   window.location.reload()
 }
 

+ 0 - 1
components/Layout/SubHeader/DataTimingRange.vue

@@ -67,7 +67,6 @@ const updateDateTimeRange = async (dates:Array<string>): Promise<any> => {
       {'historical': accessProfileStore.historical}
   )
 
-  // TODO: voir si c'est indispensable de reload la page entière
   window.location.reload()
 }
 

+ 1 - 1
components/Ui/DataTable.vue

@@ -1,5 +1,5 @@
 <!--
-Tableau interactif
+Tableau interactif conçu pour l'affichage d'une collection d'entités
 
 @see https://vuetifyjs.com/en/components/data-tables/
 -->

+ 4 - 5
components/Ui/Input/Image.vue

@@ -170,19 +170,18 @@ const save = async () => {
     width: coordinates.value.width
   })
 
-  // Mise à jour d'une image existante : on bouge simplement le cropper
   if (image.value.id > 0) {
+    // Mise à jour d'une image existante : on bouge simplement le cropper
+
     file.id = image.value.id as number
-    em.save(File, file) // TODO: le save devrait être inutile maintenant, à vérifier
 
     await em.persist(File, file) // TODO: à revoir
 
     // On émet un évent afin de mettre à jour le formulaire de départ
     emit('reload')
-  }
 
-  // Création d'une nouvelle image
-  else {
+  } else {
+    // Création d'une nouvelle image
     if (image.value.file) {
 
       // On créé l'objet File à sauvegarder

+ 1 - 1
components/Ui/Input/Phone.vue

@@ -3,7 +3,7 @@ Champs de saisie d'un numéro de téléphone
 
 @see https://github.com/yogakurniawan/vue-tel-input-vuetify
 
-// TODO: vérifier compatibilité avec Vue 3
+// TODO: tester compatibilité avec Vue 3
 -->
 
 <template>

+ 1 - 2
components/Ui/Template/DataTable.vue

@@ -1,6 +1,5 @@
 <!--
-Tableau interactif
-// TODO: expliquer ici la différence avec UiDatatable
+Template de base d'un tableau interactif
 
 @see https://vuetifyjs.com/en/components/data-tables/
 -->

+ 1 - 1
composables/data/useEntityFetch.ts

@@ -12,7 +12,7 @@ export const useEntityFetch = (lazy: boolean = false): useEntityFetchReturnType
     const { em } = useEntityManager()
 
     const fetch = (model: typeof ApiResource, id: number) => useAsyncData(
-        model.entity + '_' + id, // TODO: je me demande si on a besoin de cette clé? (https://v3.nuxtjs.org/api/composables/use-async-data#params)
+        model.entity + '_' + id,
         () => em.fetch(model, id, true),
         { lazy }
     )

+ 2 - 1
composables/layout/useRedirectToLogin.ts

@@ -1,4 +1,5 @@
 import {navigateTo, useRuntimeConfig} from "#app";
+import Url from "~/services/utils/url";
 
 export const useRedirectToLogin = () => {
     const runtimeConfig = useRuntimeConfig()
@@ -7,6 +8,6 @@ export const useRedirectToLogin = () => {
         if (!runtimeConfig.baseUrlAdminLegacy) {
             throw new Error('Configuration error : no redirection target')
         }
-        navigateTo(runtimeConfig.baseUrlAdminLegacy + '/login', {external: true})
+        navigateTo(Url.join(runtimeConfig.baseUrlAdminLegacy, '#/login'), {external: true})
     }
 }

+ 0 - 0
middleware/.gitkeep


+ 0 - 11
middleware/auth.ts

@@ -1,11 +0,0 @@
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import {defineNuxtRouteMiddleware, navigateTo} from "#app";
-
-export default defineNuxtRouteMiddleware(
-(to, from) => {
-      // Si l'utilisateur n'est pas connecté on le redirige vers la page login
-      if (!useAccessProfileStore().bearer) {
-        // TODO: vérifier qu'on passe bien par ici, car en général ça pète en amont dans le init.server.ts
-        return navigateTo(process.env.baseUrlAdminLegacy + '/login')
-      }
-})

+ 0 - 3
models/Core/AddressPostal.ts

@@ -12,9 +12,6 @@ export class AddressPostal extends ApiModel {
   @Uid()
   declare id: number | string | null
 
-  @Str('')
-  declare '@id': string // TODO: pqoi le conserver celui là?
-
   @Attr(null)
   declare organizationAddressPostalId: number | null
 

+ 2 - 0
models/models.ts

@@ -30,6 +30,8 @@ import {Subdomain} from "~/models/Organization/Subdomain";
 import ApiResource from "~/models/ApiResource";
 
 // TODO: voir si possible de se passer de ce fichier
+// Actuellement, cet import n'est utilisé que par le service EntityManager pour
+//    retrouver la classe d'une entité à partir de son nom
 
 const classes = [
     Access,

+ 2 - 0
nuxt.config.ts

@@ -19,6 +19,7 @@ export default defineNuxtConfig({
         baseUrlAdminLegacy: '',
         baseUrlTypo3: '',
         baseUrlMercure: '',
+        supportUrl: '',
         // Config within public will be also exposed to the client
         public: {
             baseUrl: '',
@@ -26,6 +27,7 @@ export default defineNuxtConfig({
             baseUrlAdminLegacy: '',
             baseUrlTypo3: '',
             baseUrlMercure: '',
+            supportUrl: '',
         }
     },
     hooks: {

+ 3 - 3
package.json

@@ -7,15 +7,15 @@
   },
   "scripts": {
     "dev": "rm -rf /tmp/nitro && nuxt dev",
-    "dev:local": "yarn dev --dotenv .env.local",
+    "dev:local": "yarn dev --dotenv .env.docker",
     "dev:preprod": "yarn dev --dotenv .env.preprod",
     "dev:prod": "yarn dev --dotenv .env.prod",
     "build": "nuxt build",
-    "build:local": "yarn build --dotenv .env.local",
+    "build:local": "yarn build --dotenv .env.docker",
     "build:preprod": "yarn build --dotenv .env.preprod",
     "build:prod": "yarn build --dotenv .env.prod",
     "start": "nuxt start --hostname '127.0.0.1' --port 3003",
-    "start:local": "yarn start --dotenv .env.local",
+    "start:local": "yarn start --dotenv .env.docker",
     "start:preprod": "yarn start --dotenv .env.preprod",
     "start:prod": "yarn start --dotenv .env.prod",
     "deploy": "git pull && yarn install && yarn build && pm2 start app",

+ 0 - 1
plugins/sse.client.ts

@@ -10,7 +10,6 @@ import {useSseStore} from "~/stores/sse";
  * @param ctx
  */
 export default defineNuxtPlugin(nuxtApp => {
-    // TODO: re-valider le fonctionnement du SSE
     const runtimeConfig = useRuntimeConfig()
 
     if (!runtimeConfig.baseUrlMercure) {

+ 17 - 2
services/data/entityManager.ts

@@ -47,10 +47,23 @@ class EntityManager {
      *
      * @param entityName
      */
-    public getModelFor(entityName: string): typeof ApiResource{
+    public getModelFor(entityName: string): typeof ApiResource {
         return models[entityName]
     }
 
+    /**
+     * Return the model class from an Opentalent Api IRI
+     *
+     * @param iri An IRI of the form .../api/<entity>/...
+     */
+    public getModelFromIri(iri: string): typeof ApiResource {
+        const matches = iri.match(/^\/api\/(\w+)\/.*/)
+        if (!matches || !matches[1]) {
+            throw new Error('cannot parse the IRI')
+        }
+        return this.getModelFor(matches[1])
+    }
+
     /**
      * Create a new instance of the given model
      *
@@ -266,6 +279,9 @@ class EntityManager {
     }
 
     /**
+     * @Deprecated : a priori ce n'est pas le bon service pour mettre à jour le profil, on devrait voir ça
+     * depuis un service dédié, un composable, ou directement dans le store
+     *
      * Re-fetch the user profile and update the store
      */
     public async refreshProfile() {
@@ -282,7 +298,6 @@ class EntityManager {
         const profile = this.newInstance(MyProfile, hydraResponse.data)
 
         // On met à jour le store accessProfile
-        // TODO: sortir le use du service, ça devrait être dans un composable
         const accessProfileStore = useAccessProfileStore()
         accessProfileStore.setProfile(profile)
     }

+ 2 - 2
services/data/imageManager.ts

@@ -29,7 +29,7 @@ class ImageManager {
         defaultImage: string | null,
         height: number = 0,
         width: number = 0
-    ): Promise<string> {
+    ): Promise<string | ArrayBuffer> {
 
         const defaultUrl = defaultImage ?? this.defaultImage
 
@@ -58,7 +58,7 @@ class ImageManager {
         }
 
         const blob = await ImageUtils.newBlob(response)
-        return await ImageUtils.blobToBase64(blob)
+        return await ImageUtils.blobToBase64(blob) ?? ''
     }
 }
 

+ 2 - 3
services/utils/imageUtils.ts

@@ -9,7 +9,7 @@ class ImageUtils {
      * @param data
      * @param filetype
      */
-    public static async newBlob(data: string, filetype: string = 'image/jpeg') {
+    public static newBlob(data: string, filetype: string = 'image/jpeg'): Blob {
         return new Blob([data as BlobPart], {type: filetype});
     }
 
@@ -17,8 +17,7 @@ class ImageUtils {
      * Transforme un Blob en Base64
      * @param {Blob} blob
      */
-    public static async blobToBase64(blob: Blob): Promise<any> {
-        // TODO: mieux typer la sortie
+    public static async blobToBase64(blob: Blob): Promise<string | ArrayBuffer | null> {
         return new Promise((resolve, _) => {
             const reader = new FileReader();
             reader.onloadend = () => resolve(reader.result);

+ 1 - 1
stores/form.ts

@@ -16,7 +16,7 @@ export const useFormStore = defineStore('form', () => {
   }
 
   const addViolation = (invalidFields: AnyJson) => {
-    // TODO: revoir
+    // TODO: à revoir
     violations.value = invalidFields
   }
 

+ 1 - 8
stores/sse.ts

@@ -10,14 +10,7 @@ export const useSseStore = defineStore('sse', () => {
   const addEvent = async (event: MercureEntityUpdate) => {
     const {em} = useEntityManager()
 
-    // TODO: voir à refactorer le "get model from iri"
-    const matches = event.iri.match(/^\/api\/(\w+)\/.*/)
-    if (!matches || !matches[1]) {
-      throw new Error('cannot parse the IRI')
-    }
-    const entityName = matches[1]
-
-    const model = em.getModelFor(entityName)
+    const model = em.getModelFromIri(event.iri)
 
     switch (event.operation) {
       case "update":