Skip to content

Commit 8e0076c

Browse files
committed
feat: Asset instance methods
1 parent 6579438 commit 8e0076c

File tree

4 files changed

+486
-31
lines changed

4 files changed

+486
-31
lines changed

lib/create-space-api.js

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,14 @@ export default function createSpaceApi ({
173173
* @param {object} data - Object representation of the Entry to be created
174174
* @return {Promise<Entities.Entry>} Promise for the newly created Entry
175175
* @example
176-
* space.createEntry({fields: {}})
176+
* space.createEntry({
177+
* fields: {
178+
* title: {
179+
* 'en-US': 'english content',
180+
* 'de-DE': 'german content'
181+
* }
182+
* }
183+
* })
177184
* .then(e => console.log(e))
178185
*/
179186
function createEntry (contentTypeId, data) {
@@ -194,7 +201,14 @@ export default function createSpaceApi ({
194201
* @param {object} data - Object representation of the Entry to be created
195202
* @return {Promise<Entities.Entry>} Promise for the newly created Entry
196203
* @example
197-
* space.createEntryWithId('post', {fields: {}})
204+
* space.createEntryWithId('post', {
205+
* fields: {
206+
* title: {
207+
* 'en-US': 'english content',
208+
* 'de-DE': 'german content'
209+
* }
210+
* }
211+
* })
198212
* .then(e => console.log(e))
199213
*/
200214
function createEntryWithId (id, contentTypeId, data) {
@@ -236,13 +250,28 @@ export default function createSpaceApi ({
236250
}
237251

238252
/**
239-
* Creates a Asset
253+
* Creates a Asset. After creation, call asset.processForLocale or asset.processForAllLocales to start asset processing.
240254
* @memberof ContentfulSpaceAPI
241255
* @see {Entities.Asset}
242-
* @param {object} data - Object representation of the Asset to be created
256+
* @param {object} data - Object representation of the Asset to be created. Note that the field object should have an upload property on asset creation, which will be removed and replaced with an url property when processing is finished.
243257
* @return {Promise<Entities.Asset>} Promise for the newly created Asset
244258
* @example
245-
* space.createAsset({fields: {}})
259+
* space.createAsset({fields: {
260+
* fields: {
261+
* file: {
262+
* 'en-US': {
263+
* contentType: 'image/jpeg',
264+
* fileName: 'filename_english.jpg',
265+
* upload: 'http://example.com/filename_original_en.jpg'
266+
* },
267+
* 'de-DE': {
268+
* contentType: 'image/jpeg',
269+
* fileName: 'filename_german.jpg',
270+
* upload: 'http://example.com/filename_original_de.jpg'
271+
* }
272+
* }
273+
* }
274+
* })
246275
* .then(e => console.log(e))
247276
*/
248277
function createAsset (data) {
@@ -251,14 +280,29 @@ export default function createSpaceApi ({
251280
}
252281

253282
/**
254-
* Creates a Asset with a specific id
283+
* Creates a Asset with a specific id. After creation, call asset.processForLocale or asset.processForAllLocales to start asset processing.
255284
* @memberof ContentfulSpaceAPI
256285
* @see {Entities.Asset}
257286
* @param {string} id - Asset ID
258-
* @param {object} data - Object representation of the Asset to be created
287+
* @param {object} data - Object representation of the Asset to be created. Note that the field object should have an upload property on asset creation, which will be removed and replaced with an url property when processing is finished.
259288
* @return {Promise<Entities.Asset>} Promise for the newly created Asset
260289
* @example
261-
* space.createAssetWithId('image', {fields: {}})
290+
* space.createAssetWithId('image', {
291+
* fields: {
292+
* file: {
293+
* 'en-US': {
294+
* contentType: 'image/jpeg',
295+
* fileName: 'filename_english.jpg',
296+
* upload: 'http://example.com/filename_original_en.jpg'
297+
* },
298+
* 'de-DE': {
299+
* contentType: 'image/jpeg',
300+
* fileName: 'filename_german.jpg',
301+
* upload: 'http://example.com/filename_original_de.jpg'
302+
* }
303+
* }
304+
* }
305+
* })
262306
* .then(e => console.log(e))
263307
*/
264308
function createAssetWithId (id, data) {

lib/entities/asset.js

Lines changed: 176 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,200 @@
1+
/**
2+
* Asset instances
3+
* @namespace Asset
4+
*/
15
import {cloneDeep} from 'lodash/lang'
26
import freezeSys from 'contentful-sdk-core/freeze-sys'
37
import enhanceWithMethods from '../enhance-with-methods'
48
import mixinToPlainObject from 'contentful-sdk-core/mixins/to-plain-object'
9+
import errorHandler from '../error-handler'
10+
import {
11+
createUpdateEntity,
12+
createDeleteEntity,
13+
createPublishEntity,
14+
createUnpublishEntity,
15+
createArchiveEntity,
16+
createUnarchiveEntity
17+
} from '../instance-actions'
518

6-
// Methods to add:
7-
// - update
8-
// - publish
9-
// - unpublish
10-
// - delete
11-
// - archive
12-
// - unarchive
13-
// - process (should check on it's own if the asset is processed and timeout, maybe allow timeout as parameter)
19+
const ASSET_PROCESSING_CHECK_WAIT = 500
20+
const ASSET_PROCESSING_CHECK_RETRIES = 5
1421

1522
/**
1623
* @memberof Entities
1724
* @typedef Asset
18-
* @prop {Entities.Sys} sys - Standard system metadata with additional entry specific properties
25+
* @prop {Entities.Sys} sys - Standard system metadata with additional asset specific properties
1926
* @prop {string=} sys.locale - If present, indicates the locale which this asset uses
2027
* @prop {Object} fields - Object with content for each field
2128
* @prop {string} fields.title - Title for this asset
2229
* @prop {string} fields.description - Description for this asset
2330
* @prop {Object} fields.file - File object for this asset
2431
* @prop {Object} fields.file.fileName - Name for the file
2532
* @prop {string} fields.file.contentType - Mime type for the file
26-
* @prop {string} fields.file.url - Url where the file is available at.
33+
* @prop {string=} fields.file.upload - Url where the file is available to be downloaded from, into the Contentful asset system. After the asset is processed this field is gone.
34+
* @prop {string=} fields.file.url - Url where the file is available at the Contentful media asset system. This field won't be available until the asset is processed.
2735
* @prop {Object} fields.file.details - Details for the file, depending on file type (example: image size in bytes, etc)
2836
* @prop {function(): Object} toPlainObject() - Returns this Asset as a plain JS object
2937
*/
3038

3139
function createAssetApi (http) {
32-
return {}
40+
function checkIfAssetHasUrl (resolve, reject, id, locale, checkCount = 0) {
41+
http.get('assets/' + id)
42+
.then((response) => wrapAsset(http, response.data), errorHandler)
43+
.then((asset) => {
44+
if (asset.fields.file[locale].url) {
45+
resolve(asset)
46+
} else if (checkCount === ASSET_PROCESSING_CHECK_RETRIES) {
47+
const error = new Error()
48+
error.name = 'AssetProcessingTimeout'
49+
error.message = 'Asset is taking longer then expected to process.'
50+
reject(error)
51+
} else {
52+
checkCount++
53+
setTimeout(
54+
() => checkIfAssetHasUrl(resolve, reject, id, locale, checkCount),
55+
ASSET_PROCESSING_CHECK_WAIT
56+
)
57+
}
58+
})
59+
}
60+
61+
function processForLocale (locale) {
62+
const assetId = this.sys.id
63+
return http.put('assets/' + this.sys.id + '/files/' + locale + '/process', null, {
64+
headers: {
65+
'X-Contentful-Version': this.sys.version
66+
}
67+
})
68+
.then(() => {
69+
return new Promise(
70+
(resolve, reject) => checkIfAssetHasUrl(resolve, reject, assetId, locale)
71+
)
72+
}, errorHandler)
73+
}
74+
75+
function processForAllLocales () {
76+
const self = this
77+
const locales = Object.keys(this.fields.file)
78+
return Promise.all(locales.map((locale) => processForLocale.call(self, locale)))
79+
.then((assets) => assets[0])
80+
}
81+
82+
return {
83+
/**
84+
* Sends an update to the server with any changes made to the object's properties
85+
* @memberof Asset
86+
* @func update
87+
* @return {Promise<Asset>} Object returned from the server with updated changes.
88+
* @example
89+
* contentType.fields.name['en-US'] = 'Blog Post'
90+
* asset.update()
91+
* .then(asset => console.log(asset.fields.name['en-US']))
92+
*/
93+
update: createUpdateEntity({
94+
http: http,
95+
entityPath: 'assets',
96+
wrapperMethod: wrapAsset
97+
}),
98+
99+
/**
100+
* Deletes this object on the server.
101+
* @memberof Asset
102+
* @func delete
103+
* @return {Promise} Promise for the deletion. It contains no data, but the Promise error case should be handled.
104+
* @example
105+
* asset.delete()
106+
* .catch(err => console.log(err))
107+
*/
108+
delete: createDeleteEntity({
109+
http: http,
110+
entityPath: 'assets'
111+
}),
112+
113+
/**
114+
* Publishes the object
115+
* @memberof Asset
116+
* @func publish
117+
* @return {Promise<Asset>} Object returned from the server with updated metadata.
118+
* @example
119+
* asset.publish()
120+
* .then(asset => console.log(asset.sys.publishedVersion))
121+
*/
122+
publish: createPublishEntity({
123+
http: http,
124+
entityPath: 'assets',
125+
wrapperMethod: wrapAsset
126+
}),
127+
128+
/**
129+
* Unpublishes the object
130+
* @memberof Asset
131+
* @func unpublish
132+
* @return {Promise<Asset>} Object returned from the server with updated metadata.
133+
* @example
134+
* asset.unpublish()
135+
* .then(asset => console.log(asset.sys))
136+
*/
137+
unpublish: createUnpublishEntity({
138+
http: http,
139+
entityPath: 'assets',
140+
wrapperMethod: wrapAsset
141+
}),
142+
143+
/**
144+
* Archives the object
145+
* @memberof Asset
146+
* @func archive
147+
* @return {Promise<Asset>} Object returned from the server with updated metadata.
148+
* @example
149+
* asset.archive()
150+
* .then(asset => console.log(asset.sys.archivedVersion))
151+
*/
152+
archive: createArchiveEntity({
153+
http: http,
154+
entityPath: 'assets',
155+
wrapperMethod: wrapAsset
156+
}),
157+
158+
/**
159+
* Unarchives the object
160+
* @memberof Asset
161+
* @func unarchive
162+
* @return {Promise<Asset>} Object returned from the server with updated metadata.
163+
* @example
164+
* asset.unarchive()
165+
* .then(asset => console.log(asset.sys))
166+
*/
167+
unarchive: createUnarchiveEntity({
168+
http: http,
169+
entityPath: 'assets',
170+
wrapperMethod: wrapAsset
171+
}),
172+
173+
/**
174+
* Triggers asset processing after an upload, for the file uploaded to a specific locale.
175+
* @memberof Asset
176+
* @func processForLocale
177+
* @param {string} locale - Locale which processing should be triggered for
178+
* @return {Promise<Asset>} Object returned from the server with updated metadata.
179+
* @throws {AssetProcessingTimeout} If the asset takes too long to process. If this happens, retrieve the asset again, and if the url property is available, then processing has succeeded. If not, your file might be damaged.
180+
* @example
181+
* asset.processForLocale('en-US')
182+
* .then(asset => console.log(asset.fields.file['en-US'].url))
183+
*/
184+
processForLocale: processForLocale,
185+
186+
/**
187+
* Triggers asset processing after an upload, for the files uploaded to all locales of an asset.
188+
* @memberof Asset
189+
* @func processForAllLocales
190+
* @return {Promise<Asset>} Object returned from the server with updated metadata.
191+
* @throws {AssetProcessingTimeout} If the asset takes too long to process. If this happens, retrieve the asset again, and if the url property is available, then processing has succeeded. If not, your file might be damaged.
192+
* @example
193+
* asset.processForAllLocales()
194+
* .then(asset => console.log(asset.fields.file['en-US'].url, asset.fields.file['de-DE'].url))
195+
*/
196+
processForAllLocales: processForAllLocales
197+
}
33198
}
34199

35200
/**

test/integration/asset.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,74 @@ export default function assetTests (t, space) {
2525
t.ok(response.items, 'items')
2626
})
2727
})
28+
29+
t.test('Create, process, update, publish, unpublish, archive, unarchive and delete asset', (t) => {
30+
t.plan(7)
31+
32+
return space.createAsset({fields: {
33+
title: {'en-US': 'this is the title'},
34+
file: {'en-US': {
35+
contentType: 'image/jpeg',
36+
fileName: 'shiba-stuck.jpg',
37+
upload: 'http://shiba.fr/wp-content/uploads/shiba-stuck-bush.jpg'
38+
}}
39+
}})
40+
.then((asset) => {
41+
t.equals(asset.fields.title['en-US'], 'this is the title', 'original title')
42+
return asset.processForLocale('en-US')
43+
.then((processedAsset) => {
44+
t.ok(processedAsset.fields.file['en-US'].url, 'file was uploaded')
45+
processedAsset.fields.title['en-US'] = 'title has changed'
46+
return processedAsset.update()
47+
.then((updatedAsset) => {
48+
t.equals(updatedAsset.fields.title['en-US'], 'title has changed', 'updated title')
49+
return updatedAsset.publish()
50+
.then((publishedAsset) => {
51+
t.ok(publishedAsset.sys.publishedVersion, 'has published version')
52+
return publishedAsset.unpublish()
53+
.then((unpublishedAsset) => {
54+
t.notOk(unpublishedAsset.sys.publishedVersion, 'published version is gone')
55+
return unpublishedAsset.archive()
56+
.then((archivedAsset) => {
57+
t.ok(archivedAsset.sys.archivedVersion, 'has archived version')
58+
return archivedAsset.unarchive()
59+
.then((unarchivedAsset) => {
60+
t.notOk(unarchivedAsset.sys.archivedVersion, 'archived version is gone')
61+
return unarchivedAsset.delete()
62+
})
63+
})
64+
})
65+
})
66+
})
67+
})
68+
})
69+
})
70+
71+
// TODO make sure locale is created beforehand
72+
t.test('Create and process asset with multiple locales', (t) => {
73+
t.plan(2)
74+
75+
return space.createAsset({fields: {
76+
title: {'en-US': 'this is the title'},
77+
file: {
78+
'en-US': {
79+
contentType: 'image/jpeg',
80+
fileName: 'shiba-stuck.jpg',
81+
upload: 'http://shiba.fr/wp-content/uploads/shiba-stuck-bush.jpg'
82+
},
83+
'de-DE': {
84+
contentType: 'image/jpeg',
85+
fileName: 'shiba-stuck.jpg',
86+
upload: 'http://shiba.fr/wp-content/uploads/shiba-stuck-bush.jpg'
87+
}
88+
}
89+
}})
90+
.then((asset) => {
91+
return asset.processForAllLocales()
92+
.then((processedAsset) => {
93+
t.ok(processedAsset.fields.file['en-US'].url, 'file en-US was uploaded')
94+
t.ok(processedAsset.fields.file['de-DE'].url, 'file de-DE was uploaded')
95+
})
96+
})
97+
})
2898
}

0 commit comments

Comments
 (0)