Vincent 6 месяцев назад
Родитель
Сommit
2b0b2e5861

+ 13 - 27
components/Ui/Form/Creation.vue

@@ -10,46 +10,32 @@
   </UiForm>
 </template>
 
-<script setup lang="ts">
-import type { PropType } from 'vue'
-import type { RouteLocationRaw } from '@intlify/vue-router-bridge'
+<script setup lang="ts" generic="T extends typeof ApiModel">
+
+import type { RouteLocationRaw } from 'vue-router'
 import type ApiModel from '~/models/ApiModel'
 import type { AnyJson } from '~/types/data'
 import { SUBMIT_TYPE } from '~/types/enum/enums'
 import { useEntityManager } from '~/composables/data/useEntityManager'
 
-const props = defineProps({
-  /**
-   * Classe de l'ApiModel (ex: Organization, Notification, ...)
-   */
-  model: {
-    type: Function as PropType<() => typeof ApiModel>,
-    required: true,
-  },
+const props = withDefaults(defineProps<{
+  model: T
   /**
    * Route de retour
    */
-  goBackRoute: {
-    type: Object as PropType<RouteLocationRaw>,
-    required: false,
-    default: null,
-  },
+  goBackRoute?: RouteLocationRaw | null
   /**
    * La validation est en cours
    */
-  validationPending: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+  validationPending?: boolean
   /**
-   * Faut-il rafraichir le profil à la soumission du formulaire ?
+   * Faut-il rafraîchir le profil à la soumission du formulaire ?
    */
-  refreshProfile: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+  refreshProfile?: boolean
+}>(), {
+  goBackRoute: null,
+  validationPending: false,
+  refreshProfile: false
 })
 
 const router = useRouter()

+ 15 - 37
components/Ui/Form/Edition.vue

@@ -13,57 +13,38 @@
   </LayoutContainer>
 </template>
 
-<script setup lang="ts">
-import type { PropType } from 'vue'
+<script setup lang="ts" generic="T extends typeof ApiModel">
 import { useRoute } from 'vue-router'
 import type { RouteLocationRaw } from 'vue-router'
-import type { AsyncData } from '#app'
 import type ApiModel from '~/models/ApiModel'
 import type { AnyJson } from '~/types/data'
 import { SUBMIT_TYPE } from '~/types/enum/enums'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 
-const props = defineProps({
-  /**
-   * Classe de l'ApiModel (ex: Organization, Notification, ...)
-   */
-  model: {
-    type: Function as PropType<() => typeof ApiModel>,
-    required: true,
-  },
+const props = withDefaults(defineProps<{
+  model: T
   /**
    * Id de l'objet
-   * Si non renseigné, le component essaiera de l'extraire de la route actuelle
+   * Si non renseigné, le composant essaiera de l'extraire de la route actuelle
    */
-  id: {
-    type: Number as PropType<number | null>,
-    required: false,
-    default: null,
-  },
+  id?: number | null
   /**
    * Route de retour
    */
-  goBackRoute: {
-    type: Object as PropType<RouteLocationRaw | null>,
-    required: false,
-    default: null,
-  },
+  goBackRoute?: RouteLocationRaw | null
   /**
    * La validation est en cours
    */
-  validationPending: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+  validationPending?: boolean
   /**
-   * Faut-il rafraichir le profil à la soumission du formulaire ?
+   * Faut-il rafraîchir le profil à la soumission du formulaire ?
    */
-  refreshProfile: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+  refreshProfile?: boolean
+}>(), {
+  id: null,
+  goBackRoute: null,
+  validationPending: false,
+  refreshProfile: false
 })
 
 const { fetch } = useEntityFetch()
@@ -73,10 +54,7 @@ const router = useRouter()
 const entityId =
   props.id !== null ? props.id : parseInt(route.params.id as string)
 
-const { data: entity, pending } = fetch(props.model, entityId) as AsyncData<
-  ApiModel,
-  Error | null
->
+const { data: entity, pending } = fetch(props.model, entityId)
 
 const submitActions = computed(() => {
   const actions: AnyJson = {}

+ 7 - 6
composables/data/useEntityFetch.ts

@@ -9,12 +9,13 @@ import { useEntityManager } from '~/composables/data/useEntityManager'
 import type ApiResource from '~/models/ApiResource'
 import type { Collection } from '~/types/data'
 import type Query from '~/services/data/Query'
+import type {Collection as PiniaOrmCollection} from "pinia-orm";
 
 interface useEntityFetchReturnType {
-  fetch: (
-    model: typeof ApiResource,
-    id?: number | null,
-  ) => AsyncData<ApiResource | null, Error | null>
+  fetch: <T extends typeof ApiResource>(
+    model: T,
+    id?: number | null
+  ) => AsyncData<InstanceType<T> | null, Error | null>
 
   fetchCollection: (
     model: typeof ApiResource,
@@ -42,7 +43,7 @@ export const useEntityFetch = (
 ): useEntityFetchReturnType => {
   const { em } = useEntityManager()
 
-  const fetch = (model: typeof ApiResource, id?: number|null) =>
+  const fetch = <T extends typeof ApiResource>(model: T, id?: number|null): AsyncData<InstanceType<T> | null, Error | null> =>
     useAsyncData(
       model.entity + '_' + id + '_' + uuid4(),
       () => em.fetch(model, id),
@@ -53,7 +54,7 @@ export const useEntityFetch = (
     model: typeof ApiResource,
     parent: ApiResource | null = null,
     query: Query | null = null,
-  ) => {
+  )  => {
     const { data, pending, refresh, error, status } = useAsyncData(
       model.entity + '_many_' + uuid4(),
       () => em.fetchCollection(model, parent, query),

+ 9 - 0
composables/data/useEntityManager.ts

@@ -2,9 +2,18 @@ import { useRepo } from 'pinia-orm'
 import EntityManager from '~/services/data/entityManager'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
 import { useAccessProfileStore } from '~/stores/accessProfile'
+import type ApiResource from "~/models/ApiResource";
+import type {AsyncData} from "#app";
 
 let entityManager: EntityManager | null = null
 
+interface useEntityManagerReturnType {
+  fetch: <T extends typeof ApiResource>(
+    model: T,
+    id?: number | null
+  ) => AsyncData<InstanceType<T> | null, Error | null>
+}
+
 export const useEntityManager = () => {
   if (entityManager === null) {
     const { apiRequestService } = useAp2iRequestService()

+ 1 - 1
services/data/Query.ts

@@ -58,7 +58,7 @@ export default class Query {
    * @see https://pinia-orm.codedredd.de/guide/repository/retrieving-data
    * @param query
    */
-  public applyToPiniaOrmQuery(
+  public applyToPiniaOrmQuery (
     query: PiniaOrmQuery<ApiResource>,
   ): PiniaOrmQuery<ApiResource> {
     // 'Where' filters shall be applied first, then orderBy filters, and finally pagination

+ 28 - 29
services/data/entityManager.ts

@@ -5,7 +5,7 @@ import type {
 } from 'pinia-orm'
 import { v4 as uuid4 } from 'uuid'
 import * as _ from 'lodash-es'
-import { computed } from 'vue'
+import {computed, type ComputedRef} from 'vue'
 import type ApiRequestService from './apiRequestService'
 import UrlUtils from '~/services/utils/urlUtils'
 import type ApiModel from '~/models/ApiModel'
@@ -35,13 +35,13 @@ class EntityManager {
    * A method to retrieve the repository of a given ApiResource
    * @protected
    */
-  protected _getRepo: (model: typeof ApiResource) => Repository<ApiResource>
+  protected _getRepo: <T extends typeof ApiResource>(model: T) => Repository<InstanceType<T>>
 
   protected _getProfileMask: () => object
 
   public constructor(
     apiRequestService: ApiRequestService,
-    getRepo: (model: typeof ApiResource) => Repository<ApiResource>,
+    getRepo: <T extends typeof ApiResource>(model: T) => Repository<InstanceType<T>>,
     getProfileMask: () => object,
   ) {
     this.apiRequestService = apiRequestService
@@ -54,7 +54,7 @@ class EntityManager {
    *
    * @param model
    */
-  public getRepository(model: typeof ApiResource): Repository<ApiResource> {
+  public getRepository<T extends typeof ApiResource>(model: T): Repository<InstanceType<T>> {
     return this._getRepo(model)
   }
 
@@ -63,7 +63,7 @@ class EntityManager {
    *
    * @param model
    */
-  public getQuery(model: typeof ApiResource): PiniaOrmQuery<ApiResource> {
+  public getQuery<T extends typeof ApiResource>(model: T): PiniaOrmQuery<InstanceType<T>> {
     // TODO: quid des uuid?
     return this.getRepository(model).where((val) => Number.isInteger(val.id))
   }
@@ -80,8 +80,8 @@ class EntityManager {
    * @param instance
    */
   // noinspection JSMethodCanBeStatic
-  public cast(model: typeof ApiResource, instance: ApiResource): ApiResource {
-    return new model(instance)
+  public cast<T extends typeof ApiResource>(model: T, instance: InstanceType<T>): InstanceType<T> {
+    return new model(instance) as InstanceType<T>
   }
 
   /**
@@ -115,13 +115,13 @@ class EntityManager {
    * @param model
    * @param properties
    */
-  public newInstance(
-    model: typeof ApiResource,
+  public newInstance<T extends typeof ApiResource>(
+    model: T,
     properties: object = {},
-  ): ApiResource {
+  ): InstanceType<T> {
     const repository = this.getRepository(model)
 
-    const instance = repository.make(properties)
+    const instance  = repository.make(properties)
 
     if (
       !Object.prototype.hasOwnProperty.call(properties, 'id') ||
@@ -142,7 +142,7 @@ class EntityManager {
    * @param permanent Is the change already persisted in the datasource? If this is the case, the initial state of this
    *                  record is also updated.
    */
-  public save(instance: ApiResource, permanent: boolean = false): ApiResource {
+  public save<T extends ApiResource>(instance: T, permanent: boolean = false): T {
     const model = this.getModel(instance)
 
     this.validateEntity(instance)
@@ -151,7 +151,7 @@ class EntityManager {
       this.saveInitialState(model, instance)
     }
 
-    return this.getRepository(model).save(instance)
+    return this.getRepository(model).save(instance) as T
   }
 
   /**
@@ -161,8 +161,7 @@ class EntityManager {
    * @param model
    * @param id
    */
-  // @ts-expect-error TODO: corriger au moment de l'implémentation des types génériques
-  public find<T extends ApiResource>(model: typeof T, id: number | string): T {
+  public find<T extends typeof ApiResource>(model: T, id: number | string): T {
     const repository = this.getRepository(model)
     return repository.find(id) as T
   }
@@ -173,10 +172,10 @@ class EntityManager {
    * @param model  Model of the object to fetch
    * @param id   Id of the object to fetch
    */
-  public async fetch(
-    model: typeof ApiResource,
+  public async fetch <T extends typeof ApiResource>(
+    model: T,
     id?: number | null,
-  ): Promise<ApiResource> {
+  ): Promise<InstanceType<T>> {
     // Else, get the object from the API
     const url = UrlUtils.join('api', model.entity, id ? String(id) : '')
     const response = await this.apiRequestService.get(url)
@@ -317,9 +316,9 @@ class EntityManager {
    * @param instance
    * @param instance
    */
-  public async delete(instance: ApiModel) {
+  public async delete<T extends ApiResource>(instance: T) {
     const model = this.getModel(instance)
-    instance = this.cast(model, instance)
+    instance = this.cast(model, instance) as T
 
     console.log('delete', instance)
 
@@ -345,7 +344,7 @@ class EntityManager {
    * @param model
    * @param instance
    */
-  public reset(instance: ApiResource) {
+  public reset<T extends ApiResource>(instance: T) {
     const model = this.getModel(instance)
 
     const initialInstance = this.getInitialStateOf(model, instance.id)
@@ -370,7 +369,7 @@ class EntityManager {
    *
    * @param model
    */
-  public flush(model: typeof ApiModel) {
+  public flush<T extends typeof ApiResource>(model: T) {
     const repository = this.getRepository(model)
     repository.flush()
   }
@@ -384,7 +383,7 @@ class EntityManager {
    * @param model
    * @param id
    */
-  public isNewInstance(model: typeof ApiModel, id: number | string): boolean {
+  public isNewInstance<T extends typeof ApiResource>(model: T, id: number | string): boolean {
     const repository = this.getRepository(model)
 
     const item = repository.find(id)
@@ -404,7 +403,7 @@ class EntityManager {
    * @param instance
    * @private
    */
-  protected saveInitialState(model: typeof ApiResource, instance: ApiResource) {
+  protected saveInitialState<T extends typeof ApiResource>(model: T, instance: InstanceType<T>) {
     const repository = this.getRepository(model)
 
     // Clone and prefix id
@@ -421,10 +420,10 @@ class EntityManager {
    * @param id
    * @private
    */
-  protected getInitialStateOf(
-    model: typeof ApiResource,
+  protected getInitialStateOf<T extends typeof ApiResource>(
+    model: T,
     id: string | number,
-  ): ApiResource | null {
+  ): InstanceType<T> | null {
     const repository = this.getRepository(model)
 
     // Find the clone by id
@@ -446,8 +445,8 @@ class EntityManager {
    * @param tempInstanceId
    * @private
    */
-  protected removeTempAfterPersist(
-    model: typeof ApiResource,
+  protected removeTempAfterPersist<T extends typeof ApiResource>(
+    model: T,
     tempInstanceId: number | string,
   ) {
     const repository = this.getRepository(model)

+ 0 - 2
services/layout/menuBuilder/accountMenuBuilder.ts

@@ -164,8 +164,6 @@ export default class AccountMenuBuilder extends AbstractMenuBuilder {
 
     children.push(...this.makeChildren([{ pageName: 'freemium_organization_page' }]))
 
-    children.push(...this.makeChildren([{ pageName: 'freemium_subscription_page' }]))
-
     actions.push(
       this.createItem('logout', undefined, `/logout`, MENU_LINK_TYPE.V1),
     )

+ 2 - 55
tests/units/services/data/entityManager.test.ts

@@ -285,74 +285,21 @@ describe('find', () => {
 })
 
 describe('fetch', () => {
-  test('not in store, no force refresh', async () => {
+  test('fetch model', async () => {
     const properties = { id: 1 }
 
     const entity = new DummyApiResource({ id: 1 })
 
-    // @ts-ignore
-    entityManager.find = vi.fn(
-      (model: typeof ApiResource, id: number) => undefined,
-    )
-
     // @ts-ignore
     apiRequestService.get = vi.fn(async (url: string) => properties)
 
     // @ts-ignore
     entityManager.newInstance = vi.fn(
-      (model: typeof ApiResource, props: object) => entity,
-    )
-
-    const result = await entityManager.fetch(DummyApiResource, 1)
-
-    expect(entityManager.find).toHaveBeenCalledWith(DummyApiResource, 1)
-    expect(apiRequestService.get).toHaveBeenCalledWith('api/dummyResource/1')
-    expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiResource, {
-      id: 1,
-      name: null,
-      _model: undefined,
-    })
-
-    expect(result).toEqual(entity)
-  })
-
-  test('in store, no force refresh', async () => {
-    const properties = { id: 1 }
-
-    const entity = new DummyApiResource({ id: 1 })
-
-    // @ts-ignore
-    entityManager.find = vi.fn(
-      (model: typeof ApiResource, id: number) => entity,
+      <T extends typeof ApiResource>(model: T, props: object) => entity,
     )
 
     const result = await entityManager.fetch(DummyApiResource, 1)
 
-    expect(entityManager.find).toHaveBeenCalledWith(DummyApiResource, 1)
-    expect(result).toEqual(entity)
-  })
-
-  test('in store, but with force refresh', async () => {
-    const properties = { id: 1 }
-
-    const entity = new DummyApiResource({ id: 1 })
-
-    // @ts-ignore
-    entityManager.find = vi.fn(
-      (model: typeof ApiResource, id: number) => undefined,
-    )
-
-    // @ts-ignore
-    apiRequestService.get = vi.fn(async (url: string) => properties)
-
-    // @ts-ignore
-    entityManager.newInstance = vi.fn(
-      (model: typeof ApiResource, props: object) => entity,
-    )
-
-    const result = await entityManager.fetch(DummyApiResource, 1, true)
-
-    expect(entityManager.find).toHaveBeenCalledTimes(0)
     expect(apiRequestService.get).toHaveBeenCalledWith('api/dummyResource/1')
     expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiResource, {
       id: 1,

+ 1 - 1
tests/units/services/layout/menuBuilder/accountMenuBuilder.test.ts

@@ -55,7 +55,7 @@ describe('build', () => {
     expect(result.label).toEqual('my_account')
 
     // @ts-ignore
-    expect(result.children.length).toEqual(15)
+    expect(result.children.length).toEqual(16)
     // @ts-ignore
     expect(result.actions.length).toEqual(1)