@nascentdigital/funnel-cms 5.0.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -169,7 +169,7 @@ export declare namespace PageClient {
169
169
  type Result = Schema.Page.Data.AsGroup;
170
170
  }
171
171
  namespace Get {
172
- type Args = (Query.ById | Query.ByDetails) & Query.Select;
172
+ type Args = Query.ById & Query.Select;
173
173
  type Result = Schema.Page.FlattenedData | undefined;
174
174
  }
175
175
  namespace Create {
@@ -6,7 +6,7 @@ export declare class ContentfulCollectionClient implements CollectionClient {
6
6
  readonly cms: IContentfulContentClient;
7
7
  constructor(cms: IContentfulContentClient);
8
8
  getOverview(args: CollectionClient.GetOverview.Args): Promise<CollectionClient.GetOverview.Result>;
9
- getItems(args: CollectionClient.GetItems.Args): Promise<CollectionClient.GetItems.Result>;
9
+ getItems({ language, ...args }: CollectionClient.GetItems.Args): Promise<CollectionClient.GetItems.Result>;
10
10
  find(args: CollectionClient.Find.Args): Promise<CollectionClient.Find.Result>;
11
11
  getGroup(args: CollectionClient.GetGroup.Args): Promise<ContentfulCollectionClient.GetGroup.Result>;
12
12
  get(args: CollectionClient.Get.Args): Promise<ContentfulCollectionClient.Get.Result>;
@@ -1,4 +1,15 @@
1
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
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.ContentfulCollectionClient = void 0;
4
15
  const funnel_core_1 = require("@nascentdigital/funnel-core");
@@ -9,10 +20,68 @@ class ContentfulCollectionClient {
9
20
  this.cms = cms;
10
21
  }
11
22
  async getOverview(args) {
12
- throw new funnel_core_1.NotImplementedError();
23
+ // fetch all collections
24
+ const { cms } = this;
25
+ const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulCollectionData.contentType, version: args.version, select: getCollectionSelect(funnel_core_1.Schema.Collection.Data.AsRef.props) }, getCollectionQueryParams(args)));
26
+ // convert entries to collection data
27
+ const collectionsData = entries.map(entry => models_1.ContentfulCollectionData.deserialize(entry, funnel_core_1.Schema.Collection.Data.AsRef.props));
28
+ // group by type
29
+ const collectionsByType = new Map();
30
+ for (const data of collectionsData) {
31
+ // get group
32
+ let group = collectionsByType.get(data.type);
33
+ if (!group) {
34
+ collectionsByType.set(data.type, group = []);
35
+ }
36
+ // push to group
37
+ group.push(data);
38
+ }
39
+ // return in overview format
40
+ return funnel_core_1.Schema.Site.current().collections.map(({ type }) => {
41
+ var _a, _b;
42
+ return ({
43
+ type,
44
+ total: (_b = (_a = collectionsByType.get(type)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0
45
+ });
46
+ });
13
47
  }
14
- async getItems(args) {
15
- throw new funnel_core_1.NotImplementedError();
48
+ async getItems(_a) {
49
+ var { language } = _a, args = __rest(_a, ["language"]);
50
+ // fetch all collections
51
+ const { cms } = this;
52
+ const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulCollectionData.contentType, version: args.version, select: getCollectionSelect(funnel_core_1.Schema.Collection.Data.AsRef.props) }, getCollectionQueryParams(args)));
53
+ // convert entries to collection data
54
+ const collectionsData = entries.map(entry => models_1.ContentfulCollectionData.deserialize(entry, funnel_core_1.Schema.Collection.Data.AsRef.props));
55
+ // group by group id
56
+ const collectionsByGroupId = new Map();
57
+ for (const data of collectionsData) {
58
+ // get group
59
+ let group = collectionsByGroupId.get(data.groupId);
60
+ if (!group) {
61
+ collectionsByGroupId.set(data.groupId, group = []);
62
+ }
63
+ // push to group
64
+ group.push(data);
65
+ }
66
+ // return in correct format
67
+ return collectionsByGroupId.entries().map(([groupId, refs]) => {
68
+ var _a;
69
+ // find ref with specified language
70
+ const target = (_a = refs.find(ref => ref.language === language)) !== null && _a !== void 0 ? _a : refs[0];
71
+ // return item
72
+ return {
73
+ id: target.id,
74
+ groupId: target.groupId,
75
+ type: args.type,
76
+ name: target.name,
77
+ refs: refs.reduce((refs, ref) => {
78
+ // map ref
79
+ refs[ref.language] = ref;
80
+ // keep reducing
81
+ return refs;
82
+ }, {})
83
+ };
84
+ }).toArray();
16
85
  }
17
86
  async find(args) {
18
87
  throw new funnel_core_1.NotImplementedError();
@@ -59,10 +128,109 @@ class ContentfulCollectionClient {
59
128
  entryId: args.id,
60
129
  version: args.version
61
130
  });
131
+ // skip if there's no entry
132
+ if (!entry) {
133
+ return undefined;
134
+ }
135
+ // or return it
62
136
  return Object.assign({ data: models_1.ContentfulCollectionData.deserialize(entry) }, models_1.ContentfulCollectionData.deserializeRefs(references));
63
137
  }
64
138
  async create(args) {
65
- throw new funnel_core_1.NotImplementedError();
139
+ // create a new language of an existing collection
140
+ if (funnel_core_1.Schema.Collection.Data.AsNewLanguage.filter(args)) {
141
+ // fetch all collections in group
142
+ const groupResult = await this.getGroup({
143
+ version: 'draft',
144
+ type: args.type,
145
+ groupId: args.groupId,
146
+ language: args.language
147
+ });
148
+ // throw if group doesn't exist
149
+ if (!groupResult) {
150
+ throw new funnel_core_1.ArgumentError(`Collection group "${args.groupId}" no longer exists`);
151
+ }
152
+ // return existing collection if it's there
153
+ if (groupResult.collection) {
154
+ return groupResult;
155
+ }
156
+ // try to create page
157
+ try {
158
+ // create entry
159
+ const { group } = groupResult;
160
+ const collectionData = await this.cms.createEntry(models_1.ContentfulCollectionData.contentType, {
161
+ type: args.type,
162
+ brand: args.brand,
163
+ version: args.version,
164
+ groupId: args.groupId,
165
+ language: args.language,
166
+ name: group.name,
167
+ references: [],
168
+ tags: [],
169
+ fields: {},
170
+ state: 'draft',
171
+ errors: true
172
+ });
173
+ // return updated group
174
+ return {
175
+ group: Object.assign(Object.assign({}, group), { instances: [
176
+ ...group.instances,
177
+ models_1.ContentfulCollectionData.deserialize(collectionData, funnel_core_1.Schema.Collection.Data.AsGroup.Instance.props)
178
+ ] }),
179
+ collection: {
180
+ data: models_1.ContentfulCollectionData.deserialize(collectionData),
181
+ files: {},
182
+ collections: {},
183
+ pages: {}
184
+ }
185
+ };
186
+ }
187
+ // or throw
188
+ catch (e) {
189
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
190
+ }
191
+ }
192
+ // or create a net-new collection
193
+ else {
194
+ // try to create
195
+ try {
196
+ // create entry
197
+ const collectionData = await this.cms.createEntry(models_1.ContentfulCollectionData.contentType, {
198
+ type: args.type,
199
+ brand: args.brand,
200
+ version: args.version,
201
+ groupId: funnel_core_1.Value.uuid(),
202
+ language: args.language,
203
+ name: args.name,
204
+ references: [],
205
+ tags: [],
206
+ fields: {},
207
+ state: 'draft',
208
+ errors: true
209
+ });
210
+ // return updated group
211
+ return {
212
+ group: {
213
+ brand: collectionData.brand,
214
+ type: collectionData.type,
215
+ groupId: collectionData.groupId,
216
+ name: collectionData.name,
217
+ instances: [
218
+ models_1.ContentfulCollectionData.deserialize(collectionData, funnel_core_1.Schema.Collection.Data.AsGroup.Instance.props)
219
+ ]
220
+ },
221
+ collection: {
222
+ data: models_1.ContentfulCollectionData.deserialize(collectionData),
223
+ files: {},
224
+ collections: {},
225
+ pages: {}
226
+ }
227
+ };
228
+ }
229
+ // or throw
230
+ catch (e) {
231
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
232
+ }
233
+ }
66
234
  }
67
235
  async duplicate(args) {
68
236
  throw new funnel_core_1.NotImplementedError();
@@ -33,8 +33,9 @@ export declare class ContentfulContentClient implements ContentClient, IContentf
33
33
  set token(token: AuthClient.AccessToken);
34
34
  clearToken(): void;
35
35
  createEntry<T extends ContentfulData>(contentTypeId: string, entry: ContentfulData.Draft<T>): Promise<T>;
36
- getEntry<TEntry extends ContentfulData>({ entryId, includeLinks, version }: IContentfulContentClient.GetEntry.Args<TEntry>): Promise<IContentfulContentClient.GetEntry.Result<TEntry>>;
37
- getEntries<TEntry extends ContentfulData>({ limit, version, includeLinks, ...query }: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
36
+ getEntry<TEntry extends ContentfulData>({ entryId, includeLinks, version, ...query }: IContentfulContentClient.GetEntry.Args<TEntry>): Promise<IContentfulContentClient.GetEntry.Result<TEntry>>;
37
+ getEntries<TEntry extends ContentfulData>({ pagination, version, includeLinks, ...query }: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
38
+ uploadAsset(args: IContentfulContentClient.UploadAsset.Args): Promise<IContentfulContentClient.UploadAsset.Result>;
38
39
  static forEditor(sdk: KnownAppSDK): Promise<ContentfulContentClient>;
39
40
  }
40
41
  export declare namespace ContentfulContentClient {
@@ -88,40 +88,43 @@ class ContentfulContentClient {
88
88
  // transform + return new entry
89
89
  return models_2.ContentfulData.fromCmaData(data, locale);
90
90
  }
91
- async getEntry({ entryId, includeLinks = false, version = 'draft' }) {
92
- var _a, _b;
91
+ async getEntry(_a) {
92
+ var _b, _c;
93
+ var { entryId, includeLinks = false, version = 'draft' } = _a, query = __rest(_a, ["entryId", "includeLinks", "version"]);
93
94
  // get entry
94
95
  const client = version === 'published' ? this.cda : this.cpa;
95
96
  const result = await client.entry.getMany({
96
- query: {
97
- 'sys.id': entryId,
98
- include: includeLinks === true
97
+ query: Object.assign(Object.assign({ 'sys.id': entryId }, query), { include: includeLinks === true
99
98
  ? 10
100
- : includeLinks || undefined
101
- }
99
+ : includeLinks || undefined })
102
100
  });
103
101
  const { items: [item], includes } = result;
104
102
  console.debug('[ContentfulContentClient/getEntry] fetched', result);
103
+ // skip
105
104
  // bind references
106
- const entry = models_2.ContentfulData.fromCdaData(item);
107
- const references = (_b = (_a = includes === null || includes === void 0 ? void 0 : includes.Entry) === null || _a === void 0 ? void 0 : _a.map(link => models_2.ContentfulData.fromCdaData(link))) !== null && _b !== void 0 ? _b : [];
105
+ const entry = item ? models_2.ContentfulData.fromCdaData(item) : undefined;
106
+ 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 : [];
108
107
  // return normalized item
109
108
  return { entry, references };
110
109
  }
111
110
  async getEntries(_a) {
112
111
  var _b, _c;
113
- var { limit = 1000, version = 'draft', includeLinks = false } = _a, query = __rest(_a, ["limit", "version", "includeLinks"]);
112
+ var { pagination = 'all', version = 'draft', includeLinks = false } = _a, query = __rest(_a, ["pagination", "version", "includeLinks"]);
113
+ // setup pagination
114
+ const itemsPerRequest = pagination === 'all' ? 1000 : Math.min(pagination.pageSize, 1000);
115
+ const page = pagination === 'all' ? 1 : pagination.page;
116
+ let skip = pagination === 'all' ? 0 : (page - 1) * pagination.pageSize;
117
+ let totalItemFetched = 0;
118
+ let hasMorePages = true;
119
+ let totalItems = undefined;
114
120
  // loop until no more pages
115
121
  const client = version === 'published' ? this.cda : this.cpa;
116
- let skip = 0;
117
- let total = 1;
118
122
  const entries = [];
119
123
  const includes = [];
120
- while (skip < total) {
124
+ do {
121
125
  // fetch next page
122
126
  const _d = await client.entry.getMany({
123
- query: Object.assign(Object.assign({}, query), { limit,
124
- skip, include: includeLinks === true
127
+ query: Object.assign(Object.assign({}, query), { limit: itemsPerRequest, skip, include: includeLinks === true
125
128
  ? 10
126
129
  : includeLinks || undefined })
127
130
  }), { items } = _d, response = __rest(_d, ["items"]);
@@ -129,19 +132,79 @@ class ContentfulContentClient {
129
132
  // add items
130
133
  items.forEach(item => entries.push(models_2.ContentfulData.fromCdaData(item)));
131
134
  includes.push(...((_c = (_b = response.includes) === null || _b === void 0 ? void 0 : _b.Entry) !== null && _c !== void 0 ? _c : []));
132
- // update counts
133
- total = response.total;
134
- skip += limit;
135
- }
135
+ // update paging info
136
+ totalItemFetched += items.length;
137
+ totalItems = response.total;
138
+ skip += items.length;
139
+ hasMorePages = pagination === 'all'
140
+ ? skip < totalItems
141
+ : totalItemFetched < pagination.pageSize && skip < totalItems;
142
+ } while (hasMorePages);
136
143
  console.debug('[ContentfulContentClient/getEntries] fetched entries', { entries, includes });
137
144
  // normalize references
138
145
  const references = includes.map(link => models_2.ContentfulData.fromCdaData(link));
139
146
  // return
140
147
  return {
141
148
  entries,
142
- references
149
+ references,
150
+ page,
151
+ pageCount: pagination === 'all'
152
+ ? 1
153
+ : Math.floor(totalItems / pagination.pageSize),
154
+ totalItemCount: totalItems
143
155
  };
144
156
  }
157
+ async uploadAsset(args) {
158
+ var _a;
159
+ // upload file
160
+ const { cma, locale } = this;
161
+ const uploadResult = await this.cma.upload.create({}, {
162
+ file: await args.file.arrayBuffer()
163
+ });
164
+ // create contentful asset
165
+ const contentfulAsset = await cma.asset.create({}, {
166
+ fields: models_2.ContentfulData.toEntryData({
167
+ title: args.name,
168
+ description: (_a = args.description) !== null && _a !== void 0 ? _a : '',
169
+ file: {
170
+ contentType: args.file.type,
171
+ fileName: args.name,
172
+ uploadFrom: {
173
+ sys: {
174
+ type: 'Link',
175
+ linkType: 'Upload',
176
+ id: uploadResult.sys.id
177
+ }
178
+ }
179
+ }
180
+ }, locale)
181
+ });
182
+ // process contentful asset (creates url etc.) and return
183
+ const processedAsset = await cma.asset.processForAllLocales({}, contentfulAsset);
184
+ // create funnel asset then return
185
+ const entry = await cma.entry.create({ contentTypeId: models_2.ContentfulAssetData.contentType }, {
186
+ fields: models_2.ContentfulData.toEntryData({
187
+ name: args.name,
188
+ description: args.description,
189
+ brand: args.brand,
190
+ type: args.type,
191
+ scope: args.scope,
192
+ tags: args.tags,
193
+ url: 'https:' + processedAsset.fields.file[locale].url,
194
+ size: args.size,
195
+ metadata: args.metadata,
196
+ media: {
197
+ sys: {
198
+ type: 'Link',
199
+ linkType: 'Asset',
200
+ id: processedAsset.sys.id
201
+ }
202
+ }
203
+ }, locale)
204
+ });
205
+ // return asset
206
+ return models_2.ContentfulData.fromCmaData(entry, locale);
207
+ }
145
208
  static async forEditor(sdk) {
146
209
  var _a;
147
210
  // get current site config (throw if we can't)
@@ -21,7 +21,7 @@ export declare class ContentfulPageClient implements PageClient {
21
21
  unpublish(args: PageClient.Unpublish.Args): Promise<PageClient.Unpublish.Result>;
22
22
  getDependants(args: PageClient.GetDependants.Args): Promise<PageClient.GetDependants.Result>;
23
23
  getDependencies(args: PageClient.GetDependencies.Args): Promise<PageClient.GetDependencies.Result>;
24
- validateSlug(args: PageClient.ValidateSlug.Args): Promise<PageClient.ValidateSlug.Result>;
24
+ validateSlug({ brand, type, region, slug, groupId }: PageClient.ValidateSlug.Args): Promise<PageClient.ValidateSlug.Result>;
25
25
  }
26
26
  export declare namespace ContentfulPageClient {
27
27
  type FlattedPageData = Omit<Schema.Page.FlattenedData, 'data'> & {
@@ -1,7 +1,19 @@
1
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
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.ContentfulPageClient = void 0;
4
15
  const funnel_core_1 = require("@nascentdigital/funnel-core");
16
+ const errors_1 = require("../errors");
5
17
  const models_1 = require("./models");
6
18
  // class
7
19
  class ContentfulPageClient {
@@ -51,7 +63,7 @@ class ContentfulPageClient {
51
63
  };
52
64
  }
53
65
  async getSummaries(args) {
54
- // fetch items (bail if there are no instances
66
+ // fetch items (bail if there are no instances)
55
67
  const { cms } = this;
56
68
  const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version: args.version, select: getPageSelect(funnel_core_1.Schema.Page.Data.AsLink.props) }, getPageQueryParams(args)));
57
69
  // return result
@@ -60,8 +72,15 @@ class ContentfulPageClient {
60
72
  getPreviews(args) {
61
73
  throw new funnel_core_1.NotImplementedError();
62
74
  }
63
- search(args) {
64
- throw new funnel_core_1.NotImplementedError();
75
+ async search(args) {
76
+ // fetch items (bail if there are no instances)
77
+ const { cms } = this;
78
+ const { entries, page, pageCount, totalItemCount } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version: args.version, select: getPageSelect(funnel_core_1.Schema.Page.Data.AsLink.props) }, getPageQueryParams(args)));
79
+ // return entries
80
+ return {
81
+ items: entries.map(entry => models_1.ContentfulPageData.deserialize(entry, funnel_core_1.Schema.Page.Data.AsLink.props)),
82
+ page, pageCount, totalItemCount
83
+ };
65
84
  }
66
85
  query(args) {
67
86
  throw new funnel_core_1.NotImplementedError();
@@ -71,58 +90,257 @@ class ContentfulPageClient {
71
90
  }
72
91
  async getGroup(args) {
73
92
  // fetch items (bail if there are no instances
93
+ const { language, region, version } = args, queryArgs = __rest(args, ["language", "region", "version"]);
74
94
  const { cms } = this;
75
- const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version: args.version, select: getPageSelect(funnel_core_1.Schema.Page.Data.AsGroup.Instance.props) }, getPageQueryParams(args)));
95
+ const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version, select: getPageSelect(funnel_core_1.Schema.Page.Data.AsGroup.Instance.props) }, getPageQueryParams(queryArgs)));
76
96
  // bail if no items
77
97
  if (entries.length === 0) {
78
98
  console.debug('[ContentfulPageClient/getGroup] no items in group', args);
79
- const { brand, groupId: possibleGroupId, type, slug, region } = args;
99
+ const { brand, groupId: possibleGroupId, type, slug } = queryArgs;
80
100
  return {
81
- group: { groupId: possibleGroupId !== null && possibleGroupId !== void 0 ? possibleGroupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, region, instances: [] }
101
+ group: { groupId: possibleGroupId !== null && possibleGroupId !== void 0 ? possibleGroupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, instances: [] }
82
102
  };
83
103
  }
84
- console.debug('[ContentfulPageClient/getGroup] processing group items', { args, entries });
104
+ console.debug('[ContentfulPageClient/getGroup] processing group items', args);
85
105
  // collate the group ids into summaries (fetches full page if region/language match exists
86
106
  const instances = entries.map(entry => models_1.ContentfulPageData.deserialize(entry, funnel_core_1.Schema.Page.Data.AsGroup.Instance.props));
87
107
  // bind page (if it exists)
88
- const { language, version } = args;
89
108
  const pageInstance = instances
90
- .find(instance => instance.language === language);
109
+ .find(instance => instance.region === region && instance.language === language);
91
110
  console.debug('[ContentfulPageClient/getGroup] getting page using this.get...', { id: pageInstance === null || pageInstance === void 0 ? void 0 : pageInstance.id, version });
92
111
  const page = pageInstance
93
112
  ? await this.get({ id: pageInstance.id, version })
94
113
  : undefined;
95
114
  // return group (and optional page)
96
- const { type, brand, groupId, region, slug } = entries[0];
115
+ const { type, brand, groupId, slug } = entries[0];
97
116
  return {
98
- group: {
99
- groupId,
100
- type,
101
- brand,
102
- region,
103
- slug,
104
- instances
105
- },
117
+ group: { groupId, type, brand, slug, instances },
106
118
  page
107
119
  };
108
120
  }
109
121
  async get(args) {
110
- // create args for query
111
- const queryArgs = 'id' in args
112
- ? { ids: [args.id], version: args.version }
113
- : args;
114
122
  // fetch entries (should be one entry)
115
123
  const { cms } = this;
116
- const { entries, references } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, includeLinks: true, version: args.version, limit: 1 }, getPageQueryParams(queryArgs)));
124
+ const [{ entries, references }, { entry: entrySnapshot }] = await Promise.all([
125
+ // query target (could be published/draft)
126
+ 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'
127
+ ? funnel_core_1.Schema.Page.Data.AsContent.props
128
+ : funnel_core_1.Schema.Page.Data.Props.all) }, getPageQueryParams({ ids: [args.id] }))),
129
+ // query published (if pulling draft above)
130
+ args.version === 'draft'
131
+ ? cms.getEntry(Object.assign({ content_type: models_1.ContentfulPageData.contentType, entryId: args.id, includeLinks: false, version: 'published', select: getPageSelect(funnel_core_1.Schema.PageSnapshot.props) }, getPageQueryParams({ ids: [args.id] })))
132
+ : Promise.resolve({ entry: undefined, references: [] })
133
+ ]);
117
134
  // skip if no entries
118
135
  if (entries.length === 0) {
119
136
  return undefined;
120
137
  }
138
+ // stitch together snapshot with entry (if available
139
+ const data = models_1.ContentfulPageData.deserialize(entries[0]);
140
+ const snapshot = entrySnapshot
141
+ ? models_1.ContentfulPageData.Snapshot.deserialize(entrySnapshot)
142
+ : undefined;
121
143
  // use first entry and deserialize
122
- return Object.assign({ data: models_1.ContentfulPageData.deserialize(entries[0]) }, models_1.ContentfulPageData.deserializeRefs(references));
144
+ 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));
123
145
  }
124
- create(args) {
125
- throw new funnel_core_1.NotImplementedError();
146
+ async create(args) {
147
+ console.debug('[ContentfulPageClient/create] processing create page request', args);
148
+ // create a new language of an existing page
149
+ if (funnel_core_1.Schema.Page.Data.AsNewLanguage.filter(args)) {
150
+ // fetch all pages in group (summaries only)
151
+ const existingPages = await this.getSummaries({
152
+ version: 'draft',
153
+ brand: args.brand,
154
+ groupId: args.groupId
155
+ });
156
+ // throw if there are no pages
157
+ if (existingPages.length === 0) {
158
+ throw new funnel_core_1.ArgumentError(`Page group "${args.groupId}" no longer exists in "${args.brand}"`);
159
+ }
160
+ // return existing page if it's there
161
+ let existingPage = existingPages
162
+ .find(existing => existing.region === args.region && existing.language === args.language);
163
+ if (existingPage) {
164
+ console.debug('[ContentfulPageClient/create/lang] returning existing page', existingPage);
165
+ return this.getGroup(Object.assign(Object.assign({}, existingPage), { version: 'draft' }));
166
+ }
167
+ // bind data + update url
168
+ existingPage = existingPages[0];
169
+ const draft = funnel_core_1.Schema.PageModel.fromDraft(Object.assign({ type: existingPage.type, slug: existingPage.slug, url: existingPage.url, name: existingPage.name, tags: existingPage.tags }, args));
170
+ draft.sync();
171
+ console.debug('[ContentfulPageClient/create/lang] creating from', { draft, existingPages });
172
+ // create page
173
+ try {
174
+ // create page
175
+ const draftData = draft.data;
176
+ const references = models_1.ContentfulData.findReferences([draftData.discover, draftData.content]);
177
+ const pageData = await this.cms.createEntry(models_1.ContentfulPageData.contentType, {
178
+ groupId: draftData.groupId,
179
+ brand: draftData.brand,
180
+ type: draftData.type,
181
+ region: draftData.region,
182
+ language: draftData.language,
183
+ name: draftData.name,
184
+ tags: draftData.tags,
185
+ slug: draftData.slug,
186
+ url: draftData.url,
187
+ preview: draftData.preview,
188
+ discover: draftData.discover,
189
+ content: draftData.content,
190
+ state: 'draft',
191
+ errors: true,
192
+ references
193
+ });
194
+ // transform entry to page data
195
+ const flattenedPage = {
196
+ data: pageData,
197
+ files: {},
198
+ collections: {},
199
+ pages: {}
200
+ };
201
+ const page = flattenedPage.data;
202
+ // return new page
203
+ return {
204
+ group: {
205
+ groupId: page.groupId,
206
+ brand: page.brand,
207
+ type: page.type,
208
+ slug: page.slug,
209
+ instances: [
210
+ // map exiting entries
211
+ ...existingPages.map(({ id, url, region, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn }) => ({ id, url, region, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn })),
212
+ // add new entry
213
+ {
214
+ id: page.id,
215
+ url: page.url,
216
+ region: page.region,
217
+ language: page.language,
218
+ name: page.name,
219
+ slug: page.slug,
220
+ tags: page.tags,
221
+ state: page.state,
222
+ errors: page.errors,
223
+ createdOn: page.createdOn,
224
+ updatedOn: page.updatedOn,
225
+ publishedOn: page.publishedOn
226
+ }
227
+ ]
228
+ },
229
+ page: flattenedPage
230
+ };
231
+ }
232
+ // or throw
233
+ catch (e) {
234
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
235
+ }
236
+ }
237
+ // or create a net-new page
238
+ else {
239
+ console.debug('[ContentfulPageClient/create/new] creating fresh');
240
+ // get page's template
241
+ const site = funnel_core_1.Schema.Site.current();
242
+ const schema = site.regions[args.region][args.type];
243
+ // check slug if dynamic page
244
+ if (schema.variant === 'dynamic') {
245
+ // make sure there is a slug for a dynamic page
246
+ if (args.slug === undefined) {
247
+ throw new funnel_core_1.ArgumentError(`A "${args.type}" Page must provide a slug when created`);
248
+ }
249
+ // validate slug
250
+ const validateSlugResult = await this.validateSlug(Object.assign(Object.assign({}, args), { slug: args.slug }));
251
+ if (validateSlugResult.status !== 'available') {
252
+ throw new errors_1.SlugValidationError({ slug: args.slug, reason: validateSlugResult });
253
+ }
254
+ }
255
+ // fetch all pages matching brand/type/region
256
+ const { group: existingGroup, page: existingPage } = await this.getGroup({
257
+ version: 'draft',
258
+ brand: args.brand,
259
+ type: args.type,
260
+ region: args.region,
261
+ language: args.language,
262
+ slug: args.slug
263
+ });
264
+ // return existing page if it's there
265
+ if (existingPage) {
266
+ console.debug('[ContentfulPageClient/create/new] returning existing page', existingPage);
267
+ return { group: existingGroup, page: existingPage };
268
+ }
269
+ else if (existingGroup.instances.length > 0) {
270
+ console.debug('[ContentfulPageClient/create/new] using existing group', existingGroup);
271
+ }
272
+ else {
273
+ console.debug('[ContentfulPageClient/create/new] no existing group found');
274
+ }
275
+ // bind data + update url
276
+ const draft = funnel_core_1.Schema.PageModel.fromDraft(Object.assign(Object.assign({}, args), { groupId: existingGroup.groupId === funnel_core_1.Schema.Page.GroupId.empty
277
+ ? funnel_core_1.Value.uuid()
278
+ : existingGroup.groupId, url: '' }));
279
+ draft.sync();
280
+ // create page
281
+ try {
282
+ console.debug('[ContentfulPageClient/create/new] adding new page', draft.data);
283
+ // create page
284
+ const draftData = draft.data;
285
+ const references = models_1.ContentfulData.findReferences([draftData.discover, draftData.content]);
286
+ const pageData = await this.cms.createEntry(models_1.ContentfulPageData.contentType, {
287
+ groupId: draftData.groupId,
288
+ brand: draftData.brand,
289
+ type: draftData.type,
290
+ region: draftData.region,
291
+ language: draftData.language,
292
+ name: draftData.name,
293
+ tags: draftData.tags,
294
+ slug: draftData.slug,
295
+ url: draftData.url,
296
+ preview: draftData.preview,
297
+ discover: draftData.discover,
298
+ content: draftData.content,
299
+ state: 'draft',
300
+ errors: true,
301
+ references
302
+ });
303
+ // transform entry to page data
304
+ const flattenedPage = {
305
+ data: pageData,
306
+ files: {},
307
+ collections: {},
308
+ pages: {}
309
+ };
310
+ const page = flattenedPage.data;
311
+ // return page
312
+ return {
313
+ group: {
314
+ groupId: page.groupId,
315
+ brand: page.brand,
316
+ type: page.type,
317
+ slug: page.slug,
318
+ instances: [
319
+ ...existingGroup.instances,
320
+ {
321
+ id: page.id,
322
+ url: page.url,
323
+ region: page.region,
324
+ language: page.language,
325
+ name: page.name,
326
+ slug: page.slug,
327
+ tags: page.tags,
328
+ state: page.state,
329
+ errors: page.errors,
330
+ createdOn: page.createdOn,
331
+ updatedOn: page.updatedOn,
332
+ publishedOn: page.publishedOn
333
+ }
334
+ ]
335
+ },
336
+ page: flattenedPage
337
+ };
338
+ }
339
+ // or throw
340
+ catch (e) {
341
+ throw funnel_core_1.IsometricErrorUtils.coerceError(e);
342
+ }
343
+ }
126
344
  }
127
345
  duplicate(args) {
128
346
  throw new funnel_core_1.NotImplementedError();
@@ -145,8 +363,26 @@ class ContentfulPageClient {
145
363
  getDependencies(args) {
146
364
  throw new funnel_core_1.NotImplementedError();
147
365
  }
148
- validateSlug(args) {
149
- throw new funnel_core_1.NotImplementedError();
366
+ async validateSlug({ brand, type, region, slug, groupId }) {
367
+ // fetch items (bail if there are no instances)
368
+ const { cms } = this;
369
+ const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version: 'draft', select: getPageSelect(funnel_core_1.Schema.Page.Data.AsLink.props) }, getPageQueryParams({ brand, type, region, slug })));
370
+ const links = entries.map(entry => models_1.ContentfulPageData.deserialize(entry, funnel_core_1.Schema.Page.Data.AsLink.props));
371
+ // ignore pages in same group (if group specified)
372
+ const matchedPages = groupId !== undefined
373
+ ? links.filter(link => link.groupId !== groupId)
374
+ : links;
375
+ // available if no pages matched
376
+ if (matchedPages.length === 0) {
377
+ return { status: 'available' };
378
+ }
379
+ // otherwise not available
380
+ else {
381
+ return {
382
+ status: 'unavailable',
383
+ takenBy: links[0]
384
+ };
385
+ }
150
386
  }
151
387
  }
152
388
  exports.ContentfulPageClient = ContentfulPageClient;
@@ -12,7 +12,7 @@ class ContentfulSiteConfigClient {
12
12
  // fetch config model
13
13
  let { entries: [siteConfig] } = await this.cms.getEntries({
14
14
  content_type: models_1.ContentfulSiteConfigData.contentType,
15
- limit: 1,
15
+ pagination: { page: 1, pageSize: 1 },
16
16
  'fields.brand': brand
17
17
  });
18
18
  // or create it if it's not there
@@ -1,6 +1,7 @@
1
1
  import { CMAClient } from '@contentful/app-sdk';
2
2
  import { PlainClientAPI, QueryOptions } from 'contentful-management';
3
- import { Environment, User } from '../models';
3
+ import { AssetClient } from '../api';
4
+ import { Environment, Pagination, User } from '../models';
4
5
  import type { ContentfulAssetClient } from './ContentfulAssetClient';
5
6
  import type { ContentfulAuthClient } from './ContentfulAuthClient';
6
7
  import type { ContentfulChangeSetClient } from './ContentfulChangeSetClient';
@@ -8,7 +9,7 @@ import type { ContentfulCollectionClient } from './ContentfulCollectionClient';
8
9
  import type { ContentfulPageClient } from './ContentfulPageClient';
9
10
  import type { ContentfulSiteConfigClient } from './ContentfulSiteConfigClient';
10
11
  import type { ContentfulUserClient } from './ContentfulUserClient';
11
- import { ContentfulData } from './models';
12
+ import { ContentfulAssetData, ContentfulData } from './models';
12
13
  export interface IContentfulContentClient {
13
14
  readonly user: User;
14
15
  readonly locale: string;
@@ -25,6 +26,7 @@ export interface IContentfulContentClient {
25
26
  createEntry<TEntry extends ContentfulData>(contentType: string, entry: ContentfulData.Draft<TEntry>): Promise<TEntry>;
26
27
  getEntry<TEntry extends ContentfulData>(args: IContentfulContentClient.GetEntry.Args<TEntry>): Promise<IContentfulContentClient.GetEntry.Result<TEntry>>;
27
28
  getEntries<TEntry extends ContentfulData>(args: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
29
+ uploadAsset(args: IContentfulContentClient.UploadAsset.Args): Promise<IContentfulContentClient.UploadAsset.Result>;
28
30
  }
29
31
  export declare namespace IContentfulContentClient {
30
32
  type EntryQueryOptions = {
@@ -32,21 +34,28 @@ export declare namespace IContentfulContentClient {
32
34
  version?: Environment;
33
35
  };
34
36
  namespace GetEntry {
35
- type Args<TEntry extends ContentfulData> = EntryQueryOptions & {
37
+ type Args<TEntry extends ContentfulData> = Pick<QueryOptions, 'content_type' | 'select'> & EntryQueryOptions & {
36
38
  entryId: ContentfulData.EntryId<TEntry>;
37
39
  };
38
40
  type Result<TEntry extends ContentfulData> = {
39
- entry: TEntry;
41
+ entry: TEntry | undefined;
40
42
  references: ContentfulData[];
41
43
  };
42
44
  }
43
45
  namespace GetEntries {
44
- type Args<TEntry extends ContentfulData> = Omit<QueryOptions, 'content_type' | 'include'> & EntryQueryOptions & {
46
+ type Args<TEntry extends ContentfulData> = Omit<QueryOptions, 'content_type' | 'include' | 'limit' | 'skip'> & EntryQueryOptions & {
45
47
  content_type: ContentfulData.ContentType<TEntry>;
48
+ pagination?: Pagination;
46
49
  };
47
- type Result<TEntry extends ContentfulData> = {
50
+ type Result<TEntry extends ContentfulData> = Pagination.Counts & {
48
51
  entries: TEntry[];
49
52
  references: ContentfulData[];
50
53
  };
51
54
  }
55
+ namespace UploadAsset {
56
+ type Args = AssetClient.CreateAsset.Args & {
57
+ file: Blob;
58
+ };
59
+ type Result = ContentfulAssetData;
60
+ }
52
61
  }
@@ -1,6 +1,8 @@
1
1
  import { Schema } from '@nascentdigital/funnel-core';
2
2
  import { ContentfulData } from './ContentfulData';
3
- export type ContentfulAssetData = ContentfulData<ContentfulAssetData.ContentType, Schema.Asset.Id> & Omit<Schema.AssetField.LinkedValue, 'id'>;
3
+ export type ContentfulAssetData = ContentfulData<ContentfulAssetData.ContentType, Schema.Asset.Id> & Omit<Schema.Asset.Linked['value'], 'id'> & {
4
+ media: ContentfulData.Link.Asset;
5
+ };
4
6
  export declare namespace ContentfulAssetData {
5
7
  const contentType: "asset";
6
8
  type ContentType = typeof contentType;
@@ -1,10 +1,12 @@
1
1
  import { RequiredBy, Schema } from '@nascentdigital/funnel-core';
2
2
  import { ContentfulData } from './ContentfulData';
3
- export type ContentfulCollectionData = ContentfulData<ContentfulCollectionData.ContentType, Schema.Collection.Id> & Omit<Schema.Collection.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'>;
3
+ export type ContentfulCollectionData = ContentfulData<ContentfulCollectionData.ContentType, Schema.Collection.Id> & Omit<Schema.Collection.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'> & {
4
+ references: ContentfulData.References;
5
+ };
4
6
  export declare namespace ContentfulCollectionData {
5
7
  const contentType: "collection";
6
8
  type ContentType = typeof contentType;
7
- type ForSchema = Schema.Collection.Data & Pick<ContentfulData, 'references'>;
9
+ type ForSchema = Schema.Collection.Data;
8
10
  type Potential = RequiredBy<Partial<ContentfulCollectionData>, keyof ContentfulData<ContentfulCollectionData.ContentType, Schema.Collection.Id>>;
9
11
  function deserialize(data: Potential, props?: ReadonlyArray<Schema.Collection.Data.Props.All>): ForSchema;
10
12
  function deserializeRefs(refs: ContentfulData[]): Schema.Collection.References;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ContentfulCollectionData = void 0;
4
4
  const funnel_core_1 = require("@nascentdigital/funnel-core");
5
5
  const ContentfulAssetData_1 = require("./ContentfulAssetData");
6
+ const ContentfulPageData_1 = require("./ContentfulPageData");
6
7
  // extension
7
8
  var ContentfulCollectionData;
8
9
  (function (ContentfulCollectionData) {
@@ -71,6 +72,7 @@ var ContentfulCollectionData;
71
72
  // setup
72
73
  const files = {};
73
74
  const collections = {};
75
+ const pages = {};
74
76
  // loop over references and serialize
75
77
  for (const ref of refs) {
76
78
  switch (ref.__type) {
@@ -86,12 +88,18 @@ var ContentfulCollectionData;
86
88
  collections[collectionRef.id] = ContentfulCollectionData.deserialize(collectionRef, funnel_core_1.Schema.Collection.Data.AsLink.props);
87
89
  break;
88
90
  }
91
+ // page
92
+ case ContentfulPageData_1.ContentfulPageData.contentType: {
93
+ const pageRef = ref;
94
+ pages[pageRef.id] = ContentfulPageData_1.ContentfulPageData.deserialize(pageRef, funnel_core_1.Schema.Page.Data.AsLink.props);
95
+ }
89
96
  }
90
97
  }
91
98
  // return
92
99
  return {
93
100
  files,
94
- collections
101
+ collections,
102
+ pages
95
103
  };
96
104
  }
97
105
  ContentfulCollectionData.deserializeRefs = deserializeRefs;
@@ -7,13 +7,12 @@ export type ContentfulData<TType extends string = string, TId extends string = s
7
7
  id: TId;
8
8
  createdOn: number;
9
9
  updatedOn: number;
10
- references: ContentfulData.References;
11
10
  };
12
11
  export declare namespace ContentfulData {
13
12
  type ContentType<T extends ContentfulData> = T['__type'];
14
13
  type EntryId<T extends ContentfulData> = T['id'];
15
14
  type References = Array<Link.Entry | Link.Asset>;
16
- type Draft<T extends ContentfulData> = Omit<T, keyof ContentfulData>;
15
+ type Draft<T extends ContentfulData> = Omit<T, keyof ContentfulData | '__root'>;
17
16
  type Link<TType extends Link.Type = Link.Type> = {
18
17
  sys: {
19
18
  type: 'Link';
@@ -1,6 +1,8 @@
1
1
  import { RequiredBy, Schema } from '@nascentdigital/funnel-core';
2
2
  import { ContentfulData } from './ContentfulData';
3
- export type ContentfulPageData = ContentfulData<ContentfulPageData.ContentType, Schema.Page.Id> & Omit<Schema.Page.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'>;
3
+ export type ContentfulPageData = ContentfulData<ContentfulPageData.ContentType, Schema.Page.Id> & Omit<Schema.Page.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'> & {
4
+ references: ContentfulData.References;
5
+ };
4
6
  export declare namespace ContentfulPageData {
5
7
  const contentType: "page";
6
8
  type ContentType = typeof contentType;
@@ -10,7 +12,11 @@ export declare namespace ContentfulPageData {
10
12
  readonly asset: "files";
11
13
  };
12
14
  type ReferencesSpec = typeof referencesSpec;
13
- type ForSchema = Schema.Page.Data & Pick<ContentfulData, 'references'>;
15
+ type Snapshot = ContentfulData<ContentfulPageData.ContentType, Schema.PageSnapshot.Id> & Omit<Schema.PageSnapshot, 'id' | 'changeSetId'>;
16
+ namespace Snapshot {
17
+ function deserialize(snapshot: Snapshot): Schema.PageSnapshot;
18
+ }
19
+ type ForSchema = Schema.Page.Data;
14
20
  type Potential = RequiredBy<Partial<ContentfulPageData>, keyof ContentfulData<ContentfulPageData.ContentType, Schema.Page.Id>>;
15
21
  function deserialize(data: Potential, props?: ReadonlyArray<Schema.Page.Data.Props.All>): ForSchema;
16
22
  function deserializeRefs(refs: ContentfulData[]): Schema.Page.References;
@@ -9,11 +9,21 @@ var ContentfulPageData;
9
9
  (function (ContentfulPageData) {
10
10
  // constants
11
11
  ContentfulPageData.contentType = 'page';
12
+ // references
12
13
  ContentfulPageData.referencesSpec = {
13
14
  'page': 'pages',
14
15
  'collection': 'collections',
15
16
  'asset': 'files'
16
17
  };
18
+ let Snapshot;
19
+ (function (Snapshot) {
20
+ function deserialize(snapshot) {
21
+ var _a, _b;
22
+ const data = ContentfulPageData.deserialize(snapshot, funnel_core_1.Schema.PageSnapshot.props);
23
+ return Object.assign(Object.assign({}, data), { changeSetId: ((_b = (_a = snapshot === null || snapshot === void 0 ? void 0 : snapshot.__version) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'unknown') });
24
+ }
25
+ Snapshot.deserialize = deserialize;
26
+ })(Snapshot = ContentfulPageData.Snapshot || (ContentfulPageData.Snapshot = {}));
17
27
  function deserialize(data, props = funnel_core_1.Schema.Page.Data.Props.all) {
18
28
  var _a;
19
29
  // create base page
@@ -17,4 +17,5 @@ export declare namespace Pagination {
17
17
  pageCount: number;
18
18
  totalItemCount: number;
19
19
  };
20
+ type Counts = Omit<Paginated<any>, 'items'>;
20
21
  }
@@ -150,12 +150,12 @@ class StrapiPageClient {
150
150
  async getGroup({ brand, groupId, type, slug, region, language, version, select }) {
151
151
  // create base result
152
152
  const result = {
153
- group: { groupId: groupId !== null && groupId !== void 0 ? groupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, region, instances: [] }
153
+ group: { groupId: groupId !== null && groupId !== void 0 ? groupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, instances: [] }
154
154
  };
155
155
  // fetch baseline page (fail if it doesn't exist)
156
156
  const client = this.provider.client;
157
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 })));
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 })));
159
159
  const [baselinePage] = baselineItems;
160
160
  if (!baselinePage) {
161
161
  return result;
@@ -172,7 +172,7 @@ class StrapiPageClient {
172
172
  const instance = serialization_1.PageData.deserializeLegacy(item, client.baseURL, funnel_core_1.Schema.Page.Data.AsGroup.Instance.props);
173
173
  group.instances.push(instance);
174
174
  // set page if language matches
175
- if (instance.language === language) {
175
+ if (instance.region === region && instance.language === language) {
176
176
  result.page = await this.get({ id: instance.id, version, select });
177
177
  }
178
178
  }
@@ -187,33 +187,16 @@ class StrapiPageClient {
187
187
  const resultProps = args.select === 'content'
188
188
  ? funnel_core_1.Schema.Page.Data.AsContent.props
189
189
  : funnel_core_1.Schema.Page.Data.Props.all;
190
- let data;
191
- if ('id' in args) {
192
- // find entry by document id
193
- const { data: rawData } = await collection.findOne(args.id, {
194
- fields: serialization_1.PageData.getStrapiFilter(resultProps),
195
- status: args.version,
196
- populate: serialization_1.PageData.Populate.full
197
- });
198
- // skip if there's no entry
199
- if (!rawData) {
200
- console.debug(`[StrapiPageClient/getPage] no "${args.version}" entry found for id ${args.id}`);
201
- return undefined;
202
- }
203
- // or capture it
204
- data = rawData;
205
- }
206
- // or by querying type (including region, and language)
207
- else {
208
- // lookup entry from collection (might be a miss)
209
- 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)));
210
- // skip if there's no entry
211
- if (rawData.length === 0) {
212
- console.warn(`[StrapiPageClient/getStaticPage] no "${args.version}" entry found for type ${args.type} in region ${args.region}/${args.language}`);
213
- return undefined;
214
- }
215
- // or capture entry
216
- data = rawData[0];
190
+ // find entry by document id
191
+ const { data } = 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 (!data) {
198
+ console.debug(`[StrapiPageClient/getPage] no "${args.version}" entry found for id ${args.id}`);
199
+ return undefined;
217
200
  }
218
201
  // convert and return
219
202
  return serialization_1.PageData.deserializeFlattened(data, strapiUrl, resultProps);
@@ -239,13 +222,14 @@ class StrapiPageClient {
239
222
  throw new funnel_core_1.ArgumentError(`Page group "${args.groupId}" no longer exists in "${args.brand}"`);
240
223
  }
241
224
  // return existing page if it's there
242
- let existingPage = existingPages.find(existing => existing.language === args.language);
225
+ let existingPage = existingPages
226
+ .find(existing => existing.region === args.region && existing.language === args.language);
243
227
  if (existingPage) {
244
228
  return this.getGroup(Object.assign(Object.assign({}, existingPage), { version: 'draft' }));
245
229
  }
246
230
  // bind data + update url
247
231
  existingPage = existingPages[0];
248
- 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));
232
+ const draft = funnel_core_1.Schema.PageModel.fromDraft(Object.assign({ type: existingPage.type, slug: existingPage.slug, url: existingPage.url, name: existingPage.name, tags: existingPage.tags }, args));
249
233
  draft.sync();
250
234
  // resolve change set (rebind)
251
235
  const { provider } = this;
@@ -290,13 +274,13 @@ class StrapiPageClient {
290
274
  groupId: page.groupId,
291
275
  brand: page.brand,
292
276
  type: page.type,
293
- region: page.region,
294
277
  slug: page.slug,
295
278
  instances: [
296
- ...existingPages.map(({ id, url, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn }) => ({ id, url, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn })),
279
+ ...existingPages.map(({ id, url, region, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn }) => ({ id, url, region, language, name, slug, tags, state, errors, createdOn, updatedOn, publishedOn })),
297
280
  {
298
281
  id: page.id,
299
282
  url: page.url,
283
+ region: page.region,
300
284
  language: page.language,
301
285
  name: page.name,
302
286
  slug: page.slug,
@@ -345,8 +329,8 @@ class StrapiPageClient {
345
329
  brand: args.brand,
346
330
  type: args.type,
347
331
  region: args.region,
348
- slug: args.slug,
349
- language: args.language
332
+ language: args.language,
333
+ slug: args.slug
350
334
  });
351
335
  // return existing page if it's there
352
336
  if (existingPage) {
@@ -400,13 +384,13 @@ class StrapiPageClient {
400
384
  groupId: page.groupId,
401
385
  brand: page.brand,
402
386
  type: page.type,
403
- region: args.region,
404
387
  slug: page.slug,
405
388
  instances: [
406
389
  ...existingGroup.instances,
407
390
  {
408
391
  id: page.id,
409
392
  url: page.url,
393
+ region: args.region,
410
394
  language: page.language,
411
395
  name: page.name,
412
396
  slug: page.slug,
@@ -181,7 +181,8 @@ var CollectionData;
181
181
  // return
182
182
  return {
183
183
  files,
184
- collections
184
+ collections,
185
+ pages: {}
185
186
  };
186
187
  }
187
188
  CollectionData.deserializeRefs = deserializeRefs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nascentdigital/funnel-cms",
3
- "version": "5.0.2",
3
+ "version": "6.0.0",
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": "24d9690cf813052f6561c3ccef11c7cf69ad7f7c"
51
+ "gitHead": "83e335312f91fb90aae73dfc31b3df1ec1895f76"
52
52
  }