blob: deaed33279920310b6784633777c23aade1013e5 [file] [log] [blame]
Matteo Scandolo1aee1982017-02-17 08:33:23 -08001// TODO test me hard!!!
2
3import * as _ from 'lodash';
4import {IXosModeldefsService, IXosModeldef, IXosModelDefsField, IXosModelDefsRelation} from '../rest/modeldefs.rest';
5import {IXosTableCfg} from '../../core/table/table';
6import {IXosFormCfg} from '../../core/form/form';
7import {IXosNavigationService} from '../../core/services/navigation';
8import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
9import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
10import {IXosModelStoreService} from '../stores/model.store';
11
12export interface IXosModel {
13 name: string; // the model name
14 app: string; // the service to wich it belong
15 fields: IXosModelDefsField[];
16 relations?: IXosModelDefsRelation[];
17 backendUrl?: string; // the api endpoint
18 clientUrl?: string; // the view url
19 tableCfg?: IXosTableCfg;
20 formCfg?: IXosFormCfg;
21}
22
23// Service
24export interface IXosModelDiscovererService {
25 discover(): ng.IPromise<boolean>;
26 get(modelName: string): IXosModel;
27}
28
29export class XosModelDiscovererService implements IXosModelDiscovererService {
30 static $inject = [
31 '$log',
32 '$q',
33 'XosModelDefs',
34 'ConfigHelpers',
35 'XosRuntimeStates',
36 'XosNavigationService',
37 'XosModelStore'
38 ];
39 private xosModels: IXosModel[] = []; // list of augmented model definitions;
40 private xosServices: string[] = []; // list of loaded services
41
42 constructor (
43 private $log: ng.ILogService,
44 private $q: ng.IQService,
45 private XosModelDefs: IXosModeldefsService,
46 private ConfigHelpers: IXosConfigHelpersService,
47 private XosRuntimeStates: IXosRuntimeStatesService,
48 private XosNavigationService: IXosNavigationService,
49 private XosModelStore: IXosModelStoreService
50 ) {
51 }
52
53 public get(modelName: string): IXosModel|null {
54 return _.find(this.xosModels, m => m.name === modelName);
55 }
56
57 public discover() {
58 const d = this.$q.defer();
59
60 this.XosModelDefs.get()
61 .then((modelsDef: IXosModeldef[]) => {
62
63 const pArray = [];
64 _.forEach(modelsDef, (model: IXosModeldef) => {
65 let p = this.cacheModelEntries(model)
66 .then(model => {
67 return this.addState(model);
68 })
69 .then(model => {
70 return this.addNavItem(model);
71 })
72 .then(model => {
73 return this.getTableCfg(model);
74 })
75 .then(model => {
76 return this.getFormCfg(model);
77 })
78 .then(model => {
79 return this.storeModel(model);
80 })
81 .then(model => {
82 this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
83 return this.$q.resolve();
84 })
85 .catch(err => {
86 this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
87 // NOTE why this does not resolve?????
88 // return this.$q.resolve();
89 return this.$q.reject();
90 });
91 pArray.push(p);
92 });
93 this.$q.all(pArray)
94 .then(() => {
95 d.resolve(true);
96 this.$log.info('[XosModelDiscovererService] All models loaded!');
97 })
98 .catch(() => {
99 d.resolve(false);
100 });
101 });
102 return d.promise;
103 }
104
105 private serviceNameFromAppName(appName: string): string {
106 return appName.replace('services.', '');
107 }
108
109 private stateNameFromModel(model: IXosModel): string {
110 return `xos.${this.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
111 }
112
113 private getParentStateFromModel(model: IXosModel): string {
114 let parentState: string;
115 if (model.app === 'core') {
116 parentState = 'xos.core';
117 }
118 else {
119 const serviceName = this.addService(model);
120 parentState = `xos.${serviceName}`;
121 }
122 return parentState;
123 }
124
125 private getApiUrlFromModel(model: IXosModel): string {
126 if (model.app === 'core') {
127 return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
128 }
129 else {
130 const serviceName = this.serviceNameFromAppName(model.app);
131 return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
132 }
133 }
134
135 // add a service state and navigation item if it is not already there
136 private addService(model: IXosModel): string {
137 const serviceName: string = this.serviceNameFromAppName(model.app);
138 if (!_.find(this.xosServices, n => n === serviceName)) {
139 const serviceState = {
140 url: serviceName,
141 parent: 'xos',
142 abstract: true,
143 template: '<div ui-view></div>'
144 };
145 this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
146
147 this.XosNavigationService.add({
148 label: this.ConfigHelpers.toLabel(serviceName),
149 state: `xos.${serviceName}`,
150 });
151 this.xosServices.push(serviceName);
152 }
153 return serviceName;
154 }
155
156 private addState(model: IXosModel): ng.IPromise<IXosModel> {
157 const d = this.$q.defer();
158 const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
159 const state: IXosState = {
160 parent: this.getParentStateFromModel(model),
161 url: clientUrl,
162 params: {
163 id: null
164 },
165 data: {
166 model: model.name
167 },
168 component: 'xosCrud',
169 };
170 this.XosRuntimeStates.addState(
171 this.stateNameFromModel(model),
172 state
173 );
174
175 // extend model
176 model.clientUrl = `${this.serviceNameFromAppName(model.app)}${clientUrl}`;
177
178 d.resolve(model);
179 return d.promise;
180 }
181
182 private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
183 const d = this.$q.defer();
184
185 const stateName = this.stateNameFromModel(model);
186
187 const parentState: string = this.getParentStateFromModel(model);
188
189 this.XosNavigationService.add({
190 label: this.ConfigHelpers.pluralize(model.name),
191 state: stateName,
192 parent: parentState
193 });
194
195 d.resolve(model);
196
197 return d.promise;
198 }
199
200 private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
201 const d = this.$q.defer();
202
203 let called = false;
204 const apiUrl = this.getApiUrlFromModel(model);
205 this.XosModelStore.query(model.name, apiUrl)
206 .subscribe(
207 () => {
208 // skipping the first response as the observable gets created as an empty array
209 if (called) {
210 return d.resolve(model);
211 }
212 called = true;
213 },
214 err => {
215 d.reject(err);
216 }
217 );
218
219 return d.promise;
220 }
221
222 private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
223
224 const d = this.$q.defer();
225
226 const stateUrl = this.stateNameFromModel(model);
227
228 model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
229
230 d.resolve(model);
231
232 return d.promise;
233 }
234
235 private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
236
237 const d = this.$q.defer();
238
239 model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
240
241 d.resolve(model);
242
243 return d.promise;
244 }
245
246 private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
247
248 const d = this.$q.defer();
249
250 if (!_.find(this.xosModels, m => m.name === model.name)) {
251 this.xosModels.push(model);
252 }
253
254 d.resolve(model);
255
256 return d.promise;
257 }
258}