studiocms 0.3.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 (263) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/cli/add/index.d.ts +2 -2
  3. package/dist/cli/add/npm-utils.d.ts +6 -6
  4. package/dist/cli/add/tryToInstallPlugins.d.ts +1 -1
  5. package/dist/cli/add/updateStudioCMSConfig.d.ts +1 -1
  6. package/dist/cli/add/validatePlugins.d.ts +1 -1
  7. package/dist/cli/crypto/genJWT/index.d.ts +1 -1
  8. package/dist/cli/crypto/index.d.ts +1 -1
  9. package/dist/cli/init/steps/env.js +12 -2
  10. package/dist/cli/init/steps/next.d.ts +1 -1
  11. package/dist/cli/migrator/index.d.ts +1 -1
  12. package/dist/cli/users/index.d.ts +1 -1
  13. package/dist/cli/users/steps/createUsers.js +3 -3
  14. package/dist/cli/users/steps/next.d.ts +1 -1
  15. package/dist/cli/utils/context.d.ts +1 -1
  16. package/dist/cli/utils/getCliDbClient.d.ts +1 -1
  17. package/dist/cli/utils/intro.d.ts +1 -1
  18. package/dist/cli/utils/loadConfig.d.ts +54 -49
  19. package/dist/cli/utils/loadConfig.js +5 -8
  20. package/dist/cli/utils/user-utils.d.ts +1 -1
  21. package/dist/client/apiClient.d.ts +4923 -0
  22. package/dist/client/apiClient.js +72 -0
  23. package/dist/config.d.ts +1734 -1
  24. package/dist/consts.d.ts +5 -5
  25. package/dist/consts.js +3 -2
  26. package/dist/db/plugins.d.ts +1 -1
  27. package/dist/db/plugins.js +5 -8
  28. package/dist/handlers/frontend/routes.d.ts +4 -18
  29. package/dist/handlers/frontend/routes.js +13 -152
  30. package/dist/handlers/frontend/types.d.ts +1 -1
  31. package/dist/handlers/frontend/utils.js +0 -18
  32. package/dist/handlers/pluginHandler.d.ts +34 -257
  33. package/dist/handlers/pluginHandler.js +92 -46
  34. package/dist/handlers/routeHandler.js +32 -11
  35. package/dist/handlers/setupDbStudio.d.ts +1 -1
  36. package/dist/handlers/storage-manager/core/effectify-astro-context.d.ts +25 -0
  37. package/dist/handlers/storage-manager/core/effectify-astro-context.js +78 -0
  38. package/dist/handlers/storage-manager/no-op.d.ts +2 -2
  39. package/dist/handlers/storage-manager/no-op.js +2 -3
  40. package/dist/index.d.ts +0 -1
  41. package/dist/index.js +9 -5
  42. package/dist/integrations/robots/index.d.ts +2 -2
  43. package/dist/integrations/robots/index.js +1 -3
  44. package/dist/integrations/robots/schema.d.ts +102 -273
  45. package/dist/integrations/robots/schema.js +220 -209
  46. package/dist/plugins/analytics/assets/schemas.d.ts +14 -9
  47. package/dist/plugins/analytics/assets/schemas.js +25 -17
  48. package/dist/plugins/analytics/db-client.d.ts +1 -1
  49. package/dist/plugins/analytics/index.d.ts +823 -3
  50. package/dist/plugins/analytics/index.js +4 -5
  51. package/dist/plugins/analytics/schemas.d.ts +54 -62
  52. package/dist/plugins/analytics/schemas.js +64 -13
  53. package/dist/plugins/analytics/table.d.ts +1 -1
  54. package/dist/plugins.d.ts +0 -1
  55. package/dist/schemas/config/api.d.ts +17 -0
  56. package/dist/schemas/config/api.js +14 -0
  57. package/dist/schemas/config/auth.d.ts +55 -59
  58. package/dist/schemas/config/auth.js +34 -11
  59. package/dist/schemas/config/dashboard.d.ts +43 -79
  60. package/dist/schemas/config/dashboard.js +43 -12
  61. package/dist/schemas/config/db.d.ts +15 -17
  62. package/dist/schemas/config/db.js +13 -5
  63. package/dist/schemas/config/developer.d.ts +33 -45
  64. package/dist/schemas/config/developer.js +22 -5
  65. package/dist/schemas/config/index.d.ts +398 -521
  66. package/dist/schemas/config/index.js +115 -57
  67. package/dist/schemas/config/sdk.d.ts +50 -196
  68. package/dist/schemas/config/sdk.js +61 -73
  69. package/dist/schemas/custom.d.ts +40 -0
  70. package/dist/schemas/custom.js +41 -0
  71. package/dist/schemas/external-schemas.d.ts +171 -0
  72. package/dist/schemas/external-schemas.js +179 -0
  73. package/dist/schemas/index.d.ts +2 -0
  74. package/dist/schemas/index.js +2 -0
  75. package/dist/schemas/plugins/i18n.d.ts +59 -39
  76. package/dist/schemas/plugins/i18n.js +42 -5
  77. package/dist/schemas/plugins/index.d.ts +7126 -10296
  78. package/dist/schemas/plugins/index.js +260 -276
  79. package/dist/schemas/plugins/shared.d.ts +1293 -3718
  80. package/dist/schemas/plugins/shared.js +320 -329
  81. package/dist/test-utils.d.ts +15 -4
  82. package/dist/test-utils.js +27 -11
  83. package/dist/toolbar/db-viewer/db-shared-types.d.ts +6 -6
  84. package/dist/toolbar/db-viewer/viewer.js +20 -21
  85. package/dist/types.d.ts +30 -0
  86. package/dist/utils/effects/smtp.d.ts +1 -1
  87. package/dist/utils/lang-helper.d.ts +10 -2
  88. package/dist/virtual.d.ts +23 -0
  89. package/dist/virtuals/auth/core.d.ts +5 -5
  90. package/dist/virtuals/auth/verify-email.d.ts +6 -6
  91. package/dist/virtuals/components/Generator.astro +2 -2
  92. package/dist/virtuals/components/Renderer.astro +9 -1
  93. package/dist/virtuals/components/renderFn.d.ts +3 -1
  94. package/dist/virtuals/components/renderFn.js +18 -0
  95. package/dist/virtuals/lib/headDefaults.d.ts +4 -2
  96. package/dist/virtuals/lib/headDefaults.js +0 -2
  97. package/dist/virtuals/lib/routeMap.d.ts +0 -12
  98. package/dist/virtuals/lib/routeMap.js +2 -14
  99. package/dist/virtuals/mailer/index.d.ts +3 -3
  100. package/dist/virtuals/notifier/index.d.ts +5 -5
  101. package/dist/virtuals/plugins/dashboard-pages.d.ts +2 -64
  102. package/dist/virtuals/scripts/StorageFileBrowser.d.ts +1 -172
  103. package/dist/virtuals/scripts/StorageFileBrowser.js +216 -119
  104. package/dist/virtuals/template-engine/index.d.ts +4 -4
  105. package/frontend/components/dashboard/configuration/ConfigForm.astro +218 -110
  106. package/frontend/components/dashboard/content-mgmt/ContentSearch.astro +21 -22
  107. package/frontend/components/dashboard/content-mgmt/CreateFolder.astro +66 -54
  108. package/frontend/components/dashboard/content-mgmt/CreatePage.astro +58 -104
  109. package/frontend/components/dashboard/content-mgmt/EditFolder.astro +65 -67
  110. package/frontend/components/dashboard/content-mgmt/EditPage.astro +86 -134
  111. package/frontend/components/dashboard/content-mgmt/InnerSidebarElement.astro +0 -1
  112. package/frontend/components/dashboard/content-mgmt/PageHeader.astro +33 -52
  113. package/frontend/components/dashboard/content-mgmt/PageTypeHandler.astro +2 -2
  114. package/frontend/components/dashboard/profile/APITokens.astro +219 -158
  115. package/frontend/components/dashboard/profile/BasicInfo.astro +165 -106
  116. package/frontend/components/dashboard/profile/Notifications.astro +27 -18
  117. package/frontend/components/dashboard/profile/UpdatePassword.astro +134 -94
  118. package/frontend/components/dashboard/sidebar/VersionCheck.astro +31 -16
  119. package/frontend/components/dashboard/sidebar/VersionCheckChangelog.astro +18 -11
  120. package/frontend/components/dashboard/sidebar-modals/VersionModal.astro +2 -2
  121. package/frontend/components/dashboard/smtp-config/TemplateEditor.astro +14 -14
  122. package/frontend/components/dashboard/taxonomy/InnerSidebarElement.astro +0 -1
  123. package/frontend/components/dashboard/taxonomy/MetaContainer.astro +0 -2
  124. package/frontend/components/dashboard/taxonomy/PageHeader.astro +16 -24
  125. package/frontend/components/dashboard/taxonomy/TaxonomySearch.astro +23 -27
  126. package/frontend/components/dashboard/user-mgmt/InnerSidebarElement.astro +111 -104
  127. package/frontend/components/dashboard/user-mgmt/UserListItem.astro +9 -22
  128. package/frontend/components/dashboard/user-mgmt/UserListItems.astro +18 -0
  129. package/frontend/components/first-time-setup/snippets/{opt2-studiocms.config.diff → studiocms.config.diff} +1 -0
  130. package/frontend/components/shared/Code.astro +1 -4
  131. package/frontend/components/shared/DynamicSettingsRenderer.astro +1 -1
  132. package/frontend/components/shared/SSRUser.astro +2 -4
  133. package/frontend/components/shared/foldertree/FolderTreeNode.astro +0 -6
  134. package/frontend/components/shared/storage-manager/StorageCopyOutput.astro +0 -1
  135. package/frontend/components/shared/taxonomy/TaxonomyTreeNode.astro +0 -6
  136. package/frontend/layouts/DashboardLayout.astro +0 -9
  137. package/frontend/layouts/TaxonomyLayout.astro +0 -1
  138. package/frontend/middleware/index.ts +102 -61
  139. package/frontend/pages/404.astro +0 -1
  140. package/frontend/pages/[dashboard]/[...pluginPage].astro +10 -1
  141. package/frontend/pages/[dashboard]/configuration.astro +10 -1
  142. package/frontend/pages/[dashboard]/content-management/createfolder.astro +10 -1
  143. package/frontend/pages/[dashboard]/content-management/createpage.astro +10 -1
  144. package/frontend/pages/[dashboard]/content-management/diff.astro +39 -14
  145. package/frontend/pages/[dashboard]/content-management/editfolder.astro +10 -1
  146. package/frontend/pages/[dashboard]/content-management/editpage.astro +10 -1
  147. package/frontend/pages/[dashboard]/content-management/index.astro +10 -1
  148. package/frontend/pages/[dashboard]/index.astro +10 -1
  149. package/frontend/pages/[dashboard]/login.astro +86 -25
  150. package/frontend/pages/[dashboard]/password-reset.astro +22 -16
  151. package/frontend/pages/[dashboard]/plugins/[plugin].astro +10 -1
  152. package/frontend/pages/[dashboard]/profile.astro +10 -1
  153. package/frontend/pages/[dashboard]/signup.astro +153 -52
  154. package/frontend/pages/[dashboard]/smtp-configuration.astro +77 -75
  155. package/frontend/pages/[dashboard]/system-management.astro +10 -1
  156. package/frontend/pages/[dashboard]/taxonomy/categories.astro +30 -41
  157. package/frontend/pages/[dashboard]/taxonomy/index.astro +10 -0
  158. package/frontend/pages/[dashboard]/taxonomy/tags.astro +33 -43
  159. package/frontend/pages/[dashboard]/unverified-email.astro +29 -21
  160. package/frontend/pages/[dashboard]/user-management/edit.astro +170 -90
  161. package/frontend/pages/[dashboard]/user-management/index.astro +10 -1
  162. package/frontend/pages/studiocms_api/[...all].ts +106 -0
  163. package/frontend/pages/studiocms_api/_handlers/_utils/auth.ts +26 -0
  164. package/frontend/pages/studiocms_api/_handlers/_utils/changelog.ts +147 -0
  165. package/frontend/pages/studiocms_api/_handlers/_utils/db-studio-driver.ts +46 -0
  166. package/frontend/pages/studiocms_api/_handlers/_utils/parseLogLevel.ts +27 -0
  167. package/frontend/pages/studiocms_api/_handlers/auth/auth.ts +459 -0
  168. package/frontend/pages/studiocms_api/_handlers/auth/index.ts +17 -0
  169. package/frontend/pages/studiocms_api/_handlers/auth/oauth.ts +91 -0
  170. package/frontend/pages/studiocms_api/_handlers/dashboard/_shared.ts +57 -0
  171. package/frontend/pages/studiocms_api/_handlers/dashboard/apiTokens.ts +134 -0
  172. package/frontend/pages/studiocms_api/_handlers/dashboard/config.ts +64 -0
  173. package/frontend/pages/studiocms_api/_handlers/dashboard/content.ts +741 -0
  174. package/frontend/pages/studiocms_api/_handlers/dashboard/create.ts +480 -0
  175. package/frontend/pages/studiocms_api/_handlers/dashboard/emailNotifications.ts +49 -0
  176. package/frontend/pages/studiocms_api/_handlers/dashboard/index.ts +45 -0
  177. package/frontend/pages/studiocms_api/_handlers/dashboard/mailer.ts +136 -0
  178. package/frontend/pages/studiocms_api/_handlers/dashboard/plugins.ts +80 -0
  179. package/frontend/pages/studiocms_api/_handlers/dashboard/profile.ts +275 -0
  180. package/frontend/pages/studiocms_api/_handlers/dashboard/resetPassword.ts +140 -0
  181. package/frontend/pages/studiocms_api/_handlers/dashboard/search.ts +63 -0
  182. package/frontend/pages/studiocms_api/_handlers/dashboard/taxonomy.ts +285 -0
  183. package/frontend/pages/studiocms_api/_handlers/dashboard/templates.ts +75 -0
  184. package/frontend/pages/studiocms_api/_handlers/dashboard/users.ts +312 -0
  185. package/frontend/pages/studiocms_api/_handlers/dashboard/verifyEndpoints.ts +307 -0
  186. package/frontend/pages/studiocms_api/_handlers/integration/dbStudio.ts +98 -0
  187. package/frontend/pages/studiocms_api/_handlers/integration/index.ts +17 -0
  188. package/frontend/pages/studiocms_api/_handlers/integration/storageManager.ts +107 -0
  189. package/frontend/pages/studiocms_api/_handlers/rest-api/index.ts +8 -0
  190. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/_shared.ts +41 -0
  191. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/index.ts +17 -0
  192. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/public.ts +195 -0
  193. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts +1726 -0
  194. package/frontend/pages/studiocms_api/_handlers/sdk.ts +129 -0
  195. package/frontend/pages/studiocms_api/_middleware/astroLocals.ts +36 -0
  196. package/frontend/pages/studiocms_api/_middleware/restApi.ts +56 -0
  197. package/frontend/pages/studiocms_api/integrations/[...all].ts +8 -0
  198. package/frontend/scripts/formdata.ts +219 -0
  199. package/frontend/setup-pages/3-done.astro +4 -20
  200. package/frontend/setup-pages/studiocms_api/dashboard/step-2.ts +3 -6
  201. package/frontend/styles/dashboard-base.css +0 -1
  202. package/frontend/web-vitals/endpoint.ts +2 -1
  203. package/package.json +34 -29
  204. package/dist/global.d.ts +0 -9
  205. package/frontend/components/dashboard/LoginChecker.astro +0 -68
  206. package/frontend/components/dashboard/user-mgmt/RankCheck.astro +0 -57
  207. package/frontend/components/first-time-setup/snippets/opt1-astro.config.diff +0 -14
  208. package/frontend/components/first-time-setup/snippets/opt2-astro.config.diff +0 -9
  209. package/frontend/middleware/_authmap.ts +0 -63
  210. package/frontend/pages/studiocms_api/auth/[path].ts +0 -384
  211. package/frontend/pages/studiocms_api/auth/[provider]/[...id].ts +0 -64
  212. package/frontend/pages/studiocms_api/auth/[provider]/_effects/index.ts +0 -101
  213. package/frontend/pages/studiocms_api/auth/_shared.ts +0 -52
  214. package/frontend/pages/studiocms_api/dashboard/api-tokens.ts +0 -115
  215. package/frontend/pages/studiocms_api/dashboard/config.ts +0 -74
  216. package/frontend/pages/studiocms_api/dashboard/content/diff.ts +0 -73
  217. package/frontend/pages/studiocms_api/dashboard/content/folder.ts +0 -220
  218. package/frontend/pages/studiocms_api/dashboard/content/page.ts +0 -359
  219. package/frontend/pages/studiocms_api/dashboard/create-reset-link.ts +0 -77
  220. package/frontend/pages/studiocms_api/dashboard/create-user-invite.ts +0 -231
  221. package/frontend/pages/studiocms_api/dashboard/create-user.ts +0 -186
  222. package/frontend/pages/studiocms_api/dashboard/email-notification-settings-site.ts +0 -74
  223. package/frontend/pages/studiocms_api/dashboard/mailer/check-email.ts +0 -75
  224. package/frontend/pages/studiocms_api/dashboard/mailer/config.ts +0 -136
  225. package/frontend/pages/studiocms_api/dashboard/plugins/[plugin].ts +0 -80
  226. package/frontend/pages/studiocms_api/dashboard/profile.ts +0 -245
  227. package/frontend/pages/studiocms_api/dashboard/resend-verify-email.ts +0 -77
  228. package/frontend/pages/studiocms_api/dashboard/reset-password.ts +0 -124
  229. package/frontend/pages/studiocms_api/dashboard/search-list.ts +0 -59
  230. package/frontend/pages/studiocms_api/dashboard/taxonomy-search.ts +0 -47
  231. package/frontend/pages/studiocms_api/dashboard/taxonomy.ts +0 -230
  232. package/frontend/pages/studiocms_api/dashboard/templates.ts +0 -74
  233. package/frontend/pages/studiocms_api/dashboard/update-user-notifications.ts +0 -86
  234. package/frontend/pages/studiocms_api/dashboard/users.ts +0 -236
  235. package/frontend/pages/studiocms_api/dashboard/verify-email.ts +0 -83
  236. package/frontend/pages/studiocms_api/dashboard/verify-session.ts +0 -187
  237. package/frontend/pages/studiocms_api/integrations/[type]/[...id].ts +0 -15
  238. package/frontend/pages/studiocms_api/integrations/[type]/_routes/db-studio.ts +0 -173
  239. package/frontend/pages/studiocms_api/integrations/[type]/_routes/storage.ts +0 -88
  240. package/frontend/pages/studiocms_api/partials/editor.astro +0 -74
  241. package/frontend/pages/studiocms_api/partials/render.astro +0 -39
  242. package/frontend/pages/studiocms_api/partials/user-list-items.astro +0 -43
  243. package/frontend/pages/studiocms_api/rest/utils/auth-token.ts +0 -59
  244. package/frontend/pages/studiocms_api/rest/v1/[type]/[...id].ts +0 -23
  245. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/categories.ts +0 -267
  246. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/folders.ts +0 -283
  247. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/pages.ts +0 -505
  248. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/settings.ts +0 -100
  249. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/tags.ts +0 -229
  250. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/users.ts +0 -553
  251. package/frontend/pages/studiocms_api/rest/v1/public/[type]/[...id].ts +0 -19
  252. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/categories.ts +0 -74
  253. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/folders.ts +0 -85
  254. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/pages.ts +0 -103
  255. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/tags.ts +0 -67
  256. package/frontend/pages/studiocms_api/sdk/[...path].ts +0 -97
  257. package/frontend/pages/studiocms_api/sdk/utils/changelog.ts +0 -119
  258. package/frontend/scripts/auth/formListener.ts +0 -81
  259. package/frontend/scripts/formdata-utils.ts +0 -116
  260. package/frontend/utils/build-partial-schema.ts +0 -46
  261. package/frontend/utils/errors.ts +0 -6
  262. package/frontend/utils/param-extractor.ts +0 -83
  263. package/frontend/utils/rest-router.ts +0 -444
@@ -0,0 +1,80 @@
1
+ import { developerConfig } from 'studiocms:config';
2
+ import pluginList from 'studiocms:plugins';
3
+ import { settingsEndpoints } from 'studiocms:plugins/endpoints';
4
+ import routeConfig from 'virtual:studiocms/route-config';
5
+ import { HttpApiBuilder } from '@effect/platform';
6
+ import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
7
+ import { AstroAPIContext, CurrentUser } from '@withstudiocms/api-spec/astro-context';
8
+ import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
9
+ import { Effect, pipe } from 'effect';
10
+ import { webHandlerToEffectHttpHandler } from 'effectify/webHandler';
11
+
12
+ /**
13
+ * Check if the Dashboard API is enabled in the route configuration.
14
+ */
15
+ const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
16
+
17
+ /**
18
+ * Plugin Handlers for the Dashboard API
19
+ */
20
+ export const PluginHandlers = HttpApiBuilder.group(
21
+ StudioCMSDashboardApiSpec,
22
+ 'plugins',
23
+ (handlers) =>
24
+ handlers.handleRaw(
25
+ 'savePluginSettings',
26
+ Effect.fn(
27
+ function* ({ path: { plugin } }) {
28
+ if (!dashboardAPIEnabled) {
29
+ return yield* new DashboardAPIError({
30
+ error: 'Dashboard API is disabled',
31
+ });
32
+ }
33
+
34
+ if (developerConfig.demoMode !== false) {
35
+ return yield* new DashboardAPIError({
36
+ error: 'Demo mode is enabled, this action is not allowed.',
37
+ });
38
+ }
39
+
40
+ const [userData, ctx] = yield* Effect.all([CurrentUser, AstroAPIContext]);
41
+
42
+ if (!userData.isLoggedIn || !userData.userPermissionLevel.isAdmin) {
43
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
44
+ }
45
+
46
+ const settingsPage = yield* Effect.try({
47
+ try: () =>
48
+ pipe(
49
+ pluginList.filter(({ settingsPage }) => !!settingsPage),
50
+ (p) => p.find(({ identifier }) => identifier === plugin),
51
+ (p) => {
52
+ if (!p) {
53
+ throw new DashboardAPIError({ error: 'Plugin not found' });
54
+ }
55
+ const exists = settingsEndpoints.find(({ identifier }) => identifier === plugin);
56
+ if (!exists) {
57
+ throw new DashboardAPIError({ error: 'Plugin does not have a settings page' });
58
+ }
59
+ return exists;
60
+ }
61
+ ),
62
+ catch: (cause) =>
63
+ new DashboardAPIError({ error: (cause as Error).message || 'Internal Server Error' }),
64
+ });
65
+
66
+ if (!settingsPage.onSave) {
67
+ return new DashboardAPIError({ error: 'Plugin does not have a settings page' });
68
+ }
69
+
70
+ return yield* webHandlerToEffectHttpHandler(
71
+ settingsPage.onSave(ctx) as unknown as (request: Request) => Promise<Response>
72
+ );
73
+ },
74
+ Effect.catchTags({
75
+ 'effectify/webHandler.WebHandlerError': (cause) =>
76
+ new DashboardAPIError({ error: cause.message || 'Internal Server Error' }),
77
+ })
78
+ )
79
+ )
80
+ );
@@ -0,0 +1,275 @@
1
+ /** biome-ignore-all lint/style/noNonNullAssertion: This is fine */
2
+ import { Password, User } from 'studiocms:auth/lib';
3
+ import { developerConfig } from 'studiocms:config';
4
+ import { Notifications } from 'studiocms:notifier';
5
+ import { SDKCore } from 'studiocms:sdk';
6
+ import routeConfig from 'virtual:studiocms/route-config';
7
+ import { HttpApiBuilder } from '@effect/platform';
8
+ import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
9
+ import { CurrentUser } from '@withstudiocms/api-spec/astro-context';
10
+ import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
11
+ import { Effect } from 'effect';
12
+ import { isValidEmail } from '#schemas/external-schemas';
13
+ import { sharedDBErrors, sharedNotifierErrors } from './_shared.js';
14
+
15
+ /**
16
+ * Check if the Dashboard API is enabled in the route configuration.
17
+ */
18
+ const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
19
+
20
+ /**
21
+ * Profile Handlers for the Dashboard API
22
+ */
23
+ export const ProfileHandlers = HttpApiBuilder.group(
24
+ StudioCMSDashboardApiSpec,
25
+ 'profile',
26
+ (handlers) =>
27
+ handlers.handle(
28
+ 'updateUserProfile',
29
+ Effect.fn(
30
+ function* ({ payload }) {
31
+ if (!dashboardAPIEnabled) {
32
+ return yield* new DashboardAPIError({
33
+ error: 'Dashboard API is disabled',
34
+ });
35
+ }
36
+
37
+ if (developerConfig.demoMode !== false) {
38
+ return yield* new DashboardAPIError({
39
+ error: 'Demo mode is enabled, this action is not allowed.',
40
+ });
41
+ }
42
+
43
+ const [sdk, userData, notifier, pass, userHelper] = yield* Effect.all([
44
+ SDKCore,
45
+ CurrentUser,
46
+ Notifications,
47
+ Password,
48
+ User.pipe(
49
+ Effect.catchAll(() => new DashboardAPIError({ error: 'Failed to access user data' }))
50
+ ),
51
+ ]);
52
+
53
+ if (!userData.isLoggedIn || !userData.userPermissionLevel.isVisitor) {
54
+ return yield* new DashboardAPIError({ error: 'Unauthorized' });
55
+ }
56
+
57
+ switch (payload.mode) {
58
+ case 'basic': {
59
+ const data = payload.data;
60
+
61
+ if (!data.name || data.name.trim() === '') {
62
+ return yield* new DashboardAPIError({ error: 'Name cannot be empty' });
63
+ }
64
+
65
+ if (!data.email || data.email.trim() === '') {
66
+ return yield* new DashboardAPIError({ error: 'Email cannot be empty' });
67
+ }
68
+
69
+ if (!data.username || data.username.trim() === '') {
70
+ return yield* new DashboardAPIError({ error: 'Username cannot be empty' });
71
+ }
72
+
73
+ const verifyUsernameResponse = yield* userHelper
74
+ .verifyUsernameInput(data.username)
75
+ .pipe(
76
+ Effect.catchAll(
77
+ (err) =>
78
+ new DashboardAPIError({
79
+ error: `Failed to verify username: ${(err as Error).message}`,
80
+ })
81
+ )
82
+ );
83
+
84
+ if (verifyUsernameResponse !== true) {
85
+ return yield* new DashboardAPIError({ error: verifyUsernameResponse });
86
+ }
87
+
88
+ const checkEmail = isValidEmail(data.email);
89
+
90
+ if (!checkEmail.success) {
91
+ return yield* new DashboardAPIError({
92
+ error: `Invalid email address: ${checkEmail.error.message}`,
93
+ });
94
+ }
95
+
96
+ const { usernameSearch, emailSearch } =
97
+ yield* sdk.AUTH.user.searchUsersForUsernameOrEmail(data.username, checkEmail.data);
98
+
99
+ if (userData.user?.username !== data.username && usernameSearch.length > 0) {
100
+ return yield* new DashboardAPIError({ error: 'Username is already taken' });
101
+ }
102
+
103
+ if (userData.user?.email !== data.email && emailSearch.length > 0) {
104
+ return yield* new DashboardAPIError({ error: 'Email is already taken' });
105
+ }
106
+
107
+ yield* sdk.AUTH.user.update({
108
+ userId: userData.user!.id,
109
+ userData: { ...data, id: userData.user!.id },
110
+ });
111
+
112
+ return {
113
+ message: 'Profile updated successfully',
114
+ };
115
+ }
116
+ case 'password': {
117
+ const data = payload.data;
118
+
119
+ const { currentPassword, newPassword, confirmNewPassword } = data;
120
+
121
+ if (!currentPassword && userData.user?.password) {
122
+ return yield* new DashboardAPIError({ error: 'Current password is required' });
123
+ }
124
+
125
+ if (!newPassword) {
126
+ return yield* new DashboardAPIError({ error: 'New password is required' });
127
+ }
128
+
129
+ if (currentPassword && userData.user?.password) {
130
+ const isValid = yield* pass
131
+ .verifyPasswordHash(userData.user.password, currentPassword)
132
+ .pipe(
133
+ Effect.catchAll(
134
+ (err) =>
135
+ new DashboardAPIError({
136
+ error: `Failed to verify current password: ${(err as Error).message}`,
137
+ })
138
+ )
139
+ );
140
+ if (!isValid) {
141
+ return yield* new DashboardAPIError({ error: 'Current password is incorrect' });
142
+ }
143
+ }
144
+
145
+ if (!confirmNewPassword) {
146
+ return yield* new DashboardAPIError({ error: 'Please confirm the new password' });
147
+ }
148
+
149
+ if (newPassword !== confirmNewPassword) {
150
+ return yield* new DashboardAPIError({
151
+ error: 'New password and confirmation do not match',
152
+ });
153
+ }
154
+
155
+ const verifyPasswordResponse = yield* pass.verifyPasswordStrength(newPassword).pipe(
156
+ Effect.catchAll(
157
+ (err) =>
158
+ new DashboardAPIError({
159
+ error: `Failed to verify password strength: ${(err as Error).message}`,
160
+ })
161
+ )
162
+ );
163
+
164
+ if (verifyPasswordResponse !== true) {
165
+ return yield* new DashboardAPIError({ error: verifyPasswordResponse });
166
+ }
167
+
168
+ const userUpdate = {
169
+ password: yield* pass.hashPassword(newPassword).pipe(
170
+ Effect.catchAll(
171
+ (err) =>
172
+ new DashboardAPIError({
173
+ error: `Failed to hash new password: ${(err as Error).message}`,
174
+ })
175
+ )
176
+ ),
177
+ };
178
+
179
+ if (userData.user) {
180
+ yield* sdk.AUTH.user.update({
181
+ userId: userData.user.id,
182
+ userData: {
183
+ id: userData.user.id,
184
+ name: userData.user.name,
185
+ username: userData.user.username,
186
+ updatedAt: new Date().toISOString(),
187
+ createdAt: undefined,
188
+ emailVerified: userData.user.emailVerified,
189
+ ...userUpdate,
190
+ },
191
+ });
192
+
193
+ yield* Effect.all([
194
+ notifier.sendUserNotification('account_updated', userData.user.id),
195
+ notifier.sendAdminNotification('user_updated', userData.user.username),
196
+ ]).pipe(
197
+ Effect.catchAll(
198
+ (err) =>
199
+ new DashboardAPIError({
200
+ error: `Failed to send notifications: ${(err as Error).message}`,
201
+ })
202
+ )
203
+ );
204
+ }
205
+
206
+ return {
207
+ message: 'Password updated successfully',
208
+ };
209
+ }
210
+ case 'avatar': {
211
+ if (!userData.user?.email) {
212
+ return yield* new DashboardAPIError({
213
+ error: 'User email is required to generate avatar',
214
+ });
215
+ }
216
+
217
+ if (userData.user)
218
+ yield* userHelper.createUserAvatar(userData.user.email).pipe(
219
+ Effect.flatMap((newAvatar) =>
220
+ Effect.all([
221
+ sdk.AUTH.user.update({
222
+ userId: userData.user!.id,
223
+ userData: {
224
+ id: userData.user!.id,
225
+ name: userData.user!.name,
226
+ username: userData.user!.username,
227
+ updatedAt: new Date().toISOString(),
228
+ emailVerified: userData.user!.emailVerified,
229
+ createdAt: undefined,
230
+ avatar: newAvatar,
231
+ },
232
+ }),
233
+ notifier.sendUserNotification('account_updated', userData.user!.id).pipe(
234
+ Effect.catchAll(
235
+ (err) =>
236
+ new DashboardAPIError({
237
+ error: `Failed to send user notification: ${(err as Error).message}`,
238
+ })
239
+ )
240
+ ),
241
+ notifier.sendAdminNotification('user_updated', userData.user!.username).pipe(
242
+ Effect.catchAll(
243
+ (err) =>
244
+ new DashboardAPIError({
245
+ error: `Failed to send admin notification: ${(err as Error).message}`,
246
+ })
247
+ )
248
+ ),
249
+ ])
250
+ ),
251
+ Effect.catchAll(
252
+ (err) =>
253
+ new DashboardAPIError({
254
+ error: `Failed to create new avatar: ${(err as Error).message}`,
255
+ })
256
+ )
257
+ );
258
+
259
+ return {
260
+ message: 'Avatar updated successfully',
261
+ };
262
+ }
263
+ default: {
264
+ return yield* new DashboardAPIError({ error: 'Invalid update mode' });
265
+ }
266
+ }
267
+ },
268
+ Notifications.Provide,
269
+ Effect.catchTags({
270
+ ...sharedDBErrors,
271
+ ...sharedNotifierErrors,
272
+ })
273
+ )
274
+ )
275
+ );
@@ -0,0 +1,140 @@
1
+ import { Password } from 'studiocms:auth/lib';
2
+ import { developerConfig } from 'studiocms:config';
3
+ import { Notifications } from 'studiocms:notifier';
4
+ import { SDKCore } from 'studiocms:sdk';
5
+ import routeConfig from 'virtual:studiocms/route-config';
6
+ import { HttpApiBuilder } from '@effect/platform';
7
+ import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
8
+ import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
9
+ import { Effect } from 'effect';
10
+ import { sharedDBErrors, sharedNotifierErrors } from './_shared.js';
11
+
12
+ /**
13
+ * Check if the Dashboard API is enabled in the route configuration.
14
+ */
15
+ const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
16
+
17
+ /**
18
+ * Reset Password Handlers for the Dashboard API
19
+ */
20
+ export const ResetPasswordHandlers = HttpApiBuilder.group(
21
+ StudioCMSDashboardApiSpec,
22
+ 'resetPassword',
23
+ (handlers) =>
24
+ handlers.handle(
25
+ 'resetPassword',
26
+ Effect.fn(
27
+ function* ({ payload }) {
28
+ if (!dashboardAPIEnabled) {
29
+ return yield* new DashboardAPIError({
30
+ error: 'Dashboard API is disabled',
31
+ });
32
+ }
33
+
34
+ if (developerConfig.demoMode !== false) {
35
+ return yield* new DashboardAPIError({
36
+ error: 'Demo mode is enabled, this action is not allowed.',
37
+ });
38
+ }
39
+
40
+ const { token, password, confirm_password } = payload;
41
+
42
+ if (password !== confirm_password) {
43
+ return yield* new DashboardAPIError({
44
+ error: 'Passwords do not match',
45
+ });
46
+ }
47
+
48
+ const [sdk, notifier, pass] = yield* Effect.all([SDKCore, Notifications, Password]);
49
+
50
+ const verifyPasswordResponse = yield* pass.verifyPasswordStrength(password).pipe(
51
+ Effect.catchAll((err) => {
52
+ return new DashboardAPIError({
53
+ error: `Password does not meet strength requirements: ${err.message}`,
54
+ });
55
+ })
56
+ );
57
+
58
+ if (verifyPasswordResponse !== true) {
59
+ return yield* new DashboardAPIError({
60
+ error: verifyPasswordResponse,
61
+ });
62
+ }
63
+
64
+ const hashedPassword = yield* pass.hashPassword(password).pipe(
65
+ Effect.catchAll(() => {
66
+ return new DashboardAPIError({
67
+ error: 'Failed to hash password',
68
+ });
69
+ })
70
+ );
71
+
72
+ const isTokenValid = yield* sdk.resetTokenBucket.check(token);
73
+
74
+ if (!isTokenValid) {
75
+ return yield* new DashboardAPIError({
76
+ error: 'Invalid or expired reset token',
77
+ });
78
+ }
79
+
80
+ const tokenInfo = yield* sdk.UTIL.Generators.testToken(token);
81
+
82
+ if (!tokenInfo || !tokenInfo.userId) {
83
+ return yield* new DashboardAPIError({
84
+ error: 'Invalid or expired reset token',
85
+ });
86
+ }
87
+
88
+ const targetUserId = tokenInfo.userId as string;
89
+
90
+ const userUpdate = {
91
+ password: hashedPassword,
92
+ };
93
+
94
+ const userData = yield* sdk.GET.users.byId(targetUserId);
95
+
96
+ if (!userData) {
97
+ return yield* new DashboardAPIError({
98
+ error: 'User not found',
99
+ });
100
+ }
101
+
102
+ yield* sdk.AUTH.user.update({
103
+ userId: targetUserId,
104
+ userData: {
105
+ id: targetUserId,
106
+ name: userData.name,
107
+ username: userData.username,
108
+ updatedAt: new Date().toISOString(),
109
+ createdAt: undefined,
110
+ emailVerified: userData.emailVerified,
111
+ ...userUpdate,
112
+ },
113
+ });
114
+
115
+ yield* sdk.resetTokenBucket.delete(targetUserId);
116
+
117
+ yield* Effect.all([
118
+ notifier.sendUserNotification('account_updated', targetUserId),
119
+ notifier.sendAdminNotification('user_updated', userData.username),
120
+ ]).pipe(
121
+ Effect.catchAll(() => {
122
+ return new DashboardAPIError({
123
+ error: 'Failed to send notifications',
124
+ });
125
+ })
126
+ );
127
+
128
+ return {
129
+ message: 'Password updated successfully',
130
+ };
131
+ },
132
+ Notifications.Provide,
133
+ Effect.catchTags({
134
+ ...sharedDBErrors,
135
+ ...sharedNotifierErrors,
136
+ GeneratorError: () => new DashboardAPIError({ error: 'Invalid or expired reset token' }),
137
+ })
138
+ )
139
+ )
140
+ );
@@ -0,0 +1,63 @@
1
+ import { SDKCore } from 'studiocms:sdk';
2
+ import routeConfig from 'virtual:studiocms/route-config';
3
+ import { HttpApiBuilder } from '@effect/platform';
4
+ import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
5
+ import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
6
+ import { Effect } from 'effect';
7
+ import { sharedDBErrors, sharedPageCollectionErrors } from './_shared.js';
8
+
9
+ /**
10
+ * Check if the Dashboard API is enabled in the route configuration.
11
+ */
12
+ const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
13
+
14
+ /**
15
+ * Search Item type for the searchList handler response.
16
+ */
17
+ type SearchItem = {
18
+ id: string;
19
+ name: string;
20
+ slug?: string;
21
+ type: 'folder' | 'page';
22
+ isDraft?: boolean | null;
23
+ };
24
+
25
+ /**
26
+ * Search Handlers for the Dashboard API
27
+ */
28
+ export const SearchHandlers = HttpApiBuilder.group(
29
+ StudioCMSDashboardApiSpec,
30
+ 'search',
31
+ (handlers) =>
32
+ handlers.handle('searchList', () =>
33
+ !dashboardAPIEnabled
34
+ ? Effect.fail(new DashboardAPIError({ error: 'Dashboard API is disabled' }))
35
+ : SDKCore.pipe(
36
+ Effect.flatMap((sdk) =>
37
+ Effect.all([
38
+ sdk.GET.folderList().pipe(
39
+ Effect.map((f) => f.map(({ id, name }) => ({ id, name, type: 'folder' })))
40
+ ),
41
+ sdk.GET.pages(true).pipe(
42
+ Effect.map((res) =>
43
+ res.map(({ id, title, slug, draft }) => ({
44
+ id,
45
+ name: title,
46
+ slug,
47
+ isDraft: draft,
48
+ type: 'page',
49
+ }))
50
+ )
51
+ ),
52
+ ])
53
+ ),
54
+ Effect.flatMap(([folders, pages]) =>
55
+ Effect.succeed([...folders, ...pages] as SearchItem[])
56
+ ),
57
+ Effect.catchTags({
58
+ ...sharedDBErrors,
59
+ ...sharedPageCollectionErrors,
60
+ })
61
+ )
62
+ )
63
+ );