@pixwel/pixwel-sdk 2.0.0 → 2.1.0
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.
- package/.babelrc +2 -1
- package/README.md +60 -0
- package/coverage/clover.xml +581 -430
- package/coverage/coverage-final.json +15 -9
- package/coverage/lcov-report/Ass.js.html +212 -212
- package/coverage/lcov-report/Asset.js.html +27 -27
- package/coverage/lcov-report/AssetType.js.html +98 -0
- package/coverage/lcov-report/Encode.js.html +98 -0
- package/coverage/lcov-report/File.js.html +98 -0
- package/coverage/lcov-report/Filename.js.html +9 -726
- package/coverage/lcov-report/Ingest.js.html +1239 -279
- package/coverage/lcov-report/IngestFile.js.html +5 -5
- package/coverage/lcov-report/Model.js.html +821 -0
- package/coverage/lcov-report/Order.js.html +25 -25
- package/coverage/lcov-report/Srt.js.html +102 -102
- package/coverage/lcov-report/Sub.js.html +43 -43
- package/coverage/lcov-report/Tag.js.html +98 -0
- package/coverage/lcov-report/TimeFormat.js.html +63 -63
- package/coverage/lcov-report/fileType.js.html +188 -0
- package/coverage/lcov-report/index.html +164 -86
- package/coverage/lcov.info +1159 -846
- package/dist/index.js +74 -22
- package/dist/src/Asset.js +10 -5
- package/dist/src/AssetType.js +54 -0
- package/dist/src/Encode.js +54 -0
- package/dist/src/File.js +54 -0
- package/dist/src/Filename.js +7 -228
- package/dist/src/Ingest.js +365 -33
- package/dist/src/IngestFile.js +6 -2
- package/dist/src/Model.js +569 -0
- package/dist/src/Order.js +20 -9
- package/dist/src/Srt.js +5 -1
- package/dist/src/Tag.js +54 -0
- package/dist/src/fileType.js +43 -0
- package/index.js +14 -0
- package/package.json +9 -3
- package/src/AssetType.js +11 -0
- package/src/Encode.js +11 -0
- package/src/File.js +11 -0
- package/src/Filename.js +0 -239
- package/src/Ingest.js +347 -27
- package/src/Model.js +252 -0
- package/src/Tag.js +11 -0
- package/src/fileType.js +41 -0
- package/test/Filename.spec.js +3 -707
- package/test/Ingest.spec.js +1107 -152
- package/test/Model.spec.js +328 -0
package/src/Ingest.js
CHANGED
|
@@ -1,16 +1,285 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
|
+
import File from "./File";
|
|
3
|
+
import Model from "./Model";
|
|
4
|
+
import { mediaType } from "./fileType";
|
|
5
|
+
import { clone, keys, flatten, filter, values } from "ramda";
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* [media description]
|
|
5
9
|
* @type {[type]}
|
|
6
10
|
*/
|
|
7
|
-
export default class Ingest {
|
|
11
|
+
export default class Ingest extends Model {
|
|
12
|
+
constructor(data) {
|
|
13
|
+
super(data);
|
|
14
|
+
this._resource = "ingests";
|
|
15
|
+
}
|
|
16
|
+
static get _resource() {
|
|
17
|
+
return 'ingests';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns path given an ingest record
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
renderPath() {
|
|
24
|
+
let ingest = this.data();
|
|
25
|
+
if (!ingest.asset.project.studio.slug) {
|
|
26
|
+
throw new Error("missing studio slug");
|
|
27
|
+
}
|
|
28
|
+
if (!ingest.asset.project.slug) {
|
|
29
|
+
throw new Error("missing asset.project.slug");
|
|
30
|
+
}
|
|
31
|
+
if (!ingest.asset.project.slug) {
|
|
32
|
+
throw new Error("missing asset.project.slug");
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
ingest.asset.project.studio.slug,
|
|
36
|
+
ingest.asset.project.slug,
|
|
37
|
+
ingest.asset.type
|
|
38
|
+
].join("/");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a File from an Ingest object with path and filename
|
|
42
|
+
*
|
|
43
|
+
* @param {object} params
|
|
44
|
+
*/
|
|
45
|
+
renderFilename({ allTags, allTypes, order } = (params = {})) {
|
|
46
|
+
let asset = this.data().asset;
|
|
47
|
+
let options = this.data().selected.download;
|
|
48
|
+
let mediaType = this.data().asset.mediaType;
|
|
49
|
+
|
|
50
|
+
if (!asset.project) {
|
|
51
|
+
throw new Error("missing asset.project");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (asset.project && !asset.project.filePrefix) {
|
|
55
|
+
throw new Error("missing asset.project.filePrefix");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!asset.name) {
|
|
59
|
+
throw new Error("missing asset.name");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!asset.type) {
|
|
63
|
+
throw new Error("missing asset.type");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let defaults = {
|
|
67
|
+
language: null,
|
|
68
|
+
country: null,
|
|
69
|
+
version: null,
|
|
70
|
+
tags: {
|
|
71
|
+
Usage: null,
|
|
72
|
+
Aspect: null,
|
|
73
|
+
SubDub: {
|
|
74
|
+
sub: null,
|
|
75
|
+
dub: null
|
|
76
|
+
},
|
|
77
|
+
Resolution: null,
|
|
78
|
+
"Frame Mode": null,
|
|
79
|
+
Framerate: null,
|
|
80
|
+
Text: null,
|
|
81
|
+
Codec: null,
|
|
82
|
+
Extra: []
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
options = _.merge(defaults, options);
|
|
86
|
+
|
|
87
|
+
if (!options.language) {
|
|
88
|
+
throw new Error("missing options.language");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (mediaType && mediaType == "video" && !options.tags.Codec) {
|
|
92
|
+
throw new Error("missing options.tags.Codec");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (mediaType && mediaType == "video" && !options.tags.Resolution) {
|
|
96
|
+
throw new Error("missing options.tags.Resolution");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (mediaType && mediaType == "video" && !options.tags.Framerate) {
|
|
100
|
+
throw new Error("missing options.tags.Framerate");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (mediaType == "video" && !options.tags["Frame Mode"]) {
|
|
104
|
+
throw new Error("missing options.tags[Frame Mode]");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function assetType(asset, types) {
|
|
108
|
+
if (/trailer/.test(asset.type)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let matchingType = _.find(types, { name: asset.type });
|
|
113
|
+
if (!matchingType) {
|
|
114
|
+
throw new Error(`Asset type "${asset.type}" could not be found`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
matchingType.file || matchingType.name.replace(".", "-").toUpperCase()
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function assetTitle(asset) {
|
|
123
|
+
let prefix = "";
|
|
124
|
+
let name = asset.slug.toUpperCase().replace("INT-L", "INTL");
|
|
125
|
+
if (/trailer/.test(asset.type)) {
|
|
126
|
+
switch (asset.type) {
|
|
127
|
+
case "trailer.intl":
|
|
128
|
+
prefix = "ITR-";
|
|
129
|
+
name = name.replace(/INTL-TRAILER-/, "");
|
|
130
|
+
break;
|
|
131
|
+
case "trailer":
|
|
132
|
+
prefix = "TR-";
|
|
133
|
+
name = name.replace(/TRAILER-/, "");
|
|
134
|
+
break;
|
|
135
|
+
case "trailer.us":
|
|
136
|
+
prefix = "DTR-";
|
|
137
|
+
name = name.replace(/DOMESTIC-TRAILER-/, "");
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return prefix + name;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function runningTime(asset) {
|
|
145
|
+
let matches = asset.type.match(/^film-clip|^featurette/);
|
|
146
|
+
if (!matches) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let minutes = Math.floor(asset.length / 60);
|
|
151
|
+
let seconds = asset.length % 60;
|
|
152
|
+
|
|
153
|
+
let result = "";
|
|
154
|
+
|
|
155
|
+
if (minutes > 0) {
|
|
156
|
+
result += minutes + "min";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (seconds > 0) {
|
|
160
|
+
result += seconds + "sec";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function locale(country, language) {
|
|
167
|
+
if (language.includes("-")) {
|
|
168
|
+
country = "";
|
|
169
|
+
}
|
|
170
|
+
return [language, country]
|
|
171
|
+
.filter(str => {
|
|
172
|
+
return !!str && str !== "WW";
|
|
173
|
+
})
|
|
174
|
+
.join("-");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function textType(subDub, text) {
|
|
178
|
+
if (!subDub || (!subDub.sub && !subDub.dub)) {
|
|
179
|
+
return text;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let result = [];
|
|
183
|
+
if (subDub.sub) {
|
|
184
|
+
result.push("sub");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (subDub.dub) {
|
|
188
|
+
result.push("dub");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function matchSet(allTags, tags) {
|
|
195
|
+
let selectedTags = _(tags)
|
|
196
|
+
.values()
|
|
197
|
+
.flatten()
|
|
198
|
+
.value();
|
|
199
|
+
|
|
200
|
+
let setTag = _(allTags)
|
|
201
|
+
.filter("set")
|
|
202
|
+
.find(setTag => {
|
|
203
|
+
return _.difference(setTag.set, selectedTags).length === 0;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (setTag) {
|
|
207
|
+
return setTag.name;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let tags = options.tags,
|
|
212
|
+
parts = [
|
|
213
|
+
asset.project.filePrefix,
|
|
214
|
+
assetType(asset, allTypes),
|
|
215
|
+
assetTitle(asset),
|
|
216
|
+
runningTime(asset),
|
|
217
|
+
locale(options.country, options.language),
|
|
218
|
+
textType(tags.SubDub, tags.Text),
|
|
219
|
+
tags.Extra.join("_"),
|
|
220
|
+
options.version,
|
|
221
|
+
tags.Codec
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
parts = _(parts)
|
|
225
|
+
.flatten()
|
|
226
|
+
.filter()
|
|
227
|
+
.map(part => part.toUpperCase());
|
|
228
|
+
|
|
229
|
+
let matchedSet = matchSet(allTags, options.tags);
|
|
230
|
+
let requiredInfo = tags.Resolution && tags["Frame Mode"] && tags.Framerate;
|
|
231
|
+
|
|
232
|
+
if (matchedSet) {
|
|
233
|
+
parts = parts.push(matchedSet);
|
|
234
|
+
} else if (mediaType == "video" && tags.Resolution && !requiredInfo) {
|
|
235
|
+
parts = parts.push(tags.Resolution.replace(/\d+x/, ""));
|
|
236
|
+
} else if (mediaType == "image" && tags.Resolution) {
|
|
237
|
+
parts = parts.push(tags.Resolution);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let name = parts.join("_");
|
|
241
|
+
|
|
242
|
+
let hasResInfo = name.match(/\d+(i|p)/i);
|
|
243
|
+
let hasAllInfo = name.match(/\d+(i|p)\d+/i);
|
|
244
|
+
|
|
245
|
+
if (requiredInfo && !hasResInfo && !hasAllInfo) {
|
|
246
|
+
let res = tags.Resolution;
|
|
247
|
+
let encodeTag = [
|
|
248
|
+
hasResInfo ? "" : res.indexOf("x") ? res.replace(/\d+x/, "") : res,
|
|
249
|
+
res.match(/i|p/i) && !hasResInfo
|
|
250
|
+
? ""
|
|
251
|
+
: tags["Frame Mode"].toLowerCase().slice(0, 1),
|
|
252
|
+
tags.Framerate.replace(".", "").toUpperCase()
|
|
253
|
+
]
|
|
254
|
+
.filter(val => {
|
|
255
|
+
return val;
|
|
256
|
+
})
|
|
257
|
+
.join("");
|
|
258
|
+
|
|
259
|
+
let match = new RegExp(encodeTag, "i");
|
|
260
|
+
name = name.match(match)
|
|
261
|
+
? name.replace(match, encodeTag)
|
|
262
|
+
: name + "_" + encodeTag;
|
|
263
|
+
} else if (hasResInfo && !hasAllInfo && tags.Framerate) {
|
|
264
|
+
name += tags.Framerate.replace(".", "").toUpperCase();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return name;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Returns the mediaType of the ingest
|
|
271
|
+
*/
|
|
272
|
+
mediaType() {
|
|
273
|
+
return this.src.mediaType;
|
|
274
|
+
}
|
|
8
275
|
/**
|
|
9
276
|
* Maps mediainfo metadata into ingest Tag format
|
|
10
|
-
*
|
|
11
|
-
* @param {*} mediaType
|
|
277
|
+
*
|
|
12
278
|
*/
|
|
13
|
-
|
|
279
|
+
mapFromMediainfo() {
|
|
280
|
+
let info = this.mediainfo;
|
|
281
|
+
let mediaType = this.asset.mediaType;
|
|
282
|
+
|
|
14
283
|
let schema = {
|
|
15
284
|
Aspect: null,
|
|
16
285
|
Standard: null,
|
|
@@ -190,7 +459,9 @@ export default class Ingest {
|
|
|
190
459
|
* @param {*} filename
|
|
191
460
|
* @param {*} allTags
|
|
192
461
|
*/
|
|
193
|
-
|
|
462
|
+
mapFromFilename(params) {
|
|
463
|
+
let { allTags } = params;
|
|
464
|
+
let filename = this.filename;
|
|
194
465
|
let tags = {
|
|
195
466
|
SubDub: {
|
|
196
467
|
sub: null,
|
|
@@ -200,11 +471,11 @@ export default class Ingest {
|
|
|
200
471
|
filename.file.tags.forEach(tag => {
|
|
201
472
|
let foundTag = _.find(allTags, { name: tag });
|
|
202
473
|
if (foundTag) {
|
|
203
|
-
if (foundTag.type ==
|
|
204
|
-
if (foundTag.name ==
|
|
474
|
+
if (foundTag.type == "SubDub") {
|
|
475
|
+
if (foundTag.name == "sub") {
|
|
205
476
|
tags.SubDub.sub = true;
|
|
206
477
|
}
|
|
207
|
-
if (foundTag.name ==
|
|
478
|
+
if (foundTag.name == "dub") {
|
|
208
479
|
tags.SubDub.dub = true;
|
|
209
480
|
}
|
|
210
481
|
} else {
|
|
@@ -218,16 +489,18 @@ export default class Ingest {
|
|
|
218
489
|
download: {
|
|
219
490
|
tags: tags
|
|
220
491
|
},
|
|
221
|
-
preview: {
|
|
222
|
-
}
|
|
492
|
+
preview: {}
|
|
223
493
|
}
|
|
224
494
|
};
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
495
|
+
if (
|
|
496
|
+
filename.file.tags.includes("sub") &&
|
|
497
|
+
filename.file.tags.includes("dub")
|
|
498
|
+
) {
|
|
499
|
+
ingest.selected.preview.type = "both";
|
|
500
|
+
} else if (filename.file.tags.includes("sub")) {
|
|
501
|
+
ingest.selected.preview.type = "sub";
|
|
502
|
+
} else if (filename.file.tags.includes("dub")) {
|
|
503
|
+
ingest.selected.preview.type = "dub";
|
|
231
504
|
}
|
|
232
505
|
if (filename.file.language) {
|
|
233
506
|
ingest.selected.preview.language = filename.file.language;
|
|
@@ -239,13 +512,45 @@ export default class Ingest {
|
|
|
239
512
|
return ingest;
|
|
240
513
|
}
|
|
241
514
|
/**
|
|
242
|
-
*
|
|
515
|
+
* Create a File from an Ingest object
|
|
516
|
+
*
|
|
517
|
+
* @param {object} params
|
|
518
|
+
*/
|
|
519
|
+
createFile(params) {
|
|
520
|
+
let { allTags, allTypes, order } = params;
|
|
521
|
+
|
|
522
|
+
this.filename = this.mapFromFilename({ allTags });
|
|
523
|
+
this.mediainfo = this.mapFromMediainfo();
|
|
524
|
+
|
|
525
|
+
let ingest = this.assemble({ order });
|
|
526
|
+
|
|
527
|
+
let file = new File({
|
|
528
|
+
name: this.renderFilename({ allTags, allTypes }),
|
|
529
|
+
path: this.renderPath(),
|
|
530
|
+
asset: ingest.asset._id,
|
|
531
|
+
generator: 'ingest',
|
|
532
|
+
contextTag: ingest.context.tag,
|
|
533
|
+
language: ingest.selected.download.language,
|
|
534
|
+
country: ingest.selected.download.country,
|
|
535
|
+
tags: flatValues(ingest.selected.download.tags),
|
|
536
|
+
ingest: ingest._id,
|
|
537
|
+
workRequest: ingest.workRequest,
|
|
538
|
+
verified: true
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
return file;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Merges ingest metadata sources
|
|
243
545
|
*
|
|
244
546
|
* @param {object} params
|
|
245
547
|
*/
|
|
246
|
-
|
|
548
|
+
assemble(params) {
|
|
549
|
+
let original = this.data();
|
|
550
|
+
let filename = this.data().filename;
|
|
551
|
+
let mediainfo = this.data().mediainfo;
|
|
247
552
|
|
|
248
|
-
let {
|
|
553
|
+
let { order, watermarks = [] } = params;
|
|
249
554
|
|
|
250
555
|
let defaults = {
|
|
251
556
|
workRequest: order._id,
|
|
@@ -261,8 +566,8 @@ export default class Ingest {
|
|
|
261
566
|
},
|
|
262
567
|
preview: {
|
|
263
568
|
enabled: false,
|
|
264
|
-
revision:
|
|
265
|
-
type:
|
|
569
|
+
revision: "Final",
|
|
570
|
+
type: "none",
|
|
266
571
|
embargo: {
|
|
267
572
|
enabled: false
|
|
268
573
|
}
|
|
@@ -277,7 +582,7 @@ export default class Ingest {
|
|
|
277
582
|
let ingest = _.merge(defaults, filename, original, mediainfo);
|
|
278
583
|
|
|
279
584
|
if (original.asset && original.asset.creative) {
|
|
280
|
-
ingest.selected.preview.revision =
|
|
585
|
+
ingest.selected.preview.revision = "Unfinished";
|
|
281
586
|
}
|
|
282
587
|
|
|
283
588
|
if (!original.context.asset && filename.asset && filename.asset._id) {
|
|
@@ -306,13 +611,13 @@ export default class Ingest {
|
|
|
306
611
|
ingest.selected.download.language = order.language;
|
|
307
612
|
}
|
|
308
613
|
if (order._id) {
|
|
309
|
-
ingest.selected.download.tags.Text =
|
|
614
|
+
ingest.selected.download.tags.Text = "txtd";
|
|
310
615
|
}
|
|
311
616
|
if (order.isSub() || order.isDub()) {
|
|
312
617
|
ingest.selected.download.tags.SubDub = {
|
|
313
618
|
sub: order.isSub(),
|
|
314
619
|
dub: order.isDub()
|
|
315
|
-
}
|
|
620
|
+
};
|
|
316
621
|
}
|
|
317
622
|
if (order.isSub()) {
|
|
318
623
|
ingest.selected.preview.type = "sub";
|
|
@@ -328,12 +633,27 @@ export default class Ingest {
|
|
|
328
633
|
|
|
329
634
|
let tags = ingest.selected.download.tags;
|
|
330
635
|
ingest.selected.preview.enabled =
|
|
331
|
-
!!(
|
|
636
|
+
!!(
|
|
637
|
+
tags.Codec == "prores" &&
|
|
332
638
|
tags.Resolution.split("x")[1] == "1080" &&
|
|
333
639
|
(tags.Framerate == "23.98" || tags.Framerate == "25") &&
|
|
334
|
-
watermarks.length
|
|
335
|
-
false;
|
|
640
|
+
watermarks.length
|
|
641
|
+
) || false;
|
|
336
642
|
|
|
337
643
|
return ingest;
|
|
338
644
|
}
|
|
339
645
|
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Flattens ingest.selected.download.tags into a simple array
|
|
649
|
+
*
|
|
650
|
+
* @param {object} data
|
|
651
|
+
*/
|
|
652
|
+
function flatValues(data) {
|
|
653
|
+
let copy = clone(data);
|
|
654
|
+
if (copy.SubDub) {
|
|
655
|
+
copy.SubDub = keys(filter(val => !!val, copy.SubDub));
|
|
656
|
+
}
|
|
657
|
+
return flatten(values(copy));
|
|
658
|
+
}
|
|
659
|
+
|
package/src/Model.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { Pixwel } from "../index";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { cloneDeep } from "lodash";
|
|
4
|
+
import { diff } from "deep-object-diff";
|
|
5
|
+
import validate from "validate.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Model base class for reading and writing to an API
|
|
9
|
+
*
|
|
10
|
+
* - persistence
|
|
11
|
+
* - validation
|
|
12
|
+
* - schema / seal
|
|
13
|
+
* - API error response handling
|
|
14
|
+
* - API success response hanlding
|
|
15
|
+
* - throws Exceptions
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
var _bindings = {
|
|
20
|
+
find: [],
|
|
21
|
+
save: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This exists outside the class, solely because I have not
|
|
26
|
+
* figured out how to reference a static class method from an instance
|
|
27
|
+
* and also from a static function.
|
|
28
|
+
*/
|
|
29
|
+
function connection() {
|
|
30
|
+
return axios.create({
|
|
31
|
+
baseURL: Pixwel.endpoint,
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Basic ${Pixwel.credentials.token}`,
|
|
34
|
+
Accept: "application/json",
|
|
35
|
+
"Content-Type": "application/json"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export default class Model {
|
|
42
|
+
constructor(data) {
|
|
43
|
+
Object.assign(this, data);
|
|
44
|
+
this._name = 'model';
|
|
45
|
+
this._endpoint = Pixwel.endpoint;
|
|
46
|
+
this._api = connection();
|
|
47
|
+
this._lastState = {};
|
|
48
|
+
this._validationErrors = [];
|
|
49
|
+
this._constraints = {};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Adds a validation contraint
|
|
53
|
+
*
|
|
54
|
+
* @param {string} name
|
|
55
|
+
* @param {object} data
|
|
56
|
+
*/
|
|
57
|
+
addConstraint(name, data) {
|
|
58
|
+
this._constraints[name] = data;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validates models data against its contraints
|
|
62
|
+
*/
|
|
63
|
+
validates() {
|
|
64
|
+
let res = validate(this.data(), this._constraints);
|
|
65
|
+
if (res == undefined) {
|
|
66
|
+
this._validationErrors = [];
|
|
67
|
+
} else {
|
|
68
|
+
this._validationErrors = res;
|
|
69
|
+
}
|
|
70
|
+
return (res == undefined) ? true : false;
|
|
71
|
+
}
|
|
72
|
+
errors() {
|
|
73
|
+
return this._validationErrors;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Makes a request to the API
|
|
77
|
+
*
|
|
78
|
+
* @param {object} config
|
|
79
|
+
*/
|
|
80
|
+
async request(config) {
|
|
81
|
+
return this._api.request(config);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns data
|
|
85
|
+
*/
|
|
86
|
+
data() {
|
|
87
|
+
let keys = Object.keys(this).filter(key => {
|
|
88
|
+
// using underscore convention to not include private data
|
|
89
|
+
return key[0] != '_';
|
|
90
|
+
});
|
|
91
|
+
// deliberately adding _id back in even though it's "private"
|
|
92
|
+
keys.push('_id');
|
|
93
|
+
let data = {};
|
|
94
|
+
keys.forEach(key => {
|
|
95
|
+
data[key] = this[key];
|
|
96
|
+
});
|
|
97
|
+
return data;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Register a callback to a model method
|
|
101
|
+
*
|
|
102
|
+
* @param {string} method
|
|
103
|
+
* @param {function} callback
|
|
104
|
+
*/
|
|
105
|
+
static registerCallback(method, callback) {
|
|
106
|
+
_bindings[method].push(callback);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Creates or updates a resource depennding on the presence
|
|
110
|
+
* of _id
|
|
111
|
+
*
|
|
112
|
+
* @param {object} data
|
|
113
|
+
*/
|
|
114
|
+
async save(data = {}) {
|
|
115
|
+
let input = data.length ? data : this.data();
|
|
116
|
+
|
|
117
|
+
if (input._id) {
|
|
118
|
+
let inputDiff = diff(this._lastState, input);
|
|
119
|
+
this._lastState = await this.update(input._id, inputDiff);
|
|
120
|
+
} else {
|
|
121
|
+
this._lastState = await this.create(input);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// fires events with the result
|
|
125
|
+
_bindings.save.forEach(callback => {
|
|
126
|
+
callback(this._lastState);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return this._lastState;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Merges incoming data into the model data
|
|
133
|
+
*
|
|
134
|
+
* @param {object} data
|
|
135
|
+
*/
|
|
136
|
+
set(data) {
|
|
137
|
+
return Object.assign(this, data);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Returns a deep clone of the current object
|
|
141
|
+
*/
|
|
142
|
+
clone() {
|
|
143
|
+
return cloneDeep(this);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Fetches all objects in a collection given params
|
|
147
|
+
*
|
|
148
|
+
* @param {object} params
|
|
149
|
+
*/
|
|
150
|
+
static all(params = {}) {
|
|
151
|
+
return this.find(params);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Finds an object or collection of objects
|
|
155
|
+
*
|
|
156
|
+
* @param {object} params
|
|
157
|
+
*/
|
|
158
|
+
static async find(params = {}) {
|
|
159
|
+
let api = connection();
|
|
160
|
+
let url;
|
|
161
|
+
if (typeof params === 'string') {
|
|
162
|
+
let id = params;
|
|
163
|
+
url = `${this._resource}/${id}`;
|
|
164
|
+
params = {};
|
|
165
|
+
} else {
|
|
166
|
+
url = this._resource;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
var res = await api.get(url, {params});
|
|
171
|
+
} catch (e) {
|
|
172
|
+
throw new Error(`${e.message}: GET /${url} ${JSON.stringify(params)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// fires events with the result
|
|
176
|
+
_bindings.find.forEach(callback => {
|
|
177
|
+
callback(res.data);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return res.data;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates a resource
|
|
184
|
+
*
|
|
185
|
+
* @param {string} name
|
|
186
|
+
* @param {object} data
|
|
187
|
+
*/
|
|
188
|
+
async create(data) {
|
|
189
|
+
let url = `${this._endpoint}/${this._resource}`;
|
|
190
|
+
var res = await this._api.post(url, data, {
|
|
191
|
+
validateStatus: (status) => { return true }
|
|
192
|
+
});
|
|
193
|
+
switch (res.status) {
|
|
194
|
+
case 500:
|
|
195
|
+
throw new Error(`HTTP 500: POST ${url}: ${res.data.type} - ${res.data.message}`);
|
|
196
|
+
}
|
|
197
|
+
Object.assign(this, res.data);
|
|
198
|
+
return res.data;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Updates a resource
|
|
202
|
+
*
|
|
203
|
+
* @param {string} name
|
|
204
|
+
* @param {object} data
|
|
205
|
+
*/
|
|
206
|
+
async update(id = '', data) {
|
|
207
|
+
let currentId = id || this._id;
|
|
208
|
+
if (!currentId) {
|
|
209
|
+
throw new Error("unable to update record without an _id");
|
|
210
|
+
}
|
|
211
|
+
let url = `${this._endpoint}/${this._resource}/${currentId}`;
|
|
212
|
+
var res = await this._api.patch(url, data, {
|
|
213
|
+
validateStatus: (status) => { return true }
|
|
214
|
+
});
|
|
215
|
+
switch (res.status) {
|
|
216
|
+
case 500:
|
|
217
|
+
throw new Error(`HTTP 500: PATCH ${url}: ${res.data.type} - ${res.data.message}`);
|
|
218
|
+
case 400:
|
|
219
|
+
throw new Error(`HTTP 400: PATCH ${url}: ${res.data.info.message} - ${JSON.stringify(data)}`);
|
|
220
|
+
}
|
|
221
|
+
Object.assign(this, res.data);
|
|
222
|
+
return res.data;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Reads a resource
|
|
226
|
+
*
|
|
227
|
+
* @param {string} name
|
|
228
|
+
* @param {object} opts
|
|
229
|
+
*/
|
|
230
|
+
async read(id = '', opts = {}) {
|
|
231
|
+
let currentId = id || this._id;
|
|
232
|
+
if (!currentId) {
|
|
233
|
+
throw new Error("unable to read record without an _id");
|
|
234
|
+
}
|
|
235
|
+
let url = `${this._endpoint}/${this._resource}/${currentId}`;
|
|
236
|
+
return this._api.get(url, opts).then(res => res.data);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Deletes a resource
|
|
240
|
+
*
|
|
241
|
+
* @param {string} name
|
|
242
|
+
* @param {string} id
|
|
243
|
+
*/
|
|
244
|
+
async delete(id = '') {
|
|
245
|
+
let currentId = id || this._id;
|
|
246
|
+
if (!currentId) {
|
|
247
|
+
throw new Error("unable to delete record without an _id");
|
|
248
|
+
}
|
|
249
|
+
let url = `${this._endpoint}/${this._resource}/${id}`;
|
|
250
|
+
return this._api.delete(url).then(res => res.data);
|
|
251
|
+
}
|
|
252
|
+
}
|