From 814c5686c5dcccac3c28b39a9f9cc59291a918a6 Mon Sep 17 00:00:00 2001 From: Yossi Kolesnicov Date: Thu, 21 Dec 2017 17:09:43 +0200 Subject: [PATCH] Saving entities --- lib/repository/repository.interface.ts | 4 +- lib/repository/repository.ts | 111 ++++++++++++++++------ lib/services/data-store.service.ts | 23 ++--- lib/services/data-transformers.service.ts | 2 +- lib/services/entities.service.base.ts | 14 --- lib/services/http.service.ts | 36 ++++++- 6 files changed, 127 insertions(+), 63 deletions(-) diff --git a/lib/repository/repository.interface.ts b/lib/repository/repository.interface.ts index 8e92723..218f35f 100644 --- a/lib/repository/repository.interface.ts +++ b/lib/repository/repository.interface.ts @@ -10,10 +10,10 @@ export interface IRepository{ createNewItem:() => EntityModelBase, getItemById:(id:any) => Observable, query:(options?:DataQuery) => Observable>, - getItemSaveData:(item:EntityModelBase) => Object, + serializeItem:(item:EntityModelBase) => Object, allItems$:Observable>, endpointName:string, endpointUrl:string, - //save:(item:EntityModelBase) => Observable, + save:(item:EntityModelBase) => Observable //save$:Observable } diff --git a/lib/repository/repository.ts b/lib/repository/repository.ts index 9a11aba..fbcd7da 100644 --- a/lib/repository/repository.ts +++ b/lib/repository/repository.ts @@ -343,43 +343,94 @@ export class Repository implements IRepository { }).map(dataSet => dataSet.items); } - // save(item: T): Observable { - // let saveData: Index = this.getItemSaveData(item); - // - // return this.dataStore.post(`${this.endpoint}/${item.id || ''}`, saveData) - // .flatMap((savedItemData: Index) => this.createItem(savedItemData)) - // .do((item: T) => { - // if (this._allValues) { - // this._allValues = [...this._allValues, item]; - // this._allItemsSubject$.next(this._allValues); - // } - // - // this._saveSubject$.next(item); - // }); - // } + /** + * Saves an entity to the server + * @param {T} item + * @returns {Observable} + */ + save(item: T): Observable { + if (!this.entity.endpoint) + throw new Error(`Entity ${this.entity.entityConstructor.name} can be saved - it doesn't specify an endpoint.`); + + try { + let saveData: Index = this.serializeItem(item); + return this.dataStore.save(`${this.entity.endpoint}/${item.id || ''}`, item.id === undefined ? "POST" : "PUT", { data: saveData }) + .flatMap((savedItemData: Index) => this.createItem(savedItemData)) + .do((item: T) => { + if (this._allValues) { + this._allValues = [...this._allValues, item]; + this._allItemsSubject$.next(this._allValues); + } + + this._saveSubject$.next(item); + }); + } + catch(e){ + return Observable.throw(e); + } + } + + /** + * Validates that the specified item is valid, according to the requirements of the entity (or value object) it belongs to. + * @param item + * @param {EntityConfigBase} entity + * @returns {boolean} + */ + static validateItem(item:any, entity:EntityConfigBase):boolean{ + entity.fields.forEach((entityField:Field) => { + let itemFieldValue: any = (item)[entityField.id]; + + if (entityField.required && (itemFieldValue === undefined || itemFieldValue === null)) + throw new Error(`Missing value for field '${entityField.id}'`); + }); + + return true; + } + + /** + * Creates a JSON object that can be saved to server, with the reverse logic of getItemModelData + * @param {T} item + * @returns {Index} + */ + serializeItem(item:T): Index { + Repository.validateItem(item, this.entity); + + return Repository.serializeItem(item, this.entity, this.paris); + } + + /** + * Serializes an object value + * @param item + * @returns {Index} + */ + static serializeItem(item:any, entity:EntityConfigBase, paris:Paris):Index{ + Repository.validateItem(item, entity); - getItemSaveData(item: T): Index { let modelData: Index = {}; - for (let propertyId in item) { - if (item.hasOwnProperty(propertyId)) { - let modelValue: any; + entity.fields.forEach((entityField:Field) => { + let itemFieldValue:any = (item)[entityField.id], + fieldRepository = paris.getRepository(entityField.type), + fieldValueObjectType:EntityConfigBase = !fieldRepository && valueObjectsService.getEntityByType(entityField.type), + isNilValue = itemFieldValue === undefined || itemFieldValue === null; - let propertyValue: any = item[propertyId], - entityField: Field = this.entity.fields.get(propertyId); + let modelValue:any; - if (entityField) { - let propertyRepository: IRepository = this.paris.getRepository(entityField.type); + if (entityField.isArray) + modelValue = itemFieldValue ? itemFieldValue.map((element:any) => Repository.serializeItem(element, fieldRepository ? fieldRepository.entity : fieldValueObjectType, paris)) : null; + else if (fieldRepository) + modelValue = isNilValue ? fieldRepository.entity.getDefaultValue() || null : itemFieldValue.id; + else if (fieldValueObjectType) + modelValue = isNilValue ? fieldValueObjectType.getDefaultValue() || null : Repository.serializeItem(itemFieldValue, fieldValueObjectType, paris); + else + modelValue = DataTransformersService.serialize(entityField.type, itemFieldValue); - if (propertyRepository) - modelValue = (propertyValue).id; - else - modelValue = DataTransformersService.serialize(entityField.type, propertyValue); + let modelProperty:string = entityField.data + ? entityField.data instanceof Array ? entityField.data[0] : entityField.data + : entityField.id; - modelData[entityField.id] = modelValue; - } - } - } + modelData[modelProperty] = modelValue; + }); return modelData; } diff --git a/lib/services/data-store.service.ts b/lib/services/data-store.service.ts index 8f668e5..3886f14 100644 --- a/lib/services/data-store.service.ts +++ b/lib/services/data-store.service.ts @@ -1,5 +1,5 @@ import {ParisConfig} from "../config/paris-config"; -import {Http, HttpOptions} from "./http.service"; +import {Http, HttpOptions, RequestMethod} from "./http.service"; import {Observable} from "rxjs/Observable"; export class DataStoreService{ @@ -8,19 +8,19 @@ export class DataStoreService{ constructor(private config:ParisConfig){} get(endpoint:string, data?:HttpOptions, baseUrl?:string):Observable{ - return this.setActiveRequest(Observable.from(Http.get(this.getEndpointUrl(endpoint, baseUrl), data, this.config.http)), HttpVerb.get, endpoint, data); + return this.setActiveRequest(Observable.from(Http.get(this.getEndpointUrl(endpoint, baseUrl), data, this.config.http)), "GET", endpoint, data); } - // post(endpoint:string, data?:RequestData, baseUrl?:string):Observable{ - // return this.http.post(this.getEndpointUrl(endpoint, baseUrl), data); - // } + save(endpoint:string, method:RequestMethod = "POST", data?:HttpOptions, baseUrl?:string):Observable{ + return Http.request(method, this.getEndpointUrl(endpoint, baseUrl), data); + } private getEndpointUrl(endpoint:string, baseUrl?:string):string{ return `${baseUrl || this.config.apiRoot || ""}/${endpoint}`; } - private setActiveRequest(obs:Observable, verb:HttpVerb, endpoint:string, data?:RequestData):Observable{ - let activeRequestId:string = DataStoreService.getActiveRequestId(verb, endpoint, data), + private setActiveRequest(obs:Observable, method:RequestMethod, endpoint:string, data?:RequestData):Observable{ + let activeRequestId:string = DataStoreService.getActiveRequestId(method, endpoint, data), existingActiveRequest = this.activeRequests.get(activeRequestId); if (existingActiveRequest) @@ -34,16 +34,11 @@ export class DataStoreService{ } } - private static getActiveRequestId(verb:HttpVerb, endpoint:string, data?:RequestData):string{ - return `${verb}__${endpoint}__${data ? JSON.stringify(data) : '|'}`; + private static getActiveRequestId(method:RequestMethod, endpoint:string, data?:RequestData):string{ + return `${method}__${endpoint}__${data ? JSON.stringify(data) : '|'}`; } } export interface RequestData{ [index:string]:any } - -enum HttpVerb{ - get = "GET", - post = "POST" -} diff --git a/lib/services/data-transformers.service.ts b/lib/services/data-transformers.service.ts index a789b5f..4f69e4d 100644 --- a/lib/services/data-transformers.service.ts +++ b/lib/services/data-transformers.service.ts @@ -4,7 +4,7 @@ const transformers:Array = [ { type: Date, parse: (dateValue:string) => new Date(dateValue), - serialize: (date:Date) => date.valueOf() + serialize: (date:Date) => date ? date.valueOf() : null }, { type: RegExp, diff --git a/lib/services/entities.service.base.ts b/lib/services/entities.service.base.ts index cfaff3a..04da43f 100644 --- a/lib/services/entities.service.base.ts +++ b/lib/services/entities.service.base.ts @@ -26,20 +26,6 @@ export abstract class EntitiesServiceBase{ return entity; } - - - getEntityByPluralName(pluralName:string):DataEntityType{ - let allEntities:Array = Array.from(this._allEntities.keys()), - pluralNameLowerCase = pluralName.toLowerCase(); - - for(let i=0, entity:DataEntityType; entity = allEntities[i]; i++){ - if (entity.entityConfig.pluralName.toLowerCase() === pluralNameLowerCase) - return entity; - } - - return null; - } - private getDataEntityTypeFields(dataEntityType:DataEntityType):EntityFields{ if (!dataEntityType) return null; diff --git a/lib/services/http.service.ts b/lib/services/http.service.ts index c70a6f8..df0365a 100644 --- a/lib/services/http.service.ts +++ b/lib/services/http.service.ts @@ -1,13 +1,45 @@ import {ParisHttpConfig} from "../config/paris-config"; import {Observable} from "rxjs/Observable"; +export type RequestMethod = "GET"|"POST"|"PUT"|"PATCH"|"DELETE"; + export class Http{ static get(url:string, options?:HttpOptions, httpConfig?:ParisHttpConfig):Observable{ + return Http.request("GET", url, options, httpConfig); + } + + static post(url:string, options?:HttpOptions, httpConfig?:ParisHttpConfig):Observable{ + return Http.request("POST", url, options, httpConfig); + } + + static put(url:string, options?:HttpOptions, httpConfig?:ParisHttpConfig):Observable{ + return Http.request("PUT", url, options, httpConfig); + } + + static delete(url:string, options?:HttpOptions, httpConfig?:ParisHttpConfig):Observable{ + return Http.request("DELETE", url, options, httpConfig); + } + + static patch(url:string, options?:HttpOptions, httpConfig?:ParisHttpConfig):Observable{ + return Http.request("PATCH", url, options, httpConfig); + } + + static request(method:RequestMethod, url:string, options?:HttpOptions, httpConfig?:ParisHttpConfig):Observable { let fullUrl:string = options && options.params ? Http.addParamsToUrl(url, options.params) : url, - tmpError:Error = new Error(`Failed to GET from ${url}.`); + tmpError:Error = new Error(`Failed to ${method} from ${url}.`); + + if (options && options.data) { + httpConfig = httpConfig || {}; + if (!httpConfig.headers) + httpConfig.headers = {}; + + httpConfig.headers["Content-Type"] = "application/json"; + } return Observable.ajax(Object.assign({ - url: fullUrl + method: method, + url: fullUrl, + body: options && options.data }, Http.httpOptionsToRequestInit(options, httpConfig))) .map(e => e.response) .catch(() => { throw tmpError });