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.
- package/CHANGELOG.md +122 -0
- package/dist/cli/add/index.d.ts +2 -2
- package/dist/cli/add/index.js +4 -3
- package/dist/cli/add/npm-utils.d.ts +6 -6
- package/dist/cli/add/tryToInstallPlugins.d.ts +1 -1
- package/dist/cli/add/tryToInstallPlugins.js +6 -5
- package/dist/cli/add/updateStudioCMSConfig.d.ts +1 -1
- package/dist/cli/add/updateStudioCMSConfig.js +3 -4
- package/dist/cli/add/validatePlugins.d.ts +1 -2
- package/dist/cli/add/validatePlugins.js +15 -9
- package/dist/cli/crypto/genJWT/index.d.ts +1 -1
- package/dist/cli/crypto/genJWT/index.js +8 -9
- package/dist/cli/crypto/index.d.ts +1 -1
- package/dist/cli/init/steps/env.js +14 -4
- package/dist/cli/init/steps/next.d.ts +1 -1
- package/dist/cli/init/steps/next.js +6 -5
- package/dist/cli/migrator/index.d.ts +1 -1
- package/dist/cli/migrator/index.js +2 -2
- package/dist/cli/users/index.d.ts +1 -1
- package/dist/cli/users/shared.js +2 -2
- package/dist/cli/users/steps/createUsers.js +7 -7
- package/dist/cli/users/steps/modifyUsers.js +2 -2
- package/dist/cli/users/steps/next.d.ts +1 -1
- package/dist/cli/utils/checkRequiredEnvVars.js +2 -2
- package/dist/cli/utils/context.d.ts +2 -4
- package/dist/cli/utils/context.js +1 -3
- package/dist/cli/utils/getCliDbClient.d.ts +1 -1
- package/dist/cli/utils/intro.d.ts +1 -1
- package/dist/cli/utils/loadConfig.d.ts +54 -49
- package/dist/cli/utils/loadConfig.js +5 -8
- package/dist/cli/utils/logger.js +3 -3
- package/dist/cli/utils/user-utils.d.ts +1 -1
- package/dist/cli/utils/user-utils.js +4 -3
- package/dist/client/apiClient.d.ts +4923 -0
- package/dist/client/apiClient.js +72 -0
- package/dist/config.d.ts +1734 -1
- package/dist/consts.d.ts +5 -5
- package/dist/consts.js +3 -2
- package/dist/db/plugins.d.ts +1 -1
- package/dist/db/plugins.js +5 -8
- package/dist/handlers/frontend/routes.d.ts +4 -18
- package/dist/handlers/frontend/routes.js +13 -152
- package/dist/handlers/frontend/types.d.ts +1 -1
- package/dist/handlers/frontend/utils.js +0 -18
- package/dist/handlers/pluginHandler.d.ts +34 -257
- package/dist/handlers/pluginHandler.js +92 -46
- package/dist/handlers/routeHandler.js +32 -11
- package/dist/handlers/setupDbStudio.d.ts +3 -1
- package/dist/handlers/setupDbStudio.js +19 -10
- package/dist/handlers/storage-manager/core/effectify-astro-context.d.ts +25 -0
- package/dist/handlers/storage-manager/core/effectify-astro-context.js +78 -0
- package/dist/handlers/storage-manager/no-op.d.ts +2 -2
- package/dist/handlers/storage-manager/no-op.js +2 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js +10 -20
- package/dist/integrations/robots/index.d.ts +2 -2
- package/dist/integrations/robots/index.js +1 -3
- package/dist/integrations/robots/schema.d.ts +102 -273
- package/dist/integrations/robots/schema.js +220 -209
- package/dist/plugins/analytics/assets/schemas.d.ts +14 -9
- package/dist/plugins/analytics/assets/schemas.js +25 -17
- package/dist/plugins/analytics/db-client.d.ts +1 -1
- package/dist/plugins/analytics/index.d.ts +823 -3
- package/dist/plugins/analytics/index.js +4 -5
- package/dist/plugins/analytics/schemas.d.ts +54 -62
- package/dist/plugins/analytics/schemas.js +64 -13
- package/dist/plugins/analytics/table.d.ts +1 -1
- package/dist/plugins.d.ts +0 -1
- package/dist/schemas/config/api.d.ts +17 -0
- package/dist/schemas/config/api.js +14 -0
- package/dist/schemas/config/auth.d.ts +55 -59
- package/dist/schemas/config/auth.js +34 -11
- package/dist/schemas/config/dashboard.d.ts +43 -79
- package/dist/schemas/config/dashboard.js +43 -12
- package/dist/schemas/config/db.d.ts +15 -17
- package/dist/schemas/config/db.js +13 -5
- package/dist/schemas/config/developer.d.ts +33 -45
- package/dist/schemas/config/developer.js +22 -5
- package/dist/schemas/config/index.d.ts +398 -521
- package/dist/schemas/config/index.js +115 -57
- package/dist/schemas/config/sdk.d.ts +50 -196
- package/dist/schemas/config/sdk.js +61 -73
- package/dist/schemas/custom.d.ts +40 -0
- package/dist/schemas/custom.js +41 -0
- package/dist/schemas/external-schemas.d.ts +171 -0
- package/dist/schemas/external-schemas.js +179 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/plugins/i18n.d.ts +59 -39
- package/dist/schemas/plugins/i18n.js +42 -5
- package/dist/schemas/plugins/index.d.ts +7126 -10296
- package/dist/schemas/plugins/index.js +260 -276
- package/dist/schemas/plugins/shared.d.ts +1293 -3718
- package/dist/schemas/plugins/shared.js +320 -329
- package/dist/test-utils.d.ts +15 -4
- package/dist/test-utils.js +27 -11
- package/dist/toolbar/db-viewer/db-shared-types.d.ts +6 -6
- package/dist/toolbar/db-viewer/studio/connection.d.ts +8 -4
- package/dist/toolbar/db-viewer/studio/connection.js +2 -28
- package/dist/toolbar/db-viewer/studio/env/libsql.d.ts +7 -0
- package/dist/toolbar/db-viewer/studio/env/libsql.js +17 -0
- package/dist/toolbar/db-viewer/studio/env/mysql.d.ts +7 -0
- package/dist/toolbar/db-viewer/studio/env/mysql.js +23 -0
- package/dist/toolbar/db-viewer/studio/env/postgres.d.ts +7 -0
- package/dist/toolbar/db-viewer/studio/env/postgres.js +23 -0
- package/dist/toolbar/db-viewer/studio/index.js +20 -56
- package/dist/toolbar/db-viewer/studio/type.d.ts +1 -2
- package/dist/toolbar/db-viewer/studio/virtual-connection/libsql.d.ts +3 -0
- package/dist/toolbar/db-viewer/studio/virtual-connection/libsql.js +24 -0
- package/dist/toolbar/db-viewer/studio/virtual-connection/mysql.d.ts +3 -0
- package/dist/toolbar/db-viewer/studio/virtual-connection/mysql.js +9 -0
- package/dist/toolbar/db-viewer/studio/virtual-connection/postgres.d.ts +3 -0
- package/dist/toolbar/db-viewer/studio/virtual-connection/postgres.js +9 -0
- package/dist/toolbar/db-viewer/viewer.js +20 -21
- package/dist/types.d.ts +30 -0
- package/dist/utils/effects/smtp.d.ts +1 -1
- package/dist/utils/lang-helper.d.ts +10 -2
- package/dist/virtual.d.ts +35 -28
- package/dist/virtuals/auth/core.d.ts +5 -5
- package/dist/virtuals/auth/verify-email.d.ts +6 -6
- package/dist/virtuals/components/Generator.astro +2 -2
- package/dist/virtuals/components/Renderer.astro +9 -1
- package/dist/virtuals/components/renderFn.d.ts +3 -1
- package/dist/virtuals/components/renderFn.js +18 -0
- package/dist/virtuals/lib/headDefaults.d.ts +4 -2
- package/dist/virtuals/lib/headDefaults.js +0 -2
- package/dist/virtuals/lib/routeMap.d.ts +0 -12
- package/dist/virtuals/lib/routeMap.js +2 -14
- package/dist/virtuals/mailer/index.d.ts +3 -3
- package/dist/virtuals/notifier/index.d.ts +5 -5
- package/dist/virtuals/plugins/dashboard-pages.d.ts +2 -64
- package/dist/virtuals/scripts/StorageFileBrowser.d.ts +1 -172
- package/dist/virtuals/scripts/StorageFileBrowser.js +216 -119
- package/dist/virtuals/template-engine/index.d.ts +4 -4
- package/frontend/components/dashboard/configuration/ConfigForm.astro +218 -110
- package/frontend/components/dashboard/content-mgmt/ContentSearch.astro +21 -22
- package/frontend/components/dashboard/content-mgmt/CreateFolder.astro +66 -54
- package/frontend/components/dashboard/content-mgmt/CreatePage.astro +58 -104
- package/frontend/components/dashboard/content-mgmt/EditFolder.astro +65 -67
- package/frontend/components/dashboard/content-mgmt/EditPage.astro +86 -134
- package/frontend/components/dashboard/content-mgmt/InnerSidebarElement.astro +0 -1
- package/frontend/components/dashboard/content-mgmt/PageHeader.astro +33 -52
- package/frontend/components/dashboard/content-mgmt/PageTypeHandler.astro +2 -2
- package/frontend/components/dashboard/profile/APITokens.astro +219 -158
- package/frontend/components/dashboard/profile/BasicInfo.astro +165 -106
- package/frontend/components/dashboard/profile/Notifications.astro +27 -18
- package/frontend/components/dashboard/profile/UpdatePassword.astro +134 -94
- package/frontend/components/dashboard/sidebar/VersionCheck.astro +31 -16
- package/frontend/components/dashboard/sidebar/VersionCheckChangelog.astro +18 -11
- package/frontend/components/dashboard/sidebar-modals/VersionModal.astro +2 -2
- package/frontend/components/dashboard/smtp-config/TemplateEditor.astro +14 -14
- package/frontend/components/dashboard/taxonomy/InnerSidebarElement.astro +0 -1
- package/frontend/components/dashboard/taxonomy/MetaContainer.astro +0 -2
- package/frontend/components/dashboard/taxonomy/PageHeader.astro +16 -24
- package/frontend/components/dashboard/taxonomy/TaxonomySearch.astro +23 -27
- package/frontend/components/dashboard/user-mgmt/InnerSidebarElement.astro +111 -104
- package/frontend/components/dashboard/user-mgmt/UserListItem.astro +9 -22
- package/frontend/components/dashboard/user-mgmt/UserListItems.astro +18 -0
- package/frontend/components/first-time-setup/snippets/{opt2-studiocms.config.diff → studiocms.config.diff} +1 -0
- package/frontend/components/shared/Code.astro +1 -4
- package/frontend/components/shared/DynamicSettingsRenderer.astro +1 -1
- package/frontend/components/shared/SSRUser.astro +2 -4
- package/frontend/components/shared/foldertree/FolderTreeNode.astro +0 -6
- package/frontend/components/shared/storage-manager/StorageCopyOutput.astro +0 -1
- package/frontend/components/shared/taxonomy/TaxonomyTreeNode.astro +0 -6
- package/frontend/layouts/DashboardLayout.astro +1 -10
- package/frontend/layouts/TaxonomyLayout.astro +0 -1
- package/frontend/middleware/index.ts +102 -61
- package/frontend/pages/404.astro +5 -9
- package/frontend/pages/[dashboard]/[...pluginPage].astro +10 -1
- package/frontend/pages/[dashboard]/configuration.astro +10 -1
- package/frontend/pages/[dashboard]/content-management/createfolder.astro +10 -1
- package/frontend/pages/[dashboard]/content-management/createpage.astro +10 -1
- package/frontend/pages/[dashboard]/content-management/diff.astro +39 -14
- package/frontend/pages/[dashboard]/content-management/editfolder.astro +10 -1
- package/frontend/pages/[dashboard]/content-management/editpage.astro +10 -1
- package/frontend/pages/[dashboard]/content-management/index.astro +10 -1
- package/frontend/pages/[dashboard]/index.astro +10 -1
- package/frontend/pages/[dashboard]/login.astro +86 -25
- package/frontend/pages/[dashboard]/password-reset.astro +22 -16
- package/frontend/pages/[dashboard]/plugins/[plugin].astro +10 -1
- package/frontend/pages/[dashboard]/profile.astro +10 -1
- package/frontend/pages/[dashboard]/signup.astro +153 -52
- package/frontend/pages/[dashboard]/smtp-configuration.astro +77 -75
- package/frontend/pages/[dashboard]/system-management.astro +10 -1
- package/frontend/pages/[dashboard]/taxonomy/categories.astro +30 -41
- package/frontend/pages/[dashboard]/taxonomy/index.astro +10 -0
- package/frontend/pages/[dashboard]/taxonomy/tags.astro +33 -43
- package/frontend/pages/[dashboard]/unverified-email.astro +29 -21
- package/frontend/pages/[dashboard]/user-management/edit.astro +170 -90
- package/frontend/pages/[dashboard]/user-management/index.astro +10 -1
- package/frontend/pages/studiocms_api/[...all].ts +106 -0
- package/frontend/pages/studiocms_api/_handlers/_utils/auth.ts +26 -0
- package/frontend/pages/studiocms_api/_handlers/_utils/changelog.ts +147 -0
- package/frontend/pages/studiocms_api/_handlers/_utils/db-studio-driver.ts +46 -0
- package/frontend/pages/studiocms_api/_handlers/_utils/parseLogLevel.ts +27 -0
- package/frontend/pages/studiocms_api/_handlers/auth/auth.ts +459 -0
- package/frontend/pages/studiocms_api/_handlers/auth/index.ts +17 -0
- package/frontend/pages/studiocms_api/_handlers/auth/oauth.ts +91 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/_shared.ts +57 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/apiTokens.ts +134 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/config.ts +64 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/content.ts +741 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/create.ts +480 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/emailNotifications.ts +49 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/index.ts +45 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/mailer.ts +136 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/plugins.ts +80 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/profile.ts +275 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/resetPassword.ts +140 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/search.ts +63 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/taxonomy.ts +285 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/templates.ts +75 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/users.ts +312 -0
- package/frontend/pages/studiocms_api/_handlers/dashboard/verifyEndpoints.ts +307 -0
- package/frontend/pages/studiocms_api/_handlers/integration/dbStudio.ts +98 -0
- package/frontend/pages/studiocms_api/_handlers/integration/index.ts +17 -0
- package/frontend/pages/studiocms_api/_handlers/integration/storageManager.ts +107 -0
- package/frontend/pages/studiocms_api/_handlers/rest-api/index.ts +8 -0
- package/frontend/pages/studiocms_api/_handlers/rest-api/v1/_shared.ts +41 -0
- package/frontend/pages/studiocms_api/_handlers/rest-api/v1/index.ts +17 -0
- package/frontend/pages/studiocms_api/_handlers/rest-api/v1/public.ts +195 -0
- package/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts +1726 -0
- package/frontend/pages/studiocms_api/_handlers/sdk.ts +129 -0
- package/frontend/pages/studiocms_api/_middleware/astroLocals.ts +36 -0
- package/frontend/pages/studiocms_api/_middleware/restApi.ts +56 -0
- package/frontend/pages/studiocms_api/integrations/[...all].ts +8 -0
- package/frontend/scripts/formdata.ts +219 -0
- package/frontend/setup-pages/3-done.astro +4 -20
- package/frontend/setup-pages/studiocms_api/dashboard/step-2.ts +3 -6
- package/frontend/styles/dashboard-base.css +0 -1
- package/frontend/web-vitals/endpoint.ts +2 -1
- package/package.json +35 -31
- package/dist/global.d.ts +0 -9
- package/frontend/components/dashboard/LoginChecker.astro +0 -68
- package/frontend/components/dashboard/user-mgmt/RankCheck.astro +0 -57
- package/frontend/components/first-time-setup/snippets/opt1-astro.config.diff +0 -14
- package/frontend/components/first-time-setup/snippets/opt2-astro.config.diff +0 -9
- package/frontend/middleware/_authmap.ts +0 -63
- package/frontend/pages/studiocms_api/auth/[path].ts +0 -390
- package/frontend/pages/studiocms_api/auth/[provider]/[...id].ts +0 -64
- package/frontend/pages/studiocms_api/auth/[provider]/_effects/index.ts +0 -101
- package/frontend/pages/studiocms_api/auth/_shared.ts +0 -52
- package/frontend/pages/studiocms_api/dashboard/api-tokens.ts +0 -115
- package/frontend/pages/studiocms_api/dashboard/config.ts +0 -74
- package/frontend/pages/studiocms_api/dashboard/content/diff.ts +0 -73
- package/frontend/pages/studiocms_api/dashboard/content/folder.ts +0 -220
- package/frontend/pages/studiocms_api/dashboard/content/page.ts +0 -359
- package/frontend/pages/studiocms_api/dashboard/create-reset-link.ts +0 -77
- package/frontend/pages/studiocms_api/dashboard/create-user-invite.ts +0 -231
- package/frontend/pages/studiocms_api/dashboard/create-user.ts +0 -186
- package/frontend/pages/studiocms_api/dashboard/email-notification-settings-site.ts +0 -74
- package/frontend/pages/studiocms_api/dashboard/mailer/check-email.ts +0 -75
- package/frontend/pages/studiocms_api/dashboard/mailer/config.ts +0 -136
- package/frontend/pages/studiocms_api/dashboard/plugins/[plugin].ts +0 -80
- package/frontend/pages/studiocms_api/dashboard/profile.ts +0 -245
- package/frontend/pages/studiocms_api/dashboard/resend-verify-email.ts +0 -77
- package/frontend/pages/studiocms_api/dashboard/reset-password.ts +0 -124
- package/frontend/pages/studiocms_api/dashboard/search-list.ts +0 -59
- package/frontend/pages/studiocms_api/dashboard/taxonomy-search.ts +0 -47
- package/frontend/pages/studiocms_api/dashboard/taxonomy.ts +0 -230
- package/frontend/pages/studiocms_api/dashboard/templates.ts +0 -74
- package/frontend/pages/studiocms_api/dashboard/update-user-notifications.ts +0 -86
- package/frontend/pages/studiocms_api/dashboard/users.ts +0 -236
- package/frontend/pages/studiocms_api/dashboard/verify-email.ts +0 -83
- package/frontend/pages/studiocms_api/dashboard/verify-session.ts +0 -187
- package/frontend/pages/studiocms_api/integrations/[type]/[...id].ts +0 -15
- package/frontend/pages/studiocms_api/integrations/[type]/_routes/db-studio.ts +0 -173
- package/frontend/pages/studiocms_api/integrations/[type]/_routes/storage.ts +0 -88
- package/frontend/pages/studiocms_api/partials/editor.astro +0 -74
- package/frontend/pages/studiocms_api/partials/render.astro +0 -39
- package/frontend/pages/studiocms_api/partials/user-list-items.astro +0 -43
- package/frontend/pages/studiocms_api/rest/utils/auth-token.ts +0 -59
- package/frontend/pages/studiocms_api/rest/v1/[type]/[...id].ts +0 -23
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/categories.ts +0 -267
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/folders.ts +0 -283
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/pages.ts +0 -505
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/settings.ts +0 -100
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/tags.ts +0 -229
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/users.ts +0 -553
- package/frontend/pages/studiocms_api/rest/v1/public/[type]/[...id].ts +0 -19
- package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/categories.ts +0 -74
- package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/folders.ts +0 -85
- package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/pages.ts +0 -103
- package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/tags.ts +0 -67
- package/frontend/pages/studiocms_api/sdk/[...path].ts +0 -97
- package/frontend/pages/studiocms_api/sdk/utils/changelog.ts +0 -119
- package/frontend/scripts/auth/formListener.ts +0 -81
- package/frontend/scripts/formdata-utils.ts +0 -116
- package/frontend/utils/build-partial-schema.ts +0 -46
- package/frontend/utils/errors.ts +0 -6
- package/frontend/utils/param-extractor.ts +0 -83
- package/frontend/utils/rest-router.ts +0 -444
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { Password, User } from 'studiocms:auth/lib';
|
|
2
|
+
import { developerConfig } from 'studiocms:config';
|
|
3
|
+
import { Mailer } from 'studiocms:mailer';
|
|
4
|
+
import { Notifications } from 'studiocms:notifier';
|
|
5
|
+
import { SDKCore } from 'studiocms:sdk';
|
|
6
|
+
import templateEngine from 'studiocms:template-engine';
|
|
7
|
+
import routeConfig from 'virtual:studiocms/route-config';
|
|
8
|
+
import { HttpApiBuilder } from '@effect/platform';
|
|
9
|
+
import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
|
|
10
|
+
import { AstroAPIContext, CurrentUser } from '@withstudiocms/api-spec/astro-context';
|
|
11
|
+
import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
|
|
12
|
+
import { availablePermissionRanks } from '@withstudiocms/auth-kit/types';
|
|
13
|
+
import { appendSearchParamsToUrl } from '@withstudiocms/effect/effect';
|
|
14
|
+
import type { APIContext } from 'astro';
|
|
15
|
+
import { Effect, pipe } from 'effect';
|
|
16
|
+
import { ValidRanks } from '#consts';
|
|
17
|
+
import { isValidEmail } from '#schemas';
|
|
18
|
+
import { sharedDBErrors, sharedNotifierErrors } from './_shared.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if the Dashboard API is enabled in the route configuration.
|
|
22
|
+
*/
|
|
23
|
+
const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Type definition for the token object returned when creating a password reset link. This includes the token ID, the user ID it is associated with, and the token string itself. This type is used to ensure that the correct data structure is returned and handled when generating password reset links for users.
|
|
27
|
+
*/
|
|
28
|
+
type Token = {
|
|
29
|
+
id: string;
|
|
30
|
+
userId: string;
|
|
31
|
+
token: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generates a password reset URL for a user based on the provided token information. This function constructs a URL that includes the necessary query parameters for resetting a user's password, such as the user ID, token, and token ID. The URL is built using the base URL of the application and the specific route for password resets in the dashboard. This allows users to securely reset their passwords by following the generated link.
|
|
36
|
+
*/
|
|
37
|
+
const generateResetUrl = (
|
|
38
|
+
{
|
|
39
|
+
locals: {
|
|
40
|
+
StudioCMS: {
|
|
41
|
+
routeMap: {
|
|
42
|
+
mainLinks: { dashboardIndex },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}: APIContext,
|
|
47
|
+
baseUrl: string,
|
|
48
|
+
{ id, userId, token }: Token
|
|
49
|
+
) => {
|
|
50
|
+
const resetURL = new URL(`${dashboardIndex}/password-reset`, baseUrl);
|
|
51
|
+
return pipe(
|
|
52
|
+
resetURL,
|
|
53
|
+
appendSearchParamsToUrl('userid', userId),
|
|
54
|
+
appendSearchParamsToUrl('token', token),
|
|
55
|
+
appendSearchParamsToUrl('id', id)
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create Handlers for the Dashboard API - This group of handlers includes endpoints for creating resources in the dashboard, such as users and password reset links. 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 user is created or when a user's password is updated.
|
|
61
|
+
*/
|
|
62
|
+
export const CreateHandlers = HttpApiBuilder.group(
|
|
63
|
+
StudioCMSDashboardApiSpec,
|
|
64
|
+
'create',
|
|
65
|
+
(handlers) =>
|
|
66
|
+
handlers
|
|
67
|
+
.handle(
|
|
68
|
+
'createPasswordResetLink',
|
|
69
|
+
Effect.fn(
|
|
70
|
+
function* ({ payload: { userId } }) {
|
|
71
|
+
if (!dashboardAPIEnabled) {
|
|
72
|
+
return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (developerConfig.demoMode !== false) {
|
|
76
|
+
return yield* new DashboardAPIError({
|
|
77
|
+
error: 'Demo mode is enabled, this action is not allowed.',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const [sdk, userData, notifier] = yield* Effect.all([
|
|
82
|
+
SDKCore,
|
|
83
|
+
CurrentUser,
|
|
84
|
+
Notifications,
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const isAuthorized = userData.userPermissionLevel.isAdmin;
|
|
88
|
+
|
|
89
|
+
if (!userData.isLoggedIn || !isAuthorized) {
|
|
90
|
+
return yield* new DashboardAPIError({ error: 'Unauthorized' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const [token, user] = yield* Effect.all([
|
|
94
|
+
sdk.resetTokenBucket.new(userId),
|
|
95
|
+
sdk.GET.users.byId(userId),
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
if (!token || !user) {
|
|
99
|
+
return yield* new DashboardAPIError({ error: 'User not found' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
yield* notifier
|
|
103
|
+
.sendAdminNotification('user_updated', user.username)
|
|
104
|
+
.pipe(
|
|
105
|
+
Effect.catchAll(
|
|
106
|
+
() => new DashboardAPIError({ error: 'Failed to send notification' })
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return token;
|
|
111
|
+
},
|
|
112
|
+
Notifications.Provide,
|
|
113
|
+
Effect.catchTags({
|
|
114
|
+
...sharedDBErrors,
|
|
115
|
+
...sharedNotifierErrors,
|
|
116
|
+
GeneratorError: () => new DashboardAPIError({ error: 'Internal Server Error' }),
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
.handle(
|
|
121
|
+
'createUser',
|
|
122
|
+
Effect.fn(
|
|
123
|
+
function* ({ payload }) {
|
|
124
|
+
if (!dashboardAPIEnabled) {
|
|
125
|
+
return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (developerConfig.demoMode !== false) {
|
|
129
|
+
return yield* new DashboardAPIError({
|
|
130
|
+
error: 'Demo mode is enabled, this action is not allowed.',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const [pass, userHelper, sdk, userData, notifier] = yield* Effect.all([
|
|
135
|
+
Password,
|
|
136
|
+
User.pipe(
|
|
137
|
+
Effect.catchAll(
|
|
138
|
+
() => new DashboardAPIError({ error: 'Failed to access User module' })
|
|
139
|
+
)
|
|
140
|
+
),
|
|
141
|
+
SDKCore,
|
|
142
|
+
CurrentUser,
|
|
143
|
+
Notifications,
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const isAuthorized = userData.userPermissionLevel.isAdmin;
|
|
147
|
+
|
|
148
|
+
if (!userData.isLoggedIn || !isAuthorized) {
|
|
149
|
+
return yield* new DashboardAPIError({ error: 'Unauthorized' });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let { username, password, email, displayname, rank } = payload;
|
|
153
|
+
|
|
154
|
+
if (!username) {
|
|
155
|
+
return yield* new DashboardAPIError({ error: 'Missing field: Username is required' });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!password) {
|
|
159
|
+
password = yield* sdk.UTIL.Generators.generateRandomPassword(12);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!email) {
|
|
163
|
+
return yield* new DashboardAPIError({ error: 'Missing field: Email is required' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!displayname) {
|
|
167
|
+
return yield* new DashboardAPIError({
|
|
168
|
+
error: 'Missing field: Display name is required',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!rank) {
|
|
173
|
+
return yield* new DashboardAPIError({ error: 'Missing field: Rank is required' });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!ValidRanks.has(rank) || rank === 'unknown') {
|
|
177
|
+
return yield* new DashboardAPIError({ error: 'Invalid rank' });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const callerPerm = availablePermissionRanks.indexOf(userData.permissionLevel);
|
|
181
|
+
const targetPerm = availablePermissionRanks.indexOf(rank);
|
|
182
|
+
|
|
183
|
+
if (targetPerm >= callerPerm) {
|
|
184
|
+
return yield* new DashboardAPIError({
|
|
185
|
+
error: 'Unauthorized: insufficient permissions to assign target rank',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const checkEmail = isValidEmail(email);
|
|
190
|
+
|
|
191
|
+
if (!checkEmail.success) {
|
|
192
|
+
return yield* new DashboardAPIError({
|
|
193
|
+
error: `Invalid email: ${checkEmail.error.message}`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const [
|
|
198
|
+
verifyUsernameResponse,
|
|
199
|
+
verifyPasswordResponse,
|
|
200
|
+
{ usernameSearch, emailSearch },
|
|
201
|
+
] = yield* Effect.all([
|
|
202
|
+
userHelper.verifyUsernameInput(username).pipe(
|
|
203
|
+
Effect.catchAll(
|
|
204
|
+
(err) =>
|
|
205
|
+
new DashboardAPIError({
|
|
206
|
+
error: 'message' in err ? (err.message as string) : 'Invalid username',
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
),
|
|
210
|
+
pass.verifyPasswordStrength(password).pipe(
|
|
211
|
+
Effect.catchAll(
|
|
212
|
+
(err) =>
|
|
213
|
+
new DashboardAPIError({
|
|
214
|
+
error: 'message' in err ? (err.message as string) : 'Invalid password',
|
|
215
|
+
})
|
|
216
|
+
)
|
|
217
|
+
),
|
|
218
|
+
sdk.AUTH.user.searchUsersForUsernameOrEmail(username, checkEmail.data),
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
if (verifyUsernameResponse !== true) {
|
|
222
|
+
return yield* new DashboardAPIError({ error: verifyUsernameResponse });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (verifyPasswordResponse !== true) {
|
|
226
|
+
return yield* new DashboardAPIError({ error: verifyPasswordResponse });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (usernameSearch.length > 0) {
|
|
230
|
+
return yield* new DashboardAPIError({
|
|
231
|
+
error: 'Invalid username: Username is already in use',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (emailSearch.length > 0) {
|
|
236
|
+
return yield* new DashboardAPIError({
|
|
237
|
+
error: 'Invalid email: Email is already in use',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
yield* userHelper.createLocalUser(displayname, username, email, password).pipe(
|
|
242
|
+
Effect.catchAll(
|
|
243
|
+
(err) =>
|
|
244
|
+
new DashboardAPIError({
|
|
245
|
+
error: 'message' in err ? (err.message as string) : 'Failed to create user',
|
|
246
|
+
})
|
|
247
|
+
),
|
|
248
|
+
Effect.flatMap((newUser) =>
|
|
249
|
+
sdk.UPDATE.permissions({
|
|
250
|
+
user: newUser.id,
|
|
251
|
+
rank: rank,
|
|
252
|
+
})
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
yield* notifier
|
|
257
|
+
.sendAdminNotification('new_user', username)
|
|
258
|
+
.pipe(
|
|
259
|
+
Effect.catchAll(
|
|
260
|
+
() => new DashboardAPIError({ error: 'Failed to send notification' })
|
|
261
|
+
)
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
message: 'User created successfully.',
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
Notifications.Provide,
|
|
269
|
+
Effect.catchTags({
|
|
270
|
+
...sharedDBErrors,
|
|
271
|
+
...sharedNotifierErrors,
|
|
272
|
+
GeneratorError: () => new DashboardAPIError({ error: 'Internal Server Error' }),
|
|
273
|
+
})
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
.handle(
|
|
277
|
+
'createUserInvite',
|
|
278
|
+
Effect.fn(
|
|
279
|
+
function* ({ payload }) {
|
|
280
|
+
if (!dashboardAPIEnabled) {
|
|
281
|
+
return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (developerConfig.demoMode !== false) {
|
|
285
|
+
return yield* new DashboardAPIError({
|
|
286
|
+
error: 'Demo mode is enabled, this action is not allowed.',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const [sdk, userHelper, userData, notifier, mailer, ctx] = yield* Effect.all([
|
|
291
|
+
SDKCore,
|
|
292
|
+
User.pipe(
|
|
293
|
+
Effect.catchAll(
|
|
294
|
+
() => new DashboardAPIError({ error: 'Failed to access User module' })
|
|
295
|
+
)
|
|
296
|
+
),
|
|
297
|
+
CurrentUser,
|
|
298
|
+
Notifications,
|
|
299
|
+
Mailer,
|
|
300
|
+
AstroAPIContext,
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const siteConfig = ctx.locals.StudioCMS.siteConfig.data;
|
|
304
|
+
|
|
305
|
+
if (!siteConfig) {
|
|
306
|
+
return yield* new DashboardAPIError({ error: 'Site configuration not found' });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const isAuthorized = userData.userPermissionLevel.isAdmin;
|
|
310
|
+
|
|
311
|
+
if (!userData.isLoggedIn || !isAuthorized) {
|
|
312
|
+
return yield* new DashboardAPIError({ error: 'Unauthorized' });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { username, email, displayname, rank, originalUrl } = payload;
|
|
316
|
+
|
|
317
|
+
if (!ValidRanks.has(rank) || rank === 'unknown') {
|
|
318
|
+
return yield* new DashboardAPIError({ error: 'Invalid rank' });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const callerPerm = availablePermissionRanks.indexOf(userData.permissionLevel);
|
|
322
|
+
const targetPerm = availablePermissionRanks.indexOf(rank);
|
|
323
|
+
|
|
324
|
+
if (targetPerm >= callerPerm) {
|
|
325
|
+
return yield* new DashboardAPIError({
|
|
326
|
+
error: 'Unauthorized: insufficient permissions to assign target rank',
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const checkEmail = isValidEmail(email);
|
|
331
|
+
|
|
332
|
+
if (!checkEmail.success) {
|
|
333
|
+
return yield* new DashboardAPIError({
|
|
334
|
+
error: `Invalid email: ${checkEmail.error.message}`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const [verifyUsernameResponse, { usernameSearch, emailSearch }] = yield* Effect.all([
|
|
339
|
+
userHelper.verifyUsernameInput(username).pipe(
|
|
340
|
+
Effect.catchAll(
|
|
341
|
+
(err) =>
|
|
342
|
+
new DashboardAPIError({
|
|
343
|
+
error:
|
|
344
|
+
'Invalid username: ' +
|
|
345
|
+
('message' in err ? (err.message as string) : 'Failed to verify username'),
|
|
346
|
+
})
|
|
347
|
+
)
|
|
348
|
+
),
|
|
349
|
+
sdk.AUTH.user.searchUsersForUsernameOrEmail(username, checkEmail.data),
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
if (verifyUsernameResponse !== true) {
|
|
353
|
+
return yield* new DashboardAPIError({ error: verifyUsernameResponse });
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (usernameSearch.length > 0) {
|
|
357
|
+
return yield* new DashboardAPIError({
|
|
358
|
+
error: 'Invalid username: Username is already in use',
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (emailSearch.length > 0) {
|
|
363
|
+
return yield* new DashboardAPIError({
|
|
364
|
+
error: 'Invalid email: Email is already in use',
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Creates a new user invite
|
|
369
|
+
const token = yield* sdk.AUTH.user
|
|
370
|
+
.create(
|
|
371
|
+
{
|
|
372
|
+
username,
|
|
373
|
+
email: checkEmail.data,
|
|
374
|
+
name: displayname,
|
|
375
|
+
createdAt: new Date().toISOString(),
|
|
376
|
+
id: crypto.randomUUID(),
|
|
377
|
+
avatar: undefined,
|
|
378
|
+
emailVerified: false,
|
|
379
|
+
notifications: undefined,
|
|
380
|
+
password: undefined,
|
|
381
|
+
updatedAt: new Date().toISOString(),
|
|
382
|
+
url: undefined,
|
|
383
|
+
},
|
|
384
|
+
rank
|
|
385
|
+
)
|
|
386
|
+
.pipe(Effect.flatMap((newUser) => sdk.resetTokenBucket.new(newUser.id)));
|
|
387
|
+
|
|
388
|
+
if (!token) {
|
|
389
|
+
return yield* new DashboardAPIError({ error: 'Failed to create user invite' });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const resetLink = generateResetUrl(ctx, originalUrl, token);
|
|
393
|
+
|
|
394
|
+
yield* notifier
|
|
395
|
+
.sendAdminNotification('new_user', username)
|
|
396
|
+
.pipe(
|
|
397
|
+
Effect.catchAll(
|
|
398
|
+
() => new DashboardAPIError({ error: 'Failed to send notification' })
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
if (siteConfig.enableMailer) {
|
|
403
|
+
const checkMailConnection = yield* mailer.verifyMailConnection.pipe(
|
|
404
|
+
Effect.catchAll(
|
|
405
|
+
() => new DashboardAPIError({ error: 'Failed to connect to mail server' })
|
|
406
|
+
)
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
if (!checkMailConnection) {
|
|
410
|
+
return yield* new DashboardAPIError({
|
|
411
|
+
error: 'Failed to send invite email: Mailer connection failed',
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if ('error' in checkMailConnection) {
|
|
416
|
+
return yield* new DashboardAPIError({
|
|
417
|
+
error: 'Failed to send invite email: Mailer connection error',
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const engine = yield* templateEngine;
|
|
422
|
+
const { title: siteTitle, description, siteIcon } = siteConfig;
|
|
423
|
+
|
|
424
|
+
const userInviteTemplate = yield* engine.render('userInvite', {
|
|
425
|
+
site: { title: siteTitle, description, icon: siteIcon ?? undefined },
|
|
426
|
+
data: { link: resetLink.toString() },
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const mailResponse = yield* mailer
|
|
430
|
+
.sendMail({
|
|
431
|
+
to: checkEmail.data,
|
|
432
|
+
subject: `You have been invited to join ${siteConfig.title}!`,
|
|
433
|
+
html: userInviteTemplate,
|
|
434
|
+
})
|
|
435
|
+
.pipe(
|
|
436
|
+
Effect.catchAll(
|
|
437
|
+
(err) =>
|
|
438
|
+
new DashboardAPIError({
|
|
439
|
+
error:
|
|
440
|
+
'Failed to send invite email: ' +
|
|
441
|
+
('message' in err
|
|
442
|
+
? (err.message as string)
|
|
443
|
+
: 'Mailer error during sending'),
|
|
444
|
+
})
|
|
445
|
+
)
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
if (!mailResponse) {
|
|
449
|
+
return yield* new DashboardAPIError({
|
|
450
|
+
error: 'Failed to send invite email: Mailer failed to send email',
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if ('error' in mailResponse) {
|
|
455
|
+
return yield* new DashboardAPIError({
|
|
456
|
+
error: 'Failed to send invite email: Mailer error during sending',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
message: 'User invite created and email sent',
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
message: resetLink.toString(),
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
Notifications.Provide,
|
|
470
|
+
Mailer.Provide,
|
|
471
|
+
Effect.catchTags({
|
|
472
|
+
...sharedDBErrors,
|
|
473
|
+
...sharedNotifierErrors,
|
|
474
|
+
GeneratorError: () => new DashboardAPIError({ error: 'Internal Server Error' }),
|
|
475
|
+
TemplateEngineError: () =>
|
|
476
|
+
new DashboardAPIError({ error: 'Failed to render email template' }),
|
|
477
|
+
})
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { developerConfig } from 'studiocms:config';
|
|
2
|
+
import { SDKCore } from 'studiocms:sdk';
|
|
3
|
+
import routeConfig from 'virtual:studiocms/route-config';
|
|
4
|
+
import { HttpApiBuilder } from '@effect/platform';
|
|
5
|
+
import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
|
|
6
|
+
import { CurrentUser } from '@withstudiocms/api-spec/astro-context';
|
|
7
|
+
import { DashboardAPIError } from '@withstudiocms/api-spec/dashboard';
|
|
8
|
+
import { Effect } from 'effect';
|
|
9
|
+
import { sharedDBErrors } from './_shared.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if the Dashboard API is enabled in the route configuration.
|
|
13
|
+
*/
|
|
14
|
+
const dashboardAPIEnabled = routeConfig.dashboardAPIEnabled;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Email Notification Handlers for the Dashboard API
|
|
18
|
+
*/
|
|
19
|
+
export const EmailNotificationHandlers = HttpApiBuilder.group(
|
|
20
|
+
StudioCMSDashboardApiSpec,
|
|
21
|
+
'emailNotifications',
|
|
22
|
+
(handlers) =>
|
|
23
|
+
handlers.handle(
|
|
24
|
+
'updateEmailNotificationsSettings',
|
|
25
|
+
Effect.fn(function* ({ payload }) {
|
|
26
|
+
if (!dashboardAPIEnabled) {
|
|
27
|
+
return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (developerConfig.demoMode !== false) {
|
|
31
|
+
return yield* new DashboardAPIError({
|
|
32
|
+
error: 'Demo mode is enabled, this action is not allowed.',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [sdk, userData] = yield* Effect.all([SDKCore, CurrentUser]);
|
|
37
|
+
|
|
38
|
+
if (!userData.isLoggedIn || !userData.userPermissionLevel.isOwner) {
|
|
39
|
+
return yield* new DashboardAPIError({ error: 'Unauthorized' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
yield* sdk.notificationSettings.site.update(payload);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
message: 'Notification settings updated',
|
|
46
|
+
};
|
|
47
|
+
}, Effect.catchTags(sharedDBErrors))
|
|
48
|
+
)
|
|
49
|
+
);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HttpApiBuilder } from '@effect/platform';
|
|
2
|
+
import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
|
|
3
|
+
import { Layer } from 'effect';
|
|
4
|
+
import { AstroLocalsAuthLive } from '../../_middleware/astroLocals.js';
|
|
5
|
+
import { ApiTokensHandler } from './apiTokens.js';
|
|
6
|
+
import { ConfigHandlers } from './config.js';
|
|
7
|
+
import { ContentHandlers } from './content.js';
|
|
8
|
+
import { CreateHandlers } from './create.js';
|
|
9
|
+
import { EmailNotificationHandlers } from './emailNotifications.js';
|
|
10
|
+
import { MailerHandlers } from './mailer.js';
|
|
11
|
+
import { PluginHandlers } from './plugins.js';
|
|
12
|
+
import { ProfileHandlers } from './profile.js';
|
|
13
|
+
import { ResetPasswordHandlers } from './resetPassword.js';
|
|
14
|
+
import { SearchHandlers } from './search.js';
|
|
15
|
+
import { TaxonomyHandlers } from './taxonomy.js';
|
|
16
|
+
import { TemplatesHandlers } from './templates.js';
|
|
17
|
+
import { UsersHandlers } from './users.js';
|
|
18
|
+
import { VerifyEndpointsHandlers } from './verifyEndpoints.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Combined Dashboard API Handlers.
|
|
22
|
+
*/
|
|
23
|
+
export const DashboardAPIHandlers = Layer.mergeAll(
|
|
24
|
+
ApiTokensHandler,
|
|
25
|
+
ConfigHandlers,
|
|
26
|
+
ContentHandlers,
|
|
27
|
+
CreateHandlers,
|
|
28
|
+
EmailNotificationHandlers,
|
|
29
|
+
MailerHandlers,
|
|
30
|
+
PluginHandlers,
|
|
31
|
+
ProfileHandlers,
|
|
32
|
+
ResetPasswordHandlers,
|
|
33
|
+
SearchHandlers,
|
|
34
|
+
TaxonomyHandlers,
|
|
35
|
+
TemplatesHandlers,
|
|
36
|
+
UsersHandlers,
|
|
37
|
+
VerifyEndpointsHandlers
|
|
38
|
+
).pipe(Layer.provide(AstroLocalsAuthLive));
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Live implementation of the Dashboard API Handlers.
|
|
42
|
+
*/
|
|
43
|
+
export const DashboardAPILive = HttpApiBuilder.api(StudioCMSDashboardApiSpec).pipe(
|
|
44
|
+
Layer.provide(DashboardAPIHandlers)
|
|
45
|
+
);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { developerConfig } from 'studiocms:config';
|
|
2
|
+
import { Mailer } from 'studiocms:mailer';
|
|
3
|
+
import routeConfig from 'virtual:studiocms/route-config';
|
|
4
|
+
import { HttpApiBuilder } from '@effect/platform';
|
|
5
|
+
import { StudioCMSDashboardApiSpec } from '@withstudiocms/api-spec';
|
|
6
|
+
import { CurrentUser } from '@withstudiocms/api-spec/astro-context';
|
|
7
|
+
import { DashboardAPIError, type MailerSmtpConfigPayload } from '@withstudiocms/api-spec/dashboard';
|
|
8
|
+
import { Effect } from 'effect';
|
|
9
|
+
import type { DynamicHttpApiHandlerParams } from 'effectify/httpApi';
|
|
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
|
+
* Shared effect for handling both setup and update mailer configuration endpoints since they have the same logic and requirements.
|
|
19
|
+
*/
|
|
20
|
+
const mailerEffect = (type: 'setup' | 'update') =>
|
|
21
|
+
Effect.fn(
|
|
22
|
+
function* ({
|
|
23
|
+
payload,
|
|
24
|
+
}: DynamicHttpApiHandlerParams<{
|
|
25
|
+
payloadSchema: typeof MailerSmtpConfigPayload.Type;
|
|
26
|
+
}>) {
|
|
27
|
+
if (!dashboardAPIEnabled) {
|
|
28
|
+
return yield* new DashboardAPIError({
|
|
29
|
+
error: 'Dashboard API is disabled',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (developerConfig.demoMode !== false) {
|
|
34
|
+
return yield* new DashboardAPIError({
|
|
35
|
+
error: 'Demo mode is enabled, this action is not allowed.',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [mailer, userData] = yield* Effect.all([Mailer, CurrentUser]);
|
|
40
|
+
|
|
41
|
+
if (!userData.isLoggedIn || !userData.userPermissionLevel.isOwner) {
|
|
42
|
+
return yield* new DashboardAPIError({ error: 'Unauthorized' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (payload.port && (payload.port < 1 || payload.port > 65535)) {
|
|
46
|
+
return yield* new DashboardAPIError({ error: 'Invalid port number' });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const config = yield* mailer.createMailerConfigTable(payload);
|
|
50
|
+
|
|
51
|
+
if (!config) {
|
|
52
|
+
return yield* new DashboardAPIError({
|
|
53
|
+
error: `Failed to ${type} mailer configuration`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
message: `Mailer configuration ${type}d successfully`,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
Mailer.Provide,
|
|
62
|
+
Effect.catchTags({
|
|
63
|
+
...sharedDBErrors,
|
|
64
|
+
...sharedNotifierErrors,
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Mailer Handlers for the Dashboard API
|
|
70
|
+
*/
|
|
71
|
+
export const MailerHandlers = HttpApiBuilder.group(
|
|
72
|
+
StudioCMSDashboardApiSpec,
|
|
73
|
+
'mailer',
|
|
74
|
+
(handlers) =>
|
|
75
|
+
handlers
|
|
76
|
+
.handle('setupMailerConfig', mailerEffect('setup'))
|
|
77
|
+
.handle('updateMailerConfig', mailerEffect('update'))
|
|
78
|
+
.handle(
|
|
79
|
+
'testEmailService',
|
|
80
|
+
Effect.fn(
|
|
81
|
+
function* ({ payload: { test_email } }) {
|
|
82
|
+
if (!dashboardAPIEnabled) {
|
|
83
|
+
return yield* new DashboardAPIError({ error: 'Dashboard API is disabled' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (developerConfig.demoMode !== false) {
|
|
87
|
+
return yield* new DashboardAPIError({
|
|
88
|
+
error: 'Demo mode is enabled, this action is not allowed.',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const [mailer, userData] = yield* Effect.all([Mailer, CurrentUser]);
|
|
93
|
+
|
|
94
|
+
if (!userData.isLoggedIn || !userData.userPermissionLevel.isOwner) {
|
|
95
|
+
return yield* new DashboardAPIError({ error: 'Unauthorized' });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof test_email !== 'string' || test_email.trim() === '') {
|
|
99
|
+
return yield* new DashboardAPIError({
|
|
100
|
+
error: 'Invalid form data, test_email is required',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const email = test_email.trim();
|
|
104
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
105
|
+
return yield* new DashboardAPIError({ error: 'Invalid email address' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const response = yield* mailer
|
|
109
|
+
.sendMail({
|
|
110
|
+
to: test_email,
|
|
111
|
+
subject: 'StudioCMS Test Email',
|
|
112
|
+
text: 'This is a test email from StudioCMS.',
|
|
113
|
+
})
|
|
114
|
+
.pipe(
|
|
115
|
+
Effect.catchAll(
|
|
116
|
+
(err) =>
|
|
117
|
+
new DashboardAPIError({ error: `Failed to send test email: ${err.message}` })
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if ('error' in response) {
|
|
122
|
+
console.error('Mailer test-email failed:', response.error);
|
|
123
|
+
return yield* new DashboardAPIError({ error: 'Failed to send test email' });
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
message: 'Test email sent successfully',
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
Mailer.Provide,
|
|
130
|
+
Effect.catchTags({
|
|
131
|
+
...sharedDBErrors,
|
|
132
|
+
...sharedNotifierErrors,
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
);
|