studiocms 0.2.0 → 0.4.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 (293) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/cli/add/index.d.ts +2 -2
  3. package/dist/cli/add/index.js +4 -3
  4. package/dist/cli/add/npm-utils.d.ts +6 -6
  5. package/dist/cli/add/tryToInstallPlugins.d.ts +1 -1
  6. package/dist/cli/add/tryToInstallPlugins.js +6 -5
  7. package/dist/cli/add/updateStudioCMSConfig.d.ts +1 -1
  8. package/dist/cli/add/updateStudioCMSConfig.js +3 -4
  9. package/dist/cli/add/validatePlugins.d.ts +1 -2
  10. package/dist/cli/add/validatePlugins.js +15 -9
  11. package/dist/cli/crypto/genJWT/index.d.ts +1 -1
  12. package/dist/cli/crypto/genJWT/index.js +8 -9
  13. package/dist/cli/crypto/index.d.ts +1 -1
  14. package/dist/cli/init/steps/env.js +14 -4
  15. package/dist/cli/init/steps/next.d.ts +1 -1
  16. package/dist/cli/init/steps/next.js +6 -5
  17. package/dist/cli/migrator/index.d.ts +1 -1
  18. package/dist/cli/migrator/index.js +2 -2
  19. package/dist/cli/users/index.d.ts +1 -1
  20. package/dist/cli/users/shared.js +2 -2
  21. package/dist/cli/users/steps/createUsers.js +7 -7
  22. package/dist/cli/users/steps/modifyUsers.js +2 -2
  23. package/dist/cli/users/steps/next.d.ts +1 -1
  24. package/dist/cli/utils/checkRequiredEnvVars.js +2 -2
  25. package/dist/cli/utils/context.d.ts +2 -4
  26. package/dist/cli/utils/context.js +1 -3
  27. package/dist/cli/utils/getCliDbClient.d.ts +1 -1
  28. package/dist/cli/utils/intro.d.ts +1 -1
  29. package/dist/cli/utils/loadConfig.d.ts +54 -49
  30. package/dist/cli/utils/loadConfig.js +5 -8
  31. package/dist/cli/utils/logger.js +3 -3
  32. package/dist/cli/utils/user-utils.d.ts +1 -1
  33. package/dist/cli/utils/user-utils.js +4 -3
  34. package/dist/client/apiClient.d.ts +4923 -0
  35. package/dist/client/apiClient.js +72 -0
  36. package/dist/config.d.ts +1734 -1
  37. package/dist/consts.d.ts +5 -5
  38. package/dist/consts.js +3 -2
  39. package/dist/db/plugins.d.ts +1 -1
  40. package/dist/db/plugins.js +5 -8
  41. package/dist/handlers/frontend/routes.d.ts +4 -18
  42. package/dist/handlers/frontend/routes.js +13 -152
  43. package/dist/handlers/frontend/types.d.ts +1 -1
  44. package/dist/handlers/frontend/utils.js +0 -18
  45. package/dist/handlers/pluginHandler.d.ts +34 -257
  46. package/dist/handlers/pluginHandler.js +92 -46
  47. package/dist/handlers/routeHandler.js +32 -11
  48. package/dist/handlers/setupDbStudio.d.ts +3 -1
  49. package/dist/handlers/setupDbStudio.js +19 -10
  50. package/dist/handlers/storage-manager/core/effectify-astro-context.d.ts +25 -0
  51. package/dist/handlers/storage-manager/core/effectify-astro-context.js +78 -0
  52. package/dist/handlers/storage-manager/no-op.d.ts +2 -2
  53. package/dist/handlers/storage-manager/no-op.js +2 -3
  54. package/dist/index.d.ts +0 -1
  55. package/dist/index.js +10 -20
  56. package/dist/integrations/robots/index.d.ts +2 -2
  57. package/dist/integrations/robots/index.js +1 -3
  58. package/dist/integrations/robots/schema.d.ts +102 -273
  59. package/dist/integrations/robots/schema.js +220 -209
  60. package/dist/plugins/analytics/assets/schemas.d.ts +14 -9
  61. package/dist/plugins/analytics/assets/schemas.js +25 -17
  62. package/dist/plugins/analytics/db-client.d.ts +1 -1
  63. package/dist/plugins/analytics/index.d.ts +823 -3
  64. package/dist/plugins/analytics/index.js +4 -5
  65. package/dist/plugins/analytics/schemas.d.ts +54 -62
  66. package/dist/plugins/analytics/schemas.js +64 -13
  67. package/dist/plugins/analytics/table.d.ts +1 -1
  68. package/dist/plugins.d.ts +0 -1
  69. package/dist/schemas/config/api.d.ts +17 -0
  70. package/dist/schemas/config/api.js +14 -0
  71. package/dist/schemas/config/auth.d.ts +55 -59
  72. package/dist/schemas/config/auth.js +34 -11
  73. package/dist/schemas/config/dashboard.d.ts +43 -79
  74. package/dist/schemas/config/dashboard.js +43 -12
  75. package/dist/schemas/config/db.d.ts +15 -17
  76. package/dist/schemas/config/db.js +13 -5
  77. package/dist/schemas/config/developer.d.ts +33 -45
  78. package/dist/schemas/config/developer.js +22 -5
  79. package/dist/schemas/config/index.d.ts +398 -521
  80. package/dist/schemas/config/index.js +115 -57
  81. package/dist/schemas/config/sdk.d.ts +50 -196
  82. package/dist/schemas/config/sdk.js +61 -73
  83. package/dist/schemas/custom.d.ts +40 -0
  84. package/dist/schemas/custom.js +41 -0
  85. package/dist/schemas/external-schemas.d.ts +171 -0
  86. package/dist/schemas/external-schemas.js +179 -0
  87. package/dist/schemas/index.d.ts +2 -0
  88. package/dist/schemas/index.js +2 -0
  89. package/dist/schemas/plugins/i18n.d.ts +59 -39
  90. package/dist/schemas/plugins/i18n.js +42 -5
  91. package/dist/schemas/plugins/index.d.ts +7126 -10296
  92. package/dist/schemas/plugins/index.js +260 -276
  93. package/dist/schemas/plugins/shared.d.ts +1293 -3718
  94. package/dist/schemas/plugins/shared.js +320 -329
  95. package/dist/test-utils.d.ts +15 -4
  96. package/dist/test-utils.js +27 -11
  97. package/dist/toolbar/db-viewer/db-shared-types.d.ts +6 -6
  98. package/dist/toolbar/db-viewer/studio/connection.d.ts +8 -4
  99. package/dist/toolbar/db-viewer/studio/connection.js +2 -28
  100. package/dist/toolbar/db-viewer/studio/env/libsql.d.ts +7 -0
  101. package/dist/toolbar/db-viewer/studio/env/libsql.js +17 -0
  102. package/dist/toolbar/db-viewer/studio/env/mysql.d.ts +7 -0
  103. package/dist/toolbar/db-viewer/studio/env/mysql.js +23 -0
  104. package/dist/toolbar/db-viewer/studio/env/postgres.d.ts +7 -0
  105. package/dist/toolbar/db-viewer/studio/env/postgres.js +23 -0
  106. package/dist/toolbar/db-viewer/studio/index.js +20 -56
  107. package/dist/toolbar/db-viewer/studio/type.d.ts +1 -2
  108. package/dist/toolbar/db-viewer/studio/virtual-connection/libsql.d.ts +3 -0
  109. package/dist/toolbar/db-viewer/studio/virtual-connection/libsql.js +24 -0
  110. package/dist/toolbar/db-viewer/studio/virtual-connection/mysql.d.ts +3 -0
  111. package/dist/toolbar/db-viewer/studio/virtual-connection/mysql.js +9 -0
  112. package/dist/toolbar/db-viewer/studio/virtual-connection/postgres.d.ts +3 -0
  113. package/dist/toolbar/db-viewer/studio/virtual-connection/postgres.js +9 -0
  114. package/dist/toolbar/db-viewer/viewer.js +20 -21
  115. package/dist/types.d.ts +30 -0
  116. package/dist/utils/effects/smtp.d.ts +1 -1
  117. package/dist/utils/lang-helper.d.ts +10 -2
  118. package/dist/virtual.d.ts +35 -28
  119. package/dist/virtuals/auth/core.d.ts +5 -5
  120. package/dist/virtuals/auth/verify-email.d.ts +6 -6
  121. package/dist/virtuals/components/Generator.astro +2 -2
  122. package/dist/virtuals/components/Renderer.astro +9 -1
  123. package/dist/virtuals/components/renderFn.d.ts +3 -1
  124. package/dist/virtuals/components/renderFn.js +18 -0
  125. package/dist/virtuals/lib/headDefaults.d.ts +4 -2
  126. package/dist/virtuals/lib/headDefaults.js +0 -2
  127. package/dist/virtuals/lib/routeMap.d.ts +0 -12
  128. package/dist/virtuals/lib/routeMap.js +2 -14
  129. package/dist/virtuals/mailer/index.d.ts +3 -3
  130. package/dist/virtuals/notifier/index.d.ts +5 -5
  131. package/dist/virtuals/plugins/dashboard-pages.d.ts +2 -64
  132. package/dist/virtuals/scripts/StorageFileBrowser.d.ts +1 -172
  133. package/dist/virtuals/scripts/StorageFileBrowser.js +216 -119
  134. package/dist/virtuals/template-engine/index.d.ts +4 -4
  135. package/frontend/components/dashboard/configuration/ConfigForm.astro +218 -110
  136. package/frontend/components/dashboard/content-mgmt/ContentSearch.astro +21 -22
  137. package/frontend/components/dashboard/content-mgmt/CreateFolder.astro +66 -54
  138. package/frontend/components/dashboard/content-mgmt/CreatePage.astro +58 -104
  139. package/frontend/components/dashboard/content-mgmt/EditFolder.astro +65 -67
  140. package/frontend/components/dashboard/content-mgmt/EditPage.astro +86 -134
  141. package/frontend/components/dashboard/content-mgmt/InnerSidebarElement.astro +0 -1
  142. package/frontend/components/dashboard/content-mgmt/PageHeader.astro +33 -52
  143. package/frontend/components/dashboard/content-mgmt/PageTypeHandler.astro +2 -2
  144. package/frontend/components/dashboard/profile/APITokens.astro +219 -158
  145. package/frontend/components/dashboard/profile/BasicInfo.astro +165 -106
  146. package/frontend/components/dashboard/profile/Notifications.astro +27 -18
  147. package/frontend/components/dashboard/profile/UpdatePassword.astro +134 -94
  148. package/frontend/components/dashboard/sidebar/VersionCheck.astro +31 -16
  149. package/frontend/components/dashboard/sidebar/VersionCheckChangelog.astro +18 -11
  150. package/frontend/components/dashboard/sidebar-modals/VersionModal.astro +2 -2
  151. package/frontend/components/dashboard/smtp-config/TemplateEditor.astro +14 -14
  152. package/frontend/components/dashboard/taxonomy/InnerSidebarElement.astro +0 -1
  153. package/frontend/components/dashboard/taxonomy/MetaContainer.astro +0 -2
  154. package/frontend/components/dashboard/taxonomy/PageHeader.astro +16 -24
  155. package/frontend/components/dashboard/taxonomy/TaxonomySearch.astro +23 -27
  156. package/frontend/components/dashboard/user-mgmt/InnerSidebarElement.astro +111 -104
  157. package/frontend/components/dashboard/user-mgmt/UserListItem.astro +9 -22
  158. package/frontend/components/dashboard/user-mgmt/UserListItems.astro +18 -0
  159. package/frontend/components/first-time-setup/snippets/{opt2-studiocms.config.diff → studiocms.config.diff} +1 -0
  160. package/frontend/components/shared/Code.astro +1 -4
  161. package/frontend/components/shared/DynamicSettingsRenderer.astro +1 -1
  162. package/frontend/components/shared/SSRUser.astro +2 -4
  163. package/frontend/components/shared/foldertree/FolderTreeNode.astro +0 -6
  164. package/frontend/components/shared/storage-manager/StorageCopyOutput.astro +0 -1
  165. package/frontend/components/shared/taxonomy/TaxonomyTreeNode.astro +0 -6
  166. package/frontend/layouts/DashboardLayout.astro +1 -10
  167. package/frontend/layouts/TaxonomyLayout.astro +0 -1
  168. package/frontend/middleware/index.ts +102 -61
  169. package/frontend/pages/404.astro +5 -9
  170. package/frontend/pages/[dashboard]/[...pluginPage].astro +10 -1
  171. package/frontend/pages/[dashboard]/configuration.astro +10 -1
  172. package/frontend/pages/[dashboard]/content-management/createfolder.astro +10 -1
  173. package/frontend/pages/[dashboard]/content-management/createpage.astro +10 -1
  174. package/frontend/pages/[dashboard]/content-management/diff.astro +39 -14
  175. package/frontend/pages/[dashboard]/content-management/editfolder.astro +10 -1
  176. package/frontend/pages/[dashboard]/content-management/editpage.astro +10 -1
  177. package/frontend/pages/[dashboard]/content-management/index.astro +10 -1
  178. package/frontend/pages/[dashboard]/index.astro +10 -1
  179. package/frontend/pages/[dashboard]/login.astro +86 -25
  180. package/frontend/pages/[dashboard]/password-reset.astro +22 -16
  181. package/frontend/pages/[dashboard]/plugins/[plugin].astro +10 -1
  182. package/frontend/pages/[dashboard]/profile.astro +10 -1
  183. package/frontend/pages/[dashboard]/signup.astro +153 -52
  184. package/frontend/pages/[dashboard]/smtp-configuration.astro +77 -75
  185. package/frontend/pages/[dashboard]/system-management.astro +10 -1
  186. package/frontend/pages/[dashboard]/taxonomy/categories.astro +30 -41
  187. package/frontend/pages/[dashboard]/taxonomy/index.astro +10 -0
  188. package/frontend/pages/[dashboard]/taxonomy/tags.astro +33 -43
  189. package/frontend/pages/[dashboard]/unverified-email.astro +29 -21
  190. package/frontend/pages/[dashboard]/user-management/edit.astro +170 -90
  191. package/frontend/pages/[dashboard]/user-management/index.astro +10 -1
  192. package/frontend/pages/studiocms_api/[...all].ts +106 -0
  193. package/frontend/pages/studiocms_api/_handlers/_utils/auth.ts +26 -0
  194. package/frontend/pages/studiocms_api/_handlers/_utils/changelog.ts +147 -0
  195. package/frontend/pages/studiocms_api/_handlers/_utils/db-studio-driver.ts +46 -0
  196. package/frontend/pages/studiocms_api/_handlers/_utils/parseLogLevel.ts +27 -0
  197. package/frontend/pages/studiocms_api/_handlers/auth/auth.ts +459 -0
  198. package/frontend/pages/studiocms_api/_handlers/auth/index.ts +17 -0
  199. package/frontend/pages/studiocms_api/_handlers/auth/oauth.ts +91 -0
  200. package/frontend/pages/studiocms_api/_handlers/dashboard/_shared.ts +57 -0
  201. package/frontend/pages/studiocms_api/_handlers/dashboard/apiTokens.ts +134 -0
  202. package/frontend/pages/studiocms_api/_handlers/dashboard/config.ts +64 -0
  203. package/frontend/pages/studiocms_api/_handlers/dashboard/content.ts +741 -0
  204. package/frontend/pages/studiocms_api/_handlers/dashboard/create.ts +480 -0
  205. package/frontend/pages/studiocms_api/_handlers/dashboard/emailNotifications.ts +49 -0
  206. package/frontend/pages/studiocms_api/_handlers/dashboard/index.ts +45 -0
  207. package/frontend/pages/studiocms_api/_handlers/dashboard/mailer.ts +136 -0
  208. package/frontend/pages/studiocms_api/_handlers/dashboard/plugins.ts +80 -0
  209. package/frontend/pages/studiocms_api/_handlers/dashboard/profile.ts +275 -0
  210. package/frontend/pages/studiocms_api/_handlers/dashboard/resetPassword.ts +140 -0
  211. package/frontend/pages/studiocms_api/_handlers/dashboard/search.ts +63 -0
  212. package/frontend/pages/studiocms_api/_handlers/dashboard/taxonomy.ts +285 -0
  213. package/frontend/pages/studiocms_api/_handlers/dashboard/templates.ts +75 -0
  214. package/frontend/pages/studiocms_api/_handlers/dashboard/users.ts +312 -0
  215. package/frontend/pages/studiocms_api/_handlers/dashboard/verifyEndpoints.ts +307 -0
  216. package/frontend/pages/studiocms_api/_handlers/integration/dbStudio.ts +98 -0
  217. package/frontend/pages/studiocms_api/_handlers/integration/index.ts +17 -0
  218. package/frontend/pages/studiocms_api/_handlers/integration/storageManager.ts +107 -0
  219. package/frontend/pages/studiocms_api/_handlers/rest-api/index.ts +8 -0
  220. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/_shared.ts +41 -0
  221. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/index.ts +17 -0
  222. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/public.ts +195 -0
  223. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts +1726 -0
  224. package/frontend/pages/studiocms_api/_handlers/sdk.ts +129 -0
  225. package/frontend/pages/studiocms_api/_middleware/astroLocals.ts +36 -0
  226. package/frontend/pages/studiocms_api/_middleware/restApi.ts +56 -0
  227. package/frontend/pages/studiocms_api/integrations/[...all].ts +8 -0
  228. package/frontend/scripts/formdata.ts +219 -0
  229. package/frontend/setup-pages/3-done.astro +4 -20
  230. package/frontend/setup-pages/studiocms_api/dashboard/step-2.ts +3 -6
  231. package/frontend/styles/dashboard-base.css +0 -1
  232. package/frontend/web-vitals/endpoint.ts +2 -1
  233. package/package.json +35 -31
  234. package/dist/global.d.ts +0 -9
  235. package/frontend/components/dashboard/LoginChecker.astro +0 -68
  236. package/frontend/components/dashboard/user-mgmt/RankCheck.astro +0 -57
  237. package/frontend/components/first-time-setup/snippets/opt1-astro.config.diff +0 -14
  238. package/frontend/components/first-time-setup/snippets/opt2-astro.config.diff +0 -9
  239. package/frontend/middleware/_authmap.ts +0 -63
  240. package/frontend/pages/studiocms_api/auth/[path].ts +0 -390
  241. package/frontend/pages/studiocms_api/auth/[provider]/[...id].ts +0 -64
  242. package/frontend/pages/studiocms_api/auth/[provider]/_effects/index.ts +0 -101
  243. package/frontend/pages/studiocms_api/auth/_shared.ts +0 -52
  244. package/frontend/pages/studiocms_api/dashboard/api-tokens.ts +0 -115
  245. package/frontend/pages/studiocms_api/dashboard/config.ts +0 -74
  246. package/frontend/pages/studiocms_api/dashboard/content/diff.ts +0 -73
  247. package/frontend/pages/studiocms_api/dashboard/content/folder.ts +0 -220
  248. package/frontend/pages/studiocms_api/dashboard/content/page.ts +0 -359
  249. package/frontend/pages/studiocms_api/dashboard/create-reset-link.ts +0 -77
  250. package/frontend/pages/studiocms_api/dashboard/create-user-invite.ts +0 -231
  251. package/frontend/pages/studiocms_api/dashboard/create-user.ts +0 -186
  252. package/frontend/pages/studiocms_api/dashboard/email-notification-settings-site.ts +0 -74
  253. package/frontend/pages/studiocms_api/dashboard/mailer/check-email.ts +0 -75
  254. package/frontend/pages/studiocms_api/dashboard/mailer/config.ts +0 -136
  255. package/frontend/pages/studiocms_api/dashboard/plugins/[plugin].ts +0 -80
  256. package/frontend/pages/studiocms_api/dashboard/profile.ts +0 -245
  257. package/frontend/pages/studiocms_api/dashboard/resend-verify-email.ts +0 -77
  258. package/frontend/pages/studiocms_api/dashboard/reset-password.ts +0 -124
  259. package/frontend/pages/studiocms_api/dashboard/search-list.ts +0 -59
  260. package/frontend/pages/studiocms_api/dashboard/taxonomy-search.ts +0 -47
  261. package/frontend/pages/studiocms_api/dashboard/taxonomy.ts +0 -230
  262. package/frontend/pages/studiocms_api/dashboard/templates.ts +0 -74
  263. package/frontend/pages/studiocms_api/dashboard/update-user-notifications.ts +0 -86
  264. package/frontend/pages/studiocms_api/dashboard/users.ts +0 -236
  265. package/frontend/pages/studiocms_api/dashboard/verify-email.ts +0 -83
  266. package/frontend/pages/studiocms_api/dashboard/verify-session.ts +0 -187
  267. package/frontend/pages/studiocms_api/integrations/[type]/[...id].ts +0 -15
  268. package/frontend/pages/studiocms_api/integrations/[type]/_routes/db-studio.ts +0 -173
  269. package/frontend/pages/studiocms_api/integrations/[type]/_routes/storage.ts +0 -88
  270. package/frontend/pages/studiocms_api/partials/editor.astro +0 -74
  271. package/frontend/pages/studiocms_api/partials/render.astro +0 -39
  272. package/frontend/pages/studiocms_api/partials/user-list-items.astro +0 -43
  273. package/frontend/pages/studiocms_api/rest/utils/auth-token.ts +0 -59
  274. package/frontend/pages/studiocms_api/rest/v1/[type]/[...id].ts +0 -23
  275. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/categories.ts +0 -267
  276. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/folders.ts +0 -283
  277. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/pages.ts +0 -505
  278. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/settings.ts +0 -100
  279. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/tags.ts +0 -229
  280. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/users.ts +0 -553
  281. package/frontend/pages/studiocms_api/rest/v1/public/[type]/[...id].ts +0 -19
  282. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/categories.ts +0 -74
  283. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/folders.ts +0 -85
  284. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/pages.ts +0 -103
  285. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/tags.ts +0 -67
  286. package/frontend/pages/studiocms_api/sdk/[...path].ts +0 -97
  287. package/frontend/pages/studiocms_api/sdk/utils/changelog.ts +0 -119
  288. package/frontend/scripts/auth/formListener.ts +0 -81
  289. package/frontend/scripts/formdata-utils.ts +0 -116
  290. package/frontend/utils/build-partial-schema.ts +0 -46
  291. package/frontend/utils/errors.ts +0 -6
  292. package/frontend/utils/param-extractor.ts +0 -83
  293. package/frontend/utils/rest-router.ts +0 -444
@@ -0,0 +1,741 @@
1
+ import { developerConfig } from 'studiocms:config';
2
+ import { Notifications } from 'studiocms:notifier';
3
+ import plugins from 'studiocms:plugins';
4
+ import { apiEndpoints } from 'studiocms:plugins/endpoints';
5
+ import { SDKCore } from 'studiocms:sdk';
6
+ import type {
7
+ CombinedInsertContent,
8
+ tsPageContentSelect,
9
+ tsPageData,
10
+ tsPageDataSelect,
11
+ } from 'studiocms:sdk/types';
12
+ import routeConfig from 'virtual:studiocms/route-config';
13
+ import { HttpApiBuilder } from '@effect/platform';
14
+ import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
15
+ import { AstroAPIContext, CurrentUser } from '@withstudiocms/api-spec/astro-context';
16
+ import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
17
+ import { StudioCMSPageData, StudioCMSPageFolderStructure } from '@withstudiocms/sdk/tables';
18
+ import { Effect, Schema } from 'effect';
19
+ import type { PluginAPIRoute } from '#plugins';
20
+ import {
21
+ encodeStringArray,
22
+ sharedDBErrors,
23
+ sharedNotifierErrors,
24
+ sharedPageCollectionErrors,
25
+ } from './_shared.js';
26
+
27
+ /**
28
+ * Check if the Dashboard API is enabled in the route configuration.
29
+ */
30
+ const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
31
+
32
+ /**
33
+ * API Endpoints types for plugins
34
+ */
35
+ type ApiEndpoints = {
36
+ onCreate?: PluginAPIRoute<'onCreate'> | null;
37
+ onEdit?: PluginAPIRoute<'onEdit'> | null;
38
+ onDelete?: PluginAPIRoute<'onDelete'> | null;
39
+ };
40
+
41
+ /**
42
+ * Type definition for the page types provided by plugins. This includes the identifier, label, description, and any associated API endpoints for creating, editing, or deleting pages of that type. This type is used to structure the information about different page types that can be managed through the dashboard, allowing for dynamic handling of various content types based on the plugins installed in the system.
43
+ */
44
+ type PageTypeOutput = {
45
+ identifier: string;
46
+ label: string;
47
+ description?: string | undefined;
48
+ pageContentComponent?: string | undefined;
49
+ apiEndpoint?: string;
50
+ apiEndpoints?: ApiEndpoints | undefined;
51
+ };
52
+
53
+ /**
54
+ * Type definition for the API response when creating or updating page content. This type includes the necessary fields for the page content, such as the content ID, the associated page ID, the content itself, and the language of the content. This structured type ensures that the API responses for content creation and updates are consistent and can be properly handled by the frontend components that consume this API.
55
+ */
56
+ type UpdatePageContent = Partial<tsPageContentSelect>;
57
+
58
+ /**
59
+ * Type definition for the structure of the page data used in the dashboard API. This type includes all the relevant fields for a page, such as the title, slug, description, author ID, categories, tags, augments, contributor IDs, and timestamps for when the page was updated and published. This comprehensive type definition allows for strong typing and validation of page data when creating or updating pages through the dashboard API.
60
+ */
61
+ const pageTypeOptions = plugins.flatMap(({ pageTypes }) => {
62
+ const pageTypeOutput: PageTypeOutput[] = [];
63
+
64
+ if (!pageTypes) return pageTypeOutput;
65
+
66
+ for (const pageType of pageTypes) {
67
+ pageTypeOutput.push({
68
+ ...pageType,
69
+ apiEndpoints: apiEndpoints.find((endpoint) => endpoint.identifier === pageType.identifier),
70
+ });
71
+ }
72
+
73
+ return pageTypeOutput;
74
+ });
75
+
76
+ /**
77
+ * Utility function to retrieve the API endpoints for a given page type and action (create, edit, delete). This function takes the package identifier and the type of action as parameters and returns the corresponding API endpoint if it exists. This allows the content handlers to dynamically determine which API routes to call for different page types based on the plugins installed in the system, enabling extensibility and customization of content management through the dashboard API.
78
+ */
79
+ function getPageTypeEndpoints<T extends 'onCreate' | 'onEdit' | 'onDelete'>(pkg: string, type: T) {
80
+ const currentPageType = pageTypeOptions.find((pageType) => pageType.identifier === pkg);
81
+
82
+ if (!currentPageType) return undefined;
83
+
84
+ return currentPageType.apiEndpoints?.[type];
85
+ }
86
+
87
+ /**
88
+ * Content Handlers for the Dashboard API - This group of handlers includes endpoints for managing content in the dashboard, such as creating, updating, and deleting folders and pages. Each handler checks if the Dashboard API is enabled, verifies user permissions, validates input data, and interacts with the SDK to perform the necessary actions. The handlers also include error handling to return appropriate error messages for various failure scenarios, such as unauthorized access, invalid input, or internal server errors. Additionally, notifications are sent for certain actions, such as when a new folder or page is created or updated.
89
+ */
90
+ export const ContentHandlers = HttpApiBuilder.group(
91
+ StudioCMSDashboardApiSpec,
92
+ 'content',
93
+ (handlers) =>
94
+ handlers
95
+
96
+ // Folder Handlers
97
+ .handle(
98
+ 'createFolder',
99
+ Effect.fn(
100
+ function* ({ payload: { folderName, parentFolder } }) {
101
+ if (!dashboardAPIEnabled) {
102
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
103
+ }
104
+
105
+ // Check if demo mode is enabled
106
+ if (developerConfig.demoMode !== false) {
107
+ return yield* new DashboardAPIError({
108
+ error: 'Demo mode is enabled, this action is not allowed.',
109
+ });
110
+ }
111
+
112
+ const [sdk, userData, notifier] = yield* Effect.all([
113
+ SDKCore,
114
+ CurrentUser,
115
+ Notifications,
116
+ ]);
117
+
118
+ const isAuthorized = userData.userPermissionLevel.isAdmin;
119
+
120
+ if (!userData.isLoggedIn || !isAuthorized) {
121
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
122
+ }
123
+
124
+ yield* Effect.all([
125
+ sdk.POST.folder({
126
+ id: crypto.randomUUID(),
127
+ name: folderName,
128
+ parent: parentFolder || null,
129
+ }),
130
+ sdk.UPDATE.folderList,
131
+ sdk.UPDATE.folderTree,
132
+ notifier
133
+ .sendEditorNotification('new_folder', folderName)
134
+ .pipe(
135
+ Effect.catchAll(
136
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
137
+ )
138
+ ),
139
+ ]);
140
+
141
+ return { message: 'Folder created successfully' };
142
+ },
143
+ Notifications.Provide,
144
+ Effect.catchTags({
145
+ ...sharedDBErrors,
146
+ ...sharedNotifierErrors,
147
+ FolderTreeError: () => new DashboardAPIError({ error: 'Failed to update folder tree' }),
148
+ })
149
+ )
150
+ )
151
+ .handle(
152
+ 'updateFolder',
153
+ Effect.fn(
154
+ function* ({ payload: { id, folderName, parentFolder } }) {
155
+ if (!dashboardAPIEnabled) {
156
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
157
+ }
158
+
159
+ // Check if demo mode is enabled
160
+ if (developerConfig.demoMode !== false) {
161
+ return yield* new DashboardAPIError({
162
+ error: 'Demo mode is enabled, this action is not allowed.',
163
+ });
164
+ }
165
+
166
+ const [sdk, userData, notifier] = yield* Effect.all([
167
+ SDKCore,
168
+ CurrentUser,
169
+ Notifications,
170
+ ]);
171
+
172
+ const isAuthorized = userData.userPermissionLevel.isEditor;
173
+
174
+ if (!userData.isLoggedIn || !isAuthorized) {
175
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
176
+ }
177
+
178
+ if (parentFolder && parentFolder === id) {
179
+ return yield* new DashboardAPIError({ error: 'A folder cannot be its own parent' });
180
+ }
181
+
182
+ yield* Effect.all([
183
+ sdk.UPDATE.folder({
184
+ id,
185
+ name: folderName,
186
+ parent: parentFolder || null,
187
+ }),
188
+ sdk.UPDATE.folderList,
189
+ sdk.UPDATE.folderTree,
190
+ notifier
191
+ .sendEditorNotification('folder_updated', folderName)
192
+ .pipe(
193
+ Effect.catchAll(
194
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
195
+ )
196
+ ),
197
+ ]);
198
+
199
+ return { message: 'Folder updated successfully' };
200
+ },
201
+ Notifications.Provide,
202
+ Effect.catchTags({
203
+ ...sharedDBErrors,
204
+ ...sharedNotifierErrors,
205
+ FolderTreeError: () => new DashboardAPIError({ error: 'Failed to update folder tree' }),
206
+ })
207
+ )
208
+ )
209
+ .handle(
210
+ 'deleteFolder',
211
+ Effect.fn(
212
+ function* ({ payload: { id } }) {
213
+ if (!dashboardAPIEnabled) {
214
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
215
+ }
216
+
217
+ // Check if demo mode is enabled
218
+ if (developerConfig.demoMode !== false) {
219
+ return yield* new DashboardAPIError({
220
+ error: 'Demo mode is enabled, this action is not allowed.',
221
+ });
222
+ }
223
+
224
+ const [sdk, userData, notifier] = yield* Effect.all([
225
+ SDKCore,
226
+ CurrentUser,
227
+ Notifications,
228
+ ]);
229
+
230
+ /**
231
+ * Check for child folders before deletion
232
+ */
233
+ const checkForChildrenFolders = sdk.dbService.withCodec({
234
+ encoder: Schema.String,
235
+ decoder: Schema.Array(StudioCMSPageFolderStructure.Select),
236
+ callbackFn: (client, id) =>
237
+ client((db) =>
238
+ db
239
+ .selectFrom('StudioCMSPageFolderStructure')
240
+ .where('parent', '=', id)
241
+ .selectAll()
242
+ .execute()
243
+ ),
244
+ });
245
+
246
+ /**
247
+ * Check for child pages before deletion
248
+ */
249
+ const checkForChildrenPages = sdk.dbService.withCodec({
250
+ encoder: Schema.String,
251
+ decoder: Schema.Array(StudioCMSPageData.Select),
252
+ callbackFn: (client, id) =>
253
+ client((db) =>
254
+ db
255
+ .selectFrom('StudioCMSPageData')
256
+ .where('parentFolder', '=', id)
257
+ .selectAll()
258
+ .execute()
259
+ ),
260
+ });
261
+
262
+ /**
263
+ * Check for any children (folders or pages) before deletion
264
+ */
265
+ const checkForChildren = Effect.fn((id: string) =>
266
+ Effect.all({
267
+ folders: checkForChildrenFolders(id),
268
+ pages: checkForChildrenPages(id),
269
+ }).pipe(
270
+ Effect.map(({ folders, pages }) => {
271
+ return { hasChildren: folders.length > 0 || pages.length > 0 };
272
+ })
273
+ )
274
+ );
275
+
276
+ const isAuthorized = userData.userPermissionLevel.isAdmin;
277
+
278
+ if (!userData.isLoggedIn || !isAuthorized) {
279
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
280
+ }
281
+
282
+ const existingFolder = yield* sdk.GET.folder(id);
283
+
284
+ if (!existingFolder) {
285
+ return yield* new DashboardAPIError({ error: 'Folder not found' });
286
+ }
287
+
288
+ const { hasChildren } = yield* checkForChildren(id);
289
+
290
+ if (hasChildren) {
291
+ return yield* new DashboardAPIError({
292
+ error: 'Folder cannot be deleted because it has child folders or pages',
293
+ });
294
+ }
295
+
296
+ yield* Effect.all([
297
+ sdk.DELETE.folder(id),
298
+ sdk.UPDATE.folderList,
299
+ sdk.UPDATE.folderTree,
300
+ notifier
301
+ .sendEditorNotification('folder_deleted', existingFolder.name)
302
+ .pipe(
303
+ Effect.catchAll(
304
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
305
+ )
306
+ ),
307
+ ]);
308
+
309
+ return { message: 'Folder deleted successfully' };
310
+ },
311
+ Notifications.Provide,
312
+ Effect.catchTags({
313
+ ...sharedDBErrors,
314
+ ...sharedNotifierErrors,
315
+ FolderTreeError: () => new DashboardAPIError({ error: 'Failed to update folder tree' }),
316
+ })
317
+ )
318
+ )
319
+
320
+ // Page Handlers
321
+ .handle(
322
+ 'createPage',
323
+ Effect.fn(
324
+ function* ({ payload }) {
325
+ if (!dashboardAPIEnabled) {
326
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
327
+ }
328
+
329
+ // Check if demo mode is enabled
330
+ if (developerConfig.demoMode !== false) {
331
+ return yield* new DashboardAPIError({
332
+ error: 'Demo mode is enabled, this action is not allowed.',
333
+ });
334
+ }
335
+
336
+ const [sdk, userData, notifier, ctx] = yield* Effect.all([
337
+ SDKCore,
338
+ CurrentUser,
339
+ Notifications,
340
+ AstroAPIContext,
341
+ ]);
342
+
343
+ const isAuthorized = userData.userPermissionLevel.isEditor;
344
+
345
+ if (!userData.isLoggedIn || !isAuthorized) {
346
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
347
+ }
348
+
349
+ const content = {
350
+ id: crypto.randomUUID(),
351
+ content: '',
352
+ } as UpdatePageContent;
353
+
354
+ if (!payload.title) {
355
+ return yield* new DashboardAPIError({ error: 'Title is required' });
356
+ }
357
+
358
+ const dataId = crypto.randomUUID();
359
+
360
+ // biome-ignore lint/style/noNonNullAssertion: We know that payload.package will be provided in this context, as it's required for page creation and is validated at the API route level.
361
+ const apiRoute = getPageTypeEndpoints(payload.package!, 'onCreate');
362
+
363
+ const pageContent: CombinedInsertContent = {
364
+ contentLang: 'default',
365
+ content: content.content || '',
366
+ };
367
+
368
+ const {
369
+ title,
370
+ slug,
371
+ description,
372
+ categories,
373
+ tags,
374
+ augments,
375
+ contributorIds,
376
+ updatedAt: ___updatedAt,
377
+ ...rest
378
+ } = payload;
379
+
380
+ const safeRest = rest as unknown as Pick<tsPageDataSelect, keyof typeof rest>;
381
+
382
+ const newData = yield* sdk.POST.page({
383
+ pageData: {
384
+ ...safeRest,
385
+ id: dataId,
386
+ title,
387
+ slug: slug || title.toLowerCase().replace(/\s+/g, '-'),
388
+ description: description || '',
389
+ authorId: userData.user?.id || '',
390
+ updatedAt: new Date().toISOString(),
391
+ publishedAt: new Date().toISOString(),
392
+ categories: yield* encodeStringArray(categories || []),
393
+ tags: yield* encodeStringArray(tags || []),
394
+ augments: yield* encodeStringArray(augments || []),
395
+ contributorIds: yield* encodeStringArray(contributorIds || []),
396
+ contentLang: 'default',
397
+ },
398
+ pageContent,
399
+ });
400
+
401
+ if (!newData) {
402
+ return yield* new DashboardAPIError({ error: 'Failed to create page' });
403
+ }
404
+
405
+ if (apiRoute) {
406
+ yield* Effect.tryPromise(() => apiRoute({ AstroCtx: ctx, pageData: newData })).pipe(
407
+ Effect.catchAll((error) => {
408
+ console.error('Error executing page type API route:', error);
409
+ return new DashboardAPIError({ error: 'Failed to execute page type API route' });
410
+ })
411
+ );
412
+ }
413
+
414
+ yield* Effect.all([
415
+ sdk.CLEAR.pages,
416
+ notifier
417
+ .sendEditorNotification('new_page', newData.title)
418
+ .pipe(
419
+ Effect.catchAll(
420
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
421
+ )
422
+ ),
423
+ ]);
424
+
425
+ return {
426
+ message: 'Page created successfully',
427
+ };
428
+ },
429
+ Notifications.Provide,
430
+ Effect.catchTags({
431
+ ...sharedDBErrors,
432
+ ...sharedNotifierErrors,
433
+ ...sharedPageCollectionErrors,
434
+ })
435
+ )
436
+ )
437
+ .handle(
438
+ 'updatePage',
439
+ Effect.fn(
440
+ function* ({ payload }) {
441
+ if (!dashboardAPIEnabled) {
442
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
443
+ }
444
+
445
+ // Check if demo mode is enabled
446
+ if (developerConfig.demoMode !== false) {
447
+ return yield* new DashboardAPIError({
448
+ error: 'Demo mode is enabled, this action is not allowed.',
449
+ });
450
+ }
451
+
452
+ const [sdk, userData, notifier, ctx] = yield* Effect.all([
453
+ SDKCore,
454
+ CurrentUser,
455
+ Notifications,
456
+ AstroAPIContext,
457
+ ]);
458
+
459
+ const isAuthorized = userData.userPermissionLevel.isEditor;
460
+
461
+ if (!userData.isLoggedIn || !isAuthorized) {
462
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
463
+ }
464
+
465
+ const { contentId, content: incomingContent, pluginFields, ...data } = payload;
466
+
467
+ if (!data.id) {
468
+ return yield* new DashboardAPIError({ error: 'Page ID is required for update' });
469
+ }
470
+
471
+ if (!contentId) {
472
+ return yield* new DashboardAPIError({ error: 'Content ID is required for update' });
473
+ }
474
+
475
+ const content = {
476
+ id: contentId,
477
+ contentId: data.id,
478
+ content: incomingContent,
479
+ contentLang: 'default',
480
+ };
481
+
482
+ const currentPageData = yield* sdk.GET.page.byId(data.id);
483
+
484
+ if (!currentPageData) {
485
+ return yield* new DashboardAPIError({ error: 'Page not found' });
486
+ }
487
+
488
+ const { authorId, contributorIds, defaultContent } = currentPageData;
489
+
490
+ let AuthorId = authorId;
491
+
492
+ if (!authorId) {
493
+ AuthorId = userData.user?.id || '';
494
+ }
495
+
496
+ const ContributorIds = contributorIds || [];
497
+
498
+ // biome-ignore lint/style/noNonNullAssertion: this is a valid use case for non-null assertion
499
+ if (!ContributorIds.includes(userData.user!.id)) {
500
+ // biome-ignore lint/style/noNonNullAssertion: this is a valid use case for non-null assertion
501
+ ContributorIds.push(userData.user!.id);
502
+ }
503
+
504
+ const newData: tsPageData['Update']['Type'] = {
505
+ ...(data as tsPageDataSelect),
506
+ authorId: AuthorId,
507
+ contributorIds: yield* encodeStringArray(ContributorIds),
508
+ updatedAt: new Date().toISOString(),
509
+ publishedAt:
510
+ currentPageData.draft && data.draft === false
511
+ ? new Date().toISOString()
512
+ : currentPageData.publishedAt?.toISOString() || new Date().toISOString(),
513
+ categories: yield* encodeStringArray(data.categories || []),
514
+ tags: yield* encodeStringArray(data.tags || []),
515
+ augments: yield* encodeStringArray(data.augments || []),
516
+ contentLang: 'default',
517
+ };
518
+
519
+ const getMetaData = sdk.dbService.withCodec({
520
+ encoder: Schema.String,
521
+ decoder: StudioCMSPageData.Select,
522
+ callbackFn: (query, input) =>
523
+ query((db) =>
524
+ db
525
+ .selectFrom('StudioCMSPageData')
526
+ .selectAll()
527
+ .where('id', '=', input)
528
+ .executeTakeFirstOrThrow()
529
+ ),
530
+ });
531
+
532
+ const startMetaData = yield* getMetaData(data.id);
533
+
534
+ // biome-ignore lint/style/noNonNullAssertion: this is a valid use case for non-null assertion
535
+ const apiRoute = getPageTypeEndpoints(data.package!, 'onEdit');
536
+
537
+ const updatedPage = yield* sdk.UPDATE.page.byId(data.id, {
538
+ pageData: newData,
539
+ pageContent: content,
540
+ });
541
+
542
+ if (!updatedPage) {
543
+ return yield* new DashboardAPIError({ error: 'Failed to update page' });
544
+ }
545
+
546
+ const updatedMetaData = yield* getMetaData(data.id);
547
+
548
+ const { enableDiffs, diffPerPage = 10 } = ctx.locals.StudioCMS.siteConfig.data;
549
+
550
+ if (enableDiffs) {
551
+ yield* sdk.diffTracking.insert(
552
+ // biome-ignore lint/style/noNonNullAssertion: this is a valid use case for non-null assertion
553
+ userData.user!.id,
554
+ data.id,
555
+ {
556
+ content: {
557
+ start: defaultContent?.content || '',
558
+ end: content.content || '',
559
+ },
560
+ // biome-ignore lint/style/noNonNullAssertion: this is a valid use case for non-null assertion
561
+ metaData: { start: startMetaData!, end: updatedMetaData! },
562
+ },
563
+ diffPerPage
564
+ );
565
+ }
566
+
567
+ if (apiRoute) {
568
+ yield* Effect.tryPromise(() =>
569
+ apiRoute({ AstroCtx: ctx, pageData: updatedPage, pluginFields })
570
+ );
571
+ }
572
+
573
+ yield* Effect.all([
574
+ sdk.CLEAR.pages,
575
+ notifier
576
+ .sendEditorNotification('page_updated', data.title || startMetaData?.title || '')
577
+ .pipe(
578
+ Effect.catchAll(
579
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
580
+ )
581
+ ),
582
+ ]);
583
+
584
+ return {
585
+ message: 'Page updated successfully',
586
+ };
587
+ },
588
+ Notifications.Provide,
589
+ Effect.catchTags({
590
+ ...sharedDBErrors,
591
+ ...sharedNotifierErrors,
592
+ ...sharedPageCollectionErrors,
593
+ ParseError: () =>
594
+ new DashboardAPIError({ error: 'Failed to parse data during page update' }),
595
+ DiffError: () =>
596
+ new DashboardAPIError({
597
+ error: 'Failed to track changes for diff during page update',
598
+ }),
599
+ ParsersError: () =>
600
+ new DashboardAPIError({ error: 'Failed to parse data for diff during page update' }),
601
+ })
602
+ )
603
+ )
604
+ .handle(
605
+ 'deletePage',
606
+ Effect.fn(
607
+ function* ({ payload: { id, slug } }) {
608
+ if (!dashboardAPIEnabled) {
609
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
610
+ }
611
+
612
+ // Check if demo mode is enabled
613
+ if (developerConfig.demoMode !== false) {
614
+ return yield* new DashboardAPIError({
615
+ error: 'Demo mode is enabled, this action is not allowed.',
616
+ });
617
+ }
618
+
619
+ const [sdk, userData, notifier, ctx] = yield* Effect.all([
620
+ SDKCore,
621
+ CurrentUser,
622
+ Notifications,
623
+ AstroAPIContext,
624
+ ]);
625
+
626
+ const isAuthorized = userData.userPermissionLevel.isAdmin;
627
+
628
+ if (!userData.isLoggedIn || !isAuthorized) {
629
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
630
+ }
631
+
632
+ if (!slug) {
633
+ return yield* new DashboardAPIError({ error: 'Slug is required for page deletion' });
634
+ }
635
+
636
+ const pageToDelete = yield* sdk.GET.page.byId(id);
637
+
638
+ if (!pageToDelete) {
639
+ return yield* new DashboardAPIError({ error: 'Page not found' });
640
+ }
641
+
642
+ if (pageToDelete.slug !== slug) {
643
+ return yield* new DashboardAPIError({ error: 'Slug does not match the page record' });
644
+ }
645
+
646
+ const apiRoute = getPageTypeEndpoints(pageToDelete.package, 'onDelete');
647
+
648
+ yield* sdk.DELETE.page(id);
649
+
650
+ if (apiRoute) {
651
+ yield* Effect.tryPromise(() => apiRoute({ AstroCtx: ctx, pageData: pageToDelete }));
652
+ }
653
+
654
+ yield* Effect.all([
655
+ sdk.CLEAR.pages,
656
+ notifier
657
+ .sendEditorNotification('page_deleted', pageToDelete.title)
658
+ .pipe(
659
+ Effect.catchAll(
660
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
661
+ )
662
+ ),
663
+ ]);
664
+
665
+ return {
666
+ message: 'Page deleted successfully',
667
+ };
668
+ },
669
+ Notifications.Provide,
670
+ Effect.catchTags({
671
+ ...sharedDBErrors,
672
+ ...sharedNotifierErrors,
673
+ ...sharedPageCollectionErrors,
674
+ })
675
+ )
676
+ )
677
+
678
+ // Diff Handlers
679
+ .handle(
680
+ 'revertToDiff',
681
+ Effect.fn(
682
+ function* ({ payload: { id, type } }) {
683
+ if (!dashboardAPIEnabled) {
684
+ return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
685
+ }
686
+
687
+ // Check if demo mode is enabled
688
+ if (developerConfig.demoMode !== false) {
689
+ return yield* new DashboardAPIError({
690
+ error: 'Demo mode is enabled, this action is not allowed.',
691
+ });
692
+ }
693
+
694
+ const [sdk, userData, notifier] = yield* Effect.all([
695
+ SDKCore,
696
+ CurrentUser,
697
+ Notifications,
698
+ ]);
699
+
700
+ const isAuthorized = userData.userPermissionLevel.isEditor;
701
+
702
+ if (!userData.isLoggedIn || !isAuthorized) {
703
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
704
+ }
705
+
706
+ if (!id || !type) {
707
+ return yield* new DashboardAPIError({ error: 'Invalid ID or Type' });
708
+ }
709
+
710
+ if (!['data', 'content', 'both'].includes(type)) {
711
+ return yield* new DashboardAPIError({ error: 'Invalid Type' });
712
+ }
713
+
714
+ const data = yield* sdk.diffTracking.revertToDiff(id, type);
715
+
716
+ yield* Effect.all([
717
+ sdk.CLEAR.pages,
718
+ notifier
719
+ .sendEditorNotification('page_updated', data.pageMetaData.end.title || '')
720
+ .pipe(
721
+ Effect.catchAll(
722
+ () => new DashboardAPIError({ error: 'Failed to send notification' })
723
+ )
724
+ ),
725
+ ]);
726
+
727
+ return { message: 'Page reverted successfully' };
728
+ },
729
+ Notifications.Provide,
730
+ Effect.catchTags({
731
+ ...sharedDBErrors,
732
+ ...sharedNotifierErrors,
733
+ DiffTrackingError: () => new DashboardAPIError({ error: 'Failed to revert to diff' }),
734
+ ParsersError: () =>
735
+ new DashboardAPIError({ error: 'Failed to parse data during diff revert' }),
736
+ ParseError: () =>
737
+ new DashboardAPIError({ error: 'Failed to parse data during diff revert' }),
738
+ })
739
+ )
740
+ )
741
+ );