@qikdev/sdk 0.1.14 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,624 @@
1
+ import axios from 'axios';
2
+ import _ from 'lodash';
3
+
4
+ ///////////////////////////////////////////////////
5
+
6
+ /**
7
+ * Creates a new QikContent instance.
8
+ * This module provides a number of helper functions for creating, reading, updating and deleting content
9
+ * @alias content
10
+ * @constructor
11
+ * @hideconstructor
12
+ * @param {QikCore} qik A reference to the parent instance of the QikCore module. This module is usually created by a QikCore instance that passes itself in as the first argument.
13
+ */
14
+ export default function(qik) {
15
+
16
+ if (!qik.api) {
17
+ throw new Error(`Can't Instantiate QikContent before QikAPI exists`);
18
+ }
19
+
20
+ ///////////////////////////////////////////////////
21
+
22
+ var service = {
23
+ debug: false,
24
+ }
25
+
26
+ ///////////////////////////////////////////////////
27
+
28
+ /**
29
+ *
30
+ * Retrieves the glossary of content types available to the user
31
+ * @alias content.list
32
+ * @param {String} type The type or definition of records we want to retrieve
33
+ * @example
34
+ *
35
+ * QikContent.glossary()
36
+ */
37
+
38
+ const glossary = {}
39
+
40
+ service.glossary = async function(options) {
41
+ options = options || {};
42
+
43
+ var reload = options.refresh || options.reload || !glossary.data;
44
+
45
+ if (reload) {
46
+ console.log('reloading the glossary')
47
+ const { data } = await qik.api.get(`/glossary`);
48
+ glossary.data = data;
49
+ }
50
+
51
+ if (options.hash) {
52
+ var result = qik.utils.hash(glossary.data, 'key');
53
+ return result;
54
+ }
55
+
56
+ return glossary.data;
57
+ }
58
+
59
+ ///////////////////////////////////////////////////
60
+
61
+ /**
62
+ *
63
+ * Retrieves all available filter comparators for the various field types
64
+ * @alias content.comparators
65
+ * @example
66
+ *
67
+ * QikContent.comparators()
68
+ */
69
+
70
+ const comparators = {}
71
+
72
+ service.comparators = async function(options) {
73
+ options = options || {};
74
+
75
+ var reload = options.refresh || !comparators.data;
76
+
77
+ if (reload) {
78
+ const { data } = await qik.api.get(`/system/comparators`);
79
+
80
+
81
+ /////////////////////////////
82
+
83
+ data.available = {};
84
+
85
+ Object.entries(data.types).forEach(([key, value]) => {
86
+ data.available[key] = value.map(function(comparatorKey) {
87
+ return data.hash[comparatorKey];
88
+ })
89
+ });
90
+
91
+
92
+ /////////////////////////////
93
+
94
+ comparators.data = data;
95
+ }
96
+
97
+
98
+
99
+ // if (options.hash) {
100
+ // var result = qik.utils.hash(comparators.data, 'key');
101
+ // return result;
102
+ // }
103
+
104
+ return comparators.data;
105
+ }
106
+
107
+ ///////////////////////////////////////////////////
108
+
109
+
110
+ function getLimits(fieldDefinition) {
111
+
112
+ var { minimum, maximum } = fieldDefinition;
113
+ minimum = qik.utils.parseInt(minimum);
114
+ minimum = Math.max(minimum, 0);
115
+
116
+ maximum = qik.utils.parseInt(maximum);
117
+ maximum = Math.max(maximum, 0); // cant be less than 0
118
+ if (maximum == 0) {
119
+ //Unlimited maximum
120
+ } else {
121
+ maximum = Math.max(maximum, minimum); // cant be less than the minimum
122
+ }
123
+ return { minimum, maximum };
124
+ }
125
+
126
+
127
+ service.meetsValidationRequirements = function(input, fieldType, validationCriteria) {
128
+
129
+ if (validationCriteria.minLength) {
130
+ var stringLength = String(input).length;
131
+ if (stringLength < validationCriteria.minLength) {
132
+ return `must be at least ${validationCriteria.minLength} characters`;
133
+
134
+ }
135
+ }
136
+
137
+ if (validationCriteria.maxLength) {
138
+ var stringLength = String(input).length;
139
+ if (stringLength > validationCriteria.maxLength) {
140
+ return `must be less than ${validationCriteria.maxLength} characters`;
141
+
142
+ }
143
+ }
144
+
145
+ if (validationCriteria.minValue) {
146
+ var number = Number(input);
147
+ if (number < validationCriteria.minValue) {
148
+ return `must be greater than ${validationCriteria.minValue}`;
149
+
150
+ }
151
+ }
152
+
153
+ if (validationCriteria.maxValue) {
154
+ var number = Number(input);
155
+ if (number > validationCriteria.maxValue) {
156
+ return `must be less than ${validationCriteria.maxValue}`;
157
+
158
+ }
159
+ }
160
+ }
161
+
162
+ service.validateField = function(input, fieldDefinition, options) {
163
+
164
+ if (!options) {
165
+ options = {}
166
+ }
167
+
168
+ var fieldType = fieldDefinition.type || 'string';
169
+ var { minimum, maximum } = getLimits(fieldDefinition);
170
+
171
+ var isObject = (fieldDefinition.type == 'group' && fieldDefinition.asObject);
172
+ var singleValue = isObject ? minimum === maximum && maximum === 1 : maximum === 1;
173
+ var multiValue = !singleValue;
174
+
175
+ //////////////////
176
+
177
+ var isNumeric;
178
+ switch (fieldType) {
179
+ case 'integer':
180
+ case 'decimal':
181
+ case 'number':
182
+ case 'float':
183
+ isNumeric = true;
184
+ break;
185
+ }
186
+
187
+ //////////////////
188
+
189
+ var inputWasProvided = qik.utils.exists(input);
190
+
191
+ //If an answer is required
192
+ if (minimum) {
193
+ //but none was provided
194
+ if (!inputWasProvided) {
195
+ //Throw an error
196
+ return {
197
+ valid: false,
198
+ message: `${fieldDefinition.title} is a required field`,
199
+ status: 400,
200
+ }
201
+ }
202
+ } else {
203
+ //No answer is needed and none was provided
204
+ if (!inputWasProvided) {
205
+ return {
206
+ valid: true,
207
+ }
208
+ }
209
+ }
210
+
211
+ //////////////////
212
+
213
+
214
+ //If we are requiring multiple values
215
+ if (multiValue) {
216
+
217
+ //But the input is not an array
218
+ if (!Array.isArray(input)) {
219
+
220
+ if (minimum) {
221
+ return {
222
+ valid: false,
223
+ message: `${fieldDefinition.title} requires at least ${minimum} values`,
224
+ status: 400,
225
+ }
226
+ } else {
227
+ return {
228
+ valid: false,
229
+ message: `${fieldDefinition.title} must be provided as an array`,
230
+ status: 400,
231
+ }
232
+ }
233
+ }
234
+
235
+
236
+
237
+ ////////////////////////////
238
+
239
+ var compacted = input;
240
+
241
+ compacted = compacted.filter(function(v) {
242
+ //Check if we care about this value
243
+ var empty = v === undefined || v === null || v === '';
244
+ return !empty;
245
+ })
246
+
247
+
248
+ // console.log('COMPACTED', input.length, compacted.length);
249
+
250
+ ////////////////////////////
251
+
252
+ //We need an exact number of answers
253
+ if (minimum == maximum) {
254
+
255
+ //But we don't have the number of answers needed
256
+ if (minimum && (compacted.length != minimum)) {
257
+ return {
258
+ valid: false,
259
+ message: `${fieldDefinition.title} requires exactly ${maximum} values`,
260
+ status: 400,
261
+ }
262
+ }
263
+ }
264
+
265
+ //We have too many answers
266
+ if (maximum && (compacted.length > maximum)) {
267
+
268
+ return {
269
+ valid: false,
270
+ message: `${fieldDefinition.title} requires less than ${maximum+1} values`,
271
+ status: 400,
272
+ }
273
+ }
274
+
275
+ //We don't have enough answers
276
+ if (compacted.length < minimum) {
277
+
278
+ // console.log(fieldDefinition.title, 'INPUT', input.length, minimum, input, compacted)
279
+
280
+ return {
281
+ valid: false,
282
+ message: `${fieldDefinition.title} requires at least ${minimum} values`,
283
+ status: 400,
284
+ }
285
+ }
286
+
287
+ ////////////////////////////
288
+
289
+ var foundBadEntry;
290
+
291
+ //Find any bad values
292
+ var badEntry = compacted.find(function(val) {
293
+
294
+ var strict = options.strict;
295
+
296
+ var valueFieldType = fieldType;
297
+
298
+ if (fieldDefinition.type == 'group') {
299
+ valueFieldType = 'object';
300
+ }
301
+
302
+ var isValid = qik.utils.isValidValue(val, valueFieldType, strict);
303
+
304
+ ////////////////////////////
305
+
306
+ if (!isValid) {
307
+ foundBadEntry = true;
308
+ return true;
309
+ }
310
+
311
+ ////////////////////////////
312
+
313
+ //Is there additional validation requirements
314
+ if (fieldDefinition.validation) {
315
+ var additionalValidationErrors = service.meetsValidationRequirements(val, fieldType, fieldDefinition.validation)
316
+ if (additionalValidationErrors) {
317
+ foundBadEntry = true;
318
+ return true;
319
+ }
320
+ }
321
+ })
322
+
323
+ if (foundBadEntry) {
324
+
325
+ // var badValueMessage = `${badEntry || "''"} is not a valid ${fieldType} value for ${fieldDefinition.title}`;
326
+ // switch(fieldType) {
327
+ // case 'string':
328
+ // badValueMessage = `Empty or invalid value for ${fieldDefinition.title}`;
329
+ // break;
330
+ // }
331
+
332
+ console.log('BAD ENTRY', badEntry, fieldDefinition)
333
+ let badValueMessage = `Invalid input for ${fieldDefinition.title}`;
334
+ return {
335
+ valid: false,
336
+ message: badValueMessage,
337
+ status: 400,
338
+ }
339
+ }
340
+
341
+ } else {
342
+
343
+ //////////////////
344
+
345
+ var cleanedValue = getCleanedValue();
346
+
347
+
348
+ //////////////////
349
+
350
+ function getCleanedValue() {
351
+ switch (fieldType) {
352
+ case 'number':
353
+ case 'float':
354
+ case 'decimal':
355
+ if (!qik.utils.exists(input)) {
356
+ return undefined;
357
+ } else {
358
+ return Number(input);
359
+ }
360
+ break;
361
+ case 'integer':
362
+ if (!qik.utils.exists(input)) {
363
+ return undefined;
364
+ } else {
365
+ return parseInt(input);
366
+ }
367
+ break;
368
+ case 'boolean':
369
+ if (!qik.utils.exists(input)) {
370
+ return undefined;
371
+ }
372
+ return qik.utils.parseBoolean(input);
373
+ break;
374
+ case 'email':
375
+ return options.strict ? input : String(input).toLowerCase();
376
+ break;
377
+ case 'reference':
378
+ return options.strict ? input : qik.utils.id(input);
379
+ break;
380
+ default:
381
+ return options.strict ? input : qik.utils.cleanValue(input, fieldType, options);
382
+ break
383
+ }
384
+
385
+ return input;
386
+ }
387
+
388
+ //////////////////
389
+
390
+ var dataType = fieldType;
391
+ if (dataType == 'group') {
392
+ dataType = 'object';
393
+ }
394
+
395
+ //////////////////
396
+
397
+ var isValidValue = qik.utils.isValidValue(cleanedValue, dataType, options.strict);
398
+
399
+ //////////////////
400
+
401
+ //Invalid input
402
+ if (!isValidValue) {
403
+
404
+ console.log('FAILED', input, fieldType, cleanedValue, dataType, isValidValue)
405
+ return {
406
+ valid: false,
407
+ message: `Single value '${input}' is not a valid ${fieldType} for ${fieldDefinition.title}`,
408
+ criteria: {
409
+ isValidValue,
410
+ cleanedValue,
411
+ fieldType,
412
+ options,
413
+ },
414
+ status: 400,
415
+ }
416
+ }
417
+
418
+ //Is there additional validation requirements
419
+ if (fieldDefinition.validation) {
420
+ var additionalValidationError = service.meetsValidationRequirements(input, fieldType, fieldDefinition.validation)
421
+ if (additionalValidationError) {
422
+ return {
423
+ valid: false,
424
+ message: additionalValidationError,
425
+ status: 400,
426
+ };
427
+ }
428
+ }
429
+ }
430
+
431
+ //////////////////
432
+
433
+ return {
434
+ valid: true,
435
+ }
436
+
437
+ }
438
+
439
+
440
+
441
+
442
+ // service.validateFieldValue = function(value, field, errorLog, options) {
443
+ // options = options || {};
444
+ // errorLog = errorLog || [];
445
+
446
+
447
+ // ///////////////////////////////////////////
448
+
449
+ // let { key, type } = field;
450
+ // let { minimum, maximum } = = getLimits(field);
451
+ // let existingValue = value;
452
+
453
+ // let isLayout = field.type == 'group' && !field.asObject;
454
+
455
+ // if (isLayout) {
456
+ // var subFields = service.mapFields(field.fields, { depth: 1 });
457
+ // subFields.forEach(function(subField) {
458
+ // service.validateFieldValue(input, subField, errorLog, options);
459
+ // })
460
+ // } else {
461
+ // //If it's a group of fields
462
+ // if (isGroup && asObject) {
463
+ // //Get all the fields
464
+ // var subFields = service.mapFields(field.fields, { depth: 1 });
465
+
466
+ // //If the group is a single object
467
+ // if (maximum === 1) {
468
+ // var groupObjectInput = existingValue;
469
+
470
+ // //Loop through each field
471
+ // subFields.forEach(function(subField) {
472
+ // //Update the entry data
473
+ // service.validateFieldValue(groupObjectInput, subField, errorLog, options);
474
+ // })
475
+ // } else {
476
+
477
+ // //Generate a new array
478
+ // (existingValue || []).forEach(function(entry) {
479
+ // //Loop through each field
480
+ // subFields.forEach(function(subField) {
481
+ // //Update the entry data
482
+ // service.validateFieldValue(entry, subField, errorLog, options);
483
+ // })
484
+ // });
485
+ // }
486
+ // } else {
487
+ // //We're a standard field so just wash and set and move on
488
+ // var { valid, message } = service.validate(existingValue, field, options)
489
+
490
+ // if (!valid) {
491
+ // errorLog.push({
492
+ // field,
493
+ // message,
494
+ // })
495
+ // }
496
+ // }
497
+ // }
498
+ // }
499
+
500
+
501
+
502
+
503
+
504
+ ///////////////////////////////////////////////////
505
+
506
+ /**
507
+ *
508
+ * Retrieves a list of records matching the provided criteria
509
+ * @alias content.list
510
+ * @param {String} type The type or definition of records we want to retrieve
511
+ * @param {Object} options The options for our query
512
+ * @param {String} options.search Freeform text keywords
513
+ * @param {Object} options.sort How to sort the results
514
+ * @param {String} options.sort.key Which key to sort on
515
+ * @param {String} options.sort.direction Which direction to sort on
516
+ * @param {Object} options.sort.type What type of data is being sorted
517
+ * @param {Object} options.page Page configuration
518
+ * @param {Number} options.page.size Page size
519
+ * @param {Number} options.page.index Page index
520
+ * @param {Object} options.filter How to filter the results
521
+ * @example
522
+ *
523
+ *
524
+ * QikContent.list('profile', {
525
+ * search:'Jim',
526
+ * page:{
527
+ * size:50,
528
+ * index:2,
529
+ * },
530
+ * sort:{
531
+ * key:'age',
532
+ * direction:'asc',
533
+ * type:'integer',
534
+ * },
535
+ * filter:{
536
+ * operator:'and',
537
+ * filters:[{
538
+ * key:'age',
539
+ * comparator:'>',
540
+ * value:5,
541
+ * }],
542
+ * },
543
+ * })
544
+ */
545
+
546
+ service.list = async function(type, options, advanced) {
547
+
548
+
549
+ if (!advanced) {
550
+ advanced = {};
551
+ }
552
+
553
+ if (advanced.cancellable) {
554
+
555
+ if (!advanced.config) {
556
+ advanced.config = {}
557
+ }
558
+
559
+ //Create a cancel token
560
+ const CancelToken = qik.api.CancelToken;
561
+ const source = CancelToken.source();
562
+ advanced.config.cancelToken = source.token;
563
+
564
+ const promise = qik.api.post(`/content/${type}/list`, options, advanced.config);
565
+ return {
566
+ promise,
567
+ cancel(message) {
568
+ source.cancel(message || 'Operation canceled by the user.');
569
+ },
570
+ }
571
+
572
+ } else {
573
+ const { data } = await qik.api.post(`/content/${type}/list`, options, advanced.config);
574
+ return data;
575
+ }
576
+
577
+
578
+
579
+
580
+
581
+ }
582
+
583
+ ///////////////////////////////////////////////////
584
+
585
+ /**
586
+ *
587
+ * Create an item
588
+ * @alias content.create
589
+ * @param {String} type The type or definition of the record we want to create
590
+ * @param {Object} data The data for our new record
591
+ * @example
592
+ *
593
+ *
594
+ * QikContent.create('profile', {
595
+ * firstName:'Michael',
596
+ * lastName:'Jordan',
597
+ * gender:'male',
598
+ * meta:{
599
+ * scopes:['61eca4746971e75c1fc670cf'],
600
+ * }
601
+ * })
602
+ */
603
+
604
+ service.create = async function(type, input) {
605
+ const { data } = await qik.api.post(`/content/${type}/create`, input);
606
+ return data;
607
+ }
608
+
609
+ service.update = async function(type, id, input) {
610
+ id = qik.utils.id(id);
611
+ const { data } = await qik.api.put(`/content/${type}/${id}`, input);
612
+ return data;
613
+ }
614
+
615
+ service.patch = async function(type, id, input) {
616
+ id = qik.utils.id(id);
617
+ const { data } = await qik.api.patch(`/content/${type}/${id}`, input);
618
+ return data;
619
+ }
620
+
621
+
622
+ return service;
623
+
624
+ }