Explorar o código

add idField decorator

Vincent hai 10 meses
pai
achega
eb60bb2274

+ 9 - 0
models/ApiResource.ts

@@ -6,6 +6,7 @@ import { Model } from 'pinia-orm'
 class ApiResource extends Model {
   // eslint-disable-next-line no-use-before-define
   protected static _iriEncodedFields: Record<string, ApiResource>
+  protected static _idField: string
 
   public static addIriEncodedField(name: string, target: ApiResource) {
     if (!this._iriEncodedFields) {
@@ -18,6 +19,14 @@ class ApiResource extends Model {
     return this._iriEncodedFields
   }
 
+  public static setIdField(name: string) {
+    this._idField = name
+  }
+
+  public static getIdField() {
+    return this._idField
+  }
+
   /**
    * Fix the 'Cannot stringify arbitrary non-POJOs' warning, meaning server can not parse the store
    *

+ 5 - 0
models/Organization/Cotisation.ts

@@ -1,5 +1,6 @@
 import { Num, Str, Uid } from 'pinia-orm/dist/decorators'
 import ApiResource from '~/models/ApiResource'
+import {IdField} from "~/models/decorators";
 
 /**
  * Ap2i ApiResource : Cotisation
@@ -17,4 +18,8 @@ export default class Cotisation extends ApiResource {
 
   @Num(null)
   declare cotisationYear: number | null
+
+  @IdField()
+  @Num(0, { notNullable: true })
+  declare organizationId: number
 }

+ 6 - 1
models/Organization/DolibarrAccount.ts

@@ -1,5 +1,6 @@
-import { Attr, Str, Uid } from 'pinia-orm/dist/decorators'
+import { Attr, Str, Uid, Num } from 'pinia-orm/dist/decorators'
 import ApiResource from '~/models/ApiResource'
+import {IdField} from "~/models/decorators";
 
 /**
  * The Dolibarr account of an organization
@@ -12,6 +13,10 @@ export default class DolibarrAccount extends ApiResource {
   @Uid()
   declare id: number | string | null
 
+  @IdField()
+  @Num(0, { notNullable: true })
+  declare organizationId: number
+
   @Str(null)
   declare clientNumber: string
 

+ 18 - 0
models/decorators.ts

@@ -17,3 +17,21 @@ export function IriEncoded(apiResource: typeof ApiResource): PropertyDecorator {
     self.addIriEncodedField(propertyKey as string, apiResource)
   }
 }
+
+/**
+ * Decorates an ApiResource's property to signal it as a field that is provided
+ * as an unique identifier by the API
+ *
+ * If the property is decorated, the HydraNormalizer will get the item[idField] value to hydrate
+ * the id property of the instance
+ */
+export function IdField(): PropertyDecorator {
+  // We have to comply with the PropertyDecorator return type
+  // eslint-disable-next-line @typescript-eslint/ban-types
+  return (target: Object, propertyKey: string | symbol) => {
+    // @ts-expect-error The object is an ApiResource
+    const self = target.$self()
+
+    self.setIdField(propertyKey as string)
+  }
+}

+ 32 - 15
services/data/normalizer/hydraNormalizer.ts

@@ -1,7 +1,7 @@
 import * as _ from 'lodash-es'
-import type { AnyJson, ApiResponse, HydraMetadata } from '~/types/data'
+import type {AnyJson, ApiResponse, HydraMetadata} from '~/types/data'
 import UrlUtils from '~/services/utils/urlUtils'
-import { METADATA_TYPE } from '~/types/enum/data'
+import {METADATA_TYPE} from '~/types/enum/data'
 import models from '~/models/models'
 import ApiResource from '~/models/ApiResource'
 
@@ -76,7 +76,7 @@ class HydraNormalizer {
   protected static getMetadata(data: AnyJson): HydraMetadata {
     if (data['@type'] !== 'hydra:Collection') {
       // A single item, no metadata
-      return { type: METADATA_TYPE.ITEM }
+      return {type: METADATA_TYPE.ITEM}
     }
 
     const metadata: HydraMetadata = {
@@ -163,17 +163,16 @@ class HydraNormalizer {
 
     throw new Error(
       'De-normalization error : Could not determine the type of the entity ' +
-        item['@id'] +
-        ' (found: ' +
-        entity +
-        ')',
+      item['@id'] +
+      ' (found: ' +
+      entity +
+      ')',
     )
   }
 
   protected static denormalizeEntity(model: typeof ApiResource, item: AnyJson) {
-    if (item.id === undefined) {
-      throw new Error('Missing id field for ' + model)
-    }
+
+    item['id'] = this.getItemIdValue(model, item)
 
     // eslint-disable-next-line new-cap
     const instance = new model(item)
@@ -236,18 +235,36 @@ class HydraNormalizer {
     iri: string,
     expectedEntity: string,
   ): number | string {
-    const { entity, id } = HydraNormalizer.parseEntityIRI(iri)
+    const {entity, id} = HydraNormalizer.parseEntityIRI(iri)
     if (entity !== expectedEntity) {
       throw new Error(
         "IRI entity does not match the field's target entity (" +
-          entity +
-          ' != ' +
-          expectedEntity +
-          ')',
+        entity +
+        ' != ' +
+        expectedEntity +
+        ')',
       )
     }
     return id
   }
+
+  /**
+   * Get the id value of an item
+   * @param model
+   * @param item
+   * @protected
+   */
+  protected static getItemIdValue(model: typeof ApiResource, item: AnyJson) {
+    if (item.id !== undefined) {
+      return item.id
+    }
+
+    if (model.getIdField() !== undefined && item[model.getIdField()] !== undefined) {
+      return item[model.getIdField()]
+    }
+
+    throw new Error('Missing id field or @IdField decorator for ' + model)
+  }
 }
 
 export default HydraNormalizer