@nascentdigital/funnel-cms 6.0.1 → 6.1.1

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.
@@ -10,7 +10,7 @@ export declare class ContentfulCollectionClient implements CollectionClient {
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>;
13
- create(args: CollectionClient.Create.Args): Promise<CollectionClient.Create.Result>;
13
+ create(args: CollectionClient.Create.Args): Promise<ContentfulCollectionClient.Create.Result>;
14
14
  duplicate(args: CollectionClient.Duplicate.Args): Promise<CollectionClient.Duplicate.Result>;
15
15
  update(args: CollectionClient.Update.Args): Promise<ContentfulCollectionClient.Update.Result>;
16
16
  replace(args: CollectionClient.Replace.Args): Promise<CollectionClient.Replace.Result>;
@@ -30,6 +30,11 @@ export declare namespace ContentfulCollectionClient {
30
30
  namespace Get {
31
31
  type Result = FlattenedCollectionData | undefined;
32
32
  }
33
+ namespace Create {
34
+ type Result = Omit<CollectionClient.Create.Result, 'collection'> & {
35
+ collection?: FlattenedCollectionData;
36
+ };
37
+ }
33
38
  namespace Update {
34
39
  type Result = Omit<CollectionClient.Update.Result, 'collection'> & {
35
40
  collection?: FlattenedCollectionData;
@@ -167,7 +167,6 @@ class ContentfulCollectionClient {
167
167
  references: [],
168
168
  tags: [],
169
169
  fields: {},
170
- state: 'draft',
171
170
  errors: true
172
171
  });
173
172
  // return updated group
@@ -204,7 +203,6 @@ class ContentfulCollectionClient {
204
203
  references: [],
205
204
  tags: [],
206
205
  fields: {},
207
- state: 'draft',
208
206
  errors: true
209
207
  });
210
208
  // return updated group
@@ -236,7 +234,35 @@ class ContentfulCollectionClient {
236
234
  throw new funnel_core_1.NotImplementedError();
237
235
  }
238
236
  async update(args) {
239
- throw new funnel_core_1.NotImplementedError();
237
+ var _a;
238
+ // fetch latest
239
+ const entryData = await this.cms.getEntryData(args.id);
240
+ const data = models_1.ContentfulCollectionData.deserialize(entryData);
241
+ // apply updates
242
+ const model = new funnel_core_1.Schema.CollectionModel(data);
243
+ model.update(args.updates);
244
+ model.sync();
245
+ // resolve references
246
+ const references = models_1.ContentfulData.findReferences([data.fields]);
247
+ // update entry
248
+ const entry = await this.cms.updateEntry({
249
+ id: data.id,
250
+ __version: data.__version,
251
+ // always update
252
+ name: data.name,
253
+ errors: data.errors,
254
+ references,
255
+ // TODO: only update if changed
256
+ // optional
257
+ tags: (_a = data.tags) !== null && _a !== void 0 ? _a : [],
258
+ fields: data.fields
259
+ });
260
+ // return
261
+ // TODO: return collection if there was an update from the outside
262
+ return {
263
+ updatedOn: entry.updatedOn,
264
+ changeSetId: ''
265
+ };
240
266
  }
241
267
  async replace(args) {
242
268
  throw new funnel_core_1.NotImplementedError();
@@ -32,11 +32,13 @@ export declare class ContentfulContentClient implements ContentClient, IContentf
32
32
  get token(): AuthClient.AccessToken;
33
33
  set token(token: AuthClient.AccessToken);
34
34
  clearToken(): void;
35
+ getEntryData<TEntry extends ContentfulData>(entryId: ContentfulData.EntryId<TEntry>): Promise<TEntry>;
35
36
  createEntry<T extends ContentfulData>(contentTypeId: string, entry: ContentfulData.Draft<T>): Promise<T>;
36
37
  getEntry<TEntry extends ContentfulData>({ entryId, includeLinks, version, ...query }: IContentfulContentClient.GetEntry.Args<TEntry>): Promise<IContentfulContentClient.GetEntry.Result<TEntry>>;
37
38
  getEntries<TEntry extends ContentfulData>({ pagination, version, includeLinks, ...query }: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
39
+ updateEntry<TEntry extends ContentfulData>(entry: ContentfulData.Update<TEntry>): Promise<TEntry>;
38
40
  uploadAsset(args: IContentfulContentClient.UploadAsset.Args): Promise<IContentfulContentClient.UploadAsset.Result>;
39
- static forEditor(sdk: KnownAppSDK): Promise<ContentfulContentClient>;
41
+ static forEditor({ sdk, contentApiPath }: ContentfulContentClient.ForEditorArgs): Promise<ContentfulContentClient>;
40
42
  }
41
43
  export declare namespace ContentfulContentClient {
42
44
  type ConstructorArgs = ConstructorArgs.ForServer | ConstructorArgs.ForEditor;
@@ -57,4 +59,8 @@ export declare namespace ContentfulContentClient {
57
59
  function isForServer(args: ConstructorArgs): args is ForServer;
58
60
  function isForEditor(args: ConstructorArgs): args is ForEditor;
59
61
  }
62
+ type ForEditorArgs = {
63
+ sdk: KnownAppSDK;
64
+ contentApiPath: string;
65
+ };
60
66
  }
@@ -30,7 +30,6 @@ class ContentfulContentClient {
30
30
  constructor(args) {
31
31
  var _a;
32
32
  // initialize instance variables
33
- let sp;
34
33
  if (ContentfulContentClient.ConstructorArgs.isForServer(args)) {
35
34
  this.user = models_1.User.UNAUTHENTICATED;
36
35
  this.locale = args.locale;
@@ -79,6 +78,13 @@ class ContentfulContentClient {
79
78
  clearToken() {
80
79
  throw new funnel_core_1.NotImplementedError();
81
80
  }
81
+ async getEntryData(entryId) {
82
+ // create entry
83
+ const { locale, cma } = this;
84
+ const data = await cma.entry.get({ entryId });
85
+ // transform + return new entry
86
+ return models_2.ContentfulData.fromCmaData(data, locale);
87
+ }
82
88
  async createEntry(contentTypeId, entry) {
83
89
  // create entry
84
90
  const { locale, cma } = this;
@@ -154,6 +160,30 @@ class ContentfulContentClient {
154
160
  totalItemCount: totalItems
155
161
  };
156
162
  }
163
+ async updateEntry(entry) {
164
+ // create entry
165
+ const { locale, cma } = this;
166
+ // create patch operations
167
+ const ops = [];
168
+ Object.entries(entry).forEach(([key, value]) => {
169
+ // skip over these keys
170
+ if (key === 'id' || key === '__version') {
171
+ return;
172
+ }
173
+ // create add op
174
+ ops.push({
175
+ op: 'add',
176
+ path: `/fields/${key}`,
177
+ value: {
178
+ [locale]: value
179
+ }
180
+ });
181
+ });
182
+ // execute patch operations
183
+ const data = await cma.entry.patch({ entryId: entry.id, version: entry.__version }, ops);
184
+ // transform + return new entry
185
+ return models_2.ContentfulData.fromCmaData(data, locale);
186
+ }
157
187
  async uploadAsset(args) {
158
188
  var _a;
159
189
  // upload file
@@ -205,7 +235,7 @@ class ContentfulContentClient {
205
235
  // return asset
206
236
  return models_2.ContentfulData.fromCmaData(entry, locale);
207
237
  }
208
- static async forEditor(sdk) {
238
+ static async forEditor({ sdk, contentApiPath }) {
209
239
  var _a;
210
240
  // get current site config (throw if we can't)
211
241
  const { user, cma, locales: { default: locale } } = sdk;
@@ -236,7 +266,7 @@ class ContentfulContentClient {
236
266
  userId: access.userId,
237
267
  token: access.token
238
268
  };
239
- const response = await fetch('/api/contentful', {
269
+ const response = await fetch(contentApiPath, {
240
270
  method: 'POST',
241
271
  body: JSON.stringify(command)
242
272
  });
@@ -92,16 +92,17 @@ class ContentfulPageClient {
92
92
  // fetch items (bail if there are no instances
93
93
  const { language, region, version } = args, queryArgs = __rest(args, ["language", "region", "version"]);
94
94
  const { cms } = this;
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)));
95
+ console.debug('[ContentfulPageClient/getGroup] query group entries for', JSON.stringify(queryArgs, undefined, ' '));
96
+ const { entries } = await cms.getEntries(Object.assign({ content_type: models_1.ContentfulPageData.contentType, version, select: getPageSelect(funnel_core_1.Schema.Page.Data.Props.id) }, getPageQueryParams(queryArgs)));
96
97
  // bail if no items
97
98
  if (entries.length === 0) {
98
- console.debug('[ContentfulPageClient/getGroup] no items in group', args);
99
+ console.debug('[ContentfulPageClient/getGroup] no items in group', JSON.stringify(args, undefined, ' '));
99
100
  const { brand, groupId: possibleGroupId, type, slug } = queryArgs;
100
101
  return {
101
102
  group: { groupId: possibleGroupId !== null && possibleGroupId !== void 0 ? possibleGroupId : funnel_core_1.Schema.Page.GroupId.empty, brand, type, slug, instances: [] }
102
103
  };
103
104
  }
104
- console.debug('[ContentfulPageClient/getGroup] processing group items', args);
105
+ console.debug('[ContentfulPageClient/getGroup] processing group items', JSON.stringify(args, undefined, ' '));
105
106
  // collate the group ids into summaries (fetches full page if region/language match exists
106
107
  const instances = entries.map(entry => models_1.ContentfulPageData.deserialize(entry, funnel_core_1.Schema.Page.Data.AsGroup.Instance.props));
107
108
  // bind page (if it exists)
@@ -187,7 +188,6 @@ class ContentfulPageClient {
187
188
  preview: draftData.preview,
188
189
  discover: draftData.discover,
189
190
  content: draftData.content,
190
- state: 'draft',
191
191
  errors: true,
192
192
  references
193
193
  });
@@ -296,7 +296,6 @@ class ContentfulPageClient {
296
296
  preview: draftData.preview,
297
297
  discover: draftData.discover,
298
298
  content: draftData.content,
299
- state: 'draft',
300
299
  errors: true,
301
300
  references
302
301
  });
@@ -24,8 +24,16 @@ export interface IContentfulContentClient {
24
24
  readonly users: ContentfulUserClient;
25
25
  readonly siteConfig: ContentfulSiteConfigClient;
26
26
  createEntry<TEntry extends ContentfulData>(contentType: string, entry: ContentfulData.Draft<TEntry>): Promise<TEntry>;
27
+ /**
28
+ * Get entry by id without any references
29
+ */
30
+ getEntryData<TEntry extends ContentfulData>(entryId: ContentfulData.EntryId<TEntry>): Promise<TEntry>;
31
+ /**
32
+ * Get entry with references
33
+ */
27
34
  getEntry<TEntry extends ContentfulData>(args: IContentfulContentClient.GetEntry.Args<TEntry>): Promise<IContentfulContentClient.GetEntry.Result<TEntry>>;
28
35
  getEntries<TEntry extends ContentfulData>(args: IContentfulContentClient.GetEntries.Args<TEntry>): Promise<IContentfulContentClient.GetEntries.Result<TEntry>>;
36
+ updateEntry<TEntry extends ContentfulData>(entry: ContentfulData.Update<TEntry>): Promise<TEntry>;
29
37
  uploadAsset(args: IContentfulContentClient.UploadAsset.Args): Promise<IContentfulContentClient.UploadAsset.Result>;
30
38
  }
31
39
  export declare namespace IContentfulContentClient {
@@ -1,13 +1,15 @@
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, Schema.Collection.State> & Omit<Schema.Collection.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'> & {
4
4
  references: ContentfulData.References;
5
5
  };
6
6
  export declare namespace ContentfulCollectionData {
7
7
  const contentType: "collection";
8
8
  type ContentType = typeof contentType;
9
- type ForSchema = Schema.Collection.Data;
10
- type Potential = RequiredBy<Partial<ContentfulCollectionData>, keyof ContentfulData<ContentfulCollectionData.ContentType, Schema.Collection.Id>>;
9
+ type ForSchema = Schema.Collection.Data & {
10
+ __version: number;
11
+ };
12
+ type Potential = RequiredBy<Partial<ContentfulCollectionData>, '__type' | '__version' | 'id' | 'createdOn' | 'updatedOn' | 'state'>;
11
13
  function deserialize(data: Potential, props?: ReadonlyArray<Schema.Collection.Data.Props.All>): ForSchema;
12
14
  function deserializeRefs(refs: ContentfulData[]): Schema.Collection.References;
13
15
  }
@@ -14,9 +14,12 @@ var ContentfulCollectionData;
14
14
  // create base collection
15
15
  const collection = {
16
16
  __root: 'collection',
17
+ __version: data.__version,
17
18
  id: data.id,
18
19
  createdOn: data.createdOn,
19
20
  updatedOn: data.updatedOn,
21
+ publishedOn: data.publishedOn,
22
+ state: data.state,
20
23
  references: (_a = data.references) !== null && _a !== void 0 ? _a : []
21
24
  };
22
25
  // helper
@@ -32,6 +35,8 @@ var ContentfulCollectionData;
32
35
  case 'id':
33
36
  case 'createdOn':
34
37
  case 'updatedOn':
38
+ case 'publishedOn':
39
+ case 'state':
35
40
  break;
36
41
  // direct transforms
37
42
  case 'groupId':
@@ -40,11 +45,9 @@ var ContentfulCollectionData;
40
45
  case 'language':
41
46
  case 'version':
42
47
  case 'name':
43
- case 'state':
44
48
  case 'errors':
45
49
  case 'pageIds':
46
50
  case 'tags':
47
- case 'publishedOn':
48
51
  bindProp(prop, prop, value => value !== null && value !== void 0 ? value : undefined);
49
52
  break;
50
53
  // content
@@ -1,18 +1,21 @@
1
1
  import type { EntryAPI } from '@contentful/app-sdk';
2
2
  import type { Entry } from 'contentful';
3
3
  import type { EntryProps } from 'contentful-management';
4
- export type ContentfulData<TType extends string = string, TId extends string = string> = {
4
+ export type ContentfulData<TType extends string = string, TId extends string = string, TState extends string = string> = {
5
5
  __type: TType;
6
6
  __version: number;
7
7
  id: TId;
8
8
  createdOn: number;
9
9
  updatedOn: number;
10
+ publishedOn?: number;
11
+ state: TState;
10
12
  };
11
13
  export declare namespace ContentfulData {
12
14
  type ContentType<T extends ContentfulData> = T['__type'];
13
15
  type EntryId<T extends ContentfulData> = T['id'];
14
16
  type References = Array<Link.Entry | Link.Asset>;
15
17
  type Draft<T extends ContentfulData> = Omit<T, keyof ContentfulData | '__root'>;
18
+ type Update<T extends ContentfulData> = Pick<ContentfulData, 'id' | '__version'> & Partial<Draft<T>>;
16
19
  type Link<TType extends Link.Type = Link.Type> = {
17
20
  sys: {
18
21
  type: 'Link';
@@ -31,6 +34,7 @@ export declare namespace ContentfulData {
31
34
  [locale: string]: TData[TKey];
32
35
  };
33
36
  };
37
+ function calcState<TState extends string = string>({ updatedOn, publishedOn }: Pick<ContentfulData, 'updatedOn' | 'publishedOn'>): TState;
34
38
  function toEntryData<T extends Record<string, any>>(entry: T, locale: string): Fields<T>;
35
39
  function fromEntryData<T extends ContentfulData>(entry: EntryAPI): T;
36
40
  function fromCdaData<T extends ContentfulData>({ sys, fields }: Entry): T;
@@ -19,6 +19,14 @@ var ContentfulData;
19
19
  Link.create = create;
20
20
  })(Link = ContentfulData.Link || (ContentfulData.Link = {}));
21
21
  // helpers
22
+ function calcState({ updatedOn, publishedOn }) {
23
+ return (publishedOn
24
+ ? publishedOn === updatedOn
25
+ ? 'published'
26
+ : 'unpublished'
27
+ : 'draft');
28
+ }
29
+ ContentfulData.calcState = calcState;
22
30
  function toEntryData(entry, locale) {
23
31
  return Object.entries(entry).reduce((entryData, [keyRaw, value]) => {
24
32
  // map entry (bucket under locale)
@@ -37,8 +45,16 @@ var ContentfulData;
37
45
  for (const key in entry.fields) {
38
46
  normalizedFields[key] = entry.fields[key].getValue();
39
47
  }
48
+ // parse date strings
49
+ const createdOn = Date.parse(sys.createdAt);
50
+ const updatedOn = Date.parse(sys.updatedAt);
51
+ const publishedOn = sys.publishedAt === undefined
52
+ ? undefined
53
+ : Date.parse(sys.publishedAt);
40
54
  // return normalized form
41
- return Object.assign({ __type: sys.contentType.sys.id, __version: sys.version, id: sys.id, createdOn: new Date(sys.createdAt).getTime(), updatedOn: new Date(sys.updatedAt).getTime() }, normalizedFields);
55
+ return Object.assign(Object.assign({}, normalizedFields), { __type: sys.contentType.sys.id, __version: sys.version, id: sys.id, createdOn,
56
+ updatedOn,
57
+ publishedOn, state: calcState({ updatedOn, publishedOn }) });
42
58
  }
43
59
  ContentfulData.fromEntryData = fromEntryData;
44
60
  function fromCdaData({ sys, fields }) {
@@ -48,7 +64,9 @@ var ContentfulData;
48
64
  normalizedFields[key] = fields[key];
49
65
  }
50
66
  // return normalized form
51
- return Object.assign({ __type: sys.contentType.sys.id, __version: sys.revision, id: sys.id, createdOn: new Date(sys.createdAt).getTime(), updatedOn: new Date(sys.updatedAt).getTime() }, normalizedFields);
67
+ return Object.assign(Object.assign({}, normalizedFields), { __type: sys.contentType.sys.id, __version: sys.revision, id: sys.id, createdOn: Date.parse(sys.createdAt), updatedOn: Date.parse(sys.updatedAt),
68
+ // using updatedAt since it has to be published for delivery api
69
+ publishedOn: Date.parse(sys.updatedAt), state: 'published' });
52
70
  }
53
71
  ContentfulData.fromCdaData = fromCdaData;
54
72
  function fromCmaData({ sys, fields }, locale) {
@@ -57,8 +75,16 @@ var ContentfulData;
57
75
  for (const key in fields) {
58
76
  normalizedFields[key] = fields[key][locale];
59
77
  }
78
+ // parse date strings
79
+ const createdOn = Date.parse(sys.createdAt);
80
+ const updatedOn = Date.parse(sys.updatedAt);
81
+ const publishedOn = sys.publishedAt === undefined
82
+ ? undefined
83
+ : Date.parse(sys.publishedAt);
60
84
  // return normalized form
61
- return Object.assign({ __type: sys.contentType.sys.id, __version: sys.version, id: sys.id, createdOn: new Date(sys.createdAt).getTime(), updatedOn: new Date(sys.updatedAt).getTime() }, normalizedFields);
85
+ return Object.assign(Object.assign({}, normalizedFields), { __type: sys.contentType.sys.id, __version: sys.version, id: sys.id, createdOn,
86
+ updatedOn,
87
+ publishedOn, state: calcState({ updatedOn, publishedOn }) });
62
88
  }
63
89
  ContentfulData.fromCmaData = fromCmaData;
64
90
  function findReferences(source) {
@@ -1,6 +1,6 @@
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, Schema.Page.State> & Omit<Schema.Page.Data, 'id' | 'changeSetId' | 'snapshotId' | 'snapshot'> & {
4
4
  references: ContentfulData.References;
5
5
  };
6
6
  export declare namespace ContentfulPageData {
@@ -17,7 +17,7 @@ export declare namespace ContentfulPageData {
17
17
  function deserialize(snapshot: Snapshot): Schema.PageSnapshot;
18
18
  }
19
19
  type ForSchema = Schema.Page.Data;
20
- type Potential = RequiredBy<Partial<ContentfulPageData>, keyof ContentfulData<ContentfulPageData.ContentType, Schema.Page.Id>>;
20
+ type Potential = RequiredBy<Partial<ContentfulPageData>, '__type' | '__version' | 'id' | 'createdOn' | 'updatedOn' | 'state'>;
21
21
  function deserialize(data: Potential, props?: ReadonlyArray<Schema.Page.Data.Props.All>): ForSchema;
22
22
  function deserializeRefs(refs: ContentfulData[]): Schema.Page.References;
23
23
  }
@@ -32,6 +32,8 @@ var ContentfulPageData;
32
32
  id: data.id,
33
33
  createdOn: data.createdOn,
34
34
  updatedOn: data.updatedOn,
35
+ publishedOn: data.publishedOn,
36
+ state: data.state,
35
37
  references: (_a = data.references) !== null && _a !== void 0 ? _a : []
36
38
  };
37
39
  // helper
@@ -47,6 +49,8 @@ var ContentfulPageData;
47
49
  case 'id':
48
50
  case 'createdOn':
49
51
  case 'updatedOn':
52
+ case 'publishedOn':
53
+ case 'state':
50
54
  break;
51
55
  // direct transforms
52
56
  case 'groupId':
@@ -58,10 +62,8 @@ var ContentfulPageData;
58
62
  case 'url':
59
63
  case 'name':
60
64
  case 'preview':
61
- case 'state':
62
65
  case 'errors':
63
66
  case 'tags':
64
- case 'publishedOn':
65
67
  case 'publication':
66
68
  bindProp(prop, prop, value => value !== null && value !== void 0 ? value : undefined);
67
69
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nascentdigital/funnel-cms",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
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": "83e335312f91fb90aae73dfc31b3df1ec1895f76"
51
+ "gitHead": "697c3651dedb43a6e6ff373a495cb29c7e7e1fc6"
52
52
  }