@nascentdigital/funnel-cms 6.1.5 → 6.1.6
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/api/ContentClient.js +2 -0
- package/dist/api/PageClient.d.ts +22 -14
- package/dist/contentful/ContentfulCollectionClient.d.ts +6 -0
- package/dist/contentful/ContentfulCollectionClient.js +9 -2
- package/dist/contentful/ContentfulContentClient.d.ts +3 -0
- package/dist/contentful/ContentfulContentClient.js +60 -5
- package/dist/contentful/ContentfulPageClient.d.ts +10 -3
- package/dist/contentful/ContentfulPageClient.js +187 -30
- package/dist/contentful/IContentfulContentClient.d.ts +26 -0
- package/dist/contentful/models/ContentfulCollectionData.d.ts +6 -1
- package/dist/contentful/models/ContentfulCollectionData.js +6 -5
- package/dist/contentful/models/ContentfulData.d.ts +3 -1
- package/dist/contentful/models/ContentfulPageData.d.ts +6 -1
- package/dist/contentful/models/ContentfulPageData.js +5 -4
- package/dist/strapi/StrapiPageClient.d.ts +1 -1
- package/dist/strapi/StrapiPageClient.js +44 -35
- package/package.json +2 -2
|
@@ -16,6 +16,7 @@ var ContentClient;
|
|
|
16
16
|
}
|
|
17
17
|
Factory.get = get;
|
|
18
18
|
function set(factory) {
|
|
19
|
+
log.d('factor - registered', factory);
|
|
19
20
|
_factory = factory;
|
|
20
21
|
}
|
|
21
22
|
Factory.set = set;
|
|
@@ -32,3 +33,4 @@ var ContentClient;
|
|
|
32
33
|
})(ContentClient || (exports.ContentClient = ContentClient = {}));
|
|
33
34
|
// storage
|
|
34
35
|
let _factory = undefined;
|
|
36
|
+
const log = funnel_core_1.Logger.get('cms');
|
package/dist/api/PageClient.d.ts
CHANGED
|
@@ -132,7 +132,7 @@ export declare namespace PageClient {
|
|
|
132
132
|
};
|
|
133
133
|
type StaticPage = {
|
|
134
134
|
type: Schema.Page.Type;
|
|
135
|
-
refs:
|
|
135
|
+
refs: Schema.Page.Data.AsLink[];
|
|
136
136
|
};
|
|
137
137
|
type DynamicPage = {
|
|
138
138
|
type: Schema.Page.Type;
|
|
@@ -179,19 +179,27 @@ export declare namespace PageClient {
|
|
|
179
179
|
type Result = Schema.Page.Data.AsGroup;
|
|
180
180
|
}
|
|
181
181
|
namespace Duplicate {
|
|
182
|
-
type
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
type Args = Args.Extend | Args.Fork;
|
|
183
|
+
namespace Args {
|
|
184
|
+
type Source = {
|
|
185
|
+
groupId: Schema.Page.GroupId;
|
|
186
|
+
region: string;
|
|
187
|
+
language: Language;
|
|
188
|
+
};
|
|
189
|
+
type Extend = {
|
|
190
|
+
operation: 'extend';
|
|
191
|
+
region: string;
|
|
192
|
+
language: Language;
|
|
193
|
+
source: Source;
|
|
194
|
+
};
|
|
195
|
+
type Fork = {
|
|
196
|
+
operation: 'fork';
|
|
197
|
+
region: string;
|
|
198
|
+
language: Language;
|
|
199
|
+
slug: string;
|
|
200
|
+
source: Source;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
195
203
|
type Result = Schema.Page.Data.AsGroup;
|
|
196
204
|
}
|
|
197
205
|
namespace Touch {
|
|
@@ -41,3 +41,9 @@ export declare namespace ContentfulCollectionClient {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
export type GetCollectionQueryParamsArgs = Omit<CollectionClient.Query.By<any>, 'version' | 'groupId'> & {
|
|
45
|
+
ids?: Schema.Collection.Id[];
|
|
46
|
+
groupId?: Schema.Collection.GroupId | ReadonlyArray<Schema.Collection.GroupId>;
|
|
47
|
+
};
|
|
48
|
+
export declare function getCollectionQueryParams(query: GetCollectionQueryParamsArgs): Record<string, any>;
|
|
49
|
+
export declare function getCollectionSelect(props: ReadonlyArray<Schema.Collection.Data.Props.All>): string | undefined;
|
|
@@ -12,6 +12,8 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.ContentfulCollectionClient = void 0;
|
|
15
|
+
exports.getCollectionQueryParams = getCollectionQueryParams;
|
|
16
|
+
exports.getCollectionSelect = getCollectionSelect;
|
|
15
17
|
const funnel_core_1 = require("@nascentdigital/funnel-core");
|
|
16
18
|
const models_1 = require("./models");
|
|
17
19
|
// class
|
|
@@ -251,6 +253,7 @@ class ContentfulCollectionClient {
|
|
|
251
253
|
// always update
|
|
252
254
|
name: data.name,
|
|
253
255
|
errors: data.errors,
|
|
256
|
+
valid: data.errors ? null : true,
|
|
254
257
|
references,
|
|
255
258
|
// TODO: only update if changed
|
|
256
259
|
// optional
|
|
@@ -278,7 +281,6 @@ class ContentfulCollectionClient {
|
|
|
278
281
|
}
|
|
279
282
|
}
|
|
280
283
|
exports.ContentfulCollectionClient = ContentfulCollectionClient;
|
|
281
|
-
// helpers
|
|
282
284
|
function getCollectionQueryParams(query) {
|
|
283
285
|
// build filters
|
|
284
286
|
const { ids = [], groupId, brand, type, slug, language, name, tags = [], tagsMatch = 'any', version } = query;
|
|
@@ -292,7 +294,12 @@ function getCollectionQueryParams(query) {
|
|
|
292
294
|
}
|
|
293
295
|
// filter group
|
|
294
296
|
if (groupId) {
|
|
295
|
-
|
|
297
|
+
if (Array.isArray(groupId)) {
|
|
298
|
+
filters['fields.groupId[in]'] = groupId.join(',');
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
filters['fields.groupId'] = groupId;
|
|
302
|
+
}
|
|
296
303
|
}
|
|
297
304
|
// filter brand
|
|
298
305
|
if (brand) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CMAClient, KnownAppSDK } from '@contentful/app-sdk';
|
|
2
|
+
import { Logger } from '@nascentdigital/funnel-core';
|
|
2
3
|
import { PlainClientAPI } from 'contentful-management';
|
|
3
4
|
import { AuthClient, ContentClient } from '../api';
|
|
4
5
|
import { User } from '../models';
|
|
@@ -13,6 +14,7 @@ import { ContentfulUserClient } from './ContentfulUserClient';
|
|
|
13
14
|
import { IContentfulContentClient } from './IContentfulContentClient';
|
|
14
15
|
import { ContentfulData } from './models';
|
|
15
16
|
export declare class ContentfulContentClient implements ContentClient, IContentfulContentClient {
|
|
17
|
+
readonly log: Logger;
|
|
16
18
|
readonly user: User;
|
|
17
19
|
readonly locale: string;
|
|
18
20
|
readonly deliveryAccessToken: string;
|
|
@@ -38,6 +40,7 @@ export declare class ContentfulContentClient implements ContentClient, IContentf
|
|
|
38
40
|
getEntries<TEntry extends ContentfulData>({ pagination, version, includeLinks, ...query }: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
|
|
39
41
|
updateEntry<TEntry extends ContentfulData>(entry: ContentfulData.Update<TEntry>): Promise<TEntry>;
|
|
40
42
|
uploadAsset(args: IContentfulContentClient.UploadAsset.Args): Promise<IContentfulContentClient.UploadAsset.Result>;
|
|
43
|
+
getTranslatedReferences({ to, pages, collections }: IContentfulContentClient.GetTranslatedReferences.Args): Promise<IContentfulContentClient.GetTranslatedReferences.Result>;
|
|
41
44
|
static forEditor({ sdk, contentApiPath }: ContentfulContentClient.ForEditorArgs): Promise<ContentfulContentClient>;
|
|
42
45
|
}
|
|
43
46
|
export declare namespace ContentfulContentClient {
|
|
@@ -30,7 +30,10 @@ class ContentfulContentClient {
|
|
|
30
30
|
constructor(args) {
|
|
31
31
|
var _a;
|
|
32
32
|
// initialize instance variables
|
|
33
|
+
const log = funnel_core_1.Logger.get('cms:client');
|
|
34
|
+
this.log = log;
|
|
33
35
|
if (ContentfulContentClient.ConstructorArgs.isForServer(args)) {
|
|
36
|
+
log.d('constructor - created for server (headless)');
|
|
34
37
|
this.user = models_1.User.UNAUTHENTICATED;
|
|
35
38
|
this.locale = args.locale;
|
|
36
39
|
const options = { defaults: { spaceId: args.spaceId, environmentId: args.environment } };
|
|
@@ -52,6 +55,7 @@ class ContentfulContentClient {
|
|
|
52
55
|
.map(({ name }) => models_2.ContentfulUserData.parseRole(name))
|
|
53
56
|
.sort((lhs, rhs) => lhs.compareTo(rhs))[0].id
|
|
54
57
|
});
|
|
58
|
+
log.d('constructor - created for user', this.user);
|
|
55
59
|
this.locale = locales.default;
|
|
56
60
|
this.cma = cma;
|
|
57
61
|
const options = { defaults: { spaceId: ids.space, environmentId: (_a = ids.environmentAlias) !== null && _a !== void 0 ? _a : ids.environment } };
|
|
@@ -97,6 +101,7 @@ class ContentfulContentClient {
|
|
|
97
101
|
async getEntry(_a) {
|
|
98
102
|
var _b, _c;
|
|
99
103
|
var { entryId, includeLinks = false, version = 'draft' } = _a, query = __rest(_a, ["entryId", "includeLinks", "version"]);
|
|
104
|
+
this.log.v('getEntry - fetching for', Object.assign({ entryId, version, includeLinks }, query));
|
|
100
105
|
// get entry
|
|
101
106
|
const client = version === 'published' ? this.cda : this.cpa;
|
|
102
107
|
const result = await client.entry.getMany({
|
|
@@ -105,8 +110,7 @@ class ContentfulContentClient {
|
|
|
105
110
|
: includeLinks || undefined })
|
|
106
111
|
});
|
|
107
112
|
const { items: [item], includes } = result;
|
|
108
|
-
|
|
109
|
-
// skip
|
|
113
|
+
this.log.v('getEntry - fetched', result);
|
|
110
114
|
// bind references
|
|
111
115
|
const entry = item ? models_2.ContentfulData.fromCdaData(item) : undefined;
|
|
112
116
|
const references = (_c = (_b = includes === null || includes === void 0 ? void 0 : includes.Entry) === null || _b === void 0 ? void 0 : _b.map(link => models_2.ContentfulData.fromCdaData(link))) !== null && _c !== void 0 ? _c : [];
|
|
@@ -116,6 +120,7 @@ class ContentfulContentClient {
|
|
|
116
120
|
async getEntries(_a) {
|
|
117
121
|
var _b, _c;
|
|
118
122
|
var { pagination = 'all', version = 'draft', includeLinks = false } = _a, query = __rest(_a, ["pagination", "version", "includeLinks"]);
|
|
123
|
+
this.log.v('getEntries - fetching for', Object.assign({ version, includeLinks, pagination }, query));
|
|
119
124
|
// setup pagination
|
|
120
125
|
const itemsPerRequest = pagination === 'all' ? 1000 : Math.min(pagination.pageSize, 1000);
|
|
121
126
|
const page = pagination === 'all' ? 1 : pagination.page;
|
|
@@ -134,7 +139,7 @@ class ContentfulContentClient {
|
|
|
134
139
|
? 10
|
|
135
140
|
: includeLinks || undefined })
|
|
136
141
|
}), { items } = _d, response = __rest(_d, ["items"]);
|
|
137
|
-
|
|
142
|
+
this.log.v('getEntries - fetching page', Object.assign({ items }, response));
|
|
138
143
|
// add items
|
|
139
144
|
items.forEach(item => entries.push(models_2.ContentfulData.fromCdaData(item)));
|
|
140
145
|
includes.push(...((_c = (_b = response.includes) === null || _b === void 0 ? void 0 : _b.Entry) !== null && _c !== void 0 ? _c : []));
|
|
@@ -146,7 +151,7 @@ class ContentfulContentClient {
|
|
|
146
151
|
? skip < totalItems
|
|
147
152
|
: totalItemFetched < pagination.pageSize && skip < totalItems;
|
|
148
153
|
} while (hasMorePages);
|
|
149
|
-
|
|
154
|
+
this.log.v('getEntries - fetched all', { entries, includes });
|
|
150
155
|
// normalize references
|
|
151
156
|
const references = includes.map(link => models_2.ContentfulData.fromCdaData(link));
|
|
152
157
|
// return
|
|
@@ -235,8 +240,58 @@ class ContentfulContentClient {
|
|
|
235
240
|
// return asset
|
|
236
241
|
return models_2.ContentfulData.fromCmaData(entry, locale);
|
|
237
242
|
}
|
|
243
|
+
async getTranslatedReferences({ to, pages, collections }) {
|
|
244
|
+
// lookup pages/collections
|
|
245
|
+
const [translatedPages, translatedCollections] = await Promise.all([
|
|
246
|
+
pages.length === 0
|
|
247
|
+
? Promise.resolve([])
|
|
248
|
+
: this.getEntries(Object.assign({ content_type: models_2.ContentfulPageData.contentType, version: 'draft', select: (0, ContentfulPageClient_1.getPageSelect)(funnel_core_1.Schema.Page.Data.Props.id) }, (0, ContentfulPageClient_1.getPageQueryParams)({
|
|
249
|
+
groupId: pages.map(page => page.groupId)
|
|
250
|
+
})))
|
|
251
|
+
.then(({ entries }) => {
|
|
252
|
+
return entries
|
|
253
|
+
.filter(entry => entry.region === to.region && entry.language === to.language)
|
|
254
|
+
.map(entry => {
|
|
255
|
+
// lookup original
|
|
256
|
+
const source = pages.find(page => page.groupId === entry.groupId);
|
|
257
|
+
// return translation (or drop it)
|
|
258
|
+
return source && {
|
|
259
|
+
from: source.id,
|
|
260
|
+
to: entry.id
|
|
261
|
+
};
|
|
262
|
+
})
|
|
263
|
+
.filter(funnel_core_1.Value.isDefined);
|
|
264
|
+
}),
|
|
265
|
+
collections.length === 0
|
|
266
|
+
? Promise.resolve([])
|
|
267
|
+
: this.getEntries(Object.assign({ content_type: models_2.ContentfulCollectionData.contentType, version: 'draft', select: (0, ContentfulCollectionClient_1.getCollectionSelect)(funnel_core_1.Schema.Collection.Data.Props.id) }, (0, ContentfulCollectionClient_1.getCollectionQueryParams)({
|
|
268
|
+
groupId: collections.map(collection => collection.groupId)
|
|
269
|
+
})))
|
|
270
|
+
.then(({ entries }) => {
|
|
271
|
+
return entries
|
|
272
|
+
.filter(entry => entry.language === to.language)
|
|
273
|
+
.map(entry => {
|
|
274
|
+
// lookup original
|
|
275
|
+
const source = collections.find(collection => collection.groupId === entry.groupId);
|
|
276
|
+
// return translation (or drop it)
|
|
277
|
+
return source && {
|
|
278
|
+
from: source.id,
|
|
279
|
+
to: entry.id
|
|
280
|
+
};
|
|
281
|
+
})
|
|
282
|
+
.filter(funnel_core_1.Value.isDefined);
|
|
283
|
+
})
|
|
284
|
+
]);
|
|
285
|
+
// return translated references
|
|
286
|
+
return {
|
|
287
|
+
pages: translatedPages,
|
|
288
|
+
collections: translatedCollections
|
|
289
|
+
};
|
|
290
|
+
}
|
|
238
291
|
static async forEditor({ sdk, contentApiPath }) {
|
|
239
292
|
var _a;
|
|
293
|
+
const log = funnel_core_1.Logger.get('cms:client');
|
|
294
|
+
log.v('forEditor - creating for', { sdk, contentApiPath });
|
|
240
295
|
// get current site config (throw if we can't)
|
|
241
296
|
const { user, cma, locales: { default: locale } } = sdk;
|
|
242
297
|
const userId = user.sys.id;
|
|
@@ -278,7 +333,7 @@ class ContentfulContentClient {
|
|
|
278
333
|
if (!deliveryAccessToken || !previewAccessToken) {
|
|
279
334
|
throw new funnel_core_1.IllegalStateError('Invalid data return from API');
|
|
280
335
|
}
|
|
281
|
-
|
|
336
|
+
log.v('forEditor - creating with', { sdk, deliveryAccessToken, previewAccessToken });
|
|
282
337
|
// create + return new client
|
|
283
338
|
return new ContentfulContentClient({ sdk, deliveryAccessToken, previewAccessToken });
|
|
284
339
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { Schema } from '@nascentdigital/funnel-core';
|
|
1
|
+
import { Logger, Schema } from '@nascentdigital/funnel-core';
|
|
2
2
|
import { PageClient } from '../api';
|
|
3
3
|
import { IContentfulContentClient } from './IContentfulContentClient';
|
|
4
4
|
import { ContentfulPageData } from './models';
|
|
5
5
|
export declare class ContentfulPageClient implements PageClient {
|
|
6
6
|
readonly cms: IContentfulContentClient;
|
|
7
|
+
readonly log: Logger;
|
|
7
8
|
constructor(cms: IContentfulContentClient);
|
|
8
|
-
getOverview(args: PageClient.GetOverview.Args): Promise<PageClient.GetOverview.Result>;
|
|
9
|
+
getOverview({ version, region, ...args }: PageClient.GetOverview.Args): Promise<PageClient.GetOverview.Result>;
|
|
9
10
|
getSummaries(args: PageClient.GetSummaries.Args): Promise<PageClient.GetSummaries.Result>;
|
|
10
11
|
getPreviews(args: PageClient.GetPreviews.Args): Promise<PageClient.GetPreviews.Result>;
|
|
11
12
|
search({ version, pagination, ...query }: PageClient.Search.Args): Promise<PageClient.Search.Result>;
|
|
@@ -14,7 +15,7 @@ export declare class ContentfulPageClient implements PageClient {
|
|
|
14
15
|
getGroup(args: PageClient.GetGroup.Args): Promise<ContentfulPageClient.GetGroup.Result>;
|
|
15
16
|
get(args: PageClient.Get.Args): Promise<ContentfulPageClient.Get.Result>;
|
|
16
17
|
create(args: PageClient.Create.Args): Promise<PageClient.Create.Result>;
|
|
17
|
-
duplicate(
|
|
18
|
+
duplicate({ source, ...target }: PageClient.Duplicate.Args): Promise<PageClient.Duplicate.Result>;
|
|
18
19
|
update(args: PageClient.Update.Args): Promise<ContentfulPageClient.Update.Result>;
|
|
19
20
|
replace(args: PageClient.Replace.Args): Promise<PageClient.Replace.Result>;
|
|
20
21
|
touch(args: PageClient.Touch.Args): Promise<PageClient.Touch.Result>;
|
|
@@ -41,3 +42,9 @@ export declare namespace ContentfulPageClient {
|
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
export type GetPageQueryParamsArgs = Omit<PageClient.Query.By<any>, 'version' | 'groupId'> & {
|
|
46
|
+
ids?: Schema.Page.Id[];
|
|
47
|
+
groupId?: Schema.Page.GroupId | ReadonlyArray<Schema.Page.GroupId>;
|
|
48
|
+
};
|
|
49
|
+
export declare function getPageQueryParams(query: GetPageQueryParamsArgs): Record<string, any>;
|
|
50
|
+
export declare function getPageSelect(props: ReadonlyArray<Schema.Page.Data.Props.All>): string | undefined;
|
|
@@ -12,6 +12,8 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.ContentfulPageClient = void 0;
|
|
15
|
+
exports.getPageQueryParams = getPageQueryParams;
|
|
16
|
+
exports.getPageSelect = getPageSelect;
|
|
15
17
|
const funnel_core_1 = require("@nascentdigital/funnel-core");
|
|
16
18
|
const errors_1 = require("../errors");
|
|
17
19
|
const models_1 = require("./models");
|
|
@@ -19,11 +21,13 @@ const models_1 = require("./models");
|
|
|
19
21
|
class ContentfulPageClient {
|
|
20
22
|
constructor(cms) {
|
|
21
23
|
this.cms = cms;
|
|
24
|
+
this.log = funnel_core_1.Logger.get('cms:page');
|
|
22
25
|
}
|
|
23
|
-
async getOverview(
|
|
26
|
+
async getOverview(_a) {
|
|
27
|
+
var { version, region } = _a, args = __rest(_a, ["version", "region"]);
|
|
24
28
|
// fetch all pages
|
|
25
29
|
const { cms } = this;
|
|
26
|
-
const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version
|
|
30
|
+
const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version, select: getPageSelect(funnel_core_1.Schema.Page.Data.AsLink.props) }, getPageQueryParams(args)));
|
|
27
31
|
// convert entries to page data
|
|
28
32
|
const pagesData = entries.map(entry => models_1.ContentfulPageData.deserialize(entry, funnel_core_1.Schema.Page.Data.AsLink.props));
|
|
29
33
|
// setup maps
|
|
@@ -31,34 +35,38 @@ class ContentfulPageClient {
|
|
|
31
35
|
const dynamicPages = new Map();
|
|
32
36
|
// go over each page data
|
|
33
37
|
for (const data of pagesData) {
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
// add static page
|
|
39
|
+
if (data.slug === undefined) {
|
|
40
|
+
// get references (or create it)
|
|
41
|
+
let refs = staticPages.get(data.type);
|
|
42
|
+
if (!refs) {
|
|
43
|
+
staticPages.set(data.type, refs = []);
|
|
44
|
+
}
|
|
45
|
+
// add page
|
|
46
|
+
refs.push(data);
|
|
47
|
+
}
|
|
48
|
+
// or dynamic
|
|
49
|
+
else {
|
|
50
|
+
// get references (or create it)
|
|
51
|
+
let refs = dynamicPages.get(data.type);
|
|
52
|
+
if (!refs) {
|
|
53
|
+
dynamicPages.set(data.type, refs = new Set());
|
|
54
|
+
}
|
|
55
|
+
// add page
|
|
56
|
+
refs.add(data.groupId);
|
|
42
57
|
}
|
|
43
|
-
// add to refs
|
|
44
|
-
refs.push(data);
|
|
45
58
|
}
|
|
46
59
|
// return
|
|
47
60
|
return {
|
|
48
61
|
// static pages
|
|
49
62
|
static: staticPages.entries().map(([type, refs]) => ({
|
|
50
63
|
type,
|
|
51
|
-
refs
|
|
52
|
-
// add page
|
|
53
|
-
refs[ref.language] = ref;
|
|
54
|
-
// keep reducing
|
|
55
|
-
return refs;
|
|
56
|
-
}, {})
|
|
64
|
+
refs
|
|
57
65
|
})).toArray(),
|
|
58
66
|
// dynamic pages
|
|
59
67
|
dynamic: dynamicPages.entries().map(([type, refs]) => ({
|
|
60
68
|
type,
|
|
61
|
-
count: refs.
|
|
69
|
+
count: refs.size
|
|
62
70
|
})).toArray()
|
|
63
71
|
};
|
|
64
72
|
}
|
|
@@ -93,27 +101,27 @@ class ContentfulPageClient {
|
|
|
93
101
|
async getGroup(args) {
|
|
94
102
|
// fetch items (bail if there are no instances
|
|
95
103
|
const { language, region, version } = args, queryArgs = __rest(args, ["language", "region", "version"]);
|
|
96
|
-
const { cms } = this;
|
|
97
|
-
|
|
104
|
+
const { cms, log } = this;
|
|
105
|
+
log.v('getGroup - querying', args);
|
|
98
106
|
const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version, select: getPageSelect(funnel_core_1.Schema.Page.Data.Props.id) }, getPageQueryParams(queryArgs)));
|
|
99
107
|
// bail if no items
|
|
100
108
|
if (entries.length === 0) {
|
|
101
|
-
|
|
109
|
+
log.v('getGroup - no items in group');
|
|
102
110
|
const { brand, groupId: possibleGroupId, type, slug } = queryArgs;
|
|
103
111
|
return {
|
|
104
112
|
group: { groupId: possibleGroupId !== null && possibleGroupId !== void 0 ? possibleGroupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, instances: [] }
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
|
-
console.debug('[ContentfulPageClient/getGroup] processing group items', JSON.stringify(args, undefined, ' '));
|
|
108
115
|
// collate the group ids into summaries (fetches full page if region/language match exists
|
|
109
116
|
const instances = entries.map(entry => models_1.ContentfulPageData.deserialize(entry, funnel_core_1.Schema.Page.Data.AsGroup.Instance.props));
|
|
110
|
-
// bind page (if it exists)
|
|
111
117
|
const pageInstance = instances
|
|
112
118
|
.find(instance => instance.region === region && instance.language === language);
|
|
113
|
-
|
|
119
|
+
log.v('getGroup - processing group items', { entries, instances, pageInstance });
|
|
120
|
+
// bind page (if it exists)
|
|
114
121
|
const page = pageInstance
|
|
115
122
|
? await this.get({ id: pageInstance.id, version })
|
|
116
123
|
: undefined;
|
|
124
|
+
log.v('getGroup - fetched page', page);
|
|
117
125
|
// return group (and optional page)
|
|
118
126
|
const { type, brand, groupId, slug } = entries[0];
|
|
119
127
|
return {
|
|
@@ -122,8 +130,9 @@ class ContentfulPageClient {
|
|
|
122
130
|
};
|
|
123
131
|
}
|
|
124
132
|
async get(args) {
|
|
133
|
+
const { log, cms } = this;
|
|
134
|
+
log.v('get - fetching page for', args);
|
|
125
135
|
// fetch entries (should be one entry)
|
|
126
|
-
const { cms } = this;
|
|
127
136
|
const [{ entries, references }, { entry: entrySnapshot }] = await Promise.all([
|
|
128
137
|
// query target (could be published/draft)
|
|
129
138
|
cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, includeLinks: true, version: args.version, pagination: { page: 1, pageSize: 1 }, select: getPageSelect(args.select === 'content'
|
|
@@ -136,6 +145,7 @@ class ContentfulPageClient {
|
|
|
136
145
|
]);
|
|
137
146
|
// skip if no entries
|
|
138
147
|
if (entries.length === 0) {
|
|
148
|
+
log.v('get - nothing found');
|
|
139
149
|
return undefined;
|
|
140
150
|
}
|
|
141
151
|
// stitch together snapshot with entry (if available
|
|
@@ -143,6 +153,7 @@ class ContentfulPageClient {
|
|
|
143
153
|
const snapshot = entrySnapshot
|
|
144
154
|
? models_1.ContentfulPageData.Snapshot.deserialize(entrySnapshot)
|
|
145
155
|
: undefined;
|
|
156
|
+
log.v('get - fetched page', { data, snapshot });
|
|
146
157
|
// use first entry and deserialize
|
|
147
158
|
return Object.assign({ data: Object.assign(Object.assign({}, data), { snapshotId: snapshot === null || snapshot === void 0 ? void 0 : snapshot.id, snapshot }) }, models_1.ContentfulPageData.deserializeRefs(references));
|
|
148
159
|
}
|
|
@@ -343,8 +354,150 @@ class ContentfulPageClient {
|
|
|
343
354
|
}
|
|
344
355
|
}
|
|
345
356
|
}
|
|
346
|
-
duplicate(
|
|
347
|
-
|
|
357
|
+
async duplicate(_a) {
|
|
358
|
+
var _b, _c, _d;
|
|
359
|
+
var { source } = _a, target = __rest(_a, ["source"]);
|
|
360
|
+
console.debug('[ContentfulPageClient/duplicate] processing duplicate page request', target);
|
|
361
|
+
// get page by id
|
|
362
|
+
const { groupId, region: sourceRegion, language: sourceLanguage } = source;
|
|
363
|
+
const { items: sourceGroup } = await this.search({
|
|
364
|
+
groupId,
|
|
365
|
+
version: 'draft',
|
|
366
|
+
pagination: 'all'
|
|
367
|
+
});
|
|
368
|
+
// throw if there's no page
|
|
369
|
+
const sourceRef = sourceGroup.find(page => page.region === sourceRegion && page.language === sourceLanguage);
|
|
370
|
+
if (!sourceRef) {
|
|
371
|
+
throw new funnel_core_1.NotFoundError(`Unable to resolve page matching group "${groupId}" in ${sourceRegion}/${sourceLanguage}`);
|
|
372
|
+
}
|
|
373
|
+
// TODO: check schema to see if language is possible
|
|
374
|
+
// make sure target language doesn't already exist if we are targeting the current group
|
|
375
|
+
if (target.operation === 'extend') {
|
|
376
|
+
if (sourceGroup.some(page => page.region === target.region && page.language === target.language)) {
|
|
377
|
+
throw new funnel_core_1.InvalidOperationError(`There is already a page in group "${groupId}" created for ${target.region}/${target.language}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// make sure a static page isn't getting duplicated and that the slug is good
|
|
381
|
+
else {
|
|
382
|
+
// get source page's schema
|
|
383
|
+
const site = funnel_core_1.Schema.Site.current();
|
|
384
|
+
const schema = site.regions[sourceRef.region][sourceRef.type];
|
|
385
|
+
// cannot duplicate a static page
|
|
386
|
+
if (schema.variant === 'static') {
|
|
387
|
+
throw new funnel_core_1.InvalidOperationError(`cannot duplicate a static page "${sourceRef.name}"`);
|
|
388
|
+
}
|
|
389
|
+
// slug must be provided
|
|
390
|
+
if (target.slug === undefined) {
|
|
391
|
+
throw new funnel_core_1.ArgumentError('slug must be provided when duplicating a page');
|
|
392
|
+
}
|
|
393
|
+
// validate slug
|
|
394
|
+
const validateSlugResult = await this.validateSlug({
|
|
395
|
+
brand: sourceRef.brand,
|
|
396
|
+
type: sourceRef.type,
|
|
397
|
+
region: sourceRef.region,
|
|
398
|
+
slug: target.slug
|
|
399
|
+
});
|
|
400
|
+
if (validateSlugResult.status !== 'available') {
|
|
401
|
+
throw new errors_1.SlugValidationError({
|
|
402
|
+
slug: target.slug,
|
|
403
|
+
reason: validateSlugResult
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// get full source with refs
|
|
408
|
+
const { cms } = this;
|
|
409
|
+
const { entries: [sourceEntry], references } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, includeLinks: true, version: 'draft', pagination: { page: 1, pageSize: 1 }, select: getPageSelect(funnel_core_1.Schema.Page.Data.AsContent.props) }, getPageQueryParams({ ids: [sourceRef.id] })));
|
|
410
|
+
if (!sourceEntry) {
|
|
411
|
+
throw new funnel_core_1.NotFoundError(`Unable to resolve page with id "${sourceRef.id}"`);
|
|
412
|
+
}
|
|
413
|
+
const sourcePage = models_1.ContentfulPageData.deserialize(sourceEntry);
|
|
414
|
+
console.debug('[ContentfulPageClient/duplicate] fetched source', { sourceEntry, sourcePage });
|
|
415
|
+
const sourceRefIds = ((_b = sourceEntry.references) !== null && _b !== void 0 ? _b : [])
|
|
416
|
+
.filter(link => link.sys.linkType === 'Entry')
|
|
417
|
+
.map(link => link.sys.id);
|
|
418
|
+
const sourceReferences = models_1.ContentfulPageData.deserializeRefs(references);
|
|
419
|
+
const sourcePageRefs = Object.values(sourceReferences.pages)
|
|
420
|
+
.filter(page => sourceRefIds.includes(page.id))
|
|
421
|
+
.map(page => ({
|
|
422
|
+
id: page.id,
|
|
423
|
+
groupId: page.groupId
|
|
424
|
+
}));
|
|
425
|
+
const sourceCollectionRefs = Object.values(sourceReferences.collections)
|
|
426
|
+
.filter(collection => sourceRefIds.includes(collection.id))
|
|
427
|
+
.map(collection => ({
|
|
428
|
+
id: collection.id,
|
|
429
|
+
groupId: collection.groupId
|
|
430
|
+
}));
|
|
431
|
+
console.debug('[ContentfulPageClient/duplicate] fetched source', sourcePage);
|
|
432
|
+
// translate references (falls back to default if there's no mapping
|
|
433
|
+
const { pages: translatedPageIds, collections: translatedCollectionIds } = await cms.getTranslatedReferences({
|
|
434
|
+
from: source,
|
|
435
|
+
to: target,
|
|
436
|
+
pages: sourcePageRefs,
|
|
437
|
+
collections: sourceCollectionRefs
|
|
438
|
+
});
|
|
439
|
+
const refIds = [...translatedPageIds, ...translatedCollectionIds].filter(funnel_core_1.Value.isDefined);
|
|
440
|
+
console.debug('[ContentfulPageClient/duplicate] found translated ids', refIds);
|
|
441
|
+
// update refs
|
|
442
|
+
const updateRefs = (source, mappings) => {
|
|
443
|
+
// skip if there's no json
|
|
444
|
+
if (!source) {
|
|
445
|
+
return source;
|
|
446
|
+
}
|
|
447
|
+
// or if there's no mappings
|
|
448
|
+
else if (mappings.length === 0) {
|
|
449
|
+
return source;
|
|
450
|
+
}
|
|
451
|
+
// serialize
|
|
452
|
+
let serialized = JSON.stringify(source);
|
|
453
|
+
// replace
|
|
454
|
+
for (const { from, to } of mappings) {
|
|
455
|
+
serialized = serialized.replaceAll(`"${from}"`, `"${to}"`);
|
|
456
|
+
}
|
|
457
|
+
// deserialize
|
|
458
|
+
return JSON.parse(serialized);
|
|
459
|
+
};
|
|
460
|
+
const targetBody = {
|
|
461
|
+
discover: updateRefs(sourcePage.discover, refIds),
|
|
462
|
+
preview: updateRefs(sourcePage.preview, refIds),
|
|
463
|
+
content: updateRefs(sourcePage.content, refIds)
|
|
464
|
+
};
|
|
465
|
+
// create duplicate page
|
|
466
|
+
try {
|
|
467
|
+
// setup data
|
|
468
|
+
const targetData = Object.assign({ groupId: target.operation === 'extend'
|
|
469
|
+
? sourcePage.groupId
|
|
470
|
+
: funnel_core_1.Value.uuid(), type: sourcePage.type, brand: sourcePage.brand, region: target.region, language: target.language, slug: target.operation === 'extend'
|
|
471
|
+
? sourcePage.slug
|
|
472
|
+
: target.slug, name: sourcePage.name, tags: sourcePage.tags, url: sourcePage.url, errors: true }, targetBody);
|
|
473
|
+
const targetModel = funnel_core_1.Schema.PageModel.from(targetData);
|
|
474
|
+
targetModel.sync();
|
|
475
|
+
console.debug('[ContentfulPageClient/duplicate] about to create page with data', targetData);
|
|
476
|
+
// create target page using content of source
|
|
477
|
+
const targetEntry = await cms.createEntry(models_1.ContentfulPageData.contentType, Object.assign(Object.assign({}, targetData), { references: ((_c = sourceEntry.references) !== null && _c !== void 0 ? _c : []).map(link => {
|
|
478
|
+
var _a, _b;
|
|
479
|
+
// replace entry ids if mapped
|
|
480
|
+
if (link.sys.linkType === 'Entry') {
|
|
481
|
+
link.sys.id = (_b = (_a = refIds.find(({ from }) => from === link.sys.id)) === null || _a === void 0 ? void 0 : _a.to) !== null && _b !== void 0 ? _b : link.sys.id;
|
|
482
|
+
}
|
|
483
|
+
// return link
|
|
484
|
+
return link;
|
|
485
|
+
}) }));
|
|
486
|
+
// return group for duplicated page
|
|
487
|
+
return this.getGroup({
|
|
488
|
+
brand: targetEntry.brand,
|
|
489
|
+
groupId: targetEntry.groupId,
|
|
490
|
+
type: targetEntry.type,
|
|
491
|
+
slug: (_d = targetEntry.slug) !== null && _d !== void 0 ? _d : undefined,
|
|
492
|
+
region: targetEntry.region,
|
|
493
|
+
language: targetEntry.language,
|
|
494
|
+
version: 'draft'
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
// handle errors
|
|
498
|
+
catch (e) {
|
|
499
|
+
throw funnel_core_1.IsometricErrorUtils.coerceError(e);
|
|
500
|
+
}
|
|
348
501
|
}
|
|
349
502
|
update(args) {
|
|
350
503
|
throw new funnel_core_1.NotImplementedError();
|
|
@@ -387,7 +540,6 @@ class ContentfulPageClient {
|
|
|
387
540
|
}
|
|
388
541
|
}
|
|
389
542
|
exports.ContentfulPageClient = ContentfulPageClient;
|
|
390
|
-
// helpers
|
|
391
543
|
function getPageQueryParams(query) {
|
|
392
544
|
// build filters
|
|
393
545
|
const { ids = [], groupId, brand, type, slug, region, language, name, tags = [], tagsMatch = 'any' } = query;
|
|
@@ -401,7 +553,12 @@ function getPageQueryParams(query) {
|
|
|
401
553
|
}
|
|
402
554
|
// filter group
|
|
403
555
|
if (groupId) {
|
|
404
|
-
|
|
556
|
+
if (Array.isArray(groupId)) {
|
|
557
|
+
filters['fields.groupId[in]'] = groupId.join(',');
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
filters['fields.groupId'] = groupId;
|
|
561
|
+
}
|
|
405
562
|
}
|
|
406
563
|
// filter brand
|
|
407
564
|
if (brand) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CMAClient } from '@contentful/app-sdk';
|
|
2
|
+
import { Locale, Schema } from '@nascentdigital/funnel-core';
|
|
2
3
|
import { PlainClientAPI, QueryOptions } from 'contentful-management';
|
|
3
4
|
import { AssetClient } from '../api';
|
|
4
5
|
import { Environment, Pagination, User } from '../models';
|
|
@@ -35,6 +36,7 @@ export interface IContentfulContentClient {
|
|
|
35
36
|
getEntries<TEntry extends ContentfulData>(args: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
|
|
36
37
|
updateEntry<TEntry extends ContentfulData>(entry: ContentfulData.Update<TEntry>): Promise<TEntry>;
|
|
37
38
|
uploadAsset(args: IContentfulContentClient.UploadAsset.Args): Promise<IContentfulContentClient.UploadAsset.Result>;
|
|
39
|
+
getTranslatedReferences(args: IContentfulContentClient.GetTranslatedReferences.Args): Promise<IContentfulContentClient.GetTranslatedReferences.Result>;
|
|
38
40
|
}
|
|
39
41
|
export declare namespace IContentfulContentClient {
|
|
40
42
|
type EntryQueryOptions = {
|
|
@@ -60,6 +62,30 @@ export declare namespace IContentfulContentClient {
|
|
|
60
62
|
references: ContentfulData[];
|
|
61
63
|
};
|
|
62
64
|
}
|
|
65
|
+
namespace GetTranslatedReferences {
|
|
66
|
+
type Args = {
|
|
67
|
+
from: Locale;
|
|
68
|
+
to: Locale;
|
|
69
|
+
pages: ReadonlyArray<PageRef>;
|
|
70
|
+
collections: ReadonlyArray<CollectionRef>;
|
|
71
|
+
};
|
|
72
|
+
type Result = {
|
|
73
|
+
pages: Translation<Schema.Page.Id>[];
|
|
74
|
+
collections: Translation<Schema.Collection.Id>[];
|
|
75
|
+
};
|
|
76
|
+
type PageRef = {
|
|
77
|
+
id: Schema.Page.Id;
|
|
78
|
+
groupId: Schema.Page.GroupId;
|
|
79
|
+
};
|
|
80
|
+
type CollectionRef = {
|
|
81
|
+
id: Schema.Collection.Id;
|
|
82
|
+
groupId: Schema.Collection.GroupId;
|
|
83
|
+
};
|
|
84
|
+
type Translation<TId> = {
|
|
85
|
+
from: TId;
|
|
86
|
+
to: TId;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
63
89
|
namespace UploadAsset {
|
|
64
90
|
type Args = AssetClient.CreateAsset.Args & {
|
|
65
91
|
file: Blob;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { RequiredBy, Schema } from '@nascentdigital/funnel-core';
|
|
2
2
|
import { ContentfulData } from './ContentfulData';
|
|
3
3
|
export type ContentfulCollectionData = ContentfulData<ContentfulCollectionData.ContentType, Schema.Collection.Id, Schema.Collection.State> & Omit<Schema.Collection.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'> & {
|
|
4
|
-
references
|
|
4
|
+
references?: ContentfulData.References;
|
|
5
|
+
/**
|
|
6
|
+
* Used specifically to prevent publishing in Contentful if the collection is not valid.
|
|
7
|
+
* The `valid` field is required so setting to null blocks publishing.
|
|
8
|
+
*/
|
|
9
|
+
valid?: boolean;
|
|
5
10
|
};
|
|
6
11
|
export declare namespace ContentfulCollectionData {
|
|
7
12
|
const contentType: "collection";
|
|
@@ -24,9 +24,7 @@ var ContentfulCollectionData;
|
|
|
24
24
|
};
|
|
25
25
|
// helper
|
|
26
26
|
const bindProp = (prop, target, transform) => {
|
|
27
|
-
|
|
28
|
-
collection[target] = transform(data[prop]);
|
|
29
|
-
}
|
|
27
|
+
collection[target] = transform(data[prop]);
|
|
30
28
|
};
|
|
31
29
|
// bind required props to page
|
|
32
30
|
for (const prop of props) {
|
|
@@ -46,10 +44,12 @@ var ContentfulCollectionData;
|
|
|
46
44
|
case 'version':
|
|
47
45
|
case 'name':
|
|
48
46
|
case 'errors':
|
|
49
|
-
case 'pageIds':
|
|
50
|
-
case 'tags':
|
|
51
47
|
bindProp(prop, prop, value => value !== null && value !== void 0 ? value : undefined);
|
|
52
48
|
break;
|
|
49
|
+
// arrays
|
|
50
|
+
case 'tags':
|
|
51
|
+
bindProp(prop, prop, value => value !== null && value !== void 0 ? value : []);
|
|
52
|
+
break;
|
|
53
53
|
// content
|
|
54
54
|
case 'fields':
|
|
55
55
|
bindProp(prop, prop, value => value !== null && value !== void 0 ? value : {});
|
|
@@ -60,6 +60,7 @@ var ContentfulCollectionData;
|
|
|
60
60
|
case 'snapshot':
|
|
61
61
|
case 'snapshotId':
|
|
62
62
|
case 'changeSetId':
|
|
63
|
+
case 'pageIds':
|
|
63
64
|
break;
|
|
64
65
|
// unhandled
|
|
65
66
|
default:
|
|
@@ -15,7 +15,9 @@ export declare namespace ContentfulData {
|
|
|
15
15
|
type EntryId<T extends ContentfulData> = T['id'];
|
|
16
16
|
type References = Array<Link.Entry | Link.Asset>;
|
|
17
17
|
type Draft<T extends ContentfulData> = Omit<T, keyof ContentfulData | '__root'>;
|
|
18
|
-
type Update<T extends ContentfulData> = Pick<ContentfulData, 'id' | '__version'> &
|
|
18
|
+
type Update<T extends ContentfulData> = Pick<ContentfulData, 'id' | '__version'> & {
|
|
19
|
+
[TKey in keyof Draft<T>]?: Draft<T>[TKey] | null;
|
|
20
|
+
};
|
|
19
21
|
type Link<TType extends Link.Type = Link.Type> = {
|
|
20
22
|
sys: {
|
|
21
23
|
type: 'Link';
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { RequiredBy, Schema } from '@nascentdigital/funnel-core';
|
|
2
2
|
import { ContentfulData } from './ContentfulData';
|
|
3
3
|
export type ContentfulPageData = ContentfulData<ContentfulPageData.ContentType, Schema.Page.Id, Schema.Page.State> & Omit<Schema.Page.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'> & {
|
|
4
|
-
references
|
|
4
|
+
references?: ContentfulData.References;
|
|
5
|
+
/**
|
|
6
|
+
* Used specifically to prevent publishing in Contentful if the page is not valid.
|
|
7
|
+
* The `valid` field is required so setting to null blocks publishing.
|
|
8
|
+
*/
|
|
9
|
+
valid?: boolean;
|
|
5
10
|
};
|
|
6
11
|
export declare namespace ContentfulPageData {
|
|
7
12
|
const contentType: "page";
|
|
@@ -38,9 +38,7 @@ var ContentfulPageData;
|
|
|
38
38
|
};
|
|
39
39
|
// helper
|
|
40
40
|
const bindProp = (prop, target, transform) => {
|
|
41
|
-
|
|
42
|
-
page[target] = transform(data[prop]);
|
|
43
|
-
}
|
|
41
|
+
page[target] = transform(data[prop]);
|
|
44
42
|
};
|
|
45
43
|
// bind required props to page
|
|
46
44
|
for (const prop of props) {
|
|
@@ -63,10 +61,13 @@ var ContentfulPageData;
|
|
|
63
61
|
case 'name':
|
|
64
62
|
case 'preview':
|
|
65
63
|
case 'errors':
|
|
66
|
-
case 'tags':
|
|
67
64
|
case 'publication':
|
|
68
65
|
bindProp(prop, prop, value => value !== null && value !== void 0 ? value : undefined);
|
|
69
66
|
break;
|
|
67
|
+
// arrays
|
|
68
|
+
case 'tags':
|
|
69
|
+
bindProp(prop, prop, value => value !== null && value !== void 0 ? value : []);
|
|
70
|
+
break;
|
|
70
71
|
// content
|
|
71
72
|
case 'discover':
|
|
72
73
|
case 'content':
|
|
@@ -12,7 +12,7 @@ export declare class StrapiPageClient implements PageClient {
|
|
|
12
12
|
getGroup({ brand, groupId, type, slug, region, language, version, select }: PageClient.GetGroup.Args): Promise<PageClient.GetGroup.Result>;
|
|
13
13
|
get(args: PageClient.Get.Args): Promise<PageClient.Get.Result>;
|
|
14
14
|
create(args: PageClient.Create.Args): Promise<PageClient.Create.Result>;
|
|
15
|
-
duplicate(
|
|
15
|
+
duplicate(args: PageClient.Duplicate.Args): Promise<PageClient.Duplicate.Result>;
|
|
16
16
|
touch({ id: pageId, version: status }: PageClient.Touch.Args): Promise<PageClient.Touch.Result>;
|
|
17
17
|
update(args: PageClient.Update.Args): Promise<PageClient.Update.Result>;
|
|
18
18
|
replace({ page, version, skipSync }: PageClient.Replace.Args): Promise<PageClient.Replace.Result>;
|
|
@@ -24,18 +24,13 @@ class StrapiPageClient {
|
|
|
24
24
|
async getOverview({ brand, region, version }) {
|
|
25
25
|
const { static: staticPages, dynamic } = await this.provider.customRequest({
|
|
26
26
|
command: 'query/pages',
|
|
27
|
-
params: { brand,
|
|
27
|
+
params: { brand, preview: version === 'draft' ? 'true' : 'false' }
|
|
28
28
|
});
|
|
29
29
|
// transform static links
|
|
30
30
|
return {
|
|
31
31
|
static: staticPages.map(({ type, refs }) => ({
|
|
32
32
|
type,
|
|
33
|
-
refs: Object.
|
|
34
|
-
// map page
|
|
35
|
-
refs[language] = serialization_1.PageData.deserializeRef(ref);
|
|
36
|
-
// keep reducing
|
|
37
|
-
return refs;
|
|
38
|
-
}, {})
|
|
33
|
+
refs: Object.values(refs).map(serialization_1.PageData.deserializeRef)
|
|
39
34
|
})),
|
|
40
35
|
dynamic
|
|
41
36
|
};
|
|
@@ -417,7 +412,7 @@ class StrapiPageClient {
|
|
|
417
412
|
}
|
|
418
413
|
}
|
|
419
414
|
}
|
|
420
|
-
async duplicate(
|
|
415
|
+
async duplicate(args) {
|
|
421
416
|
var _a;
|
|
422
417
|
// resolve current user (or throw)
|
|
423
418
|
const { client, users } = this.provider;
|
|
@@ -427,64 +422,76 @@ class StrapiPageClient {
|
|
|
427
422
|
throw new funnel_core_1.NotAuthenticatedError();
|
|
428
423
|
}
|
|
429
424
|
// get page by id
|
|
425
|
+
const { source } = args;
|
|
430
426
|
const pageCollection = client.collection('pages');
|
|
431
|
-
const {
|
|
432
|
-
fields: serialization_1.PageData.getStrapiFilter(funnel_core_1.Schema.Page.Data.Props.all),
|
|
433
|
-
status: 'draft',
|
|
434
|
-
populate: serialization_1.PageData.Populate.full
|
|
435
|
-
});
|
|
436
|
-
const sourcePage = sourceData;
|
|
427
|
+
const { items: [sourceRef] } = await this.search(Object.assign(Object.assign({}, source), { version: 'draft', pagination: { page: 1, pageSize: 1 } }));
|
|
437
428
|
// throw if there's no page
|
|
438
|
-
if (!
|
|
439
|
-
throw new funnel_core_1.NotFoundError(`Unable to resolve page
|
|
429
|
+
if (!sourceRef) {
|
|
430
|
+
throw new funnel_core_1.NotFoundError(`Unable to resolve page for group "${source.groupId}" in ${source.region}/${source.language}`);
|
|
440
431
|
}
|
|
441
432
|
// TODO: check schema to see if language is possible
|
|
442
433
|
// make sure target language doesn't already exist if we are targeting the current group
|
|
443
|
-
if (
|
|
444
|
-
const {
|
|
445
|
-
|
|
446
|
-
|
|
434
|
+
if (args.operation === 'extend') {
|
|
435
|
+
const { region, language } = args;
|
|
436
|
+
const { data: duplicates } = await pageCollection.find(Object.assign({ fields: ['documentId'] }, getPageQueryParams({
|
|
437
|
+
version: 'draft',
|
|
438
|
+
groupId: sourceRef.groupId,
|
|
439
|
+
region: args.region,
|
|
440
|
+
language: args.language
|
|
441
|
+
})));
|
|
442
|
+
if (duplicates.length > 0) {
|
|
443
|
+
throw new funnel_core_1.InvalidOperationError(`There is already a page in group "${sourceRef.groupId}" in ${region}/${language}`);
|
|
447
444
|
}
|
|
448
445
|
}
|
|
449
446
|
// make sure a static page isn't getting duplicated and that the slug is good
|
|
450
|
-
else
|
|
447
|
+
else {
|
|
451
448
|
// get source page's schema
|
|
452
449
|
const site = funnel_core_1.Schema.Site.current();
|
|
453
|
-
const schema = site.regions[
|
|
450
|
+
const schema = site.regions[sourceRef.region][sourceRef.type];
|
|
454
451
|
// cannot duplicate a static page
|
|
455
452
|
if (schema.variant === 'static') {
|
|
456
|
-
throw new funnel_core_1.InvalidOperationError(`cannot duplicate a static page "${
|
|
453
|
+
throw new funnel_core_1.InvalidOperationError(`cannot duplicate a static page "${sourceRef.name}"`);
|
|
457
454
|
}
|
|
458
455
|
// slug must be provided
|
|
459
|
-
|
|
456
|
+
const { region, slug } = args;
|
|
457
|
+
if (slug === undefined) {
|
|
460
458
|
throw new funnel_core_1.ArgumentError('slug must be provided when duplicating a page');
|
|
461
459
|
}
|
|
462
460
|
// validate slug
|
|
463
461
|
const validateSlugResult = await this.validateSlug({
|
|
464
|
-
brand:
|
|
465
|
-
type:
|
|
466
|
-
region
|
|
467
|
-
slug
|
|
462
|
+
brand: sourceRef.brand,
|
|
463
|
+
type: sourceRef.type,
|
|
464
|
+
region,
|
|
465
|
+
slug
|
|
468
466
|
});
|
|
469
467
|
if (validateSlugResult.status !== 'available') {
|
|
470
468
|
throw new errors_1.SlugValidationError({
|
|
471
|
-
slug
|
|
469
|
+
slug,
|
|
472
470
|
reason: validateSlugResult
|
|
473
471
|
});
|
|
474
472
|
}
|
|
475
473
|
}
|
|
476
|
-
//
|
|
477
|
-
const {
|
|
474
|
+
// fetch the page for the source
|
|
475
|
+
const { data: sourceData } = await pageCollection.findOne(sourceRef.id, {
|
|
476
|
+
fields: serialization_1.PageData.getStrapiFilter(funnel_core_1.Schema.Page.Data.Props.all),
|
|
477
|
+
status: 'draft',
|
|
478
|
+
populate: serialization_1.PageData.Populate.full
|
|
479
|
+
});
|
|
480
|
+
const sourcePage = sourceData;
|
|
481
|
+
if (!sourcePage) {
|
|
482
|
+
throw new funnel_core_1.NotFoundError(`Unable to resolve page with id "${sourceRef.id}" in ${source.region}/${source.language}`);
|
|
483
|
+
}
|
|
484
|
+
const { pageRefs, collectionRefs, assetRefs } = sourcePage;
|
|
478
485
|
// target refs and body
|
|
479
486
|
let targetPageRefIds;
|
|
480
487
|
let targetCollectionRefIds;
|
|
481
488
|
let targetBody;
|
|
482
489
|
// if we are targeting a different language then we need to "translate" the references
|
|
483
|
-
if (
|
|
490
|
+
if (args.language !== sourcePage.lang) {
|
|
484
491
|
// fetch target language refs
|
|
485
492
|
const { pageIdMappings, collectionIdMappings } = await Strapi_1.Strapi.translateReferences({
|
|
486
493
|
client,
|
|
487
|
-
targetLanguage,
|
|
494
|
+
targetLanguage: args.language,
|
|
488
495
|
pageRefs,
|
|
489
496
|
collectionRefs
|
|
490
497
|
});
|
|
@@ -515,9 +522,11 @@ class StrapiPageClient {
|
|
|
515
522
|
// create duplicate page
|
|
516
523
|
try {
|
|
517
524
|
// create target page using content of source
|
|
518
|
-
const { data: dupData } = await pageCollection.create(Object.assign({ groupId:
|
|
525
|
+
const { data: dupData } = await pageCollection.create(Object.assign({ groupId: args.operation === 'extend'
|
|
519
526
|
? sourcePage.groupId
|
|
520
|
-
: funnel_core_1.Value.uuid(), type: sourcePage.type, brand: sourcePage.brand, region: sourcePage.region, lang:
|
|
527
|
+
: funnel_core_1.Value.uuid(), type: sourcePage.type, brand: sourcePage.brand, region: sourcePage.region, lang: args.language, slug: args.operation === 'extend'
|
|
528
|
+
? sourcePage.slug
|
|
529
|
+
: args.slug, name: sourcePage.name, tags: sourcePage.tags, url: sourcePage.url, state: 'draft', errors: sourcePage.errors, assetRefs: { set: assetRefs.map(ref => ref.documentId) }, pageRefs: { set: targetPageRefIds }, collectionRefs: { set: targetCollectionRefIds } }, targetBody), { status: 'draft', populate: serialization_1.PageData.Populate.full });
|
|
521
530
|
const dupPageData = dupData;
|
|
522
531
|
console.debug('[StrapiPageClient] duplicate: created duplicate page', { dupPageData });
|
|
523
532
|
// touch new duplicated page to update url/name
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nascentdigital/funnel-cms",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.6",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/nascentdigital/funnel.git",
|
|
6
6
|
"directory": "libs/cms"
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"ts-jest": "^29",
|
|
49
49
|
"type-fest": "^4"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "63e7e3fa90a0d490576311a388e8a0d99bd4b5e4"
|
|
52
52
|
}
|