@endora-commerce/mod-cms 0.0.0-stage → 0.100.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.
Files changed (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -2
  3. package/dist/admin/api/cms-client.d.ts +77 -0
  4. package/dist/admin/api/cms-client.d.ts.map +1 -0
  5. package/dist/admin/api/cms-client.js +162 -0
  6. package/dist/admin/api/cms-client.js.map +1 -0
  7. package/dist/admin/components/AdminCatalogPreviewProvider.d.ts +6 -0
  8. package/dist/admin/components/AdminCatalogPreviewProvider.d.ts.map +1 -0
  9. package/dist/admin/components/AdminCatalogPreviewProvider.js +74 -0
  10. package/dist/admin/components/AdminCatalogPreviewProvider.js.map +1 -0
  11. package/dist/admin/components/AdminCmsAssetProvider.d.ts +9 -0
  12. package/dist/admin/components/AdminCmsAssetProvider.d.ts.map +1 -0
  13. package/dist/admin/components/AdminCmsAssetProvider.js +50 -0
  14. package/dist/admin/components/AdminCmsAssetProvider.js.map +1 -0
  15. package/dist/admin/components/BackgroundFields.d.ts +4 -0
  16. package/dist/admin/components/BackgroundFields.d.ts.map +1 -0
  17. package/dist/admin/components/BackgroundFields.js +40 -0
  18. package/dist/admin/components/BackgroundFields.js.map +1 -0
  19. package/dist/admin/components/ButtonLinkFields.d.ts +6 -0
  20. package/dist/admin/components/ButtonLinkFields.d.ts.map +1 -0
  21. package/dist/admin/components/ButtonLinkFields.js +127 -0
  22. package/dist/admin/components/ButtonLinkFields.js.map +1 -0
  23. package/dist/admin/components/CarouselPreviewNav.d.ts +12 -0
  24. package/dist/admin/components/CarouselPreviewNav.d.ts.map +1 -0
  25. package/dist/admin/components/CarouselPreviewNav.js +31 -0
  26. package/dist/admin/components/CarouselPreviewNav.js.map +1 -0
  27. package/dist/admin/components/CmsContentEditorLayout.d.ts +12 -0
  28. package/dist/admin/components/CmsContentEditorLayout.d.ts.map +1 -0
  29. package/dist/admin/components/CmsContentEditorLayout.js +9 -0
  30. package/dist/admin/components/CmsContentEditorLayout.js.map +1 -0
  31. package/dist/admin/components/ComponentDragHandle.d.ts +6 -0
  32. package/dist/admin/components/ComponentDragHandle.d.ts.map +1 -0
  33. package/dist/admin/components/ComponentDragHandle.js +119 -0
  34. package/dist/admin/components/ComponentDragHandle.js.map +1 -0
  35. package/dist/admin/components/ContentSliderActionBarExtras.d.ts +6 -0
  36. package/dist/admin/components/ContentSliderActionBarExtras.d.ts.map +1 -0
  37. package/dist/admin/components/ContentSliderActionBarExtras.js +109 -0
  38. package/dist/admin/components/ContentSliderActionBarExtras.js.map +1 -0
  39. package/dist/admin/components/HookBlockAttachmentsPanel.d.ts +9 -0
  40. package/dist/admin/components/HookBlockAttachmentsPanel.d.ts.map +1 -0
  41. package/dist/admin/components/HookBlockAttachmentsPanel.js +137 -0
  42. package/dist/admin/components/HookBlockAttachmentsPanel.js.map +1 -0
  43. package/dist/admin/components/PageBuilderActionBar.d.ts +10 -0
  44. package/dist/admin/components/PageBuilderActionBar.d.ts.map +1 -0
  45. package/dist/admin/components/PageBuilderActionBar.js +118 -0
  46. package/dist/admin/components/PageBuilderActionBar.js.map +1 -0
  47. package/dist/admin/components/PageBuilderDrawer.d.ts +16 -0
  48. package/dist/admin/components/PageBuilderDrawer.d.ts.map +1 -0
  49. package/dist/admin/components/PageBuilderDrawer.js +53 -0
  50. package/dist/admin/components/PageBuilderDrawer.js.map +1 -0
  51. package/dist/admin/components/PageBuilderEditor.d.ts +45 -0
  52. package/dist/admin/components/PageBuilderEditor.d.ts.map +1 -0
  53. package/dist/admin/components/PageBuilderEditor.js +632 -0
  54. package/dist/admin/components/PageBuilderEditor.js.map +1 -0
  55. package/dist/admin/components/PuckActionGuard.d.ts +12 -0
  56. package/dist/admin/components/PuckActionGuard.d.ts.map +1 -0
  57. package/dist/admin/components/PuckActionGuard.js +30 -0
  58. package/dist/admin/components/PuckActionGuard.js.map +1 -0
  59. package/dist/admin/components/RowLayoutPicker.d.ts +8 -0
  60. package/dist/admin/components/RowLayoutPicker.d.ts.map +1 -0
  61. package/dist/admin/components/RowLayoutPicker.js +17 -0
  62. package/dist/admin/components/RowLayoutPicker.js.map +1 -0
  63. package/dist/admin/components/SliderPreviewActionBarExtras.d.ts +6 -0
  64. package/dist/admin/components/SliderPreviewActionBarExtras.d.ts.map +1 -0
  65. package/dist/admin/components/SliderPreviewActionBarExtras.js +32 -0
  66. package/dist/admin/components/SliderPreviewActionBarExtras.js.map +1 -0
  67. package/dist/admin/components/admin-catalog-preview-map.d.ts +17 -0
  68. package/dist/admin/components/admin-catalog-preview-map.d.ts.map +1 -0
  69. package/dist/admin/components/admin-catalog-preview-map.js +18 -0
  70. package/dist/admin/components/admin-catalog-preview-map.js.map +1 -0
  71. package/dist/admin/components/build-viewports.d.ts +23 -0
  72. package/dist/admin/components/build-viewports.d.ts.map +1 -0
  73. package/dist/admin/components/build-viewports.js +23 -0
  74. package/dist/admin/components/build-viewports.js.map +1 -0
  75. package/dist/admin/components/cms-template-layout.d.ts +16 -0
  76. package/dist/admin/components/cms-template-layout.d.ts.map +1 -0
  77. package/dist/admin/components/cms-template-layout.js +53 -0
  78. package/dist/admin/components/cms-template-layout.js.map +1 -0
  79. package/dist/admin/components/page-builder-i18n.d.ts +27 -0
  80. package/dist/admin/components/page-builder-i18n.d.ts.map +1 -0
  81. package/dist/admin/components/page-builder-i18n.js +34 -0
  82. package/dist/admin/components/page-builder-i18n.js.map +1 -0
  83. package/dist/admin/components/puck-safe.d.ts +10 -0
  84. package/dist/admin/components/puck-safe.d.ts.map +1 -0
  85. package/dist/admin/components/puck-safe.js +22 -0
  86. package/dist/admin/components/puck-safe.js.map +1 -0
  87. package/dist/admin/components/scope-utils.d.ts +4 -0
  88. package/dist/admin/components/scope-utils.d.ts.map +1 -0
  89. package/dist/admin/components/scope-utils.js +7 -0
  90. package/dist/admin/components/scope-utils.js.map +1 -0
  91. package/dist/admin/editors/BlockEditor.d.ts +9 -0
  92. package/dist/admin/editors/BlockEditor.d.ts.map +1 -0
  93. package/dist/admin/editors/BlockEditor.js +217 -0
  94. package/dist/admin/editors/BlockEditor.js.map +1 -0
  95. package/dist/admin/editors/PageEditor.d.ts +9 -0
  96. package/dist/admin/editors/PageEditor.d.ts.map +1 -0
  97. package/dist/admin/editors/PageEditor.js +316 -0
  98. package/dist/admin/editors/PageEditor.js.map +1 -0
  99. package/dist/admin/editors/TemplateEditor.d.ts +9 -0
  100. package/dist/admin/editors/TemplateEditor.d.ts.map +1 -0
  101. package/dist/admin/editors/TemplateEditor.js +180 -0
  102. package/dist/admin/editors/TemplateEditor.js.map +1 -0
  103. package/dist/admin/index.d.ts +38 -0
  104. package/dist/admin/index.d.ts.map +1 -0
  105. package/dist/admin/index.js +144 -0
  106. package/dist/admin/index.js.map +1 -0
  107. package/dist/admin/pages/BlocksListPage.d.ts +9 -0
  108. package/dist/admin/pages/BlocksListPage.d.ts.map +1 -0
  109. package/dist/admin/pages/BlocksListPage.js +47 -0
  110. package/dist/admin/pages/BlocksListPage.js.map +1 -0
  111. package/dist/admin/pages/HooksPage.d.ts +9 -0
  112. package/dist/admin/pages/HooksPage.d.ts.map +1 -0
  113. package/dist/admin/pages/HooksPage.js +41 -0
  114. package/dist/admin/pages/HooksPage.js.map +1 -0
  115. package/dist/admin/pages/PagesListPage.d.ts +9 -0
  116. package/dist/admin/pages/PagesListPage.d.ts.map +1 -0
  117. package/dist/admin/pages/PagesListPage.js +51 -0
  118. package/dist/admin/pages/PagesListPage.js.map +1 -0
  119. package/dist/admin/pages/TemplatesListPage.d.ts +9 -0
  120. package/dist/admin/pages/TemplatesListPage.d.ts.map +1 -0
  121. package/dist/admin/pages/TemplatesListPage.js +47 -0
  122. package/dist/admin/pages/TemplatesListPage.js.map +1 -0
  123. package/dist/admin-ui/index.d.ts +90 -0
  124. package/dist/admin-ui/index.d.ts.map +1 -0
  125. package/dist/admin-ui/index.js +89 -0
  126. package/dist/admin-ui/index.js.map +1 -0
  127. package/dist/backend/cli/block-names.d.ts +92 -0
  128. package/dist/backend/cli/block-names.d.ts.map +1 -0
  129. package/dist/backend/cli/block-names.js +132 -0
  130. package/dist/backend/cli/block-names.js.map +1 -0
  131. package/dist/backend/entities/cms-block.entity.d.ts +20 -0
  132. package/dist/backend/entities/cms-block.entity.d.ts.map +1 -0
  133. package/dist/backend/entities/cms-block.entity.js +77 -0
  134. package/dist/backend/entities/cms-block.entity.js.map +1 -0
  135. package/dist/backend/entities/cms-hook-block-attachment.entity.d.ts +15 -0
  136. package/dist/backend/entities/cms-hook-block-attachment.entity.d.ts.map +1 -0
  137. package/dist/backend/entities/cms-hook-block-attachment.entity.js +46 -0
  138. package/dist/backend/entities/cms-hook-block-attachment.entity.js.map +1 -0
  139. package/dist/backend/entities/cms-hook.entity.d.ts +20 -0
  140. package/dist/backend/entities/cms-hook.entity.d.ts.map +1 -0
  141. package/dist/backend/entities/cms-hook.entity.js +73 -0
  142. package/dist/backend/entities/cms-hook.entity.js.map +1 -0
  143. package/dist/backend/entities/cms-page.entity.d.ts +43 -0
  144. package/dist/backend/entities/cms-page.entity.d.ts.map +1 -0
  145. package/dist/backend/entities/cms-page.entity.js +140 -0
  146. package/dist/backend/entities/cms-page.entity.js.map +1 -0
  147. package/dist/backend/entities/cms-template.entity.d.ts +20 -0
  148. package/dist/backend/entities/cms-template.entity.d.ts.map +1 -0
  149. package/dist/backend/entities/cms-template.entity.js +73 -0
  150. package/dist/backend/entities/cms-template.entity.js.map +1 -0
  151. package/dist/backend/index.d.ts +157 -0
  152. package/dist/backend/index.d.ts.map +1 -0
  153. package/dist/backend/index.js +317 -0
  154. package/dist/backend/index.js.map +1 -0
  155. package/dist/backend/plugin.d.ts +81 -0
  156. package/dist/backend/plugin.d.ts.map +1 -0
  157. package/dist/backend/plugin.js +81 -0
  158. package/dist/backend/plugin.js.map +1 -0
  159. package/dist/backend/routes.admin.d.ts +19 -0
  160. package/dist/backend/routes.admin.d.ts.map +1 -0
  161. package/dist/backend/routes.admin.js +164 -0
  162. package/dist/backend/routes.admin.js.map +1 -0
  163. package/dist/backend/routes.storefront.d.ts +6 -0
  164. package/dist/backend/routes.storefront.d.ts.map +1 -0
  165. package/dist/backend/routes.storefront.js +89 -0
  166. package/dist/backend/routes.storefront.js.map +1 -0
  167. package/dist/backend/services/asset-embed-resolver.d.ts +17 -0
  168. package/dist/backend/services/asset-embed-resolver.d.ts.map +1 -0
  169. package/dist/backend/services/asset-embed-resolver.js +40 -0
  170. package/dist/backend/services/asset-embed-resolver.js.map +1 -0
  171. package/dist/backend/services/asset-references.d.ts +4 -0
  172. package/dist/backend/services/asset-references.d.ts.map +1 -0
  173. package/dist/backend/services/asset-references.js +55 -0
  174. package/dist/backend/services/asset-references.js.map +1 -0
  175. package/dist/backend/services/cms-block-read-port.d.ts +27 -0
  176. package/dist/backend/services/cms-block-read-port.d.ts.map +1 -0
  177. package/dist/backend/services/cms-block-read-port.js +50 -0
  178. package/dist/backend/services/cms-block-read-port.js.map +1 -0
  179. package/dist/backend/services/cms-block-seed-port.d.ts +31 -0
  180. package/dist/backend/services/cms-block-seed-port.d.ts.map +1 -0
  181. package/dist/backend/services/cms-block-seed-port.js +68 -0
  182. package/dist/backend/services/cms-block-seed-port.js.map +1 -0
  183. package/dist/backend/services/cms-block-service.d.ts +38 -0
  184. package/dist/backend/services/cms-block-service.d.ts.map +1 -0
  185. package/dist/backend/services/cms-block-service.js +218 -0
  186. package/dist/backend/services/cms-block-service.js.map +1 -0
  187. package/dist/backend/services/cms-cache.d.ts +54 -0
  188. package/dist/backend/services/cms-cache.d.ts.map +1 -0
  189. package/dist/backend/services/cms-cache.js +113 -0
  190. package/dist/backend/services/cms-cache.js.map +1 -0
  191. package/dist/backend/services/cms-hook-service.d.ts +35 -0
  192. package/dist/backend/services/cms-hook-service.d.ts.map +1 -0
  193. package/dist/backend/services/cms-hook-service.js +216 -0
  194. package/dist/backend/services/cms-hook-service.js.map +1 -0
  195. package/dist/backend/services/cms-language-reference.d.ts +11 -0
  196. package/dist/backend/services/cms-language-reference.d.ts.map +1 -0
  197. package/dist/backend/services/cms-language-reference.js +26 -0
  198. package/dist/backend/services/cms-language-reference.js.map +1 -0
  199. package/dist/backend/services/cms-page-read-port.d.ts +23 -0
  200. package/dist/backend/services/cms-page-read-port.d.ts.map +1 -0
  201. package/dist/backend/services/cms-page-read-port.js +54 -0
  202. package/dist/backend/services/cms-page-read-port.js.map +1 -0
  203. package/dist/backend/services/cms-page-service.d.ts +104 -0
  204. package/dist/backend/services/cms-page-service.d.ts.map +1 -0
  205. package/dist/backend/services/cms-page-service.js +468 -0
  206. package/dist/backend/services/cms-page-service.js.map +1 -0
  207. package/dist/backend/services/cms-reference-registry.d.ts +30 -0
  208. package/dist/backend/services/cms-reference-registry.d.ts.map +1 -0
  209. package/dist/backend/services/cms-reference-registry.js +98 -0
  210. package/dist/backend/services/cms-reference-registry.js.map +1 -0
  211. package/dist/backend/services/cms-template-service.d.ts +37 -0
  212. package/dist/backend/services/cms-template-service.d.ts.map +1 -0
  213. package/dist/backend/services/cms-template-service.js +205 -0
  214. package/dist/backend/services/cms-template-service.js.map +1 -0
  215. package/dist/backend/services/content-tree-walker.d.ts +7 -0
  216. package/dist/backend/services/content-tree-walker.d.ts.map +1 -0
  217. package/dist/backend/services/content-tree-walker.js +89 -0
  218. package/dist/backend/services/content-tree-walker.js.map +1 -0
  219. package/dist/backend/services/page-builder-registry.d.ts +120 -0
  220. package/dist/backend/services/page-builder-registry.d.ts.map +1 -0
  221. package/dist/backend/services/page-builder-registry.js +220 -0
  222. package/dist/backend/services/page-builder-registry.js.map +1 -0
  223. package/dist/backend/services/seed-hooks.d.ts +22 -0
  224. package/dist/backend/services/seed-hooks.d.ts.map +1 -0
  225. package/dist/backend/services/seed-hooks.js +94 -0
  226. package/dist/backend/services/seed-hooks.js.map +1 -0
  227. package/dist/backend/services/storefront-resolver.d.ts +71 -0
  228. package/dist/backend/services/storefront-resolver.d.ts.map +1 -0
  229. package/dist/backend/services/storefront-resolver.js +329 -0
  230. package/dist/backend/services/storefront-resolver.js.map +1 -0
  231. package/dist/manifest.d.ts +225 -0
  232. package/dist/manifest.d.ts.map +1 -0
  233. package/dist/manifest.js +695 -0
  234. package/dist/manifest.js.map +1 -0
  235. package/dist/migrations/20260425T162418_cms_pages_init.d.ts +10 -0
  236. package/dist/migrations/20260425T162418_cms_pages_init.d.ts.map +1 -0
  237. package/dist/migrations/20260425T162418_cms_pages_init.js +30 -0
  238. package/dist/migrations/20260425T162418_cms_pages_init.js.map +1 -0
  239. package/dist/migrations/20260505T130214_cms_init.d.ts +28 -0
  240. package/dist/migrations/20260505T130214_cms_init.d.ts.map +1 -0
  241. package/dist/migrations/20260505T130214_cms_init.js +332 -0
  242. package/dist/migrations/20260505T130214_cms_init.js.map +1 -0
  243. package/dist/migrations/20260903T101741_cms_namespace_block_names.d.ts +6 -0
  244. package/dist/migrations/20260903T101741_cms_namespace_block_names.d.ts.map +1 -0
  245. package/dist/migrations/20260903T101741_cms_namespace_block_names.js +60 -0
  246. package/dist/migrations/20260903T101741_cms_namespace_block_names.js.map +1 -0
  247. package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.d.ts +27 -0
  248. package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.d.ts.map +1 -0
  249. package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.js +44 -0
  250. package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.js.map +1 -0
  251. package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.d.ts +32 -0
  252. package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.d.ts.map +1 -0
  253. package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.js +36 -0
  254. package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.js.map +1 -0
  255. package/dist/migrations/index.d.ts +31 -0
  256. package/dist/migrations/index.d.ts.map +1 -0
  257. package/dist/migrations/index.js +37 -0
  258. package/dist/migrations/index.js.map +1 -0
  259. package/docs/cms/extending-page-builder.md +256 -0
  260. package/docs/cms/index.md +233 -0
  261. package/i18n/en.json +231 -0
  262. package/i18n/pl.json +231 -0
  263. package/package.json +125 -3
  264. package/tailwind.css +16 -0
@@ -0,0 +1,216 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ export class CmsHookService {
5
+ emFactory;
6
+ cache;
7
+ constructor(emFactory, cache) {
8
+ this.emFactory = emFactory;
9
+ this.cache = cache;
10
+ }
11
+ async invalidateForHookId(hookId) {
12
+ if (!this.cache)
13
+ return;
14
+ const rows = (await this.emFactory().execute('select code from cms_hooks where id = ?', [hookId]));
15
+ const codes = rows.map((r) => r.code).filter((c) => c && c.length > 0);
16
+ if (codes.length > 0)
17
+ await this.cache.invalidateHooksByCode(codes);
18
+ }
19
+ async list(filters = {}) {
20
+ const params = [];
21
+ const where = [];
22
+ if (filters.salesChannelId) {
23
+ params.push(filters.salesChannelId);
24
+ where.push(`exists (
25
+ select 1 from cms_hook_sales_channels chsc
26
+ where chsc.hook_id = h.id and chsc.sales_channel_id = ?
27
+ )`);
28
+ }
29
+ const rows = (await this.emFactory().execute(`select h.*,
30
+ (select count(*) from cms_hook_block_attachments a where a.hook_id = h.id) as attachment_count
31
+ from cms_hooks h
32
+ ${where.length > 0 ? `where ${where.join(' and ')}` : ''}
33
+ order by h.code asc
34
+ limit 200`, params));
35
+ return { data: await Promise.all(rows.map((row) => this.toSummary(row))), nextCursor: null };
36
+ }
37
+ async create(input) {
38
+ const em = this.emFactory();
39
+ const id = randomUUID();
40
+ const now = new Date();
41
+ await em.transactional(async (tx) => {
42
+ await this.assertCodeAvailable(tx, input.code);
43
+ await tx.execute(`insert into cms_hooks
44
+ (id, name, code, active, description, is_system, version, created_at, updated_at)
45
+ values (?, ?, ?, ?, ?, false, 1, ?, ?)`, [id, input.name, input.code, input.active ?? true, input.description ?? null, now, now]);
46
+ await this.replaceChannelScope(tx, id, input.salesChannelIds);
47
+ });
48
+ if (this.cache)
49
+ await this.cache.invalidateHooksByCode([input.code]);
50
+ return this.get(id);
51
+ }
52
+ async get(id) {
53
+ const row = await this.findRow(id);
54
+ if (!row)
55
+ throw new HttpError(404, ERROR_CODES.CMS_HOOK_NOT_FOUND, 'CMS Hook not found.');
56
+ return this.toDetail(row);
57
+ }
58
+ async patch(id, input) {
59
+ const em = this.emFactory();
60
+ await em.transactional(async (tx) => {
61
+ const row = await this.findRow(id, tx);
62
+ if (!row)
63
+ throw new HttpError(404, ERROR_CODES.CMS_HOOK_NOT_FOUND, 'CMS Hook not found.');
64
+ this.assertVersion(row, input.version);
65
+ const sets = [];
66
+ const params = [];
67
+ const set = (sql, value) => {
68
+ sets.push(sql);
69
+ params.push(value);
70
+ };
71
+ if (input.name !== undefined)
72
+ set('name = ?', input.name);
73
+ if (input.active !== undefined)
74
+ set('active = ?', input.active);
75
+ if (input.description !== undefined)
76
+ set('description = ?', input.description);
77
+ if (sets.length > 0) {
78
+ params.push(id);
79
+ await tx.execute(`update cms_hooks
80
+ set ${sets.join(', ')}, version = version + 1, updated_at = now()
81
+ where id = ?`, params);
82
+ }
83
+ if (input.salesChannelIds) {
84
+ await this.replaceChannelScope(tx, id, input.salesChannelIds);
85
+ await tx.execute(`update cms_hooks set version = version + 1, updated_at = now() where id = ?`, [id]);
86
+ }
87
+ });
88
+ await this.invalidateForHookId(id);
89
+ return this.get(id);
90
+ }
91
+ async delete(id) {
92
+ const row = await this.findRow(id);
93
+ if (!row)
94
+ throw new HttpError(404, ERROR_CODES.CMS_HOOK_NOT_FOUND, 'CMS Hook not found.');
95
+ if (row.is_system) {
96
+ throw new HttpError(409, ERROR_CODES.CMS_HOOK_SYSTEM_PROTECTED, 'System CMS Hooks cannot be deleted.');
97
+ }
98
+ await this.emFactory().execute('delete from cms_hooks where id = ?', [id]);
99
+ if (this.cache)
100
+ await this.cache.invalidateHooksByCode([row.code]);
101
+ }
102
+ async listAttachments(hookId) {
103
+ await this.assertExists(hookId);
104
+ return this.attachmentsFor(hookId);
105
+ }
106
+ async addAttachment(hookId, blockId, position) {
107
+ const em = this.emFactory();
108
+ await em.transactional(async (tx) => {
109
+ await this.assertExists(hookId, tx);
110
+ await this.assertBlockExists(blockId, tx);
111
+ await tx.execute(`insert into cms_hook_block_attachments (hook_id, block_id, position, created_at)
112
+ values (?, ?, ?, now())`, [hookId, blockId, position]);
113
+ });
114
+ await this.invalidateForHookId(hookId);
115
+ return this.attachmentsFor(hookId);
116
+ }
117
+ async reorderAttachment(hookId, blockId, position) {
118
+ const em = this.emFactory();
119
+ await em.transactional(async (tx) => {
120
+ await this.assertExists(hookId, tx);
121
+ const result = (await tx.execute(`update cms_hook_block_attachments
122
+ set position = ?
123
+ where hook_id = ? and block_id = ?`, [position, hookId, blockId]));
124
+ if (result.rowCount === 0) {
125
+ throw new HttpError(404, ERROR_CODES.CMS_BLOCK_NOT_FOUND, 'CMS Hook attachment not found.');
126
+ }
127
+ });
128
+ await this.invalidateForHookId(hookId);
129
+ return this.attachmentsFor(hookId);
130
+ }
131
+ async removeAttachment(hookId, blockId) {
132
+ await this.assertExists(hookId);
133
+ await this.emFactory().execute('delete from cms_hook_block_attachments where hook_id = ? and block_id = ?', [hookId, blockId]);
134
+ await this.invalidateForHookId(hookId);
135
+ }
136
+ async findRow(id, em = this.emFactory()) {
137
+ const rows = (await em.execute(`select h.*,
138
+ (select count(*) from cms_hook_block_attachments a where a.hook_id = h.id) as attachment_count
139
+ from cms_hooks h
140
+ where h.id = ?
141
+ limit 1`, [id]));
142
+ return rows[0] ?? null;
143
+ }
144
+ async assertExists(hookId, em = this.emFactory()) {
145
+ const row = await this.findRow(hookId, em);
146
+ if (!row)
147
+ throw new HttpError(404, ERROR_CODES.CMS_HOOK_NOT_FOUND, 'CMS Hook not found.');
148
+ }
149
+ async assertBlockExists(blockId, em) {
150
+ const rows = (await em.execute('select id from cms_blocks where id = ? limit 1', [blockId]));
151
+ if (rows.length === 0) {
152
+ throw new HttpError(404, ERROR_CODES.CMS_BLOCK_NOT_FOUND, 'CMS Block not found.');
153
+ }
154
+ }
155
+ async assertCodeAvailable(em, code) {
156
+ const rows = (await em.execute('select id from cms_hooks where code = ? limit 1', [code]));
157
+ if (rows.length > 0) {
158
+ throw new HttpError(409, ERROR_CODES.CMS_CODE_CONFLICT, 'CMS Hook code already exists.');
159
+ }
160
+ }
161
+ async replaceChannelScope(em, hookId, salesChannelIds) {
162
+ await em.execute('delete from cms_hook_sales_channels where hook_id = ?', [
163
+ hookId,
164
+ ]);
165
+ for (const salesChannelId of salesChannelIds) {
166
+ await em.execute(`insert into cms_hook_sales_channels (hook_id, sales_channel_id)
167
+ values (?, ?)`, [hookId, salesChannelId]);
168
+ }
169
+ }
170
+ async channelIdsFor(hookId, em = this.emFactory()) {
171
+ const rows = (await em.execute('select sales_channel_id::text as id from cms_hook_sales_channels where hook_id = ?', [hookId]));
172
+ return rows.map((row) => row.id);
173
+ }
174
+ async attachmentsFor(hookId) {
175
+ const rows = (await this.emFactory().execute(`select a.block_id::text, b.code as block_code, a.position
176
+ from cms_hook_block_attachments a
177
+ join cms_blocks b on b.id = a.block_id
178
+ where a.hook_id = ?
179
+ order by a.position asc, a.block_id asc`, [hookId]));
180
+ return rows.map((row) => ({
181
+ blockId: row.block_id,
182
+ blockCode: row.block_code,
183
+ position: row.position,
184
+ }));
185
+ }
186
+ assertVersion(row, version) {
187
+ if (version !== undefined && version !== row.version) {
188
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'CMS Hook was updated concurrently.');
189
+ }
190
+ }
191
+ async toSummary(row) {
192
+ return {
193
+ id: row.id,
194
+ name: row.name,
195
+ code: row.code,
196
+ active: row.active,
197
+ description: row.description,
198
+ isSystem: row.is_system,
199
+ salesChannelIds: await this.channelIdsFor(row.id),
200
+ attachmentCount: Number(row.attachment_count ?? 0),
201
+ version: row.version,
202
+ createdAt: this.iso(row.created_at),
203
+ updatedAt: this.iso(row.updated_at),
204
+ };
205
+ }
206
+ async toDetail(row) {
207
+ return {
208
+ ...(await this.toSummary(row)),
209
+ attachments: await this.attachmentsFor(row.id),
210
+ };
211
+ }
212
+ iso(value) {
213
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
214
+ }
215
+ }
216
+ //# sourceMappingURL=cms-hook-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-hook-service.js","sourceRoot":"","sources":["../../../src/backend/services/cms-hook-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { DictionaryReferenceRegistryPort } from '@endora-commerce/contracts';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ /**
4
+ * Who in `cms` still points at a language (feature 077, D-87).
5
+ *
6
+ * `cms_pages.languages` is a JSON array of codes, which is why `languages`
7
+ * used to reach for `@> ?::jsonb` against this module's table directly. The
8
+ * containment test lives here now, next to the column it is about.
9
+ */
10
+ export declare function registerCmsLanguageReferences(registry: DictionaryReferenceRegistryPort, emFactory: () => EntityManager): void;
11
+ //# sourceMappingURL=cms-language-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-language-reference.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cms-language-reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,+BAA+B,EACzC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAuBN"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Who in `cms` still points at a language (feature 077, D-87).
3
+ *
4
+ * `cms_pages.languages` is a JSON array of codes, which is why `languages`
5
+ * used to reach for `@> ?::jsonb` against this module's table directly. The
6
+ * containment test lives here now, next to the column it is about.
7
+ */
8
+ export function registerCmsLanguageReferences(registry, emFactory) {
9
+ registry.register({
10
+ ownerModuleId: 'cms',
11
+ consumer: 'cms',
12
+ tableName: 'cms_pages',
13
+ columnName: 'languages[]',
14
+ blocking: true,
15
+ countReferences: async (code) => {
16
+ const rows = (await emFactory().execute(`select count(*)::int as n from "cms_pages" where "languages" @> ?::jsonb`, [JSON.stringify([code])]));
17
+ return rows[0]?.n ?? 0;
18
+ },
19
+ usedCodes: async () => (await emFactory().execute(`select value.code as code, count(*)::int as count
20
+ from "cms_pages" t
21
+ cross join lateral jsonb_array_elements_text(
22
+ coalesce(t."languages", '[]'::jsonb)) as value(code)
23
+ group by value.code`)),
24
+ });
25
+ }
26
+ //# sourceMappingURL=cms-language-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-language-reference.js","sourceRoot":"","sources":["../../../src/backend/services/cms-language-reference.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B,CAC3C,QAAyC,EACzC,SAA8B;IAE9B,QAAQ,CAAC,QAAQ,CAAC;QAChB,aAAa,EAAE,KAAK;QACpB,QAAQ,EAAE,KAAK;QACf,SAAS,EAAE,WAAW;QACtB,UAAU,EAAE,aAAa;QACzB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACrC,0EAA0E,EAC1E,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACzB,CAAyB,CAAC;YAC3B,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QACD,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACxB;;;;8BAIsB,CACvB,CAA2C;KAC/C,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CmsPageReadPort, CmsPageRecord } from '@endora-commerce/contracts';
3
+ import { CmsPage } from '../entities/cms-page.entity.js';
4
+ /**
5
+ * The page read model `cms` publishes (feature 075, Phase P).
6
+ *
7
+ * `seo` is the only consumer, and it reads pages twice: to resolve one page's
8
+ * meta tags, and to enumerate the published ones for the sitemap.
9
+ *
10
+ * The `body` and `content` columns do not survive the mapping. A sitemap and a
11
+ * meta-tag resolver have no use for a page's rendered tree, and shipping it
12
+ * would make every sitemap build carry the whole CMS through memory.
13
+ */
14
+ export declare class CmsPageReadService implements CmsPageReadPort {
15
+ private readonly emFactory;
16
+ constructor(emFactory: () => EntityManager);
17
+ findById(id: string): Promise<CmsPageRecord | null>;
18
+ findByPath(path: string): Promise<CmsPageRecord | null>;
19
+ findByIds(ids: readonly string[]): Promise<CmsPageRecord[]>;
20
+ listPublished(): Promise<CmsPageRecord[]>;
21
+ }
22
+ export declare function toCmsPageRecord(page: CmsPage): CmsPageRecord;
23
+ //# sourceMappingURL=cms-page-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-page-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cms-page-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAEzD;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKnD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKvD,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAM3D,aAAa,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;CAQhD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,CAiB5D"}
@@ -0,0 +1,54 @@
1
+ import { CmsPage } from '../entities/cms-page.entity.js';
2
+ /**
3
+ * The page read model `cms` publishes (feature 075, Phase P).
4
+ *
5
+ * `seo` is the only consumer, and it reads pages twice: to resolve one page's
6
+ * meta tags, and to enumerate the published ones for the sitemap.
7
+ *
8
+ * The `body` and `content` columns do not survive the mapping. A sitemap and a
9
+ * meta-tag resolver have no use for a page's rendered tree, and shipping it
10
+ * would make every sitemap build carry the whole CMS through memory.
11
+ */
12
+ export class CmsPageReadService {
13
+ emFactory;
14
+ constructor(emFactory) {
15
+ this.emFactory = emFactory;
16
+ }
17
+ async findById(id) {
18
+ const page = await this.emFactory().findOne(CmsPage, { id });
19
+ return page ? toCmsPageRecord(page) : null;
20
+ }
21
+ async findByPath(path) {
22
+ const page = await this.emFactory().findOne(CmsPage, { path });
23
+ return page ? toCmsPageRecord(page) : null;
24
+ }
25
+ async findByIds(ids) {
26
+ if (ids.length === 0)
27
+ return [];
28
+ const pages = await this.emFactory().find(CmsPage, { id: { $in: [...ids] } });
29
+ return pages.map(toCmsPageRecord);
30
+ }
31
+ async listPublished() {
32
+ const pages = await this.emFactory().find(CmsPage, { status: 'published' }, { orderBy: { path: 'asc' } });
33
+ return pages.map(toCmsPageRecord);
34
+ }
35
+ }
36
+ export function toCmsPageRecord(page) {
37
+ return {
38
+ id: page.id,
39
+ path: page.path,
40
+ slug: page.slug,
41
+ status: page.status,
42
+ name: page.name,
43
+ active: page.active,
44
+ title: page.title,
45
+ metaTitle: page.metaTitle ?? null,
46
+ metaDescription: page.metaDescription ?? null,
47
+ metaKeywords: page.metaKeywords ?? null,
48
+ publishedAt: page.publishedAt ?? null,
49
+ archivedAt: page.archivedAt ?? null,
50
+ createdAt: page.createdAt,
51
+ updatedAt: page.updatedAt,
52
+ };
53
+ }
54
+ //# sourceMappingURL=cms-page-read-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-page-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/cms-page-read-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAEzD;;;;;;;;;GASG;AACH,MAAM,OAAO,kBAAkB;IACA;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAsB;QACpC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9E,OAAO,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACvC,OAAO,EACP,EAAE,MAAM,EAAE,WAAW,EAAE,EACvB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAC7B,CAAC;QACF,OAAO,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACpC,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QACjC,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,IAAI;QAC7C,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI;QACvC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;QACrC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QACnC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,104 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CmsPageDetail, type CmsPageSummary, type CreateCmsPageRequest, type PatchCmsPageRequest } from '@endora-commerce/contracts';
3
+ import type { CmsCache } from './cms-cache.js';
4
+ import type { CmsReferenceRegistry } from './cms-reference-registry.js';
5
+ /**
6
+ * Answers the deployment's reserved first path segments
7
+ * (`specs/105-cms-root-page-urls/` FR-032).
8
+ *
9
+ * A function rather than a value because the answer is a Setting an operator
10
+ * may edit while the platform runs, and because reading it is the *module's*
11
+ * job: the resolver is installed in `backend/index.ts`, which is where every
12
+ * other settings-backed value of this module is wired, so no composition root
13
+ * owns a knob on this module's behalf (composition checklist item 6).
14
+ *
15
+ * It answers `unknown` rather than `string[]`, and that is the design: the value
16
+ * is free-form JSON an operator edits, so judging an entry belongs in
17
+ * `normalizeReservedSegments` below — one entry at a time, dropping what it
18
+ * cannot use — and not in a schema whose failure would take every page save down
19
+ * because somebody typed a number into a list.
20
+ */
21
+ export type ReservedSlugSegmentsResolver = () => Promise<unknown>;
22
+ /**
23
+ * The reserved set as the refusal reads it, from the value an operator typed.
24
+ *
25
+ * The Setting is free-form JSON, so this is forgiving in the three ways an
26
+ * operator's list is likely to be wrong and in no other: `"/cart"` and
27
+ * `"Cart"` and `"checkout/pay"` all mean the segment `cart`, `cart` and
28
+ * `checkout`. A non-string entry is dropped rather than stringified — an
29
+ * operator who typed `["cart", 7]` reserved one segment, not two, and
30
+ * `"7"` reserving a path is a rule nobody wrote.
31
+ */
32
+ export declare function normalizeReservedSegments(value: unknown): string[];
33
+ export declare class CmsPageService {
34
+ private readonly emFactory;
35
+ private readonly knownComponentNames;
36
+ private readonly cache?;
37
+ private readonly references?;
38
+ /**
39
+ * Absent until `registerModule` installs it, and absent means "nothing is
40
+ * reserved". That is the manifest's own default (`defaultValue: []`), so an
41
+ * unwired service and a deployment that reserves nothing answer alike — and
42
+ * a *failing* read is not this state: it propagates, see `reservedSegments`.
43
+ */
44
+ private reservedSegmentsResolver;
45
+ constructor(emFactory: () => EntityManager, knownComponentNames: () => Iterable<string>, cache?: CmsCache | undefined, references?: CmsReferenceRegistry | undefined);
46
+ setReservedSegmentsResolver(resolver: ReservedSlugSegmentsResolver | null): void;
47
+ /**
48
+ * The deployment's reserved first path segments — the **one** value the
49
+ * save-time refusal below and the editor's inline warning both read
50
+ * (FR-033). Two lists would be two answers waiting to disagree, and a warning
51
+ * that disagrees with a refusal is worse than no warning.
52
+ */
53
+ reservedSegments(): Promise<string[]>;
54
+ /**
55
+ * FR-031 — refuse a slug whose first segment the deployment reserves.
56
+ *
57
+ * Nothing here creates precedence and nothing may (`contracts/cms-page-url.md`
58
+ * §5.0): the storefront's route already wins, measured, with no mechanism.
59
+ * What this removes is the **silence** — a page that saves, publishes and is
60
+ * never served while the operator is told nothing, which is a defect
61
+ * indistinguishable from a legitimate state because the URL answers 200 with
62
+ * somebody else's content.
63
+ *
64
+ * The offending segment travels in `details` so the envelope can fill the
65
+ * `{segment}` placeholder in the operator's own language (§5.4): the code for
66
+ * a consumer to branch on, the sentence for the operator to read.
67
+ */
68
+ private assertSlugNotReserved;
69
+ private invalidateForPageId;
70
+ private invalidateForSlugs;
71
+ list(filters?: {
72
+ salesChannelId?: string;
73
+ status?: string;
74
+ q?: string;
75
+ }): Promise<{
76
+ data: CmsPageSummary[];
77
+ nextCursor: null;
78
+ }>;
79
+ create(input: CreateCmsPageRequest): Promise<CmsPageDetail>;
80
+ get(id: string): Promise<CmsPageDetail>;
81
+ patch(id: string, input: PatchCmsPageRequest): Promise<CmsPageDetail>;
82
+ setContent(id: string, language: string, data: unknown, version: number): Promise<CmsPageDetail>;
83
+ publish(id: string): Promise<CmsPageDetail>;
84
+ archive(id: string): Promise<CmsPageDetail>;
85
+ unarchive(id: string): Promise<CmsPageDetail>;
86
+ delete(id: string): Promise<void>;
87
+ private transition;
88
+ private findRow;
89
+ private assertSlugAvailable;
90
+ private channelLanguageCodes;
91
+ private resolvedChannelLanguageCodes;
92
+ private assertLanguagesInChannelScope;
93
+ private replaceChannelScope;
94
+ private assertBlockEmbedsExist;
95
+ private channelIdsFor;
96
+ private assertVersion;
97
+ private splitMeta;
98
+ private toSummary;
99
+ private toDetail;
100
+ private combineMeta;
101
+ private iso;
102
+ private legacyPath;
103
+ }
104
+ //# sourceMappingURL=cms-page-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-page-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cms-page-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAqBxE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,4BAA4B,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;AAElE;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CASlE;AAED,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAZ9B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB,CAA6C;gBAG1D,SAAS,EAAE,MAAM,aAAa,EAC9B,mBAAmB,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,EAC3C,KAAK,CAAC,EAAE,QAAQ,YAAA,EAChB,UAAU,CAAC,EAAE,oBAAoB,YAAA;IAGpD,2BAA2B,CAAC,QAAQ,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IAIhF;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAK3C;;;;;;;;;;;;;OAaG;YACW,qBAAqB;YAarB,mBAAmB;YAYnB,kBAAkB;IAM1B,IAAI,CAAC,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAC1F,IAAI,EAAE,cAAc,EAAE,CAAC;QACvB,UAAU,EAAE,IAAI,CAAC;KAClB,CAAC;IAoCI,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAqD3D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAMvC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IA6DrE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAyChG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAI3C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAI3C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAI7C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAWzB,UAAU;YAsBV,OAAO;YAOP,mBAAmB;IAoBjC,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,4BAA4B;YAStB,6BAA6B;YA+B7B,mBAAmB;YAkBnB,sBAAsB;YAwBtB,aAAa;IAQ3B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,SAAS;YA4BH,SAAS;YAgBT,QAAQ;IAUtB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,UAAU;CAInB"}