浏览代码

Gestion des metadas et du playload dans les réponses

Vincent GUFFON 4 年之前
父节点
当前提交
76e9bf3554

+ 22 - 10
services/connection/connection.ts

@@ -2,6 +2,7 @@ import {NuxtAxiosInstance} from '@nuxtjs/axios'
 import {AxiosRequestConfig} from 'axios'
 import {AnyJson, DataPersisterArgs, DataProviderArgs, UrlArgs} from '~/types/interfaces'
 import {HTTP_METHOD, QUERY_TYPE} from '~/types/enums'
+import TypesTesting from "~/services/utils/typesTesting";
 
 /**
  * @category Services/connection
@@ -37,13 +38,15 @@ class Connection {
         }
 
       case HTTP_METHOD.PUT:
-        if (!Connection.isDataPersisterArgs(args)) {
+      case HTTP_METHOD.POST:
+        if (!TypesTesting.isDataPersisterArgs(args)) {
           throw new Error('*args* is not a dataPersisterArgs')
         }
         if (!args.data) {
           throw new Error('*args* has no data')
         }
-        return Connection.put(url, args.id, args.data, args.showProgress)
+        return method === HTTP_METHOD.PUT ? Connection.put(url, args.id, args.data, args.showProgress) :
+                                            Connection.post(url, args.data, args.showProgress)
 
       case HTTP_METHOD.DELETE:
         return Connection.deleteItem(url, args.id, args.showProgress)
@@ -87,6 +90,23 @@ class Connection {
     return Connection.request(config)
   }
 
+  /**
+   * Post : préparation de la config pour la création d'un item
+   * @param {string} url
+   * @param {AnyJson} data
+   * @param {boolean} progress
+   * @return {Promise<any>}
+   */
+  public static post (url: string, data: AnyJson, progress: boolean = true): Promise<any> {
+    const config: AxiosRequestConfig = {
+      url: `${url}`,
+      method: HTTP_METHOD.POST,
+      data,
+      progress
+    }
+    return Connection.request(config)
+  }
+
   /**
    * Put : préparation de la config pour la mise à jour d'un item
    * @param {string} url
@@ -129,14 +149,6 @@ class Connection {
   public static async request (config: AxiosRequestConfig): Promise<any> {
     return await Connection.connector.$request(config)
   }
-
-  /**
-   * Test si l'argument est bien de type DataPersister
-   * @param args
-   */
-  private static isDataPersisterArgs (args: DataProviderArgs|DataPersisterArgs): args is DataPersisterArgs {
-    return (args as DataPersisterArgs).data !== undefined
-  }
 }
 
 export default Connection

+ 1 - 1
services/data/dataProvider.ts

@@ -38,7 +38,7 @@ class DataProvider extends BaseDataManager {
     const data = await Serializer.denormalize(response, DENORMALIZER_TYPE.HYDRA)
 
     // post-process the data with the first supported processor
-    return await this.process(data, queryArguments  )
+    return await this.process(data, queryArguments)
   }
 
   /**

+ 1 - 3
services/data/processor/baseProcessor.ts

@@ -1,13 +1,11 @@
 import { Context } from '@nuxt/types/app'
 import { AnyJson, DataProviderArgs } from '~/types/interfaces'
-import Hookable from '~/services/data/hookable'
 
-class BaseProcessor extends Hookable {
+class BaseProcessor {
   protected arguments!: DataProviderArgs;
   protected ctx!: Context;
 
   constructor (ctx: Context, args: DataProviderArgs) {
-    super()
     this.arguments = args
     this.ctx = ctx
   }

+ 3 - 3
services/data/processor/enumProcessor.ts

@@ -1,5 +1,5 @@
 import * as _ from 'lodash'
-import { AnyJson, DataProviderArgs, EnumChoice, EnumChoices, Processor } from '~/types/interfaces'
+import {ApiResponse, DataProviderArgs, EnumChoice, EnumChoices, Processor} from '~/types/interfaces'
 import BaseProcessor from '~/services/data/processor/baseProcessor'
 import { QUERY_TYPE } from '~/types/enums'
 
@@ -18,10 +18,10 @@ class EnumProcessor extends BaseProcessor implements Processor {
    * @param data
    */
   // eslint-disable-next-line require-await
-  async process (data: AnyJson): Promise<any> {
+  async process (payload: ApiResponse): Promise<any> {
     const enums: EnumChoices = []
 
-    _.each(data.items, (item, key) => {
+    _.each(payload.data.items, (item, key) => {
       const entry:EnumChoice = {
         value: key,
         label: this.ctx.app.i18n.t(item) as string

+ 5 - 3
services/data/processor/imageProcessor.ts

@@ -1,4 +1,4 @@
-import {DataProviderArgs, Processor} from '~/types/interfaces'
+import {ApiResponse, DataProviderArgs, Processor} from '~/types/interfaces'
 import BaseProcessor from '~/services/data/processor/baseProcessor'
 import {QUERY_TYPE} from '~/types/enums'
 
@@ -15,8 +15,10 @@ class ImageProcessor extends BaseProcessor implements Processor {
    * @param {BlobPart[]} data
    */
   // eslint-disable-next-line require-await
-  async process(data: any): Promise<any> {
-    let blob = new Blob([data], {type: 'image/jpeg'});
+  async process(payload: ApiResponse): Promise<any> {
+    if(payload.data.size === 0) throw new Error('image not found')
+
+    let blob = new Blob([payload.data as BlobPart], {type: 'image/jpeg'});
     return await this.blobToBase64(blob);
   }
 

+ 9 - 6
services/data/processor/modelProcessor.ts

@@ -1,7 +1,7 @@
 import * as _ from 'lodash'
-import { AnyJson, DataProviderArgs, Processor } from '~/types/interfaces'
+import {ApiResponse, DataProviderArgs, Processor} from '~/types/interfaces'
 import BaseProcessor from '~/services/data/processor/baseProcessor'
-import { QUERY_TYPE } from '~/types/enums'
+import {METADATA_TYPE, QUERY_TYPE} from '~/types/enums'
 import { repositoryHelper } from '~/services/store/repository'
 
 class ModelProcessor extends BaseProcessor implements Processor {
@@ -17,15 +17,18 @@ class ModelProcessor extends BaseProcessor implements Processor {
    * Exécute la requête et retourne la réponse désérialisée
    * @param data
    */
-  async process (data: AnyJson): Promise<any> {
+  async process (payload: ApiResponse): Promise<any> {
     if (typeof this.arguments.model === 'undefined') {
       throw new TypeError('model must be defined')
     }
 
-    data.originalState = _.cloneDeep(data)
-    await repositoryHelper.persist(this.arguments.model, data)
+    if(payload.metadata.type !== METADATA_TYPE.COLLECTION){
+      payload.data.originalState = _.cloneDeep(payload)
+    }
+    // console.log(payload.data)
+    await repositoryHelper.persist(this.arguments.model, payload.data)
 
-    return data
+    return payload
   }
 }
 

+ 80 - 44
services/serializer/denormalizer/hydra.ts

@@ -1,6 +1,7 @@
-import { AnyJson } from '~/types/interfaces'
+import {AnyJson, ApiResponse, HydraMetadata} from '~/types/interfaces'
 import BaseDenormalizer from '~/services/serializer/denormalizer/baseDenormalizer'
-import { DENORMALIZER_TYPE } from '~/types/enums'
+import {DENORMALIZER_TYPE, METADATA_TYPE} from '~/types/enums'
+import {parseInt} from "lodash";
 
 /**
  * Classe permettant d'assurer la dénormalization d'un objet Hydra en JSON
@@ -25,27 +26,40 @@ class Hydra extends BaseDenormalizer {
     }
   }
 
-  /**
+  private static parseItem (hydraData: AnyJson): ApiResponse {
+    const itemResponse: ApiResponse = {
+      data: hydraData,
+      metadata: Hydra.definedMetadataForItem(hydraData)
+    }
+    return itemResponse
+  }
+    /**
    * Méthode de parsing appelé si on est dans un GET
    *
    * @param {AnyJson} hydraData
    */
-  private static parseItem (hydraData: AnyJson): AnyJson {
-    if (hydraData['hydra:previous']) {
-      const iriParts = hydraData['hydra:previous'].split('/')
-      hydraData.previous = iriParts[iriParts.length - 1]
-    }
-    if (hydraData['hydra:next']) {
-      const iriParts = hydraData['hydra:next'].split('/')
-      hydraData.next = iriParts[iriParts.length - 1]
-    }
-    if (hydraData['hydra:totalItems']) {
-      hydraData.totalItems = hydraData['hydra:totalItems']
-    }
-    if (hydraData['hydra:itemPosition']) {
-      hydraData.itemPosition = hydraData['hydra:itemPosition']
-    }
-    return hydraData
+  private static definedMetadataForItem (hydraData: AnyJson): AnyJson {
+    const metadata:HydraMetadata = {}
+
+    // if (hydraData['hydra:previous']) {
+    //   const iriParts = hydraData['hydra:previous'].split('/')
+    //   hydraData.previous = iriParts[iriParts.length - 1]
+    // }
+    // if (hydraData['hydra:next']) {
+    //   const iriParts = hydraData['hydra:next'].split('/')
+    //   hydraData.next = iriParts[iriParts.length - 1]
+    // }
+    // if (hydraData['hydra:totalItems']) {
+    //   hydraData.totalItems = hydraData['hydra:totalItems']
+    // }
+    // if (hydraData['hydra:itemPosition']) {
+    //   hydraData.itemPosition = hydraData['hydra:itemPosition']
+    // }
+
+    metadata.type = METADATA_TYPE.ITEM
+
+    return metadata
+
   }
 
   /**
@@ -53,40 +67,62 @@ class Hydra extends BaseDenormalizer {
    *
    * @param {AnyJson} hydraData
    */
-  private static parseCollection (hydraData: AnyJson): AnyJson {
-    const collectionResponse = hydraData['hydra:member']
-    collectionResponse.metadata = {}
-    collectionResponse.order = {}
-    collectionResponse.search = {}
-
-    // Put metadata in a property of the collection
-    for (const key in hydraData) {
-      const value = hydraData[key]
-      if (key !== 'hydra:member') {
-        collectionResponse.metadata[key] = value
-      }
+  private static parseCollection (hydraData: AnyJson): ApiResponse {
+    const collectionResponse:ApiResponse = {
+      data:hydraData['hydra:member'],
+      metadata : Hydra.definedMetadataForCollection(hydraData)
     }
 
+    // collectionResponse.order = {}
+    // collectionResponse.search = {}
+
+
     // Populate href property for all elements of the collection
-    for (const key in collectionResponse) {
-      const value = collectionResponse[key]
+    for (const key in collectionResponse.data) {
+      const value = collectionResponse.data[key]
       Hydra.populateAllData(value)
     }
 
-    if (typeof (hydraData['hydra:search']) !== 'undefined') {
-      const collectionSearch = hydraData['hydra:search']['hydra:mapping']
-      for (const key in collectionSearch) {
-        const value = collectionSearch[key]
-        if (value.variable.indexOf('filter[order]') === 0) {
-          collectionResponse.order[value.property] = value
-        } else if (value.variable.indexOf('filter[where]') === 0) {
-          collectionResponse.search[value.property] = value
-        }
-      }
-    }
+    // if (typeof (hydraData['hydra:search']) !== 'undefined') {
+    //   const collectionSearch = hydraData['hydra:search']['hydra:mapping']
+    //   for (const key in collectionSearch) {
+    //     const value = collectionSearch[key]
+    //     if (value.variable.indexOf('filter[order]') === 0) {
+    //       collectionResponse.order[value.property] = value
+    //     } else if (value.variable.indexOf('filter[where]') === 0) {
+    //       collectionResponse.search[value.property] = value
+    //     }
+    //   }
+    // }
+
     return collectionResponse
   }
 
+  private static  definedMetadataForCollection(data:AnyJson){
+    const metadata:HydraMetadata = {
+      totalItems: data['hydra:totalItems']
+    }
+
+    if(data['hydra:view']){
+      metadata.firstPage = Hydra.getPageNumber(data['hydra:view']['hydra:first'])
+      metadata.lastPage = Hydra.getPageNumber(data['hydra:view']['hydra:last'])
+      metadata.nextPage = Hydra.getPageNumber(data['hydra:view']['hydra:next'])
+      metadata.previousPage = Hydra.getPageNumber(data['hydra:view']['hydra:previous'])
+    }
+
+    metadata.type = METADATA_TYPE.COLLECTION
+
+    return metadata
+  }
+
+  private static  getPageNumber(uri:string):number {
+    if(uri){
+      const number = uri.split('page=').pop()
+      return number ? parseInt(number) : 0
+    }
+    return 0
+  }
+
   /**
    * Hydrate l'objet JSON de façon récursive (afin de gérer les objet nested)
    *

+ 23 - 3
services/serializer/normalizer/model.ts

@@ -1,6 +1,6 @@
 import * as _ from 'lodash'
 import BaseNormalizer from '~/services/serializer/normalizer/baseNormalizer'
-import { DataPersisterArgs } from '~/types/interfaces'
+import {AnyJson, DataPersisterArgs} from '~/types/interfaces'
 import { QUERY_TYPE } from '~/types/enums'
 import { repositoryHelper } from '~/services/store/repository'
 
@@ -24,14 +24,34 @@ class Model extends BaseNormalizer {
       throw new Error('*args* has no model attribute')
     }
 
-    const item = repositoryHelper.findItemFromModel(args.model, args.id)
+    const item = repositoryHelper.findItemFromModel(args.model, args.idTemp ? args.idTemp : args.id)
 
     if (!item || typeof item === 'undefined') {
       throw new Error('Item not found')
     }
 
-    const data = item.$toJson()
+    let data = item.$toJson()
+
+    if(Model.isPostQuery(args)) data = Model.sanitizeBeforePost(data)
+
     return _.omit(data, 'originalState')
   }
+
+  /**
+   * Return true si on est dans un POST
+   * @param args
+   */
+  public static isPostQuery(args: DataPersisterArgs): boolean{
+    return args.idTemp
+  }
+
+  /**
+   * Opération de nettoyage avant un POST
+   * @param data
+   */
+  public static sanitizeBeforePost(data:AnyJson): AnyJson{
+    data.id = null
+    return data
+  }
 }
 export default Model

+ 20 - 3
services/store/repository.ts

@@ -3,6 +3,7 @@ import { Store } from 'vuex'
 import * as _ from 'lodash'
 import { $objectProperties } from '~/services/utils/objectProperties'
 import { AnyJson } from '~/types/interfaces'
+import {OrderByVuexOrm} from "~/types/types";
 
 /**
  * Classe Wrapper pour assurer les opérations les plus communes des Repository de VuexORM
@@ -43,16 +44,26 @@ class Repository {
     return this.getRepository(model).getModel().$entity()
   }
 
+  /**
+   * Créer une entry dans le repository
+   *
+   * @param {Model} model
+   * @param {AnyJson} entry
+   */
+  public make (model: typeof Model, entry?: AnyJson): Model {
+    return this.getRepository(model).make(entry)
+  }
+
   /**
    * Persist l'entry dans le repository
    *
    * @param {Model} model
    * @param {AnyJson} entry
    */
-  public persist (model: typeof Model, entry: AnyJson): void {
+  public persist (model: typeof Model, entry: AnyJson): Model {
     if (_.isEmpty(entry)) { throw new Error('entry is empty') }
 
-    this.getRepository(model).save(entry)
+    return this.getRepository(model).save(entry)
   }
 
   /**
@@ -89,10 +100,16 @@ class Repository {
    * Récupération de la Collection du Model souhaité
    *
    * @param {Model} model
+   * @param {OrderByVuexOrm} orderBy
    * @return {Collection} la collection
    */
-  public findCollectionFromModel (model: typeof Model): Collection {
+  public findCollectionFromModel (model: typeof Model, orderBy?: OrderByVuexOrm): Collection {
     const repository = this.getRepository(model)
+    if(orderBy){
+      for(const orderKey in orderBy){
+        repository.orderBy(orderKey, orderBy[orderKey])
+      }
+    }
     return repository.all()
   }
 

+ 20 - 0
services/utils/typesTesting.ts

@@ -0,0 +1,20 @@
+import {DataPersisterArgs, DataProviderArgs} from "~/types/interfaces";
+
+export default class TypesTesting {
+  /**
+   * Test si l'argument est bien de type DataProviderArgs
+   * @param args
+   */
+  public static isDataProviderArgs (args: DataProviderArgs|DataPersisterArgs): args is DataProviderArgs {
+    return (args as DataProviderArgs).imgArgs !== undefined
+        || (args as DataProviderArgs).listArgs !== undefined
+  }
+
+  /**
+   * Test si l'argument est bien de type DataPersister
+   * @param args
+   */
+  public static isDataPersisterArgs (args: DataProviderArgs|DataPersisterArgs): args is DataPersisterArgs {
+    return (args as DataPersisterArgs).data !== undefined
+  }
+}