@raketa-cloud/admin 1.7.4 → 1.7.5

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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/HonoAdapter.js +7 -3
  3. package/src/cloud/Articles.js +10 -3
  4. package/src/cloud/Events.js +9 -2
  5. package/src/cloud/Pages.js +11 -1
  6. package/src/cloud/documents/articlesDocuments.js +4 -2
  7. package/src/cloud/documents/eventsDocuments.js +4 -2
  8. package/src/cloud/documents/pagesDocuments.js +4 -2
  9. package/src/handlers/articles/ArticlesHandler.js +10 -4
  10. package/src/handlers/articles/ExportsHandler.js +35 -0
  11. package/src/handlers/events/EventsHandler.js +10 -4
  12. package/src/handlers/events/ExportsHandler.js +35 -0
  13. package/src/handlers/pages/ExportsHandler.js +35 -0
  14. package/src/handlers/pages/PagesHandler.js +10 -4
  15. package/src/helpers/CSVExportHelper.js +103 -0
  16. package/src/routeArticlesModule.js +13 -0
  17. package/src/routeEventsModule.js +13 -0
  18. package/src/routePagesModule.js +13 -0
  19. package/src/tests/handlers/articles/ArticlesHandler.test/index.test.js +18 -2
  20. package/src/tests/handlers/articles/ExportsHandler.test/index.test.js +217 -0
  21. package/src/tests/handlers/events/EventsHandler.test/index.test.js +17 -1
  22. package/src/tests/handlers/events/ExportsHandler.test/index.test.js +212 -0
  23. package/src/tests/handlers/pages/ExportsHandler.test/index.test.js +212 -0
  24. package/src/tests/handlers/pages/PagesHandler.test/index.test.js +17 -1
  25. package/src/tests/helpers/CSVExportHelper.test.js +364 -0
  26. package/src/views/articles/_shared/_export_button.ejs +9 -0
  27. package/src/views/articles/index.ejs +37 -1
  28. package/src/views/events/_shared/_export_button.ejs +9 -0
  29. package/src/views/events/index.ejs +37 -1
  30. package/src/views/layouts/application.ejs +2 -0
  31. package/src/views/pages/_shared/_export_button.ejs +9 -0
  32. package/src/views/pages/index.ejs +37 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "1.7.4",
3
+ "version": "1.7.5",
4
4
  "main": "src/index.js",
5
5
  "files": [
6
6
  "src/",
@@ -364,6 +364,7 @@ export default class HonoAdapter {
364
364
  if (result.headers) this.applyHeaders(result.headers, routerCtx);
365
365
  if (result.redirect) return routerCtx.redirect(result.redirect);
366
366
  if (result.json) return routerCtx.json(result.json);
367
+ if (result.text) return routerCtx.text(result.text);
367
368
 
368
369
  // Process global after filters
369
370
  for (const { pattern, filter } of this.globalAfterFilters) {
@@ -373,6 +374,7 @@ export default class HonoAdapter {
373
374
  if (afterResult.redirect)
374
375
  return routerCtx.redirect(afterResult.redirect);
375
376
  if (afterResult.json) return routerCtx.json(afterResult.json);
377
+ if (afterResult.text) return routerCtx.text(afterResult.text);
376
378
 
377
379
  Object.assign(
378
380
  assigns.afterFilterAssigns,
@@ -437,9 +439,11 @@ export default class HonoAdapter {
437
439
 
438
440
  formatQuery(queries) {
439
441
  const result = {};
440
- Object.entries(queries).forEach(([key, value]) => {
441
- result[key] = value.length === 1 ? value[0] : value;
442
- });
442
+ if (queries && typeof queries === "object") {
443
+ Object.entries(queries).forEach(([key, value]) => {
444
+ result[key] = value.length === 1 ? value[0] : value;
445
+ });
446
+ }
443
447
  return result;
444
448
  }
445
449
  }
@@ -1,4 +1,4 @@
1
- import { dateToUTCISO8601String, toDateInput } from "../utils";
1
+ import { dateToUTCISO8601String, toUTCISO8601String, toDateInput } from "../utils";
2
2
  import RaketaClient from "./RaketaClient";
3
3
  import {
4
4
  ArticleByIdDocument,
@@ -71,6 +71,13 @@ export default class Articles extends RaketaClient {
71
71
  async list(query, locale, articleTypeSlug) {
72
72
  const vars = this.buildListInput(query, locale);
73
73
 
74
+ if (vars.input.filters.last_updated_from) {
75
+ vars.input.filters.last_updated_from = toUTCISO8601String(vars.input.filters.last_updated_from);
76
+ }
77
+ if (vars.input.filters.last_updated_to) {
78
+ vars.input.filters.last_updated_to = toUTCISO8601String(vars.input.filters.last_updated_to);
79
+ }
80
+
74
81
  const { articles: items } = await this.query(ArticlesDocument, {
75
82
  input: {
76
83
  ...vars.input,
@@ -150,7 +157,7 @@ export default class Articles extends RaketaClient {
150
157
  paramsCopy.settings = JSON.stringify(paramsCopy.settings);
151
158
 
152
159
  const BatchTranslateDocument = buildBatchTranslateArticleDocument(
153
- locales.length
160
+ locales.length,
154
161
  );
155
162
 
156
163
  const variables = {};
@@ -204,7 +211,7 @@ export default class Articles extends RaketaClient {
204
211
  ArticleChangePreviewDocument,
205
212
  {
206
213
  input,
207
- }
214
+ },
208
215
  );
209
216
 
210
217
  return articleChangePreview;
@@ -86,6 +86,13 @@ export default class Events extends RaketaClient {
86
86
  async list(query, locale, eventTypeSlug) {
87
87
  const vars = this.buildListInput(query, locale);
88
88
 
89
+ if (vars.input.filters.last_updated_from) {
90
+ vars.input.filters.last_updated_from = toUTCISO8601String(vars.input.filters.last_updated_from);
91
+ }
92
+ if (vars.input.filters.last_updated_to) {
93
+ vars.input.filters.last_updated_to = toUTCISO8601String(vars.input.filters.last_updated_to);
94
+ }
95
+
89
96
  const { events: items } = await this.query(EventsDocument, {
90
97
  input: {
91
98
  ...vars.input,
@@ -165,7 +172,7 @@ export default class Events extends RaketaClient {
165
172
  paramsCopy.settings = JSON.stringify(paramsCopy.settings);
166
173
 
167
174
  const BatchTranslateDocument = buildBatchTranslateEventDocument(
168
- locales.length
175
+ locales.length,
169
176
  );
170
177
 
171
178
  const variables = {};
@@ -219,7 +226,7 @@ export default class Events extends RaketaClient {
219
226
  EventChangePreviewDocument,
220
227
  {
221
228
  input,
222
- }
229
+ },
223
230
  );
224
231
 
225
232
  return eventChangePreview;
@@ -1,3 +1,4 @@
1
+ import { toUTCISO8601String } from "../utils";
1
2
  import RaketaClient from "./RaketaClient";
2
3
  import {
3
4
  ChangePageSlugDocument,
@@ -61,6 +62,15 @@ export default class Pages extends RaketaClient {
61
62
  async list(query, locale, pageTypeSlug) {
62
63
  const vars = this.buildListInput(query, locale);
63
64
 
65
+ if (vars.input.filters.last_updated_from) {
66
+ vars.input.filters.last_updated_from = toUTCISO8601String(vars.input.filters.last_updated_from);
67
+ }
68
+ if (vars.input.filters.last_updated_to) {
69
+ vars.input.filters.last_updated_to = toUTCISO8601String(vars.input.filters.last_updated_to);
70
+ }
71
+
72
+ console.log(vars.input.filters)
73
+
64
74
  const { pages: items } = await this.query(PagesDocument, {
65
75
  input: {
66
76
  ...vars.input,
@@ -141,7 +151,7 @@ export default class Pages extends RaketaClient {
141
151
  copyParams.settings = JSON.stringify(copyParams.settings);
142
152
 
143
153
  const BatchTranslateDocument = buildBatchTranslatePageDocument(
144
- locales.length
154
+ locales.length,
145
155
  );
146
156
 
147
157
  const variables = {};
@@ -18,6 +18,8 @@ function minimalArticleFragment() {
18
18
  title
19
19
  visibility
20
20
  publishAt
21
+ insertedAt
22
+ updatedAt
21
23
  }
22
24
  `;
23
25
  }
@@ -196,7 +198,7 @@ const buildBatchTranslateArticleDocument = (count) => gql`
196
198
  article ${articleFragment()}
197
199
  errors ${errorsFragment()}
198
200
  }
199
- `
201
+ `,
200
202
  )
201
203
  .join("\n")}
202
204
  }
@@ -240,7 +242,7 @@ const buildBatchStartArticleRevisionDocument = (count) => gql`
240
242
  errors ${errorsFragment()}
241
243
  articleErrors ${errorsFragment()}
242
244
  }
243
- `
245
+ `,
244
246
  )
245
247
  .join("\n")}
246
248
  }
@@ -20,6 +20,8 @@ function minimalEventFragment() {
20
20
  publishAt
21
21
  startsAt
22
22
  endsAt
23
+ insertedAt
24
+ updatedAt
23
25
  }
24
26
  `;
25
27
  }
@@ -204,7 +206,7 @@ const buildBatchTranslateEventDocument = (count) => gql`
204
206
  event ${eventFragment()}
205
207
  errors ${errorsFragment()}
206
208
  }
207
- `
209
+ `,
208
210
  )
209
211
  .join("\n")}
210
212
  }
@@ -232,7 +234,7 @@ const buildBatchStartEventRevisionDocument = (count) => gql`
232
234
  errors ${errorsFragment()}
233
235
  eventErrors ${errorsFragment()}
234
236
  }
235
- `
237
+ `,
236
238
  )
237
239
  .join("\n")}
238
240
  }
@@ -17,6 +17,8 @@ function minimalPageFragment() {
17
17
  slug
18
18
  title
19
19
  visibility
20
+ insertedAt
21
+ updatedAt
20
22
  }
21
23
  `;
22
24
  }
@@ -192,7 +194,7 @@ const buildBatchTranslatePageDocument = (count) => gql`
192
194
  page ${pageFragment()}
193
195
  errors ${errorsFragment()}
194
196
  }
195
- `
197
+ `,
196
198
  )
197
199
  .join("\n")}
198
200
  }
@@ -236,7 +238,7 @@ const buildBatchStartPageRevisionDocument = (count) => gql`
236
238
  errors ${errorsFragment()}
237
239
  pageErrors ${errorsFragment()}
238
240
  }
239
- `
241
+ `,
240
242
  )
241
243
  .join("\n")}
242
244
  }
@@ -4,13 +4,19 @@ const index = async (ctx) => {
4
4
  const items = await ctx.client.articles.list(
5
5
  ctx.query,
6
6
  ctx.locale,
7
- ctx.resourceConfig.articleType
7
+ ctx.resourceConfig.articleType,
8
8
  );
9
9
  const tags = await ctx.client.tags.all(ctx.locale);
10
+ const languages = ctx.client.articles.getConfig().i18n.locales;
11
+ const translations = Object.keys(languages).map((key) => ({
12
+ name: languages[key].name,
13
+ code: key,
14
+ }));
10
15
 
11
16
  return {
12
17
  assigns: {
13
18
  tags: tags.list,
19
+ translations,
14
20
  items,
15
21
  visibilityOptions: ctx.client.articles.visibilityOptions(),
16
22
  },
@@ -36,7 +42,7 @@ const show = async (ctx) => {
36
42
  ],
37
43
  },
38
44
  id,
39
- ctx.locale
45
+ ctx.locale,
40
46
  );
41
47
 
42
48
  return {
@@ -81,13 +87,13 @@ const create = async (ctx) => {
81
87
 
82
88
  const resource = ctx.client.articles.deepMerge(
83
89
  ctx.client.articles.defaults(),
84
- ctx.client.articles.formatDates(params)
90
+ ctx.client.articles.formatDates(params),
85
91
  );
86
92
 
87
93
  const { errors } = await ctx.client.articles.create(
88
94
  resource,
89
95
  ctx.locale,
90
- ctx.resourceConfig.articleType
96
+ ctx.resourceConfig.articleType,
91
97
  );
92
98
 
93
99
  if (errors) {
@@ -0,0 +1,35 @@
1
+ import CSVExportHelper from "../../helpers/CSVExportHelper";
2
+
3
+ const exportCSV = async (ctx) => {
4
+ const { query, client, resourceConfig, remoteConfig } = ctx;
5
+ const articleType = resourceConfig.articleType;
6
+ const locale = query.locale || "en";
7
+
8
+ const articles = await client.articles.listAll(query, locale, articleType);
9
+
10
+ const fieldMapping = CSVExportHelper.standardFieldMapping(
11
+ remoteConfig,
12
+ locale,
13
+ articleType,
14
+ );
15
+
16
+ const headers = [...CSVExportHelper.standardHeaders];
17
+
18
+ const csv = CSVExportHelper.formatDataAsCSV(
19
+ articles.list,
20
+ headers,
21
+ fieldMapping,
22
+ );
23
+
24
+ return {
25
+ text: csv,
26
+ headers: {
27
+ "Content-Type": "text/csv",
28
+ "Content-Disposition": `attachment; filename="${CSVExportHelper.generateExportFilename(articleType)}"`,
29
+ },
30
+ };
31
+ };
32
+
33
+ export default {
34
+ index: exportCSV,
35
+ };
@@ -4,13 +4,19 @@ const index = async (ctx) => {
4
4
  const items = await ctx.client.events.list(
5
5
  ctx.query,
6
6
  ctx.locale,
7
- ctx.resourceConfig.eventType
7
+ ctx.resourceConfig.eventType,
8
8
  );
9
9
  const tags = await ctx.client.tags.all(ctx.locale);
10
+ const languages = ctx.client.events.getConfig().i18n.locales;
11
+ const translations = Object.keys(languages).map((key) => ({
12
+ name: languages[key].name,
13
+ code: key,
14
+ }));
10
15
 
11
16
  return {
12
17
  assigns: {
13
18
  tags: tags.list,
19
+ translations,
14
20
  items,
15
21
  visibilityOptions: ctx.client.events.visibilityOptions(),
16
22
  },
@@ -36,7 +42,7 @@ const show = async (ctx) => {
36
42
  ],
37
43
  },
38
44
  id,
39
- ctx.locale
45
+ ctx.locale,
40
46
  );
41
47
 
42
48
  return {
@@ -81,13 +87,13 @@ const create = async (ctx) => {
81
87
 
82
88
  const resource = ctx.client.events.deepMerge(
83
89
  ctx.client.events.defaults(),
84
- ctx.client.events.formatDates(params)
90
+ ctx.client.events.formatDates(params),
85
91
  );
86
92
 
87
93
  const { errors } = await ctx.client.events.create(
88
94
  resource,
89
95
  ctx.locale,
90
- ctx.resourceConfig.eventType
96
+ ctx.resourceConfig.eventType,
91
97
  );
92
98
 
93
99
  if (errors) {
@@ -0,0 +1,35 @@
1
+ import CSVExportHelper from "../../helpers/CSVExportHelper";
2
+
3
+ const exportCSV = async (ctx) => {
4
+ const { query, client, resourceConfig, remoteConfig } = ctx;
5
+ const eventType = resourceConfig.eventType;
6
+ const locale = query.locale || "en";
7
+
8
+ const events = await client.events.listAll(query, locale, eventType);
9
+
10
+ const fieldMapping = CSVExportHelper.standardFieldMapping(
11
+ remoteConfig,
12
+ locale,
13
+ eventType,
14
+ );
15
+
16
+ const headers = [...CSVExportHelper.standardHeaders];
17
+
18
+ const csv = CSVExportHelper.formatDataAsCSV(
19
+ events.list,
20
+ headers,
21
+ fieldMapping,
22
+ );
23
+
24
+ return {
25
+ text: csv,
26
+ headers: {
27
+ "Content-Type": "text/csv",
28
+ "Content-Disposition": `attachment; filename="${CSVExportHelper.generateExportFilename(eventType)}"`,
29
+ },
30
+ };
31
+ };
32
+
33
+ export default {
34
+ index: exportCSV,
35
+ };
@@ -0,0 +1,35 @@
1
+ import CSVExportHelper from "../../helpers/CSVExportHelper";
2
+
3
+ const exportCSV = async (ctx) => {
4
+ const { query, client, resourceConfig, remoteConfig } = ctx;
5
+ const pageType = resourceConfig.pageType;
6
+ const locale = query.locale || "en";
7
+
8
+ const pages = await client.pages.listAll(query, locale, pageType);
9
+
10
+ const fieldMapping = CSVExportHelper.standardFieldMapping(
11
+ remoteConfig,
12
+ locale,
13
+ pageType,
14
+ );
15
+
16
+ const headers = [...CSVExportHelper.standardHeaders];
17
+
18
+ const csv = CSVExportHelper.formatDataAsCSV(
19
+ pages.list,
20
+ headers,
21
+ fieldMapping,
22
+ );
23
+
24
+ return {
25
+ text: csv,
26
+ headers: {
27
+ "Content-Type": "text/csv",
28
+ "Content-Disposition": `attachment; filename="${CSVExportHelper.generateExportFilename(pageType)}"`,
29
+ },
30
+ };
31
+ };
32
+
33
+ export default {
34
+ index: exportCSV,
35
+ };
@@ -2,14 +2,20 @@ const index = async (ctx) => {
2
2
  const items = await ctx.client.pages.list(
3
3
  ctx.query,
4
4
  ctx.locale,
5
- ctx.resourceConfig.pageType
5
+ ctx.resourceConfig.pageType,
6
6
  );
7
7
 
8
8
  const tags = await ctx.client.tags.all(ctx.locale);
9
+ const languages = ctx.client.pages.getConfig().i18n.locales;
10
+ const translations = Object.keys(languages).map((key) => ({
11
+ name: languages[key].name,
12
+ code: key,
13
+ }));
9
14
 
10
15
  return {
11
16
  assigns: {
12
17
  tags: tags.list,
18
+ translations,
13
19
  items,
14
20
  visibilityOptions: ctx.client.pages.visibilityOptions(),
15
21
  },
@@ -35,7 +41,7 @@ const show = async (ctx) => {
35
41
  ],
36
42
  },
37
43
  id,
38
- ctx.locale
44
+ ctx.locale,
39
45
  );
40
46
 
41
47
  return {
@@ -79,13 +85,13 @@ const create = async (ctx) => {
79
85
  const params = { ...ctx.resourceParams.resource };
80
86
  const resource = ctx.client.pages.deepMerge(
81
87
  ctx.client.pages.defaults(),
82
- params
88
+ params,
83
89
  );
84
90
 
85
91
  const { errors } = await ctx.client.pages.create(
86
92
  resource,
87
93
  ctx.locale,
88
- ctx.resourceConfig.pageType
94
+ ctx.resourceConfig.pageType,
89
95
  );
90
96
 
91
97
  if (errors) {
@@ -0,0 +1,103 @@
1
+ function escapeCSVValue(value) {
2
+ if (value === null || value === undefined) {
3
+ return "";
4
+ }
5
+
6
+ const stringValue = String(value);
7
+
8
+ if (
9
+ stringValue.includes(",") ||
10
+ stringValue.includes('"') ||
11
+ stringValue.includes("\n") ||
12
+ stringValue.includes("\r")
13
+ ) {
14
+ return `"${stringValue.replace(/"/g, '""')}"`;
15
+ }
16
+
17
+ return stringValue;
18
+ }
19
+
20
+ function formatDataAsCSV(data, headers, fieldMapping) {
21
+ const headerRow = headers.join(",");
22
+
23
+ const rows = data.map((item) => {
24
+ return headers
25
+ .map((header) => {
26
+ const field = fieldMapping[header];
27
+ let value = "";
28
+
29
+ if (typeof field === "function") {
30
+ value = field(item);
31
+ } else if (field.includes(".")) {
32
+ const props = field.split(".");
33
+ let nestedValue = item;
34
+
35
+ for (const prop of props) {
36
+ if (nestedValue && nestedValue[prop] !== undefined) {
37
+ nestedValue = nestedValue[prop];
38
+ } else {
39
+ nestedValue = "";
40
+ break;
41
+ }
42
+ }
43
+
44
+ value = nestedValue;
45
+ } else {
46
+ value = item[field] !== undefined ? item[field] : "";
47
+ }
48
+
49
+ return escapeCSVValue(value);
50
+ })
51
+ .join(",");
52
+ });
53
+
54
+ return [headerRow, ...rows].join("\r\n");
55
+ }
56
+
57
+ function generateExportFilename(contentType) {
58
+ const date = new Date();
59
+ const day = String(date.getDate()).padStart(2, "0");
60
+ const month = String(date.getMonth() + 1).padStart(2, "0");
61
+ const year = String(date.getFullYear()).slice(2);
62
+
63
+ return `${day}${month}${year}_${contentType}_export.csv`;
64
+ }
65
+
66
+ const extractResourceType = (resource, type) => {
67
+ if (resource.pageType) return resource.pageType;
68
+ if (resource.articleType) return resource.articleType;
69
+ if (resource.eventType) return resource.eventType;
70
+ if (resource.resourceType) return resource.resourceType;
71
+ if (resource.type) return resource.type;
72
+ return type;
73
+ };
74
+
75
+ const standardFieldMapping = (remoteConfig, locale, type) => ({
76
+ id: "id",
77
+ type: (item) => extractResourceType(item, type),
78
+ locale: (item) => (item.locale ? item.locale : locale),
79
+ title: "title",
80
+ URL: (item) => remoteConfig.resourceUrl(item, type, locale),
81
+ visibility: "visibility",
82
+ created_at: "insertedAt",
83
+ updated_at: "updatedAt",
84
+ });
85
+
86
+ const standardHeaders = [
87
+ "id",
88
+ "type",
89
+ "locale",
90
+ "title",
91
+ "URL",
92
+ "visibility",
93
+ "created_at",
94
+ "updated_at",
95
+ ];
96
+
97
+ export default {
98
+ escapeCSVValue,
99
+ formatDataAsCSV,
100
+ generateExportFilename,
101
+ standardFieldMapping,
102
+ standardHeaders,
103
+ };
@@ -15,6 +15,7 @@ import ArticlesHandler from "./handlers/articles/ArticlesHandler";
15
15
  import ChangeHandler from "./handlers/articles/ChangeHandler";
16
16
  import ClonesHandler from "./handlers/articles/ClonesHandler";
17
17
  import DeletesHandler from "./handlers/articles/DeletesHandler";
18
+ import ExportsHandler from "./handlers/articles/ExportsHandler";
18
19
  import HistoryHandler from "./handlers/articles/HistoryHandler";
19
20
  import TranslationsHandler from "./handlers/articles/TranslationsHandler";
20
21
  import URLsHandler from "./handlers/articles/URLsHandler";
@@ -35,6 +36,18 @@ function routeArticlesModule(
35
36
  articleTypePlural,
36
37
  };
37
38
 
39
+ const exportsMapping = [
40
+ [
41
+ "GET",
42
+ `/${baseResource}/exports`,
43
+ "exports",
44
+ "articles/exports",
45
+ "index",
46
+ ExportsHandler.index,
47
+ ],
48
+ ];
49
+ adapter.routeCollection(exportsMapping, resourceConfig);
50
+
38
51
  routeCollection(adapter, baseResource, ArticlesHandler, {
39
52
  ...resourceConfig,
40
53
  viewsFallback: "articles",
@@ -15,6 +15,7 @@ import ClonesHandler from "./handlers/events/ClonesHandler";
15
15
  import DeletesHandler from "./handlers/events/DeletesHandler";
16
16
  import EventsAPIHandler from "./handlers/events/EventsAPIHandler";
17
17
  import EventsHandler from "./handlers/events/EventsHandler";
18
+ import ExportsHandler from "./handlers/events/ExportsHandler";
18
19
  import HistoryHandler from "./handlers/events/HistoryHandler";
19
20
  import TranslationsHandler from "./handlers/events/TranslationsHandler";
20
21
  import URLsHandler from "./handlers/events/URLsHandler";
@@ -35,6 +36,18 @@ function routeEventsModule(
35
36
  eventTypePlural,
36
37
  };
37
38
 
39
+ const exportsMapping = [
40
+ [
41
+ "GET",
42
+ `/${baseResource}/exports`,
43
+ "exports",
44
+ "events/exports",
45
+ "index",
46
+ ExportsHandler.index,
47
+ ],
48
+ ];
49
+ adapter.routeCollection(exportsMapping, resourceConfig);
50
+
38
51
  routeCollection(adapter, baseResource, EventsHandler, {
39
52
  ...resourceConfig,
40
53
  viewsFallback: "events",
@@ -13,6 +13,7 @@ import WidgetHandler from "./handlers/page_revisions/WidgetHandler";
13
13
  import ChangeHandler from "./handlers/pages/ChangeHandler";
14
14
  import ClonesHandler from "./handlers/pages/ClonesHandler";
15
15
  import DeletesHandler from "./handlers/pages/DeletesHandler";
16
+ import ExportsHandler from "./handlers/pages/ExportsHandler";
16
17
  import HistoryHandler from "./handlers/pages/HistoryHandler";
17
18
  import PagesAPIHandler from "./handlers/pages/PagesAPIHandler";
18
19
  import PagesHandler from "./handlers/pages/PagesHandler";
@@ -35,6 +36,18 @@ function routePagesModule(
35
36
  pageTypePlural,
36
37
  };
37
38
 
39
+ const exportsMapping = [
40
+ [
41
+ "GET",
42
+ `/${baseResource}/exports`,
43
+ "exports",
44
+ "pages/exports",
45
+ "index",
46
+ ExportsHandler.index,
47
+ ],
48
+ ];
49
+ adapter.routeCollection(exportsMapping, resourceConfig);
50
+
38
51
  routeCollection(adapter, pageTypePlural, PagesHandler, {
39
52
  ...resourceConfig,
40
53
  viewsFallback: "pages",