@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.
Files changed (47) hide show
  1. package/.babelrc +2 -1
  2. package/README.md +60 -0
  3. package/coverage/clover.xml +581 -430
  4. package/coverage/coverage-final.json +15 -9
  5. package/coverage/lcov-report/Ass.js.html +212 -212
  6. package/coverage/lcov-report/Asset.js.html +27 -27
  7. package/coverage/lcov-report/AssetType.js.html +98 -0
  8. package/coverage/lcov-report/Encode.js.html +98 -0
  9. package/coverage/lcov-report/File.js.html +98 -0
  10. package/coverage/lcov-report/Filename.js.html +9 -726
  11. package/coverage/lcov-report/Ingest.js.html +1239 -279
  12. package/coverage/lcov-report/IngestFile.js.html +5 -5
  13. package/coverage/lcov-report/Model.js.html +821 -0
  14. package/coverage/lcov-report/Order.js.html +25 -25
  15. package/coverage/lcov-report/Srt.js.html +102 -102
  16. package/coverage/lcov-report/Sub.js.html +43 -43
  17. package/coverage/lcov-report/Tag.js.html +98 -0
  18. package/coverage/lcov-report/TimeFormat.js.html +63 -63
  19. package/coverage/lcov-report/fileType.js.html +188 -0
  20. package/coverage/lcov-report/index.html +164 -86
  21. package/coverage/lcov.info +1159 -846
  22. package/dist/index.js +74 -22
  23. package/dist/src/Asset.js +10 -5
  24. package/dist/src/AssetType.js +54 -0
  25. package/dist/src/Encode.js +54 -0
  26. package/dist/src/File.js +54 -0
  27. package/dist/src/Filename.js +7 -228
  28. package/dist/src/Ingest.js +365 -33
  29. package/dist/src/IngestFile.js +6 -2
  30. package/dist/src/Model.js +569 -0
  31. package/dist/src/Order.js +20 -9
  32. package/dist/src/Srt.js +5 -1
  33. package/dist/src/Tag.js +54 -0
  34. package/dist/src/fileType.js +43 -0
  35. package/index.js +14 -0
  36. package/package.json +9 -3
  37. package/src/AssetType.js +11 -0
  38. package/src/Encode.js +11 -0
  39. package/src/File.js +11 -0
  40. package/src/Filename.js +0 -239
  41. package/src/Ingest.js +347 -27
  42. package/src/Model.js +252 -0
  43. package/src/Tag.js +11 -0
  44. package/src/fileType.js +41 -0
  45. package/test/Filename.spec.js +3 -707
  46. package/test/Ingest.spec.js +1107 -152
  47. 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
- * @param {*} info
11
- * @param {*} mediaType
277
+ *
12
278
  */
13
- static mapFromMediainfo(info, mediaType) {
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
- static mapFromFilename(filename, allTags) {
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 == 'SubDub') {
204
- if (foundTag.name == 'sub') {
474
+ if (foundTag.type == "SubDub") {
475
+ if (foundTag.name == "sub") {
205
476
  tags.SubDub.sub = true;
206
477
  }
207
- if (foundTag.name == 'dub') {
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 (filename.file.tags.includes('sub') && filename.file.tags.includes('dub')) {
226
- ingest.selected.preview.type = 'both';
227
- } else if (filename.file.tags.includes('sub')) {
228
- ingest.selected.preview.type = 'sub';
229
- } else if (filename.file.tags.includes('dub')) {
230
- ingest.selected.preview.type = 'dub';
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
- * Assembles Ingest based on weighted rules
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
- static assemble(params) {
548
+ assemble(params) {
549
+ let original = this.data();
550
+ let filename = this.data().filename;
551
+ let mediainfo = this.data().mediainfo;
247
552
 
248
- let { original, filename, mediainfo, order, watermarks = [] } = params;
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: 'Final',
265
- type: 'none',
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 = 'Unfinished';
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 = 'txtd';
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
- !!(tags.Codec == "prores" &&
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
+ }
package/src/Tag.js ADDED
@@ -0,0 +1,11 @@
1
+ import Model from "./Model";
2
+
3
+ export default class Tag extends Model {
4
+ constructor(data) {
5
+ super(data);
6
+ this._resource = 'tags';
7
+ }
8
+ static get _resource() {
9
+ return 'tags';
10
+ }
11
+ }