@nascentdigital/funnel-cms 4.4.4

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 (125) hide show
  1. package/dist/api/AssetClient.d.ts +53 -0
  2. package/dist/api/AssetClient.js +2 -0
  3. package/dist/api/AssetUploadClient.d.ts +17 -0
  4. package/dist/api/AssetUploadClient.js +26 -0
  5. package/dist/api/AuthClient.d.ts +29 -0
  6. package/dist/api/AuthClient.js +2 -0
  7. package/dist/api/ChangeSetClient.d.ts +84 -0
  8. package/dist/api/ChangeSetClient.js +31 -0
  9. package/dist/api/CollectionClient.d.ts +187 -0
  10. package/dist/api/CollectionClient.js +2 -0
  11. package/dist/api/ContentClient.d.ts +43 -0
  12. package/dist/api/ContentClient.js +34 -0
  13. package/dist/api/IAssetApiClient.d.ts +9 -0
  14. package/dist/api/IAssetApiClient.js +2 -0
  15. package/dist/api/PageClient.d.ts +264 -0
  16. package/dist/api/PageClient.js +2 -0
  17. package/dist/api/SiteClient.d.ts +23 -0
  18. package/dist/api/SiteClient.js +2 -0
  19. package/dist/api/SiteConfigClient.d.ts +26 -0
  20. package/dist/api/SiteConfigClient.js +2 -0
  21. package/dist/api/UpgradeClient.d.ts +16 -0
  22. package/dist/api/UpgradeClient.js +2 -0
  23. package/dist/api/UserClient.d.ts +46 -0
  24. package/dist/api/UserClient.js +2 -0
  25. package/dist/api/index.d.ts +11 -0
  26. package/dist/api/index.js +27 -0
  27. package/dist/errors/CMSError.d.ts +8 -0
  28. package/dist/errors/CMSError.js +17 -0
  29. package/dist/errors/CMSErrors.d.ts +5 -0
  30. package/dist/errors/CMSErrors.js +36 -0
  31. package/dist/errors/LinkedDependantsError.d.ts +21 -0
  32. package/dist/errors/LinkedDependantsError.js +31 -0
  33. package/dist/errors/LockdownError.d.ts +4 -0
  34. package/dist/errors/LockdownError.js +12 -0
  35. package/dist/errors/SlugValidationError.d.ts +21 -0
  36. package/dist/errors/SlugValidationError.js +23 -0
  37. package/dist/errors/TranslateReferencesError.d.ts +21 -0
  38. package/dist/errors/TranslateReferencesError.js +22 -0
  39. package/dist/errors/index.d.ts +4 -0
  40. package/dist/errors/index.js +25 -0
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.js +19 -0
  43. package/dist/models/Entity.d.ts +14 -0
  44. package/dist/models/Entity.js +2 -0
  45. package/dist/models/Environment.d.ts +1 -0
  46. package/dist/models/Environment.js +2 -0
  47. package/dist/models/Pagination.d.ts +20 -0
  48. package/dist/models/Pagination.js +21 -0
  49. package/dist/models/Privilege.d.ts +57 -0
  50. package/dist/models/Privilege.js +163 -0
  51. package/dist/models/Role.d.ts +33 -0
  52. package/dist/models/Role.js +118 -0
  53. package/dist/models/SiteConfig.d.ts +14 -0
  54. package/dist/models/SiteConfig.js +2 -0
  55. package/dist/models/Upgrade.d.ts +40 -0
  56. package/dist/models/Upgrade.js +19 -0
  57. package/dist/models/User.d.ts +41 -0
  58. package/dist/models/User.js +71 -0
  59. package/dist/models/Versioned.d.ts +22 -0
  60. package/dist/models/Versioned.js +45 -0
  61. package/dist/models/index.d.ts +9 -0
  62. package/dist/models/index.js +25 -0
  63. package/dist/strapi/IStrapiContentClient.d.ts +39 -0
  64. package/dist/strapi/IStrapiContentClient.js +2 -0
  65. package/dist/strapi/Strapi.d.ts +37 -0
  66. package/dist/strapi/Strapi.js +156 -0
  67. package/dist/strapi/StrapiAssetClient.d.ts +11 -0
  68. package/dist/strapi/StrapiAssetClient.js +143 -0
  69. package/dist/strapi/StrapiAssetUploadClient.d.ts +6 -0
  70. package/dist/strapi/StrapiAssetUploadClient.js +109 -0
  71. package/dist/strapi/StrapiAuthClient.d.ts +9 -0
  72. package/dist/strapi/StrapiAuthClient.js +64 -0
  73. package/dist/strapi/StrapiChangeSetClient.d.ts +22 -0
  74. package/dist/strapi/StrapiChangeSetClient.js +218 -0
  75. package/dist/strapi/StrapiCollectionClient.d.ts +18 -0
  76. package/dist/strapi/StrapiCollectionClient.js +610 -0
  77. package/dist/strapi/StrapiContentClient.d.ts +38 -0
  78. package/dist/strapi/StrapiContentClient.js +105 -0
  79. package/dist/strapi/StrapiContentClientFactory.d.ts +13 -0
  80. package/dist/strapi/StrapiContentClientFactory.js +19 -0
  81. package/dist/strapi/StrapiPageClient.d.ts +23 -0
  82. package/dist/strapi/StrapiPageClient.js +882 -0
  83. package/dist/strapi/StrapiSiteConfigClient.d.ts +8 -0
  84. package/dist/strapi/StrapiSiteConfigClient.js +50 -0
  85. package/dist/strapi/StrapiUpgradeClient.d.ts +13 -0
  86. package/dist/strapi/StrapiUpgradeClient.js +118 -0
  87. package/dist/strapi/StrapiUserClient.d.ts +17 -0
  88. package/dist/strapi/StrapiUserClient.js +199 -0
  89. package/dist/strapi/client.d.ts +1 -0
  90. package/dist/strapi/client.js +17 -0
  91. package/dist/strapi/index.d.ts +3 -0
  92. package/dist/strapi/index.js +19 -0
  93. package/dist/strapi/models/StrapiAsset.d.ts +10 -0
  94. package/dist/strapi/models/StrapiAsset.js +16 -0
  95. package/dist/strapi/models/StrapiData.d.ts +13 -0
  96. package/dist/strapi/models/StrapiData.js +16 -0
  97. package/dist/strapi/models/StrapiFile.d.ts +18 -0
  98. package/dist/strapi/models/StrapiFile.js +2 -0
  99. package/dist/strapi/models/index.d.ts +3 -0
  100. package/dist/strapi/models/index.js +19 -0
  101. package/dist/strapi/serialization/ChangeSetData.d.ts +15 -0
  102. package/dist/strapi/serialization/ChangeSetData.js +76 -0
  103. package/dist/strapi/serialization/CollectionData.d.ts +57 -0
  104. package/dist/strapi/serialization/CollectionData.js +258 -0
  105. package/dist/strapi/serialization/CollectionSnapshotData.d.ts +16 -0
  106. package/dist/strapi/serialization/CollectionSnapshotData.js +66 -0
  107. package/dist/strapi/serialization/PageData.d.ts +126 -0
  108. package/dist/strapi/serialization/PageData.js +335 -0
  109. package/dist/strapi/serialization/PageSnapshotData.d.ts +20 -0
  110. package/dist/strapi/serialization/PageSnapshotData.js +69 -0
  111. package/dist/strapi/serialization/RoleData.d.ts +5 -0
  112. package/dist/strapi/serialization/RoleData.js +2 -0
  113. package/dist/strapi/serialization/SiteConfigData.d.ts +11 -0
  114. package/dist/strapi/serialization/SiteConfigData.js +23 -0
  115. package/dist/strapi/serialization/StrapiAssetData.d.ts +17 -0
  116. package/dist/strapi/serialization/StrapiAssetData.js +39 -0
  117. package/dist/strapi/serialization/UserData.d.ts +18 -0
  118. package/dist/strapi/serialization/UserData.js +42 -0
  119. package/dist/strapi/serialization/VersionData.d.ts +26 -0
  120. package/dist/strapi/serialization/VersionData.js +88 -0
  121. package/dist/strapi/serialization/index.d.ts +10 -0
  122. package/dist/strapi/serialization/index.js +26 -0
  123. package/dist/strapi/test.d.ts +563 -0
  124. package/dist/strapi/test.js +1131 -0
  125. package/package.json +42 -0
@@ -0,0 +1,882 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.StrapiPageClient = void 0;
15
+ const funnel_core_1 = require("@nascentdigital/funnel-core");
16
+ const errors_1 = require("../errors");
17
+ const serialization_1 = require("./serialization");
18
+ const Strapi_1 = require("./Strapi");
19
+ // client extension
20
+ class StrapiPageClient {
21
+ constructor(provider) {
22
+ this.provider = provider;
23
+ }
24
+ async getOverview({ brand, region, version }) {
25
+ const { static: staticPages, dynamic } = await this.provider.customRequest({
26
+ command: 'query/pages',
27
+ params: { brand, region, preview: version === 'draft' ? 'true' : 'false' }
28
+ });
29
+ // transform static links
30
+ return {
31
+ static: staticPages.map(({ type, refs }) => ({
32
+ type,
33
+ refs: Object.entries(refs).reduce((refs, [language, ref]) => {
34
+ // map page
35
+ refs[language] = serialization_1.PageData.deserializeRef(ref);
36
+ // keep reducing
37
+ return refs;
38
+ }, {})
39
+ })),
40
+ dynamic
41
+ };
42
+ }
43
+ async getSummaries(args) {
44
+ // fetch all published pages (as summaries)
45
+ const client = this.provider.client;
46
+ const strapiUrl = client.baseURL;
47
+ const collection = client.collection('pages');
48
+ const resultProps = funnel_core_1.Schema.Page.Data.AsLink.props;
49
+ const entries = await Strapi_1.Strapi.paginate(Object.assign({ collection, pagination: 'all', fields: serialization_1.PageData.getStrapiFilter(resultProps), sort: args.version === 'published'
50
+ ? ['type', 'publishedOn:desc', 'updatedAt:desc']
51
+ : ['type', 'updatedAt:desc'] }, getPageQueryParams(args)));
52
+ // transform to dtos
53
+ return entries.items.map(item => serialization_1.PageData.deserialize(item, strapiUrl, resultProps));
54
+ }
55
+ async getPreviews(_a) {
56
+ var { pagination } = _a, args = __rest(_a, ["pagination"]);
57
+ // fetch all pages that match (as link data)
58
+ const client = this.provider.client;
59
+ const strapiUrl = client.baseURL;
60
+ const collection = client.collection('pages');
61
+ const resultProps = funnel_core_1.Schema.Page.Data.AsPreview.props;
62
+ const { filters, status } = getPageQueryParams(args);
63
+ const _b = await Strapi_1.Strapi.paginate({
64
+ collection,
65
+ pagination,
66
+ fields: serialization_1.PageData.getStrapiFilter(resultProps),
67
+ filters: {
68
+ $and: [
69
+ Object.assign(Object.assign({}, filters), { preview: { $not: { $eq: '{}' } } }),
70
+ { preview: { $notNull: true } }
71
+ ]
72
+ },
73
+ populate: serialization_1.PageData.Populate.preview,
74
+ sort: args.version === 'published'
75
+ ? ['publishedOn:desc', 'updatedAt:desc']
76
+ : ['updatedAt:desc'],
77
+ status
78
+ }), { items } = _b, paginated = __rest(_b, ["items"]);
79
+ // transform and return items
80
+ return Object.assign({ items: items.map(item => serialization_1.PageData.deserialize(item, strapiUrl, resultProps)) }, paginated);
81
+ }
82
+ async search(_a) {
83
+ var { pagination } = _a, args = __rest(_a, ["pagination"]);
84
+ // fetch all pages that match (as link data)
85
+ const client = this.provider.client;
86
+ const strapiUrl = client.baseURL;
87
+ const collection = client.collection('pages');
88
+ const resultProps = funnel_core_1.Schema.Page.Data.AsLink.props;
89
+ const _b = await Strapi_1.Strapi.paginate(Object.assign({ collection,
90
+ pagination, fields: serialization_1.PageData.getStrapiFilter(resultProps), sort: args.version === 'published'
91
+ ? ['publishedOn:desc', 'updatedAt:desc']
92
+ : ['updatedAt:desc'] }, getPageQueryParams(args))), { items } = _b, paginated = __rest(_b, ["items"]);
93
+ // transform and return items
94
+ return Object.assign({ items: items.map(item => serialization_1.PageData.deserialize(item, strapiUrl, resultProps)) }, paginated);
95
+ }
96
+ async query(_a) {
97
+ var { pagination } = _a, args = __rest(_a, ["pagination"]);
98
+ // setup
99
+ const client = this.provider.client;
100
+ const strapiUrl = client.baseURL;
101
+ const collection = client.collection('pages');
102
+ const resultProps = funnel_core_1.Schema.Page.Data.AsPreview.props;
103
+ // fetch ranking of pages
104
+ const { type, version, brand, region, language, sort, excludeIds = [] } = args;
105
+ const rankedPageIds = await this.provider.customRequest({
106
+ method: 'GET',
107
+ command: 'query/rank-pages',
108
+ params: {
109
+ preview: version === 'draft',
110
+ type: funnel_core_1.ArrayUtils.coerce(type),
111
+ brand, region, language, sort
112
+ }
113
+ });
114
+ // filter out excluded ids
115
+ const filteredRankedPageIds = rankedPageIds.filter(id => !excludeIds.includes(id));
116
+ // get subset of ids based on pagination
117
+ const start = pagination === 'all' ? 0 : pagination.page - 1;
118
+ const end = pagination === 'all' ? rankedPageIds.length : pagination.page * pagination.pageSize;
119
+ const scopedRankedPageIds = filteredRankedPageIds.slice(start, end);
120
+ // fetch page previews by ids
121
+ const { data: unorderedPages } = await collection.find(Object.assign({ fields: serialization_1.PageData.getStrapiFilter(resultProps), populate: serialization_1.PageData.Populate.preview }, getPageQueryParams(Object.assign(Object.assign({}, args), { ids: scopedRankedPageIds }))));
122
+ // apply sorting to fetched page previews
123
+ const rankedPages = scopedRankedPageIds.map(id => {
124
+ const pageIndex = unorderedPages.findIndex(page => page.documentId === id);
125
+ const [page] = unorderedPages.splice(pageIndex, 1);
126
+ return page;
127
+ });
128
+ // deserialize pages and return
129
+ return {
130
+ items: rankedPages.map(page => serialization_1.PageData.deserialize(page, strapiUrl, resultProps)),
131
+ page: pagination === 'all' ? 1 : pagination.page,
132
+ pageCount: pagination === 'all' ? 1 : Math.ceil(filteredRankedPageIds.length / pagination.pageSize),
133
+ totalItemCount: filteredRankedPageIds.length
134
+ };
135
+ }
136
+ async find(_a) {
137
+ var { pagination } = _a, args = __rest(_a, ["pagination"]);
138
+ // fetch all pages that match (as link data)
139
+ const client = this.provider.client;
140
+ const strapiUrl = client.baseURL;
141
+ const collection = client.collection('pages');
142
+ const resultProps = funnel_core_1.Schema.Page.Data.Props.all;
143
+ const _b = await Strapi_1.Strapi.paginate(Object.assign({ collection,
144
+ pagination, fields: serialization_1.PageData.getStrapiFilter(resultProps), sort: args.version === 'published'
145
+ ? ['publishedOn:desc', 'updatedAt:desc']
146
+ : ['updatedAt:desc'], populate: serialization_1.PageData.Populate.full }, getPageQueryParams(args))), { items } = _b, paginated = __rest(_b, ["items"]);
147
+ // transform to DTOs
148
+ return Object.assign({ items: items.map(item => serialization_1.PageData.deserialize(item, strapiUrl, resultProps)) }, paginated);
149
+ }
150
+ async getGroup({ brand, groupId, type, slug, region, language, version }) {
151
+ // create base result
152
+ const result = {
153
+ group: { groupId: groupId !== null && groupId !== void 0 ? groupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, region, instances: [] }
154
+ };
155
+ // fetch baseline page (fail if it doesn't exist)
156
+ const client = this.provider.client;
157
+ const collection = client.collection('pages');
158
+ const { items: baselineItems = [] } = await Strapi_1.Strapi.paginate(Object.assign({ collection, pagination: 'all', fields: serialization_1.PageData.getStrapiFilter(funnel_core_1.Schema.Page.Data.Props.id) }, getPageQueryParams({ version, brand, groupId, type, slug, region })));
159
+ const [baselinePage] = baselineItems;
160
+ if (!baselinePage) {
161
+ return result;
162
+ }
163
+ // FIXME: fail if multiple group ids come back in the result
164
+ // fetch all pages (as summaries) using the groupId to target
165
+ const { items = [] } = await Strapi_1.Strapi.paginate(Object.assign({ collection, pagination: 'all', fields: serialization_1.PageData.getStrapiFilter(funnel_core_1.Schema.Page.Data.AsGroup.Instance.props) }, getPageQueryParams({ version, brand, groupId: baselinePage.groupId })));
166
+ // add items if any
167
+ const { group } = result;
168
+ for (const item of items) {
169
+ // connect groupId + preset (should all be the same, so it's ok to pave over)
170
+ group.groupId = item.groupId;
171
+ // add instance
172
+ const instance = serialization_1.PageData.deserialize(item, client.baseURL, funnel_core_1.Schema.Page.Data.AsGroup.Instance.props);
173
+ group.instances.push(instance);
174
+ // set page if language matches
175
+ if (instance.language === language) {
176
+ result.page = await this.get({ id: instance.id, version });
177
+ }
178
+ }
179
+ // return result
180
+ return result;
181
+ }
182
+ async get(args) {
183
+ // get page by id
184
+ const client = this.provider.client;
185
+ const strapiUrl = client.baseURL;
186
+ const collection = client.collection('pages');
187
+ const resultProps = funnel_core_1.Schema.Page.Data.Props.all;
188
+ let data;
189
+ if ('id' in args) {
190
+ // find entry by document id
191
+ const { data: rawData } = await collection.findOne(args.id, {
192
+ fields: serialization_1.PageData.getStrapiFilter(resultProps),
193
+ status: args.version,
194
+ populate: serialization_1.PageData.Populate.full
195
+ });
196
+ // skip if there's no entry
197
+ if (!rawData) {
198
+ console.debug(`[StrapiPageClient/getPage] no "${args.version}" entry found for id ${args.id}`);
199
+ return undefined;
200
+ }
201
+ // or capture it
202
+ data = rawData;
203
+ }
204
+ // or by querying type (including region, and language)
205
+ else {
206
+ // lookup entry from collection (might be a miss)
207
+ const { data: rawData } = await collection.find(Object.assign({ fields: serialization_1.PageData.getStrapiFilter(resultProps), sort: ['updatedAt:desc'], populate: serialization_1.PageData.Populate.full }, getPageQueryParams(args)));
208
+ // skip if there's no entry
209
+ if (rawData.length === 0) {
210
+ console.warn(`[StrapiPageClient/getStaticPage] no "${args.version}" entry found for type ${args.type} in region ${args.region}/${args.language}`);
211
+ return undefined;
212
+ }
213
+ // or capture entry
214
+ data = rawData[0];
215
+ }
216
+ // convert and return
217
+ return serialization_1.PageData.deserialize(data, strapiUrl, resultProps);
218
+ }
219
+ async create(args) {
220
+ // resolve current user (or throw)
221
+ const { client, users } = this.provider;
222
+ const strapiUrl = client.baseURL;
223
+ const user = await users.me();
224
+ if (!user) {
225
+ throw new funnel_core_1.NotAuthenticatedError();
226
+ }
227
+ // create a new language of an existing page
228
+ if (funnel_core_1.Schema.Page.Data.AsNewLanguage.filter(args)) {
229
+ // fetch all pages in group (summaries only)
230
+ const existingPages = await this.getSummaries({
231
+ version: 'draft',
232
+ brand: args.brand,
233
+ groupId: args.groupId
234
+ });
235
+ // throw if there are no pages
236
+ if (existingPages.length === 0) {
237
+ throw new funnel_core_1.ArgumentError(`Page group "${args.groupId}" no longer exists in "${args.brand}"`);
238
+ }
239
+ // return existing page if it's there
240
+ let existingPage = existingPages.find(existing => existing.language === args.language);
241
+ if (existingPage) {
242
+ return this.getGroup(Object.assign(Object.assign({}, existingPage), { version: 'draft' }));
243
+ }
244
+ // bind data + update url
245
+ existingPage = existingPages[0];
246
+ const draft = funnel_core_1.Schema.PageModel.fromDraft(Object.assign({ type: existingPage.type, slug: existingPage.slug, url: existingPage.url, region: existingPage.region, name: existingPage.name, tags: existingPage.tags }, args));
247
+ draft.sync();
248
+ // resolve change set (rebind)
249
+ const { provider } = this;
250
+ const changeSet = await provider.changeSets.resolve();
251
+ // create page
252
+ try {
253
+ // connect page to changeset
254
+ draft.data.changeSetId = changeSet.id;
255
+ // create data model based on existing page
256
+ const { data } = await client.collection('pages')
257
+ .create(serialization_1.PageData.serialize(draft.data), {
258
+ status: 'draft',
259
+ populate: serialization_1.PageData.Populate.full
260
+ });
261
+ // transform the deserialized entry
262
+ const page = serialization_1.PageData.deserialize(data, strapiUrl, funnel_core_1.Schema.Page.Data.Props.all);
263
+ // add page to changeset
264
+ await provider.changeSets.update({
265
+ id: changeSet.id,
266
+ changes: [
267
+ ...changeSet.changes,
268
+ {
269
+ __type: 'page',
270
+ type: 'create',
271
+ schema: page.type,
272
+ id: page.id,
273
+ name: page.name,
274
+ region: page.region,
275
+ language: page.language,
276
+ slug: page.slug,
277
+ errors: page.errors,
278
+ attribution: [
279
+ { userId: user.id, updatedOn: Date.now() }
280
+ ]
281
+ }
282
+ ]
283
+ });
284
+ // return new page
285
+ return {
286
+ group: {
287
+ groupId: page.groupId,
288
+ brand: page.brand,
289
+ type: page.type,
290
+ region: page.region,
291
+ slug: page.slug,
292
+ instances: [
293
+ ...existingPages.map(({ id, url, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn }) => ({ id, url, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn })),
294
+ {
295
+ id: page.id,
296
+ url: page.url,
297
+ language: page.language,
298
+ name: page.name,
299
+ slug: page.slug,
300
+ tags: page.tags,
301
+ state: page.state,
302
+ errors: page.errors,
303
+ createdOn: page.createdOn,
304
+ updatedOn: page.updatedOn,
305
+ publishedOn: page.publishedOn
306
+ }
307
+ ]
308
+ },
309
+ page
310
+ };
311
+ }
312
+ // remove
313
+ catch (e) {
314
+ // discard if it had no changes
315
+ if (changeSet.changes.length === 0) {
316
+ await provider.changeSets.delete(changeSet.id);
317
+ }
318
+ // throw error
319
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
320
+ }
321
+ }
322
+ // or create a net new page
323
+ else {
324
+ // get page's template
325
+ const site = funnel_core_1.Schema.Site.current();
326
+ const schema = site.regions[args.region][args.type];
327
+ // check slug if dynamic page
328
+ if (schema.variant === 'dynamic') {
329
+ // make sure there is a slug for a dynamic page
330
+ if (args.slug === undefined) {
331
+ throw new funnel_core_1.ArgumentError(`A "${args.type}" Page must provide a slug when created`);
332
+ }
333
+ // validate slug
334
+ const validateSlugResult = await this.validateSlug(Object.assign(Object.assign({}, args), { slug: args.slug }));
335
+ if (validateSlugResult.status !== 'available') {
336
+ throw new errors_1.SlugValidationError({ slug: args.slug, reason: validateSlugResult });
337
+ }
338
+ }
339
+ // fetch all pages matching brand/type/region
340
+ const { group: existingGroup, page: existingPage } = await this.getGroup({
341
+ version: 'draft',
342
+ brand: args.brand,
343
+ type: args.type,
344
+ region: args.region,
345
+ slug: args.slug,
346
+ language: args.language
347
+ });
348
+ // return existing page if it's there
349
+ if (existingPage) {
350
+ return { group: existingGroup, page: existingPage };
351
+ }
352
+ // bind data + update url
353
+ const draft = funnel_core_1.Schema.PageModel.fromDraft(Object.assign(Object.assign({}, args), { groupId: existingGroup.groupId === funnel_core_1.Schema.Page.GroupId.empty
354
+ ? funnel_core_1.Value.uuid()
355
+ : existingGroup.groupId, url: '' }));
356
+ draft.sync();
357
+ // resolve change set (rebind)
358
+ const { provider } = this;
359
+ const changeSet = await provider.changeSets.resolve();
360
+ // create page
361
+ try {
362
+ // connect page to changeset
363
+ draft.data.changeSetId = changeSet.id;
364
+ // create page
365
+ const { data } = await client.collection('pages')
366
+ .create(serialization_1.PageData.serialize(draft.data), {
367
+ status: 'draft',
368
+ populate: serialization_1.PageData.Populate.full
369
+ });
370
+ // transform the deserialized entry
371
+ const page = serialization_1.PageData.deserialize(data, strapiUrl, funnel_core_1.Schema.Page.Data.Props.all);
372
+ // add page to changeset
373
+ await provider.changeSets.update({
374
+ id: changeSet.id,
375
+ changes: [
376
+ ...changeSet.changes,
377
+ {
378
+ __type: 'page',
379
+ type: 'create',
380
+ schema: page.type,
381
+ id: page.id,
382
+ name: page.name,
383
+ region: page.region,
384
+ language: page.language,
385
+ slug: page.slug,
386
+ errors: page.errors,
387
+ attribution: [
388
+ { userId: user.id, updatedOn: Date.now() }
389
+ ]
390
+ }
391
+ ]
392
+ });
393
+ // return page
394
+ return {
395
+ group: {
396
+ groupId: page.groupId,
397
+ brand: page.brand,
398
+ type: page.type,
399
+ region: args.region,
400
+ slug: page.slug,
401
+ instances: [
402
+ ...existingGroup.instances,
403
+ {
404
+ id: page.id,
405
+ url: page.url,
406
+ language: page.language,
407
+ name: page.name,
408
+ slug: page.slug,
409
+ tags: page.tags,
410
+ state: page.state,
411
+ errors: page.errors,
412
+ createdOn: page.createdOn,
413
+ updatedOn: page.updatedOn,
414
+ publishedOn: page.publishedOn
415
+ }
416
+ ]
417
+ },
418
+ page
419
+ };
420
+ }
421
+ // remove
422
+ catch (e) {
423
+ // discard page if it had no changes
424
+ if (changeSet.changes.length === 0) {
425
+ await provider.changeSets.delete(changeSet.id);
426
+ }
427
+ // throw error
428
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
429
+ }
430
+ }
431
+ }
432
+ async duplicate({ source: pageId, targetPage, targetLanguage, targetSlug }) {
433
+ var _a;
434
+ // resolve current user (or throw)
435
+ const { client, users } = this.provider;
436
+ const strapiUrl = client.baseURL;
437
+ const user = await users.me();
438
+ if (!user) {
439
+ throw new funnel_core_1.NotAuthenticatedError();
440
+ }
441
+ // get page by id
442
+ const pageCollection = client.collection('pages');
443
+ const { data: sourceData } = await pageCollection.findOne(pageId, {
444
+ fields: serialization_1.PageData.getStrapiFilter(funnel_core_1.Schema.Page.Data.Props.all),
445
+ status: 'draft',
446
+ populate: serialization_1.PageData.Populate.full
447
+ });
448
+ const sourcePage = sourceData;
449
+ // throw if there's no page
450
+ if (!sourcePage) {
451
+ throw new funnel_core_1.NotFoundError(`Unable to resolve page with id "${pageId}"`);
452
+ }
453
+ // TODO: check schema to see if language is possible
454
+ // make sure target language doesn't already exist if we are targeting the current group
455
+ if (targetPage === 'current') {
456
+ const { data: pagesWithTargetLanguage } = await pageCollection.find(Object.assign({ fields: ['documentId'] }, getPageQueryParams({ version: 'draft', groupId: sourcePage.groupId, language: targetLanguage })));
457
+ if (pagesWithTargetLanguage.length > 0) {
458
+ throw new funnel_core_1.InvalidOperationError(`There is already a page in group "${sourcePage.groupId}" with ${targetLanguage} language`);
459
+ }
460
+ }
461
+ // make sure a static page isn't getting duplicated and that the slug is good
462
+ else if (targetPage === 'new') {
463
+ // get source page's schema
464
+ const site = funnel_core_1.Schema.Site.current();
465
+ const schema = site.regions[sourcePage.region][sourcePage.type];
466
+ // cannot duplicate a static page
467
+ if (schema.variant === 'static') {
468
+ throw new funnel_core_1.InvalidOperationError(`cannot duplicate a static page "${sourcePage.name}"`);
469
+ }
470
+ // slug must be provided
471
+ if (targetSlug === undefined) {
472
+ throw new funnel_core_1.ArgumentError('slug must be provided when duplicating a page');
473
+ }
474
+ // validate slug
475
+ const validateSlugResult = await this.validateSlug({
476
+ brand: sourcePage.brand,
477
+ type: sourcePage.type,
478
+ region: sourcePage.region,
479
+ slug: targetSlug
480
+ });
481
+ if (validateSlugResult.status !== 'available') {
482
+ throw new errors_1.SlugValidationError({
483
+ slug: targetSlug,
484
+ reason: validateSlugResult
485
+ });
486
+ }
487
+ }
488
+ // get refs from source page
489
+ const { assetRefs, pageRefs, collectionRefs } = sourcePage;
490
+ // target refs and body
491
+ let targetPageRefIds;
492
+ let targetCollectionRefIds;
493
+ let targetBody;
494
+ // if we are targeting a different language then we need to "translate" the references
495
+ if (targetLanguage !== sourcePage.lang) {
496
+ // fetch target language refs
497
+ const { pageIdMappings, collectionIdMappings } = await Strapi_1.Strapi.translateReferences({
498
+ client,
499
+ targetLanguage,
500
+ pageRefs,
501
+ collectionRefs
502
+ });
503
+ console.debug('[StrapiPageClient] duplicate: calculated mappings (source to target)', { pageIdMappings, collectionIdMappings });
504
+ // set target values
505
+ targetPageRefIds = pageIdMappings.map(mapping => mapping.targetId);
506
+ targetCollectionRefIds = collectionIdMappings.map(mapping => mapping.targetId);
507
+ targetBody = {
508
+ discover: Strapi_1.Strapi.updateRefs({ value: sourcePage.discover, pageIdMappings, collectionIdMappings }),
509
+ preview: Strapi_1.Strapi.updateRefs({ value: sourcePage.preview, pageIdMappings, collectionIdMappings }),
510
+ content: Strapi_1.Strapi.updateRefs({ value: sourcePage.content, pageIdMappings, collectionIdMappings }),
511
+ };
512
+ }
513
+ // otherwise we can keep everything the same as the source page
514
+ else {
515
+ // set target values
516
+ targetPageRefIds = pageRefs.map(ref => ref.documentId);
517
+ targetCollectionRefIds = collectionRefs.map(ref => ref.documentId);
518
+ targetBody = {
519
+ discover: sourcePage.discover,
520
+ preview: sourcePage.preview,
521
+ content: sourcePage.content
522
+ };
523
+ }
524
+ // resolve change set (rebind)
525
+ const { provider } = this;
526
+ const changeSet = await provider.changeSets.resolve();
527
+ // create duplicate page
528
+ try {
529
+ // create target page using content of source
530
+ const { data: dupData } = await pageCollection.create(Object.assign({ groupId: targetPage === 'current'
531
+ ? sourcePage.groupId
532
+ : funnel_core_1.Value.uuid(), type: sourcePage.type, brand: sourcePage.brand, region: sourcePage.region, lang: targetLanguage, slug: targetSlug !== null && targetSlug !== void 0 ? targetSlug : sourcePage.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 });
533
+ const dupPageData = dupData;
534
+ console.debug('[StrapiPageClient] duplicate: created duplicate page', { dupPageData });
535
+ // touch new duplicated page to update url/name
536
+ let dupPage = serialization_1.PageData.deserialize(dupPageData, strapiUrl, funnel_core_1.Schema.Page.Data.Props.all);
537
+ const dupPageModel = funnel_core_1.Schema.PageModel.from(dupPage);
538
+ dupPageModel.sync();
539
+ // connect page to changeset
540
+ dupPage.changeSetId = changeSet.id;
541
+ // update duplicate page with updated url/name
542
+ const update = serialization_1.PageData.serialize(dupPage);
543
+ const { data: updatedData } = await pageCollection.update(dupPage.id, update, { status: 'draft' });
544
+ dupPage = serialization_1.PageData.deserialize(updatedData, strapiUrl, funnel_core_1.Schema.Page.Data.Props.all);
545
+ console.debug('[StrapiPageClient] duplicate: touched duplicated page');
546
+ // add page to changeset
547
+ await provider.changeSets.update({
548
+ id: changeSet.id,
549
+ changes: [
550
+ ...changeSet.changes,
551
+ {
552
+ __type: 'page',
553
+ type: 'create',
554
+ schema: dupPage.type,
555
+ id: dupPage.id,
556
+ name: dupPage.name,
557
+ region: dupPage.region,
558
+ language: dupPage.language,
559
+ slug: dupPage.slug,
560
+ errors: dupPage.errors,
561
+ attribution: [
562
+ { userId: user.id, updatedOn: Date.now() }
563
+ ]
564
+ }
565
+ ]
566
+ });
567
+ // return group for duplicated page
568
+ return this.getGroup({
569
+ brand: dupPage.brand,
570
+ groupId: dupPage.groupId,
571
+ type: dupPage.type,
572
+ slug: (_a = dupPage.slug) !== null && _a !== void 0 ? _a : undefined,
573
+ region: dupPage.region,
574
+ language: dupPage.language,
575
+ version: 'draft'
576
+ });
577
+ }
578
+ // handle errors
579
+ catch (e) {
580
+ // discard changeset if it had no changes
581
+ if (changeSet.changes.length === 0) {
582
+ await provider.changeSets.delete(changeSet.id);
583
+ }
584
+ // throw error
585
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
586
+ }
587
+ }
588
+ async touch({ id: pageId, version: status }) {
589
+ // get latest version of page
590
+ const client = this.provider.client;
591
+ const strapiUrl = client.baseURL;
592
+ const collection = client.collection('pages');
593
+ const resultProps = funnel_core_1.Schema.Page.Data.Props.all;
594
+ const { data: currentData } = await collection.findOne(pageId, {
595
+ status,
596
+ populate: serialization_1.PageData.Populate.full
597
+ });
598
+ // TODO: switch to isometric error so we can catch it on client-side
599
+ // throw if page doesn't exist
600
+ if (!currentData) {
601
+ throw new funnel_core_1.NotFoundError();
602
+ }
603
+ // deserialize page (we'll be updating this version)
604
+ const page = serialization_1.PageData.deserialize(currentData, strapiUrl, resultProps);
605
+ // merge changes (the original page will have changed)
606
+ const model = funnel_core_1.Schema.PageModel.from(page);
607
+ model.sync();
608
+ console.debug(`[StrapiPageClient/touch] saving updated page`);
609
+ // save page
610
+ const update = serialization_1.PageData.serialize(page);
611
+ const { data: updatedData } = await collection.update(page.id, update, {
612
+ status,
613
+ populate: serialization_1.PageData.Populate.full
614
+ });
615
+ console.debug(`[StrapiPageClient/update] returning final page`);
616
+ // return page ref
617
+ return serialization_1.PageData.deserializeRef(updatedData);
618
+ }
619
+ async update({ id: pageId, slug: pageSlug, publication: pagePublication, errors, updates }) {
620
+ // resolve current user (or throw)
621
+ const { client, users } = this.provider;
622
+ const strapiUrl = client.baseURL;
623
+ const user = await users.me();
624
+ if (!user) {
625
+ throw new funnel_core_1.NotAuthenticatedError();
626
+ }
627
+ // get latest version of page
628
+ const collection = client.collection('pages');
629
+ const resultProps = funnel_core_1.Schema.Page.Data.Props.all;
630
+ const { data: currentData } = await collection.findOne(pageId, {
631
+ status: 'draft',
632
+ populate: serialization_1.PageData.Populate.full
633
+ });
634
+ // TODO: validate page is able to be updated (e.g. not archived, etc.)
635
+ // throw if page doesn't exist
636
+ if (!currentData) {
637
+ throw new funnel_core_1.NotFoundError();
638
+ }
639
+ // deserialize page (we'll be updating this version)
640
+ let page = serialization_1.PageData.deserialize(currentData, strapiUrl, resultProps);
641
+ // update slug
642
+ if (pageSlug && page.slug) {
643
+ console.debug(`[StrapiPageClient/update] updating page slug from "${page.slug}" to ${pageSlug}`);
644
+ // validate slug before updating
645
+ const validateSlugResult = await this.validateSlug({
646
+ brand: page.brand,
647
+ type: page.type,
648
+ region: page.region,
649
+ groupId: page.groupId,
650
+ slug: pageSlug
651
+ });
652
+ if (validateSlugResult.status !== 'available') {
653
+ throw new errors_1.SlugValidationError({ slug: pageSlug, reason: validateSlugResult });
654
+ }
655
+ page.slug = pageSlug;
656
+ }
657
+ // update publication
658
+ if (pagePublication !== undefined) {
659
+ page.publication = pagePublication !== null && pagePublication !== void 0 ? pagePublication : -1;
660
+ }
661
+ // set errors
662
+ page.errors = errors;
663
+ console.debug(`[StrapiPageClient/update] applying ${updates.length} page updates`);
664
+ // merge changes (the original page will have changed)
665
+ const model = funnel_core_1.Schema.PageModel.from(page);
666
+ model.update(updates);
667
+ console.debug(`[StrapiPageClient/update] saving updated page`);
668
+ // resolve change set (rebind)
669
+ const { provider } = this;
670
+ const changeSet = await provider.changeSets.resolve({ id: page.changeSetId, fallbackToGlobal: true });
671
+ page.changeSetId = changeSet.id;
672
+ // save page
673
+ const update = serialization_1.PageData.serialize(page);
674
+ console.debug(`[StrapiPageClient/update] sending update`, JSON.stringify(update, undefined, ' '));
675
+ const { data: updatedData } = await collection.update(page.id, Object.assign(Object.assign({}, update), { state: 'draft' }), {
676
+ status: 'draft',
677
+ populate: serialization_1.PageData.Populate.full
678
+ });
679
+ page = serialization_1.PageData.deserialize(updatedData, strapiUrl, resultProps);
680
+ // update changeset
681
+ changeSet.addPageChange(user.id, {
682
+ __type: 'page',
683
+ id: page.id,
684
+ name: page.name,
685
+ schema: page.type,
686
+ region: page.region,
687
+ language: page.language,
688
+ slug: page.slug,
689
+ type: page.publishedOn ? 'update' : 'create',
690
+ errors: page.errors
691
+ });
692
+ provider.changeSets.update(changeSet.toData())
693
+ .catch(console.error);
694
+ // return page
695
+ return page;
696
+ }
697
+ async replace({ page, version, skipSync = false }) {
698
+ console.debug(`[StrapiPageClient/replace] replacing page`);
699
+ // get latest version of page
700
+ const client = this.provider.client;
701
+ const strapiUrl = client.baseURL;
702
+ const collection = client.collection('pages');
703
+ // sync changes (the original page will have changed)
704
+ const model = funnel_core_1.Schema.PageModel.from(page);
705
+ if (!skipSync) {
706
+ model.sync();
707
+ }
708
+ // save page
709
+ const update = serialization_1.PageData.serialize(page);
710
+ const { data: updatedData } = await collection.update(page.id, update, {
711
+ status: version,
712
+ populate: serialization_1.PageData.Populate.full
713
+ });
714
+ console.debug(`[StrapiPageClient/replace] returning final page`);
715
+ // return page
716
+ return serialization_1.PageData.deserialize(updatedData, strapiUrl);
717
+ }
718
+ async unpublish(pageId) {
719
+ // get page
720
+ const page = await this.get({ id: pageId, version: 'draft' });
721
+ // throw if there's no page
722
+ if (!page) {
723
+ throw new funnel_core_1.NotFoundError('Unable to resolve page');
724
+ }
725
+ // or throw if it's already draft
726
+ else if (!page.publishedOn) {
727
+ throw new funnel_core_1.InvalidOperationError('Page is not published');
728
+ }
729
+ // unpublish the document
730
+ await this.provider.customRequest({
731
+ method: 'POST',
732
+ command: 'unpublish',
733
+ data: JSON.stringify({
734
+ collection: 'page',
735
+ documentId: page.id
736
+ }),
737
+ onError: ({ pageRefs, collectionRefs }) => {
738
+ // throw dependants error if we got any refs
739
+ if (pageRefs || collectionRefs) {
740
+ const client = this.provider.client;
741
+ const strapiUrl = client.baseURL;
742
+ throw new errors_1.LinkedDependantsError({
743
+ message: 'This collection cannot be unpublished because it would break dependencies',
744
+ pages: pageRefs.map(data => serialization_1.PageData.deserialize(data, strapiUrl, funnel_core_1.Schema.Page.Data.AsLink.props)),
745
+ collections: collectionRefs.map(data => serialization_1.CollectionData.deserialize(data, strapiUrl, funnel_core_1.Schema.Collection.Data.AsLink.props))
746
+ });
747
+ }
748
+ // or assume the page isn't published
749
+ else {
750
+ throw new funnel_core_1.NotFoundError();
751
+ }
752
+ }
753
+ });
754
+ // transform dto
755
+ return true;
756
+ }
757
+ async getDependants({ id: pageId, version }) {
758
+ // get client
759
+ const client = this.provider.client;
760
+ // pages are the only direct dependants currently
761
+ const strapiUrl = client.baseURL;
762
+ const collection = client.collection('pages');
763
+ const { items: pages = [] } = await Strapi_1.Strapi.paginate({
764
+ collection,
765
+ pagination: 'all',
766
+ filters: {
767
+ pageRefs: {
768
+ documentId: pageId
769
+ }
770
+ },
771
+ status: version
772
+ });
773
+ // return
774
+ return {
775
+ pages: pages.map(page => serialization_1.PageData.deserialize(page, strapiUrl, funnel_core_1.Schema.Page.Data.AsLink.props))
776
+ };
777
+ }
778
+ async getDependencies({ id: pageId, version }) {
779
+ // get page by id
780
+ const client = this.provider.client;
781
+ const strapiUrl = client.baseURL;
782
+ const collection = client.collection('pages');
783
+ const { data } = await collection.findOne(pageId, {
784
+ status: version,
785
+ populate: ['pageRefs', 'collectionRefs']
786
+ });
787
+ const page = data;
788
+ // throw if not found
789
+ if (!page) {
790
+ throw new funnel_core_1.NotFoundError(`cannot find page with id "${pageId}"`);
791
+ }
792
+ // return
793
+ return {
794
+ pages: page.pageRefs.map(pageRef => serialization_1.PageData.deserialize(pageRef, strapiUrl, funnel_core_1.Schema.Page.Data.AsLink.props)),
795
+ collections: page.collectionRefs.map(collectionRef => serialization_1.CollectionData.deserialize(collectionRef, strapiUrl, funnel_core_1.Schema.Collection.Data.AsLink.props))
796
+ };
797
+ }
798
+ async validateSlug({ brand, type, region, slug, groupId }) {
799
+ // TODO: check if slug is a valid format
800
+ // setup collection
801
+ const client = this.provider.client;
802
+ const collection = client.collection('pages');
803
+ // fetch pages with the same slug with same brand, type, and region
804
+ const { items } = await Strapi_1.Strapi.paginate(Object.assign({ collection, pagination: 'all', fields: serialization_1.PageData.getStrapiFilter(funnel_core_1.Schema.Page.Data.AsLink.props) }, getPageQueryParams({ version: 'draft', brand, type, region, slug })));
805
+ // ignore pages in same group (if group specified)
806
+ const matchedPages = groupId !== undefined
807
+ ? items.filter(item => item.groupId !== groupId)
808
+ : items;
809
+ // available if no pages matched
810
+ if (matchedPages.length === 0) {
811
+ return { status: 'available' };
812
+ }
813
+ // otherwise not available
814
+ else {
815
+ return {
816
+ status: 'unavailable',
817
+ takenBy: serialization_1.PageData.deserializeRef(matchedPages[0])
818
+ };
819
+ }
820
+ }
821
+ }
822
+ exports.StrapiPageClient = StrapiPageClient;
823
+ // helper
824
+ function getPageQueryParams(query) {
825
+ // build filters
826
+ const { ids = [], groupId, brand, type, slug, region, language, name, tags = [], tagsMatch = 'any', version } = query;
827
+ const filters = [];
828
+ // filter ids
829
+ if (ids.length > 0) {
830
+ filters.push({ documentId: { $in: ids } });
831
+ }
832
+ // filter group
833
+ if (groupId) {
834
+ filters.push({ groupId: { $eq: groupId } });
835
+ }
836
+ // filter brand
837
+ if (brand) {
838
+ filters.push({ brand: { $eq: brand } });
839
+ }
840
+ // filter type
841
+ const normalizedType = type
842
+ ? Array.isArray(type)
843
+ ? type
844
+ : [type]
845
+ : [];
846
+ if (normalizedType.length > 0) {
847
+ filters.push({ type: { $in: type } });
848
+ }
849
+ // filter slug
850
+ if (slug) {
851
+ filters.push({ slug: { $eq: slug } });
852
+ }
853
+ // filter region
854
+ if (region) {
855
+ filters.push({ region: { $eq: region } });
856
+ }
857
+ // filter language
858
+ if (language) {
859
+ filters.push({ lang: { $eq: language } });
860
+ }
861
+ // filter name
862
+ if (name) {
863
+ filters.push({ name: { $contains: name } });
864
+ }
865
+ // filter tags
866
+ if (tags.length === 1) {
867
+ filters.push({ tags: { $contains: `"${tags[0]}"` } });
868
+ }
869
+ else if (tags.length > 1) {
870
+ const operator = tagsMatch === 'any' ? '$or' : '$and';
871
+ filters.push({
872
+ [operator]: tags
873
+ .filter((tag) => !!tag)
874
+ .map((tag) => ({ tags: { $contains: `"${tag}"` } }))
875
+ });
876
+ }
877
+ // return query
878
+ return {
879
+ filters: filters.length > 0 ? { $and: filters } : undefined,
880
+ status: version
881
+ };
882
+ }