@stackbit/cms-contentful 0.4.49-staging.1 → 0.4.49
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/dist/.tsbuildinfo +1 -1
- package/dist/content-poller.d.ts +13 -6
- package/dist/content-poller.d.ts.map +1 -1
- package/dist/content-poller.js +11 -10
- package/dist/content-poller.js.map +1 -1
- package/dist/contentful-api-client.d.ts +7 -4
- package/dist/contentful-api-client.d.ts.map +1 -1
- package/dist/contentful-api-client.js +26 -10
- package/dist/contentful-api-client.js.map +1 -1
- package/dist/contentful-content-source.d.ts +55 -4
- package/dist/contentful-content-source.d.ts.map +1 -1
- package/dist/contentful-content-source.js +89 -60
- package/dist/contentful-content-source.js.map +1 -1
- package/dist/utils.d.ts +0 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/content-poller.ts +30 -17
- package/src/contentful-api-client.ts +27 -9
- package/src/contentful-content-source.ts +148 -68
- package/src/utils.ts +0 -6
|
@@ -39,11 +39,17 @@ interface PlainApiClientOptions {
|
|
|
39
39
|
accessToken: string;
|
|
40
40
|
spaceId: string;
|
|
41
41
|
environment: string;
|
|
42
|
+
managementHost?: string;
|
|
43
|
+
uploadHost?: string;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
export function createPlainApiClient({ accessToken, spaceId, environment }: PlainApiClientOptions) {
|
|
46
|
+
export function createPlainApiClient({ accessToken, spaceId, environment, managementHost, uploadHost }: PlainApiClientOptions) {
|
|
45
47
|
return createClient(
|
|
46
|
-
{
|
|
48
|
+
{
|
|
49
|
+
accessToken: accessToken,
|
|
50
|
+
...(managementHost ? { host: managementHost } : null),
|
|
51
|
+
...(uploadHost ? { hostUpload: uploadHost } : null)
|
|
52
|
+
},
|
|
47
53
|
{
|
|
48
54
|
type: 'plain',
|
|
49
55
|
defaults: {
|
|
@@ -178,7 +184,7 @@ export async function fetchEntriesUpdatedAfter(client: PlainClientAPI, date: str
|
|
|
178
184
|
return fetchAllItems<EntryProps>({
|
|
179
185
|
getMany: client.entry.getMany,
|
|
180
186
|
logger,
|
|
181
|
-
|
|
187
|
+
query: {
|
|
182
188
|
'sys.updatedAt[gt]': date
|
|
183
189
|
}
|
|
184
190
|
});
|
|
@@ -192,7 +198,7 @@ export async function fetchAssetsUpdatedAfter(client: PlainClientAPI, date: stri
|
|
|
192
198
|
return fetchAllItems<AssetProps>({
|
|
193
199
|
getMany: client.asset.getMany,
|
|
194
200
|
logger,
|
|
195
|
-
|
|
201
|
+
query: {
|
|
196
202
|
'sys.updatedAt[gt]': date
|
|
197
203
|
}
|
|
198
204
|
});
|
|
@@ -210,11 +216,23 @@ export async function fetchAllContentTypes(client: PlainClientAPI, logger?: Logg
|
|
|
210
216
|
return fetchAllItems<ContentTypeProps>({ getMany: client.contentType.getMany, logger });
|
|
211
217
|
}
|
|
212
218
|
|
|
213
|
-
export async function
|
|
219
|
+
export async function hasContentTypesUpdatedAfter(client: PlainClientAPI, date: string, logger?: Logger): Promise<boolean> {
|
|
220
|
+
const updatedContentTypes = await fetchAllItems<ContentTypeProps>({
|
|
221
|
+
getMany: client.contentType.getMany,
|
|
222
|
+
logger,
|
|
223
|
+
query: {
|
|
224
|
+
'sys.updatedAt[gt]': date,
|
|
225
|
+
limit: 1
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return updatedContentTypes.length > 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function fetchContentTypesUpdatedAfter(client: PlainClientAPI, date: string, logger?: Logger) {
|
|
214
232
|
return fetchAllItems<ContentTypeProps>({
|
|
215
233
|
getMany: client.contentType.getMany,
|
|
216
234
|
logger,
|
|
217
|
-
|
|
235
|
+
query: {
|
|
218
236
|
'sys.updatedAt[gt]': date
|
|
219
237
|
}
|
|
220
238
|
});
|
|
@@ -366,11 +384,11 @@ export async function getScheduledAction(client: PlainClientAPI, scheduledAction
|
|
|
366
384
|
export async function fetchAllItems<T>({
|
|
367
385
|
getMany,
|
|
368
386
|
logger,
|
|
369
|
-
|
|
387
|
+
query
|
|
370
388
|
}: {
|
|
371
389
|
getMany: (params: QueryParams) => Promise<CollectionProp<T>>;
|
|
372
390
|
logger?: Logger;
|
|
373
|
-
|
|
391
|
+
query?: QueryOptions;
|
|
374
392
|
}): Promise<T[]> {
|
|
375
393
|
let limit = 1000;
|
|
376
394
|
let items: T[] = [];
|
|
@@ -382,7 +400,7 @@ export async function fetchAllItems<T>({
|
|
|
382
400
|
query: {
|
|
383
401
|
skip: items.length,
|
|
384
402
|
limit: limit,
|
|
385
|
-
...
|
|
403
|
+
...query
|
|
386
404
|
}
|
|
387
405
|
});
|
|
388
406
|
items = items.concat(result.items);
|
|
@@ -76,10 +76,10 @@ import {
|
|
|
76
76
|
convertDocumentVersions
|
|
77
77
|
} from './contentful-entries-converter';
|
|
78
78
|
import { convertAndFilterScheduledActions } from './contentful-scheduled-actions-converter';
|
|
79
|
-
import { ContentPoller,
|
|
79
|
+
import { ContentPoller, ContentPollerSyncResult, ContentPollerSyncContext } from './content-poller';
|
|
80
80
|
import { Readable } from 'stream';
|
|
81
81
|
import { CONTENTFUL_BUILT_IN_IMAGE_SOURCES, CONTENTFUL_BYNDER_APP, CONTENTFUL_CLOUDINARY_APP } from './contentful-consts';
|
|
82
|
-
import { getLastUpdatedEntityDate
|
|
82
|
+
import { getLastUpdatedEntityDate } from './utils';
|
|
83
83
|
|
|
84
84
|
export interface ContentSourceOptions {
|
|
85
85
|
/** Contentful Space ID */
|
|
@@ -103,13 +103,50 @@ export interface ContentSourceOptions {
|
|
|
103
103
|
* Use webhook for content updates.
|
|
104
104
|
*/
|
|
105
105
|
useWebhookForContentUpdates?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Use accessToken instead of userContext.accessToken for write operations.
|
|
108
|
+
*/
|
|
109
|
+
useAccessTokenForUpdates?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Switches the ContentfulContentSource to work with EU data regions.
|
|
112
|
+
* When set to true, the following updated accordingly:
|
|
113
|
+
* previewHost: 'preview.eu.contentful.com'
|
|
114
|
+
* manageHost: 'api.eu.contentful.com'
|
|
115
|
+
* uploadHost: 'upload.eu.contentful.com'
|
|
116
|
+
*/
|
|
117
|
+
useEURegion?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The host of the Contentful's preview API.
|
|
120
|
+
* By default, this value is set to 'preview.contentful.com'.
|
|
121
|
+
* If `useEURegion` is set, uses 'preview.eu.contentful.com'.
|
|
122
|
+
*/
|
|
123
|
+
previewHost?: string;
|
|
124
|
+
/**
|
|
125
|
+
* The host of the Contentful's management API.
|
|
126
|
+
* By default, this value is `undefined` which makes the contentful-management
|
|
127
|
+
* use the 'api.contentful.com'.
|
|
128
|
+
* If `useEURegion` is set, this value is set to 'api.eu.contentful.com'.
|
|
129
|
+
*/
|
|
130
|
+
managementHost?: string;
|
|
131
|
+
/**
|
|
132
|
+
* The host used to upload assets to Contentful.
|
|
133
|
+
* By default, this value is `undefined` which makes the contentful-management
|
|
134
|
+
* use the 'upload.contentful.com'.
|
|
135
|
+
* If `useEURegion` is set, this value is set to 'upload.eu.contentful.com'.
|
|
136
|
+
*/
|
|
137
|
+
uploadHost?: string;
|
|
106
138
|
}
|
|
107
139
|
|
|
108
140
|
type UserContext = {
|
|
141
|
+
// The user's accessToken is provided by the "Netlify Create" Contentful
|
|
142
|
+
// OAuth Application when users connect their Netlify Create and Contentful
|
|
143
|
+
// accounts via OAuth flow.
|
|
109
144
|
accessToken: string;
|
|
110
145
|
};
|
|
111
146
|
|
|
112
|
-
export type SchemaContext =
|
|
147
|
+
export type SchemaContext = {
|
|
148
|
+
lastUpdatedContentTypeDate?: string;
|
|
149
|
+
};
|
|
113
150
|
|
|
114
151
|
type EntityError = {
|
|
115
152
|
name: string;
|
|
@@ -129,6 +166,11 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
129
166
|
private localDev!: boolean;
|
|
130
167
|
private contentPoller: ContentPoller | null = null;
|
|
131
168
|
private useWebhookForContentUpdates: boolean;
|
|
169
|
+
private useAccessTokenForUpdates: boolean;
|
|
170
|
+
private useEURegion: boolean;
|
|
171
|
+
private previewHost?: string;
|
|
172
|
+
private managementHost?: string;
|
|
173
|
+
private uploadHost?: string;
|
|
132
174
|
private locales: LocaleProps[] = [];
|
|
133
175
|
private defaultLocale?: LocaleProps;
|
|
134
176
|
private userMap: Record<string, UserProps> = {};
|
|
@@ -138,7 +180,6 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
138
180
|
private webhookUrl?: string;
|
|
139
181
|
private devAppRestartNeeded?: () => void;
|
|
140
182
|
private cache!: ContentSourceTypes.Cache<SchemaContext, DocumentContext, AssetContext>;
|
|
141
|
-
private syncContext!: SyncContext;
|
|
142
183
|
private taskQueue: TaskQueue;
|
|
143
184
|
|
|
144
185
|
constructor(options: ContentSourceOptions) {
|
|
@@ -148,6 +189,11 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
148
189
|
this.previewToken = options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;
|
|
149
190
|
this.useLocalizedAssetFields = options.useLocalizedAssetFields ?? false;
|
|
150
191
|
this.useWebhookForContentUpdates = options.useWebhookForContentUpdates ?? false;
|
|
192
|
+
this.useAccessTokenForUpdates = options.useAccessTokenForUpdates ?? false;
|
|
193
|
+
this.useEURegion = options.useEURegion ?? false;
|
|
194
|
+
this.previewHost = options.previewHost ?? (this.useEURegion ? 'preview.eu.contentful.com' : undefined);
|
|
195
|
+
this.managementHost = options.managementHost ?? (this.useEURegion ? 'api.eu.contentful.com' : undefined);
|
|
196
|
+
this.uploadHost = options.uploadHost ?? (this.useEURegion ? 'upload.eu.contentful.com' : undefined);
|
|
151
197
|
// Max active bulk actions per space is limited to 5.
|
|
152
198
|
// Max of 200 items per bulk action.
|
|
153
199
|
this.taskQueue = new TaskQueue({ limit: 5 });
|
|
@@ -158,7 +204,7 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
158
204
|
}
|
|
159
205
|
|
|
160
206
|
getContentSourceType(): string {
|
|
161
|
-
return 'contentful';
|
|
207
|
+
return this.useEURegion ? 'contentful-eu' : 'contentful';
|
|
162
208
|
}
|
|
163
209
|
|
|
164
210
|
getProjectId(): string {
|
|
@@ -197,12 +243,12 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
197
243
|
this.userLogger.info('Using webhook for content updates');
|
|
198
244
|
}
|
|
199
245
|
|
|
200
|
-
this.syncContext = ((await this.cache.get('syncContext')) as SyncContext) ?? {};
|
|
201
|
-
|
|
202
246
|
this.plainClient = createPlainApiClient({
|
|
203
247
|
spaceId: this.spaceId,
|
|
204
248
|
accessToken: this.accessToken,
|
|
205
|
-
environment: this.environment
|
|
249
|
+
environment: this.environment,
|
|
250
|
+
managementHost: this.managementHost,
|
|
251
|
+
uploadHost: this.uploadHost
|
|
206
252
|
});
|
|
207
253
|
|
|
208
254
|
await this.validateConfig();
|
|
@@ -217,15 +263,6 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
217
263
|
const appInstallations: AppInstallationProps[] = await fetchAllAppInstallations(this.plainClient);
|
|
218
264
|
await this.fetchUsers();
|
|
219
265
|
|
|
220
|
-
// The contentPoller updates its syncContext property internally when
|
|
221
|
-
// it gets an updated content. But, the actual cached data is not
|
|
222
|
-
// updated. Therefore, when content source module is restarted, we need
|
|
223
|
-
// to reset the contentPoller's internal syncContext, so it will be
|
|
224
|
-
// able to fetch all the updated content from its cached state.
|
|
225
|
-
if (this.contentPoller && this.contentPoller.pollType === 'date') {
|
|
226
|
-
this.contentPoller.setSyncContext(this.syncContext);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
266
|
// replace all data at once in atomic action
|
|
230
267
|
this.locales = locales.filter((locale) => {
|
|
231
268
|
// filter out disabled locales
|
|
@@ -338,12 +375,15 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
338
375
|
environment: this.environment,
|
|
339
376
|
previewToken: this.previewToken,
|
|
340
377
|
managementToken: this.accessToken,
|
|
378
|
+
previewHost: this.previewHost,
|
|
379
|
+
managementHost: this.managementHost,
|
|
380
|
+
uploadHost: this.uploadHost,
|
|
341
381
|
pollType: 'date',
|
|
342
|
-
syncContext: this.
|
|
382
|
+
syncContext: this.getSyncContextForContentPollerFromCache(),
|
|
343
383
|
notificationCallback: async (syncResult) => {
|
|
344
384
|
if (syncResult.contentTypes.length) {
|
|
345
385
|
this.logger.debug('content type was changed, invalidate schema');
|
|
346
|
-
this.cache.invalidateSchema();
|
|
386
|
+
await this.cache.invalidateSchema();
|
|
347
387
|
} else {
|
|
348
388
|
const result = await this.convertSyncResult(syncResult);
|
|
349
389
|
await this.cache.updateContent(result);
|
|
@@ -367,7 +407,32 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
367
407
|
this.userMap = _.keyBy(users, 'sys.id');
|
|
368
408
|
}
|
|
369
409
|
|
|
370
|
-
private async
|
|
410
|
+
private async fetchUsersIfNeeded(entities: (EntryProps | AssetProps)[]) {
|
|
411
|
+
const entityHasNoCachedAuthor = entities.some((entity) => !this.userMap[entity.sys.updatedBy?.sys.id ?? '']);
|
|
412
|
+
if (entityHasNoCachedAuthor) {
|
|
413
|
+
await this.fetchUsers();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private updateContentPollerSyncContext(syncContext: ContentPollerSyncContext) {
|
|
418
|
+
if (this.contentPoller && this.contentPoller.pollType === 'date') {
|
|
419
|
+
this.contentPoller.setSyncContext({
|
|
420
|
+
...this.getSyncContextForContentPollerFromCache(),
|
|
421
|
+
...syncContext
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private getSyncContextForContentPollerFromCache() {
|
|
427
|
+
const cacheSyncContext = this.cache.getSyncContext();
|
|
428
|
+
return {
|
|
429
|
+
lastUpdatedEntryDate: cacheSyncContext.documentsSyncContext as string | undefined,
|
|
430
|
+
lastUpdatedAssetDate: cacheSyncContext.assetsSyncContext as string | undefined,
|
|
431
|
+
lastUpdatedContentTypeDate: this.cache.getSchema().context?.lastUpdatedContentTypeDate
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async convertSyncResult(syncResult: ContentPollerSyncResult) {
|
|
371
436
|
// remove deleted entries and assets from fieldData
|
|
372
437
|
// generally, the "sync" method of the preview API never notifies of deleted objects, therefore we rely on
|
|
373
438
|
// the deleteObject method to notify the user that restart is needed. Then, once user restarts the SSG, it
|
|
@@ -380,11 +445,7 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
380
445
|
deletedAssets: syncResult.deletedAssets.length
|
|
381
446
|
});
|
|
382
447
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (resultHasNoCachedAuthor) {
|
|
386
|
-
await this.fetchUsers();
|
|
387
|
-
}
|
|
448
|
+
await this.fetchUsersIfNeeded([...syncResult.entries, ...syncResult.assets]);
|
|
388
449
|
|
|
389
450
|
// convert updated/created entries and assets
|
|
390
451
|
const documents = this.convertEntries(syncResult.entries, this.cache.getModelByName);
|
|
@@ -413,94 +474,111 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
413
474
|
bynderImagesAsList: this.bynderImagesAsList
|
|
414
475
|
});
|
|
415
476
|
|
|
477
|
+
const prevLastUpdatedContentTypeDate = this.cache.getSchema().context?.lastUpdatedContentTypeDate;
|
|
478
|
+
|
|
416
479
|
// Check if one of the content types was changed from the last time the
|
|
417
480
|
// content types were fetched, in which case remove all cached content.
|
|
418
481
|
const lastUpdatedContentTypeDate = getLastUpdatedEntityDate(contentTypes);
|
|
419
|
-
if (
|
|
482
|
+
if (prevLastUpdatedContentTypeDate !== lastUpdatedContentTypeDate) {
|
|
420
483
|
this.logger.debug(
|
|
421
|
-
`last updated content type date '${lastUpdatedContentTypeDate}' is different
|
|
422
|
-
`
|
|
484
|
+
`last updated content type date '${lastUpdatedContentTypeDate}' is different ` +
|
|
485
|
+
`from the cached date '${prevLastUpdatedContentTypeDate}', clearing cache`
|
|
423
486
|
);
|
|
424
|
-
await this.cache.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
487
|
+
await this.cache.clearSyncContext({
|
|
488
|
+
clearDocumentsSyncContext: true,
|
|
489
|
+
clearAssetsSyncContext: false
|
|
490
|
+
});
|
|
428
491
|
}
|
|
429
492
|
|
|
493
|
+
this.updateContentPollerSyncContext({ lastUpdatedContentTypeDate });
|
|
494
|
+
|
|
430
495
|
return {
|
|
431
496
|
models,
|
|
432
497
|
locales: this.locales.map((locale) => ({
|
|
433
498
|
code: locale.code,
|
|
434
499
|
default: locale.default
|
|
435
500
|
})),
|
|
436
|
-
context: {
|
|
501
|
+
context: {
|
|
502
|
+
lastUpdatedContentTypeDate: lastUpdatedContentTypeDate
|
|
503
|
+
}
|
|
437
504
|
};
|
|
438
505
|
}
|
|
439
506
|
|
|
440
|
-
async getDocuments(): Promise<ContextualDocument[]> {
|
|
507
|
+
async getDocuments(options?: { syncContext?: string }): Promise<ContextualDocument[] | { documents: ContextualDocument[]; syncContext?: string }> {
|
|
441
508
|
this.logger.debug('getDocuments');
|
|
509
|
+
let lastUpdatedEntryDate = options?.syncContext;
|
|
442
510
|
let entries: EntryProps[];
|
|
443
|
-
const cachedEntries = ((await this.cache.get('entries')) as EntryProps[]) ?? [];
|
|
444
511
|
try {
|
|
445
|
-
if (
|
|
446
|
-
this.logger.debug(`
|
|
447
|
-
|
|
448
|
-
this.logger.debug(`got ${
|
|
449
|
-
if (
|
|
450
|
-
|
|
512
|
+
if (lastUpdatedEntryDate) {
|
|
513
|
+
this.logger.debug(`fetching entries updated after ${lastUpdatedEntryDate}`);
|
|
514
|
+
entries = await fetchEntriesUpdatedAfter(this.plainClient, lastUpdatedEntryDate, this.userLogger);
|
|
515
|
+
this.logger.debug(`got ${entries.length} updated/created entries after ${lastUpdatedEntryDate}`);
|
|
516
|
+
if (entries.length) {
|
|
517
|
+
lastUpdatedEntryDate = getLastUpdatedEntityDate(entries);
|
|
451
518
|
}
|
|
452
|
-
entries = _.unionBy(updatedEntries, cachedEntries, 'sys.id');
|
|
453
519
|
} else {
|
|
454
520
|
entries = await fetchAllEntries(this.plainClient, this.userLogger);
|
|
455
|
-
|
|
521
|
+
lastUpdatedEntryDate = getLastUpdatedEntityDate(entries);
|
|
456
522
|
}
|
|
457
523
|
} catch (error: any) {
|
|
458
524
|
// Stackbit won't be able to work properly even if one of the entries was not fetched.
|
|
459
525
|
// All fetch methods use Contentful's API client that handles errors and retries automatically.
|
|
460
526
|
this.logger.error(`Failed fetching documents from Contentful, error: ${error.message}`);
|
|
461
|
-
|
|
527
|
+
// By returning the original syncContext we are ensuring that next
|
|
528
|
+
// time the getDocuments is called, it will try to get the documents
|
|
529
|
+
// using the same syncContext
|
|
530
|
+
return { documents: [], syncContext: lastUpdatedEntryDate };
|
|
462
531
|
}
|
|
463
|
-
|
|
464
|
-
|
|
532
|
+
|
|
533
|
+
this.updateContentPollerSyncContext({ lastUpdatedEntryDate });
|
|
534
|
+
|
|
465
535
|
this.logger.debug(
|
|
466
|
-
`got ${entries.length} entries from space ${this.spaceId}, environment ${this.environment}, lastUpdatedEntryDate: ${
|
|
536
|
+
`got ${entries.length} entries from space ${this.spaceId}, environment ${this.environment}, lastUpdatedEntryDate: ${lastUpdatedEntryDate}`
|
|
467
537
|
);
|
|
468
|
-
|
|
538
|
+
await this.fetchUsersIfNeeded(entries);
|
|
539
|
+
|
|
540
|
+
const documents = this.convertEntries(entries, this.cache.getModelByName);
|
|
541
|
+
|
|
542
|
+
return { documents, syncContext: lastUpdatedEntryDate };
|
|
469
543
|
}
|
|
470
544
|
|
|
471
|
-
async getAssets() {
|
|
545
|
+
async getAssets(options?: { syncContext?: string }): Promise<ContextualAsset[] | { assets: ContextualAsset[]; syncContext?: string }> {
|
|
472
546
|
this.logger.debug('getAssets');
|
|
473
|
-
let
|
|
474
|
-
|
|
547
|
+
let lastUpdatedAssetDate = options?.syncContext;
|
|
548
|
+
let ctflAssets: AssetProps[];
|
|
475
549
|
try {
|
|
476
|
-
if (
|
|
477
|
-
this.logger.debug(`
|
|
478
|
-
|
|
479
|
-
this.logger.debug(`got ${
|
|
480
|
-
if (
|
|
481
|
-
|
|
550
|
+
if (lastUpdatedAssetDate) {
|
|
551
|
+
this.logger.debug(`fetching assets updated after ${lastUpdatedAssetDate}`);
|
|
552
|
+
ctflAssets = await fetchAssetsUpdatedAfter(this.plainClient, lastUpdatedAssetDate, this.userLogger);
|
|
553
|
+
this.logger.debug(`got ${ctflAssets.length} updated/created assets after ${lastUpdatedAssetDate}`);
|
|
554
|
+
if (ctflAssets.length) {
|
|
555
|
+
lastUpdatedAssetDate = getLastUpdatedEntityDate(ctflAssets);
|
|
482
556
|
}
|
|
483
|
-
assets = _.unionBy(updatedAssets, cachedAssets, 'sys.id');
|
|
484
557
|
} else {
|
|
485
|
-
|
|
486
|
-
|
|
558
|
+
ctflAssets = await fetchAllAssets(this.plainClient, this.userLogger);
|
|
559
|
+
lastUpdatedAssetDate = getLastUpdatedEntityDate(ctflAssets);
|
|
487
560
|
}
|
|
488
561
|
} catch (error: any) {
|
|
489
562
|
// Stackbit won't be able to work properly even if one of the entries or the assets was not fetched.
|
|
490
563
|
// All fetch methods use Contentful's API client that handles errors and retries automatically.
|
|
491
564
|
this.logger.error(`Failed fetching assets from Contentful, error: ${error.message}`);
|
|
492
|
-
return [];
|
|
565
|
+
return { assets: [], syncContext: lastUpdatedAssetDate };
|
|
493
566
|
}
|
|
494
|
-
|
|
495
|
-
|
|
567
|
+
|
|
568
|
+
this.updateContentPollerSyncContext({ lastUpdatedAssetDate });
|
|
569
|
+
|
|
496
570
|
this.logger.debug(
|
|
497
|
-
`got ${
|
|
571
|
+
`got ${ctflAssets.length} assets from space ${this.spaceId}, environment ${this.environment}, lastUpdatedAssetDate: ${lastUpdatedAssetDate}`
|
|
498
572
|
);
|
|
499
|
-
|
|
573
|
+
await this.fetchUsersIfNeeded(ctflAssets);
|
|
574
|
+
|
|
575
|
+
const assets = this.convertAssets(ctflAssets);
|
|
576
|
+
|
|
577
|
+
return { assets, syncContext: lastUpdatedAssetDate };
|
|
500
578
|
}
|
|
501
579
|
|
|
502
580
|
async hasAccess({ userContext }: { userContext?: UserContext }): Promise<{ hasConnection: boolean; hasPermissions: boolean }> {
|
|
503
|
-
if (!this.localDev && !userContext?.accessToken) {
|
|
581
|
+
if (!this.localDev && !this.useAccessTokenForUpdates && !userContext?.accessToken) {
|
|
504
582
|
return {
|
|
505
583
|
hasConnection: false,
|
|
506
584
|
hasPermissions: false
|
|
@@ -1011,7 +1089,7 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
1011
1089
|
}
|
|
1012
1090
|
|
|
1013
1091
|
private getPlainApiClientForUser({ userContext }: { userContext?: UserContext }): PlainClientAPI {
|
|
1014
|
-
if (this.localDev) {
|
|
1092
|
+
if (this.localDev || this.useAccessTokenForUpdates) {
|
|
1015
1093
|
return this.plainClient;
|
|
1016
1094
|
}
|
|
1017
1095
|
const userAccessToken = userContext?.accessToken;
|
|
@@ -1021,7 +1099,9 @@ export class ContentfulContentSource implements ContentSourceTypes.ContentSource
|
|
|
1021
1099
|
return createPlainApiClient({
|
|
1022
1100
|
spaceId: this.spaceId,
|
|
1023
1101
|
accessToken: userAccessToken,
|
|
1024
|
-
environment: this.environment
|
|
1102
|
+
environment: this.environment,
|
|
1103
|
+
managementHost: this.managementHost,
|
|
1104
|
+
uploadHost: this.uploadHost
|
|
1025
1105
|
});
|
|
1026
1106
|
}
|
|
1027
1107
|
|
package/src/utils.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { BasicMetaSysProps } from 'contentful-management';
|
|
2
2
|
|
|
3
|
-
export type SyncContext = {
|
|
4
|
-
lastUpdatedEntryDate?: string;
|
|
5
|
-
lastUpdatedAssetDate?: string;
|
|
6
|
-
lastUpdatedContentTypeDate?: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
3
|
export function getLastUpdatedEntityDate(entities: { sys: BasicMetaSysProps }[]): string | undefined {
|
|
10
4
|
let lastUpdatedDate: string = '';
|
|
11
5
|
for (const entity of entities) {
|