From 66caff1f5e4b3fd9885f7b50daa7434810367571 Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Tue, 25 Nov 2025 17:51:20 +0100
Subject: [PATCH 01/43] installed neverthrow
---
src/management-system-v2/package.json | 3 ++-
yarn.lock | 12 ++++++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/management-system-v2/package.json b/src/management-system-v2/package.json
index eaf81dd7e..4ac22101c 100644
--- a/src/management-system-v2/package.json
+++ b/src/management-system-v2/package.json
@@ -72,7 +72,8 @@
"react-resizable": "^3.0.5",
"mqtt": "^5.10.1",
"bcryptjs": "3.0.2",
- "sharp": "0.34.3"
+ "sharp": "0.34.3",
+ "neverthrow": "^8.2.0"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "5.28.11",
diff --git a/yarn.lock b/yarn.lock
index 7ebf9ba03..43391efc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2693,6 +2693,11 @@
resolved "https://registry.npmjs.org/@react-email/text/-/text-0.0.7.tgz"
integrity sha512-eHCx0mdllGcgK9X7wiLKjNZCBRfxRVNjD3NNYRmOc3Icbl8M9JHriJIfxBuGCmGg2UAORK5P3KmaLQ8b99/pbA==
+"@rollup/rollup-linux-x64-gnu@^4.24.0":
+ version "4.53.3"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz#fd0dea3bb9aa07e7083579f25e1c2285a46cb9fa"
+ integrity sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==
+
"@rushstack/eslint-patch@^1.3.3":
version "1.10.2"
resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz"
@@ -10925,6 +10930,13 @@ neo-bpmn-engine@^8.2.5:
rxjs "^6.5.1"
uuid "^3.3.2"
+neverthrow@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-8.2.0.tgz#925d988295758534d01fb7468f998680b62064f2"
+ integrity sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==
+ optionalDependencies:
+ "@rollup/rollup-linux-x64-gnu" "^4.24.0"
+
next-auth@5.0.0-beta.25:
version "5.0.0-beta.25"
resolved "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.25.tgz"
From d7c1d8a0a6f35dd4ddc85ac72e34c7af52253b29 Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Mon, 8 Dec 2025 14:52:58 +0100
Subject: [PATCH 02/43] # This is a combination of 2 commits. # This is the 1st
commit message:
temp-1
# This is the commit message #2:
temp-2 errors
---
.../executions/deployment-hook.ts | 2 +-
.../executions/deployments-modal.tsx | 2 +-
.../executions/deployments-view.tsx | 2 +-
.../(automation)/executions/page.tsx | 2 +-
.../iam/roles/[roleId]/rolePermissions.tsx | 2 +-
.../iam/users/invite-users.tsx | 2 +-
.../[mode]/[processId]/modeler-toolbar.tsx | 2 +-
.../[mode]/[processId]/script-editor.tsx | 2 +-
.../[environmentId]/profile/user-profile.tsx | 2 +-
.../settings/@generalSettings/page.tsx | 3 +-
.../app/admin/ms-config/page.tsx | 2 +-
.../app/admin/spaces/page.tsx | 2 +-
.../app/admin/systemadmins/page.tsx | 2 +-
.../app/admin/users/page.tsx | 2 +-
.../app/create-organization/page.tsx | 2 +-
src/management-system-v2/app/error.tsx | 14 +-
.../app/transfer-processes/server-actions.ts | 2 +-
src/management-system-v2/components/auth.tsx | 34 +-
.../bpmn-timeline/GanttSettingsModal.tsx | 2 +-
.../components/bpmn-timeline/index.tsx | 2 +-
.../components/process-modal.tsx | 2 +-
.../components/share-modal/export.tsx | 6 +-
.../components/share-modal/share-helpers.ts | 2 +-
.../lib/data/db/engines.ts | 77 ++--
.../lib/data/db/folders.ts | 140 +++---
.../lib/data/db/html-forms.ts | 42 +-
.../lib/data/db/iam/environments.ts | 157 ++++---
.../lib/data/db/iam/memberships.ts | 96 ++--
.../lib/data/db/iam/role-mappings.ts | 89 ++--
.../lib/data/db/iam/roles.ts | 69 +--
.../lib/data/db/iam/system-admins.ts | 28 +-
.../lib/data/db/iam/users.ts | 209 +++++----
.../lib/data/db/iam/verification-tokens.ts | 47 +-
.../lib/data/db/process.ts | 432 +++++++++++-------
.../lib/data/db/space-settings.ts | 15 +-
.../lib/data/db/user-tasks.ts | 69 +--
src/management-system-v2/lib/data/db/util.ts | 30 ++
src/management-system-v2/lib/data/engines.ts | 4 +-
.../lib/data/environment-memberships.ts | 2 +-
.../lib/data/environments.ts | 12 +-
.../lib/data/file-manager-facade.ts | 2 +-
src/management-system-v2/lib/data/folders.ts | 2 +-
.../lib/data/html-forms.ts | 2 +-
.../lib/data/processes.tsx | 2 +-
.../lib/data/role-mappings.ts | 2 +-
src/management-system-v2/lib/data/roles.ts | 2 +-
.../lib/data/space-settings.ts | 2 +-
.../lib/data/user-tasks.ts | 2 +-
src/management-system-v2/lib/data/users.tsx | 2 +-
.../server-actions.ts | 2 +-
.../lib/engines/deployment.ts | 2 +-
.../lib/engines/server-actions.ts | 2 +-
src/management-system-v2/lib/errors.ts | 8 -
.../lib/page-error-handling.tsx | 8 +
.../lib/process-export/export-preparation.ts | 2 +-
src/management-system-v2/lib/result.ts | 50 ++
.../lib/server-error-handling/errors.ts | 12 +
.../page-error-response.tsx | 32 ++
.../server-error-handling/retry-button.tsx | 10 +
.../{ => server-error-handling}/user-error.ts | 0
.../lib/sharing/process-sharing.ts | 2 +-
src/management-system-v2/lib/ui-error.ts | 15 -
.../lib/useFavouriteProcesses.ts | 2 +-
.../lib/wrap-server-call.ts | 7 +-
64 files changed, 1117 insertions(+), 668 deletions(-)
create mode 100644 src/management-system-v2/lib/data/db/util.ts
delete mode 100644 src/management-system-v2/lib/errors.ts
create mode 100644 src/management-system-v2/lib/page-error-handling.tsx
create mode 100644 src/management-system-v2/lib/result.ts
create mode 100644 src/management-system-v2/lib/server-error-handling/errors.ts
create mode 100644 src/management-system-v2/lib/server-error-handling/page-error-response.tsx
create mode 100644 src/management-system-v2/lib/server-error-handling/retry-button.tsx
rename src/management-system-v2/lib/{ => server-error-handling}/user-error.ts (100%)
delete mode 100644 src/management-system-v2/lib/ui-error.ts
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployment-hook.ts b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployment-hook.ts
index bd2a8f6bb..4ade674a7 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployment-hook.ts
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployment-hook.ts
@@ -15,7 +15,7 @@ import { Engine } from '@/lib/engines/machines';
import { getStartFormFromMachine } from '@/lib/engines/tasklist';
import useEngines from '@/lib/engines/use-engines';
import { asyncFilter, asyncForEach, deepEquals } from '@/lib/helpers/javascriptHelpers';
-import { getErrorMessage, userError } from '@/lib/user-error';
+import { getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-modal.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-modal.tsx
index 497508733..aad155993 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-modal.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-modal.tsx
@@ -25,7 +25,7 @@ import { useEnvironment } from '@/components/auth-can';
import { getFolder, getFolderContents } from '@/lib/data/folders';
import { ProcessDeploymentList } from '@/components/process-list';
import { useQuery } from '@tanstack/react-query';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import { getAvailableSpaceEngines } from '@/lib/engines/server-actions';
import { SpaceEngine } from '@/lib/engines/machines';
import { MdOutlineComputer } from 'react-icons/md';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx
index e76e55c05..5ad925a44 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx
@@ -15,7 +15,7 @@ import { useRouter } from 'next/navigation';
import { deployProcess as serverDeployProcess } from '@/lib/engines/server-actions';
import { wrapServerCall } from '@/lib/wrap-server-call';
import { SpaceEngine } from '@/lib/engines/machines';
-import { userError } from '@/lib/user-error';
+import { userError } from '@/lib/server-error-handling/user-error';
import { removeDeployment as serverRemoveDeployment } from '@/lib/engines/server-actions';
import { useQueryClient } from '@tanstack/react-query';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx
index 644e97817..35cb0244d 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx
@@ -5,7 +5,7 @@ import { getRootFolder, getFolderById, getFolderContents } from '@/lib/data/db/f
import { getUsersFavourites } from '@/lib/data/users';
import { getDeployedProcessesFromSavedEngines } from '@/lib/engines/saved-engines-helpers';
import { DeployedProcessInfo } from '@/lib/engines/deployment';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import { Skeleton } from 'antd';
import { Suspense } from 'react';
import { getDbEngines } from '@/lib/data/db/engines';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/rolePermissions.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/rolePermissions.tsx
index dbbcc7372..be741e57e 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/rolePermissions.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/rolePermissions.tsx
@@ -8,7 +8,7 @@ import { useAbilityStore } from '@/lib/abilityStore';
import { updateRole as serverUpdateRole } from '@/lib/data/roles';
import { Role } from '@/lib/data/role-schema';
import { useEnvironment } from '@/components/auth-can';
-import { UserErrorType } from '@/lib/user-error';
+import { UserErrorType } from '@/lib/server-error-handling/user-error';
import { EnvVarsContext } from '@/components/env-vars-context';
type PermissionCategory = {
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/invite-users.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/invite-users.tsx
index acff9a1ce..afb613d1b 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/invite-users.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/invite-users.tsx
@@ -29,7 +29,7 @@ import { EnvVarsContext } from '@/components/env-vars-context';
import useOrganizationRoles from './use-org-roles';
import useDebounce from '@/lib/useDebounce';
import { queryUsers } from '@/lib/data/users';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import UserAvatar from '@/components/user-avatar';
import { z } from 'zod';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/modeler-toolbar.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/modeler-toolbar.tsx
index 424a8f131..c121aa6ae 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/modeler-toolbar.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/modeler-toolbar.tsx
@@ -27,7 +27,7 @@ import { ShareModal } from '@/components/share-modal/share-modal';
import { useAddControlCallback } from '@/lib/controls-store';
import { spaceURL } from '@/lib/utils';
import { generateSharedViewerUrl } from '@/lib/sharing/process-sharing';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import ScriptEditor from '@/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-editor';
import useTimelineViewStore from '@/lib/use-timeline-view-store';
import { handleOpenDocumentation } from '../../processes-helper';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-editor.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-editor.tsx
index d591493dd..4402c611f 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-editor.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-editor.tsx
@@ -37,7 +37,7 @@ import { useEnvironment } from '@/components/auth-can';
import { generateScriptTaskFileName } from '@proceed/bpmn-helper';
import { type BlocklyEditorRefType } from './blockly-editor';
import { useQuery } from '@tanstack/react-query';
-import { isUserErrorResponse, userError } from '@/lib/user-error';
+import { isUserErrorResponse, userError } from '@/lib/server-error-handling/user-error';
import { wrapServerCall } from '@/lib/wrap-server-call';
import useProcessVariables from './use-process-variables';
import ProcessVariableForm from './variable-definition/process-variable-form';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/profile/user-profile.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/profile/user-profile.tsx
index d90bb15f9..8c1a56fc3 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/profile/user-profile.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/profile/user-profile.tsx
@@ -29,7 +29,7 @@ import { requestEmailChange as serverRequestEmailChange } from '@/lib/email-veri
import Link from 'next/link';
import { EnvVarsContext } from '@/components/env-vars-context';
import ChangeUserPasswordModal from './change-password-modal';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
const UserProfile: FC<{ userData: User; userHasPassword: boolean }> = ({
userData,
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx
index f4cd38e16..36b3f93b3 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx
@@ -4,8 +4,7 @@ import SettingsInjector from '../settings-injector';
import { SettingGroup } from '../type-util';
import Wrapper from './wrapper';
import db from '@/lib/data/db';
-import { SpaceNotFoundError } from '@/lib/errors';
-import { getMSConfig } from '@/lib/ms-config/ms-config';
+import { SpaceNotFoundError } from '@/lib/server-error-handling/errors';
const Page = async ({ params }: { params: { environmentId: string } }) => {
const {
diff --git a/src/management-system-v2/app/admin/ms-config/page.tsx b/src/management-system-v2/app/admin/ms-config/page.tsx
index b7173673c..d2a353a92 100644
--- a/src/management-system-v2/app/admin/ms-config/page.tsx
+++ b/src/management-system-v2/app/admin/ms-config/page.tsx
@@ -5,7 +5,7 @@ import { redirect } from 'next/navigation';
import { Suspense } from 'react';
import { getMSConfig, updateMSConfig, writeDefaultMSConfig } from '@/lib/ms-config/ms-config';
import MSConfigForm from './ms-config-form';
-import { userError } from '@/lib/user-error';
+import { userError } from '@/lib/server-error-handling/user-error';
import { SettingGroup } from '@/app/(dashboard)/[environmentId]/settings/type-util';
async function saveConfig(newConfig: Record) {
diff --git a/src/management-system-v2/app/admin/spaces/page.tsx b/src/management-system-v2/app/admin/spaces/page.tsx
index 5974ea70c..91a247bc8 100644
--- a/src/management-system-v2/app/admin/spaces/page.tsx
+++ b/src/management-system-v2/app/admin/spaces/page.tsx
@@ -7,7 +7,7 @@ import {
import { getSystemAdminByUserId } from '@/lib/data/db/iam/system-admins';
import { redirect } from 'next/navigation';
import SpacesTable from './spaces-table';
-import { UserErrorType, userError } from '@/lib/user-error';
+import { UserErrorType, userError } from '@/lib/server-error-handling/user-error';
import Content from '@/components/content';
import { getSpaceRepresentation, getUserName } from './space-representation';
import { getUserOrganizationEnvironments } from '@/lib/data/db/iam/memberships';
diff --git a/src/management-system-v2/app/admin/systemadmins/page.tsx b/src/management-system-v2/app/admin/systemadmins/page.tsx
index c780385cb..4117609bc 100644
--- a/src/management-system-v2/app/admin/systemadmins/page.tsx
+++ b/src/management-system-v2/app/admin/systemadmins/page.tsx
@@ -9,7 +9,7 @@ import {
} from '@/lib/data/db/iam/system-admins';
import { getUserById, getUsers } from '@/lib/data/db/iam/users';
import { AuthenticatedUser } from '@/lib/data/user-schema';
-import { UserErrorType, userError } from '@/lib/user-error';
+import { UserErrorType, userError } from '@/lib/server-error-handling/user-error';
import { notFound, redirect } from 'next/navigation';
import SystemAdminsTable from './admins-table';
import { SystemAdminCreationInput } from '@/lib/data/system-admin-schema';
diff --git a/src/management-system-v2/app/admin/users/page.tsx b/src/management-system-v2/app/admin/users/page.tsx
index fa3399403..8dfd020e6 100644
--- a/src/management-system-v2/app/admin/users/page.tsx
+++ b/src/management-system-v2/app/admin/users/page.tsx
@@ -4,7 +4,7 @@ import { getSystemAdminByUserId } from '@/lib/data/db/iam/system-admins';
import { deleteUser, getUsers } from '@/lib/data/db/iam/users';
import { notFound, redirect } from 'next/navigation';
import UserTable from './user-table';
-import { UserErrorType, userError } from '@/lib/user-error';
+import { UserErrorType, userError } from '@/lib/server-error-handling/user-error';
import Content from '@/components/content';
import { UserHasToDeleteOrganizationsError } from '@/lib/data/db/iam/users';
import { env } from '@/lib/ms-config/env-vars';
diff --git a/src/management-system-v2/app/create-organization/page.tsx b/src/management-system-v2/app/create-organization/page.tsx
index 074c83f2a..5d4591523 100644
--- a/src/management-system-v2/app/create-organization/page.tsx
+++ b/src/management-system-v2/app/create-organization/page.tsx
@@ -3,7 +3,7 @@ import CreateOrganizationPage from './client-page';
import { getProviders } from '@/lib/auth';
import { UserOrganizationEnvironmentInput } from '@/lib/data/environment-schema';
import { addEnvironment } from '@/lib/data/db/iam/environments';
-import { getErrorMessage, userError } from '@/lib/user-error';
+import { getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
import { getMSConfig } from '@/lib/ms-config/ms-config';
import { notFound } from 'next/navigation';
import { env } from '@/lib/ms-config/env-vars';
diff --git a/src/management-system-v2/app/error.tsx b/src/management-system-v2/app/error.tsx
index c504ce625..84be1eab4 100644
--- a/src/management-system-v2/app/error.tsx
+++ b/src/management-system-v2/app/error.tsx
@@ -3,8 +3,8 @@
import Content from '@/components/content';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
import { UnauthorizedError } from '@/lib/ability/abilityHelper';
-import { SpaceNotFoundError } from '@/lib/errors';
-import { UIError } from '@/lib/ui-error';
+import { SpaceNotFoundError } from '@/lib/server-error-handling/errors';
+// import { UIError } from '@/lib/ui-error';
import { Button, Result } from 'antd';
export default function Error({
@@ -17,8 +17,8 @@ export default function Error({
let title = 'Something went wrong!';
if (error.message.startsWith(UnauthorizedError.prefix)) {
return ;
- } else if (error.message.startsWith(UIError.prefix)) {
- title = error.message.substring(UIError.prefix.length + 2);
+ // } else if (error.message.startsWith(UIError.prefix)) {
+ // title = error.message.substring(UIError.prefix.length + 2);
}
const retryButton = (
@@ -36,9 +36,9 @@ export default function Error({
/>
);
- if (error.message.startsWith(SpaceNotFoundError.prefix)) {
- ;
- }
+ // if (error.message.startsWith(SpaceNotFoundError.prefix)) {
+ // ;
+ // }
return {feedback};
}
diff --git a/src/management-system-v2/app/transfer-processes/server-actions.ts b/src/management-system-v2/app/transfer-processes/server-actions.ts
index 4dd9e148c..c44ecad35 100644
--- a/src/management-system-v2/app/transfer-processes/server-actions.ts
+++ b/src/management-system-v2/app/transfer-processes/server-actions.ts
@@ -7,7 +7,7 @@ import { getProcesses, updateProcess } from '@/lib/data/db/process';
import { getUserById, deleteUser } from '@/lib/data/db/iam/users';
import { Process } from '@/lib/data/process-schema';
import { getGuestReference } from '@/lib/reference-guest-user-token';
-import { UserErrorType, userError } from '@/lib/user-error';
+import { UserErrorType, userError } from '@/lib/server-error-handling/user-error';
import { redirect } from 'next/navigation';
export async function transferProcesses(referenceToken: string, callbackUrl: string = '/') {
diff --git a/src/management-system-v2/components/auth.tsx b/src/management-system-v2/components/auth.tsx
index 4e7c073c5..f3d79eb68 100644
--- a/src/management-system-v2/components/auth.tsx
+++ b/src/management-system-v2/components/auth.tsx
@@ -17,15 +17,16 @@ import { cookies } from 'next/headers';
import { getMSConfig } from '@/lib/ms-config/ms-config';
import { UIError as UserUIError } from '@/lib/ui-error';
import { packedStaticRules } from '@/lib/authorization/caslRules';
+import { ok } from 'neverthrow';
export const getCurrentUser = cache(async () => {
if (!env.PROCEED_PUBLIC_IAM_ACTIVE) {
- return {
+ return ok({
session: noIamUser.session,
userId: noIamUser.userId,
systemAdmin: noIamUser.systemAdmin,
user: noIamUser.user,
- };
+ });
}
const session = await auth();
@@ -35,12 +36,15 @@ export const getCurrentUser = cache(async () => {
userId !== '' ? getUserById(userId) : undefined,
]);
+ if (systemAdmin.isErr()) return systemAdmin;
+ if (user && user.isErr()) return user;
+
// Sign out user if the id doesn't correspond to a user in the db
// We need to reset the cookie that stores the user id, this isn't possible
// inside a server components, so we need to redirect the user to an endpoint
// that logs him out, this endpoint needs to csrf protected, for this we use
// the user's csrf token (which was added by next-auth)
- if (userId !== '' && !user) {
+ if (userId !== '' && !user?.value) {
const cookieStore = cookies();
const csrfToken = cookieStore.get('proceed.csrf-token')!.value;
@@ -56,7 +60,7 @@ export const getCurrentUser = cache(async () => {
redirect(`/api/private/signout?${searchParams}`);
}
- return { session, userId, systemAdmin, user };
+ return ok({ session, userId, systemAdmin: systemAdmin.value, user: user?.value });
});
const systemAdminRulesForOrganizations = packedAdminRules
@@ -89,7 +93,12 @@ export const getCurrentEnvironment = cache(
permissionErrorHandling: { action: 'redirect' },
},
) => {
- const { userId, systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return currentUser;
+ }
+
+ const { userId, systemAdmin } = currentUser.value;
const msConfig = await getMSConfig();
if (
@@ -107,8 +116,11 @@ export const getCurrentEnvironment = cache(
if (userId && !isOrganization && !env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
// Note: will be undefined for not logged in users
const userOrgs = await getUserOrganizationEnvironments(userId);
+ if (userOrgs.isErr()) {
+ return userOrgs;
+ }
- if (userOrgs.length === 0) {
+ if (userOrgs.value.length === 0) {
if (env.PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE) {
throw new UserUIError('You are not part of an organization.');
} else {
@@ -116,7 +128,7 @@ export const getCurrentEnvironment = cache(
}
}
- activeSpace = userOrgs[0];
+ activeSpace = userOrgs.value[0];
isOrganization = true;
}
@@ -125,10 +137,10 @@ export const getCurrentEnvironment = cache(
if (systemAdmin || !msConfig.PROCEED_PUBLIC_IAM_ACTIVE) {
const rules = getSystemAdminRules(isOrganization);
- return {
+ return ok({
ability: new Ability(rules, activeSpace),
activeEnvironment: { spaceId: activeSpace, isOrganization },
- };
+ });
}
if (!userId || !isMember(decodeURIComponent(spaceIdParam), userId)) {
@@ -147,9 +159,9 @@ export const getCurrentEnvironment = cache(
const ability = await getAbilityForUser(userId, activeSpace);
- return {
+ return ok({
ability,
activeEnvironment: { spaceId: activeSpace, isOrganization },
- };
+ });
},
);
diff --git a/src/management-system-v2/components/bpmn-timeline/GanttSettingsModal.tsx b/src/management-system-v2/components/bpmn-timeline/GanttSettingsModal.tsx
index f6add6c1a..9850ca55c 100644
--- a/src/management-system-v2/components/bpmn-timeline/GanttSettingsModal.tsx
+++ b/src/management-system-v2/components/bpmn-timeline/GanttSettingsModal.tsx
@@ -7,7 +7,7 @@ import { useEnvironment } from '@/components/auth-can';
import { SettingsGroup } from '../../app/(dashboard)/[environmentId]/settings/components';
import { debouncedSettingsUpdate } from '../../app/(dashboard)/[environmentId]/settings/utils';
import { getSpaceSettingsValues } from '@/lib/data/space-settings';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import { ganttViewSettingsDefinition } from './gantt-settings-definition';
import { createGanttSettingsRenderer } from './gantt-settings-utils';
import type { SettingGroup } from '../../app/(dashboard)/[environmentId]/settings/type-util';
diff --git a/src/management-system-v2/components/bpmn-timeline/index.tsx b/src/management-system-v2/components/bpmn-timeline/index.tsx
index 72ace0288..8b378ae3f 100644
--- a/src/management-system-v2/components/bpmn-timeline/index.tsx
+++ b/src/management-system-v2/components/bpmn-timeline/index.tsx
@@ -7,7 +7,7 @@ import { GanttChartCanvas } from '@/components/gantt-chart-canvas';
import type { GanttElementType, GanttDependency } from '@/components/gantt-chart-canvas/types';
import useTimelineViewStore from '@/lib/use-timeline-view-store';
import { getSpaceSettingsValues } from '@/lib/data/space-settings';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import { useEnvironment } from '@/components/auth-can';
import { moddle } from '@proceed/bpmn-helper';
import useModelerStateStore from '@/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/use-modeler-state-store';
diff --git a/src/management-system-v2/components/process-modal.tsx b/src/management-system-v2/components/process-modal.tsx
index b0c863a62..5f884639e 100644
--- a/src/management-system-v2/components/process-modal.tsx
+++ b/src/management-system-v2/components/process-modal.tsx
@@ -18,7 +18,7 @@ import {
Skeleton,
} from 'antd';
import { MdArrowBackIos, MdArrowForwardIos } from 'react-icons/md';
-import { UserError } from '@/lib/user-error';
+import { UserError } from '@/lib/server-error-handling/user-error';
import { useAddControlCallback } from '@/lib/controls-store';
import { checkIfProcessExistsByName } from '@/lib/data/processes';
import { useEnvironment } from './auth-can';
diff --git a/src/management-system-v2/components/share-modal/export.tsx b/src/management-system-v2/components/share-modal/export.tsx
index d79626f99..43b7e3e07 100644
--- a/src/management-system-v2/components/share-modal/export.tsx
+++ b/src/management-system-v2/components/share-modal/export.tsx
@@ -14,7 +14,11 @@ import useModelerStateStore from '@/app/(dashboard)/[environmentId]/processes/[m
import { is as bpmnIs } from 'bpmn-js/lib/util/ModelUtil';
import { ProcessMetadata } from '@/lib/data/process-schema';
import useProcessVersion from './use-process-version';
-import { UserError, UserErrorType, isUserErrorResponse } from '@/lib/user-error';
+import {
+ UserError,
+ UserErrorType,
+ isUserErrorResponse,
+} from '@/lib/server-error-handling/user-error';
import { FaRegQuestionCircle } from 'react-icons/fa';
export type ProcessExportTypes = ProcessExportOptions['type'] | 'pdf';
diff --git a/src/management-system-v2/components/share-modal/share-helpers.ts b/src/management-system-v2/components/share-modal/share-helpers.ts
index 572f29d6c..21cedb316 100644
--- a/src/management-system-v2/components/share-modal/share-helpers.ts
+++ b/src/management-system-v2/components/share-modal/share-helpers.ts
@@ -4,7 +4,7 @@ import {
updateProcessGuestAccessRights,
} from '@/lib/sharing/process-sharing';
import { wrapServerCall } from '@/lib/wrap-server-call';
-import { isUserErrorResponse } from '@/lib/user-error';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
export function updateShare(
{
diff --git a/src/management-system-v2/lib/data/db/engines.ts b/src/management-system-v2/lib/data/db/engines.ts
index 118961e74..f411eca86 100644
--- a/src/management-system-v2/lib/data/db/engines.ts
+++ b/src/management-system-v2/lib/data/db/engines.ts
@@ -3,6 +3,7 @@ import { toCaslResource } from '@/lib/ability/caslAbility';
import db from '@/lib/data/db';
import { SpaceEngineInput, SpaceEngineInputSchema } from '@/lib/space-engine-schema';
import { SystemAdmin } from '@prisma/client';
+import { ok, err } from 'neverthrow';
export async function getDbEngines(
environmentId: string | null,
@@ -11,13 +12,13 @@ export async function getDbEngines(
) {
// engines without an environmentId are PROCEED engines
if (environmentId === null && systemAdmin !== 'dont-check' && !systemAdmin)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
const engines = await db.engine.findMany({
where: { environmentId: environmentId },
});
- return ability ? ability.filter('view', 'Machine', engines) : engines;
+ return ok(ability ? ability.filter('view', 'Machine', engines) : engines);
}
export async function getDbEngineById(
@@ -28,7 +29,7 @@ export async function getDbEngineById(
) {
// engines without an environmentId are PROCEED engines
if (environmentId === null && systemAdmin !== 'dont-check' && !systemAdmin)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
const engine = await db.engine.findUnique({
where: {
@@ -37,16 +38,16 @@ export async function getDbEngineById(
},
});
- if (!engine) return undefined;
+ if (!engine) return ok(undefined);
if (
ability &&
!ability.can('view', toCaslResource('Machine', engine), { environmentId: environmentId! })
) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- return engine;
+ return ok(engine);
}
export async function getDbEngineByAddress(
@@ -57,7 +58,7 @@ export async function getDbEngineByAddress(
) {
// engines without an environmentId are PROCEED engines
if (spaceId === null && systemAdmin !== 'dont-check' && !systemAdmin)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
const engine = await db.engine.findFirst({
where: {
@@ -67,17 +68,17 @@ export async function getDbEngineByAddress(
});
if (!engine) {
- if (ability && !ability.can('view', 'Machine')) throw new UnauthorizedError();
- return undefined;
+ if (ability && !ability.can('view', 'Machine')) return err(new UnauthorizedError());
+ return ok(undefined);
}
if (
ability &&
!ability.can('view', toCaslResource('Machine', engine), { environmentId: spaceId! })
)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
- return engine;
+ return ok(engine);
}
const SpaceEngineArraySchema = SpaceEngineInputSchema.array();
@@ -89,15 +90,17 @@ export async function addDbEngines(
) {
// engines without an environmentId are PROCEED engines
if (environmentId === null && systemAdmin !== 'dont-check' && !systemAdmin)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
const newEngines = SpaceEngineArraySchema.parse(enginesInput);
- if (ability && !ability.can('create', 'Machine')) throw new UnauthorizedError();
+ if (ability && !ability.can('create', 'Machine')) return err(new UnauthorizedError());
- return db.engine.createMany({
- data: newEngines.map((e) => ({ ...e, environmentId: environmentId ?? null })),
- });
+ return ok(
+ db.engine.createMany({
+ data: newEngines.map((e) => ({ ...e, environmentId: environmentId ?? null })),
+ }),
+ );
}
const PartialSpaceEngineInputSchema = SpaceEngineInputSchema.partial();
@@ -110,26 +113,28 @@ export async function updateDbEngine(
) {
// engines without an environmentId are PROCEED engines
if (environmentId === null && systemAdmin !== 'dont-check' && !systemAdmin)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
const newEngineData = PartialSpaceEngineInputSchema.parse(engineInput);
if (ability) {
const engine = await getDbEngineById(engineId, environmentId, ability, systemAdmin);
- if (!engine) throw new Error('Engine not found');
+ if (!engine) return err(new Error('Engine not found'));
if (
!ability.can('update', toCaslResource('Machine', engine), { environmentId: environmentId! })
)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- return await db.engine.update({
- data: newEngineData,
- where: {
- environmentId,
- id: engineId,
- },
- });
+ return ok(
+ await db.engine.update({
+ data: newEngineData,
+ where: {
+ environmentId,
+ id: engineId,
+ },
+ }),
+ );
}
export async function deleteSpaceEngine(
@@ -140,21 +145,23 @@ export async function deleteSpaceEngine(
) {
// engines without an environmentId are PROCEED engines
if (environmentId === null && systemAdmin !== 'dont-check' && !systemAdmin)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
if (ability) {
const engine = await getDbEngineById(engineId, environmentId, ability, systemAdmin);
- if (!engine) throw new Error('Engine not found');
+ if (!engine) return err(new Error('Engine not found'));
if (
!ability.can('delete', toCaslResource('Machine', engine), { environmentId: environmentId! })
)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- return await db.engine.delete({
- where: {
- environmentId: environmentId,
- id: engineId,
- },
- });
+ return ok(
+ await db.engine.delete({
+ where: {
+ environmentId: environmentId,
+ id: engineId,
+ },
+ }),
+ );
}
diff --git a/src/management-system-v2/lib/data/db/folders.ts b/src/management-system-v2/lib/data/db/folders.ts
index d4359b55b..b2cab9181 100644
--- a/src/management-system-v2/lib/data/db/folders.ts
+++ b/src/management-system-v2/lib/data/db/folders.ts
@@ -1,8 +1,9 @@
+import { ok, err, Result } from 'neverthrow';
import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
import { Folder, FolderInput, FolderSchema, FolderUserInput } from '../folder-schema';
import { toCaslResource } from '@/lib/ability/caslAbility';
import { v4 } from 'uuid';
-import { Process, ProcessMetadata } from '../process-schema';
+import { ProcessMetadata } from '../process-schema';
import db from '@/lib/data/db';
import { getProcess } from './process';
import { Prisma } from '@prisma/client';
@@ -16,14 +17,14 @@ export async function getRootFolder(environmentId: string, ability?: Ability) {
});
if (!rootFolder) {
- throw new Error(`MS Error: environment ${environmentId} has no root folder`);
+ return err(new Error(`MS Error: environment ${environmentId} has no root folder`));
}
if (ability && !ability.can('view', toCaslResource('Folder', rootFolder))) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- return rootFolder;
+ return ok(rootFolder);
}
export async function getFolderById(folderId: string, ability?: Ability) {
@@ -37,21 +38,21 @@ export async function getFolderById(folderId: string, ability?: Ability) {
});
if (!folder) {
- throw new Error('Folder not found');
+ return err(new Error('Folder not found'));
}
if (ability && !ability.can('view', toCaslResource('Folder', folder))) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- return folder;
+ return ok(folder);
}
export async function getFolders(spaceId?: string) {
const selection = await db.folder.findMany({
where: { environmentId: spaceId },
});
- return selection;
+ return ok(selection);
}
export async function getFolderChildren(folderId: string, ability?: Ability) {
@@ -66,59 +67,64 @@ export async function getFolderChildren(folderId: string, ability?: Ability) {
});
if (!folder) {
- throw new Error('Folder not found');
+ return err(new Error('Folder not found'));
}
if (ability && !ability.can('view', toCaslResource('Folder', folder))) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
const combinedResults = [
...folder.childrenFolder.map((child) => ({ ...child, type: 'folder' })),
...folder.processes.map((process) => ({ ...process, type: process.type.toLowerCase() })),
];
- return combinedResults;
+ return ok(combinedResults);
}
export async function getFolderContents(folderId: string, ability?: Ability) {
const folderChildren = await getFolderChildren(folderId, ability);
+ if (folderChildren.isErr()) return folderChildren;
+
const folderContent: ((Folder & { type: 'folder' }) | ProcessMetadata)[] = [];
- for (let i = 0; i < folderChildren.length; i++) {
+ for (let i = 0; i < folderChildren.value.length; i++) {
try {
- const child = folderChildren[i];
+ const child = folderChildren.value[i];
if (child.type !== 'folder') {
- const process = (await getProcess(child.id)) as unknown as Process;
+ const process = await getProcess(child.id);
+ if (process.isErr()) return process;
+
// NOTE: this check should probably done inside inside getprocess
- if (ability && !ability.can('view', toCaslResource('Process', process))) continue;
- folderContent.push(process);
+ if (ability && !ability.can('view', toCaslResource('Process', process.value))) continue;
+ folderContent.push(process.value as ProcessMetadata);
} else {
- folderContent.push({ ...(await getFolderById(child.id, ability)), type: 'folder' });
+ const folder = await getFolderById(child.id, ability);
+ if (folder.isErr()) return folder;
+
+ folderContent.push({ ...folder.value, type: 'folder' });
}
} catch (e) {}
}
- return folderContent;
+ return ok(folderContent);
}
-export async function createFolder(
+// This is needed to inferr the return type
+async function _createFolder(
folderInput: FolderInput,
- ability?: Ability,
- tx?: Prisma.TransactionClient,
-): Promise {
- if (!tx) {
- return await db.$transaction(async (trx: Prisma.TransactionClient) => {
- return await createFolder(folderInput, ability, trx);
- });
- }
+ ability: Ability | undefined,
+ tx: Prisma.TransactionClient,
+) {
+ const folderParseResult = FolderSchema.safeParse(folderInput);
+ if (!folderParseResult.success) return err(folderParseResult.error);
- const folder = FolderSchema.parse(folderInput);
+ const folder = folderParseResult.data;
if (!folder.id) folder.id = v4();
// Checks
if (ability && !ability.can('create', toCaslResource('Folder', folder)))
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
const existingFolder = await db.folder.findUnique({
where: {
@@ -126,7 +132,7 @@ export async function createFolder(
},
});
if (existingFolder) {
- throw new Error('Folder already exists');
+ return err(new Error('Folder already exists'));
}
if (folder.parentId) {
@@ -137,12 +143,13 @@ export async function createFolder(
});
if (!parentFolder) {
- throw new Error('Parent folder does not exist');
+ return err(new Error('Parent folder does not exist'));
}
if (parentFolder.environmentId !== folder.environmentId) {
- throw new Error('Parent folder is in a different environment');
+ return err(new Error('Parent folder is in a different environment'));
}
+
await tx.folder.update({
where: {
id: folder.parentId,
@@ -160,7 +167,7 @@ export async function createFolder(
});
if (rootFolder) {
- throw new Error(`Environment ${folder.environmentId} already has a root folder`);
+ return err(new Error(`Environment ${folder.environmentId} already has a root folder`));
}
}
@@ -176,7 +183,20 @@ export async function createFolder(
},
});
- return createdFolder;
+ return ok(createdFolder);
+}
+export async function createFolder(
+ folderInput: FolderInput,
+ ability?: Ability,
+ tx?: Prisma.TransactionClient,
+) {
+ if (!tx) {
+ return await db.$transaction(async (trx: Prisma.TransactionClient) => {
+ return await _createFolder(folderInput, ability, trx);
+ });
+ } else {
+ return _createFolder(folderInput, ability, tx);
+ }
}
/** Deletes a folder and every child recursively */
@@ -187,18 +207,18 @@ export async function deleteFolder(folderId: string, ability?: Ability) {
});
if (!folderToDelete) {
- throw new Error('Folder not found');
+ return err(new Error('Folder not found'));
}
if (ability && !ability.can('delete', toCaslResource('Folder', folderToDelete))) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
await db.folder.delete({
where: { id: folderId },
});
- return { success: true };
+ return ok();
}
export async function updateFolderMetaData(
@@ -211,15 +231,15 @@ export async function updateFolderMetaData(
});
if (!folder) {
- throw new Error('Folder not found');
+ return err(new Error('Folder not found'));
}
if (ability && !ability.can('update', toCaslResource('Folder', folder))) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
if (newMetaDataInput.environmentId && newMetaDataInput.environmentId !== folder.environmentId) {
- throw new Error('environmentId cannot be changed');
+ return err(new Error('environmentId cannot be changed'));
}
const updatedFolder = await db.folder.update({
@@ -227,17 +247,17 @@ export async function updateFolderMetaData(
data: { ...newMetaDataInput, lastEditedOn: new Date() },
});
- return updatedFolder;
+ return ok(updatedFolder);
}
-async function isInSubtree(rootId: string, nodeId: string) {
+async function isInSubtree(rootId: string, nodeId: string): Promise> {
const folderData = await db.folder.findUnique({
where: { id: rootId },
include: { childrenFolder: true },
});
if (!folderData) {
- throw new Error('RootId not found');
+ return err(new Error('RootId not found'));
}
const nodeFolder = await db.folder.findUnique({
@@ -245,16 +265,20 @@ async function isInSubtree(rootId: string, nodeId: string) {
});
if (!nodeFolder) {
- throw new Error('NodeId not found');
+ return err(new Error('NodeId not found'));
}
if (rootId === nodeId) {
- return true;
+ return ok(true);
}
+
for (const child of folderData.childrenFolder) {
- if (await isInSubtree(child.id, nodeId)) return true;
+ const recursiveCallResult = await isInSubtree(child.id, nodeId);
+ if (recursiveCallResult.isErr()) return recursiveCallResult;
+
+ if (recursiveCallResult.value) return ok(true);
}
- return false;
+ return ok(false);
}
export async function moveFolder(folderId: string, newParentId: string, ability?: Ability) {
@@ -264,11 +288,11 @@ export async function moveFolder(folderId: string, newParentId: string, ability?
});
if (!folder) {
- throw new Error('Folder not found');
+ return err(new Error('Folder not found'));
}
if (!folder.parentId) {
- throw new Error('Root folders cannot be moved');
+ return err(new Error('Root folders cannot be moved'));
}
if (folder.parentId === newParentId) {
@@ -280,11 +304,11 @@ export async function moveFolder(folderId: string, newParentId: string, ability?
});
if (!newParentFolder) {
- throw new Error('New parent folder not found');
+ return err(new Error('New parent folder not found'));
}
if (newParentFolder.environmentId !== folder.environmentId) {
- throw new Error('Cannot move folder to a different environment');
+ return err(new Error('Cannot move folder to a different environment'));
}
// Check permissions
@@ -296,12 +320,12 @@ export async function moveFolder(folderId: string, newParentId: string, ability?
ability.can('update', toCaslResource('Folder', folder.parentFolder!))
)
) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
// Check if moving to its own subtree
if (await isInSubtree(folderId, newParentId)) {
- throw new Error('Folder cannot be moved to its children');
+ return err(new Error('Folder cannot be moved to its children'));
}
// Update folder
@@ -321,9 +345,9 @@ export async function moveProcess(processId: string, newParentId: string, abilit
where: { id: processId },
});
- if (!process) throw new Error('Folder not found');
+ if (!process) return err(new Error('Folder not found'));
- if (process.folderId === newParentId) return;
+ if (process.folderId === newParentId) return ok();
const [oldParentFolder, newParentFolder] = await Promise.all([
db.folder.findUnique({
@@ -335,10 +359,10 @@ export async function moveProcess(processId: string, newParentId: string, abilit
}),
]);
- if (!newParentFolder) throw new Error('New parent folder not found');
+ if (!newParentFolder) return err(new Error('New parent folder not found'));
if (newParentFolder.environmentId !== process.environmentId)
- throw new Error('Cannot move folder to a different environment');
+ return err(new Error('Cannot move folder to a different environment'));
// Check permissions
if (
@@ -349,7 +373,7 @@ export async function moveProcess(processId: string, newParentId: string, abilit
ability.can('update', toCaslResource('Folder', oldParentFolder!))
)
) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
// Update process
diff --git a/src/management-system-v2/lib/data/db/html-forms.ts b/src/management-system-v2/lib/data/db/html-forms.ts
index 0039271cd..d40e935ab 100644
--- a/src/management-system-v2/lib/data/db/html-forms.ts
+++ b/src/management-system-v2/lib/data/db/html-forms.ts
@@ -1,7 +1,8 @@
+import { ok, err } from 'neverthrow';
import Ability from '@/lib/ability/abilityHelper';
import db from '@/lib/data/db';
import { HtmlForm, HtmlFormMetaDataSchema, HtmlFormSchema } from '@/lib/html-form-schema';
-import { UserFacingError } from '@/lib/user-error';
+import { UserFacingError } from '@/lib/server-error-handling/user-error';
/**
* Returns all html forms in an environment
@@ -27,8 +28,13 @@ export async function getHtmlForms(environmentId: string, ability?: Ability) {
//TODO: use ability
// return ability ? ability.filter('view', 'Html Form', spaceForms) : spaceForms;
+ const parseResult = HtmlFormMetaDataSchema.array().safeParse(spaceForms);
- return HtmlFormMetaDataSchema.array().parse(spaceForms);
+ if (parseResult.success) {
+ return ok(parseResult.data);
+ } else {
+ return err(parseResult.error);
+ }
}
export async function getHtmlForm(formId: string) {
@@ -53,15 +59,26 @@ export async function getHtmlForm(formId: string) {
});
if (!form) {
- throw new UserFacingError(`Html form with id ${formId} does not exist!`);
+ return err(new UserFacingError(`Html form with id ${formId} does not exist!`));
}
- return HtmlFormSchema.parse(form);
+ const parseResult = HtmlFormSchema.safeParse(form);
+
+ if (parseResult.success) {
+ return ok(parseResult.data);
+ } else {
+ return err(parseResult.error);
+ }
}
/** Handles adding a html form */
export async function addHtmlForm(formInput: HtmlForm) {
- const form = HtmlFormSchema.parse(formInput);
+ const parseResult = HtmlFormSchema.safeParse(formInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+
+ const form = parseResult.data;
// check if there is an id collision
const existingForm = await db.htmlForm.findUnique({
@@ -70,7 +87,7 @@ export async function addHtmlForm(formInput: HtmlForm) {
},
});
if (existingForm) {
- throw new Error(`Html form with id ${formInput.id} already exists!`);
+ return err(new Error(`Html form with id ${formInput.id} already exists!`));
}
// save form info
@@ -81,7 +98,12 @@ export async function addHtmlForm(formInput: HtmlForm) {
/** Updates an existing form */
export async function updateHtmlForm(formId: string, newInfoInput: Partial) {
- const formInput = HtmlFormSchema.partial().parse(newInfoInput);
+ const parseResult = HtmlFormSchema.partial().safeParse(newInfoInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+
+ const formInput = parseResult.data;
const existingForm = await db.htmlForm.findUnique({
where: {
@@ -90,7 +112,7 @@ export async function updateHtmlForm(formId: string, newInfoInput: Partial {
await tx.space.update({
@@ -69,7 +80,7 @@ export async function activateEnvrionment(environmentId: string, userId: string)
[
{
environmentId,
- roleId: adminRole.id,
+ roleId: adminRole.value.id,
userId,
},
],
@@ -79,27 +90,32 @@ export async function activateEnvrionment(environmentId: string, userId: string)
});
}
-export async function addEnvironment(
+export const addEnvironment = ensureTransactionWrapper(_addEnvironment, 2);
+async function _addEnvironment(
environmentInput: EnvironmentInput,
ability?: Ability,
- tx?: Prisma.TransactionClient,
-): Promise {
- // If `tx` is provided, use it; otherwise, start a new transaction
- if (!tx) {
- return db.$transaction(async (trx) => {
- return await addEnvironment(environmentInput, ability, trx);
- });
- }
+ _tx?: Prisma.TransactionClient,
+) {
+ const tx = _tx!;
- const dbMutator = tx;
+ const parseResult = environmentSchema.safeParse(environmentInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const newEnvironment = parseResult.data;
- const newEnvironment = environmentSchema.parse(environmentInput);
const id = newEnvironment.isOrganization ? newEnvironment.id ?? v4() : newEnvironment.ownerId;
- if (await getEnvironmentById(id)) throw new Error('Environment id already exists');
+ const existingEnvironment = await getEnvironmentById(id);
+ if (existingEnvironment.isErr()) {
+ return existingEnvironment;
+ }
+ if (existingEnvironment.value) {
+ return err(new Error('Environment id already exists'));
+ }
const newEnvironmentWithId = { ...newEnvironment, id };
- await dbMutator.space.create({ data: { ...newEnvironmentWithId } });
+ await tx.space.create({ data: { ...newEnvironmentWithId } });
if (newEnvironment.isOrganization) {
const adminRole = await addRole(
@@ -112,7 +128,9 @@ export async function addEnvironment(
undefined,
tx,
);
- await addRole(
+ if (adminRole.isErr()) return adminRole;
+
+ const guestRole = await addRole(
{
environmentId: id,
name: '@guest',
@@ -122,7 +140,9 @@ export async function addEnvironment(
undefined,
tx,
);
- await addRole(
+ if (guestRole.isErr()) return guestRole;
+
+ const everyoneRole = await addRole(
{
environmentId: id,
name: '@everyone',
@@ -132,26 +152,29 @@ export async function addEnvironment(
undefined,
tx,
);
+ if (everyoneRole.isErr()) return everyoneRole;
if (newEnvironment.isActive) {
- await addMember(id, newEnvironment.ownerId, undefined, tx);
+ const ownerAdded = await addMember(id, newEnvironment.ownerId, undefined, tx);
+ if (ownerAdded?.isErr()) return ownerAdded;
- await addRoleMappings(
+ const adminRoleMapping = await addRoleMappings(
[
{
environmentId: id,
- roleId: adminRole.id,
+ roleId: adminRole.value.id,
userId: newEnvironment.ownerId,
},
],
undefined,
tx,
);
+ if (adminRoleMapping?.isErr()) return adminRoleMapping;
}
}
// add root folder
- await createFolder(
+ const rootFolder = await createFolder(
{
environmentId: id,
name: '',
@@ -161,21 +184,24 @@ export async function addEnvironment(
undefined,
tx,
);
+ if (rootFolder.isErr()) return rootFolder;
- return newEnvironmentWithId;
+ return ok(newEnvironmentWithId);
}
export async function deleteEnvironment(environmentId: string, ability?: Ability) {
const environment = await getEnvironmentById(environmentId);
- if (!environment) throw new Error('Environment not found');
+ if (environment.isErr() || !environment.value) return err(new Error('Environment not found'));
- if (env.PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE && environment.isOrganization) {
- throw new Error(
- 'Organizations cannot be deleted when PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE is true',
+ if (env.PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE && environment.value.isOrganization) {
+ return err(
+ new Error(
+ 'Organizations cannot be deleted when PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE is true',
+ ),
);
}
- if (ability && !ability.can('delete', 'Environment')) throw new UnauthorizedError();
+ if (ability && !ability.can('delete', 'Environment')) return err(new UnauthorizedError());
await db.space.delete({
where: { id: environmentId },
});
@@ -189,25 +215,36 @@ export async function updateOrganization(
ability?: Ability,
) {
const environment = await getEnvironmentById(environmentId, ability, { throwOnNotFound: true });
-
- if (!environment) {
- throw new Error('Environment not found');
+ if (environment.isErr()) {
+ return environment;
+ }
+ if (!environment.value) {
+ return err(new Error('Environment not found'));
}
if (
ability &&
!ability.can('update', toCaslResource('Environment', environment), { environmentId })
)
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
- if (!environment.isOrganization) throw new Error('Environment is not an organization');
+ if (!environment.value.isOrganization)
+ return err(new Error('Environment is not an organization'));
- const update = UserOrganizationEnvironmentInputSchema.partial().parse(environmentInput);
- const newEnvironmentData: Environment = { ...environment, ...update } as Environment;
+ const updateParseResult =
+ UserOrganizationEnvironmentInputSchema.partial().safeParse(environmentInput);
+ if (!updateParseResult.success) {
+ return err(updateParseResult.error);
+ }
- await db.space.update({ where: { id: environment.id }, data: { ...newEnvironmentData } });
+ const newEnvironmentData: Environment = {
+ ...environment.value,
+ ...updateParseResult.data,
+ } as Environment;
- return newEnvironmentData;
+ await db.space.update({ where: { id: environment.value.id }, data: { ...newEnvironmentData } });
+
+ return ok(newEnvironmentData);
}
// TODO below: implement db logic
@@ -216,27 +253,32 @@ export async function saveSpaceLogo(organizationId: string, image: Buffer, abili
const organization = await getEnvironmentById(organizationId, undefined, {
throwOnNotFound: true,
});
- if (!organization?.isOrganization)
- throw new Error("You can't save a logo for a personal environment");
+ if (organization.isErr()) {
+ return organization;
+ }
+ if (!organization.value?.isOrganization)
+ return err(new Error("You can't save a logo for a personal environment"));
if (ability && ability.can('update', 'Environment', { environmentId: organizationId }))
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
try {
//saveLogo(organizationId, image);
- } catch (err) {
- throw new Error('Failed to store image');
+ } catch (error) {
+ return err(new Error('Failed to store image'));
}
}
export async function getSpaceLogo(organizationId: string) {
try {
- return await db.space.findUnique({
- where: { id: organizationId },
- select: { spaceLogo: true },
- });
- } catch (err) {
- return undefined;
+ return ok(
+ await db.space.findUnique({
+ where: { id: organizationId },
+ select: { spaceLogo: true },
+ }),
+ );
+ } catch (error) {
+ return err(error);
}
}
@@ -246,9 +288,10 @@ export async function spaceHasLogo(organizationId: string) {
select: { spaceLogo: true },
});
if (res?.spaceLogo) {
- return true;
+ return ok(true);
}
- return false;
+
+ return ok(false);
}
export async function deleteSpaceLogo(organizationId: string) {
diff --git a/src/management-system-v2/lib/data/db/iam/memberships.ts b/src/management-system-v2/lib/data/db/iam/memberships.ts
index 44ce5167c..7ccc17a1f 100644
--- a/src/management-system-v2/lib/data/db/iam/memberships.ts
+++ b/src/management-system-v2/lib/data/db/iam/memberships.ts
@@ -1,11 +1,13 @@
+import { ok, err } from 'neverthrow';
import { z } from 'zod';
import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
import { getEnvironmentById } from './environments';
import { v4 } from 'uuid';
-import { ActiveOrganizationEnvironment, Environment } from '../../environment-schema.js';
+import { ActiveOrganizationEnvironment } from '../../environment-schema.js';
import db from '@/lib/data/db';
import { Prisma } from '@prisma/client';
import { UserHasToDeleteOrganizationsError } from './users';
+import { ensureTransactionWrapper } from '../util';
const MembershipInputSchema = z.object({
userId: z.string(),
@@ -19,36 +21,25 @@ export type Membership = MembershipInput & {
createdOn: string;
};
-function isOrganization(environment: Environment, opts: { throwIfNotFound?: boolean } = {}) {
- if (!environment)
- if (opts.throwIfNotFound) throw new Error('Environment not found');
- else return false;
-
- if (!environment.isOrganization)
- if (opts.throwIfNotFound)
- throw new Error("Environment isn't an organization, it can't have members");
- else return false;
-
- return true;
-}
-
export async function getUserOrganizationEnvironments(userId: string) {
- return (
- await db.space.findMany({
- where: {
- isOrganization: true,
- //ownerId: userId,
- members: {
- some: {
- userId: userId,
+ return ok(
+ (
+ await db.space.findMany({
+ where: {
+ isOrganization: true,
+ //ownerId: userId,
+ members: {
+ some: {
+ userId: userId,
+ },
},
},
- },
- select: {
- id: true,
- },
- })
- ).map((workspace) => workspace.id);
+ select: {
+ id: true,
+ },
+ })
+ ).map((workspace) => workspace.id),
+ );
}
export async function getMembers(environmentId: string, ability?: Ability) {
@@ -64,8 +55,8 @@ export async function getMembers(environmentId: string, ability?: Ability) {
members: true,
},
});
- if (!workspace) throw new Error('Environment not found');
- return workspace.members;
+ if (!workspace) return err(new Error('Environment not found'));
+ return ok(workspace.members);
}
export async function getFullMembersWithRoles(environmentId: string, ability?: Ability) {
@@ -126,7 +117,7 @@ export async function getUsersInSpace(spaceId: string, ability?: Ability) {
},
});
- return users;
+ return ok(users);
}
export async function isMember(
@@ -134,11 +125,14 @@ export async function isMember(
userId: string,
tx?: Prisma.TransactionClient,
) {
- const dbMutator = tx || db;
+ const dbMutator = tx!;
const environment = await getEnvironmentById(environmentId, undefined, undefined, tx);
- if (!environment?.isOrganization) {
- return userId === environmentId;
+ if (environment.isErr()) {
+ return environment;
+ }
+ if (!environment.value?.isOrganization) {
+ return ok(userId === environmentId);
}
const membership = await dbMutator.membership.findFirst({
where: {
@@ -146,7 +140,7 @@ export async function isMember(
userId: userId,
},
});
- return membership ? true : false;
+ return ok(membership ? true : false);
}
export async function addMember(
@@ -172,8 +166,8 @@ export async function addMember(
where: { id: userId },
});
- if (!user) throw new Error('User not found');
- if (user.isGuest) throw new Error('Guest users cannot be added to environments');
+ if (!user) return err(new Error('User not found'));
+ if (user.isGuest) return err(new Error('Guest users cannot be added to environments'));
await dbMutator.membership.create({
data: {
@@ -185,18 +179,13 @@ export async function addMember(
});
}
-export async function removeMember(
+export const removeMember = ensureTransactionWrapper(_removeMember, 2);
+async function _removeMember(
environmentId: string,
userId: string,
ability?: Ability,
_tx?: Prisma.TransactionClient,
-): Promise {
- if (!_tx) {
- return db.$transaction(async (tx) => {
- await removeMember(environmentId, userId, ability, tx);
- });
- }
-
+) {
const tx = _tx!;
const environment = await tx.space.findUnique({
@@ -205,7 +194,7 @@ export async function removeMember(
});
if (!environment) {
- throw new Error('Environment not found');
+ return err(new Error('Environment not found'));
}
const organization = environment as ActiveOrganizationEnvironment;
@@ -213,8 +202,11 @@ export async function removeMember(
if (ability) ability;
const memberExists = await isMember(environmentId, userId, tx);
- if (!memberExists) {
- throw new Error('User is not a member of this environment');
+ if (memberExists.isErr()) {
+ return memberExists;
+ }
+ if (!memberExists.value) {
+ return err(new Error('User is not a member of this environment'));
}
const adminRole = await tx.role.findFirst({
@@ -231,11 +223,13 @@ export async function removeMember(
},
});
if (!adminRole)
- throw new Error(`Consistency error: admin role of environment ${environmentId} not found`);
+ return err(
+ new Error(`Consistency error: admin role of environment ${environmentId} not found`),
+ );
if (adminRole.members.find((role) => role.userId === userId)) {
if (adminRole.members.length === 1) {
- throw new UserHasToDeleteOrganizationsError([environmentId]);
+ return err(new UserHasToDeleteOrganizationsError([environmentId]));
}
if (organization.ownerId === userId) {
@@ -253,4 +247,6 @@ export async function removeMember(
userId: userId,
},
});
+
+ return ok();
}
diff --git a/src/management-system-v2/lib/data/db/iam/role-mappings.ts b/src/management-system-v2/lib/data/db/iam/role-mappings.ts
index ad287e4d5..0ce141dd2 100644
--- a/src/management-system-v2/lib/data/db/iam/role-mappings.ts
+++ b/src/management-system-v2/lib/data/db/iam/role-mappings.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { v4 } from 'uuid';
import { getRoleById } from './roles';
import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
@@ -7,7 +8,8 @@ import { getUserById } from './users';
import { getEnvironmentById } from './environments';
import db from '@/lib/data/db';
import { Prisma } from '@prisma/client';
-import { UserFacingError } from '@/lib/user-error';
+import { UserFacingError } from '@/lib/server-error-handling/user-error';
+import { ensureTransactionWrapper } from '../util';
const RoleMappingInputSchema = z.object({
roleId: z.string(),
@@ -44,7 +46,7 @@ export async function getRoleMappings(ability?: Ability, environmentId?: string)
})),
);
- return ability ? ability.filter('view', 'RoleMapping', roleMappings) : roleMappings;
+ return ok(ability ? ability.filter('view', 'RoleMapping', roleMappings) : roleMappings);
}
/** Returns a role mapping by user id */
@@ -96,31 +98,40 @@ export async function getRoleMappingByUserId(
memberCreatedOn: role.members[0].createdOn,
}));
- return ability ? ability.filter('view', 'RoleMapping', userRoleMappings) : userRoleMappings;
+ const roleMappings = ability
+ ? ability.filter('view', 'RoleMapping', userRoleMappings)
+ : userRoleMappings;
+ return ok(roleMappings);
}
// TODO: also check if user exists?
/** Adds a user role mapping */
-export async function addRoleMappings(
+export const addRoleMappings = ensureTransactionWrapper(_addRoleMappings, 2);
+export async function _addRoleMappings(
roleMappingsInput: RoleMappingInput[],
ability?: Ability,
_tx?: Prisma.TransactionClient,
-): Promise {
+) {
if (ability && !ability.can('admin', 'All')) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- if (!_tx) {
- return db.$transaction(async (tx) => {
- return await addRoleMappings(roleMappingsInput, ability, tx);
- });
- }
const tx = _tx!;
- const roleMappings = roleMappingsInput.map((roleMappingInput) =>
- RoleMappingInputSchema.parse(roleMappingInput),
+ const roleMappingsParseResults = roleMappingsInput.map((roleMappingInput) =>
+ RoleMappingInputSchema.safeParse(roleMappingInput),
);
+ type ParsedRoleMapping = z.infer;
+ const parseError = roleMappingsParseResults.find((result) => !result.success && result) as
+ | z.SafeParseError
+ | undefined;
+ if (parseError) return err(parseError.error);
+
+ const roleMappings = (
+ roleMappingsParseResults as unknown as z.SafeParseSuccess[]
+ ).map((mapping) => mapping.data);
+
const allowedRoleMappings = ability
? ability.filter('create', 'RoleMapping', roleMappings)
: roleMappings;
@@ -129,26 +140,33 @@ export async function addRoleMappings(
const { roleId, userId, environmentId } = roleMapping;
const environment = await getEnvironmentById(environmentId, undefined, undefined, tx);
- if (!environment) throw new Error(`Environment ${environmentId} doesn't exist`);
- if (!environment.isOrganization) {
- throw new UserFacingError('Cannot add role mapping to personal environment');
+ if (environment.isErr()) {
+ return environment;
+ }
+ if (!environment.value) return err(new Error(`Environment ${environmentId} doesn't exist`));
+ if (!environment.value.isOrganization) {
+ return err(new UserFacingError('Cannot add role mapping to personal environment'));
}
const role = await getRoleById(roleId, undefined, tx);
- if (!role) throw new UserFacingError('Role not found');
+ if (role.isErr()) {
+ return role;
+ }
+ if (!role.value) return err(new UserFacingError('Role not found'));
- if (role.name === '@everyone' || role.name === '@guest') {
- throw new UserFacingError(`Cannot add role mappings to ${role.name} role`);
+ if (role.value.name === '@everyone' || role.value.name === '@guest') {
+ return err(new UserFacingError(`Cannot add role mappings to ${role.value.name} role`));
}
const user = await getUserById(userId, undefined, tx);
- if (!user) throw new Error('User not found');
- if (user.isGuest) throw new UserFacingError('Guests cannot have role mappings');
+ if (user.isErr()) return user;
+ if (!user.value) return err(new Error('User not found'));
+ if (user.value.isGuest) return err(new UserFacingError('Guests cannot have role mappings'));
const existingRoleMapping = await tx.roleMember.findFirst({
where: { roleId, userId },
});
- if (existingRoleMapping) throw new UserFacingError('Role mapping already exists');
+ if (existingRoleMapping) return err(new UserFacingError('Role mapping already exists'));
const id = v4();
const createdOn = new Date().toISOString();
@@ -183,7 +201,7 @@ export async function deleteRoleMapping(
ability?: Ability,
) {
if (ability && !ability.can('admin', 'All')) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
const [environment, user, roleMapping, role] = await Promise.all([
@@ -192,34 +210,43 @@ export async function deleteRoleMapping(
getRoleMappingByUserId(userId, environmentId, ability, roleId),
getRoleById(roleId),
]);
- if (!environment) throw new Error("Environment doesn't exist");
+ if (environment.isErr()) return environment;
+ if (!environment.value) return err(new Error("Environment doesn't exist"));
- if (!user) throw new Error("User doesn't exist");
+ if (user.isErr()) return user;
+ if (!user.value) return err(new Error("User doesn't exist"));
- if (!roleMapping[0]) throw new Error("Role mapping doesn't exist");
+ if (role.isErr()) return role;
+
+ if (roleMapping.isErr()) return roleMapping;
+ if (!roleMapping.value[0]) return err(new Error("Role mapping doesn't exist"));
// Check ability
if (
ability &&
!ability.can('delete', toCaslResource('RoleMapping', roleMapping), { environmentId })
) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- if (role!.name === '@admin') {
+ if (role.value!.name === '@admin') {
const memberIds = await db.roleMember.findMany({
where: { roleId },
select: { userId: true },
});
if (memberIds.length === 1) {
- throw new UserFacingError(
- 'Cannot remove user from @admin role, at least one user must be in the role.',
+ return err(
+ new UserFacingError(
+ 'Cannot remove user from @admin role, at least one user must be in the role.',
+ ),
);
}
}
await db.roleMember.delete({
- where: { id: roleMapping[0].id },
+ where: { id: roleMapping.value[0].id },
});
+
+ return ok();
}
diff --git a/src/management-system-v2/lib/data/db/iam/roles.ts b/src/management-system-v2/lib/data/db/iam/roles.ts
index d07131af1..c3507347a 100644
--- a/src/management-system-v2/lib/data/db/iam/roles.ts
+++ b/src/management-system-v2/lib/data/db/iam/roles.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { v4 } from 'uuid';
import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
import { toCaslResource } from '@/lib/ability/caslAbility';
@@ -14,7 +15,7 @@ export async function getRoles(environmentId?: string, ability?: Ability) {
const filteredRoles = ability ? ability.filter('view', 'Role', roles) : roles;
- return filteredRoles as Role[];
+ return ok(filteredRoles as Role[]);
}
/** Returns all roles in form of an array including the members of each role included in its data */
@@ -49,7 +50,7 @@ export async function getRolesWithMembers(environmentId?: string, ability?: Abil
.map((role) => ({ ...role, members: ability.filter('view', 'User', role.members) }))
: mappedRoles;
- return filteredRoles;
+ return ok(filteredRoles);
}
/**
@@ -65,13 +66,13 @@ export async function getRoleByName(environmentId: string, name: string, ability
},
});
- if (!role) return undefined;
+ if (!role) return ok(undefined);
if (ability && !ability.can('view', toCaslResource('Role', role))) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
- return role;
+ return ok(role);
}
/**
@@ -91,11 +92,12 @@ export async function getRoleById(
},
});
- if (!ability) return role as Role;
+ if (!ability) return ok(role as Role);
- if (role && !ability.can('view', toCaslResource('Role', role))) throw new UnauthorizedError();
+ if (role && !ability.can('view', toCaslResource('Role', role)))
+ return err(new UnauthorizedError());
- return role as Role;
+ return ok(role as Role | null);
}
/**
@@ -125,21 +127,23 @@ export async function getRoleWithMembersById(roleId: string, ability?: Ability)
},
});
- if (!role) return null;
+ if (!role) return ok(null);
const mappedRole = {
...role,
members: role.members.map((member) => member.user),
} as RoleWithMembers;
- if (!ability) return mappedRole;
+ if (!ability) return ok(mappedRole);
if (mappedRole && !ability.can('view', toCaslResource('Role', mappedRole)))
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
- return ability
+ const filteredRoles = ability
? { ...mappedRole, members: ability.filter('view', 'User', mappedRole.members) }
: mappedRole;
+
+ return ok(filteredRoles);
}
/**
@@ -159,7 +163,7 @@ export async function getUserRoles(userId: string, environmentId?: string, abili
const filteredRoles = ability ? ability.filter('view', 'Role', roles) : roles;
- return filteredRoles as Role[];
+ return ok(filteredRoles as Role[]);
}
/**
@@ -175,7 +179,12 @@ export async function addRole(
) {
const dbMutator = tx ? tx : db;
- const roleRepresentation = RoleInputSchema.parse(roleRepresentationInput);
+ const parseResult = RoleInputSchema.safeParse(roleRepresentationInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+
+ const roleRepresentation = parseResult.data;
// if (ability && !ability.can('create', toCaslResource('Role', roleRepresentation))) {
if (
@@ -183,7 +192,7 @@ export async function addRole(
(!ability.can('create', toCaslResource('Role', roleRepresentation)) ||
!ability.can('admin', 'All'))
) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
const { name, description, note, permissions, expiration, environmentId } = roleRepresentation;
@@ -197,7 +206,7 @@ export async function addRole(
});
if (existingRole) {
- throw new Error('Role already exists');
+ return err(new Error('Role already exists'));
}
const createdOn = new Date().toISOString();
@@ -219,7 +228,7 @@ export async function addRole(
},
});
- return createdRole as Role;
+ return ok(createdRole as Role);
}
/**
@@ -234,21 +243,28 @@ export async function updateRole(
ability: Ability,
) {
const targetRole = await getRoleById(roleId);
- if (!targetRole) throw new Error('Role not found');
+ if (targetRole.isErr()) return targetRole;
+ if (!targetRole.value) return err(new Error('Role not found'));
- const roleRepresentation = RoleInputSchema.partial().parse(roleRepresentationInput);
+ const parseResult = RoleInputSchema.partial().safeParse(roleRepresentationInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const roleRepresentation = parseResult.data;
// Casl isn't really built to check the value of input fields when updating, so we have to perform this two checks
if (
!(
ability.checkInputFields(toCaslResource('Role', targetRole), 'update', roleRepresentation) &&
ability.can('create', toCaslResource('Role', roleRepresentation), {
- environmentId: targetRole.environmentId,
+ environmentId: targetRole.value.environmentId,
})
) ||
!ability.can('admin', 'All')
- )
- throw new UnauthorizedError();
+ ) {
+ return err(new UnauthorizedError());
+ }
+
const updatedRole = await db.role.update({
where: {
id: roleId,
@@ -258,9 +274,10 @@ export async function updateRole(
lastEditedOn: new Date().toISOString(),
},
});
+
rulesCacheDeleteAll();
- return updatedRole as Role;
+ return ok(updatedRole as Role);
}
/**
@@ -278,7 +295,7 @@ export async function deleteRole(roleId: string, ability?: Ability) {
// Throw error if role not found
if (!role) {
- throw new Error('Role not found');
+ return err(new Error('Role not found'));
}
// Check if user has permission to delete the role
@@ -286,7 +303,7 @@ export async function deleteRole(roleId: string, ability?: Ability) {
ability &&
(!ability.can('delete', toCaslResource('Role', role)) || !ability.can('admin', 'All'))
) {
- throw new UnauthorizedError();
+ return err(new UnauthorizedError());
}
// Delete role from database
@@ -296,5 +313,5 @@ export async function deleteRole(roleId: string, ability?: Ability) {
},
});
- return true;
+ return ok(true);
}
diff --git a/src/management-system-v2/lib/data/db/iam/system-admins.ts b/src/management-system-v2/lib/data/db/iam/system-admins.ts
index de5ef62bd..d6315fce2 100644
--- a/src/management-system-v2/lib/data/db/iam/system-admins.ts
+++ b/src/management-system-v2/lib/data/db/iam/system-admins.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { v4 } from 'uuid';
import {
SystemAdmin,
@@ -7,21 +8,20 @@ import {
SystemAdminUpdateInput,
SystemAdminUpdateInputSchema,
} from '../../system-admin-schema';
-import { enableUseDB } from 'FeatureFlags';
import db from '@/lib/data/db';
import { Prisma } from '@prisma/client';
export async function getSystemAdmins() {
const sysAdmins = await db.systemAdmin.findMany({});
- return sysAdmins as SystemAdmin[];
+ return ok(sysAdmins as SystemAdmin[]);
}
export async function getAdminById(id: string) {
- return await db.systemAdmin.findUnique({ where: { id: id } });
+ return ok(await db.systemAdmin.findUnique({ where: { id: id } }));
}
export async function getSystemAdminByUserId(userId: string) {
- return (await db.systemAdmin.findUnique({ where: { userId: userId } })) as SystemAdmin;
+ return ok((await db.systemAdmin.findUnique({ where: { userId: userId } })) as SystemAdmin);
}
export async function addSystemAdmin(
@@ -42,7 +42,7 @@ export async function addSystemAdmin(
await dbMutator.systemAdmin.create({ data: { ...admin } });
- return admin;
+ return ok(admin);
}
export async function updateSystemAdmin(
@@ -51,23 +51,31 @@ export async function updateSystemAdmin(
) {
// TODO: decide if permissions should be checkded here
const adminData = await getAdminById(adminId);
- if (!adminData) throw new Error('System admin not found');
+ if (adminData.isErr()) {
+ return adminData;
+ }
+ if (!adminData.value) return err(new Error('System admin not found'));
+ const parseResult = SystemAdminUpdateInputSchema.partial().safeParse(adminUpdate);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
const newAdminData = {
- ...adminData,
- ...SystemAdminUpdateInputSchema.partial().parse(adminUpdate),
+ ...adminData.value,
+ ...parseResult.data,
lastEditedOn: new Date(),
} as SystemAdmin;
await db.systemAdmin.update({ where: { id: adminId }, data: { ...newAdminData } });
- return newAdminData;
+ return ok(newAdminData);
}
export async function deleteSystemAdmin(adminId: string) {
// TODO: decide if permissions should be checkded here
const adminData = await getAdminById(adminId);
- if (!adminData) throw new Error('System admin not found');
+ if (adminData.isErr()) return adminData;
+ if (!adminData.value) return err(new Error('System admin not found'));
await db.systemAdmin.delete({ where: { id: adminId } });
}
diff --git a/src/management-system-v2/lib/data/db/iam/users.ts b/src/management-system-v2/lib/data/db/iam/users.ts
index a0099f8a6..a4c31edf3 100644
--- a/src/management-system-v2/lib/data/db/iam/users.ts
+++ b/src/management-system-v2/lib/data/db/iam/users.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { v4 } from 'uuid';
import {
User,
@@ -13,9 +14,10 @@ import { getUserOrganizationEnvironments } from './memberships';
import { getRoleMappingByUserId } from './role-mappings';
import db from '@/lib/data/db';
import { Prisma, PasswordAccount } from '@prisma/client';
-import { UserFacingError } from '@/lib/user-error';
+import { UserFacingError } from '@/lib/server-error-handling/user-error';
import { env } from '@/lib/ms-config/env-vars';
import { NextAuthEmailTakenError, NextAuthUsernameTakenError } from '@/lib/authjs-error-message';
+import { ensureTransactionWrapper } from '../util';
export async function getUsers(page: number = 1, pageSize: number = 10) {
// TODO ability check
@@ -28,7 +30,7 @@ export async function getUsers(page: number = 1, pageSize: number = 10) {
const totalUsers = await db.user.count();
const totalPages = Math.ceil(totalUsers / pageSize);
- return {
+ return ok({
users,
pagination: {
currentPage: page,
@@ -36,7 +38,7 @@ export async function getUsers(page: number = 1, pageSize: number = 10) {
totalUsers,
totalPages,
},
- };
+ });
}
export async function getUserById(
@@ -48,36 +50,39 @@ export async function getUserById(
const user = await dbMutator.user.findUnique({ where: { id: id } });
- if (!user && opts && opts.throwIfNotFound) throw new Error('User not found');
+ if (!user && opts && opts.throwIfNotFound) return err(new Error('User not found'));
- return user as User;
+ return ok(user as User);
}
-export async function getUserByEmail(email: string, opts?: { throwIfNotFound?: boolean }) {
+export async function getUserByEmail(email: string) {
const user = await db.user.findUnique({ where: { email: email } });
- if (!user && opts?.throwIfNotFound) throw new Error('User not found');
+ if (!user) return err(new Error('User not found'));
- return user as User;
+ return ok(user as User);
}
export async function getUserByUsername(username: string, opts?: { throwIfNotFound?: boolean }) {
const user = await db.user.findUnique({ where: { username } });
- if (!user && opts?.throwIfNotFound) throw new Error('User not found');
+ if (!user && opts?.throwIfNotFound) return err(new Error('User not found'));
- return user as User;
+ return ok(user as User);
}
-export async function addUser(
+export const addUser = ensureTransactionWrapper(_addUser, 1);
+export async function _addUser(
inputUser: OptionalKeys,
- tx?: Prisma.TransactionClient,
-): Promise {
- if (!tx) {
- return await db.$transaction(async (trx: Prisma.TransactionClient) => addUser(inputUser, trx));
- }
+ _tx?: Prisma.TransactionClient,
+) {
+ const tx = _tx!;
- const user = UserSchema.parse(inputUser);
+ const parseResult = UserSchema.safeParse(inputUser);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const user = parseResult.data;
if (!user.isGuest) {
const checks = [];
@@ -87,10 +92,10 @@ export async function addUser(
const [usernameRes, emailRes] = await Promise.all(checks);
if (usernameRes) {
- throw new NextAuthUsernameTakenError();
+ return err(new NextAuthUsernameTakenError());
}
if (emailRes) {
- throw new NextAuthEmailTakenError();
+ return err(new NextAuthEmailTakenError());
}
}
@@ -98,7 +103,7 @@ export async function addUser(
try {
const userExists = await tx.user.findUnique({ where: { id: user.id } });
- if (userExists) throw new Error('User already exists');
+ if (userExists) return err(new Error('User already exists'));
await tx.user.create({
data: {
@@ -107,8 +112,16 @@ export async function addUser(
},
});
- if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE)
- await addEnvironment({ ownerId: user.id!, isOrganization: false }, undefined, tx);
+ if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
+ const personalSpace = await addEnvironment(
+ { ownerId: user.id!, isOrganization: false },
+ undefined,
+ tx,
+ );
+ if (personalSpace.isErr()) {
+ return personalSpace;
+ }
+ }
if (user.isGuest) {
await tx.guestSignin.create({
@@ -121,7 +134,7 @@ export async function addUser(
console.error('Error adding new user: ', error);
}
- return user as User;
+ return ok(user as User);
}
export class UserHasToDeleteOrganizationsError extends Error {
@@ -134,35 +147,38 @@ export class UserHasToDeleteOrganizationsError extends Error {
}
}
-export async function deleteUser(userId: string, tx?: Prisma.TransactionClient): Promise {
- // if no tx, start own transaction
- if (!tx) {
- return await db.$transaction(async (trx: Prisma.TransactionClient) => {
- return await deleteUser(userId, trx);
- });
- }
-
- const dbMutator = tx;
+export const deleteUser = ensureTransactionWrapper(_deleteUser, 1);
+export async function _deleteUser(userId: string, tx?: Prisma.TransactionClient) {
+ const dbMutator = tx!;
const user = await db.user.findUnique({ where: { id: userId } });
- if (!user) throw new Error("User doesn't exist");
+ if (!user) return err(new Error("User doesn't exist"));
if (user.username === 'admin') {
- throw new UserFacingError('The user "admin" cannot be deleted');
+ return err(new UserFacingError('The user "admin" cannot be deleted'));
}
const userOrganizations = await getUserOrganizationEnvironments(userId);
+ if (userOrganizations.isErr()) {
+ return userOrganizations;
+ }
+
const orgsWithNoNextAdmin: string[] = [];
- for (const environmentId of userOrganizations) {
+ for (const environmentId of userOrganizations.value) {
const userRoles = await getRoleMappingByUserId(userId, environmentId);
+ if (userRoles.isErr()) {
+ return userRoles;
+ }
- if (!userRoles.find((role) => role.roleName === '@admin')) continue;
+ if (!userRoles.value.find((role) => role.roleName === '@admin')) continue;
const adminRole = await db.role.findFirst({
where: { name: '@admin', environmentId: environmentId },
include: { members: true },
});
if (!adminRole)
- throw new Error(`Consistency error: admin role of environment ${environmentId} not found`);
+ return err(
+ new Error(`Consistency error: admin role of environment ${environmentId} not found`),
+ );
if (adminRole.members.length === 1) {
orgsWithNoNextAdmin.push(environmentId);
@@ -177,7 +193,7 @@ export async function deleteUser(userId: string, tx?: Prisma.TransactionClient):
}
if (orgsWithNoNextAdmin.length > 0)
- throw new UserHasToDeleteOrganizationsError(orgsWithNoNextAdmin);
+ return err(new UserHasToDeleteOrganizationsError(orgsWithNoNextAdmin));
if (user.isGuest) {
await dbMutator.guestSignin.delete({ where: { userId: userId } });
@@ -185,7 +201,7 @@ export async function deleteUser(userId: string, tx?: Prisma.TransactionClient):
await dbMutator.user.delete({ where: { id: userId } });
- return user as User;
+ return ok(user as User);
}
export async function updateUser(
@@ -194,37 +210,47 @@ export async function updateUser(
tx?: Prisma.TransactionClient,
) {
const dbMutator = tx || db;
+
const user = await getUserById(userId, { throwIfNotFound: true });
- const isGoingToBeGuest = inputUser.isGuest !== undefined ? inputUser.isGuest : user?.isGuest;
+ if (user.isErr()) {
+ return user;
+ }
+
+ const isGoingToBeGuest =
+ inputUser.isGuest !== undefined ? inputUser.isGuest : user.value?.isGuest;
let updatedUser: Prisma.UserUpdateInput;
if (isGoingToBeGuest) {
if (inputUser.username || inputUser.lastName || inputUser.firstName || inputUser.email) {
- throw new Error('Guest users cannot update their user data');
+ return err(new Error('Guest users cannot update their user data'));
}
updatedUser = { isGuest: true };
} else {
- const newUserData = AuthenticatedUserSchema.partial().parse(inputUser);
+ const parseResult = AuthenticatedUserSchema.partial().safeParse(inputUser);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const newUserData = parseResult.data;
if (newUserData.username && newUserData.username === 'admin') {
- throw new UserFacingError('The username is already taken');
+ return err(new UserFacingError('The username is already taken'));
}
- if (!user.isGuest && user.username === 'admin' && 'username' in newUserData) {
- throw new UserFacingError('The username "admin" cannot be changed');
+ if (!user.value.isGuest && user.value.username === 'admin' && 'username' in newUserData) {
+ return err(new UserFacingError('The username "admin" cannot be changed'));
}
if (newUserData.email) {
const existingUser = await db.user.findUnique({ where: { email: newUserData.email } });
if (existingUser && existingUser.id !== userId)
- throw new UserFacingError('User with this email or username already exists');
+ return err(new UserFacingError('User with this email or username already exists'));
}
if (newUserData.username) {
const existingUser = await db.user.findUnique({ where: { username: newUserData.username } });
if (existingUser && existingUser.id !== userId)
- throw new UserFacingError('The username is already taken');
+ return err(new UserFacingError('The username is already taken'));
}
updatedUser = { ...user, ...newUserData };
@@ -235,15 +261,16 @@ export async function updateUser(
data: updatedUser,
});
- return updatedUserFromDB;
+ return ok(updatedUserFromDB);
}
export async function addOauthAccount(accountInput: Omit) {
const newAccount = OauthAccountSchema.parse(accountInput);
const user = await getUserById(newAccount.userId);
- if (!user) throw new Error('User not found');
- if (user.isGuest) throw new Error('Guest users cannot have oauth accounts');
+ if (user.isErr()) return user;
+ if (!user.value) return err(new Error('User not found'));
+ if (user.value.isGuest) return err(new Error('Guest users cannot have oauth accounts'));
const id = v4();
@@ -251,7 +278,7 @@ export async function addOauthAccount(accountInput: Omit) {
await db.oauthAccount.create({ data: account });
- return account;
+ return ok(account);
}
export async function deleteOauthAccount(id: string) {
@@ -262,12 +289,14 @@ export async function deleteOauthAccount(id: string) {
});
}
export async function getOauthAccountByProviderId(provider: string, providerAccountId: string) {
- return await db.oauthAccount.findUnique({
- where: {
- provider: provider,
- providerAccountId: providerAccountId,
- },
- });
+ return ok(
+ await db.oauthAccount.findUnique({
+ where: {
+ provider: provider,
+ providerAccountId: providerAccountId,
+ },
+ }),
+ );
}
export async function updateGuestUserLastSigninTime(
@@ -276,25 +305,28 @@ export async function updateGuestUserLastSigninTime(
tx?: Prisma.TransactionClient,
) {
const dbMutator = tx || db;
- const user = await getUserById(userId, { throwIfNotFound: true });
- if (!user.isGuest) throw new Error('User is not a guest user');
-
- return await dbMutator.guestSignin.update({
- where: { userId: userId },
- data: { lastSigninAt: date },
+ const user = await dbMutator.user.findUnique({
+ where: { id: userId },
+ select: { isGuest: true },
});
+
+ if (!user) return err(new Error('User does not exist'));
+ if (!user.isGuest) return err(new Error('User is not a guest user'));
+
+ return ok(
+ await dbMutator.guestSignin.update({
+ where: { userId: userId },
+ data: { lastSigninAt: date },
+ }),
+ );
}
-export async function deleteInactiveGuestUsers(
+export const deleteInactiveGuestUsers = ensureTransactionWrapper(_deleteInactiveGuestUsers, 1);
+export async function _deleteInactiveGuestUsers(
inactiveTimeInMS: number,
- tx?: Prisma.TransactionClient,
-): Promise<{ count: number }> {
- // if no tx, start own transaction
- if (!tx) {
- return await db.$transaction(async (trx: Prisma.TransactionClient) => {
- return await deleteInactiveGuestUsers(inactiveTimeInMS, trx);
- });
- }
+ _tx?: Prisma.TransactionClient,
+) {
+ const tx = _tx!;
const cutoff = new Date(Date.now() - inactiveTimeInMS);
const staleSignins = await tx.guestSignin.findMany({
@@ -303,7 +335,7 @@ export async function deleteInactiveGuestUsers(
},
select: { userId: true },
});
- if (staleSignins.length === 0) return { count: 0 };
+ if (staleSignins.length === 0) return ok({ count: 0 });
const userIds = staleSignins.map((s) => s.userId);
@@ -313,12 +345,14 @@ export async function deleteInactiveGuestUsers(
},
});
- return await tx.user.deleteMany({
- where: {
- id: { in: userIds },
- isGuest: true,
- },
- });
+ return ok(
+ await tx.user.deleteMany({
+ where: {
+ id: { in: userIds },
+ isGuest: true,
+ },
+ }),
+ );
}
/** Note: make sure to save a salted hash of the password */
@@ -334,7 +368,7 @@ export async function setUserPassword(
where: { id: userId },
include: { passwordAccount: true },
});
- if (!user) throw new Error('User not found');
+ if (!user) return err(new Error('User not found'));
if (user.passwordAccount) {
await dbMutator.passwordAccount.update({
@@ -346,14 +380,17 @@ export async function setUserPassword(
data: { userId, password: passwordHash, isTemporaryPassword },
});
}
+ return ok();
}
export async function getUserPassword(userId: string, tx?: Prisma.TransactionClient) {
const dbMutator = tx || db;
- return await dbMutator.passwordAccount.findUnique({
- where: { userId },
- });
+ return ok(
+ await dbMutator.passwordAccount.findUnique({
+ where: { userId },
+ }),
+ );
}
/** returns null if the user exists but has no password */
@@ -368,7 +405,7 @@ export async function getUserAndPasswordByUsername(
include: { passwordAccount: true },
});
- if (!userAndPassword) return null;
- if (!userAndPassword.passwordAccount) return null;
- return userAndPassword as typeof userAndPassword & { passwordAccount: PasswordAccount };
+ if (!userAndPassword) return ok(null);
+ if (!userAndPassword.passwordAccount) return ok(null);
+ return ok(userAndPassword as typeof userAndPassword & { passwordAccount: PasswordAccount });
}
diff --git a/src/management-system-v2/lib/data/db/iam/verification-tokens.ts b/src/management-system-v2/lib/data/db/iam/verification-tokens.ts
index 9e35cf222..cbedff185 100644
--- a/src/management-system-v2/lib/data/db/iam/verification-tokens.ts
+++ b/src/management-system-v2/lib/data/db/iam/verification-tokens.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { z } from 'zod';
import db from '@/lib/data/db';
import { Prisma } from '@prisma/client';
@@ -34,12 +35,14 @@ export async function getEmailVerificationToken({
token: string;
identifier: string;
}) {
- return (await db.emailVerificationToken.findFirst({
- where: {
- token,
- identifier,
- },
- })) as EmailVerificationToken | null;
+ return ok(
+ (await db.emailVerificationToken.findFirst({
+ where: {
+ token,
+ identifier,
+ },
+ })) as EmailVerificationToken | null,
+ );
}
export async function deleteEmailVerificationToken({
@@ -53,17 +56,23 @@ export async function deleteEmailVerificationToken({
where: { token, identifier },
});
- if (!result) throw new Error('Token not found');
+ if (!result) return err(new Error('Token not found'));
- return result;
+ return ok(result);
}
export async function saveEmailVerificationToken(tokenInput: EmailVerificationToken) {
- const token = emailVerificationTokenSchemam.parse(tokenInput);
+ const parseResult = emailVerificationTokenSchemam.safeParse(tokenInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const token = parseResult.data;
- return await db.emailVerificationToken.create({
- data: token,
- });
+ return ok(
+ await db.emailVerificationToken.create({
+ data: token,
+ }),
+ );
}
export async function updateEmailVerificationTokenExpiration(
@@ -76,10 +85,12 @@ export async function updateEmailVerificationTokenExpiration(
) {
const mutator = tx || db;
- return await mutator.emailVerificationToken.update({
- where: tokenIdentifier,
- data: {
- expires: newExpiration,
- },
- });
+ return ok(
+ await mutator.emailVerificationToken.update({
+ where: tokenIdentifier,
+ data: {
+ expires: newExpiration,
+ },
+ }),
+ );
}
diff --git a/src/management-system-v2/lib/data/db/process.ts b/src/management-system-v2/lib/data/db/process.ts
index 713a1f0ee..646daea70 100644
--- a/src/management-system-v2/lib/data/db/process.ts
+++ b/src/management-system-v2/lib/data/db/process.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { getFolderById } from './folders';
import eventHandler from '../legacy/eventHandler.js';
import logger from '../legacy/logging.js';
@@ -26,6 +27,7 @@ import { copyFile, retrieveFile } from '../file-manager/file-manager';
import { generateProcessFilePath } from '@/lib/helpers/fileManagerHelpers';
import { Prisma } from '@prisma/client';
import { getUsedImagesFromJson } from '@/components/html-form-editor/serialized-format-utils';
+import { ensureTransactionWrapper } from './util';
/**
* Returns all processes in an environment
@@ -61,7 +63,7 @@ export async function getProcesses(environmentId: string, ability?: Ability, inc
//TODO: add pagination
- return ability ? ability.filter('view', 'Process', spaceProcesses) : spaceProcesses;
+ return ok(ability ? ability.filter('view', 'Process', spaceProcesses) : spaceProcesses);
}
export async function getProcess(processDefinitionsId: string, includeBPMN = false) {
@@ -92,7 +94,7 @@ export async function getProcess(processDefinitionsId: string, includeBPMN = fal
},
});
if (!process) {
- throw new Error(`Process with id ${processDefinitionsId} could not be found!`);
+ return err(new Error(`Process with id ${processDefinitionsId} could not be found!`));
}
// Convert BigInt fields to number
@@ -120,9 +122,11 @@ export async function getProcess(processDefinitionsId: string, includeBPMN = fal
// TODO: implement inEditingBy
};
- return convertedProcess as typeof convertedProcess & {
- inEditingBy?: { id: string; task?: string }[];
- };
+ return ok(
+ convertedProcess as typeof convertedProcess & {
+ inEditingBy?: { id: string; task?: string }[];
+ },
+ );
}
/**
@@ -140,9 +144,9 @@ export async function checkIfProcessExists(
},
});
if (!existingProcess && throwError) {
- throw new Error(`Process with id ${processDefinitionsId} does not exist!`);
+ return err(new Error(`Process with id ${processDefinitionsId} does not exist!`));
}
- return existingProcess;
+ return ok(existingProcess);
}
export async function checkIfProcessAlreadyExistsForAUserInASpaceByName(
@@ -163,9 +167,9 @@ export async function checkIfProcessAlreadyExistsForAUserInASpaceByName(
},
});
- return !!existingProcess;
+ return ok(!!existingProcess);
} catch (err: any) {
- throw new Error('Error checking if process exists by name:', err.message);
+ return err(new Error('Error checking if process exists by name:', err.message));
}
}
@@ -198,32 +202,30 @@ export async function checkIfProcessAlreadyExistsForAUserInASpaceByNameWithBatch
const existingSet = new Set(existingProcesses.map((p) => `${p.name}:::${p.folderId}`));
// Return an array of booleans per process
- return processes.map(({ name, folderId }) => {
- return existingSet.has(`${name}:::${folderId}`);
- });
+ return ok(processes.map(({ name, folderId }) => existingSet.has(`${name}:::${folderId}`)));
} catch (err: any) {
- throw new Error(`Error checking process names in batch: ${err.message}`);
+ return err(new Error(`Error checking process names in batch: ${err.message}`));
}
}
/** Handles adding a process, makes sure all necessary information gets parsed from bpmn */
-export async function addProcess(
+export const addProcess = ensureTransactionWrapper(_addProcess, 2);
+export async function _addProcess(
processInput: ProcessServerInput & { bpmn: string },
referencedProcessId?: string,
- tx?: Prisma.TransactionClient,
-): Promise {
- if (!tx) {
- return await db.$transaction(async (trx: Prisma.TransactionClient) => {
- return await addProcess(processInput, referencedProcessId, trx);
- });
- }
- const { bpmn } = processInput;
-
- const processData = ProcessServerInputSchema.parse(processInput);
+ _tx?: Prisma.TransactionClient,
+) {
+ const tx = _tx!;
+ const { bpmn } = processInput;
if (!bpmn) {
- throw new Error("Can't create a process without a bpmn!");
+ return err(new Error("Can't create a process without a bpmn!"));
}
+ const parseResult = ProcessServerInputSchema.safeParse(processInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const processData = parseResult.data;
// create meta info object
const metadata = {
@@ -233,11 +235,14 @@ export async function addProcess(
};
if (!metadata.folderId) {
- metadata.folderId = (await getRootFolder(metadata.environmentId)).id;
+ const rootFolder = await getRootFolder(metadata.environmentId);
+ if (rootFolder.isErr()) return rootFolder;
+ metadata.folderId = rootFolder.value.id;
}
const folderData = await getFolderById(metadata.folderId);
- if (!folderData) throw new Error('Folder not found');
+ if (folderData.isErr()) return folderData;
+ if (!folderData) return err(new Error('Folder not found'));
// TODO check folder permissions here, they're checked in movefolder,
// but by then the folder was already created
@@ -250,7 +255,7 @@ export async function addProcess(
},
});
if (existingProcess) {
- throw new Error(`Process with id ${processDefinitionsId} already exists!`);
+ return err(new Error(`Process with id ${processDefinitionsId} already exists!`));
}
const bpmnWithPlaceholders = await transformBpmnAttributes(
@@ -259,32 +264,28 @@ export async function addProcess(
);
// save process info
- try {
- await tx.process.create({
- data: {
- id: metadata.id,
- originalId: metadata.originalId ?? '',
- name: metadata.name,
- description: metadata.description,
- createdOn: new Date().toISOString(),
- lastEditedOn: new Date().toISOString(),
- type: metadata.type,
- processIds: { set: metadata.processIds },
- folderId: metadata.folderId,
- sharedAs: metadata.sharedAs,
- shareTimestamp: metadata.shareTimestamp,
- allowIframeTimestamp: metadata.allowIframeTimestamp,
- environmentId: metadata.environmentId,
- creatorId: metadata.creatorId,
- userDefinedId: metadata.userDefinedId,
- //departments: { set: metadata.departments },
- //variables: { set: metadata.variables },
- bpmn: bpmnWithPlaceholders,
- },
- });
- } catch (error) {
- console.error('Error adding new process: ', error);
- }
+ await tx.process.create({
+ data: {
+ id: metadata.id,
+ originalId: metadata.originalId ?? '',
+ name: metadata.name,
+ description: metadata.description,
+ createdOn: new Date().toISOString(),
+ lastEditedOn: new Date().toISOString(),
+ type: metadata.type,
+ processIds: { set: metadata.processIds },
+ folderId: metadata.folderId,
+ sharedAs: metadata.sharedAs,
+ shareTimestamp: metadata.shareTimestamp,
+ allowIframeTimestamp: metadata.allowIframeTimestamp,
+ environmentId: metadata.environmentId,
+ creatorId: metadata.creatorId,
+ userDefinedId: metadata.userDefinedId,
+ //departments: { set: metadata.departments },
+ //variables: { set: metadata.variables },
+ bpmn: bpmnWithPlaceholders,
+ },
+ });
//if referencedProcessId is present, the process was copied from a shared process
if (referencedProcessId) {
@@ -302,18 +303,27 @@ export async function addProcess(
eventHandler.dispatch('processAdded', { process: metadata });
- return metadata;
+ return ok(metadata as ProcessMetadata);
}
/** Updates an existing process with the given bpmn */
-export async function updateProcess(
+export const updateProcess = ensureTransactionWrapper(_updateProcess, 2);
+export async function _updateProcess(
processDefinitionsId: string,
newInfoInput: Partial & { bpmn?: string },
+ _tx?: Prisma.TransactionClient,
) {
const { bpmn: newBpmn } = newInfoInput;
- const newInfo = ProcessServerInputSchema.partial().parse(newInfoInput);
- checkIfProcessExists(processDefinitionsId);
- const currentParent = (await getProcess(processDefinitionsId)).folderId;
+ const parseResult = ProcessServerInputSchema.partial().safeParse(newInfoInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const newInfo = parseResult.data;
+
+ const process = await getProcess(processDefinitionsId);
+ if (process.isErr()) return process;
+
+ const currentParent = process.value.folderId;
let metaChanges = {
...newInfo,
@@ -329,11 +339,16 @@ export async function updateProcess(
// Update folders
if (metaChanges.folderId && metaChanges.folderId !== currentParent) {
- moveProcess({ processDefinitionsId, newFolderId: metaChanges.folderId });
+ const moveResult = await moveProcess({
+ processDefinitionsId,
+ newFolderId: metaChanges.folderId,
+ });
+ if (moveResult?.isErr()) return moveResult;
//delete metaChanges.folderId;
}
const newMetaData = await updateProcessMetaData(processDefinitionsId, metaChanges);
+ if (newMetaData?.isErr()) return newMetaData;
if (newBpmn) {
try {
await db.process.update({
@@ -350,7 +365,7 @@ export async function updateProcess(
});
}
- return newMetaData;
+ return ok(newMetaData);
}
export async function moveProcess({
@@ -369,11 +384,14 @@ export async function moveProcess({
const dbMutator = tx || db;
try {
const process = await getProcess(processDefinitionsId);
+ if (process.isErr()) {
+ return process;
+ }
if (!process) {
- throw new Error('Process not found');
+ return err(new Error('Process not found'));
}
- const oldFolderId = process.folderId;
+ const oldFolderId = process.value.folderId;
const [oldFolder, newFolder] = await Promise.all([
db.folder.findUnique({
where: { id: oldFolderId! },
@@ -386,22 +404,22 @@ export async function moveProcess({
]);
if (!oldFolder) {
- throw new Error("Consistency Error: Process' old folder not found");
+ return err(new Error("Consistency Error: Process' old folder not found"));
}
if (!newFolder) {
- throw new Error('New folder not found');
+ return err(new Error('New folder not found'));
}
// Permission checks
if (
ability &&
!(
- ability.can('update', toCaslResource('Process', process)) &&
+ ability.can('update', toCaslResource('Process', process.value)) &&
ability.can('update', toCaslResource('Folder', oldFolder)) &&
ability.can('update', toCaslResource('Folder', newFolder))
)
) {
- throw new Error('Unauthorized');
+ return err(new Error('Unauthorized'));
}
// Update process' folderId in the database
@@ -411,9 +429,10 @@ export async function moveProcess({
folderId: newFolderId,
},
});
- return updatedProcess;
+ return ok(updatedProcess);
} catch (error) {
console.error('Error moving process:', error);
+ return err(error);
}
}
@@ -424,7 +443,12 @@ export async function updateProcessMetaData(
metaChanges: Partial>,
) {
try {
- const existingProcess = await checkIfProcessExists(processDefinitionsId);
+ const existingProcess = await db.process.findUnique({
+ where: {
+ id: processDefinitionsId,
+ },
+ select: { originalId: true },
+ });
const updatedProcess = await db.process.update({
where: { id: processDefinitionsId },
@@ -440,38 +464,41 @@ export async function updateProcessMetaData(
updatedInfo: updatedProcess,
});
- return updatedProcess;
+ return ok(updatedProcess);
} catch (error) {
console.error('Error updating process metadata:', error);
+ return err(error);
}
}
/** Removes an existing process */
-export async function removeProcess(processDefinitionsId: string, tx?: Prisma.TransactionClient) {
- if (!tx) {
- return await db.$transaction(async (trx: Prisma.TransactionClient) => {
- await removeProcess(processDefinitionsId, trx);
- });
- }
+export const removeProcess = ensureTransactionWrapper(_removeProcess, 1);
+export async function _removeProcess(processDefinitionsId: string, _tx?: Prisma.TransactionClient) {
+ try {
+ const tx = _tx!;
- const process = await tx.process.findUnique({
- where: { id: processDefinitionsId },
- include: { artifactProcessReferences: { include: { artifact: true } } },
- });
+ const process = await tx.process.findUnique({
+ where: { id: processDefinitionsId },
+ include: { artifactProcessReferences: { include: { artifact: true } } },
+ });
- if (!process) {
- throw new Error(`Process with id: ${processDefinitionsId} not found`);
- }
+ if (!process) {
+ return err(new Error(`Process with id: ${processDefinitionsId} not found`));
+ }
- await Promise.all(
- process.artifactProcessReferences.map((artifactRef) => {
- return deleteProcessArtifact(artifactRef.artifact.filePath, true, processDefinitionsId, tx);
- }),
- );
+ await Promise.all(
+ process.artifactProcessReferences.map((artifactRef) => {
+ return deleteProcessArtifact(artifactRef.artifact.filePath, true, processDefinitionsId, tx);
+ }),
+ );
- await tx.process.delete({ where: { id: processDefinitionsId } });
+ await tx.process.delete({ where: { id: processDefinitionsId } });
- eventHandler.dispatch('processRemoved', { processDefinitionsId });
+ eventHandler.dispatch('processRemoved', { processDefinitionsId });
+ } catch (error) {
+ console.error(error);
+ return err(error);
+ }
}
/** Stores a new version of an existing process */
@@ -486,28 +513,32 @@ export async function addProcessVersion(
let versionInformation = await getDefinitionsVersionInformation(bpmn);
if (!versionInformation) {
- throw new Error('The given bpmn does not contain a version.');
+ return err(new Error('The given bpmn does not contain a version.'));
}
const existingProcess = await getProcess(processDefinitionsId);
+ if (existingProcess.isErr()) {
+ return existingProcess;
+ }
if (!existingProcess) {
- // TODO: create the process and use the given version as the "HEAD"
- throw new Error('The process for which you try to create a version does not exist');
+ return err(new Error('The process for which you try to create a version does not exist'));
}
if (
- existingProcess.type !== 'project' &&
+ existingProcess.value.type !== 'project' &&
(!versionInformation.name || !versionInformation.description)
) {
- throw new Error(
- 'A bpmn that should be stored as a version of a process has to contain both a version name and a version description!',
+ return err(
+ new Error(
+ 'A bpmn that should be stored as a version of a process has to contain both a version name and a version description!',
+ ),
);
}
// don't add a version a second time
if (
- existingProcess.versions.some(
+ existingProcess.value.versions.some(
({ createdOn }) => toCustomUTCString(createdOn) == versionInformation.versionCreatedOn,
)
) {
@@ -523,7 +554,7 @@ export async function addProcessVersion(
);
if (!res.filePath) {
- throw new Error('Error saving version bpmn');
+ return err(new Error('Error saving version bpmn'));
}
try {
@@ -577,14 +608,17 @@ export async function addProcessVersion(
}
}
- await versionProcessArtifactRefs(processDefinitionsId, version.id);
+ const versionResult = await versionProcessArtifactRefs(processDefinitionsId, version.id);
+ if (versionResult?.isErr()) {
+ return versionResult;
+ }
} catch (error) {
console.error('Error creating version: ', error);
- throw new Error('Error creating the version');
+ return err(new Error('Error creating the version'));
}
// add information about the new version to the meta information and inform others about its existence
- const newVersions = existingProcess.versions ? [...existingProcess.versions] : [];
+ const newVersions = existingProcess.value.versions ? [...existingProcess.value.versions] : [];
//@ts-ignore
newVersions.push(versionInformation);
@@ -594,22 +628,25 @@ export async function addProcessVersion(
/** Returns the bpmn of a specific process version */
export async function getProcessVersionBpmn(processDefinitionsId: string, versionId: string) {
let existingProcess = await getProcess(processDefinitionsId);
+ if (existingProcess.isErr()) {
+ return existingProcess;
+ }
if (!existingProcess) {
- throw new Error('The process for which you try to get a version does not exist');
+ return err(new Error('The process for which you try to get a version does not exist'));
}
- const existingVersion = existingProcess.versions?.find(
+ const existingVersion = existingProcess.value.versions?.find(
(existingVersionInfo) => existingVersionInfo.id === versionId,
);
if (!existingVersion) {
- throw new Error('The version you are trying to get does not exist');
+ return err(new Error('The version you are trying to get does not exist'));
}
const versn = await db.version.findUnique({
where: { id: versionId },
});
- return ((await retrieveFile(versn?.bpmnFilePath!, false)) as Buffer).toString('utf8');
+ return ok(((await retrieveFile(versn?.bpmnFilePath!, false)) as Buffer).toString('utf8'));
}
/** Removes information from the meta data that would not be correct after a restart */
@@ -639,7 +676,7 @@ export async function getProcessBpmn(processDefinitionsId: string) {
});
if (!process) {
- throw new Error('Process not found');
+ return err(new Error('Process not found'));
}
const processWithStringDate = {
@@ -650,10 +687,10 @@ export async function getProcessBpmn(processDefinitionsId: string) {
processWithStringDate!,
BpmnAttributeType.ACTUAL_VALUE,
);
- return bpmnWithDBValue;
- } catch (err) {
- logger.debug(`Error reading bpmn of process. Reason:\n${err}`);
- throw new Error('Unable to find process bpmn!');
+ return ok(bpmnWithDBValue);
+ } catch (error) {
+ logger.debug(`Error reading bpmn of process. Reason:\n${error}`);
+ return err(new Error('Unable to find process bpmn!'));
}
}
@@ -674,7 +711,10 @@ export async function getProcessHtmlFormJSON(
fileName: string,
ignoreDeletedStatus = false,
) {
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId);
+ if (check.isErr()) {
+ return check;
+ }
try {
let artifact;
@@ -701,11 +741,13 @@ export async function getProcessHtmlFormJSON(
}
if (artifact) {
const jsonAsBuffer = (await retrieveFile(artifact.filePath, true)) as Buffer;
- return jsonAsBuffer.toString('utf8');
+ return ok(jsonAsBuffer.toString('utf8'));
+ } else {
+ return ok();
}
- } catch (err) {
- logger.debug(`Error getting data of process html form ${fileName}. Reason\n${err}`);
- throw new Error(`Unable to get data for process html form ${fileName}!`);
+ } catch (error) {
+ logger.debug(`Error getting data of process html form ${fileName}. Reason\n${error}`);
+ return err(new Error(`Unable to get data for process html form ${fileName}!`));
}
}
@@ -717,10 +759,10 @@ export async function checkIfHtmlFormExists(processDefinitionsId: string, fileNa
const htmlArtifact = await db.artifact.findUnique({
where: { fileName: `${fileName}.html` },
});
- return jsonArtifact || htmlArtifact ? { json: jsonArtifact, html: htmlArtifact } : null;
+ return ok(jsonArtifact || htmlArtifact ? { json: jsonArtifact, html: htmlArtifact } : null);
} catch (error) {
console.error(`Error checking if html form ${fileName} exists:`, error);
- throw new Error(`Failed to check if html form ${fileName} exists.`);
+ return err(new Error(`Failed to check if html form ${fileName} exists.`));
}
}
@@ -754,15 +796,19 @@ export async function checkIfScriptTaskFileExists(
const artifact = await db.artifact.findUnique({
where: { fileName: scriptFilenameWithExtension },
});
- return artifact;
+ return ok(artifact);
} catch (error) {
console.error('Error checking if script task file exists:', error);
- throw new Error('Failed to check if script task file exists.');
+ return err(new Error('Failed to check if script task file exists.'));
}
}
export async function getHtmlForm(processDefinitionsId: string, fileName: string) {
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId);
+ if (check.isErr()) {
+ return check;
+ }
+
try {
const res = await db.artifact.findFirst({
where: {
@@ -788,19 +834,23 @@ export async function getHtmlForm(processDefinitionsId: string, fileName: string
});
if (!res) {
- throw new Error(`Unable to get html for ${fileName} from the database!`);
+ return err(new Error(`Unable to get html for ${fileName} from the database!`));
}
const html = (await retrieveFile(res.filePath, false)).toString('utf-8');
- return html;
- } catch (err) {
- logger.debug(`Error getting html for ${fileName} from the database. Reason:\n${err}`);
- throw new Error('Unable to get html for start form!');
+ return ok(html);
+ } catch (error) {
+ logger.debug(`Error getting html for ${fileName} from the database. Reason:\n${error}`);
+ return err(new Error('Unable to get html for start form!'));
}
}
export async function getProcessScriptTaskScript(processDefinitionsId: string, fileName: string) {
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId);
+ if (check.isErr()) {
+ return check;
+ }
+
try {
const res = await db.artifact.findFirst({
where: {
@@ -826,14 +876,14 @@ export async function getProcessScriptTaskScript(processDefinitionsId: string, f
});
if (!res) {
- throw new Error('Unable to get script for script task!');
+ return err(new Error('Unable to get script for script task!'));
}
const script = (await retrieveFile(res.filePath, false)).toString('utf-8');
- return script;
- } catch (err) {
- logger.debug(`Error getting script of script task. Reason:\n${err}`);
- throw new Error('Unable to get script for script task!');
+ return ok(script);
+ } catch (error) {
+ logger.debug(`Error getting script of script task. Reason:\n${error}`);
+ return err(new Error('Unable to get script for script task!'));
}
}
@@ -846,9 +896,16 @@ export async function saveProcessHtmlForm(
updateImageReferences = false,
) {
// TODO: Use a transaction to avoid storing inconsistent states in case of errors
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId);
+ if (check.isErr()) {
+ return check;
+ }
+
try {
const res = await checkIfHtmlFormExists(processDefinitionsId, fileName);
+ if (res.isErr()) {
+ return res;
+ }
const content = new TextEncoder().encode(json);
// The code that creates versions, already handles creating new references
@@ -856,11 +913,12 @@ export async function saveProcessHtmlForm(
let newUsedImages = getUsedImagesFromJson(JSON.parse(json));
let removedImages = new Set();
- if (res?.json) {
+ if (res.value?.json) {
const oldJson = await getProcessHtmlFormJSON(processDefinitionsId, fileName);
- if (!oldJson) throw new Error(`Couldn't get JSON for user task ${fileName}`);
+ if (oldJson.isErr()) return oldJson;
+ if (!oldJson.value) return err(new Error(`Couldn't get JSON for user task ${fileName}`));
- const oldUsedImages = getUsedImagesFromJson(JSON.parse(oldJson));
+ const oldUsedImages = getUsedImagesFromJson(JSON.parse(oldJson.value));
for (const oldImage of oldUsedImages) {
if (!newUsedImages.has(oldImage)) removedImages.add(oldImage);
@@ -888,7 +946,7 @@ export async function saveProcessHtmlForm(
{
generateNewFileName: false,
versionCreatedOn,
- replaceFileContentOnly: res?.json?.filePath ? true : false,
+ replaceFileContentOnly: res.value?.json?.filePath ? true : false,
context: 'html-forms',
},
);
@@ -901,15 +959,15 @@ export async function saveProcessHtmlForm(
{
generateNewFileName: false,
versionCreatedOn: versionCreatedOn,
- replaceFileContentOnly: res?.html?.filePath ? true : false,
+ replaceFileContentOnly: res.value?.html?.filePath ? true : false,
context: 'html-forms',
},
);
- return filePath;
- } catch (err) {
- logger.debug(`Error storing html form data for ${fileName}. Reason:\n${err}`);
- throw new Error('Failed to store the html form data.');
+ return ok(filePath);
+ } catch (error) {
+ logger.debug(`Error storing html form data for ${fileName}. Reason:\n${error}`);
+ return err(new Error('Failed to store the html form data.'));
}
}
@@ -919,9 +977,16 @@ export async function saveProcessScriptTask(
script: string,
versionCreatedOn?: string,
) {
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId);
+ if (check.isErr()) {
+ return check;
+ }
+
try {
const res = await checkIfScriptTaskFileExists(processDefinitionsId, filenameWithExtension);
+ if (res.isErr()) {
+ return res;
+ }
await saveProcessArtifact(
processDefinitionsId,
@@ -931,35 +996,43 @@ export async function saveProcessScriptTask(
{
generateNewFileName: false,
versionCreatedOn: versionCreatedOn,
- replaceFileContentOnly: res?.filePath ? true : false,
+ replaceFileContentOnly: res.value?.filePath ? true : false,
context: 'script-tasks',
},
);
- return filenameWithExtension;
- } catch (err) {
- logger.debug(`Error storing script task data. Reason:\n${err}`);
- throw new Error('Failed to store the script task data');
+ return ok(filenameWithExtension);
+ } catch (error) {
+ logger.debug(`Error storing script task data. Reason:\n${error}`);
+ return err(new Error('Failed to store the script task data'));
}
}
/** Removes a stored user task from disk */
export async function deleteHtmlForm(processDefinitionsId: string, fileName: string) {
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId);
+ if (check.isErr()) {
+ return check;
+ }
+
try {
const res = await checkIfHtmlFormExists(processDefinitionsId, fileName);
+ if (res.isErr()) {
+ return res;
+ }
let isDeleted = false;
- if (res?.json) {
- isDeleted = await deleteProcessArtifact(res.json.filePath, true);
+ if (res.value?.json) {
+ isDeleted = await deleteProcessArtifact(res.value.json.filePath, true);
}
- if (res?.html) {
- isDeleted = (await deleteProcessArtifact(res.html.filePath, true)) || isDeleted;
+ if (res.value?.html) {
+ isDeleted = (await deleteProcessArtifact(res.value.html.filePath, true)) || isDeleted;
}
- return isDeleted;
- } catch (err) {
- logger.debug(`Error removing html form data. Reason:\n${err}`);
+ return ok(isDeleted);
+ } catch (error) {
+ logger.debug(`Error removing html form data. Reason:\n${error}`);
+ return err(error);
}
}
@@ -968,14 +1041,25 @@ export async function deleteProcessScriptTask(
processDefinitionsId: string,
taskFileNameWithExtension: string,
) {
- checkIfProcessExists(processDefinitionsId);
+ const processExists = await checkIfProcessExists(processDefinitionsId);
+ if (processExists.isErr()) {
+ return processExists;
+ }
+
+ // Not sure what should be returned here
+ if (!processExists.value) return;
+
try {
const res = await checkIfScriptTaskFileExists(processDefinitionsId, taskFileNameWithExtension);
- if (res) {
- return await deleteProcessArtifact(res.filePath, true);
+ if (res.isErr()) {
+ return res;
}
- } catch (err) {
- logger.debug(`Error removing script task file. Reason:\n${err}`);
+ if (res.value) {
+ return ok(await deleteProcessArtifact(res.value?.filePath, true));
+ }
+ } catch (error) {
+ logger.debug(`Error removing script task file. Reason:\n${error}`);
+ return err(error);
}
}
@@ -999,7 +1083,7 @@ export async function copyProcessArtifactReferences(
}),
);
} catch (error) {
- throw new Error('error copying process artifact references');
+ return err(new Error('error copying process artifact references'));
}
}
@@ -1020,7 +1104,7 @@ export async function versionProcessArtifactRefs(processId: string, versionId: s
}),
);
} catch (error) {
- throw new Error('error copying process artifact references');
+ return err(new Error('error copying process artifact references'));
}
}
@@ -1075,25 +1159,29 @@ export async function copyProcessFiles(sourceProcessId: string, destinationProce
});
console.log(`Successfully copied artifact with ID ${artifactId} to ${newFilename}`);
- return {
+ return ok({
mapping: { oldFilename: artifact.fileName, newFilename: newFilename },
artifactType: artifact.artifactType,
- };
+ });
} catch (error) {
console.error(
`Failed to create new artifact for destination process: ${destinationProcessId}`,
);
+ return err(error);
}
} else {
- console.warn(`Failed to copy artifact with ID ${artifactId}`);
+ const error = new Error(`Failed to copy artifact with ID ${artifactId}`);
+ console.warn(error.message);
+ return err(error);
}
});
- return oldNewFilenameMapping;
+ return ok(oldNewFilenameMapping);
}
export async function getProcessImage(processDefinitionsId: string, imageFileName: string) {
- checkIfProcessExists(processDefinitionsId);
+ const check = await checkIfProcessExists(processDefinitionsId, true);
+ if (check.isErr()) return check;
try {
const res = await db.artifact.findFirst({
@@ -1106,13 +1194,13 @@ export async function getProcessImage(processDefinitionsId: string, imageFileNam
select: { filePath: true },
});
if (!res) {
- throw new Error(`Unable to get image : ${imageFileName}`);
+ return err(new Error(`Unable to get image : ${imageFileName}`));
}
const image = (await retrieveFile(res?.filePath, false)) as Buffer;
- return image;
- } catch (err) {
- logger.debug(`Error getting image. Reason:\n${err}`);
- throw new Error(`Unable to get image : ${imageFileName}`);
+ return ok(image);
+ } catch (error) {
+ logger.debug(`Error getting image. Reason:\n${error}`);
+ return err(new Error(`Unable to get image : ${imageFileName}`));
}
}
diff --git a/src/management-system-v2/lib/data/db/space-settings.ts b/src/management-system-v2/lib/data/db/space-settings.ts
index 920fdb1f3..f742d4d16 100644
--- a/src/management-system-v2/lib/data/db/space-settings.ts
+++ b/src/management-system-v2/lib/data/db/space-settings.ts
@@ -1,3 +1,4 @@
+import { ok, err } from 'neverthrow';
import { Setting, SettingGroup } from '@/app/(dashboard)/[environmentId]/settings/type-util';
import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
import prisma from '@/lib/data/db';
@@ -10,7 +11,7 @@ export async function getSpaceSettingsValues(
searchKey: string,
ability?: Ability,
) {
- if (ability && !ability.can('view', 'Setting')) throw new UnauthorizedError();
+ if (ability && !ability.can('view', 'Setting')) return err(new UnauthorizedError());
const settings = await db.spaceSettings.findUnique({
where: { environmentId: spaceId },
@@ -35,7 +36,7 @@ export async function getSpaceSettingsValues(
});
}
- return res;
+ return ok(res);
}
export async function populateSpaceSettingsGroup(
@@ -43,7 +44,7 @@ export async function populateSpaceSettingsGroup(
settingsGroup: SettingGroup,
ability?: Ability,
) {
- if (ability && ability.can('update', 'Setting')) throw new UnauthorizedError();
+ if (ability && ability.can('update', 'Setting')) return err(new UnauthorizedError());
const settings = await db.spaceSettings.findUnique({
where: { environmentId: spaceId },
@@ -65,6 +66,8 @@ export async function populateSpaceSettingsGroup(
if (el && 'value' in el) el.value = value;
});
+
+ return ok();
}
export async function updateSpaceSettings(
@@ -72,9 +75,9 @@ export async function updateSpaceSettings(
data: Record,
ability?: Ability,
) {
- if (ability && !ability.can('update', 'Setting')) throw new UnauthorizedError();
+ if (ability && !ability.can('update', 'Setting')) return err(new UnauthorizedError());
- prisma.$transaction(async (tx) => {
+ await prisma.$transaction(async (tx) => {
const existingSettings = await tx.spaceSettings.findUnique({
where: { environmentId: spaceId },
});
@@ -96,4 +99,6 @@ export async function updateSpaceSettings(
},
});
});
+
+ return ok();
}
diff --git a/src/management-system-v2/lib/data/db/user-tasks.ts b/src/management-system-v2/lib/data/db/user-tasks.ts
index e8696b0df..311772aa1 100644
--- a/src/management-system-v2/lib/data/db/user-tasks.ts
+++ b/src/management-system-v2/lib/data/db/user-tasks.ts
@@ -1,3 +1,4 @@
+import { err, ok } from 'neverthrow';
import db from '@/lib/data/db';
import { z } from 'zod';
import { UserTask, UserTaskInput, UserTaskInputSchema } from '@/lib/user-task-schema';
@@ -5,10 +6,12 @@ import { UserTask, UserTaskInput, UserTaskInputSchema } from '@/lib/user-task-sc
export async function getUserTasks() {
const userTasks = await db.userTask.findMany();
- return userTasks.map((userTask) => ({
- ...userTask,
- offline: userTask.machineId !== 'ms-local',
- })) as unknown as UserTask[];
+ return ok(
+ userTasks.map((userTask) => ({
+ ...userTask,
+ offline: userTask.machineId !== 'ms-local',
+ })) as unknown as UserTask[],
+ );
}
export async function getUserTaskById(userTaskId: string) {
@@ -18,26 +21,32 @@ export async function getUserTaskById(userTaskId: string) {
},
});
- if (!userTask) return undefined;
+ if (!userTask) return ok(undefined);
// TODO: maybe handle view capability for specific user tasks
- return { ...userTask, offline: true } as unknown as UserTask;
+ return ok({ ...userTask, offline: true } as unknown as UserTask);
}
const UserTaskArraySchema = UserTaskInputSchema.array();
export async function addUserTasks(userTaskInput: UserTaskInput[]) {
- const newUserTasks = UserTaskArraySchema.parse(userTaskInput);
+ const parseResult = UserTaskArraySchema.safeParse(userTaskInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const newUserTasks = parseResult.data;
// TODO: maybe check if the user can work on/add user tasks
- return await db.userTask.createMany({
- data: newUserTasks.map((task) => ({
- ...task,
- startTime: new Date(task.startTime),
- endTime: typeof task.endTime !== 'number' ? undefined : new Date(task.endTime),
- })),
- });
+ return ok(
+ await db.userTask.createMany({
+ data: newUserTasks.map((task) => ({
+ ...task,
+ startTime: new Date(task.startTime),
+ endTime: typeof task.endTime !== 'number' ? undefined : new Date(task.endTime),
+ })),
+ }),
+ );
}
const PartialUserTaskInputSchema = UserTaskInputSchema.partial();
@@ -49,7 +58,11 @@ type PartialDatabaseUserTaskInput = Omit<
endTime?: Date;
};
export async function updateUserTask(userTaskId: string, userTaskInput: Partial) {
- const newUserTaskData = PartialUserTaskInputSchema.parse(userTaskInput);
+ const parseResult = PartialUserTaskInputSchema.safeParse(userTaskInput);
+ if (!parseResult.success) {
+ return err(parseResult.error);
+ }
+ const newUserTaskData = parseResult.data;
const updateData: PartialDatabaseUserTaskInput = {
...newUserTaskData,
@@ -68,19 +81,23 @@ export async function updateUserTask(userTaskId: string, userTaskInput: Partial<
// TODO: maybe check if a user is allowed to edit a user task
- return await db.userTask.update({
- data: updateData,
- where: {
- id: userTaskId,
- },
- });
+ return ok(
+ await db.userTask.update({
+ data: updateData,
+ where: {
+ id: userTaskId,
+ },
+ }),
+ );
}
export async function deleteUserTask(userTaskId: string) {
// TODO: check if a user is allowed to delete a user task
- return await db.userTask.delete({
- where: {
- id: userTaskId,
- },
- });
+ return ok(
+ await db.userTask.delete({
+ where: {
+ id: userTaskId,
+ },
+ }),
+ );
}
diff --git a/src/management-system-v2/lib/data/db/util.ts b/src/management-system-v2/lib/data/db/util.ts
new file mode 100644
index 000000000..967127c56
--- /dev/null
+++ b/src/management-system-v2/lib/data/db/util.ts
@@ -0,0 +1,30 @@
+import db from '@/lib/data/db';
+import { Err, err } from 'neverthrow';
+
+export function ensureTransactionWrapper | unknown, Args extends any[]>(
+ fn: (...args: Args) => Promise,
+ transactionIdx: number,
+): (...args: Args) => Promise {
+ const wrappedFn = async (...args: any[]) => {
+ const tx = args[transactionIdx];
+
+ if (!tx) {
+ try {
+ return await db.$transaction(async (trx) => {
+ args[transactionIdx] = trx;
+ const functionResult = await fn(...(args as Args));
+ // The error has to be thrown in order to cancel the transaction
+ if (functionResult instanceof Err && functionResult.isErr()) throw functionResult.error;
+
+ return functionResult;
+ });
+ } catch (e) {
+ return err(e);
+ }
+ } else {
+ return (await fn(...(args as Args))) as Ret;
+ }
+ };
+
+ return wrappedFn as (...args: Args) => Promise;
+}
diff --git a/src/management-system-v2/lib/data/engines.ts b/src/management-system-v2/lib/data/engines.ts
index 3f55d6ad6..9a0810d77 100644
--- a/src/management-system-v2/lib/data/engines.ts
+++ b/src/management-system-v2/lib/data/engines.ts
@@ -10,7 +10,7 @@ import {
deleteSpaceEngine as _deleteDbEngine,
} from '@/lib/data/db/engines';
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
-import { UserErrorType, userError } from '../user-error';
+import { UserErrorType, userError } from '../server-error-handling/user-error';
import { z } from 'zod';
import { enableUseDB } from 'FeatureFlags';
@@ -100,7 +100,7 @@ export async function deleteSpaceEngine(engineId: string, environmentId: string
const systemAdmin = (await getCurrentUser()).systemAdmin;
try {
- return await _deleteDbEngine(engineId, environmentId ?? null, ability, systemAdmin);
+ const result = await _deleteDbEngine(engineId, environmentId ?? null, ability, systemAdmin);
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
diff --git a/src/management-system-v2/lib/data/environment-memberships.ts b/src/management-system-v2/lib/data/environment-memberships.ts
index 12aa549ef..498c1e694 100644
--- a/src/management-system-v2/lib/data/environment-memberships.ts
+++ b/src/management-system-v2/lib/data/environment-memberships.ts
@@ -1,7 +1,7 @@
'use server';
import { getCurrentEnvironment } from '@/components/auth';
-import { UserErrorType, getErrorMessage, userError } from '../user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import { z } from 'zod';
import { sendEmail } from '../email/mailer';
import renderOrganizationInviteEmail from '../organization-invite-email';
diff --git a/src/management-system-v2/lib/data/environments.ts b/src/management-system-v2/lib/data/environments.ts
index 95a20898b..a7dbffdc3 100644
--- a/src/management-system-v2/lib/data/environments.ts
+++ b/src/management-system-v2/lib/data/environments.ts
@@ -5,7 +5,7 @@ import {
UserOrganizationEnvironmentInput,
UserOrganizationEnvironmentInputSchema,
} from './environment-schema';
-import { UserErrorType, getErrorMessage, userError } from '../user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import { UnauthorizedError } from '../ability/abilityHelper';
import {
addEnvironment,
@@ -31,12 +31,20 @@ export async function addOrganizationEnvironment(
try {
const environmentData = UserOrganizationEnvironmentInputSchema.parse(environmentInput);
- return await addEnvironment({
+ const result = await addEnvironment({
ownerId: userId,
isActive: true,
isOrganization: true,
...environmentData,
});
+
+ if (result.isOk()) {
+ result.value;
+ }
+ if (result.isErr()) {
+ // Handle error
+ result.error;
+ }
} catch (e) {
console.error(e);
return userError('Error adding environment');
diff --git a/src/management-system-v2/lib/data/file-manager-facade.ts b/src/management-system-v2/lib/data/file-manager-facade.ts
index 62460fb99..e169a3f6c 100644
--- a/src/management-system-v2/lib/data/file-manager-facade.ts
+++ b/src/management-system-v2/lib/data/file-manager-facade.ts
@@ -17,7 +17,7 @@ import { use } from 'react';
import { checkValidity } from './processes';
import { env } from '@/lib/ms-config/env-vars';
import { getUsedImagesFromJson } from '@/components/html-form-editor/serialized-format-utils';
-import { getErrorMessage, userError } from '../user-error';
+import { getErrorMessage, userError } from '../server-error-handling/user-error';
const DEPLOYMENT_ENV = env.PROCEED_PUBLIC_STORAGE_DEPLOYMENT_ENV;
diff --git a/src/management-system-v2/lib/data/folders.ts b/src/management-system-v2/lib/data/folders.ts
index 5c9047773..9ed8aca80 100644
--- a/src/management-system-v2/lib/data/folders.ts
+++ b/src/management-system-v2/lib/data/folders.ts
@@ -2,7 +2,7 @@
import * as util from 'util';
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
import { FolderUserInput, FolderUserInputSchema } from './folder-schema';
-import { UserErrorType, userError } from '../user-error';
+import { UserErrorType, userError } from '../server-error-handling/user-error';
import { TreeMap, toCaslResource } from '../ability/caslAbility';
import Ability, { UnauthorizedError } from '../ability/abilityHelper';
diff --git a/src/management-system-v2/lib/data/html-forms.ts b/src/management-system-v2/lib/data/html-forms.ts
index bab2dd4ba..b2f3eccb8 100644
--- a/src/management-system-v2/lib/data/html-forms.ts
+++ b/src/management-system-v2/lib/data/html-forms.ts
@@ -1,7 +1,7 @@
'use server';
import { HtmlForm } from '../html-form-schema';
-import { UserFacingError, getErrorMessage, userError } from '../user-error';
+import { UserFacingError, getErrorMessage, userError } from '../server-error-handling/user-error';
import {
getHtmlForms as _getHtmlForms,
getHtmlForm as _getHtmlForm,
diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx
index 56c5dad64..ab9668939 100644
--- a/src/management-system-v2/lib/data/processes.tsx
+++ b/src/management-system-v2/lib/data/processes.tsx
@@ -17,7 +17,7 @@ import {
updateBpmnCreatorAttributes,
} from '@proceed/bpmn-helper';
import { createProcess, getFinalBpmn, updateFileNames } from '../helpers/processHelpers';
-import { UserErrorType, getErrorMessage, userError } from '../user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import {
areVersionsEqual,
getLocalVersionBpmn,
diff --git a/src/management-system-v2/lib/data/role-mappings.ts b/src/management-system-v2/lib/data/role-mappings.ts
index 266cc6786..3466e4a94 100644
--- a/src/management-system-v2/lib/data/role-mappings.ts
+++ b/src/management-system-v2/lib/data/role-mappings.ts
@@ -6,7 +6,7 @@ import {
addRoleMappings as _addRoleMappings,
RoleMappingInput,
} from '@/lib/data/db/iam/role-mappings';
-import { getErrorMessage, userError } from '../user-error';
+import { getErrorMessage, userError } from '../server-error-handling/user-error';
export async function addRoleMappings(
environmentId: string,
diff --git a/src/management-system-v2/lib/data/roles.ts b/src/management-system-v2/lib/data/roles.ts
index 13585a807..ec04f34a7 100644
--- a/src/management-system-v2/lib/data/roles.ts
+++ b/src/management-system-v2/lib/data/roles.ts
@@ -2,7 +2,7 @@
import { getCurrentEnvironment } from '@/components/auth';
import { redirect } from 'next/navigation';
-import { UserErrorType, userError } from '../user-error';
+import { UserErrorType, userError } from '../server-error-handling/user-error';
import { RedirectType } from 'next/dist/client/components/redirect';
import {
deleteRole,
diff --git a/src/management-system-v2/lib/data/space-settings.ts b/src/management-system-v2/lib/data/space-settings.ts
index 9bc02c68c..7b4764385 100644
--- a/src/management-system-v2/lib/data/space-settings.ts
+++ b/src/management-system-v2/lib/data/space-settings.ts
@@ -2,7 +2,7 @@
import { SettingGroup } from '@/app/(dashboard)/[environmentId]/settings/type-util';
import { getCurrentEnvironment } from '@/components/auth';
-import { UserErrorType, getErrorMessage, userError } from '@/lib/user-error';
+import { UserErrorType, getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
import { Record } from '@prisma/client/runtime/library';
import {
getSpaceSettingsValues as _getSpaceSettingsValues,
diff --git a/src/management-system-v2/lib/data/user-tasks.ts b/src/management-system-v2/lib/data/user-tasks.ts
index e1beb3719..d1fef27e7 100644
--- a/src/management-system-v2/lib/data/user-tasks.ts
+++ b/src/management-system-v2/lib/data/user-tasks.ts
@@ -10,7 +10,7 @@ import {
deleteUserTask as _deleteUserTask,
} from './db/user-tasks';
import { UnauthorizedError } from '../ability/abilityHelper';
-import { UserErrorType, userError } from '../user-error';
+import { UserErrorType, userError } from '../server-error-handling/user-error';
import { UserTaskInput } from '../user-task-schema';
export async function getUserTasks() {
diff --git a/src/management-system-v2/lib/data/users.tsx b/src/management-system-v2/lib/data/users.tsx
index 8bdd42d37..ee93aa6e9 100644
--- a/src/management-system-v2/lib/data/users.tsx
+++ b/src/management-system-v2/lib/data/users.tsx
@@ -1,7 +1,7 @@
'use server';
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
-import { UserErrorType, getErrorMessage, userError } from '../user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import { AuthenticatedUserData, AuthenticatedUserDataSchema } from './user-schema';
import { ReactNode } from 'react';
import { OrganizationEnvironment } from './environment-schema';
diff --git a/src/management-system-v2/lib/email-verification-tokens/server-actions.ts b/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
index 0b3160620..3d8ae3836 100644
--- a/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
+++ b/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
@@ -1,7 +1,7 @@
'use server';
import { z } from 'zod';
-import { userError } from '../user-error';
+import { userError } from '../server-error-handling/user-error';
import { createChangeEmailVerificationToken, getTokenHash, notExpired } from './utils';
import { getCurrentUser } from '@/components/auth';
import {
diff --git a/src/management-system-v2/lib/engines/deployment.ts b/src/management-system-v2/lib/engines/deployment.ts
index d1796d8d3..d2beb940e 100644
--- a/src/management-system-v2/lib/engines/deployment.ts
+++ b/src/management-system-v2/lib/engines/deployment.ts
@@ -15,7 +15,7 @@ import { prepareExport } from '../process-export/export-preparation';
import { Prettify } from '../typescript-utils';
import { engineRequest } from './endpoints/index';
import { asyncForEach } from '../helpers/javascriptHelpers';
-import { UserFacingError } from '../user-error';
+import { UserFacingError } from '../server-error-handling/user-error';
type ProcessesExportData = Prettify>>;
diff --git a/src/management-system-v2/lib/engines/server-actions.ts b/src/management-system-v2/lib/engines/server-actions.ts
index c73ea7c29..baf890f1d 100644
--- a/src/management-system-v2/lib/engines/server-actions.ts
+++ b/src/management-system-v2/lib/engines/server-actions.ts
@@ -1,6 +1,6 @@
'use server';
-import { UserFacingError, getErrorMessage, userError } from '../user-error';
+import { UserFacingError, getErrorMessage, userError } from '../server-error-handling/user-error';
import {
DeployedProcessInfo,
deployProcess as _deployProcess,
diff --git a/src/management-system-v2/lib/errors.ts b/src/management-system-v2/lib/errors.ts
deleted file mode 100644
index 86c08b20f..000000000
--- a/src/management-system-v2/lib/errors.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { UserFacingError } from './user-error';
-
-export class SpaceNotFoundError extends UserFacingError {
- static prefix = '404' as const;
- constructor(message?: string) {
- super(`${SpaceNotFoundError.prefix}: ${message || 'Space not found'}`);
- }
-}
diff --git a/src/management-system-v2/lib/page-error-handling.tsx b/src/management-system-v2/lib/page-error-handling.tsx
new file mode 100644
index 000000000..2439b829d
--- /dev/null
+++ b/src/management-system-v2/lib/page-error-handling.tsx
@@ -0,0 +1,8 @@
+import { Err, type Result } from 'neverthrow';
+import { UserFacingError } from './server-error-handling/user-error';
+import { UnauthorizedError } from './ability/abilityHelper';
+import { Err } from './errors';
+
+export function errorResponse(
+ result: Err,
+) {}
diff --git a/src/management-system-v2/lib/process-export/export-preparation.ts b/src/management-system-v2/lib/process-export/export-preparation.ts
index f6ca4863a..1b7d7480e 100644
--- a/src/management-system-v2/lib/process-export/export-preparation.ts
+++ b/src/management-system-v2/lib/process-export/export-preparation.ts
@@ -31,7 +31,7 @@ import { is as bpmnIs } from 'bpmn-js/lib/util/ModelUtil';
import { ArrayEntryType, truthyFilter } from '../typescript-utils';
import { SerializedNode } from '@craftjs/core';
-import { UserError } from '../user-error';
+import { UserError } from '../server-error-handling/user-error';
/**
* The options that can be used to select what should be exported
diff --git a/src/management-system-v2/lib/result.ts b/src/management-system-v2/lib/result.ts
new file mode 100644
index 000000000..cc6b8d5b4
--- /dev/null
+++ b/src/management-system-v2/lib/result.ts
@@ -0,0 +1,50 @@
+export type Result = Ok | Err;
+
+export class Ok {
+ readonly isOk = true;
+ readonly isErr = false;
+
+ constructor(readonly value: T) {}
+
+ map(fn: (value: T) => U): Result {
+ return new Ok(fn(this.value));
+ }
+
+ mapErr(_fn: (error: E) => F): Result {
+ return new Ok(this.value);
+ }
+
+ andThen(fn: (value: T) => Result): Result {
+ return fn(this.value);
+ }
+
+ match(pattern: { ok: (value: T) => U; err: (error: E) => U }): U {
+ return pattern.ok(this.value);
+ }
+}
+
+export class Err {
+ readonly isOk = false;
+ readonly isErr = true;
+
+ constructor(readonly error: E) {}
+
+ map(_fn: (value: T) => U): Result {
+ return new Err(this.error);
+ }
+
+ mapErr(fn: (error: E) => F): Result {
+ return new Err(fn(this.error));
+ }
+
+ andThen(_fn: (value: T) => Result): Result {
+ return new Err(this.error);
+ }
+
+ match(pattern: { ok: (value: T) => U; err: (error: E) => U }): U {
+ return pattern.err(this.error);
+ }
+}
+
+export const ok = (value: T): Result => new Ok(value);
+export const err = (error: E): Result => new Err(error);
diff --git a/src/management-system-v2/lib/server-error-handling/errors.ts b/src/management-system-v2/lib/server-error-handling/errors.ts
new file mode 100644
index 000000000..e4ba76bf5
--- /dev/null
+++ b/src/management-system-v2/lib/server-error-handling/errors.ts
@@ -0,0 +1,12 @@
+import { UserFacingError } from './user-error';
+
+export class NotFoundError extends UserFacingError {
+ constructor(message?: string) {
+ super(`${message || 'Not found'}`);
+ }
+}
+export class SpaceNotFoundError extends UserFacingError {
+ constructor(message?: string) {
+ super(`${message || 'Space not found'}`);
+ }
+}
diff --git a/src/management-system-v2/lib/server-error-handling/page-error-response.tsx b/src/management-system-v2/lib/server-error-handling/page-error-response.tsx
new file mode 100644
index 000000000..d8ed5dfbd
--- /dev/null
+++ b/src/management-system-v2/lib/server-error-handling/page-error-response.tsx
@@ -0,0 +1,32 @@
+import { Err } from 'neverthrow';
+import { UserFacingError } from './user-error';
+import Content from '@/components/content';
+import { NotFoundError, SpaceNotFoundError } from '@/lib/server-error-handling/errors';
+import { Result, ResultProps } from 'antd';
+import { UnauthorizedError } from '../ability/abilityHelper';
+import RetryButton from './retry-button';
+
+export function errorResponse(
+ result: Err,
+) {
+ const error = result.error;
+
+ let title = 'Something Went wrong';
+ let status: ResultProps['status'] = 'warning';
+
+ if (error instanceof UserFacingError) {
+ title = error.message;
+ if (error instanceof NotFoundError || error instanceof SpaceNotFoundError) {
+ status = '404';
+ }
+ } else if (error instanceof UnauthorizedError) {
+ title = 'Not allowed';
+ status = '403';
+ }
+
+ return (
+
+ } />
+
+ );
+}
diff --git a/src/management-system-v2/lib/server-error-handling/retry-button.tsx b/src/management-system-v2/lib/server-error-handling/retry-button.tsx
new file mode 100644
index 000000000..76cb73bba
--- /dev/null
+++ b/src/management-system-v2/lib/server-error-handling/retry-button.tsx
@@ -0,0 +1,10 @@
+'use client';
+
+import { Button, ButtonProps } from 'antd';
+import { useRouter } from 'next/navigation';
+
+export default function RetryButton(props: ButtonProps) {
+ const router = useRouter();
+
+ return
- {role?.name}
+ {role.value.name}
}
>
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
index 43291d0c7..fdcea9cf2 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
@@ -3,18 +3,26 @@ import Content from '@/components/content';
import { getRolesWithMembers } from '@/lib/data/db/iam/roles';
import RolesPage from './role-page';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const Page = async ({ params }: { params: { environmentId: string } }) => {
- const { ability, activeEnvironment } = await getCurrentEnvironment(params.environmentId);
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { ability, activeEnvironment } = currentSpace.value;
// if (!ability.can('manage', 'Role')) return ;
if (!ability.can('admin', 'All')) return ;
const roles = await getRolesWithMembers(activeEnvironment.spaceId, ability);
+ if (roles.isErr()) {
+ return errorResponse(roles);
+ }
return (
-
+
);
};
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx
index bd475af18..52e9d8cba 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx
@@ -2,17 +2,36 @@ import { getCurrentEnvironment } from '@/components/auth';
import UsersPage from './users-page';
import Content from '@/components/content';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
-import { getFullMembersWithRoles } from '@/lib/data/db/iam/memberships';
+import { getMembers } from '@/lib/data/db/iam/memberships';
+import { getUserById } from '@/lib/data/db/iam/users';
+import { AuthenticatedUser } from '@/lib/data/user-schema';
+import { asyncMap } from '@/lib/helpers/javascriptHelpers';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { Result } from 'neverthrow';
const Page = async ({ params }: { params: { environmentId: string } }) => {
- const { ability, activeEnvironment } = await getCurrentEnvironment(params.environmentId);
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { ability, activeEnvironment } = currentSpace.value;
if (!ability.can('manage', 'User')) return ;
- const users = await getFullMembersWithRoles(activeEnvironment.spaceId, ability);
+ const memberships = await getMembers(activeEnvironment.spaceId, ability);
+ if (memberships.isErr()) {
+ return errorResponse(memberships);
+ }
+
+ const users = Result.combine(
+ await asyncMap(memberships.value, (user) => getUserById(user.userId)),
+ );
+ if (users.isErr()) {
+ return errorResponse(users);
+ }
return (
-
+
);
};
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx
index 6c2ae05ee..386c9ad1e 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx
@@ -4,6 +4,7 @@ import { SetAbility } from '@/lib/abilityStore';
import Layout, { ExtendedMenuItems } from './layout-client';
import { getUserOrganizationEnvironments } from '@/lib/data/db/iam/memberships';
import { MenuProps } from 'antd';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
import {
PartitionOutlined,
@@ -42,38 +43,72 @@ import { customLinkIcons } from '@/lib/custom-links/icons';
import { CustomNavigationLink } from '@/lib/custom-links/custom-link';
import { env } from '@/lib/ms-config/env-vars';
import { getUserPassword } from '@/lib/data/db/iam/users';
+import { Result } from 'neverthrow';
const DashboardLayout = async ({
children,
params,
}: PropsWithChildren<{ params: { environmentId: string } }>) => {
- const { userId, systemAdmin, user } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { userId, systemAdmin, user } = currentUser.value;
+
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { activeEnvironment, ability } = currentSpace.value;
- const { activeEnvironment, ability } = await getCurrentEnvironment(params.environmentId);
const can = ability.can.bind(ability);
const userEnvironments: Environment[] = [];
- if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE)
- userEnvironments.push(await getEnvironmentById(userId))!;
+ if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
+ const personalEnvironment = await getEnvironmentById(userId);
+ if (personalEnvironment.isErr()) return errorResponse(personalEnvironment);
+
+ userEnvironments.push(personalEnvironment.value);
+ }
const userOrgEnvs = await getUserOrganizationEnvironments(userId);
- const orgEnvironments = await asyncMap(
- userOrgEnvs,
- async (envId) => (await getEnvironmentById(envId))!,
+ if (userOrgEnvs.isErr()) {
+ return errorResponse(userOrgEnvs);
+ }
+
+ const orgEnvironments = Result.combine(
+ await asyncMap(userOrgEnvs.value, async (envId) => await getEnvironmentById(envId)),
);
+ if (orgEnvironments.isErr()) {
+ return errorResponse(orgEnvironments);
+ }
+
const msConfig = await getMSConfig();
- userEnvironments.push(...orgEnvironments);
+ userEnvironments.push(...orgEnvironments.value);
- const userRules = systemAdmin
- ? getSystemAdminRules(activeEnvironment.isOrganization)
- : await getUserRules(userId, activeEnvironment.spaceId);
+ let userRules;
+ if (systemAdmin) {
+ userRules = getSystemAdminRules(activeEnvironment.isOrganization);
+ } else {
+ const rules = await getUserRules(userId, activeEnvironment.spaceId);
+ if (rules.isErr()) {
+ return errorResponse(rules);
+ }
+
+ userRules = rules.value;
+ }
const generalSettings = await getSpaceSettingsValues(
activeEnvironment.spaceId,
'general-settings',
);
- const customNavLinks: CustomNavigationLink[] = generalSettings.customNavigationLinks?.links || [];
+ if (generalSettings.isErr()) {
+ return errorResponse(generalSettings);
+ }
+
+ const customNavLinks: CustomNavigationLink[] =
+ generalSettings.value.customNavigationLinks?.links || [];
const topCustomNavLinks = customNavLinks.filter((link) => link.position === 'top');
const middleCustomNavLinks = customNavLinks.filter((link) => link.position === 'middle');
const bottomCustomNavLinks = customNavLinks.filter((link) => link.position === 'bottom');
@@ -112,7 +147,11 @@ const DashboardLayout = async ({
}
const userPassword = await getUserPassword(user!.id);
- const userNeedsToChangePassword = userPassword ? userPassword.isTemporaryPassword : false;
+ if (userPassword.isErr()) {
+ return errorResponse(userPassword);
+ }
+
+ const userNeedsToChangePassword = userPassword ? userPassword.value?.isTemporaryPassword : false;
let layoutMenuItems: ExtendedMenuItems = [];
@@ -136,17 +175,20 @@ const DashboardLayout = async ({
activeEnvironment.spaceId,
'process-documentation',
);
+ if (documentationSettings.isErr()) {
+ return errorResponse(documentationSettings);
+ }
- if (documentationSettings.active !== false) {
+ if (documentationSettings.value.active !== false) {
const processRegex = '/processes($|/)';
let children: ExtendedMenuItems = [
- documentationSettings.list?.active !== false && {
+ documentationSettings.value.list?.active !== false && {
key: 'processes-list',
label: List,
icon: ,
selectedRegex: '/processes/list($|/)',
},
- documentationSettings.editor?.active !== false && {
+ documentationSettings.value.editor?.active !== false && {
key: 'processes-editor',
label: Editor,
icon: ,
@@ -170,12 +212,18 @@ const DashboardLayout = async ({
activeEnvironment.spaceId,
'process-automation',
);
+ if (automationSettings.isErr()) {
+ return errorResponse(automationSettings);
+ }
- if (msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE && automationSettings.active !== false) {
+ if (
+ msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE &&
+ automationSettings.value.active !== false
+ ) {
let childRegex = '';
let children: ExtendedMenuItems = [];
- if (automationSettings.task_editor?.active !== false) {
+ if (automationSettings.value.task_editor?.active !== false) {
childRegex = '/tasks($|/)';
children.push({
key: 'task-editor',
@@ -200,11 +248,11 @@ const DashboardLayout = async ({
}
if (msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE) {
- if (automationSettings.active !== false) {
+ if (automationSettings.value.active !== false) {
let childRegex = '';
let children: ExtendedMenuItems = [];
- if (automationSettings.dashboard?.active !== false) {
+ if (automationSettings.value.dashboard?.active !== false) {
const dashboardRegex = '/executions-dashboard($|/)';
childRegex = !childRegex ? dashboardRegex : `(${childRegex})|(${dashboardRegex})`;
children.push({
@@ -214,7 +262,7 @@ const DashboardLayout = async ({
selectedRegex: dashboardRegex,
});
}
- if (automationSettings.executions?.active !== false) {
+ if (automationSettings.value.executions?.active !== false) {
const executionsRegex = '/executions($|/)';
childRegex = !childRegex ? executionsRegex : `(${childRegex})|(${executionsRegex})`;
children.push({
@@ -224,7 +272,7 @@ const DashboardLayout = async ({
selectedRegex: executionsRegex,
});
}
- if (automationSettings.machines?.active !== false) {
+ if (automationSettings.value.machines?.active !== false) {
const machinesRegex = '/engines($|/)';
childRegex = !childRegex ? machinesRegex : `(${childRegex})|(${machinesRegex})`;
children.push({
@@ -408,14 +456,22 @@ const DashboardLayout = async ({
);
}
- const logo = (await getSpaceLogo(activeEnvironment.spaceId))?.spaceLogo ?? undefined;
+ const spaceLogo = await getSpaceLogo(activeEnvironment.spaceId);
+ if (spaceLogo.isErr()) {
+ return errorResponse(spaceLogo);
+ }
+
+ const logo = spaceLogo.value?.spaceLogo ?? undefined;
+
+ const treeMap = await getSpaceFolderTree(activeEnvironment.spaceId);
+ if (treeMap.isErr()) return errorResponse(treeMap);
return (
<>
{
- const { ability, activeEnvironment } = await getCurrentEnvironment(params.environmentId);
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { ability, activeEnvironment } = currentSpace.value;
if (
!activeEnvironment.isOrganization ||
(!ability.can('update', 'Environment') && !ability.can('delete', 'Environment'))
@@ -16,9 +21,12 @@ const GeneralSettingsPage = async ({ params }: { params: { environmentId: string
throw new UnauthorizedError();
}
- const organization = (await getEnvironmentById(
- activeEnvironment.spaceId,
- )) as OrganizationEnvironment;
+ const _organization = await getEnvironmentById(activeEnvironment.spaceId);
+ if (_organization.isErr()) {
+ return errorResponse(_organization);
+ }
+
+ const organization = _organization.value as OrganizationEnvironment;
const children: (Setting | SettingGroup)[] = [];
if (ability.can('update', 'Environment')) {
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx
index 3b630fe1e..c6b841a63 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx
@@ -13,6 +13,9 @@ import { RoleType, UserType } from './use-potentialOwner-store';
import type { Process } from '@/lib/data/process-schema';
import { redirect } from 'next/navigation';
import { spaceURL } from '@/lib/utils';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
+import { err } from 'neverthrow';
type ProcessPageProps = {
params: { processId: string; environmentId: string; mode: string };
@@ -32,17 +35,24 @@ const ProcessComponent = async ({
// refresh in processes.tsx anymore?
//console.log('processId', processId);
//console.log('query', searchParams);
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
+ const currentSpace = await getCurrentEnvironment(environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { ability, activeEnvironment } = currentSpace.value;
const selectedVersionId = searchParams.version;
// Only load BPMN if no version selected (for latest version)
const process = await getProcess(processId, !selectedVersionId);
+ if (process.isErr()) {
+ return errorResponse(process);
+ }
// For list view: check for redirect
if (isListView) {
// If no version specified but released versions exist, redirect to last released version
- if (!searchParams.version && process.versions.length > 0) {
- const lastVersionId = process.versions[process.versions.length - 1].id;
+ if (!searchParams.version && process.value.versions.length > 0) {
+ const lastVersionId = process.value.versions[process.value.versions.length - 1].id;
const currentPath = `/processes/list/${processId}`;
const redirectUrl = spaceURL(activeEnvironment, `${currentPath}?version=${lastVersionId}`);
redirect(redirectUrl);
@@ -66,15 +76,25 @@ const ProcessComponent = async ({
// return acc;
// }, {} as UserType);
- if (!ability.can('view', toCaslResource('Process', process))) {
+ if (!ability.can('view', toCaslResource('Process', process.value))) {
throw new UnauthorizedError();
}
- const selectedVersionBpmn = selectedVersionId
- ? await getProcessBPMN(processId, environmentId, selectedVersionId)
- : process.bpmn;
+ let selectedVersionBpmn;
+ if (selectedVersionId) {
+ const bpmn = await getProcessBPMN(processId, environmentId, selectedVersionId);
+ // TODO: don't use server action
+ if (isUserErrorResponse(bpmn)) {
+ return errorResponse(err());
+ }
+
+ selectedVersionBpmn = bpmn;
+ } else {
+ selectedVersionBpmn = process.value.bpmn;
+ }
+
const selectedVersion = selectedVersionId
- ? process.versions.find((version) => version.id === selectedVersionId)
+ ? process.value.versions.find((version) => version.id === selectedVersionId)
: undefined;
// Since the user is able to minimize and close the page, everything is in a
@@ -82,8 +102,8 @@ const ProcessComponent = async ({
return (
<>
}
timelineComponent={
}
/>
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/potentialOwner-server-action.ts b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/potentialOwner-server-action.ts
index 8639bc654..e9f381cdf 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/potentialOwner-server-action.ts
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/potentialOwner-server-action.ts
@@ -3,17 +3,26 @@
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
import { getRolesWithMembers } from '@/lib/data/db/iam/roles';
import { RoleType, UserType } from './use-potentialOwner-store';
+import { getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
export const fetchPotentialOwner = async (environmentId: string) => {
const user: UserType = {};
const roles: RoleType = {};
if (environmentId) {
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
+ const currentSpace = await getCurrentEnvironment(environmentId);
+ if (currentSpace.isErr()) {
+ return userError(getErrorMessage(currentSpace.error));
+ }
+
+ const { ability, activeEnvironment } = currentSpace.value;
if (activeEnvironment.isOrganization) {
const rawRoles = await getRolesWithMembers(activeEnvironment.spaceId, ability);
+ if (rawRoles.isErr()) {
+ return userError(getErrorMessage(rawRoles.error));
+ }
- rawRoles.forEach((role) => {
+ rawRoles.value.forEach((role) => {
roles[role.id] = role.name;
role.members.forEach((member) => {
@@ -26,8 +35,12 @@ export const fetchPotentialOwner = async (environmentId: string) => {
} else {
// make sure to get the current user that might not be assigned to any role
const u = await getCurrentUser();
- if (u.session?.user) {
- const currUser = u.session.user;
+ if (u.isErr()) {
+ return userError(getErrorMessage(u.error));
+ }
+
+ if (u.value.session?.user) {
+ const currUser = u.value.session.user;
if (!currUser.isGuest) {
user[currUser.id] = {
userName: currUser.username,
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/use-potentialOwner-store.ts b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/use-potentialOwner-store.ts
index 220baf4c5..0722f8435 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/use-potentialOwner-store.ts
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/use-potentialOwner-store.ts
@@ -3,6 +3,7 @@ import { immer } from 'zustand/middleware/immer';
import { useEffect } from 'react';
import { fetchPotentialOwner } from './potentialOwner-server-action';
import { useEnvironment } from '@/components/auth-can';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
export type UserType = {
[key: string]: {
@@ -50,7 +51,10 @@ export const useInitialisePotentialOwnerStore = () => {
const environment = useEnvironment();
useEffect(() => {
const initialiseStore = async () => {
- const { user, roles } = await fetchPotentialOwner(environment.spaceId);
+ const response = await fetchPotentialOwner(environment.spaceId);
+ if (isUserErrorResponse(response)) return;
+
+ const { user, roles } = response;
const store = usePotentialOwnerStore.getState();
store.setUser(user);
store.setRoles(roles);
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/folder/[folderId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/folder/[folderId]/page.tsx
index 093ce90e8..afe45468a 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/folder/[folderId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/folder/[folderId]/page.tsx
@@ -16,6 +16,7 @@ import EllipsisBreadcrumb from '@/components/ellipsis-breadcrumb';
import { ComponentProps } from 'react';
import { spaceURL } from '@/lib/utils';
import { getFolderById, getRootFolder, getFolderContents } from '@/lib/data/db/folders';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
export type ListItem = ProcessMetadata | (Folder & { type: 'folder' });
const ProcessesPage = async ({
@@ -23,25 +24,30 @@ const ProcessesPage = async ({
}: {
params: { environmentId: string; mode: string; folderId?: string };
}) => {
- const { ability, activeEnvironment } = await getCurrentEnvironment(params.environmentId);
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) return errorResponse(currentSpace);
+ const { ability, activeEnvironment } = currentSpace.value;
const favs = await getUsersFavourites();
const rootFolder = await getRootFolder(activeEnvironment.spaceId, ability);
+ if (rootFolder.isErr()) return errorResponse(rootFolder);
const folder = await getFolderById(
- params.folderId ? decodeURIComponent(params.folderId) : rootFolder.id,
+ params.folderId ? decodeURIComponent(params.folderId) : rootFolder.value.id,
);
+ if (folder.isErr()) return errorResponse(folder);
- const folderContents = await getFolderContents(folder.id, ability);
+ const folderContents = await getFolderContents(folder.value.id, ability);
+ if (folderContents.isErr()) return errorResponse(folderContents);
const isListView = params.mode === 'list';
const folderContentsFiltered = isListView
- ? folderContents.filter(
+ ? folderContents.value.filter(
(folderContent) => folderContent.type === 'folder' || folderContent.versions.length > 0,
)
- : folderContents;
+ : folderContents.value;
const hasNoReleasedProcesses = isListView
? folderContentsFiltered.every((item) => item.type === 'folder')
@@ -49,7 +55,7 @@ const ProcessesPage = async ({
const pathToFolder: ComponentProps['items'] = [];
const wrappingFolderIds = [] as string[];
- let currentFolder: Folder | null = folder;
+ let currentFolder: Folder | null = folder.value;
do {
pathToFolder.push({
title: (
@@ -61,7 +67,17 @@ const ProcessesPage = async ({
),
});
if (currentFolder) wrappingFolderIds.push(currentFolder.id);
- currentFolder = currentFolder.parentId ? await getFolderById(currentFolder.parentId) : null;
+
+ if (currentFolder.parentId) {
+ const result = await getFolderById(currentFolder.parentId);
+ if (result.isErr()) {
+ return errorResponse(result);
+ }
+
+ currentFolder = result.value;
+ } else {
+ currentFolder = null;
+ }
} while (currentFolder);
pathToFolder.reverse();
wrappingFolderIds.reverse();
@@ -71,11 +87,11 @@ const ProcessesPage = async ({
- {folder.parentId && (
+ {folder.value.parentId && (
} type="text">
@@ -90,10 +106,10 @@ const ProcessesPage = async ({
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/layout.tsx
index 496c686b1..e39d3d785 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/layout.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/layout.tsx
@@ -2,20 +2,31 @@ import React from 'react';
import { getSpaceSettingsValues } from '@/lib/data/db/space-settings';
import { notFound } from 'next/navigation';
import { getCurrentEnvironment } from '@/components/auth';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
type DocumentationLayoutProps = {
params: { environmentId: string };
} & React.PropsWithChildren;
const DocumentationLayout: React.FC = async ({ params, children }) => {
- const { activeEnvironment } = await getCurrentEnvironment(params.environmentId);
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { activeEnvironment } = currentSpace.value;
const documentationSettings = await getSpaceSettingsValues(
activeEnvironment.spaceId,
'process-documentation',
);
+ if (documentationSettings.isErr()) {
+ return errorResponse(documentationSettings);
+ }
- if (documentationSettings.active === false || documentationSettings.editor?.active === false) {
+ if (
+ documentationSettings.value.active === false ||
+ documentationSettings.value.editor?.active === false
+ ) {
return notFound();
}
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx
index 1437d4a1b..f4be9a9e5 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx
@@ -4,20 +4,30 @@ import Content from '@/components/content';
import { getUserById, getUserPassword } from '@/lib/data/db/iam/users';
import { notFound } from 'next/navigation';
import { env } from '@/lib/ms-config/env-vars';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const ProfilePage = async () => {
- const { userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { userId } = currentUser.value;
//TODO take guest into consideration
const userData = await getUserById(userId);
- const userHasPassword = !!(await getUserPassword(userId));
+ if (userData.isErr()) return errorResponse(userData);
+
+ const userHasPassword = await getUserPassword(userId);
+ if (userHasPassword && userHasPassword.isErr()) {
+ return errorResponse(userHasPassword);
+ }
if (!env.PROCEED_PUBLIC_IAM_ACTIVE) return notFound();
return (
-
+
);
};
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx
index 36b3f93b3..3d93ed333 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@generalSettings/page.tsx
@@ -5,12 +5,17 @@ import { SettingGroup } from '../type-util';
import Wrapper from './wrapper';
import db from '@/lib/data/db';
import { SpaceNotFoundError } from '@/lib/server-error-handling/errors';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const Page = async ({ params }: { params: { environmentId: string } }) => {
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
const {
ability,
activeEnvironment: { spaceId },
- } = await getCurrentEnvironment(params.environmentId);
+ } = currentSpace.value;
//if (!ability.can('update', 'Environment')) return null;
const spaceLogo = await db.space.findUnique({
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx
index 780d02748..2cefb293b 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx
@@ -4,15 +4,20 @@ import SettingsInjector from '../settings-injector';
import { settings } from './settings';
import Wrapper from './wrapper';
import { getMSConfig } from '@/lib/ms-config/ms-config';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const Page = async ({ params }: { params: { environmentId: string } }) => {
const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE) return null;
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
const {
ability,
activeEnvironment: { spaceId },
- } = await getCurrentEnvironment(params.environmentId);
+ } = currentSpace.value;
await populateSpaceSettingsGroup(spaceId, settings);
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processDocumentation/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processDocumentation/page.tsx
index 00bcc2eb1..50522ba8f 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processDocumentation/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processDocumentation/page.tsx
@@ -3,11 +3,16 @@ import { getCurrentEnvironment } from '@/components/auth';
import SettingsInjector from '../settings-injector';
import Wrapper from './wrapper';
import { getSettings } from './settings';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const Page = async ({ params }: { params: { environmentId: string } }) => {
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
const {
activeEnvironment: { spaceId },
- } = await getCurrentEnvironment(params.environmentId);
+ } = currentSpace.value;
const settings = await getSettings();
await populateSpaceSettingsGroup(spaceId, settings);
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/spaces/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/spaces/page.tsx
index f14065d9b..1fe1f1227 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/spaces/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/spaces/page.tsx
@@ -5,17 +5,37 @@ import { getUserOrganizationEnvironments } from '@/lib/data/db/iam/memberships';
import EnvironmentsPage from './environments-page';
import { getUserById } from '@/lib/data/db/iam/users';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { Result } from 'neverthrow';
const Page = async () => {
- const { userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { userId } = currentUser.value;
const user = await getUserById(userId);
- if (user?.isGuest) return ;
+ if (user.isErr()) return errorResponse(user);
+ if (user.value?.isGuest) return ;
const environmentIds = await getUserOrganizationEnvironments(userId);
- const userSpaces = (await Promise.all(
- environmentIds.map((environmentId: string) => getEnvironmentById(environmentId)),
- )) as { id: string; name: string; description: string; isOrganization: boolean }[];
+ if (environmentIds.isErr()) return errorResponse(environmentIds);
+
+ const _userSpaces = Result.combine(
+ await Promise.all(
+ environmentIds.value.map((environmentId: string) => getEnvironmentById(environmentId)),
+ ),
+ );
+ if (_userSpaces.isErr()) return errorResponse(_userSpaces);
+
+ const userSpaces = _userSpaces.value as {
+ id: string;
+ name: string;
+ description: string;
+ isOrganization: boolean;
+ }[];
+
userSpaces.unshift({
id: userId,
name: 'My Personal Space',
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/tasklist/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/tasklist/page.tsx
index a2767aaa3..597b1b57c 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/tasklist/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/tasklist/page.tsx
@@ -9,6 +9,7 @@ import { getUserRoles } from '@/lib/data/db/iam/roles';
import { truthyFilter } from '@/lib/typescript-utils';
import { getSpaceSettingsValues } from '@/lib/data/db/space-settings';
import { getUsersInSpace } from '@/lib/data/db/iam/memberships';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const TasklistPage = async ({ params }: { params: { environmentId: string } }) => {
const msConfig = await getMSConfig();
@@ -16,7 +17,11 @@ const TasklistPage = async ({ params }: { params: { environmentId: string } }) =
return notFound();
}
- const { userId, user: userData } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { userId, user: userData } = currentUser.value;
if (!userData || userData?.isGuest) {
return (
@@ -28,13 +33,24 @@ const TasklistPage = async ({ params }: { params: { environmentId: string } }) =
);
}
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
const {
ability,
activeEnvironment: { spaceId, isOrganization },
- } = await getCurrentEnvironment(params.environmentId);
+ } = currentSpace.value;
const automationSettings = await getSpaceSettingsValues(spaceId, 'process-automation');
- if (automationSettings.active === false || automationSettings.tasklist?.active === false) {
+ if (automationSettings.isErr()) {
+ return errorResponse(automationSettings);
+ }
+
+ if (
+ automationSettings.value.active === false ||
+ automationSettings.value.tasklist?.active === false
+ ) {
return notFound();
}
@@ -49,8 +65,11 @@ const TasklistPage = async ({ params }: { params: { environmentId: string } }) =
}
const spaceUsers = await getUsersInSpace(spaceId, ability);
+ if (spaceUsers.isErr()) {
+ return errorResponse(spaceUsers);
+ }
const users = Object.fromEntries(
- spaceUsers.map((member) => [
+ spaceUsers.value.map((member) => [
member.id,
{
userName: member.username || undefined,
@@ -65,13 +84,17 @@ const TasklistPage = async ({ params }: { params: { environmentId: string } }) =
}
const userRoles = await getUserRoles(userId, spaceId, ability);
+ if (userRoles.isErr()) {
+ return errorResponse(userRoles);
+ }
+
userTasks = userTasks.filter((uT) => {
const utRoles = uT.potentialOwners?.roles || [];
const utUsers = uT.potentialOwners?.user || [];
if (!utUsers.length && !utRoles.length) return true;
const userCanOwn = utUsers.some((id) => id === userId);
- const userRoleCanOwn = utRoles.some((id) => userRoles.some((role) => role.id === id));
+ const userRoleCanOwn = utRoles.some((id) => userRoles.value.some((role) => role.id === id));
return userCanOwn || userRoleCanOwn;
});
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/[formId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/[formId]/page.tsx
index 001c416a2..4e48d9a2e 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/[formId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/[formId]/page.tsx
@@ -5,15 +5,26 @@ import { getSpaceSettingsValues } from '@/lib/data/db/space-settings';
import { Result } from 'antd';
import { notFound } from 'next/navigation';
import FormView from './form-view';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const FormPage = async ({ params }: { params: { environmentId: string; formId: string } }) => {
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
const {
activeEnvironment: { spaceId },
- } = await getCurrentEnvironment(params.environmentId);
+ } = currentSpace.value;
const automationSettings = await getSpaceSettingsValues(spaceId, 'process-automation');
+ if (automationSettings.isErr()) {
+ return errorResponse(automationSettings);
+ }
- if (automationSettings.active === false || automationSettings.tasklist?.active === false) {
+ if (
+ automationSettings.value.active === false ||
+ automationSettings.value.tasklist?.active === false
+ ) {
return notFound();
}
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/layout.tsx
index bd2e8df61..a96acc0a5 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/layout.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/layout.tsx
@@ -3,6 +3,7 @@ import { getSpaceSettingsValues } from '@/lib/data/db/space-settings';
import { notFound } from 'next/navigation';
import { getCurrentEnvironment } from '@/components/auth';
import { getMSConfig } from '@/lib/ms-config/ms-config';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
type DocumentationLayoutProps = {
params: { environmentId: string };
@@ -14,14 +15,24 @@ const DocumentationLayout: React.FC = async ({ params,
return notFound();
}
- const { activeEnvironment } = await getCurrentEnvironment(params.environmentId);
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { activeEnvironment } = currentSpace.value;
const automationSettings = await getSpaceSettingsValues(
activeEnvironment.spaceId,
'process-automation',
);
+ if (automationSettings.isErr()) {
+ return errorResponse(automationSettings);
+ }
- if (automationSettings.active === false || automationSettings.task_editor?.active === false) {
+ if (
+ automationSettings.value.active === false ||
+ automationSettings.value.task_editor?.active === false
+ ) {
return notFound();
}
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/page.tsx
index dad3a5446..9a75add34 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/page.tsx
@@ -5,15 +5,26 @@ import { Result, Space } from 'antd';
import { notFound } from 'next/navigation';
import FormList from './form-list';
import { getHtmlForms } from '@/lib/data/html-forms';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const FormsPage = async ({ params }: { params: { environmentId: string } }) => {
+ const currentSpace = await getCurrentEnvironment(params.environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
const {
activeEnvironment: { spaceId },
- } = await getCurrentEnvironment(params.environmentId);
+ } = currentSpace.value;
const automationSettings = await getSpaceSettingsValues(spaceId, 'process-automation');
+ if (automationSettings.isErr()) {
+ return errorResponse(automationSettings);
+ }
- if (automationSettings.active === false || automationSettings.tasklist?.active === false) {
+ if (
+ automationSettings.value.active === false ||
+ automationSettings.value.tasklist?.active === false
+ ) {
return notFound();
}
diff --git a/src/management-system-v2/app/accept-invitation/page.tsx b/src/management-system-v2/app/accept-invitation/page.tsx
index 69376412c..7f142d3c4 100644
--- a/src/management-system-v2/app/accept-invitation/page.tsx
+++ b/src/management-system-v2/app/accept-invitation/page.tsx
@@ -8,6 +8,7 @@ import { getUserByEmail } from '@/lib/data/db/iam/users';
import { acceptInvitation, getInvitation as getInvitationFromToken } from '@/lib/invitation-tokens';
import { Result, ResultProps } from 'antd';
import { redirect } from 'next/navigation';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
function Error(props: ResultProps) {
return (
@@ -18,7 +19,11 @@ function Error(props: ResultProps) {
}
export default async function IvitationPage({ searchParams }: { searchParams: { token: string } }) {
- const { session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { session } = currentUser.value;
if (!session)
redirect(
`/api/auth/signin?callbackUrl=${encodeURIComponent('/accept-invitation?token=' + searchParams.token)}`,
diff --git a/src/management-system-v2/app/admin/engines/[dbEngineId]/page.tsx b/src/management-system-v2/app/admin/engines/[dbEngineId]/page.tsx
index 79cc51752..7a5f77188 100644
--- a/src/management-system-v2/app/admin/engines/[dbEngineId]/page.tsx
+++ b/src/management-system-v2/app/admin/engines/[dbEngineId]/page.tsx
@@ -9,6 +9,7 @@ import { type Engine } from '@/lib/engines/machines';
import { getDbEngineById } from '@/lib/data/db/engines';
import { getMSConfig } from '@/lib/ms-config/ms-config';
import EngineDashboard from '@/components/engine-dashboard/server-component';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
export type TableEngine = Engine & { id: string };
@@ -23,11 +24,18 @@ export default async function EnginesPage({
if (!msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE) return notFound();
const user = await getCurrentUser();
- if (!user.systemAdmin) redirect('/');
+ if (user.isErr()) {
+ return errorResponse(user);
+ }
+
+ if (!user.value.systemAdmin) redirect('/');
const dbEngineId = decodeURIComponent(params.dbEngineId);
const engineId = decodeURIComponent(searchParams.engineId || '');
const dbEngine = await getDbEngineById(dbEngineId, null, undefined, 'dont-check');
+ if (dbEngine.isErr()) {
+ return errorResponse(dbEngine);
+ }
return (
diff --git a/src/management-system-v2/app/admin/engines/page.tsx b/src/management-system-v2/app/admin/engines/page.tsx
index c44ba343d..4a6738e0f 100644
--- a/src/management-system-v2/app/admin/engines/page.tsx
+++ b/src/management-system-v2/app/admin/engines/page.tsx
@@ -8,6 +8,7 @@ import { Suspense } from 'react';
import { getMSConfig } from '@/lib/ms-config/ms-config';
import { savedEnginesToEngines } from '@/lib/engines/saved-engines-helpers';
import { Engine as DBEngine } from '@prisma/client';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const getEngineStatus = async (engine: DBEngine) => {
const engines = await savedEnginesToEngines([engine]);
@@ -23,12 +24,19 @@ const EnginesPage = async () => {
const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE) return notFound();
- const { systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { systemAdmin } = currentUser.value;
if (!systemAdmin) return redirect('/');
const engines = await getDbEngines(null, undefined, systemAdmin);
+ if (engines.isErr()) {
+ return errorResponse(engines);
+ }
- const enginesWithStatus = engines.map((engine) => {
+ const enginesWithStatus = engines.value.map((engine) => {
return {
...engine,
status: (
diff --git a/src/management-system-v2/app/admin/ms-config/page.tsx b/src/management-system-v2/app/admin/ms-config/page.tsx
index d2a353a92..e4322bddc 100644
--- a/src/management-system-v2/app/admin/ms-config/page.tsx
+++ b/src/management-system-v2/app/admin/ms-config/page.tsx
@@ -7,6 +7,7 @@ import { getMSConfig, updateMSConfig, writeDefaultMSConfig } from '@/lib/ms-conf
import MSConfigForm from './ms-config-form';
import { userError } from '@/lib/server-error-handling/user-error';
import { SettingGroup } from '@/app/(dashboard)/[environmentId]/settings/type-util';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
async function saveConfig(newConfig: Record) {
'use server';
@@ -30,7 +31,10 @@ export type restoreDefaultValues = typeof restoreDefaultValues;
async function ConfigPage() {
const user = await getCurrentUser();
- if (!user.session || !user.systemAdmin) redirect('/');
+ if (user.isErr()) {
+ return errorResponse(user);
+ }
+ if (!user.value.session || !user.value.systemAdmin) redirect('/');
const _msConfig = await getMSConfig();
diff --git a/src/management-system-v2/app/admin/page.tsx b/src/management-system-v2/app/admin/page.tsx
index b5201dbe1..93e2674ce 100644
--- a/src/management-system-v2/app/admin/page.tsx
+++ b/src/management-system-v2/app/admin/page.tsx
@@ -5,13 +5,16 @@ import { Card, Space, Statistic } from 'antd';
import { redirect } from 'next/navigation';
import { CSSProperties } from 'react';
import db from '@/lib/data/db';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
export default async function AdminDashboard() {
const user = await getCurrentUser();
- if (!user.session) redirect('/');
+ if (user.isErr()) return errorResponse(user);
+ if (!user.value.session) redirect('/');
- const adminData = await getSystemAdminByUserId(user.userId);
- if (!adminData) redirect('/');
+ const adminData = await getSystemAdminByUserId(user.value.userId);
+ if (adminData.isErr()) return errorResponse(adminData);
+ if (!adminData.value) redirect('/');
// NOTE: this should be replaced to a more efficient count query
// when the data persistence layer is implemented
diff --git a/src/management-system-v2/app/admin/spaces/page.tsx b/src/management-system-v2/app/admin/spaces/page.tsx
index 91a247bc8..d275eb617 100644
--- a/src/management-system-v2/app/admin/spaces/page.tsx
+++ b/src/management-system-v2/app/admin/spaces/page.tsx
@@ -17,10 +17,16 @@ import { ReactNode } from 'react';
import { LeftOutlined } from '@ant-design/icons';
import { User } from '@/lib/data/user-schema';
import { Environment } from '@/lib/data/environment-schema';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { Result } from 'neverthrow';
async function deleteSpace(spaceIds: string[]) {
'use server';
- const { systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { systemAdmin } = currentUser.value;
if (!systemAdmin) return userError('Not a system admin', UserErrorType.PermissionError);
// TODO: decide what to do if space is a personal space
@@ -30,9 +36,12 @@ export type deleteSpace = typeof deleteSpace;
export default async function SysteAdminDashboard({ params }: { params?: { userId: string } }) {
const user = await getCurrentUser();
- if (!user.session) redirect('/');
- const adminData = getSystemAdminByUserId(user.userId);
- if (!adminData) redirect('/');
+ if (user.isErr()) return errorResponse(user);
+ if (!user.value.session) redirect('/');
+
+ const adminData = await getSystemAdminByUserId(user.value.userId);
+ if (adminData.isErr()) return errorResponse(adminData);
+ if (!adminData.value) redirect('/');
let spacesTableRepresentation;
let title: ReactNode = 'MS Spaces';
@@ -40,37 +49,52 @@ export default async function SysteAdminDashboard({ params }: { params?: { userI
if (params?.userId) {
const userId = decodeURIComponent(params.userId);
const user = await getUserById(userId, { throwIfNotFound: false });
- if (!user) redirect('/admin/spaces');
+ if (user.isErr()) {
+ return errorResponse(user);
+ }
+ if (!user.value) redirect('/admin/spaces');
title = (
} href="/admin/spaces">
Back to MS spaces
- {`${getUserName(user as User)}'s Spaces`}
+ {`${getUserName(user.value as User)}'s Spaces`}
);
- const userSpaces: any[] = [await getEnvironmentById(userId)];
+ const personalEnvironment = await getEnvironmentById(userId);
+ if (personalEnvironment.isErr()) return errorResponse(personalEnvironment);
+ const userSpaces: any[] = [personalEnvironment.value];
+
const userOrgEnvs = await getUserOrganizationEnvironments(userId);
- const orgEnvironmentsPromises = userOrgEnvs.map(async (environmentId) => {
+ if (userOrgEnvs.isErr()) return errorResponse(userOrgEnvs);
+ const orgEnvironmentsPromises = userOrgEnvs.value.map(async (environmentId) => {
return await getEnvironmentById(environmentId);
});
- const orgEnvironments = await Promise.all(orgEnvironmentsPromises);
+ const orgEnvironments = Result.combine(await Promise.all(orgEnvironmentsPromises));
+ if (orgEnvironments.isErr()) {
+ return errorResponse(orgEnvironments);
+ }
- userSpaces.push(...orgEnvironments);
+ userSpaces.push(...orgEnvironments.value);
spacesTableRepresentation = await getSpaceRepresentation(userSpaces);
} else {
- spacesTableRepresentation = await getSpaceRepresentation(
- (await getEnvironments()) as Environment[],
- );
+ const environments = await getEnvironments();
+ if (environments.isErr()) {
+ return errorResponse(environments);
+ }
+
+ spacesTableRepresentation = await getSpaceRepresentation(environments.value as Environment[]);
}
+ if (spacesTableRepresentation.isErr()) return errorResponse(spacesTableRepresentation);
+
return (
-
+
);
}
diff --git a/src/management-system-v2/app/admin/spaces/space-representation.ts b/src/management-system-v2/app/admin/spaces/space-representation.ts
index a5fb194de..4ffc27942 100644
--- a/src/management-system-v2/app/admin/spaces/space-representation.ts
+++ b/src/management-system-v2/app/admin/spaces/space-representation.ts
@@ -2,6 +2,7 @@ import 'server-only';
import { Environment } from '@/lib/data/environment-schema';
import { getUserById } from '@/lib/data/db/iam/users';
import { User } from '@/lib/data/user-schema';
+import { Result, err, ok } from 'neverthrow';
export function getUserName(user: User) {
if (user.isGuest) return 'Guest';
@@ -12,35 +13,41 @@ export function getUserName(user: User) {
}
export type SpaceRepresentation = { id: string; name: string; type: string; owner: string };
-export function getSpaceRepresentation(spaces: Environment[]): Promise {
- return Promise.all(
- spaces.map(async (space) => {
- if (space.isOrganization && !space.isActive)
- return {
- id: space.id,
- name: `${space.name}`,
- type: 'Organization',
- owner: 'None',
- };
+export async function getSpaceRepresentation(spaces: Environment[]) {
+ return Result.combine(
+ await Promise.all(
+ spaces.map(async (space) => {
+ if (space.isOrganization && !space.isActive) {
+ return ok({
+ id: space.id,
+ name: `${space.name}`,
+ type: 'Organization',
+ owner: 'None',
+ });
+ }
+
+ const user = await getUserById(space.isOrganization ? space.ownerId : space.id);
+ if (user.isErr()) return user;
+ if (!user.value) err(new Error('Space user not found'));
- const user = await getUserById(space.isOrganization ? space.ownerId : space.id);
- if (!user) throw new Error('Space user not found');
- const userName = getUserName(user as User);
+ const userName = getUserName(user.value as User);
- if (space.isOrganization)
- return {
+ if (space.isOrganization) {
+ return ok({
+ id: space.id,
+ name: `${space.name}`,
+ type: 'Organization',
+ owner: userName,
+ });
+ }
+
+ return ok({
id: space.id,
- name: `${space.name}`,
- type: 'Organization',
+ name: `Personal space: ${userName}`,
+ type: 'Personal space',
owner: userName,
- };
-
- return {
- id: space.id,
- name: `Personal space: ${userName}`,
- type: 'Personal space',
- owner: userName,
- };
- }),
+ });
+ }),
+ ),
);
}
diff --git a/src/management-system-v2/app/admin/systemadmins/page.tsx b/src/management-system-v2/app/admin/systemadmins/page.tsx
index 4117609bc..09daf7762 100644
--- a/src/management-system-v2/app/admin/systemadmins/page.tsx
+++ b/src/management-system-v2/app/admin/systemadmins/page.tsx
@@ -9,24 +9,33 @@ import {
} from '@/lib/data/db/iam/system-admins';
import { getUserById, getUsers } from '@/lib/data/db/iam/users';
import { AuthenticatedUser } from '@/lib/data/user-schema';
-import { UserErrorType, userError } from '@/lib/server-error-handling/user-error';
+import { UserErrorType, getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
import { notFound, redirect } from 'next/navigation';
import SystemAdminsTable from './admins-table';
import { SystemAdminCreationInput } from '@/lib/data/system-admin-schema';
import { getMSConfig } from '@/lib/ms-config/ms-config';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { Result, ok } from 'neverthrow';
async function deleteAdmins(userIds: string[]) {
'use server';
- const { systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { systemAdmin } = currentUser.value;
if (!systemAdmin || systemAdmin.role !== 'admin')
return userError('Not a system admin', UserErrorType.PermissionError);
try {
for (const userId of userIds) {
const adminMapping = await getSystemAdminByUserId(userId);
- if (!adminMapping) return userError('Admin not found');
+ if (adminMapping.isErr()) {
+ return userError(getErrorMessage(adminMapping.error));
+ }
+ if (!adminMapping.value) return userError('Admin not found');
- deleteSystemAdmin(adminMapping.id);
+ deleteSystemAdmin(adminMapping.value.id);
}
} catch (e) {
return userError('Something went wrong');
@@ -36,7 +45,11 @@ export type deleteAdmins = typeof deleteAdmins;
async function addAdmin(admins: SystemAdminCreationInput[]) {
'use server';
- const { systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { systemAdmin } = currentUser.value;
if (!systemAdmin || systemAdmin.role !== 'admin')
return userError('Not a system admin', UserErrorType.PermissionError);
@@ -52,16 +65,28 @@ export type addAdmin = typeof addAdmin;
async function getNonAdminUsers(page: number = 1, pageSize: number = 10) {
'use server';
- const { systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+
+ const { systemAdmin } = currentUser.value;
if (!systemAdmin || systemAdmin.role !== 'admin')
return userError('Not a system admin', UserErrorType.PermissionError);
try {
const systemAdmins = await getSystemAdmins();
- const { users, pagination } = await getUsers(page, pageSize);
+ if (systemAdmins.isErr()) {
+ return userError(getErrorMessage(systemAdmins.error));
+ }
+
+ const usersResult = await getUsers(page, pageSize);
+ if (usersResult.isErr()) return userError(getErrorMessage(usersResult.error));
+
+ const { users, pagination } = usersResult.value;
const filteredUsers = users.filter(
- (user) => !user.isGuest && !systemAdmins.some((admin) => admin.userId === user.id),
+ (user) => !user.isGuest && !systemAdmins.value.some((admin) => admin.userId === user.id),
) as AuthenticatedUser[];
const totalFilteredUsers = filteredUsers.length;
@@ -86,27 +111,44 @@ export default async function ManageAdminsPage() {
if (!msConfig.PROCEED_PUBLIC_IAM_ACTIVE) return notFound();
const user = await getCurrentUser();
- if (!user.session) redirect('/');
- const adminData = await getSystemAdminByUserId(user.userId);
- if (!adminData) redirect('/');
- if (adminData.role !== 'admin') return ;
+ if (user.isErr()) return errorResponse(user);
+ if (!user.value.session) redirect('/');
- const getFullSystemAdmins = async (): Promise<(AuthenticatedUser & { role: 'admin' })[]> => {
+ const adminData = await getSystemAdminByUserId(user.value.userId);
+ if (adminData.isErr()) {
+ return errorResponse(adminData);
+ }
+ if (!adminData.value) redirect('/');
+ if (adminData.value.role !== 'admin') return ;
+
+ type aa = Promise<(AuthenticatedUser & { role: 'admin' })[]>;
+ const getFullSystemAdmins = async () => {
const admins = await getSystemAdmins();
- return Promise.all(
- admins.map(async (admin) => {
- const user = (await getUserById(admin.userId)) as AuthenticatedUser;
- return { ...user, role: admin.role };
- }),
+ if (admins.isErr()) return admins;
+
+ return Result.combine(
+ await Promise.all(
+ admins.value.map(async (admin) => {
+ const user = await getUserById(admin.userId);
+ if (user.isErr()) {
+ return user;
+ }
+
+ return ok({ ...(user.value as AuthenticatedUser), role: admin.role });
+ }),
+ ),
);
};
const adminsList = await getFullSystemAdmins();
+ if (adminsList.isErr()) {
+ return errorResponse(adminsList);
+ }
return (
{
- const orgs = (await getUserOrganizationEnvironments(user.id)).length;
- if (orgs > 0) {
- console.log(await getUserOrganizationEnvironments(user.id));
- }
- return user.isGuest
- ? {
- ...user,
- isGuest: false as const,
- email: '',
- username: 'Guest',
- firstName: 'Guest',
- lastName: '',
- orgs,
- }
- : { ...user, orgs };
- }),
+ const processedUsers = Result.combine(
+ await Promise.all(
+ paginatedUsers.map(async (user) => {
+ const userOrgs = await getUserOrganizationEnvironments(user.id);
+ if (userOrgs.isErr()) return userOrgs as Err;
+
+ const orgs = userOrgs.value.length;
+ const ret = user.isGuest
+ ? {
+ ...user,
+ isGuest: false as const,
+ email: '',
+ username: 'Guest',
+ firstName: 'Guest',
+ lastName: '',
+ orgs,
+ }
+ : { ...user, orgs };
+
+ return ok(ret);
+ }),
+ ),
);
+ if (processedUsers.isErr()) {
+ return processedUsers;
+ }
- return {
- users: processedUsers,
+ return ok({
+ users: processedUsers.value,
pagination,
- };
+ });
}
- const { users, pagination } = await getProcessedUsers();
+ const proceedUsers = await getProcessedUsers();
+ if (proceedUsers.isErr()) return errorResponse(proceedUsers);
+
+ const { users } = proceedUsers.value;
return (
diff --git a/src/management-system-v2/app/api/private/[environmentId]/logo/route.ts b/src/management-system-v2/app/api/private/[environmentId]/logo/route.ts
deleted file mode 100644
index 81029deec..000000000
--- a/src/management-system-v2/app/api/private/[environmentId]/logo/route.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { getCurrentEnvironment } from '@/components/auth';
-import { NextRequest, NextResponse } from 'next/server';
-import { invalidRequest, readImage } from '../image-helpers';
-import { deleteSpaceLogo, getEnvironmentById, getSpaceLogo } from '@/lib/data/db/iam/environments';
-
-export async function GET(
- _: NextRequest,
- { params: { environmentId } }: { params: { environmentId: string } },
-) {
- const organization = await getEnvironmentById(environmentId);
- if (!organization)
- return new NextResponse(null, {
- status: 404,
- statusText: 'Space with this id does not exist.',
- });
- if (!organization.isOrganization)
- return new NextResponse(null, {
- status: 405,
- statusText: "Personal spaces don't support logos",
- });
-
- try {
- await getCurrentEnvironment(environmentId, {
- permissionErrorHandling: {
- action: 'throw-error',
- },
- });
- } catch (e) {
- return new NextResponse(null, {
- status: 403,
- statusText: "You're not allowed to view this logo",
- });
- }
-
- // TODO: implement this here
- // const imageBuffer = (await getOrganizationLogo(decodeURIComponent(environmentId)))?.logo;
- //
- // if (!imageBuffer)
- // return new NextResponse(null, {
- // status: 204,
- // statusText: 'Organization has no logo',
- // });
- //
- // const fileType = await fileTypeFromBuffer(imageBuffer);
- //
- // if (!fileType) {
- // return new NextResponse(null, {
- // status: 415,
- // statusText: 'Can not read file type of requested image',
- // });
- // }
- //
- // const headers = new Headers();
- // headers.set('Content-Type', fileType.mime);
- //
- // return new NextResponse(imageBuffer, { status: 200, statusText: 'OK', headers });
-}
-
-async function updateOrgLogo(
- request: NextRequest,
- { params: { environmentId } }: { params: { environmentId: string } },
-) {
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
-
- if (!activeEnvironment.isOrganization)
- return new NextResponse(null, {
- status: 405,
- statusText: "Personal spaces don't support logos",
- });
-
- if (!ability.can('update', 'Environment', { environmentId: activeEnvironment.spaceId })) {
- return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to change the logo from an organization',
- });
- }
-
- const isInvalidRequest = invalidRequest(request);
- if (isInvalidRequest) return isInvalidRequest;
-
- const readImageResult = await readImage(request);
- if (readImageResult.error) return readImageResult.error;
-
- // TODO: implement this here
- // saveEntityFile(EntityType.ORGANIZATION,
-
- return new NextResponse(activeEnvironment.spaceId, { status: 201, statusText: 'Created' });
-}
-
-export { updateOrgLogo as POST, updateOrgLogo as PUT };
-
-export async function DELETE(
- _: NextRequest,
- { params: { environmentId } }: { params: { environmentId: string } },
-) {
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
-
- if (!activeEnvironment.isOrganization)
- return new NextResponse(null, {
- status: 405,
- statusText: "Personal spaces don't support logos",
- });
-
- if (!ability.can('update', 'Environment', { environmentId: activeEnvironment.spaceId })) {
- return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to change the logo from an organization',
- });
- }
-
- try {
- deleteSpaceLogo(activeEnvironment.spaceId);
- } catch (e) {
- // We assume the organization didn't have a logo
- return new NextResponse(null, {
- status: 405,
- statusText: 'Something went wrong',
- });
- }
-
- return new NextResponse(activeEnvironment.spaceId, { status: 200, statusText: 'Created' });
-}
diff --git a/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/[imageFileName]/route.ts b/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/[imageFileName]/route.ts
index 34b24d498..ee244c59f 100644
--- a/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/[imageFileName]/route.ts
+++ b/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/[imageFileName]/route.ts
@@ -13,6 +13,7 @@ import jwt from 'jsonwebtoken';
import { TokenPayload } from '@/lib/sharing/process-sharing';
import { invalidRequest, readImage } from '../../../../image-helpers';
import { v4 } from 'uuid';
+import { getErrorMessage } from '@/lib/server-error-handling/user-error';
export async function GET(
request: NextRequest,
@@ -20,60 +21,73 @@ export async function GET(
params: { environmentId, processId, imageFileName },
}: { params: { environmentId: string; processId: string; imageFileName: string } },
) {
- const processMeta = await getProcess(processId, false);
-
- if (!processMeta) {
- return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
- });
- }
-
- let canAccess = false;
-
- // if the user is not unauthenticated check if they have access to the process due to being an owner
- if (environmentId !== 'unauthenticated') {
- const { ability } = await getCurrentEnvironment(environmentId);
-
- canAccess = ability.can('view', toCaslResource('Process', processMeta));
- }
-
- // if the user is not an owner check if they have access if a share token is provided in the query data of the url
- const shareToken = request.nextUrl.searchParams.get('shareToken');
- if (!canAccess && shareToken) {
- const key = process.env.SHARING_ENCRYPTION_SECRET!;
- const {
- processId: shareProcessId,
- embeddedMode,
- timestamp,
- } = jwt.verify(shareToken, key!) as TokenPayload;
-
- canAccess =
- !embeddedMode && shareProcessId === processId && timestamp === processMeta.shareTimestamp;
- }
-
- if (!canAccess) {
+ try {
+ const processMeta = await getProcess(processId, false);
+ if (processMeta.isErr()) throw processMeta.error;
+
+ if (!processMeta.value) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
+
+ let canAccess = false;
+
+ // if the user is not unauthenticated check if they have access to the process due to being an owner
+ if (environmentId !== 'unauthenticated') {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
+ const { ability } = currentEnvironment.value;
+
+ canAccess = ability.can('view', toCaslResource('Process', processMeta.value));
+ }
+
+ // if the user is not an owner check if they have access if a share token is provided in the query data of the url
+ const shareToken = request.nextUrl.searchParams.get('shareToken');
+ if (!canAccess && shareToken) {
+ const key = process.env.SHARING_ENCRYPTION_SECRET!;
+ const {
+ processId: shareProcessId,
+ embeddedMode,
+ timestamp,
+ } = jwt.verify(shareToken, key!) as TokenPayload;
+
+ canAccess =
+ !embeddedMode &&
+ shareProcessId === processId &&
+ timestamp === processMeta.value.shareTimestamp;
+ }
+
+ if (!canAccess) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to view image in this process',
+ });
+ }
+
+ const imageBuffer = await getProcessImage(processId, imageFileName);
+ if (imageBuffer.isErr()) throw imageBuffer.error;
+
+ const fileType = await fileTypeFromBuffer(imageBuffer.value);
+
+ if (!fileType) {
+ return new NextResponse(null, {
+ status: 415,
+ statusText: 'Can not read file type of requested image',
+ });
+ }
+
+ const headers = new Headers();
+ headers.set('Content-Type', fileType.mime);
+
+ return new NextResponse(imageBuffer.value, { status: 200, statusText: 'OK', headers });
+ } catch (error) {
return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to view image in this process',
+ status: 500,
+ statusText: getErrorMessage(error),
});
}
-
- const imageBuffer = await getProcessImage(processId, imageFileName);
-
- const fileType = await fileTypeFromBuffer(imageBuffer);
-
- if (!fileType) {
- return new NextResponse(null, {
- status: 415,
- statusText: 'Can not read file type of requested image',
- });
- }
-
- const headers = new Headers();
- headers.set('Content-Type', fileType.mime);
-
- return new NextResponse(imageBuffer, { status: 200, statusText: 'OK', headers });
}
export async function PUT(
@@ -82,35 +96,45 @@ export async function PUT(
params: { environmentId, processId, imageFileName },
}: { params: { environmentId: string; processId: string; imageFileName: string } },
) {
- const { ability } = await getCurrentEnvironment(environmentId);
+ try {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
+ const { ability } = currentEnvironment.value;
- const process = await getProcess(processId, false);
+ const process = await getProcess(processId, false);
+ if (process.isErr()) throw process.error;
- if (!process) {
- return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
- });
- }
+ if (!process.value) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
- if (!ability.can('view', toCaslResource('Process', process))) {
- return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to view image in this process',
- });
- }
+ if (!ability.can('view', toCaslResource('Process', process.value))) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to view image in this process',
+ });
+ }
- const isInvalidRequest = invalidRequest(request);
- if (isInvalidRequest) return isInvalidRequest;
+ const isInvalidRequest = invalidRequest(request);
+ if (isInvalidRequest) return isInvalidRequest;
- const readImageResult = await readImage(request);
- if (readImageResult.error) return readImageResult.error;
+ const readImageResult = await readImage(request);
+ if (readImageResult.error) return readImageResult.error;
- const newImageFileName = `_image${v4()}.${readImageResult.fileType.ext}`;
+ const newImageFileName = `_image${v4()}.${readImageResult.fileType.ext}`;
- await saveProcessImage(processId, newImageFileName, readImageResult.buffer);
+ await saveProcessImage(processId, newImageFileName, readImageResult.buffer);
- return new NextResponse(newImageFileName, { status: 201, statusText: 'Created' });
+ return new NextResponse(newImageFileName, { status: 201, statusText: 'Created' });
+ } catch (error) {
+ return new NextResponse(null, {
+ status: 500,
+ statusText: getErrorMessage(error),
+ });
+ }
}
export async function DELETE(
@@ -119,25 +143,36 @@ export async function DELETE(
params: { environmentId, processId, imageFileName },
}: { params: { environmentId: string; processId: string; imageFileName: string } },
) {
- const { ability } = await getCurrentEnvironment(environmentId);
+ try {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
- const process = await getProcess(processId, false);
+ const { ability } = currentEnvironment.value;
- if (!process) {
- return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
- });
- }
+ const process = await getProcess(processId, false);
+ if (process.isErr()) throw process.error;
+
+ if (!process.value) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
- if (!ability.can('delete', toCaslResource('Process', process))) {
+ if (!ability.can('delete', toCaslResource('Process', process.value))) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to delete image in this process',
+ });
+ }
+
+ await deleteProcessImage(processId, imageFileName);
+
+ return new NextResponse(null, { status: 200, statusText: 'OK' });
+ } catch (error) {
return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to delete image in this process',
+ status: 500,
+ statusText: getErrorMessage(error),
});
}
-
- await deleteProcessImage(processId, imageFileName);
-
- return new NextResponse(null, { status: 200, statusText: 'OK' });
}
diff --git a/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/route.ts b/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/route.ts
index d826056d9..3cef9237c 100644
--- a/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/route.ts
+++ b/src/management-system-v2/app/api/private/[environmentId]/processes/[processId]/images/route.ts
@@ -4,6 +4,8 @@ import { getProcess, getProcessImageFileNames, saveProcessImage } from '@/lib/da
import { NextRequest, NextResponse } from 'next/server';
import { v4 } from 'uuid';
import { invalidRequest, readImage } from '../../../image-helpers';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { getErrorMessage } from '@/lib/server-error-handling/user-error';
export async function GET(
request: NextRequest,
@@ -11,27 +13,36 @@ export async function GET(
params: { environmentId, processId },
}: { params: { environmentId: string; processId: string } },
) {
- const { ability } = await getCurrentEnvironment(environmentId);
+ try {
+ const currentSpace = await getCurrentEnvironment(environmentId);
+ if (currentSpace.isErr()) throw currentSpace.error;
- const process = await getProcess(processId, false);
+ const process = await getProcess(processId, false);
+ if (process.isErr()) throw process.error;
- if (!process) {
- return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
- });
- }
+ if (!process.value) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
+
+ if (!currentSpace.value.ability.can('view', toCaslResource('Process', process.value))) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to view image filenames in this process',
+ });
+ }
- if (!ability.can('view', toCaslResource('Process', process))) {
+ const fileNames = await getProcessImageFileNames(processId);
+
+ return NextResponse.json(fileNames);
+ } catch (error) {
return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to view image filenames in this process',
+ status: 500,
+ statusText: getErrorMessage(error),
});
}
-
- const fileNames = await getProcessImageFileNames(processId);
-
- return NextResponse.json(fileNames);
}
export async function POST(
@@ -40,33 +51,43 @@ export async function POST(
params: { environmentId, processId },
}: { params: { environmentId: string; processId: string } },
) {
- const isInvalidRequest = invalidRequest(request);
- if (isInvalidRequest) return isInvalidRequest;
+ try {
+ const isInvalidRequest = invalidRequest(request);
+ if (isInvalidRequest) return isInvalidRequest;
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
+ const ability = currentEnvironment.value.ability;
- const process = await getProcess(processId, false);
+ const process = await getProcess(processId, false);
+ if (process.isErr()) throw process.error;
- if (!process) {
- return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
- });
- }
+ if (!process.value) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
- if (!ability.can('view', toCaslResource('Process', process))) {
- return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to view image in this process',
- });
- }
+ if (!ability.can('view', toCaslResource('Process', process.value))) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to view image in this process',
+ });
+ }
- const readImageResult = await readImage(request);
- if (readImageResult.error) return readImageResult.error;
+ const readImageResult = await readImage(request);
+ if (readImageResult.error) return readImageResult.error;
- const imageFileName = `_image${v4()}.${readImageResult.fileType.ext}`;
+ const imageFileName = `_image${v4()}.${readImageResult.fileType.ext}`;
- await saveProcessImage(processId, imageFileName, readImageResult.buffer);
+ await saveProcessImage(processId, imageFileName, readImageResult.buffer);
- return new NextResponse(imageFileName, { status: 201, statusText: 'Created' });
+ return new NextResponse(imageFileName, { status: 201, statusText: 'Created' });
+ } catch (error) {
+ return new NextResponse(null, {
+ status: 500,
+ statusText: getErrorMessage(error),
+ });
+ }
}
diff --git a/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts b/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
index 3bc7cf608..53df21f17 100644
--- a/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
+++ b/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
@@ -18,11 +18,12 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized: Invalid Bearer token' }, { status: 403 });
}
- const { count } = await deleteInactiveGuestUsers(GUESET_INACTIVE_TIME);
+ const result = await deleteInactiveGuestUsers(GUESET_INACTIVE_TIME);
+ if (result.isErr()) throw result.error;
return NextResponse.json(
{
- message: `${count} guest users deleted`,
+ message: `${result.value.count} guest users deleted`,
},
{ status: 200 },
);
diff --git a/src/management-system-v2/app/api/private/file-manager/route.ts b/src/management-system-v2/app/api/private/file-manager/route.ts
index 77bf5c752..5d48f4ba7 100644
--- a/src/management-system-v2/app/api/private/file-manager/route.ts
+++ b/src/management-system-v2/app/api/private/file-manager/route.ts
@@ -3,7 +3,6 @@ import { getCurrentEnvironment } from '@/components/auth';
import { toCaslResource } from '@/lib/ability/caslAbility';
import { NextRequest, NextResponse } from 'next/server';
import { Readable } from 'node:stream';
-import type { ReadableStream } from 'node:stream/web';
import jwt from 'jsonwebtoken';
import { TokenPayload } from '@/lib/sharing/process-sharing';
import { getProcess } from '@/lib/data/processes';
@@ -13,244 +12,271 @@ import {
retrieveEntityFile,
saveEntityFileOrGetPresignedUrl,
} from '@/lib/data/file-manager-facade';
+import { getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
const MAX_IMAGE_SIZE = 2 * 1024 * 1024; // 2 MB
export async function GET(request: NextRequest) {
- const searchParams = request.nextUrl.searchParams;
- const entityId = searchParams.get('entityId');
- const entityType = searchParams.get('entityType');
- const environmentId = searchParams.get('environmentId') || 'unauthenticated';
- const filePath = searchParams.get('filePath') || undefined;
- if (
- !entityId ||
- !entityType ||
- !environmentId ||
- (entityType === EntityType.PROCESS && !filePath)
- ) {
- return new NextResponse(null, {
- status: 400,
- statusText: 'entityId, entityType, environmentId and filePath required as URL search params',
- });
- }
-
- if (entityType === EntityType.PROCESS) {
- let canAccess = false;
-
- const processMeta = await getProcess(entityId, environmentId, true); // true --> skip the validity check as it will be done below
- if (!processMeta || 'error' in processMeta) {
+ try {
+ const searchParams = request.nextUrl.searchParams;
+ const entityId = searchParams.get('entityId');
+ const entityType = searchParams.get('entityType');
+ const environmentId = searchParams.get('environmentId') || 'unauthenticated';
+ const filePath = searchParams.get('filePath') || undefined;
+ if (
+ !entityId ||
+ !entityType ||
+ !environmentId ||
+ (entityType === EntityType.PROCESS && !filePath)
+ ) {
return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
+ status: 400,
+ statusText:
+ 'entityId, entityType, environmentId and filePath required as URL search params',
});
}
- if (environmentId !== 'unauthenticated') {
- const { ability } = await getCurrentEnvironment(environmentId);
+ if (entityType === EntityType.PROCESS) {
+ let canAccess = false;
- canAccess = ability.can('view', toCaslResource('Process', processMeta));
- }
+ const processMeta = await getProcess(entityId, environmentId, true); // true --> skip the validity check as it will be done below
+ if (!processMeta || 'error' in processMeta) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
- // if the user is not an owner check if they have access if a share token is provided in the query data of the url
- const shareToken = searchParams.get('shareToken');
- if (!canAccess && shareToken) {
- const key = process.env.SHARING_ENCRYPTION_SECRET!;
- const {
- processId: shareProcessId,
- embeddedMode,
- timestamp,
- } = jwt.verify(shareToken, key!) as TokenPayload;
- canAccess =
- !embeddedMode && shareProcessId === entityId && timestamp === processMeta.shareTimestamp;
- }
+ if (environmentId !== 'unauthenticated') {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
+ const { ability } = currentEnvironment.value;
- if (!canAccess) {
- return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to access files in this process',
- });
- }
- }
+ canAccess = ability.can('view', toCaslResource('Process', processMeta));
+ }
- try {
- const data = await retrieveEntityFile(entityType as EntityType, entityId, filePath);
+ // if the user is not an owner check if they have access if a share token is provided in the query data of the url
+ const shareToken = searchParams.get('shareToken');
+ if (!canAccess && shareToken) {
+ const key = process.env.SHARING_ENCRYPTION_SECRET!;
+ const {
+ processId: shareProcessId,
+ embeddedMode,
+ timestamp,
+ } = jwt.verify(shareToken, key!) as TokenPayload;
+ canAccess =
+ !embeddedMode && shareProcessId === entityId && timestamp === processMeta.shareTimestamp;
+ }
- const fileType = await fileTypeFromBuffer(data as Buffer);
- if (!fileType) {
- return new NextResponse(null, {
- status: 415,
- statusText: 'Cannot read file type of requested file',
- });
+ if (!canAccess) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to access files in this process',
+ });
+ }
}
- let mimeType: string = fileType.mime;
-
- if (fileType.mime === 'application/xml' && filePath?.endsWith('.svg'))
- mimeType = 'image/svg+xml';
- const headers = new Headers();
- headers.set('Content-Type', mimeType);
- return new NextResponse(data, { status: 200, statusText: 'OK', headers });
- } catch (error: any) {
- console.error('Error retrieving file:', error);
+ try {
+ const data = await retrieveEntityFile(entityType as EntityType, entityId, filePath);
- if (error.message.includes('File') && error.message.includes('does not exist')) {
+ const fileType = await fileTypeFromBuffer(data as Buffer);
+ if (!fileType) {
+ return new NextResponse(null, {
+ status: 415,
+ statusText: 'Cannot read file type of requested file',
+ });
+ }
+ let mimeType: string = fileType.mime;
+
+ if (fileType.mime === 'application/xml' && filePath?.endsWith('.svg'))
+ mimeType = 'image/svg+xml';
+
+ const headers = new Headers();
+ headers.set('Content-Type', mimeType);
+ return new NextResponse(data, { status: 200, statusText: 'OK', headers });
+ } catch (error: any) {
+ console.error('Error retrieving file:', error);
+
+ if (error.message.includes('File') && error.message.includes('does not exist')) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'File not found',
+ });
+ }
return new NextResponse(null, {
- status: 404,
- statusText: 'File not found',
+ status: 500,
+ statusText: 'Internal Server Error',
});
}
+ } catch (error) {
return new NextResponse(null, {
status: 500,
- statusText: 'Internal Server Error',
+ statusText: getErrorMessage(error),
});
}
}
export async function PUT(request: NextRequest) {
- const searchParams = request.nextUrl.searchParams;
- const entityId = searchParams.get('entityId');
- const entityType = searchParams.get('entityType');
- const environmentId = searchParams.get('environmentId');
- const filePath = searchParams.get('filePath');
- const saveWithoutSavingReference = !!searchParams.get('saveWithoutSavingReference');
-
- if (!entityId || !environmentId || !entityType || !filePath) {
- return new NextResponse(null, {
- status: 400,
- statusText: 'entityId, entityType, environmentId and filePath required as URL search params',
- });
- }
-
- const body = request.body;
- if (!body)
- return new NextResponse(null, {
- status: 400,
- statusText: 'No file data provided in request body',
- });
-
- const { ability } = await getCurrentEnvironment(environmentId);
- if (entityType === EntityType.PROCESS) {
- const process = await getProcess(entityId, environmentId);
- if (!process) {
+ try {
+ const searchParams = request.nextUrl.searchParams;
+ const entityId = searchParams.get('entityId');
+ const entityType = searchParams.get('entityType');
+ const environmentId = searchParams.get('environmentId');
+ const filePath = searchParams.get('filePath');
+ const saveWithoutSavingReference = !!searchParams.get('saveWithoutSavingReference');
+
+ if (!entityId || !environmentId || !entityType || !filePath) {
return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
+ status: 400,
+ statusText:
+ 'entityId, entityType, environmentId and filePath required as URL search params',
});
}
- if (!ability.can('view', toCaslResource('Process', process))) {
+
+ const body = request.body;
+ if (!body)
return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to view image in this process',
+ status: 400,
+ statusText: 'No file data provided in request body',
});
- }
- }
-
- // NOTE: This may need changing
- // @ts-expect-error ReadableStream in next.js' request isn't quite compatible with Readable.fromWeb (node:stream)
- const reader = Readable.fromWeb(body);
- const chunks: Uint8Array[] = [];
- let totalLength = 0;
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
+ const { ability } = currentEnvironment.value;
+
+ if (entityType === EntityType.PROCESS) {
+ const process = await getProcess(entityId, environmentId);
+ if (!process) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
+ if (!ability.can('view', toCaslResource('Process', process))) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to view image in this process',
+ });
+ }
+ }
- for await (const chunk of reader) {
- if (chunk) {
- chunks.push(chunk);
- totalLength += chunk.length;
- if (totalLength > MAX_FILE_SIZE) {
- // For some reason, after calling destroy the http response is not sent, causing the request to hang
- reader.pause();
+ // NOTE: This may need changing
+ // @ts-expect-error ReadableStream in next.js' request isn't quite compatible with Readable.fromWeb (node:stream)
+ const reader = Readable.fromWeb(body);
+
+ const chunks: Uint8Array[] = [];
+ let totalLength = 0;
+
+ for await (const chunk of reader) {
+ if (chunk) {
+ chunks.push(chunk);
+ totalLength += chunk.length;
+ if (totalLength > MAX_FILE_SIZE) {
+ // For some reason, after calling destroy the http response is not sent, causing the request to hang
+ reader.pause();
+ }
}
}
- }
- // The return doesn't work inside of the iterator for loop
- if (totalLength > MAX_FILE_SIZE)
- return new NextResponse(null, {
- status: 413,
- statusText: `Allowed file size of ${MAX_FILE_SIZE / (1024 * 1024)} MB exceeded`,
- });
+ // The return doesn't work inside of the iterator for loop
+ if (totalLength > MAX_FILE_SIZE)
+ return new NextResponse(null, {
+ status: 413,
+ statusText: `Allowed file size of ${MAX_FILE_SIZE / (1024 * 1024)} MB exceeded`,
+ });
- // Proceed with processing if the size limit is not exceeded
- const buffer = Buffer.concat(
- chunks.map((chunk) => Buffer.from(chunk)),
- totalLength,
- );
+ // Proceed with processing if the size limit is not exceeded
+ const buffer = Buffer.concat(
+ chunks.map((chunk) => Buffer.from(chunk)),
+ totalLength,
+ );
- const fileType = await fileTypeFromBuffer(buffer);
- if (!fileType) {
- return new NextResponse(null, {
- status: 415,
- statusText: 'Cannot store file with unknown file type',
- });
- }
+ const fileType = await fileTypeFromBuffer(buffer);
+ if (!fileType) {
+ return new NextResponse(null, {
+ status: 415,
+ statusText: 'Cannot store file with unknown file type',
+ });
+ }
- try {
- const res = await saveEntityFileOrGetPresignedUrl(
- entityType as EntityType,
- entityId,
- fileType.mime,
- filePath,
- buffer,
- { saveWithoutSavingReference },
- );
+ try {
+ const res = await saveEntityFileOrGetPresignedUrl(
+ entityType as EntityType,
+ entityId,
+ fileType.mime,
+ filePath,
+ buffer,
+ { saveWithoutSavingReference },
+ );
- if ('error' in res) throw new Error((res.error as any).message);
+ if ('error' in res) throw new Error((res.error as any).message);
- if (!res.filePath) {
- throw new Error('No file name returned');
- }
+ if (!res.filePath) {
+ throw new Error('No file name returned');
+ }
- const { filePath: newFileName } = res;
+ const { filePath: newFileName } = res;
- return new NextResponse(newFileName, {
- status: 200,
- statusText: 'OK',
- });
+ return new NextResponse(newFileName, {
+ status: 200,
+ statusText: 'OK',
+ });
+ } catch (error) {
+ console.error('Error saving file:', error);
+ return new NextResponse(null, { status: 500, statusText: 'Internal Server Error' });
+ }
} catch (error) {
- console.error('Error saving file:', error);
- return new NextResponse(null, { status: 500, statusText: 'Internal Server Error' });
+ return new NextResponse(null, { status: 500, statusText: getErrorMessage(error) });
}
}
export async function DELETE(request: NextRequest) {
- const searchParams = request.nextUrl.searchParams;
- const entityId = searchParams.get('entityId');
- const entityType = searchParams.get('entityType');
- const environmentId = searchParams.get('environmentId');
- const filePath = searchParams.get('filePath ');
-
- if (!entityId || !entityType || !environmentId || !filePath) {
- return new NextResponse(null, {
- status: 400,
- statusText: 'entityId, entityType, environmentId and filePath required as URL search params',
- });
- }
-
- const { ability } = await getCurrentEnvironment(environmentId);
- if (entityType === EntityType.PROCESS) {
- const process = await getProcess(entityId, environmentId);
+ try {
+ const searchParams = request.nextUrl.searchParams;
+ const entityId = searchParams.get('entityId');
+ const entityType = searchParams.get('entityType');
+ const environmentId = searchParams.get('environmentId');
+ const filePath = searchParams.get('filePath ');
- if (!process) {
+ if (!entityId || !entityType || !environmentId || !filePath) {
return new NextResponse(null, {
- status: 404,
- statusText: 'Process with this id does not exist.',
+ status: 400,
+ statusText:
+ 'entityId, entityType, environmentId and filePath required as URL search params',
});
}
- if (!ability.can('delete', toCaslResource('Process', process))) {
- return new NextResponse(null, {
- status: 403,
- statusText: 'Not allowed to delete image in this process',
- });
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) throw currentEnvironment.error;
+ const { ability } = currentEnvironment.value;
+
+ if (entityType === EntityType.PROCESS) {
+ const process = await getProcess(entityId, environmentId);
+
+ if (!process) {
+ return new NextResponse(null, {
+ status: 404,
+ statusText: 'Process with this id does not exist.',
+ });
+ }
+
+ if (!ability.can('delete', toCaslResource('Process', process))) {
+ return new NextResponse(null, {
+ status: 403,
+ statusText: 'Not allowed to delete image in this process',
+ });
+ }
}
- }
- try {
- await deleteEntityFile(entityType as EntityType, entityId, filePath);
- return new NextResponse(null, { status: 200, statusText: 'OK' });
+ try {
+ await deleteEntityFile(entityType as EntityType, entityId, filePath);
+ return new NextResponse(null, { status: 200, statusText: 'OK' });
+ } catch (error) {
+ console.error('Error deleting file:', error);
+ return new NextResponse(null, { status: 500, statusText: 'Internal Server Error' });
+ }
} catch (error) {
- console.error('Error deleting file:', error);
- return new NextResponse(null, { status: 500, statusText: 'Internal Server Error' });
+ return new NextResponse(null, { status: 500, statusText: getErrorMessage(error) });
}
}
diff --git a/src/management-system-v2/app/api/register-new-user/route.ts b/src/management-system-v2/app/api/register-new-user/route.ts
index e35fd41ff..5a6a7e528 100644
--- a/src/management-system-v2/app/api/register-new-user/route.ts
+++ b/src/management-system-v2/app/api/register-new-user/route.ts
@@ -7,6 +7,7 @@ import db from '@/lib/data/db';
import { addUser, setUserPassword } from '@/lib/data/db/iam/users';
import { getTokenHash, notExpired } from '@/lib/email-verification-tokens/utils';
import { env } from '@/lib/ms-config/env-vars';
+import { getErrorMessage } from '@/lib/server-error-handling/user-error';
// TODO: maybe add PRETTIER error handling
@@ -22,7 +23,12 @@ export const GET = async (req: Request) => {
const tokenHash = await getTokenHash(token);
- const verificationToken = await getEmailVerificationToken({ token: tokenHash, identifier });
+ const _verificationToken = await getEmailVerificationToken({ token: tokenHash, identifier });
+ if (_verificationToken.isErr()) throw _verificationToken.error;
+ if (!_verificationToken.value)
+ return Response.json({ message: 'Bad request' }, { status: 400 });
+ const verificationToken = _verificationToken.value!;
+
if (verificationToken?.type !== 'register_new_user')
return Response.json({ message: 'Bad request' }, { status: 400 });
@@ -42,9 +48,16 @@ export const GET = async (req: Request) => {
},
tx,
);
+ if (user.isErr()) throw user.error;
- if (verificationToken.passwordHash)
- await setUserPassword(user.id, verificationToken.passwordHash, tx);
+ if (verificationToken.passwordHash) {
+ const setPassword = await setUserPassword(
+ user.value.id,
+ verificationToken.passwordHash,
+ tx,
+ );
+ if (setPassword.isErr()) throw setPassword.error;
+ }
// We can't delete the token yet, because we need it to exist in the db for the redirect to
// nextAuth's email flow. We set the expiration to 5 minutes in the future, so that it can't
@@ -53,11 +66,12 @@ export const GET = async (req: Request) => {
// username & email, and if the user manages to use it again, nothing bad will happen, the
// user will just have 2 or more users in the db.
- await updateEmailVerificationTokenExpiration(
+ const updateToken = await updateEmailVerificationTokenExpiration(
{ token: tokenHash, identifier },
new Date(Date.now() + 5 * 60 * 1000),
tx,
);
+ if (updateToken.isErr()) throw updateToken.error;
});
// The user is already created, now we redirect to nextAuth, so that it can set the cookies
@@ -76,7 +90,7 @@ export const GET = async (req: Request) => {
redirectUrl = nextAuthEmailRedirect.toString();
} catch (e) {
console.error(e);
- return Response.json({ message: 'Error registeering user' }, { status: 500 });
+ return Response.json({ message: getErrorMessage(e) }, { status: 500 });
}
redirect(redirectUrl);
diff --git a/src/management-system-v2/app/change-email/page.tsx b/src/management-system-v2/app/change-email/page.tsx
index 52322dd3d..49430a8e1 100644
--- a/src/management-system-v2/app/change-email/page.tsx
+++ b/src/management-system-v2/app/change-email/page.tsx
@@ -7,6 +7,7 @@ import ChangeEmailCard from './change-email-card';
import { Card } from 'antd';
import Link from 'next/link';
import Content from '@/components/content';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
const searchParamsSchema = z.object({ email: z.string().email(), token: z.string() });
@@ -15,7 +16,11 @@ export default async function ChangeEmailPage({ searchParams }: { searchParams:
if (!parsedSearchParams.success) redirect('/');
const { email, token } = parsedSearchParams.data;
- const { session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { session } = currentUser.value;
const userId = session?.user.id;
if (!userId)
return (
@@ -41,12 +46,15 @@ export default async function ChangeEmailPage({ searchParams }: { searchParams:
identifier: email,
token: await getTokenHash(token),
});
+ if (verificationToken.isErr()) {
+ return errorResponse(verificationToken);
+ }
if (
- !verificationToken ||
- verificationToken.type !== 'change_email' ||
- verificationToken.userId !== userId ||
- !(await notExpired(verificationToken))
+ !verificationToken.value ||
+ verificationToken.value.type !== 'change_email' ||
+ verificationToken.value.userId !== userId ||
+ !(await notExpired(verificationToken.value))
)
redirect('/');
diff --git a/src/management-system-v2/app/create-organization/page.tsx b/src/management-system-v2/app/create-organization/page.tsx
index 5d4591523..eb530598c 100644
--- a/src/management-system-v2/app/create-organization/page.tsx
+++ b/src/management-system-v2/app/create-organization/page.tsx
@@ -7,14 +7,24 @@ import { getErrorMessage, userError } from '@/lib/server-error-handling/user-err
import { getMSConfig } from '@/lib/ms-config/ms-config';
import { notFound } from 'next/navigation';
import { env } from '@/lib/ms-config/env-vars';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
async function createInactiveEnvironment(data: UserOrganizationEnvironmentInput) {
'use server';
try {
const user = await getCurrentUser();
- if (user.session?.user && !user.session?.user.isGuest)
+ if (user.isErr()) {
+ return userError(getErrorMessage(user.error));
+ }
+ if (user.value.session?.user && !user.value.session?.user.isGuest)
return userError('This function is only for guest users and users that are not signed in');
- return addEnvironment({ ...data, isOrganization: true, isActive: false });
+
+ const result = await addEnvironment({ ...data, isOrganization: true, isActive: false });
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
const message = getErrorMessage(e);
return userError(message);
@@ -33,7 +43,11 @@ const Page = async () => {
return notFound();
}
- const { session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { session } = currentUser.value;
const needsToAuthenticate = !session?.user || session?.user.isGuest;
let providers = getProviders();
diff --git a/src/management-system-v2/app/shared-viewer/documentation-page.tsx b/src/management-system-v2/app/shared-viewer/documentation-page.tsx
index 86aa375ad..ae65613ad 100644
--- a/src/management-system-v2/app/shared-viewer/documentation-page.tsx
+++ b/src/management-system-v2/app/shared-viewer/documentation-page.tsx
@@ -14,14 +14,9 @@ import { Button, Tooltip, Typography, Space, Grid } from 'antd';
import { PrinterOutlined } from '@ant-design/icons';
import Content from '@/components/content';
-
-import { getProcess } from '@/lib/data/db/process';
import { useRouter } from 'next/navigation';
-
import { getSVGFromBPMN } from '@/lib/process-export/util';
-
import styles from './documentation-page.module.scss';
-
import { getRootFromElement, getDefinitionsVersionInformation } from '@proceed/bpmn-helper';
import SettingsModal, { settingsOptions, SettingsOption } from './settings-modal';
@@ -38,6 +33,7 @@ import {
getElementSVG,
} from './documentation-page-utils';
import { Environment } from '@/lib/data/environment-schema';
+import { Process } from '@/lib/data/process-schema';
/**
* Import the Editor asynchronously since it implicitly uses browser logic which leads to errors when this file is loaded on the server
@@ -53,7 +49,7 @@ const markdownEditor: Promise =
: (Promise.resolve(null) as any);
type BPMNSharedViewerProps = {
- processData: Awaited>;
+ processData: Process;
isOwner: boolean;
userWorkspaces: Environment[];
defaultSettings?: SettingsOption;
diff --git a/src/management-system-v2/app/shared-viewer/page.tsx b/src/management-system-v2/app/shared-viewer/page.tsx
index da344e905..d01fd1424 100644
--- a/src/management-system-v2/app/shared-viewer/page.tsx
+++ b/src/management-system-v2/app/shared-viewer/page.tsx
@@ -9,6 +9,7 @@ import { ImportsInfo } from './documentation-page-utils';
import BPMNCanvas from '@/components/bpmn-canvas';
import { Process } from '@/lib/data/process-schema';
import ErrorMessage from '../../components/error-message';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
import styles from './page.module.scss';
import Layout from '@/app/(dashboard)/[environmentId]/layout-client';
@@ -21,6 +22,8 @@ import { getDefinitionsAndProcessIdForEveryCallActivity } from '@proceed/bpmn-he
import { SettingsOption } from './settings-modal';
import { asyncMap } from '@/lib/helpers/javascriptHelpers';
import { env } from '@/lib/ms-config/env-vars';
+import { getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
+import { Result } from 'neverthrow';
interface PageProps {
searchParams: {
@@ -45,20 +48,31 @@ const getProcessInfo = async (
isImport: boolean,
versionId?: string,
) => {
- const { session, userId } = await getCurrentUser();
-
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { session, userId } = currentUser.value;
let spaceId;
let isOwner = false;
let processData;
-
// check if there is a session (=> the user is already logged in)
if (session) {
- const { ability, activeEnvironment } = await getCurrentEnvironment(session?.user.id);
+ const currentSpace = await getCurrentEnvironment(session?.user.id);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { ability, activeEnvironment } = currentSpace.value;
+
({ spaceId } = activeEnvironment);
// get all the processes the user has access to
const ownedProcesses = await getProcesses(spaceId, ability);
+ if (ownedProcesses.isErr()) {
+ return userError(getErrorMessage(ownedProcesses.error));
+ }
+
// check if the current user is the owner of the process(/has access to the process) => if yes give access regardless of sharing status
- isOwner = ownedProcesses.some((process) => process.id === definitionId);
+ isOwner = ownedProcesses.value.some((process) => process.id === definitionId);
}
if (isOwner) {
@@ -70,7 +84,11 @@ const getProcessInfo = async (
? await getProcessVersionBpmn(definitionId, versionId)
: await getProcessBpmn(definitionId);
- processData = { ...processMetaData, bpmn };
+ if (bpmn.isErr()) {
+ return userError(getErrorMessage(bpmn.error));
+ }
+
+ processData = { ...processMetaData, bpmn: bpmn.value };
}
} else {
// the user has no regular access to the process so get the process data from the sharing api
@@ -83,8 +101,8 @@ const getProcessInfo = async (
if (
// bypass the timestamp check for imports
!isImport &&
- ((embeddedMode && timestamp !== processData.allowIframeTimestamp) ||
- (!embeddedMode && timestamp !== processData.shareTimestamp))
+ ((embeddedMode && timestamp !== processData.value.allowIframeTimestamp) ||
+ (!embeddedMode && timestamp !== processData.value.shareTimestamp))
) {
return ;
}
@@ -126,20 +144,32 @@ const getImportInfos = async (bpmn: string, knownInfos: ImportsInfo) => {
const SharedViewer = async ({ searchParams }: PageProps) => {
const { token, version, settings } = searchParams;
- const { session, userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { session, userId } = currentUser.value;
if (typeof token !== 'string') {
return ;
}
- const userEnvironments: Environment[] = [(await getEnvironmentById(userId))!];
+ const personalEnvironment = await getEnvironmentById(userId);
+ if (personalEnvironment.isErr()) return errorResponse(personalEnvironment);
+
+ const userEnvironments: Environment[] = [personalEnvironment.value];
+
const userOrgEnvs = await getUserOrganizationEnvironments(userId);
+ if (userOrgEnvs.isErr()) return errorResponse(userOrgEnvs);
- const orgEnvironments = await asyncMap(
- userOrgEnvs,
- async (environmentId) => (await getEnvironmentById(environmentId))!,
+ const orgEnvironments = Result.combine(
+ await asyncMap(
+ userOrgEnvs.value,
+ async (environmentId) => (await getEnvironmentById(environmentId))!,
+ ),
);
+ if (orgEnvironments.isErr()) return errorResponse(orgEnvironments);
- userEnvironments.push(...orgEnvironments);
+ userEnvironments.push(...orgEnvironments.value);
let isOwner = false;
diff --git a/src/management-system-v2/app/shared-viewer/process-document.tsx b/src/management-system-v2/app/shared-viewer/process-document.tsx
index 09b1f055c..874c57e8e 100644
--- a/src/management-system-v2/app/shared-viewer/process-document.tsx
+++ b/src/management-system-v2/app/shared-viewer/process-document.tsx
@@ -18,6 +18,7 @@ import { useEnvironment } from '@/components/auth-can';
import { EntityType } from '@/lib/helpers/fileManagerHelpers';
import { useFileManager } from '@/lib/useFileManager';
import { fromCustomUTCString } from '@/lib/helpers/timeHelper';
+import { Process } from '@/lib/data/process-schema';
export type VersionInfo = {
id?: string;
@@ -27,7 +28,7 @@ export type VersionInfo = {
};
type ProcessDocumentProps = {
- processData: Awaited>;
+ processData: Process;
settings: ActiveSettings;
processHierarchy?: ElementInfo;
version: VersionInfo;
diff --git a/src/management-system-v2/app/shared-viewer/workspace-selection.tsx b/src/management-system-v2/app/shared-viewer/workspace-selection.tsx
index 7310452d4..e977cf18a 100644
--- a/src/management-system-v2/app/shared-viewer/workspace-selection.tsx
+++ b/src/management-system-v2/app/shared-viewer/workspace-selection.tsx
@@ -18,9 +18,11 @@ import { FolderTreeNode, getSpaceFolderTree } from '@/lib/data/folders';
import { useFileManager } from '@/lib/useFileManager';
import { EntityType } from '@/lib/helpers/fileManagerHelpers';
import { useSession } from '@/components/auth-can';
+import { Process } from '@/lib/data/process-schema';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
type WorkspaceSelectionProps = {
- processData: Awaited>;
+ processData: Process;
versionInfo: VersionInfo;
workspaces: Environment[];
};
@@ -65,7 +67,10 @@ const WorkspaceSelection: React.FC<
const handleWorkspaceClick = async (workspace: Environment) => {
setSelectedWorkspace(workspace);
onWorkspaceSelect(workspace);
+
const spaceFolderTree = await getSpaceFolderTree(workspace.id);
+ if (isUserErrorResponse(spaceFolderTree)) return;
+
setSelectedSpaceFolderTree(spaceFolderTree);
onFolderSelect(null);
};
diff --git a/src/management-system-v2/app/transfer-processes/page.tsx b/src/management-system-v2/app/transfer-processes/page.tsx
index 88ddd048e..44af18ed1 100644
--- a/src/management-system-v2/app/transfer-processes/page.tsx
+++ b/src/management-system-v2/app/transfer-processes/page.tsx
@@ -6,6 +6,7 @@ import { Card, Result } from 'antd';
import { redirect } from 'next/navigation';
import ProcessTransferButtons from './transfer-processes-confirmation-buttons';
import { getGuestReference } from '@/lib/reference-guest-user-token';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
export default async function TransferProcessesPage({
searchParams,
@@ -15,7 +16,11 @@ export default async function TransferProcessesPage({
referenceToken?: string;
};
}) {
- const { userId, session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+ const { userId, session } = currentUser.value;
if (!session) redirect('api/auth/signin');
if (session.user.isGuest) redirect('/');
@@ -44,17 +49,23 @@ export default async function TransferProcessesPage({
if (!guestId || guestId === userId) redirect(callbackUrl);
const possibleGuest = await getUserById(guestId);
+ if (possibleGuest.isErr()) {
+ return errorResponse(possibleGuest);
+ }
// possibleGuest might be a normal user, this would happen if the user signed in with an existing
// accocunt, generating the token above, and before using it, he signed in with a new account.
// We only go further then this redirect, if the user signed in with an account that was
// already linked to an existing user
- if (!possibleGuest || !possibleGuest.isGuest) redirect(callbackUrl);
+ if (!possibleGuest || !possibleGuest.value.isGuest) redirect(callbackUrl);
// NOTE: this ignores folders
const guestProcesses = await getProcesses(guestId);
+ if (guestProcesses.isErr()) {
+ return errorResponse(guestProcesses);
+ }
// If the guest has no processes -> nothing to do
- if (guestProcesses.length === 0) redirect(callbackUrl);
+ if (guestProcesses.value.length === 0) redirect(callbackUrl);
return (
@@ -62,7 +73,8 @@ export default async function TransferProcessesPage({
title="Would you like to transfer your processes?"
style={{ maxWidth: '70ch', margin: 'auto' }}
>
- Your guest account had {guestProcesses.length} process{guestProcesses.length !== 1 && 'es'}.
+ Your guest account had {guestProcesses.value.length} process
+ {guestProcesses.value.length !== 1 && 'es'}.
Would you like to transfer them to your account?
diff --git a/src/management-system-v2/app/transfer-processes/server-actions.ts b/src/management-system-v2/app/transfer-processes/server-actions.ts
index c44ecad35..f3a06a1ac 100644
--- a/src/management-system-v2/app/transfer-processes/server-actions.ts
+++ b/src/management-system-v2/app/transfer-processes/server-actions.ts
@@ -1,5 +1,4 @@
'use server';
-
import { getCurrentUser } from '@/components/auth';
import { Folder } from '@/lib/data/folder-schema';
import { getFolders, getRootFolder, moveFolder, updateFolderMetaData } from '@/lib/data/db/folders';
@@ -7,11 +6,16 @@ import { getProcesses, updateProcess } from '@/lib/data/db/process';
import { getUserById, deleteUser } from '@/lib/data/db/iam/users';
import { Process } from '@/lib/data/process-schema';
import { getGuestReference } from '@/lib/reference-guest-user-token';
-import { UserErrorType, userError } from '@/lib/server-error-handling/user-error';
+import { UserErrorType, getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
import { redirect } from 'next/navigation';
+import db from '@/lib/data/db';
export async function transferProcesses(referenceToken: string, callbackUrl: string = '/') {
- const { session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { session } = currentUser.value;
if (!session) return userError("You're not signed in", UserErrorType.PermissionError);
if (session.user.isGuest)
return userError("You can't be a guest to transfer processes", UserErrorType.PermissionError);
@@ -23,44 +27,91 @@ export async function transferProcesses(referenceToken: string, callbackUrl: str
if (guestId === session.user.id) redirect(callbackUrl);
const possibleGuest = await getUserById(guestId);
- if (!possibleGuest || !possibleGuest.isGuest)
+ if (possibleGuest.isErr()) {
+ return userError(getErrorMessage(possibleGuest.error));
+ }
+ if (!possibleGuest.value || !possibleGuest.value.isGuest)
return userError('Invalid guest id', UserErrorType.PermissionError);
// Processes and folders under root folder of guest space guet their folderId changed to the
// root folder of the new owner space, for the rest we just update the environmentId
- const userRootFolderId = (await getRootFolder(session.user.id)).id;
- const guestRootFolderId = (await getRootFolder(guestId)).id;
+ const userRootFolder = await getRootFolder(session.user.id);
+ if (userRootFolder.isErr()) {
+ return userError(getErrorMessage(userRootFolder.error));
+ }
+
+ const guestRootFolder = await getRootFolder(guestId);
+ if (guestRootFolder.isErr()) {
+ return userError(getErrorMessage(guestRootFolder.error));
+ }
// no ability check necessary, owners of personal spaces can do anything
const guestProcesses = await getProcesses(guestId);
- for (const process of guestProcesses) {
- const processUpdate: Partial = {
- environmentId: session.user.id,
- creatorId: session.user.id,
- };
- if (process.folderId === guestRootFolderId) processUpdate.folderId = userRootFolderId;
- await updateProcess(process.id, processUpdate);
+ if (guestProcesses.isErr()) {
+ return userError(getErrorMessage(guestProcesses.error));
}
- const guestFolders = await getFolders(guestId);
- for (const folder of guestFolders) {
- if (folder.id === guestRootFolderId) continue;
-
- const folderData: Partial = { createdBy: session.user.id };
-
- if (folder.parentId === guestRootFolderId) moveFolder(folder.id, userRootFolderId);
- else folderData.environmentId = session.user.id;
-
- updateFolderMetaData(folder.id, folderData);
+ try {
+ await db.$transaction(async (tx) => {
+ for (const process of guestProcesses.value) {
+ const processUpdate: Partial = {
+ environmentId: session.user.id,
+ creatorId: session.user.id,
+ };
+
+ if (process.folderId === guestRootFolder.value.id) {
+ processUpdate.folderId = userRootFolder.value.id;
+ }
+ const result = await updateProcess(process.id, processUpdate, tx);
+ if (result.isErr()) {
+ throw result.error;
+ }
+ }
+
+ const guestFolders = await getFolders(guestId);
+ if (guestFolders.isErr()) {
+ throw guestFolders.error;
+ }
+
+ for (const folder of guestFolders.value) {
+ // skip the guest's root folder
+ if (folder.id === guestRootFolder.value.id) continue;
+
+ const folderData: Partial = { createdBy: session.user.id };
+
+ if (folder.parentId === guestRootFolder.value.id) {
+ const moveResult = await moveFolder(folder.id, userRootFolder.value.id, undefined, tx);
+ if (moveResult?.isErr()) {
+ throw moveResult.error;
+ }
+ } else {
+ folderData.environmentId = session.user.id;
+ }
+
+ const updateResult = await updateFolderMetaData(folder.id, folderData, undefined, tx);
+ if (updateResult.isErr()) {
+ throw updateResult.error;
+ }
+ }
+
+ const deleteResult = await deleteUser(guestId, tx);
+ if (deleteResult.isErr()) {
+ throw deleteResult.error;
+ }
+ });
+ } catch (error) {
+ return userError(getErrorMessage(error));
}
- deleteUser(guestId);
-
redirect(callbackUrl);
}
export async function discardProcesses(referenceToken: string, redirectUrl: string = '/') {
- const { session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { session } = currentUser.value;
if (!session) return userError("You're not signed in", UserErrorType.PermissionError);
if (session.user.isGuest)
return userError("You can't be a guest to transfer processes", UserErrorType.PermissionError);
@@ -72,10 +123,17 @@ export async function discardProcesses(referenceToken: string, redirectUrl: stri
if (guestId === session.user.id) redirect(redirectUrl);
const possibleGuest = await getUserById(guestId);
- if (!possibleGuest || !possibleGuest.isGuest)
+ if (possibleGuest.isErr()) {
+ return userError(getErrorMessage(possibleGuest.error));
+ }
+
+ if (!possibleGuest.value || !possibleGuest.value.isGuest)
return userError('Invalid guest id', UserErrorType.PermissionError);
- deleteUser(guestId);
+ const deleteResult = await deleteUser(guestId);
+ if (deleteResult.isErr()) {
+ return userError(getErrorMessage(deleteResult.error));
+ }
redirect(redirectUrl);
}
diff --git a/src/management-system-v2/components/auth.tsx b/src/management-system-v2/components/auth.tsx
index f3d79eb68..76f71c72f 100644
--- a/src/management-system-v2/components/auth.tsx
+++ b/src/management-system-v2/components/auth.tsx
@@ -15,9 +15,10 @@ import * as noIamUser from '@/lib/no-iam-user';
import { getUserById } from '@/lib/data/db/iam/users';
import { cookies } from 'next/headers';
import { getMSConfig } from '@/lib/ms-config/ms-config';
-import { UIError as UserUIError } from '@/lib/ui-error';
import { packedStaticRules } from '@/lib/authorization/caslRules';
-import { ok } from 'neverthrow';
+import { err, ok } from 'neverthrow';
+import { UserFacingError } from '@/lib/server-error-handling/user-error';
+import { Prettify } from '@/lib/typescript-utils';
export const getCurrentUser = cache(async () => {
if (!env.PROCEED_PUBLIC_IAM_ACTIVE) {
@@ -79,6 +80,7 @@ export const getSystemAdminRules = cache((isOrganization: boolean) => {
}
});
+type a = Awaited>;
// TODO: To enable PPR move the session redirect into this function, so it will
// be called when the session is first accessed and everything above can PPR. For
// permissions, each server component should check its permissions anyway, for
@@ -95,7 +97,7 @@ export const getCurrentEnvironment = cache(
) => {
const currentUser = await getCurrentUser();
if (currentUser.isErr()) {
- return currentUser;
+ return err('pepe');
}
const { userId, systemAdmin } = currentUser.value;
@@ -116,13 +118,13 @@ export const getCurrentEnvironment = cache(
if (userId && !isOrganization && !env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
// Note: will be undefined for not logged in users
const userOrgs = await getUserOrganizationEnvironments(userId);
- if (userOrgs.isErr()) {
- return userOrgs;
- }
+ // if (userOrgs.isErr()) {
+ // return userOrgs;
+ // }
if (userOrgs.value.length === 0) {
if (env.PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE) {
- throw new UserUIError('You are not part of an organization.');
+ return err(new UserFacingError('You are not part of an organization.'));
} else {
return redirect(`/create-organization`);
}
@@ -146,7 +148,7 @@ export const getCurrentEnvironment = cache(
if (!userId || !isMember(decodeURIComponent(spaceIdParam), userId)) {
switch (opts?.permissionErrorHandling.action) {
case 'throw-error':
- throw new Error('User does not have access to this environment');
+ return err(new Error('User does not have access to this environment'));
case 'redirect':
default:
if (opts.permissionErrorHandling.redirectUrl)
@@ -158,9 +160,12 @@ export const getCurrentEnvironment = cache(
}
const ability = await getAbilityForUser(userId, activeSpace);
+ if (ability.isErr()) {
+ return ability;
+ }
return ok({
- ability,
+ ability: ability.value,
activeEnvironment: { spaceId: activeSpace, isOrganization },
});
},
diff --git a/src/management-system-v2/components/process-modal.tsx b/src/management-system-v2/components/process-modal.tsx
index 5f884639e..69ed80f03 100644
--- a/src/management-system-v2/components/process-modal.tsx
+++ b/src/management-system-v2/components/process-modal.tsx
@@ -18,7 +18,7 @@ import {
Skeleton,
} from 'antd';
import { MdArrowBackIos, MdArrowForwardIos } from 'react-icons/md';
-import { UserError } from '@/lib/server-error-handling/user-error';
+import { UserError, isUserErrorResponse } from '@/lib/server-error-handling/user-error';
import { useAddControlCallback } from '@/lib/controls-store';
import { checkIfProcessExistsByName } from '@/lib/data/processes';
import { useEnvironment } from './auth-can';
@@ -110,6 +110,7 @@ const ProcessModal = <
spaceId: environment.spaceId,
userId: session.data?.user.id!,
});
+ if (isUserErrorResponse(existsResults)) return;
existsResults.forEach((exists, index) => {
if (exists) {
@@ -417,7 +418,7 @@ const ProcessInputs = ({ index, initialName, readonly = false }: ProcessInputsPr
folderId: currentFolderId,
});
- if (!exists) {
+ if (isUserErrorResponse(exists) || !exists) {
callback();
return;
}
diff --git a/src/management-system-v2/components/processes/index.tsx b/src/management-system-v2/components/processes/index.tsx
index 1335e25e5..3d5e430c4 100644
--- a/src/management-system-v2/components/processes/index.tsx
+++ b/src/management-system-v2/components/processes/index.tsx
@@ -22,7 +22,6 @@ import {
Card,
Badge,
Divider,
- MenuProps,
Typography,
} from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
@@ -34,7 +33,6 @@ import {
AppstoreOutlined,
FolderOutlined,
FileOutlined,
- FolderFilled,
ShareAltOutlined,
} from '@ant-design/icons';
import IconView from '@/components/process-icon-list';
@@ -57,7 +55,7 @@ import {
import ProcessModal from '@/components/process-modal';
import ConfirmationButton from '@/components/confirmation-button';
import ProcessImportButton from '@/components/process-import';
-import { Process, ProcessMetadata } from '@/lib/data/process-schema';
+import { ProcessMetadata } from '@/lib/data/process-schema';
import MetaDataContent from '@/components/process-info-card-content';
import { useEnvironment } from '@/components/auth-can';
import { Folder } from '@/lib/data/folder-schema';
@@ -89,6 +87,7 @@ import { ContextActions, RowActions } from './types';
import { canDoActionOnResource } from './helpers';
import { useInitialisePotentialOwnerStore } from '@/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/use-potentialOwner-store';
import { useSession } from 'next-auth/react';
+import { isUserErrorResponse } from '@/lib/server-error-handling/user-error';
// TODO: improve ordering
export type ProcessActions = {
@@ -401,6 +400,13 @@ const Processes = ({
spaceId: space.spaceId,
userId: user?.id!,
});
+ if (isUserErrorResponse(existsResults)) {
+ const errorMessage = existsResults.error.message;
+ throw new Error(
+ typeof errorMessage === 'string' ? errorMessage : 'Something went wrong',
+ );
+ }
+
existsResults.forEach((exists, idx) => {
if (exists) {
throw new Error(
diff --git a/src/management-system-v2/lib/auth-database-adapter.ts b/src/management-system-v2/lib/auth-database-adapter.ts
index d79cd5331..fb5e4e4fe 100644
--- a/src/management-system-v2/lib/auth-database-adapter.ts
+++ b/src/management-system-v2/lib/auth-database-adapter.ts
@@ -44,7 +44,12 @@ const Adapter = {
try {
// next-auth checks if the token is expired
const token = await deleteEmailVerificationToken(params);
- if (token.type === 'signin_with_email' || token.type === 'register_new_user') return token;
+ if (token.isErr()) {
+ throw token;
+ }
+
+ if (token.value.type === 'signin_with_email' || token.value.type === 'register_new_user')
+ return token;
else return null;
} catch (_) {
return null;
@@ -63,10 +68,13 @@ const Adapter = {
account.provider,
account.providerAccountId,
);
+ if (userAccount.isErr()) {
+ return userAccount;
+ }
- if (!userAccount) return null;
+ if (!userAccount.value) return null;
- return getUserById(userAccount.userId) as unknown as AdapterAccount;
+ return getUserById(userAccount.value.userId) as unknown as AdapterAccount;
},
};
diff --git a/src/management-system-v2/lib/auth.ts b/src/management-system-v2/lib/auth.ts
index 660452a49..5568bfccf 100644
--- a/src/management-system-v2/lib/auth.ts
+++ b/src/management-system-v2/lib/auth.ts
@@ -58,7 +58,14 @@ const nextAuthOptions: NextAuthConfig = {
let user = _user as User | undefined;
- if (trigger === 'update') user = (await getUserById(token.user.id)) as User;
+ if (trigger === 'update') {
+ const newUserData = await getUserById(token.user.id);
+ if (newUserData.isErr()) {
+ throw newUserData.error;
+ }
+
+ user = newUserData.value as User;
+ }
if (user) token.user = user;
@@ -86,7 +93,11 @@ const nextAuthOptions: NextAuthConfig = {
) {
// Check if the user's cookie is correct
const sessionUserInDb = await getUserById(sessionUser.id);
- if (!sessionUserInDb || !sessionUserInDb.isGuest) throw new Error('Something went wrong');
+ if (sessionUserInDb.isErr()) {
+ throw sessionUserInDb.error;
+ }
+ if (!sessionUserInDb || !sessionUserInDb.value.isGuest)
+ throw new Error('Something went wrong');
const userSigningIn = _user.id ? await getUserById(_user.id) : null;
@@ -114,13 +125,16 @@ const nextAuthOptions: NextAuthConfig = {
if (!token.user.isGuest) return;
const user = await getUserById(token.user.id);
+ if (user.isErr()) {
+ throw user.error;
+ }
if (user) {
- if (!user.isGuest) {
+ if (!user.value.isGuest) {
console.warn('User with invalid session');
return;
}
- await deleteUser(user.id);
+ await deleteUser(user.value.id);
}
},
async session({ session }) {
@@ -226,7 +240,11 @@ if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
id: 'guest-signin',
credentials: {},
async authorize() {
- return addUser({ isGuest: true });
+ const result = await addUser({ isGuest: true });
+ if (result.isErr()) {
+ throw result.error;
+ }
+ return result.value;
},
}),
);
@@ -257,16 +275,19 @@ if (env.NODE_ENV === 'development') {
},
},
async authorize(credentials) {
- let user: User | null = null;
+ let user;
if (credentials.username === 'johndoe') {
- user = await getUserByUsername('johndoe');
+ let user = await getUserByUsername('johndoe');
if (!user) user = await addUser(johnDoeTemplate);
} else if (credentials.username === 'admin') {
user = await getUserByUsername('admin');
}
- return user;
+ if (!user) return null;
+ if (user.isErr()) throw user.error;
+
+ return user.value;
},
}),
);
@@ -307,16 +328,19 @@ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE) {
},
authorize: async (credentials, req) => {
const userAndPassword = await getUserAndPasswordByUsername(credentials.username as string);
+ if (userAndPassword.isErr()) {
+ throw userAndPassword.error;
+ }
- if (!userAndPassword) return null;
+ if (!userAndPassword.value) return null;
const passwordIsCorrect = await comparePassword(
credentials.password as string,
- userAndPassword.passwordAccount.password,
+ userAndPassword.value.passwordAccount.password,
);
if (!passwordIsCorrect) return null;
- return userAndPassword as User;
+ return userAndPassword.value as User;
},
}),
);
@@ -421,7 +445,7 @@ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE || env.PROCEED_PUBLIC_IAM_
} else {
// Only password is enabled -> immediately create user
await db.$transaction(async (tx) => {
- user = await addUser(
+ const addUserResult = await addUser(
{
username: credentials.username,
firstName: credentials.firstName,
@@ -431,9 +455,12 @@ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE || env.PROCEED_PUBLIC_IAM_
},
tx,
);
+ if (addUserResult.isErr()) throw addUserResult.error;
+ user = addUserResult.value;
const hashedPassword = await hashPassword(credentials.password as string);
- await setUserPassword(user.id, hashedPassword, tx);
+ const setUserResult = await setUserPassword(user.id, hashedPassword, tx);
+ if (setUserResult.isErr()) throw setUserResult.error;
});
}
diff --git a/src/management-system-v2/lib/authorization/authorization.ts b/src/management-system-v2/lib/authorization/authorization.ts
index 2c250b1f7..b20a4d0fc 100644
--- a/src/management-system-v2/lib/authorization/authorization.ts
+++ b/src/management-system-v2/lib/authorization/authorization.ts
@@ -6,6 +6,7 @@ import { getFolders } from '../data/db/folders';
import { getEnvironmentById } from '../data/db/iam/environments';
import { getAppliedRolesForUser } from './organizationEnvironmentRolesHelper';
import { MSEnabledResources } from './globalRules';
+import { ok } from 'neverthrow';
type PackedRules = PackedRulesForUser['rules'];
@@ -52,11 +53,11 @@ export async function getSpaceFolderTree(spaceId: string) {
const tree: TreeMap = {};
const folders = await getFolders(spaceId);
- for (const folder of folders) {
+ for (const folder of folders.value) {
if (folder.parentId) tree[folder.id] = folder.parentId;
}
- return tree;
+ return ok(tree);
}
/**
@@ -70,18 +71,25 @@ export async function getUserRules(userId: string, environmentId: string) {
// cached rules aren't being correctly removed after roles are updated
let userRules = undefined;
- if (userRules) return userRules;
+ if (userRules) return ok(userRules);
const space = (await getEnvironmentById(environmentId))!;
+ if (space.isErr()) {
+ return space;
+ }
- if (!space.isOrganization) {
- const { rules, expiration } = computeRulesForUser({ userId, space });
+ if (!space.value.isOrganization) {
+ const { rules, expiration } = computeRulesForUser({ userId, space: space.value });
cacheRulesForUser(userId, environmentId, rules, expiration);
- return rules;
+ return ok(rules);
}
- if (space.isActive) {
+ if (space.value.isActive) {
const roles = await getAppliedRolesForUser(userId, environmentId);
+ if (roles.isErr()) {
+ return roles;
+ }
+
// TODO: get bough features from db
const getPurhasedFeatures = (_: string) => [];
@@ -90,18 +98,27 @@ export async function getUserRules(userId: string, environmentId: string) {
MSEnabledResources.includes(resource as any),
);
- const { rules, expiration } = computeRulesForUser({ userId, space, roles, purchasedResources });
+ const { rules, expiration } = computeRulesForUser({
+ userId,
+ space: space.value,
+ roles: roles.value,
+ purchasedResources,
+ });
cacheRulesForUser(userId, environmentId, rules, expiration);
- return rules;
+ return ok(rules);
}
// Non active organization
- return [];
+ return ok([]);
}
export async function getAbilityForUser(userId: string, environmentId: string) {
const spaceFolderTree = await getSpaceFolderTree(environmentId);
+
const userRules = await getUserRules(userId, environmentId);
+ if (userRules.isErr()) {
+ return userRules;
+ }
- return new Ability(userRules, environmentId, spaceFolderTree);
+ return ok(new Ability(userRules.value, environmentId, spaceFolderTree.value));
}
diff --git a/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts b/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts
index bbb5c978c..b569f585e 100644
--- a/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts
+++ b/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts
@@ -2,31 +2,45 @@ import { Role } from '../data/role-schema';
import { getRoleById, getRoles } from '../data/db/iam/roles';
import { isMember } from '../data/db/iam/memberships';
import { getRoleMappingByUserId } from '../data/db/iam/role-mappings';
+import { ok } from 'neverthrow';
/** Returns all roles that are applied to a user in a given organization environment */
-export async function getAppliedRolesForUser(
- userId: string,
- environmentId: string,
-): Promise {
+export async function getAppliedRolesForUser(userId: string, environmentId: string) {
// enforces environment to be an organization
if (!isMember(environmentId, userId)) throw new Error('User is not a member of this environment');
const environmentRoles = await getRoles(environmentId);
+ if (environmentRoles.isErr()) {
+ return environmentRoles;
+ }
const userRoles: Role[] = [];
- const guestRole = environmentRoles.find((role: any) => role.name === '@guest') as Role;
+ const guestRole = environmentRoles.value.find((role: any) => role.name === '@guest') as Role;
userRoles.push(guestRole);
- if (userId === '') return userRoles;
+ if (userId === '') return ok(userRoles);
- const everyoneRole = environmentRoles.find(
+ const everyoneRole = environmentRoles.value.find(
(role: any) => role.default && role.name === '@everyone',
) as Role;
userRoles.push(everyoneRole);
+
const roleMappings = await getRoleMappingByUserId(userId, environmentId);
- const mappedRoles = await Promise.all(roleMappings.map((mapping) => getRoleById(mapping.roleId)));
- userRoles.push(...mappedRoles);
+ if (roleMappings.isErr()) {
+ return roleMappings;
+ }
+
+ const roleResults = await Promise.all(
+ roleMappings.value.map((mapping) => getRoleById(mapping.roleId)),
+ );
+
+ for (const role of roleResults) {
+ if (role && role.isErr()) {
+ return role;
+ }
+ if (role.value) userRoles.push(role.value);
+ }
- return userRoles.filter((e) => e !== undefined);
+ return ok(userRoles);
}
diff --git a/src/management-system-v2/lib/custom-links/client-state.tsx b/src/management-system-v2/lib/custom-links/client-state.tsx
index 55f399624..64a01987c 100644
--- a/src/management-system-v2/lib/custom-links/client-state.tsx
+++ b/src/management-system-v2/lib/custom-links/client-state.tsx
@@ -5,6 +5,7 @@ import { createContext, use } from 'react';
import { getCustomLinksStatus } from './server-actions';
import { Badge } from 'antd';
import { CustomNavigationLink } from './custom-link';
+import { isUserErrorResponse } from '../server-error-handling/user-error';
type LinkState = (CustomNavigationLink & { status?: boolean })[];
@@ -19,7 +20,11 @@ export function CustomLinkStateProvider({
}) {
const { data } = useQuery({
queryKey: [spaceId, 'custom-links-state'],
- queryFn: () => getCustomLinksStatus(spaceId),
+ queryFn: async () => {
+ const response = await getCustomLinksStatus(spaceId);
+ if (isUserErrorResponse(response)) throw response.error;
+ return response;
+ },
refetchInterval: 15_000,
});
diff --git a/src/management-system-v2/lib/custom-links/server-actions.tsx b/src/management-system-v2/lib/custom-links/server-actions.tsx
index 696d00fe2..e25446fe0 100644
--- a/src/management-system-v2/lib/custom-links/server-actions.tsx
+++ b/src/management-system-v2/lib/custom-links/server-actions.tsx
@@ -5,13 +5,19 @@ import { getSpaceSettingsValues } from '@/lib/data/db/space-settings';
import { asyncMap } from '../helpers/javascriptHelpers';
import { checkCustomLinkStatus } from './get-link-state';
import { CustomNavigationLink } from './custom-link';
+import { getErrorMessage, userError } from '../server-error-handling/user-error';
export async function getCustomLinksStatus(spaceId: string) {
// Check that the user is a member of the space
getCurrentEnvironment(spaceId);
const generalSettings = await getSpaceSettingsValues(spaceId, 'general-settings');
- const customNavLinks: CustomNavigationLink[] = generalSettings.customNavigationLinks?.links || [];
+ if (generalSettings.isErr()) {
+ return userError(getErrorMessage(generalSettings.error));
+ }
+
+ const customNavLinks: CustomNavigationLink[] =
+ generalSettings.value.customNavigationLinks?.links || [];
return await asyncMap(customNavLinks, async (link) => {
return {
diff --git a/src/management-system-v2/lib/data/db/folders.ts b/src/management-system-v2/lib/data/db/folders.ts
index b2cab9181..1c4075eb8 100644
--- a/src/management-system-v2/lib/data/db/folders.ts
+++ b/src/management-system-v2/lib/data/db/folders.ts
@@ -104,7 +104,9 @@ export async function getFolderContents(folderId: string, ability?: Ability) {
folderContent.push({ ...folder.value, type: 'folder' });
}
- } catch (e) {}
+ } catch (e) {
+ return err(e);
+ }
}
return ok(folderContent);
@@ -225,8 +227,10 @@ export async function updateFolderMetaData(
folderId: string,
newMetaDataInput: Partial,
ability?: Ability,
+ tx?: Prisma.TransactionClient,
) {
- const folder = await db.folder.findUnique({
+ const mutator = tx || db;
+ const folder = await mutator.folder.findUnique({
where: { id: folderId },
});
@@ -242,7 +246,7 @@ export async function updateFolderMetaData(
return err(new Error('environmentId cannot be changed'));
}
- const updatedFolder = await db.folder.update({
+ const updatedFolder = await mutator.folder.update({
where: { id: folderId },
data: { ...newMetaDataInput, lastEditedOn: new Date() },
});
@@ -281,8 +285,15 @@ async function isInSubtree(rootId: string, nodeId: string): Promise ({ id })) } });
+ return ok();
}
/** Returns the html form html */
diff --git a/src/management-system-v2/lib/data/db/process.ts b/src/management-system-v2/lib/data/db/process.ts
index 646daea70..deff5bb13 100644
--- a/src/management-system-v2/lib/data/db/process.ts
+++ b/src/management-system-v2/lib/data/db/process.ts
@@ -1,4 +1,4 @@
-import { ok, err } from 'neverthrow';
+import { ok, err, Result } from 'neverthrow';
import { getFolderById } from './folders';
import eventHandler from '../legacy/eventHandler.js';
import logger from '../legacy/logging.js';
@@ -9,7 +9,7 @@ import {
transformBpmnAttributes,
} from '../../helpers/processHelpers';
import { getDefinitionsVersionInformation, generateBpmnId } from '@proceed/bpmn-helper';
-import Ability from '@/lib/ability/abilityHelper';
+import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
import { ProcessMetadata, ProcessServerInput, ProcessServerInputSchema } from '../process-schema';
import { getRootFolder } from './folders';
import { toCaslResource } from '@/lib/ability/caslAbility';
@@ -107,10 +107,18 @@ export async function getProcess(processDefinitionsId: string, includeBPMN = fal
// : version.versionBasedOn,
// }));
+ let bpmn;
+ if (includeBPMN) {
+ const result = await getProcessBpmn(processDefinitionsId);
+ if (result.isErr()) return result;
+
+ bpmn = result.value;
+ }
+
const convertedProcess = {
...process,
//versions: convertedVersions,
- bpmn: includeBPMN ? await getProcessBpmn(processDefinitionsId) : null,
+ bpmn,
shareTimestamp:
typeof process.shareTimestamp === 'bigint'
? Number(process.shareTimestamp)
@@ -631,7 +639,7 @@ export async function getProcessVersionBpmn(processDefinitionsId: string, versio
if (existingProcess.isErr()) {
return existingProcess;
}
- if (!existingProcess) {
+ if (!existingProcess.value) {
return err(new Error('The process for which you try to get a version does not exist'));
}
const existingVersion = existingProcess.value.versions?.find(
@@ -657,7 +665,7 @@ function removeExcessiveInformation(processInfo: Omit)
}
/** Returns the process definition for the process with the given id */
-export async function getProcessBpmn(processDefinitionsId: string) {
+export async function getProcessBpmn(processDefinitionsId: string, ability?: Ability) {
try {
const process = await db.process.findUnique({
where: {
@@ -672,6 +680,7 @@ export async function getProcessBpmn(processDefinitionsId: string) {
id: true,
name: true,
originalId: true,
+ environmentId: true,
},
});
@@ -679,6 +688,10 @@ export async function getProcessBpmn(processDefinitionsId: string) {
return err(new Error('Process not found'));
}
+ if (ability && !ability.can('view', toCaslResource('Process', process))) {
+ return err(UnauthorizedError);
+ }
+
const processWithStringDate = {
...process,
createdOn: process.createdOn.toISOString(),
@@ -743,7 +756,7 @@ export async function getProcessHtmlFormJSON(
const jsonAsBuffer = (await retrieveFile(artifact.filePath, true)) as Buffer;
return ok(jsonAsBuffer.toString('utf8'));
} else {
- return ok();
+ return ok(undefined);
}
} catch (error) {
logger.debug(`Error getting data of process html form ${fileName}. Reason\n${error}`);
@@ -1176,7 +1189,7 @@ export async function copyProcessFiles(sourceProcessId: string, destinationProce
}
});
- return ok(oldNewFilenameMapping);
+ return Result.combine(oldNewFilenameMapping);
}
export async function getProcessImage(processDefinitionsId: string, imageFileName: string) {
diff --git a/src/management-system-v2/lib/data/engines.ts b/src/management-system-v2/lib/data/engines.ts
index 9a0810d77..20d2b763e 100644
--- a/src/management-system-v2/lib/data/engines.ts
+++ b/src/management-system-v2/lib/data/engines.ts
@@ -10,7 +10,7 @@ import {
deleteSpaceEngine as _deleteDbEngine,
} from '@/lib/data/db/engines';
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
-import { UserErrorType, userError } from '../server-error-handling/user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import { z } from 'zod';
import { enableUseDB } from 'FeatureFlags';
@@ -18,11 +18,26 @@ export async function getDbEngines(environmentId: string | null) {
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
let ability;
- if (environmentId) ability = (await getCurrentEnvironment(environmentId)).ability;
- const systemAdmin = (await getCurrentUser()).systemAdmin;
+ if (environmentId) {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) return userError(getErrorMessage(currentEnvironment.error));
+ ability = currentEnvironment.value.ability;
+ }
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
try {
- return await _getDbEngines(environmentId ?? null, ability, systemAdmin);
+ const result = await _getDbEngines(
+ environmentId ?? null,
+ ability,
+ currentUser.value.systemAdmin,
+ );
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -34,11 +49,27 @@ export async function getDbEngineById(engineId: string, environmentId: string |
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
let ability;
- if (environmentId) ability = (await getCurrentEnvironment(environmentId)).ability;
- const systemAdmin = (await getCurrentUser()).systemAdmin;
+ if (environmentId) {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) return userError(getErrorMessage(currentEnvironment.error));
+ ability = currentEnvironment.value.ability;
+ }
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
try {
- return await _getDbEngineById(engineId, environmentId ?? null, ability, systemAdmin);
+ const result = await _getDbEngineById(
+ engineId,
+ environmentId ?? null,
+ ability,
+ currentUser.value.systemAdmin,
+ );
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -50,11 +81,27 @@ export async function addDbEngines(enginesInput: SpaceEngineInput[], environment
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
let ability;
- if (environmentId) ability = (await getCurrentEnvironment(environmentId)).ability;
- const systemAdmin = (await getCurrentUser()).systemAdmin;
+ if (environmentId) {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) return userError(getErrorMessage(currentEnvironment.error));
+ ability = currentEnvironment.value.ability;
+ }
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
try {
- return await _addDbEngines(enginesInput, environmentId ?? null, ability, systemAdmin);
+ const result = await _addDbEngines(
+ enginesInput,
+ environmentId ?? null,
+ ability,
+ currentUser.value.systemAdmin,
+ );
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -72,17 +119,28 @@ export async function updateDbEngine(
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
let ability;
- if (environmentId) ability = (await getCurrentEnvironment(environmentId)).ability;
- const systemAdmin = (await getCurrentUser()).systemAdmin;
+ if (environmentId) {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) return userError(getErrorMessage(currentEnvironment.error));
+ ability = currentEnvironment.value.ability;
+ }
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
try {
- return await _updateDbEngine(
+ const result = await _updateDbEngine(
engineId,
engineInput,
environmentId ?? null,
ability,
- systemAdmin,
+ currentUser.value.systemAdmin,
);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -96,11 +154,27 @@ export async function deleteSpaceEngine(engineId: string, environmentId: string
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
let ability;
- if (environmentId) ability = (await getCurrentEnvironment(environmentId)).ability;
- const systemAdmin = (await getCurrentUser()).systemAdmin;
+ if (environmentId) {
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) return userError(getErrorMessage(currentEnvironment.error));
+ ability = currentEnvironment.value.ability;
+ }
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
try {
- const result = await _deleteDbEngine(engineId, environmentId ?? null, ability, systemAdmin);
+ const result = await _deleteDbEngine(
+ engineId,
+ environmentId ?? null,
+ ability,
+ currentUser.value.systemAdmin,
+ );
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
diff --git a/src/management-system-v2/lib/data/environment-memberships.ts b/src/management-system-v2/lib/data/environment-memberships.ts
index 498c1e694..a041a8718 100644
--- a/src/management-system-v2/lib/data/environment-memberships.ts
+++ b/src/management-system-v2/lib/data/environment-memberships.ts
@@ -22,6 +22,8 @@ import { env } from '../ms-config/env-vars';
import { AuthenticatedUser, AuthenticatedUserSchema, User } from './user-schema';
import { hashPassword } from '../password-hashes';
import db from '@/lib/data/db';
+import { Err, Ok, Result, err, ok } from 'neverthrow';
+import { Role } from './role-schema';
const EmailListSchema = z.array(
z.union([z.object({ email: z.string().email() }), z.object({ username: z.string() })]),
@@ -38,9 +40,15 @@ export async function inviteUsersToEnvironment(
try {
const invitedEmails = EmailListSchema.parse(invitedUsersIdentifiers);
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
- const organization = (await getEnvironmentById(environmentId)) as OrganizationEnvironment;
+ const _organization = await getEnvironmentById(environmentId);
+ if (_organization.isErr()) return userError(getErrorMessage(_organization.error));
+ const organization = _organization.value as OrganizationEnvironment;
// ability check disallows from adding to personal environments
if (!ability.can('create', 'User'))
@@ -49,28 +57,53 @@ export async function inviteUsersToEnvironment(
UserErrorType.PermissionError,
);
- const filteredRoles = roleIds?.filter(async (roleId) => {
- return (
- ability.can('admin', 'All') &&
- ability.can('manage', toCaslResource('Role', await getRoleById(roleId))) &&
- ability.can(
- 'create',
- toCaslResource('RoleMapping', { userId: '', roleId } satisfies Partial),
- { environmentId },
- )
+ let filteredRoles;
+ if (roleIds) {
+ const allowedRolesResults = await Promise.all(
+ roleIds!.map(async (roleId) => {
+ const role = await getRoleById(roleId);
+ if (role.isErr()) return role;
+ if (!role.value) return err();
+
+ if (
+ ability.can('admin', 'All') &&
+ ability.can('manage', toCaslResource('Role', role.value)) &&
+ ability.can(
+ 'create',
+ toCaslResource('RoleMapping', {
+ userId: '',
+ roleId,
+ } satisfies Partial),
+ { environmentId },
+ )
+ ) {
+ return ok(role.value.id);
+ } else {
+ return err();
+ }
+ }),
);
- });
+
+ filteredRoles = (allowedRolesResults.filter((result) => result.isOk()) as any[]).map(
+ (result) => result.value,
+ ) as string[];
+ }
for (const invitedUserIdentifier of invitedEmails) {
let invitedUser: User | null = null;
let invitedUserEmail: string | null | undefined = null;
if ('email' in invitedUserIdentifier) {
- invitedUser = await getUserByEmail(invitedUserIdentifier.email);
+ const userResult = await getUserByEmail(invitedUserIdentifier.email);
+ if (userResult.isErr()) continue;
+ invitedUser = userResult.value;
} else if ('username' in invitedUserIdentifier) {
- invitedUser = await getUserByUsername(invitedUserIdentifier.username);
- // If there is no user with the given username there is nothing we can do
- if (!invitedUser) continue;
+ const userResult = await getUserByUsername(invitedUserIdentifier.username);
+
+ // If there is no user there is nothing we can do
+ if (userResult.isErr() || !userResult.value) continue;
+
+ invitedUser = userResult.value;
}
// NOTE: technically not possible as guests cannot have an email or username
@@ -121,7 +154,11 @@ export async function removeUsersFromEnvironment(environmentId: string, userIdsI
try {
const userIds = z.array(z.string()).parse(userIdsInput);
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability, activeEnvironment } = currentEnvironment.value;
// TODO refine ability check
@@ -156,7 +193,11 @@ export async function createUserAndAddToOrganization(
}: z.infer & { password: string; roles: string[] },
) {
try {
- const { ability } = await getCurrentEnvironment(organizationId);
+ const currentEnvironment = await getCurrentEnvironment(organizationId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
// Check if the user is an admin
if (!ability.can('admin', 'All')) {
@@ -169,27 +210,39 @@ export async function createUserAndAddToOrganization(
const userDataParsed = createUserDataSchema.parse(userDataInput);
let user: AuthenticatedUser;
- await db.$transaction(async (tx) => {
- // no need to check the if the user has permissions to create the role mappings since it's
- // he's an admin
- user = (await addUser(
- { ...userDataParsed, isGuest: false, emailVerifiedOn: null },
- tx,
- )) as AuthenticatedUser;
- const passwordHash = await hashPassword(password);
- await setUserPassword(user.id, passwordHash, tx, true);
-
- await addMember(organizationId, user.id, ability, tx);
- await addRoleMappings(
- roles.map((roleId) => ({
- roleId,
- environmentId: organizationId,
- userId: user.id,
- })),
- ability,
- tx,
- );
- });
+ try {
+ await db.$transaction(async (tx) => {
+ // no need to check the if the user has permissions to create the role mappings since it's
+ // he's an admin
+ const userResult = await addUser(
+ { ...userDataParsed, isGuest: false, emailVerifiedOn: null },
+ tx,
+ );
+ if (userResult.isErr()) throw userResult.error;
+
+ user = userResult.value as AuthenticatedUser;
+
+ const passwordHash = await hashPassword(password);
+ const passwordResult = await setUserPassword(user.id, passwordHash, tx, true);
+ if (passwordResult.isErr()) throw passwordResult.error;
+
+ const memberResult = await addMember(organizationId, user.id, ability, tx);
+ if (memberResult?.isErr()) throw memberResult.error;
+
+ const roleMappingsResult = await addRoleMappings(
+ roles.map((roleId) => ({
+ roleId,
+ environmentId: organizationId,
+ userId: user.id,
+ })),
+ ability,
+ tx,
+ );
+ if (roleMappingsResult?.isErr()) throw roleMappingsResult.error;
+ });
+ } catch (error) {
+ return userError(getErrorMessage(error));
+ }
return user!;
} catch (error) {
diff --git a/src/management-system-v2/lib/data/environments.ts b/src/management-system-v2/lib/data/environments.ts
index a7dbffdc3..a35c7bdbf 100644
--- a/src/management-system-v2/lib/data/environments.ts
+++ b/src/management-system-v2/lib/data/environments.ts
@@ -26,7 +26,11 @@ export async function addOrganizationEnvironment(
UserErrorType.PermissionError,
);
- const { userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
try {
const environmentData = UserOrganizationEnvironmentInputSchema.parse(environmentInput);
@@ -37,14 +41,11 @@ export async function addOrganizationEnvironment(
isOrganization: true,
...environmentData,
});
-
- if (result.isOk()) {
- result.value;
- }
if (result.isErr()) {
- // Handle error
- result.error;
+ return userError(getErrorMessage(result.error));
}
+
+ return result.value;
} catch (e) {
console.error(e);
return userError('Error adding environment');
@@ -61,14 +62,24 @@ export async function deleteOrganizationEnvironments(environmentIds: string[]) {
try {
for (const environmentId of environmentIds) {
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
const environment = await getEnvironmentById(environmentId);
+ if (environment.isErr()) {
+ return userError(getErrorMessage(environment.error));
+ }
- if (!environment?.isOrganization)
+ if (!environment.value?.isOrganization)
return userError(`Environment ${environmentId} is not an organization environment`);
- deleteEnvironment(environmentId, ability);
+ const deleteResult = await deleteEnvironment(environmentId, ability);
+ if (deleteResult?.isErr()) {
+ return userError(getErrorMessage(deleteResult.error));
+ }
}
} catch (e) {
if (e instanceof UnauthorizedError)
@@ -87,9 +98,16 @@ export async function updateOrganization(
data: Partial,
) {
try {
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
+ const result = await _updateOrganization(environmentId, data, ability);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
- return _updateOrganization(environmentId, data, ability);
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError("You're not allowed to update this organization");
@@ -100,7 +118,11 @@ export async function updateOrganization(
export async function leaveOrganization(spaceId: string) {
try {
- const { user } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { user } = currentUser.value;
if (!user || user.isGuest) {
return userError('You need to be signed in');
@@ -115,7 +137,10 @@ export async function leaveOrganization(spaceId: string) {
throw new Error();
}
- await removeMember(spaceId, user.id);
+ const result = await removeMember(spaceId, user.id);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (e) {
console.error(e);
let message;
diff --git a/src/management-system-v2/lib/data/file-manager-facade.ts b/src/management-system-v2/lib/data/file-manager-facade.ts
index e169a3f6c..31ec4010c 100644
--- a/src/management-system-v2/lib/data/file-manager-facade.ts
+++ b/src/management-system-v2/lib/data/file-manager-facade.ts
@@ -412,13 +412,15 @@ export async function updateFileDeletableStatus(
export async function softDeleteProcessUserTask(processId: string, userTaskFilename: string) {
try {
const userTaskJson = await getProcessHtmlFormJSON(processId, userTaskFilename);
- if (userTaskJson) {
+ if (userTaskJson.isErr()) throw userTaskJson.error;
+
+ if (userTaskJson.value) {
const artifactsPromises = [];
artifactsPromises.push(getArtifactMetaData(`${userTaskFilename}.json`, false));
artifactsPromises.push(getArtifactMetaData(`${userTaskFilename}.html`, false));
- const referencedArtifactFilePaths = getUsedImagesFromJson(JSON.parse(userTaskJson));
+ const referencedArtifactFilePaths = getUsedImagesFromJson(JSON.parse(userTaskJson.value));
for (const referencedArtifactFilePath of referencedArtifactFilePaths) {
artifactsPromises.push(getArtifactMetaData(referencedArtifactFilePath, true));
}
@@ -472,13 +474,15 @@ export async function softDeleteProcessScriptTask(processId: string, scriptTaskF
export async function revertSoftDeleteProcessUserTask(processId: string, userTaskFilename: string) {
try {
const userTaskJson = await getProcessHtmlFormJSON(processId, userTaskFilename, true);
- if (userTaskJson) {
+ if (userTaskJson.isErr()) throw userTaskJson.error;
+
+ if (userTaskJson.value) {
const artifactsPromises = [];
artifactsPromises.push(getArtifactMetaData(`${userTaskFilename}.json`, false));
artifactsPromises.push(getArtifactMetaData(`${userTaskFilename}.html`, false));
- const referencedArtifactFilePaths = getUsedImagesFromJson(JSON.parse(userTaskJson));
+ const referencedArtifactFilePaths = getUsedImagesFromJson(JSON.parse(userTaskJson.value));
for (const referencedArtifactFilePath of referencedArtifactFilePaths) {
artifactsPromises.push(getArtifactMetaData(referencedArtifactFilePath, true));
}
diff --git a/src/management-system-v2/lib/data/folders.ts b/src/management-system-v2/lib/data/folders.ts
index 9ed8aca80..b65a67b18 100644
--- a/src/management-system-v2/lib/data/folders.ts
+++ b/src/management-system-v2/lib/data/folders.ts
@@ -1,9 +1,8 @@
'use server';
-import * as util from 'util';
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
import { FolderUserInput, FolderUserInputSchema } from './folder-schema';
-import { UserErrorType, userError } from '../server-error-handling/user-error';
-import { TreeMap, toCaslResource } from '../ability/caslAbility';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
+import { toCaslResource } from '../ability/caslAbility';
import Ability, { UnauthorizedError } from '../ability/abilityHelper';
@@ -19,18 +18,35 @@ import {
moveProcess,
} from '@/lib/data/db/folders';
import { Process } from './process-schema';
+import { ResultAsync, ok } from 'neverthrow';
export type FolderChildren = { id: string; type: 'folder' } | { id: string; type: Process['type'] };
export async function createFolder(folderInput: FolderUserInput) {
try {
const folder = FolderUserInputSchema.parse(folderInput);
- const { ability } = await getCurrentEnvironment(folder.environmentId);
- const { userId } = await getCurrentUser();
- if (!folder.parentId) folder.parentId = (await getRootFolder(folder.environmentId)).id;
+ const currentEnvironment = await getCurrentEnvironment(folder.environmentId);
+ if (currentEnvironment.isErr()) return userError(getErrorMessage(currentEnvironment.error));
+ const { ability } = currentEnvironment.value;
- await _createFolder({ ...folder, createdBy: userId }, ability);
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
+ const { userId } = currentUser.value;
+
+ if (!folder.parentId) {
+ const rootFolder = await getRootFolder(folder.environmentId);
+ if (rootFolder.isErr()) {
+ return userError(getErrorMessage(rootFolder.error));
+ }
+
+ folder.parentId = rootFolder.value.id;
+ }
+
+ const result = await _createFolder({ ...folder, createdBy: userId }, ability);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
} catch (e) {
return userError("Couldn't create folder");
}
@@ -41,24 +57,24 @@ export type FolderTreeNode = {
children: FolderTreeNode[];
};
-export async function getSpaceFolderTree(
- spaceId: string,
- ability?: Ability,
-): Promise {
+export async function getSpaceFolderTree(spaceId: string, ability?: Ability) {
//TODO: ability check
const folders = await getFolders(spaceId);
+ if (folders.isErr()) {
+ return userError(getErrorMessage(folders.error));
+ }
const folderMap: Record = {};
// Initialize the folder map with empty children arrays and only id and name
- for (const folder of folders) {
+ for (const folder of folders.value) {
folderMap[folder.id] = { id: folder.id, name: folder.name, children: [] };
}
const rootFolders: FolderTreeNode[] = [];
- for (const folder of folders) {
+ for (const folder of folders.value) {
if (folder.parentId) {
const parent = folderMap[folder.parentId];
if (parent) {
@@ -70,16 +86,23 @@ export async function getSpaceFolderTree(
}
}
- return rootFolders;
+ return rootFolders as FolderTreeNode[];
}
export async function moveIntoFolder(items: FolderChildren[], folderId: string) {
const folder = await getFolderById(folderId);
+ if (folder.isErr()) {
+ return userError(getErrorMessage(folder.error));
+ }
if (!folder) return userError('Folder not found');
- const { ability } = await getCurrentEnvironment(folder.environmentId);
+ const currentEnvironment = await getCurrentEnvironment(folder.value.environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
- if (!ability.can('update', toCaslResource('Folder', folder)))
+ if (!ability.can('update', toCaslResource('Folder', folder.value)))
return userError('Permission denied');
for (const item of items) {
@@ -92,27 +115,49 @@ export async function moveIntoFolder(items: FolderChildren[], folderId: string)
}
export async function getFolder(environmentId: string, folderId?: string) {
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
let folder;
if (!folderId) folder = await getRootFolder(environmentId, ability);
else folder = await getFolderById(folderId);
- if (folder && !ability.can('view', toCaslResource('Folder', folder)))
+ if (folder.isErr()) {
+ return userError(getErrorMessage(folder.error));
+ }
+
+ if (folder.value && !ability.can('view', toCaslResource('Folder', folder.value)))
return userError('Permission denied');
- if (!folder) return userError('Folder not found');
+ if (!folder.value) return userError('Folder not found');
- return folder;
+ return folder.value;
}
export async function getFolderContents(environmentId: string, folderId?: string) {
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
+ const rootFolder = await getRootFolder(environmentId);
+ if (rootFolder.isErr()) {
+ return userError(getErrorMessage(rootFolder.error));
+ }
- if (!folderId) folderId = (await getRootFolder(environmentId)).id;
+ if (!folderId) folderId = rootFolder.value.id;
try {
- return _getFolderContent(folderId, ability);
+ const result = await _getFolderContent(folderId, ability);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ } else {
+ return result.value;
+ }
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -128,9 +173,16 @@ export async function updateFolder(
) {
try {
const folder = await getFolderById(folderId);
- if (!folder) return userError('Folder not found');
+ if (folder.isErr()) {
+ return userError(getErrorMessage(folder.error));
+ }
+ if (!folder.value) return userError('Folder not found');
- const { ability } = await getCurrentEnvironment(folder.environmentId);
+ const currentEnvironment = await getCurrentEnvironment(folder.value.environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
const folderUpdate = FolderUserInputSchema.partial().parse(folderInput);
if (folderUpdate.parentId) return userError('Wrong method for moving folders');
@@ -146,7 +198,11 @@ export async function updateFolder(
export async function deleteFolder(folderIds: string[], spaceId: string) {
try {
- const { ability } = await getCurrentEnvironment(spaceId);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
for (const folderId of folderIds) await _deleteFolder(folderId, ability);
} catch (e) {
diff --git a/src/management-system-v2/lib/data/html-forms.ts b/src/management-system-v2/lib/data/html-forms.ts
index b2f3eccb8..1dbfa68f8 100644
--- a/src/management-system-v2/lib/data/html-forms.ts
+++ b/src/management-system-v2/lib/data/html-forms.ts
@@ -14,7 +14,12 @@ import { getCurrentUser } from '@/components/auth';
export const getHtmlForms = async (spaceId: string) => {
try {
- return await _getHtmlForms(spaceId);
+ const result = await _getHtmlForms(spaceId);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (err) {
console.error(`Unable to get html forms from the database. Reason: ${err}`);
return userError('Unable to get data of html forms.');
@@ -23,7 +28,12 @@ export const getHtmlForms = async (spaceId: string) => {
export const getHtmlForm = async (formId: string) => {
try {
- return await _getHtmlForm(formId);
+ const result = await _getHtmlForm(formId);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (err) {
console.error(`Unable to get html form (${formId}) from the database. Reason: ${err}`);
if (err instanceof UserFacingError) {
@@ -39,13 +49,22 @@ export const addHtmlForm = async (
) => {
try {
const creationTime = new Date();
- const { userId } = await getCurrentUser();
- await _addHtmlForm({
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
+
+ const result = await _addHtmlForm({
...formData,
createdOn: creationTime,
lastEditedOn: creationTime,
creatorId: userId,
});
+
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
} catch (err) {
console.error(`Unable to add html form to the database. Reason: ${err}`);
return userError('Unable to add html form.');
@@ -54,7 +73,10 @@ export const addHtmlForm = async (
export const updateHtmlForm = async (formId: string, newData: Partial) => {
try {
- await _updateHtmlForm(formId, newData);
+ const result = await _updateHtmlForm(formId, newData);
+ if (result && result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
} catch (err) {
console.error(`Unable to update html form ${formId} in the database. Reason: ${err}`);
if (err instanceof UserFacingError) {
@@ -67,7 +89,10 @@ export const updateHtmlForm = async (formId: string, newData: Partial)
export const removeHtmlForms = async (formIds: string[]) => {
try {
- await _removeHtmlForms(formIds);
+ const result = await _removeHtmlForms(formIds);
+ if (result && result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
} catch (err) {
console.error(`Unable to remove html forms from the database. Reason: ${err}`);
if (err instanceof UserFacingError) {
@@ -80,7 +105,12 @@ export const removeHtmlForms = async (formIds: string[]) => {
export const getHtmlFormHtml = async (formId: string) => {
try {
- return await _getHtmlFormHtml(formId);
+ const result = await _getHtmlFormHtml(formId);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (err) {
console.error(`Unable to get html form html data from the database. Reason: ${err}`);
if (err instanceof UserFacingError) {
diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx
index ab9668939..039b7aa05 100644
--- a/src/management-system-v2/lib/data/processes.tsx
+++ b/src/management-system-v2/lib/data/processes.tsx
@@ -17,7 +17,13 @@ import {
updateBpmnCreatorAttributes,
} from '@proceed/bpmn-helper';
import { createProcess, getFinalBpmn, updateFileNames } from '../helpers/processHelpers';
-import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
+import {
+ UserError,
+ UserErrorType,
+ getErrorMessage,
+ isUserErrorResponse,
+ userError,
+} from '../server-error-handling/user-error';
import {
areVersionsEqual,
getLocalVersionBpmn,
@@ -62,6 +68,10 @@ import { ProcessData } from '@/components/process-import';
import { saveProcessArtifact } from './file-manager-facade';
import { getRootFolder } from './db/folders';
import { truthyFilter } from '../typescript-utils';
+import { Result, ok } from 'neverthrow';
+import { isSupportedCountry } from 'libphonenumber-js';
+
+// FIXME: Check abilities
// Import necessary functions from processModule
@@ -70,10 +80,18 @@ export const checkValidity = async (
operation: 'view' | 'update' | 'delete',
spaceId: string,
) => {
- const { ability } = await getCurrentEnvironment(spaceId);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
const process = await _getProcess(definitionId);
- if (!process) {
+ if (process.isErr()) {
+ return userError(getErrorMessage(process.error));
+ }
+
+ if (!process.value) {
return userError('A process with this id does not exist.', UserErrorType.NotFoundError);
}
@@ -89,7 +107,7 @@ export const checkValidity = async (
if (
!ability.can(operation, toCaslResource('Process', process), {
- environmentId: process.environmentId,
+ environmentId: process.value.environmentId,
})
) {
return userError(errorMessages[operation], UserErrorType.PermissionError);
@@ -98,9 +116,10 @@ export const checkValidity = async (
const getBpmnVersion = async (definitionId: string, versionId?: string) => {
const process = await _getProcess(definitionId);
+ if (process.isErr()) return userError(getErrorMessage(process.error));
if (versionId) {
- const version = process.versions.find((version) => version.id === versionId);
+ const version = process.value.versions.find((version) => version.id === versionId);
if (!version) {
return userError(
@@ -109,20 +128,29 @@ const getBpmnVersion = async (definitionId: string, versionId?: string) => {
);
}
- return await getProcessVersionBpmn(definitionId, versionId);
+ const result = await getProcessVersionBpmn(definitionId, versionId);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} else {
- return await _getProcessBpmn(definitionId);
+ const result = await _getProcessBpmn(definitionId);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
}
};
export const getSharedProcessWithBpmn = async (definitionId: string, versionCreatedOn?: string) => {
const processMetaObj = await _getProcess(definitionId);
+ if (processMetaObj.isErr()) {
+ return userError(getErrorMessage(processMetaObj.error));
+ }
- if (!processMetaObj) {
+ if (!processMetaObj.value) {
return userError(`Process does not exist `);
}
- if (processMetaObj.shareTimestamp > 0 || processMetaObj.allowIframeTimestamp > 0) {
+ if (processMetaObj.value.shareTimestamp > 0 || processMetaObj.value.allowIframeTimestamp > 0) {
const bpmn = await getBpmnVersion(definitionId, versionCreatedOn);
// check if getBpmnVersion returned an error that should be shown to the user instead of the bpmn
@@ -140,20 +168,24 @@ export const getSharedProcessWithBpmn = async (definitionId: string, versionCrea
export const getProcess = async (
definitionId: string,
spaceId: string,
+ // FIXME: This allows anyone to just skip authorization
skipValidityCheck = false,
) => {
if (!skipValidityCheck) {
const error = await checkValidity(definitionId, 'view', spaceId);
-
if (error) return error;
}
+
const result = await _getProcess(definitionId);
- return result as Process;
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value as Process;
};
export const getProcessBPMN = async (definitionId: string, spaceId: string, versionId?: string) => {
const error = await checkValidity(definitionId, 'view', spaceId);
-
if (error) return error;
return await getBpmnVersion(definitionId, versionId);
@@ -165,7 +197,8 @@ export const deleteProcesses = async (definitionIds: string[], spaceId: string)
if (error) return error;
- await removeProcess(definitionId);
+ const result = await removeProcess(definitionId);
+ if (result && result.isErr()) return userError(getErrorMessage(result.error));
}
};
@@ -181,8 +214,17 @@ export const addProcesses = async (
spaceId: string,
generateNewId: boolean = false,
) => {
- const { ability, activeEnvironment } = await getCurrentEnvironment(spaceId);
- const { userId } = await getCurrentUser();
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability, activeEnvironment } = currentEnvironment.value;
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
const newProcesses: Process[] = [];
@@ -195,8 +237,15 @@ export const addProcesses = async (
});
// if imported process has a id that is not present in system, we can use that
- if (value.id && (await checkIfProcessExists(value.id, false))) {
- generateNewId = true;
+ if (value.id) {
+ const processExists = await checkIfProcessExists(value.id, false);
+ if (processExists.isErr()) {
+ return userError(getErrorMessage(processExists.error));
+ }
+
+ if (processExists.value) {
+ generateNewId = true;
+ }
}
if (generateNewId) {
@@ -223,12 +272,15 @@ export const addProcesses = async (
// bpmn prop gets deleted in addProcess()
const process = await _addProcess({ ...newProcess, folderId: value.folderId });
+ if (process.isErr()) {
+ return userError(getErrorMessage(process.error));
+ }
- if (typeof process !== 'object') {
+ if (typeof process.value !== 'object') {
return userError('A process with this id does already exist');
}
- newProcesses.push({ ...process, bpmn });
+ newProcesses.push({ ...process.value, bpmn });
}
return newProcesses;
@@ -245,11 +297,14 @@ export const updateProcessShareInfo = async (
if (error) return error;
- await _updateProcessMetaData(definitionsId, {
+ const result = await _updateProcessMetaData(definitionsId, {
sharedAs: sharedAs,
shareTimestamp: shareTimestamp,
allowIframeTimestamp: allowIframeTimestamp,
});
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
};
export const updateProcess = async (
@@ -266,9 +321,17 @@ export const updateProcess = async (
if (error) return error;
// Either replace or update the old BPMN.
- let newBpmn = bpmn ?? (await _getProcessBpmn(definitionsId));
+ let newBpmn = bpmn;
+ if (bpmn === undefined) {
+ const result = await _getProcessBpmn(definitionsId);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ newBpmn = result.value;
+ }
+
if (description !== undefined) {
- newBpmn = (await addDocumentation(newBpmn!, description)) as string;
+ const result = await addDocumentation(newBpmn!, description);
+ newBpmn = result as string;
}
if (name !== undefined) {
newBpmn = (await setDefinitionsName(newBpmn!, name)) as string;
@@ -297,10 +360,12 @@ export const updateProcessMetaData = async (
invalidate = false,
) => {
const error = await checkValidity(definitionsId, 'update', spaceId);
-
if (error) return error;
- await _updateProcessMetaData(definitionsId, metaChanges);
+ const result = await _updateProcessMetaData(definitionsId, metaChanges);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
if (invalidate) {
revalidatePath(`/processes/editor/${definitionsId}`);
@@ -382,10 +447,9 @@ export const importProcesses = async (processData: ProcessData[], spaceId: strin
} else {
const newFileName = generateStartFormFileName();
fileNameMapping['start-form'].set(filename, newFileName);
- try {
- await _saveProcessHtmlForm(process.id, newFileName, json, html);
- } catch (error) {
- console.log(`Error processing start form ${newFileName}:`, error);
+ const result = await _saveProcessHtmlForm(process.id, newFileName, json, html);
+ if (result.isErr()) {
+ console.log(`Error processing start form ${newFileName}:`, result.error);
}
}
}
@@ -403,10 +467,9 @@ export const importProcesses = async (processData: ProcessData[], spaceId: strin
const newUserTaskFileName =
fileNameMapping['user-tasks'].get(baseName) || generateUserTaskFileName();
fileNameMapping['user-tasks'].set(baseName, newUserTaskFileName);
- try {
- await _saveProcessHtmlForm(process.id, newUserTaskFileName, json, html);
- } catch (error) {
- console.error(`Error processing user task ${newUserTaskFileName}:`, error);
+ const result = await _saveProcessHtmlForm(process.id, newUserTaskFileName, json, html);
+ if (result.isErr()) {
+ console.error(`Error processing user task ${newUserTaskFileName}:`, result.error);
}
}
}
@@ -448,8 +511,18 @@ export const copyProcesses = async (
destinationfolderId?: string,
referencedProcessId?: string,
) => {
- const { ability, activeEnvironment } = await getCurrentEnvironment(spaceId);
- const { userId } = await getCurrentUser();
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability, activeEnvironment } = currentEnvironment.value;
+
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
+
const copiedProcesses: Process[] = [];
for (const copyProcess of processes) {
@@ -459,9 +532,12 @@ export const copyProcesses = async (
const originalBpmn = copyProcess.originalVersion
? await getProcessVersionBpmn(copyProcess.originalId, copyProcess.originalVersion)
: await _getProcessBpmn(copyProcess.originalId);
+ if (originalBpmn.isErr()) {
+ return userError(getErrorMessage(originalBpmn.error));
+ }
// TODO: Does createProcess() do the same as this function?
- let newBpmn = await getFinalBpmn({ ...copyProcess, id: newId, bpmn: originalBpmn! });
+ let newBpmn = await getFinalBpmn({ ...copyProcess, id: newId, bpmn: originalBpmn.value! });
// TODO: include variables in copy?
const newProcess = {
@@ -476,15 +552,20 @@ export const copyProcesses = async (
return userError('Not allowed to create this process', UserErrorType.PermissionError);
}
const process = await _addProcess(newProcess, referencedProcessId);
+ if (process.isErr()) {
+ return userError(getErrorMessage(process.error));
+ }
- if (typeof process !== 'object') {
+ if (typeof process.value !== 'object') {
return userError('A process with this id does already exist');
}
await copyProcessArtifactReferences(copyProcess.originalId, newProcess.definitionId);
const copiedFiles = await copyProcessFiles(copyProcess.originalId, newProcess.definitionId);
- const changesFileNames = copiedFiles
+ if (copiedFiles.isErr()) return userError(getErrorMessage(copiedFiles.error));
+
+ const changesFileNames = copiedFiles.value
.filter(truthyFilter)
.filter((file) => file.artifactType === 'html-forms' || file.artifactType === 'script-tasks')
.map(
@@ -494,7 +575,7 @@ export const copyProcesses = async (
newBpmn = await updateFileNames(newBpmn, changesFileNames);
await _updateProcess(newProcess.definitionId, { bpmn: newBpmn });
- copiedProcesses.push({ ...process, bpmn: newBpmn });
+ copiedProcesses.push({ ...process.value, bpmn: newBpmn });
}
return copiedProcesses;
@@ -506,20 +587,25 @@ export const processHasChangesSinceLastVersion = async (processId: string, space
if (error) return error;
const process = await _getProcess(processId, true);
- if (!process) return userError('Process not found', UserErrorType.NotFoundError);
+ if (process.isErr()) return userError(getErrorMessage(process.error));
+ if (!process.value) return userError('Process not found', UserErrorType.NotFoundError);
- const bpmnObj = await toBpmnObject(process.bpmn!);
- const { versionBasedOn, versionCreatedOn } = await getDefinitionsVersionInformation(bpmnObj);
+ const bpmnObj = await toBpmnObject(process.value.bpmn!);
+ const { versionBasedOn } = await getDefinitionsVersionInformation(bpmnObj);
const versionedBpmn = await toBpmnXml(bpmnObj);
// if the new version has no changes to the version it is based on don't create a new version and return the previous version
const basedOnBPMN =
versionBasedOn !== undefined
- ? await getLocalVersionBpmn(process as Process, versionBasedOn)
+ ? await getLocalVersionBpmn(process.value as Process, versionBasedOn)
: undefined;
+ if (basedOnBPMN && basedOnBPMN.isErr()) {
+ return userError(getErrorMessage(basedOnBPMN.error));
+ }
- const versionsAreEqual = basedOnBPMN && (await areVersionsEqual(versionedBpmn, basedOnBPMN));
+ const versionsAreEqual =
+ basedOnBPMN && (await areVersionsEqual(versionedBpmn, basedOnBPMN?.value));
return !versionsAreEqual;
};
@@ -530,14 +616,13 @@ export const createVersion = async (
spaceId: string,
) => {
const error = await checkValidity(processId, 'update', spaceId);
-
if (error) return error;
const bpmn = await _getProcessBpmn(processId);
- if (!bpmn) {
- return null;
- }
- const bpmnObj = await toBpmnObject(bpmn);
+ if (bpmn.isErr()) return userError(getErrorMessage(bpmn.error));
+ if (!bpmn) return null;
+
+ const bpmnObj = await toBpmnObject(bpmn.value);
const { versionBasedOn } = await getDefinitionsVersionInformation(bpmnObj);
const versionCreatedOn = toCustomUTCString(new Date());
@@ -551,19 +636,30 @@ export const createVersion = async (
versionCreatedOn,
});
- const process = (await _getProcess(processId)) as Process;
+ const _process = await _getProcess(processId);
+ if (_process.isErr()) return userError(getErrorMessage(_process.error));
+ const process = _process.value as Process;
const versionedProcessStartFormFilenames = await versionStartForm(process, versionId, bpmnObj);
+ if (isUserErrorResponse(versionedProcessStartFormFilenames))
+ return versionedProcessStartFormFilenames;
+
const versionedUserTaskFilenames = await versionUserTasks(process, versionId, bpmnObj);
+ if (isUserErrorResponse(versionedUserTaskFilenames)) return versionedUserTaskFilenames;
+
const versionedScriptTaskFilenames = await versionScriptTasks(process, versionId, bpmnObj);
+ if (isUserErrorResponse(versionedScriptTaskFilenames)) return versionedScriptTaskFilenames;
const versionedBpmn = await toBpmnXml(bpmnObj);
// if the new version has no changes to the version it is based on don't create a new version and return the previous version
const basedOnBPMN =
versionBasedOn !== undefined ? await getLocalVersionBpmn(process, versionCreatedOn) : undefined;
+ if (basedOnBPMN && basedOnBPMN.isErr()) {
+ return userError(getErrorMessage(basedOnBPMN.error));
+ }
- if (basedOnBPMN && (await areVersionsEqual(versionedBpmn, basedOnBPMN))) {
+ if (basedOnBPMN && (await areVersionsEqual(versionedBpmn, basedOnBPMN?.value))) {
return versionBasedOn;
}
@@ -576,7 +672,7 @@ export const createVersion = async (
versionedScriptTaskFilenames,
);
- await updateProcessVersionBasedOn({ ...process, bpmn }, versionId);
+ await updateProcessVersionBasedOn({ ...process, bpmn: bpmn.value }, versionId);
return versionId;
};
@@ -605,7 +701,10 @@ export const getProcessHtmlFormData = async (
if (error) return error;
try {
- return await _getProcessHtmlFormJSON(definitionId, fileName);
+ const result = await _getProcessHtmlFormJSON(definitionId, fileName);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (err) {
return userError(
`Unable to get the requested process html form data for ${fileName}`,
@@ -624,7 +723,10 @@ export const getProcessHtmlFormHTML = async (
if (error) return error;
try {
- return await _getHtmlForm(definitionId, fileName);
+ const result = await _getHtmlForm(definitionId, fileName);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (err) {
return userError(
`Unable to get the requested html form html ${fileName}.`,
@@ -669,7 +771,13 @@ export const getProcessScriptTaskData = async (
if (error) return error;
try {
- return await _getProcessScriptTaskScript(definitionId, `${taskFileName}.${fileExtension}`);
+ const result = await _getProcessScriptTaskScript(
+ definitionId,
+ `${taskFileName}.${fileExtension}`,
+ );
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (err) {
return userError('Unable to get the requested Script Task data.', UserErrorType.NotFoundError);
}
@@ -717,7 +825,10 @@ export const getProcessImage = async (
if (error) return error;
- return _getProcessImage!(definitionId, imageFileName);
+ const result = await _getProcessImage!(definitionId, imageFileName);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
};
interface BaseProcessCheckData {
@@ -738,35 +849,69 @@ interface BatchProcessCheckData extends BaseProcessCheckData {
type ProcessCheckData = SingleProcessCheckData | BatchProcessCheckData;
-export async function checkIfProcessExistsByName(data: SingleProcessCheckData): Promise;
-export async function checkIfProcessExistsByName(data: BatchProcessCheckData): Promise;
export async function checkIfProcessExistsByName(
- data: ProcessCheckData,
-): Promise {
+ data: SingleProcessCheckData,
+): Promise;
+export async function checkIfProcessExistsByName(
+ data: BatchProcessCheckData,
+): Promise;
+export async function checkIfProcessExistsByName(data: ProcessCheckData) {
try {
- const { ability } = await getCurrentEnvironment(data.spaceId);
+ const currentEnvironment = await getCurrentEnvironment(data.spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
if (data.batch === true) {
- const processesWithFolderIds = await Promise.all(
- data.processes.map(async (process) => ({
- name: process.name,
- folderId: process.folderId ?? (await getRootFolder(data.spaceId, ability)).id,
- })),
+ let rootFolderId: string | undefined = undefined;
+ const processesWithFolderIds = Result.combine(
+ await Promise.all(
+ data.processes.map(async (process) => {
+ let folderId = process.folderId;
+
+ if (!folderId) {
+ if (!rootFolderId) {
+ const rootFolder = await getRootFolder(data.spaceId, ability);
+ if (rootFolder.isErr()) return rootFolder;
+ rootFolderId = rootFolder.value.id;
+ }
+
+ folderId = rootFolderId;
+ }
+ return ok({
+ name: process.name,
+ folderId: folderId,
+ });
+ }),
+ ),
);
+ if (processesWithFolderIds.isErr()) {
+ return userError(getErrorMessage(processesWithFolderIds.error));
+ }
- return await checkIfProcessAlreadyExistsForAUserInASpaceByNameWithBatching(
- processesWithFolderIds,
+ const result = await checkIfProcessAlreadyExistsForAUserInASpaceByNameWithBatching(
+ processesWithFolderIds.value,
data.spaceId,
data.userId,
);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} else {
- const folderId = data.folderId ?? (await getRootFolder(data.spaceId, ability)).id;
- return await checkIfProcessAlreadyExistsForAUserInASpaceByName(
+ const rootFolder = await getRootFolder(data.spaceId, ability);
+ if (rootFolder.isErr()) return userError(getErrorMessage(rootFolder.error));
+
+ const folderId = data.folderId ?? rootFolder.value.id;
+ const result = await checkIfProcessAlreadyExistsForAUserInASpaceByName(
data.processName,
data.spaceId,
data.userId,
folderId,
);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
}
} catch (error) {
console.log(error);
diff --git a/src/management-system-v2/lib/data/role-mappings.ts b/src/management-system-v2/lib/data/role-mappings.ts
index 3466e4a94..4a72fcb4a 100644
--- a/src/management-system-v2/lib/data/role-mappings.ts
+++ b/src/management-system-v2/lib/data/role-mappings.ts
@@ -13,7 +13,11 @@ export async function addRoleMappings(
roleMappings: Omit[],
) {
try {
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability, activeEnvironment } = currentEnvironment.value;
await _addRoleMappings(
roleMappings.map((roleMapping) => ({
@@ -34,7 +38,11 @@ export async function deleteRoleMappings(
) {
const errors: unknown[] = [];
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability, activeEnvironment } = currentEnvironment.value;
for (const { userId, roleId } of roleMappings) {
try {
diff --git a/src/management-system-v2/lib/data/roles.ts b/src/management-system-v2/lib/data/roles.ts
index ec04f34a7..aab9894d2 100644
--- a/src/management-system-v2/lib/data/roles.ts
+++ b/src/management-system-v2/lib/data/roles.ts
@@ -2,7 +2,7 @@
import { getCurrentEnvironment } from '@/components/auth';
import { redirect } from 'next/navigation';
-import { UserErrorType, userError } from '../server-error-handling/user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import { RedirectType } from 'next/dist/client/components/redirect';
import {
deleteRole,
@@ -13,7 +13,11 @@ import {
import { UnauthorizedError } from '../ability/abilityHelper';
export async function deleteRoles(envitonmentId: string, roleIds: string[]) {
- const { ability } = await getCurrentEnvironment(envitonmentId);
+ const currentEnvironment = await getCurrentEnvironment(envitonmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
try {
for (const roleId of roleIds) {
@@ -27,14 +31,26 @@ export async function deleteRoles(envitonmentId: string, roleIds: string[]) {
}
export async function addRole(environmentId: string, role: Parameters[0]) {
- const { activeEnvironment } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { activeEnvironment } = currentEnvironment.value;
let newRoleId;
try {
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
const newRole = await _addRole({ ...role, environmentId: activeEnvironment.spaceId }, ability);
- newRoleId = newRole.id;
+ if (newRole.isErr()) {
+ return userError(getErrorMessage(newRole.error));
+ }
+
+ newRoleId = newRole.value.id;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -50,12 +66,18 @@ export async function updateRole(
updatedRole: Omit[1], 'environmentId'>,
) {
try {
- const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
- await _updateRole(
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability, activeEnvironment } = currentEnvironment.value;
+
+ const result = await _updateRole(
roleId,
{ ...updatedRole, environmentId: activeEnvironment.spaceId },
ability,
);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -65,9 +87,18 @@ export async function updateRole(
export async function getRoles(environmentId: string) {
try {
- const { ability } = await getCurrentEnvironment(environmentId);
+ const currentEnvironment = await getCurrentEnvironment(environmentId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
+ const result = await _getRoles(environmentId, ability);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
- return await _getRoles(environmentId, ability);
+ return result.value;
} catch (_) {
return userError("Something wen't wrong");
}
diff --git a/src/management-system-v2/lib/data/space-settings.ts b/src/management-system-v2/lib/data/space-settings.ts
index 7b4764385..799d71209 100644
--- a/src/management-system-v2/lib/data/space-settings.ts
+++ b/src/management-system-v2/lib/data/space-settings.ts
@@ -13,8 +13,18 @@ import { UnauthorizedError } from '../ability/abilityHelper';
export async function getSpaceSettingsValues(spaceId: string, searchKey: string) {
try {
- const { ability } = await getCurrentEnvironment(spaceId);
- return await _getSpaceSettingsValues(spaceId, searchKey, ability);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
+ const result = await _getSpaceSettingsValues(spaceId, searchKey, ability);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('You do not have permission view settings', UserErrorType.PermissionError);
@@ -24,8 +34,18 @@ export async function getSpaceSettingsValues(spaceId: string, searchKey: string)
export async function populateSpaceSettingsGroup(spaceId: string, settingsGroup: SettingGroup) {
try {
- const { ability } = await getCurrentEnvironment(spaceId);
- return await _populateSpaceSettingsGroup(spaceId, settingsGroup, ability);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
+ const result = await _populateSpaceSettingsGroup(spaceId, settingsGroup, ability);
+ if (result && result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result?.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError(
@@ -38,8 +58,18 @@ export async function populateSpaceSettingsGroup(spaceId: string, settingsGroup:
export async function updateSpaceSettings(spaceId: string, data: Record) {
try {
- const { ability } = await getCurrentEnvironment(spaceId);
- return await _updateSpaceSettings(spaceId, data, ability);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
+ const result = await _updateSpaceSettings(spaceId, data, ability);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError(
diff --git a/src/management-system-v2/lib/data/user-tasks.ts b/src/management-system-v2/lib/data/user-tasks.ts
index d1fef27e7..464a11be4 100644
--- a/src/management-system-v2/lib/data/user-tasks.ts
+++ b/src/management-system-v2/lib/data/user-tasks.ts
@@ -10,18 +10,19 @@ import {
deleteUserTask as _deleteUserTask,
} from './db/user-tasks';
import { UnauthorizedError } from '../ability/abilityHelper';
-import { UserErrorType, userError } from '../server-error-handling/user-error';
+import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
import { UserTaskInput } from '../user-task-schema';
export async function getUserTasks() {
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
try {
- return await _getUserTasks();
+ const result = await _getUserTasks();
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (err) {
- if (err instanceof UnauthorizedError)
- return userError('Permission denied', UserErrorType.PermissionError);
- else return userError('Error getting user tasks');
+ return userError('Error getting user tasks');
}
}
@@ -29,11 +30,12 @@ export async function getUserTaskById(userTaskId: string) {
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
try {
- return await _getUserTaskById(userTaskId);
+ const result = await _getUserTaskById(userTaskId);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (err) {
- if (err instanceof UnauthorizedError)
- return userError('Permission denied', UserErrorType.PermissionError);
- else return userError('Error getting user tasks');
+ return userError('Error getting user tasks');
}
}
@@ -41,7 +43,10 @@ export async function addUserTasks(userTasks: UserTaskInput[]) {
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
try {
- return await _addUserTasks(userTasks);
+ const result = await _addUserTasks(userTasks);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (e) {
if (e instanceof UnauthorizedError)
return userError('Permission denied', UserErrorType.PermissionError);
@@ -55,13 +60,12 @@ export async function updateUserTask(userTaskId: string, userTaskInput: Partial<
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
try {
- return await _updateUserTask(userTaskId, userTaskInput);
+ const result = await _updateUserTask(userTaskId, userTaskInput);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (e) {
- if (e instanceof UnauthorizedError)
- return userError('Permission denied', UserErrorType.PermissionError);
- else if (e instanceof z.ZodError)
- return userError('Schema validation failed', UserErrorType.SchemaValidationError);
- else return userError('Error getting updating user task');
+ return userError('Error getting updating user task');
}
}
@@ -69,10 +73,11 @@ export async function deleteUserTask(userTaskId: string) {
if (!enableUseDB) throw new Error('Not implemented for enableUseDB=false');
try {
- return await _deleteUserTask(userTaskId);
+ const result = await _deleteUserTask(userTaskId);
+ if (result.isErr()) return userError(getErrorMessage(result.error));
+
+ return result.value;
} catch (e) {
- if (e instanceof UnauthorizedError)
- return userError('Permission denied', UserErrorType.PermissionError);
- else return userError('Error deleting user task');
+ return userError('Error deleting user task');
}
}
diff --git a/src/management-system-v2/lib/data/users.tsx b/src/management-system-v2/lib/data/users.tsx
index ee93aa6e9..8970b4628 100644
--- a/src/management-system-v2/lib/data/users.tsx
+++ b/src/management-system-v2/lib/data/users.tsx
@@ -1,10 +1,9 @@
'use server';
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
-import { UserErrorType, getErrorMessage, userError } from '../server-error-handling/user-error';
+import { UserErrorType, getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
import { AuthenticatedUserData, AuthenticatedUserDataSchema } from './user-schema';
import { ReactNode } from 'react';
-import { OrganizationEnvironment } from './environment-schema';
import Link from 'next/link';
import {
UserHasToDeleteOrganizationsError,
@@ -13,25 +12,35 @@ import {
setUserPassword as _setUserPassword,
getUserById,
} from '@/lib/data/db/iam/users';
-import { getEnvironmentById } from './db/iam/environments';
import { hashPassword } from '../password-hashes';
import { getAppliedRolesForUser } from '../authorization/organizationEnvironmentRolesHelper';
import db from '@/lib/data/db/index';
import { env } from '../ms-config/env-vars';
export async function deleteUser() {
- const { userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
+
+ const deleteResult = await _deleteUser(userId);
+ if (deleteResult.isOk()) return;
try {
- await _deleteUser(userId);
- } catch (e) {
+ const e = deleteResult.error;
let message: ReactNode;
if (e instanceof UserHasToDeleteOrganizationsError) {
- const conflictingOrgsNames = e.conflictingOrgs.map(
- async (orgId: string) =>
- ((await getEnvironmentById(orgId)) as OrganizationEnvironment).name,
- );
+ const conflictingOrgs = await db.space.findMany({
+ where: {
+ id: { in: e.conflictingOrgs },
+ },
+ select: {
+ name: true,
+ id: true,
+ },
+ });
message = (
<>
@@ -41,14 +50,9 @@ export async function deleteUser() {
The affected organizations are:
- {conflictingOrgsNames.map((name, idx) => (
- -
- {name}:{' '}
-
- manage roles here
-
+ {conflictingOrgs.map(({ name, id }) => (
+
-
+ {name}: manage roles here
))}
@@ -62,19 +66,31 @@ export async function deleteUser() {
}
return userError(message);
+ } catch (_) {
+ return userError('Something weng wrong');
}
}
export async function updateUser(newUserDataInput: AuthenticatedUserData) {
try {
- const { userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
+
const user = await getUserById(userId);
+ if (user.isErr()) {
+ return userError(getErrorMessage(user.error));
+ }
- if (user?.isGuest) {
+ if (user.value?.isGuest) {
return userError('Guest users cannot be updated');
}
- const newUserData = AuthenticatedUserDataSchema.parse(newUserDataInput);
+ const parseResult = AuthenticatedUserDataSchema.safeParse(newUserDataInput);
+ if (!parseResult.success) return userError('Malformed data');
+ const newUserData = parseResult.data;
await _updateUser(userId, { ...newUserData });
} catch (e) {
@@ -83,32 +99,50 @@ export async function updateUser(newUserDataInput: AuthenticatedUserData) {
}
}
-export async function getUsersFavourites(): Promise {
- const { userId } = await getCurrentUser();
+export async function getUsersFavourites() {
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
const user = await getUserById(userId);
+ if (user.isErr()) {
+ return userError(getErrorMessage(user.error));
+ }
- if (user?.isGuest) {
+ if (user.value?.isGuest) {
return []; // Guest users have no favourites
}
- return user?.favourites ?? [];
+ return user.value?.favourites ?? [];
}
export async function setUserPassword(newPassword: string) {
try {
- const { userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
+
const user = await getUserById(userId);
+ if (user.isErr()) {
+ return userError(getErrorMessage(user.error));
+ }
- if (!user) {
+ if (!user.value) {
return userError('Invalid session, please sign in again');
}
- if (user?.isGuest) {
+ if (user.value?.isGuest) {
return userError('Guest users cannot change their password');
}
const passwordHash = await hashPassword(newPassword);
- await _setUserPassword(userId, passwordHash);
+ const result = await _setUserPassword(userId, passwordHash);
+ if (result.isErr()) {
+ return userError(getErrorMessage(result.error));
+ }
} catch (e) {
const message = getErrorMessage(e);
return userError(message);
@@ -124,15 +158,26 @@ export async function queryUsers(organizationId: string, searchQuery: string) {
return userError('Unauthorized', UserErrorType.PermissionError);
}
- const { userId } = await getCurrentUser();
- const { activeEnvironment } = await getCurrentEnvironment(organizationId);
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { userId } = currentUser.value;
+
+ const currentEnvironment = await getCurrentEnvironment(organizationId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { activeEnvironment } = currentEnvironment.value;
if (!activeEnvironment.isOrganization) {
return userError('Unauthorized', UserErrorType.PermissionError);
}
const userRoles = await getAppliedRolesForUser(userId, organizationId);
- const isAdmin = userRoles.some((role) => role.name === '@admin');
+ if (userRoles.isErr()) return userError(getErrorMessage(userRoles.error));
+
+ const isAdmin = userRoles.value.some((role) => role.name === '@admin');
if (!isAdmin) {
return userError('Unauthorized', UserErrorType.PermissionError);
}
@@ -175,7 +220,11 @@ export async function setUserTemporaryPassword(
spaceId?: string,
) {
try {
- const { user, systemAdmin } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { user, systemAdmin } = currentUser.value;
let allowed = false;
if (systemAdmin) {
diff --git a/src/management-system-v2/lib/db-seed.ts b/src/management-system-v2/lib/db-seed.ts
index e1012e4df..33dcab129 100644
--- a/src/management-system-v2/lib/db-seed.ts
+++ b/src/management-system-v2/lib/db-seed.ts
@@ -5,7 +5,6 @@ import { addRoleMappings } from './data/db/iam/role-mappings';
import {
OrganizationEnvironment,
UserOrganizationEnvironmentInputSchema,
- environmentSchema,
} from './data/environment-schema';
import { addRole, getRoleById } from './data/db/iam/roles';
import { addEnvironment, getEnvironmentById } from './data/db/iam/environments';
@@ -166,6 +165,7 @@ function verifySeed(seed: DBSeed) {
* -----------------------------------------------------------------------------------------------*/
async function writeSeedToDb(seed: DBSeed) {
+ // We need to throw inside the transcation to cancel it
await db.$transaction(async (tx) => {
const seedVersion = new Date(seed.version);
const seedVersionDb = await tx.seedVersion.findUnique({
@@ -196,16 +196,20 @@ async function writeSeedToDb(seed: DBSeed) {
const usernameToId = new Map();
for (const user of seed.users) {
const existingUser = await getUserById(user.id);
+ if (existingUser.isErr()) throw existingUser.error;
+
if (existingUser) {
// Use the username in seed-file instead of username in the db, as it may have changed
- usernameToId.set(user.username, existingUser.id);
+ usernameToId.set(user.username, existingUser.value.id);
continue;
}
const newUser = await addUser({ ...user, isGuest: false, emailVerifiedOn: null }, tx);
+ if (newUser.isErr()) throw newUser.error;
+
const hashedPassword = await hashPassword(user.initialPassword);
await setUserPassword(user.id, hashedPassword, tx, true);
- usernameToId.set(user.username, newUser.id);
+ usernameToId.set(user.username, newUser.value.id);
}
// Add system administrators
@@ -235,9 +239,13 @@ async function writeSeedToDb(seed: DBSeed) {
// Create / Update organizations
for (const organization of seed.organizations) {
// create org
- let org = await getEnvironmentById(organization.id);
+ const existingOrg = await getEnvironmentById(organization.id);
+ if (existingOrg.isErr()) throw existingOrg.error;
+
+ let org = existingOrg.value;
+
if (!org) {
- org = (await addEnvironment(
+ const newOrg = await addEnvironment(
{
id: organization.id,
ownerId: usernameToId.get(organization.owner)!,
@@ -251,7 +259,10 @@ async function writeSeedToDb(seed: DBSeed) {
},
undefined,
tx,
- )) as OrganizationEnvironment;
+ );
+ if (newOrg.isErr()) throw newOrg.error;
+
+ org = newOrg.value as OrganizationEnvironment;
}
// Add members + get their roles
@@ -264,9 +275,11 @@ async function writeSeedToDb(seed: DBSeed) {
// get members role mappings
const userRoles = await getRoleMappingByUserId(memberId, org.id, undefined, undefined, tx);
+ if (userRoles.isErr()) throw userRoles.error;
+
userRoleMappings.set(
memberId,
- userRoles.map((role) => role.roleId),
+ userRoles.value.map((role) => role.roleId),
);
}
@@ -306,14 +319,17 @@ async function writeSeedToDb(seed: DBSeed) {
const roleMembers = roleInput.members;
delete (roleInput as any)['members'];
- let role = await getRoleById(roleInput.id, undefined, tx);
+ const existingRole = await getRoleById(roleInput.id, undefined, tx);
+ if (existingRole.isErr()) throw existingRole.error;
+
+ let role = existingRole.value;
if (!role) {
const rolePermissions: Partial> = {};
for (const [action, permissions] of Object.entries(roleInput.permissions)) {
rolePermissions[action as ResourceType] = permissionIdentifiersToNumber(permissions);
}
- role = await addRole(
+ const newRole = await addRole(
{
...roleInput,
permissions: rolePermissions,
@@ -322,6 +338,9 @@ async function writeSeedToDb(seed: DBSeed) {
undefined,
tx,
);
+ if (newRole.isErr()) throw newRole.error;
+
+ role = newRole.value;
}
for (const roleMember of roleMembers) {
diff --git a/src/management-system-v2/lib/email-verification-tokens/server-actions.ts b/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
index 3d8ae3836..b1f6a7737 100644
--- a/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
+++ b/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
@@ -1,7 +1,5 @@
'use server';
-
import { z } from 'zod';
-import { userError } from '../server-error-handling/user-error';
import { createChangeEmailVerificationToken, getTokenHash, notExpired } from './utils';
import { getCurrentUser } from '@/components/auth';
import {
@@ -12,10 +10,16 @@ import {
import { updateUser } from '@/lib/data/db/iam/users';
import { sendEmail } from '../email/mailer';
import renderSigninLinkEmail from '../email/signin-link-email';
+import { getErrorMessage, userError } from '../server-error-handling/user-error';
export async function requestEmailChange(newEmail: string) {
try {
- const { session } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return userError(getErrorMessage(currentUser.error));
+ }
+ const { session } = currentUser.value;
+
if (!session || session.user.isGuest)
return userError('You must be signed in to change your email');
const userId = session.user.id;
@@ -54,21 +58,32 @@ export async function requestEmailChange(newEmail: string) {
}
export async function changeEmail(token: string, identifier: string, cancel: boolean = false) {
- const { session, userId } = await getCurrentUser();
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return currentUser;
+ }
+ const { session, userId } = currentUser.value;
if (!session || session.user.isGuest)
return userError('You must be signed in to change your email');
const tokenParams = { identifier, token: await getTokenHash(token) };
const verificationToken = await getEmailVerificationToken(tokenParams);
+ if (verificationToken.isErr()) {
+ return userError(getErrorMessage(verificationToken.error));
+ }
+
if (
!verificationToken ||
- verificationToken.type !== 'change_email' ||
- verificationToken.userId !== userId ||
- !(await notExpired(verificationToken))
+ verificationToken.value?.type !== 'change_email' ||
+ verificationToken.value?.userId !== userId ||
+ !(await notExpired(verificationToken.value))
)
return userError('Invalid token');
- if (!cancel) updateUser(userId, { email: verificationToken.identifier, isGuest: false });
+ if (!cancel) updateUser(userId, { email: verificationToken.value.identifier, isGuest: false });
- await deleteEmailVerificationToken(tokenParams);
+ const deleteResult = await deleteEmailVerificationToken(tokenParams);
+ if (deleteResult.isErr()) {
+ return userError(getErrorMessage(deleteResult.error));
+ }
}
diff --git a/src/management-system-v2/lib/engines/server-actions.ts b/src/management-system-v2/lib/engines/server-actions.ts
index baf890f1d..866019dc7 100644
--- a/src/management-system-v2/lib/engines/server-actions.ts
+++ b/src/management-system-v2/lib/engines/server-actions.ts
@@ -1,6 +1,11 @@
'use server';
-import { UserFacingError, getErrorMessage, userError } from '../server-error-handling/user-error';
+import {
+ UserFacingError,
+ getErrorMessage,
+ isUserErrorResponse,
+ userError,
+} from '../server-error-handling/user-error';
import {
DeployedProcessInfo,
deployProcess as _deployProcess,
@@ -8,7 +13,7 @@ import {
getProcessImageFromMachine,
removeDeploymentFromMachines,
} from './deployment';
-import { Engine, SpaceEngine } from './machines';
+import { Engine, ProceedEngine, SpaceEngine } from './machines';
import { savedEnginesToEngines } from './saved-engines-helpers';
import { getCurrentEnvironment } from '@/components/auth';
import { enableUseDB } from 'FeatureFlags';
@@ -49,18 +54,34 @@ export async function getCorrectTargetEngines(
onlyProceedEngines = false,
validatorFunc?: (engine: Engine) => Promise,
) {
- const { ability } = await getCurrentEnvironment(spaceId);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
let engines: Engine[] = [];
if (onlyProceedEngines) {
// force that only proceed engines are supposed to be used
const proceedSavedEngines = await getDbEngines(null, undefined, 'dont-check');
- engines = await savedEnginesToEngines(proceedSavedEngines);
+ if (proceedSavedEngines.isErr()) return userError(getErrorMessage(proceedSavedEngines.error));
+
+ engines = await savedEnginesToEngines(proceedSavedEngines.value);
} else {
// use all available engines
const [proceedEngines, spaceEngines] = await Promise.allSettled([
- getDbEngines(null, undefined, 'dont-check').then(savedEnginesToEngines),
- getDbEngines(spaceId, ability).then(savedEnginesToEngines),
+ (async () => {
+ const engines = await getDbEngines(null, undefined, 'dont-check');
+ if (engines.isErr()) throw engines.error;
+
+ return await savedEnginesToEngines(engines.value);
+ })(),
+ (async () => {
+ const engines = await getDbEngines(spaceId, ability);
+ if (engines.isErr()) throw engines.error;
+
+ return await savedEnginesToEngines(engines.value);
+ })(),
]);
if (proceedEngines.status === 'fulfilled') engines = proceedEngines.value;
@@ -87,15 +108,24 @@ export async function deployProcess(
let engines;
if (_forceEngine && _forceEngine !== 'PROCEED') {
// forcing a specific engine
- const { ability } = await getCurrentEnvironment(spaceId);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
const address =
_forceEngine.type === 'http' ? _forceEngine.address : _forceEngine.brokerAddress;
+
const spaceEngine = await getDbEngineByAddress(address, spaceId, ability);
- if (!spaceEngine) throw new Error('No matching space engine found');
- engines = await savedEnginesToEngines([spaceEngine]);
+ if (spaceEngine.isErr()) return userError(getErrorMessage(spaceEngine.error));
+ if (!spaceEngine.value) throw new Error('No matching space engine found');
+
+ engines = await savedEnginesToEngines([spaceEngine.value]);
if (engines.length === 0) throw new Error("Engine couldn't be reached");
} else {
engines = await getCorrectTargetEngines(spaceId, _forceEngine === 'PROCEED');
+ if (isUserErrorResponse(engines)) return engines;
}
if (engines.length === 0) throw new UserFacingError('No fitting engine found.');
@@ -117,6 +147,8 @@ export async function removeDeployment(definitionId: string, spaceId: string) {
return deployments.some((deployment) => deployment.definitionId === definitionId);
});
+ if (isUserErrorResponse(engines)) return engines;
+
await removeDeploymentFromMachines(engines, definitionId);
} catch (e) {
const message = getErrorMessage(e);
@@ -130,6 +162,7 @@ export async function getAvailableTaskListEntries(spaceId: string) {
throw new Error('getAvailableTaskListEntries only available with enableUseDB');
const engines = await getCorrectTargetEngines(spaceId);
+ if (isUserErrorResponse(engines)) return engines;
let stored = await getUserTasks();
@@ -248,6 +281,8 @@ export async function getTasklistEntryHTML(spaceId: string, userTaskId: string,
const definitionId = instanceId.split('-_')[0];
let engines = await getCorrectTargetEngines(spaceId);
+ if (isUserErrorResponse(engines)) return engines;
+
let deployments = await asyncMap(engines, async (engine) => {
return [engine, await fetchDeployments([engine])] as [Engine, DeployedProcessInfo[]];
});
@@ -365,6 +400,8 @@ export async function addOwnerToTaskListEntry(spaceId: string, userTaskId: strin
return instance.tokens.some((token) => token.currentFlowElementId === taskId);
});
+ if (isUserErrorResponse(engines)) return engines;
+
if (engines.length) {
return await addOwnerToTaskListEntryOnMachine(engines[0], instanceId, taskId, owner);
}
@@ -414,6 +451,8 @@ export async function setTasklistEntryVariableValues(
return instance.tokens.some((token) => token.currentFlowElementId === taskId);
});
+ if (isUserErrorResponse(engines)) return engines;
+
if (engines.length) {
await setTasklistEntryVariableValuesOnMachine(engines[0], instanceId, taskId, variables);
}
@@ -461,6 +500,8 @@ export async function setTasklistMilestoneValues(
return instance.tokens.some((token) => token.currentFlowElementId === taskId);
});
+ if (isUserErrorResponse(engines)) return engines;
+
if (engines.length) {
await setTasklistEntryMilestoneValuesOnMachine(engines[0], instanceId, taskId, milestones);
}
@@ -506,6 +547,8 @@ export async function completeTasklistEntry(
return instance.tokens.some((token) => token.currentFlowElementId === taskId);
});
+ if (isUserErrorResponse(engines)) return engines;
+
if (!engines.length)
throw new Error('Failed to find the engine the user task is running on!');
@@ -555,6 +598,8 @@ export async function updateVariables(
);
});
+ if (isUserErrorResponse(engines)) return engines;
+
await asyncForEach(
engines,
async (engine) => await updateVariablesOnMachine(definitionId, instanceId, engine, variables),
@@ -571,9 +616,16 @@ export async function getAvailableSpaceEngines(spaceId: string) {
if (!enableUseDB)
throw new Error('getAvailableEnginesForSpace only available with enableUseDB');
- const { ability } = await getCurrentEnvironment(spaceId);
+ const currentEnvironment = await getCurrentEnvironment(spaceId);
+ if (currentEnvironment.isErr()) {
+ return userError(getErrorMessage(currentEnvironment.error));
+ }
+ const { ability } = currentEnvironment.value;
+
const spaceEngines = await getDbEngines(spaceId, ability);
- return (await savedEnginesToEngines(spaceEngines)) as SpaceEngine[];
+ if (spaceEngines.isErr()) return userError(getErrorMessage(spaceEngines.error));
+
+ return (await savedEnginesToEngines(spaceEngines.value)) as SpaceEngine[];
} catch (e) {
const message = getErrorMessage(e);
return userError(message);
@@ -582,6 +634,7 @@ export async function getAvailableSpaceEngines(spaceId: string) {
export async function getDeployment(spaceId: string, definitionId: string) {
const engines = await getCorrectTargetEngines(spaceId);
+ if (isUserErrorResponse(engines)) return engines;
const deployments = await fetchDeployments(engines);
@@ -601,6 +654,8 @@ export async function getProcessImage(spaceId: string, definitionId: string, fil
return deployments.some((deployment) => deployment.definitionId === definitionId);
});
+ if (isUserErrorResponse(engines)) return engines;
+
if (!engines.length) throw new Error('Failed to an engine the process was deployed to!');
return await getProcessImageFromMachine(engines[0], definitionId, fileName);
diff --git a/src/management-system-v2/lib/engines/use-engines.tsx b/src/management-system-v2/lib/engines/use-engines.tsx
index 4c8d8e7a4..a07cfa56d 100644
--- a/src/management-system-v2/lib/engines/use-engines.tsx
+++ b/src/management-system-v2/lib/engines/use-engines.tsx
@@ -1,17 +1,11 @@
import { useEnvironment } from '@/components/auth-can';
-import {
- Engine,
- HttpEngine,
- MqttEngine,
- SpaceEngine,
- isHttpEngine,
- isMqttEngine,
-} from './machines';
+import { Engine, HttpEngine, MqttEngine, isHttpEngine, isMqttEngine } from './machines';
import { useCallback } from 'react';
import { getCorrectTargetEngines } from './server-actions';
import { useQuery } from '@tanstack/react-query';
import { asyncFilter } from '../helpers/javascriptHelpers';
import { truthyFilter } from '../typescript-utils';
+import { isUserErrorResponse } from '../server-error-handling/user-error';
function useEngines(
filter: { key: any[]; fn: (engine: Engine) => Promise } = {
@@ -24,6 +18,8 @@ function useEngines(
const queryFn = useCallback(async () => {
if (space.spaceId) {
let res = await getCorrectTargetEngines(space.spaceId);
+ if (isUserErrorResponse(res)) throw res.error;
+
const knownEngines: Record = {};
res = await asyncFilter(res, filter.fn);
diff --git a/src/management-system-v2/lib/helpers/processVersioning.ts b/src/management-system-v2/lib/helpers/processVersioning.ts
index c70a60aa4..abe734aab 100644
--- a/src/management-system-v2/lib/helpers/processVersioning.ts
+++ b/src/management-system-v2/lib/helpers/processVersioning.ts
@@ -28,6 +28,7 @@ import {
getHtmlForm,
} from '@/lib/data/db/process';
import { getProcessHtmlFormHTML } from '../data/processes';
+import { getErrorMessage, userError } from '../server-error-handling/user-error';
const { diff } = require('bpmn-js-differ');
// TODO: This used to be a helper file in the old management system. It used
@@ -159,12 +160,16 @@ export async function versionStartForm(
if (!dryRun) {
const startFormHtml = await getHtmlForm(processInfo.id, fileName);
+ if (startFormHtml.isErr()) return userError(getErrorMessage(startFormHtml.error));
+
const startFormData = await getProcessHtmlFormJSON(processInfo.id, fileName);
+ if (startFormData.isErr()) return userError(getErrorMessage(startFormData.error));
+
await saveProcessHtmlForm(
processInfo.id,
versionFileName,
- startFormData!,
- startFormHtml!,
+ startFormData.value!,
+ startFormHtml.value!,
versionCreatedOn,
);
}
@@ -204,12 +209,16 @@ export async function versionUserTasks(
if (!dryRun) {
const userTaskHtml = await getHtmlForm(processInfo.id, fileName);
+ if (userTaskHtml.isErr()) return userError(getErrorMessage(userTaskHtml.error));
+
const userTaskData = await getProcessHtmlFormJSON(processInfo.id, fileName);
+ if (userTaskData.isErr()) return userError(getErrorMessage(userTaskData.error));
+
await saveProcessHtmlForm(
processInfo.id,
versionFileName,
- userTaskData!,
- userTaskHtml!,
+ userTaskData.value!,
+ userTaskHtml.value!,
versionCreatedOn,
);
}
@@ -247,33 +256,36 @@ export async function versionScriptTasks(
if (!dryRun) {
try {
const scriptTaskJS = await getProcessScriptTaskScript(processInfo.id, fileName + '.js');
+ if (scriptTaskJS.isErr()) return userError(getErrorMessage(scriptTaskJS.error));
await saveProcessScriptTask(
processInfo.id,
versionFileName + '.js',
- scriptTaskJS,
+ scriptTaskJS.value,
versionCreatedOn,
);
} catch (err) {}
try {
const scriptTaskTS = await getProcessScriptTaskScript(processInfo.id, fileName + '.ts');
+ if (scriptTaskTS.isErr()) return userError(getErrorMessage(scriptTaskTS.error));
await saveProcessScriptTask(
processInfo.id,
versionFileName + '.ts',
- scriptTaskTS,
+ scriptTaskTS.value,
versionCreatedOn,
);
} catch (err) {}
try {
const scriptTaskXML = await getProcessScriptTaskScript(processInfo.id, fileName + '.xml');
+ if (scriptTaskXML.isErr()) return userError(getErrorMessage(scriptTaskXML.error));
await saveProcessScriptTask(
processInfo.id,
versionFileName + '.xml',
- scriptTaskXML,
+ scriptTaskXML.value,
versionCreatedOn,
);
} catch (err) {}
@@ -346,18 +358,20 @@ const getUsedStartFormFileNames = async (bpmn: string) => {
};
export async function selectAsLatestVersion(processId: string, versionId: string) {
- const versionBpmn = (await getProcessVersionBpmn(processId, versionId)) as string;
+ const versionBpmn = await getProcessVersionBpmn(processId, versionId);
+ if (versionBpmn.isErr()) return userError(getErrorMessage(versionBpmn.error));
const {
bpmn: convertedBpmn,
changedStartFormTaskFileNames,
changedScriptTaskFileNames,
changedUserTaskFileNames,
- } = await convertToEditableBpmn(versionBpmn);
+ } = await convertToEditableBpmn(versionBpmn.value);
- const editableBpmn = (await getProcessBpmn(processId)) as string;
+ const editableBpmn = await getProcessBpmn(processId);
+ if (editableBpmn.isErr()) return userError(getErrorMessage(editableBpmn.error));
- const startFormFileNameInEditableVersion = await getUsedStartFormFileNames(editableBpmn);
+ const startFormFileNameInEditableVersion = await getUsedStartFormFileNames(editableBpmn.value);
await asyncForEach(startFormFileNameInEditableVersion, async (processFileName) => {
await deleteHtmlForm(processId, processFileName);
@@ -365,12 +379,16 @@ export async function selectAsLatestVersion(processId: string, versionId: string
await asyncForEach(Object.entries(changedStartFormTaskFileNames), async ([oldName, newName]) => {
const json = await getProcessHtmlFormJSON(processId, oldName);
+ if (json.isErr()) return;
+
const html = await getHtmlForm(processId, oldName);
+ if (html.isErr()) return;
- if (json && html) await saveProcessHtmlForm(processId, newName, json, html);
+ if (json.value && html.value)
+ await saveProcessHtmlForm(processId, newName, json.value, html.value);
});
- const scriptFileNamesinEditableVersion = await getUsedScriptTaskFileNames(editableBpmn);
+ const scriptFileNamesinEditableVersion = await getUsedScriptTaskFileNames(editableBpmn.value);
// delete scripts stored for latest version
await asyncForEach(scriptFileNamesinEditableVersion, async (taskFileName) => {
@@ -384,12 +402,14 @@ export async function selectAsLatestVersion(processId: string, versionId: string
for (const type of ['js', 'ts', 'xml']) {
try {
const fileContent = await getProcessScriptTaskScript(processId, oldName + '.' + type);
- await saveProcessScriptTask(processId, newName + '.' + type, fileContent);
+ if (fileContent.isErr()) return;
+
+ await saveProcessScriptTask(processId, newName + '.' + type, fileContent.value);
} catch (err) {}
}
});
- const userTaskFileNamesinEditableVersion = await getUsedUserTaskFileNames(editableBpmn);
+ const userTaskFileNamesinEditableVersion = await getUsedUserTaskFileNames(editableBpmn.value);
// Delete UserTasks stored for latest version
await asyncForEach(userTaskFileNamesinEditableVersion, async (taskFileName) => {
@@ -399,9 +419,13 @@ export async function selectAsLatestVersion(processId: string, versionId: string
// Store UserTasks from this version as UserTasks from latest version
await asyncForEach(Object.entries(changedUserTaskFileNames), async ([oldName, newName]) => {
const json = await getProcessHtmlFormJSON(processId, oldName);
+ if (json.isErr()) return;
+
const html = await getHtmlForm(processId, oldName);
+ if (html.isErr()) return;
- if (json && html) await saveProcessHtmlForm(processId, newName, json, html);
+ if (json.value && html.value)
+ await saveProcessHtmlForm(processId, newName, json.value, html.value);
});
// Store bpmn from this version as latest version
diff --git a/src/management-system-v2/lib/invitation-tokens.ts b/src/management-system-v2/lib/invitation-tokens.ts
index e4d144213..87e83271a 100644
--- a/src/management-system-v2/lib/invitation-tokens.ts
+++ b/src/management-system-v2/lib/invitation-tokens.ts
@@ -8,6 +8,7 @@ import { addMember, isMember } from './data/db/iam/memberships';
import { getUserByEmail } from './data/db/iam/users';
import { getRoleById } from './data/db/iam/roles';
import { addRoleMappings } from './data/db/iam/role-mappings';
+import { getErrorMessage, userError } from './server-error-handling/user-error';
const baseInvitationSchema = {
spaceId: z.string(),
@@ -40,34 +41,52 @@ export function getInvitation(token: string) {
}
export async function acceptInvitation(invite: Invitation, userIdAcceptingInvite?: string) {
- const organization = await getEnvironmentById(invite.spaceId);
+ const _organization = await getEnvironmentById(invite.spaceId);
+ if (_organization.isErr()) return userError(getErrorMessage(_organization.error));
+ const organization = _organization.value;
+
if (!organization || !organization.isOrganization || !organization.isActive)
return { error: 'InvalidOrganization' as const };
- const userId = 'userId' in invite ? invite.userId : (await getUserByEmail(invite.email))?.id;
+ let userId;
+ if ('userId' in invite) {
+ userId = invite.userId;
+ } else {
+ const userByEmail = await getUserByEmail(invite.email);
+ if (userByEmail.isErr()) return userError(getErrorMessage(userByEmail.error));
+ if (!userByEmail.value) return userError('User not found');
+
+ userId = userByEmail.value.id;
+ }
if (!userId) return { error: 'UserNotFound' as const };
if (userIdAcceptingInvite && userIdAcceptingInvite !== userId)
return { error: 'WrongUser' as const };
- if (!(await isMember(invite.spaceId, userId))) {
- addMember(invite.spaceId, userId);
+ const userIsMember = await isMember(invite.spaceId, userId);
+ if (userIsMember.isErr()) return userError(getErrorMessage(userIsMember.error));
+
+ if (!userIsMember.value) {
+ const memberAdded = await addMember(invite.spaceId, userId);
+ if (memberAdded?.isErr()) return userError(getErrorMessage(memberAdded.error));
if (invite.roleIds) {
const validRoles = [];
for (const roleId of invite.roleIds) {
// skip roles that have been deleted
- if (await getRoleById(roleId)) validRoles.push(roleId);
+ const role = await getRoleById(roleId);
+ if (role.isOk() && role.value) validRoles.push(roleId);
}
- await addRoleMappings(
+ const result = await addRoleMappings(
validRoles.map((roleId) => ({
environmentId: invite.spaceId,
roleId,
userId,
})),
);
+ if (result?.isErr()) return userError(getErrorMessage(result.error));
}
}
}
diff --git a/src/management-system-v2/lib/page-error-handling.tsx b/src/management-system-v2/lib/page-error-handling.tsx
deleted file mode 100644
index 2439b829d..000000000
--- a/src/management-system-v2/lib/page-error-handling.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Err, type Result } from 'neverthrow';
-import { UserFacingError } from './server-error-handling/user-error';
-import { UnauthorizedError } from './ability/abilityHelper';
-import { Err } from './errors';
-
-export function errorResponse(
- result: Err,
-) {}
diff --git a/src/management-system-v2/lib/server-error-handling/page-error-response.tsx b/src/management-system-v2/lib/server-error-handling/page-error-response.tsx
index d8ed5dfbd..7cd2ee2ca 100644
--- a/src/management-system-v2/lib/server-error-handling/page-error-response.tsx
+++ b/src/management-system-v2/lib/server-error-handling/page-error-response.tsx
@@ -7,9 +7,9 @@ import { UnauthorizedError } from '../ability/abilityHelper';
import RetryButton from './retry-button';
export function errorResponse(
- result: Err,
+ result: Err | unknown,
) {
- const error = result.error;
+ const error = result instanceof Err ? result.error : undefined;
let title = 'Something Went wrong';
let status: ResultProps['status'] = 'warning';
diff --git a/src/management-system-v2/lib/sharing/process-sharing.ts b/src/management-system-v2/lib/sharing/process-sharing.ts
index 23280a081..0049da881 100644
--- a/src/management-system-v2/lib/sharing/process-sharing.ts
+++ b/src/management-system-v2/lib/sharing/process-sharing.ts
@@ -76,16 +76,3 @@ export async function generateSharedViewerUrl(
return userError('Something went wrong');
}
}
-
-export async function getAllUserWorkspaces(userId: string, ability?: Ability) {
- // if (ability && !ability.can('delete', 'Environment')) throw new UnauthorizedError();
-
- const userEnvironments: Environment[] = [(await getEnvironmentById(userId))!];
- const userOrgEnvs = await getUserOrganizationEnvironments(userId);
- const orgEnvironments = (await asyncMap(userOrgEnvs, (environmentId) =>
- getEnvironmentById(environmentId),
- )) as Environment[];
-
- userEnvironments.push(...orgEnvironments);
- return userEnvironments;
-}
From 114b62732e9c62a0c073cae4c6f820e26781c55a Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Tue, 9 Dec 2025 02:16:05 +0100
Subject: [PATCH 04/43] fix
---
.../[environmentId]/iam/users/page.tsx | 21 ++++---------------
1 file changed, 4 insertions(+), 17 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx
index 52e9d8cba..69ae53904 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/page.tsx
@@ -2,12 +2,8 @@ import { getCurrentEnvironment } from '@/components/auth';
import UsersPage from './users-page';
import Content from '@/components/content';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
-import { getMembers } from '@/lib/data/db/iam/memberships';
-import { getUserById } from '@/lib/data/db/iam/users';
-import { AuthenticatedUser } from '@/lib/data/user-schema';
-import { asyncMap } from '@/lib/helpers/javascriptHelpers';
+import { getFullMembersWithRoles } from '@/lib/data/db/iam/memberships';
import { errorResponse } from '@/lib/server-error-handling/page-error-response';
-import { Result } from 'neverthrow';
const Page = async ({ params }: { params: { environmentId: string } }) => {
const currentSpace = await getCurrentEnvironment(params.environmentId);
@@ -17,21 +13,12 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
const { ability, activeEnvironment } = currentSpace.value;
if (!ability.can('manage', 'User')) return ;
- const memberships = await getMembers(activeEnvironment.spaceId, ability);
- if (memberships.isErr()) {
- return errorResponse(memberships);
- }
-
- const users = Result.combine(
- await asyncMap(memberships.value, (user) => getUserById(user.userId)),
- );
- if (users.isErr()) {
- return errorResponse(users);
- }
+ const users = await getFullMembersWithRoles(activeEnvironment.spaceId, ability);
+ if (users.isErr()) return errorResponse(users);
return (
-
+
);
};
From 6563262da4e55238b45d12cc87f01ba5e3ffb31e Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Tue, 9 Dec 2025 10:05:23 +0100
Subject: [PATCH 05/43] fixes
---
src/management-system-v2/lib/data/db/iam/memberships.ts | 5 +++--
src/management-system-v2/lib/engines/instances.ts | 2 +-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/management-system-v2/lib/data/db/iam/memberships.ts b/src/management-system-v2/lib/data/db/iam/memberships.ts
index 7ccc17a1f..af358b94a 100644
--- a/src/management-system-v2/lib/data/db/iam/memberships.ts
+++ b/src/management-system-v2/lib/data/db/iam/memberships.ts
@@ -60,7 +60,7 @@ export async function getMembers(environmentId: string, ability?: Ability) {
}
export async function getFullMembersWithRoles(environmentId: string, ability?: Ability) {
- if (ability && !ability.can('admin', 'User')) throw new UnauthorizedError();
+ if (ability && !ability.can('admin', 'User')) err(new UnauthorizedError());
const usersWithRoles = await db.user.findMany({
where: {
@@ -95,7 +95,8 @@ export async function getFullMembersWithRoles(environmentId: string, ability?: A
roles: User['roleMembers'][number]['role'][];
isGuest: false;
};
- return usersWithRoles as unknown as TransformedUserType[];
+
+ return ok(usersWithRoles as unknown as TransformedUserType[]);
}
/**
diff --git a/src/management-system-v2/lib/engines/instances.ts b/src/management-system-v2/lib/engines/instances.ts
index 4910ca2fb..c8058651a 100644
--- a/src/management-system-v2/lib/engines/instances.ts
+++ b/src/management-system-v2/lib/engines/instances.ts
@@ -2,7 +2,7 @@
import { Engine } from './machines';
import { engineRequest } from './endpoints/index';
-import { userError } from '../user-error';
+import { userError } from '@/lib/server-error-handling/user-error';
export async function startInstanceOnMachine(
definitionId: string,
From f53390667060235b942bb52d9f97fce384397cf2 Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Tue, 9 Dec 2025 13:09:48 +0100
Subject: [PATCH 06/43] fix: error type checks
---
src/management-system-v2/lib/data/db/iam/users.ts | 8 ++++++--
src/management-system-v2/lib/db-seed.ts | 4 +++-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/management-system-v2/lib/data/db/iam/users.ts b/src/management-system-v2/lib/data/db/iam/users.ts
index a4c31edf3..f0ba17ff1 100644
--- a/src/management-system-v2/lib/data/db/iam/users.ts
+++ b/src/management-system-v2/lib/data/db/iam/users.ts
@@ -92,10 +92,14 @@ export async function _addUser(
const [usernameRes, emailRes] = await Promise.all(checks);
if (usernameRes) {
- return err(new NextAuthUsernameTakenError());
+ if (usernameRes.isErr()) return usernameRes;
+
+ if (usernameRes.value) return err(new NextAuthUsernameTakenError());
}
if (emailRes) {
- return err(new NextAuthEmailTakenError());
+ if (emailRes.isErr()) return emailRes;
+
+ if (emailRes.value) return err(new NextAuthEmailTakenError());
}
}
diff --git a/src/management-system-v2/lib/db-seed.ts b/src/management-system-v2/lib/db-seed.ts
index 33dcab129..55ce88150 100644
--- a/src/management-system-v2/lib/db-seed.ts
+++ b/src/management-system-v2/lib/db-seed.ts
@@ -198,14 +198,16 @@ async function writeSeedToDb(seed: DBSeed) {
const existingUser = await getUserById(user.id);
if (existingUser.isErr()) throw existingUser.error;
- if (existingUser) {
+ if (existingUser.value) {
// Use the username in seed-file instead of username in the db, as it may have changed
usernameToId.set(user.username, existingUser.value.id);
continue;
}
+ console.log('1');
const newUser = await addUser({ ...user, isGuest: false, emailVerifiedOn: null }, tx);
if (newUser.isErr()) throw newUser.error;
+ console.log('2');
const hashedPassword = await hashPassword(user.initialPassword);
await setUserPassword(user.id, hashedPassword, tx, true);
From a39e94c87905b6cfa0c78c3214d9d5af5ded6ed4 Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Tue, 9 Dec 2025 17:56:16 +0100
Subject: [PATCH 07/43] fix(ms2): error handling get process with bpmn
---
.../app/shared-viewer/page.tsx | 89 +++++++++++--------
.../components/error-message.tsx | 3 +-
.../lib/data/processes.tsx | 2 +-
3 files changed, 53 insertions(+), 41 deletions(-)
diff --git a/src/management-system-v2/app/shared-viewer/page.tsx b/src/management-system-v2/app/shared-viewer/page.tsx
index d01fd1424..734aaf6c5 100644
--- a/src/management-system-v2/app/shared-viewer/page.tsx
+++ b/src/management-system-v2/app/shared-viewer/page.tsx
@@ -22,8 +22,15 @@ import { getDefinitionsAndProcessIdForEveryCallActivity } from '@proceed/bpmn-he
import { SettingsOption } from './settings-modal';
import { asyncMap } from '@/lib/helpers/javascriptHelpers';
import { env } from '@/lib/ms-config/env-vars';
-import { getErrorMessage, userError } from '@/lib/server-error-handling/user-error';
+import {
+ UserError,
+ getErrorMessage,
+ isUserErrorResponse,
+ userError,
+} from '@/lib/server-error-handling/user-error';
import { Result } from 'neverthrow';
+import { ReactNode } from 'react';
+import { undefined } from 'zod';
interface PageProps {
searchParams: {
@@ -49,27 +56,23 @@ const getProcessInfo = async (
versionId?: string,
) => {
const currentUser = await getCurrentUser();
- if (currentUser.isErr()) {
- return errorResponse(currentUser);
- }
- const { session, userId } = currentUser.value;
+ if (currentUser.isErr()) return userError(getErrorMessage(currentUser.error));
+ const { session } = currentUser.value;
+
let spaceId;
let isOwner = false;
let processData;
// check if there is a session (=> the user is already logged in)
if (session) {
const currentSpace = await getCurrentEnvironment(session?.user.id);
- if (currentSpace.isErr()) {
- return errorResponse(currentSpace);
- }
+ if (currentSpace.isErr()) return userError(getErrorMessage(currentSpace.error));
+
const { ability, activeEnvironment } = currentSpace.value;
({ spaceId } = activeEnvironment);
// get all the processes the user has access to
const ownedProcesses = await getProcesses(spaceId, ability);
- if (ownedProcesses.isErr()) {
- return userError(getErrorMessage(ownedProcesses.error));
- }
+ if (ownedProcesses.isErr()) return userError(getErrorMessage(ownedProcesses.error));
// check if the current user is the owner of the process(/has access to the process) => if yes give access regardless of sharing status
isOwner = ownedProcesses.value.some((process) => process.id === definitionId);
@@ -93,19 +96,17 @@ const getProcessInfo = async (
} else {
// the user has no regular access to the process so get the process data from the sharing api
const res = await getSharedProcessWithBpmn(definitionId, versionId);
- if ('error' in res) {
- return ;
- } else {
- processData = res;
-
- if (
- // bypass the timestamp check for imports
- !isImport &&
- ((embeddedMode && timestamp !== processData.value.allowIframeTimestamp) ||
- (!embeddedMode && timestamp !== processData.value.shareTimestamp))
- ) {
- return ;
- }
+ if ('error' in res) return res;
+
+ processData = res;
+
+ if (
+ // bypass the timestamp check for imports
+ !isImport &&
+ ((embeddedMode && timestamp !== processData.allowIframeTimestamp) ||
+ (!embeddedMode && timestamp !== processData.shareTimestamp))
+ ) {
+ return userError('Token expired');
}
}
@@ -118,7 +119,10 @@ const getProcessInfo = async (
* @param bpmn the bpmn of the process to get the imports for
* @param knownInfos the object to put the bpmns into
*/
-const getImportInfos = async (bpmn: string, knownInfos: ImportsInfo) => {
+const getImportInfos = async (
+ bpmn: string,
+ knownInfos: ImportsInfo,
+): Promise<{ error: UserError } | undefined> => {
// information which tasks reference which processes
const taskImportMap = await getDefinitionsAndProcessIdForEveryCallActivity(bpmn, true);
@@ -127,17 +131,15 @@ const getImportInfos = async (bpmn: string, knownInfos: ImportsInfo) => {
if (!(knownInfos[definitionId] && knownInfos[definitionId][versionId])) {
const processInfo = await getProcessInfo(definitionId, 0, false, true, versionId);
+ if (isUserErrorResponse(processInfo)) return processInfo;
- // check if the return value is a valid process info (might also be a react component that signals an error => no isOwner)
- if ('isOwner' in processInfo && processInfo.processData) {
- const { bpmn: importBpmn } = processInfo.processData;
+ const { bpmn: importBpmn } = processInfo.processData;
- if (!knownInfos[definitionId]) knownInfos[definitionId] = {};
- knownInfos[definitionId][versionId] = importBpmn as string;
+ if (!knownInfos[definitionId]) knownInfos[definitionId] = {};
+ knownInfos[definitionId][versionId] = importBpmn as string;
- // recursively get the imports of the imports
- await getImportInfos(importBpmn as string, knownInfos);
- }
+ // recursively get the imports of the imports
+ return await getImportInfos(importBpmn as string, knownInfos);
}
}
};
@@ -191,10 +193,12 @@ const SharedViewer = async ({ searchParams }: PageProps) => {
);
// the return value of getProcessInfo might be an error that should just be returned to the user
- if (!('isOwner' in processInfo)) {
- return processInfo;
+ if (isUserErrorResponse(processInfo)) {
+ return ;
}
+ console.log('processData', processData);
+
({ isOwner, processData } = processInfo);
if (!processData) {
@@ -208,10 +212,17 @@ const SharedViewer = async ({ searchParams }: PageProps) => {
let availableImports: ImportsInfo = {};
if (!iframeMode) {
+ let errorMessage: ReactNode;
try {
- await getImportInfos(processData.bpmn, availableImports);
- } catch (err) {
- console.error('Failed to resolve the information for process imports: ', err);
+ const error = await getImportInfos(processData.bpmn, availableImports);
+ if (isUserErrorResponse(error)) errorMessage = error.error.message;
+ } catch (error) {
+ errorMessage = getErrorMessage(error);
+ console.error('Failed to resolve the information for process imports: ', error);
+ }
+
+ if (errorMessage) {
+ return ;
}
}
@@ -244,7 +255,7 @@ const SharedViewer = async ({ searchParams }: PageProps) => {
diff --git a/src/management-system-v2/components/error-message.tsx b/src/management-system-v2/components/error-message.tsx
index f25fa0b37..683ac6e0d 100644
--- a/src/management-system-v2/components/error-message.tsx
+++ b/src/management-system-v2/components/error-message.tsx
@@ -2,9 +2,10 @@
import { Typography } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { ReactNode } from 'react';
interface ErrorMessageProps {
- message: string;
+ message: ReactNode;
}
const ErrorMessage = ({ message }: ErrorMessageProps) => {
diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx
index 039b7aa05..5ae69cd6b 100644
--- a/src/management-system-v2/lib/data/processes.tsx
+++ b/src/management-system-v2/lib/data/processes.tsx
@@ -158,7 +158,7 @@ export const getSharedProcessWithBpmn = async (definitionId: string, versionCrea
return bpmn;
}
- const processWithBPMN = { ...processMetaObj, bpmn: bpmn };
+ const processWithBPMN = { ...processMetaObj.value, bpmn: bpmn };
return processWithBPMN;
}
From f899c4eea5846bc07bf41802772837b4ef93818c Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Wed, 10 Dec 2025 02:35:33 +0100
Subject: [PATCH 08/43] remove unused import
---
src/management-system-v2/app/shared-viewer/page.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/management-system-v2/app/shared-viewer/page.tsx b/src/management-system-v2/app/shared-viewer/page.tsx
index 734aaf6c5..8e594f950 100644
--- a/src/management-system-v2/app/shared-viewer/page.tsx
+++ b/src/management-system-v2/app/shared-viewer/page.tsx
@@ -30,7 +30,6 @@ import {
} from '@/lib/server-error-handling/user-error';
import { Result } from 'neverthrow';
import { ReactNode } from 'react';
-import { undefined } from 'zod';
interface PageProps {
searchParams: {
From 5982dc14008c778877ae6bfc038fc1abcd2c82eb Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Wed, 10 Dec 2025 18:04:29 +0100
Subject: [PATCH 09/43] fix error returns
---
src/management-system-v2/lib/data/db/process.ts | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/management-system-v2/lib/data/db/process.ts b/src/management-system-v2/lib/data/db/process.ts
index deff5bb13..d8977aa4b 100644
--- a/src/management-system-v2/lib/data/db/process.ts
+++ b/src/management-system-v2/lib/data/db/process.ts
@@ -176,8 +176,8 @@ export async function checkIfProcessAlreadyExistsForAUserInASpaceByName(
});
return ok(!!existingProcess);
- } catch (err: any) {
- return err(new Error('Error checking if process exists by name:', err.message));
+ } catch (error: any) {
+ return err(new Error('Error checking if process exists by name:', error.message));
}
}
@@ -211,8 +211,8 @@ export async function checkIfProcessAlreadyExistsForAUserInASpaceByNameWithBatch
// Return an array of booleans per process
return ok(processes.map(({ name, folderId }) => existingSet.has(`${name}:::${folderId}`)));
- } catch (err: any) {
- return err(new Error(`Error checking process names in batch: ${err.message}`));
+ } catch (error: any) {
+ return err(new Error(`Error checking process names in batch: ${error.message}`));
}
}
@@ -654,7 +654,8 @@ export async function getProcessVersionBpmn(processDefinitionsId: string, versio
where: { id: versionId },
});
- return ok(((await retrieveFile(versn?.bpmnFilePath!, false)) as Buffer).toString('utf8'));
+ const bpmn = ((await retrieveFile(versn?.bpmnFilePath!, false)) as Buffer).toString('utf8');
+ return ok(bpmn);
}
/** Removes information from the meta data that would not be correct after a restart */
From 39e13f18de8486b3e31ff6cc48308a793de9f618 Mon Sep 17 00:00:00 2001
From: Felipe Trost
Date: Wed, 10 Dec 2025 18:05:05 +0100
Subject: [PATCH 10/43] fix(shared-viewer): don't stop import on error
---
.../app/shared-viewer/page.tsx | 20 +++++--------------
1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/src/management-system-v2/app/shared-viewer/page.tsx b/src/management-system-v2/app/shared-viewer/page.tsx
index 8e594f950..723604299 100644
--- a/src/management-system-v2/app/shared-viewer/page.tsx
+++ b/src/management-system-v2/app/shared-viewer/page.tsx
@@ -118,10 +118,7 @@ const getProcessInfo = async (
* @param bpmn the bpmn of the process to get the imports for
* @param knownInfos the object to put the bpmns into
*/
-const getImportInfos = async (
- bpmn: string,
- knownInfos: ImportsInfo,
-): Promise<{ error: UserError } | undefined> => {
+const getImportInfos = async (bpmn: string, knownInfos: ImportsInfo): Promise => {
// information which tasks reference which processes
const taskImportMap = await getDefinitionsAndProcessIdForEveryCallActivity(bpmn, true);
@@ -130,7 +127,7 @@ const getImportInfos = async (
if (!(knownInfos[definitionId] && knownInfos[definitionId][versionId])) {
const processInfo = await getProcessInfo(definitionId, 0, false, true, versionId);
- if (isUserErrorResponse(processInfo)) return processInfo;
+ if (isUserErrorResponse(processInfo)) continue;
const { bpmn: importBpmn } = processInfo.processData;
@@ -138,7 +135,7 @@ const getImportInfos = async (
knownInfos[definitionId][versionId] = importBpmn as string;
// recursively get the imports of the imports
- return await getImportInfos(importBpmn as string, knownInfos);
+ await getImportInfos(importBpmn as string, knownInfos);
}
}
};
@@ -196,8 +193,6 @@ const SharedViewer = async ({ searchParams }: PageProps) => {
return ;
}
- console.log('processData', processData);
-
({ isOwner, processData } = processInfo);
if (!processData) {
@@ -211,17 +206,12 @@ const SharedViewer = async ({ searchParams }: PageProps) => {
let availableImports: ImportsInfo = {};
if (!iframeMode) {
- let errorMessage: ReactNode;
try {
- const error = await getImportInfos(processData.bpmn, availableImports);
- if (isUserErrorResponse(error)) errorMessage = error.error.message;
+ await getImportInfos(processData.bpmn, availableImports);
} catch (error) {
- errorMessage = getErrorMessage(error);
console.error('Failed to resolve the information for process imports: ', error);
- }
- if (errorMessage) {
- return ;
+ return ;
}
}
From ed698cb88276bc88ff81c65ae9bc752671f25629 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:02:08 +0100
Subject: [PATCH 11/43] Removed debug loggin
---
src/management-system-v2/lib/db-seed.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/management-system-v2/lib/db-seed.ts b/src/management-system-v2/lib/db-seed.ts
index 55ce88150..0b90583bf 100644
--- a/src/management-system-v2/lib/db-seed.ts
+++ b/src/management-system-v2/lib/db-seed.ts
@@ -204,10 +204,8 @@ async function writeSeedToDb(seed: DBSeed) {
continue;
}
- console.log('1');
const newUser = await addUser({ ...user, isGuest: false, emailVerifiedOn: null }, tx);
if (newUser.isErr()) throw newUser.error;
- console.log('2');
const hashedPassword = await hashPassword(user.initialPassword);
await setUserPassword(user.id, hashedPassword, tx, true);
From 2873e2aeb59cce47e9ce21d0e3743fded9bbd4a2 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:02:30 +0100
Subject: [PATCH 12/43] Removed unused file
---
src/management-system-v2/lib/result.ts | 50 --------------------------
1 file changed, 50 deletions(-)
delete mode 100644 src/management-system-v2/lib/result.ts
diff --git a/src/management-system-v2/lib/result.ts b/src/management-system-v2/lib/result.ts
deleted file mode 100644
index cc6b8d5b4..000000000
--- a/src/management-system-v2/lib/result.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export type Result = Ok | Err;
-
-export class Ok {
- readonly isOk = true;
- readonly isErr = false;
-
- constructor(readonly value: T) {}
-
- map(fn: (value: T) => U): Result {
- return new Ok(fn(this.value));
- }
-
- mapErr(_fn: (error: E) => F): Result {
- return new Ok(this.value);
- }
-
- andThen(fn: (value: T) => Result): Result {
- return fn(this.value);
- }
-
- match(pattern: { ok: (value: T) => U; err: (error: E) => U }): U {
- return pattern.ok(this.value);
- }
-}
-
-export class Err {
- readonly isOk = false;
- readonly isErr = true;
-
- constructor(readonly error: E) {}
-
- map(_fn: (value: T) => U): Result {
- return new Err(this.error);
- }
-
- mapErr(fn: (error: E) => F): Result {
- return new Err(fn(this.error));
- }
-
- andThen(_fn: (value: T) => Result): Result {
- return new Err(this.error);
- }
-
- match(pattern: { ok: (value: T) => U; err: (error: E) => U }): U {
- return pattern.err(this.error);
- }
-}
-
-export const ok = (value: T): Result => new Ok(value);
-export const err = (error: E): Result => new Err(error);
From 3ec5a322499f0d0c22a8a0b173c06978b8da7b27 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:09:35 +0100
Subject: [PATCH 13/43] Fixed: If the signin page encounters an error when
fetching the user the error page is displayed above the dummy process list
page instead of replacing it
---
src/management-system-v2/app/(auth)/layout.tsx | 9 ++++++++-
src/management-system-v2/app/(auth)/signin/page.tsx | 5 +++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/management-system-v2/app/(auth)/layout.tsx b/src/management-system-v2/app/(auth)/layout.tsx
index dbeedfec3..fe8cd401c 100644
--- a/src/management-system-v2/app/(auth)/layout.tsx
+++ b/src/management-system-v2/app/(auth)/layout.tsx
@@ -1,11 +1,18 @@
import Layout from '@/app/(dashboard)/[environmentId]/layout-client';
+import { getCurrentUser } from '@/components/auth';
import Content from '@/components/content';
import Processes from '@/components/processes';
import { SetAbility } from '@/lib/abilityStore';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
import { FC, PropsWithChildren } from 'react';
import { AiOutlineFile, AiOutlineProfile } from 'react-icons/ai';
-const SigninLayout: FC = ({ children }) => {
+const SigninLayout: FC = async ({ children }) => {
+ const currentUser = await getCurrentUser();
+ if (currentUser.isErr()) {
+ return errorResponse(currentUser);
+ }
+
return (
<>
{children}
diff --git a/src/management-system-v2/app/(auth)/signin/page.tsx b/src/management-system-v2/app/(auth)/signin/page.tsx
index 3f9f9ae1e..bf6f0ced3 100644
--- a/src/management-system-v2/app/(auth)/signin/page.tsx
+++ b/src/management-system-v2/app/(auth)/signin/page.tsx
@@ -1,6 +1,6 @@
import { getProviders } from '@/lib/auth';
import { getCurrentUser } from '@/components/auth';
-import { redirect } from 'next/navigation';
+import { notFound, redirect } from 'next/navigation';
import SignIn from './signin';
import { generateGuestReferenceToken } from '@/lib/reference-guest-user-token';
import { env } from '@/lib/ms-config/env-vars';
@@ -13,7 +13,8 @@ const dayInMS = 1000 * 60 * 60 * 24;
const SignInPage = async ({ searchParams }: { searchParams: { callbackUrl: string } }) => {
const currentUser = await getCurrentUser();
if (currentUser.isErr()) {
- return errorResponse(currentUser);
+ // this should be handled in the layout in the parent folder already
+ return notFound();
}
const { session } = currentUser.value;
const isGuest = session?.user.isGuest;
From 75125026e1820c4e431ac189a28010fcf296131a Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:09:56 +0100
Subject: [PATCH 14/43] Fixed a typo
---
.../[environmentId]/(automation)/executions/layout.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/layout.tsx
index d8771df9c..c28a1a738 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/layout.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/layout.tsx
@@ -20,15 +20,15 @@ const ExecutionsLayout: React.FC = async ({ params, childr
}
const { activeEnvironment } = currentSpace.value;
- const exeuctionsSettings = await getSpaceSettingsValues(
+ const executionsSettings = await getSpaceSettingsValues(
activeEnvironment.spaceId,
'process-automation.executions',
);
- if (exeuctionsSettings.isErr()) {
- return errorResponse(exeuctionsSettings);
+ if (executionsSettings.isErr()) {
+ return errorResponse(executionsSettings);
}
- if (exeuctionsSettings.value.active === false) {
+ if (executionsSettings.value.active === false) {
return notFound();
}
From 751fe79d0d239484b9b4d9e9a8a727f9feedf64b Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:13:32 +0100
Subject: [PATCH 15/43] Removed unused code and added/updated some comments
---
.../[environmentId]/processes/[mode]/[processId]/page.tsx | 2 --
.../[environmentId]/settings/@processAutomation/page.tsx | 1 -
src/management-system-v2/app/admin/systemadmins/page.tsx | 3 ++-
src/management-system-v2/app/shared-viewer/page.tsx | 2 --
src/management-system-v2/components/auth.tsx | 4 +---
src/management-system-v2/lib/data/file-manager-facade.ts | 6 ++----
6 files changed, 5 insertions(+), 13 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx
index c6b841a63..d5186898b 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/page.tsx
@@ -5,11 +5,9 @@ import Modeler from './modeler';
import { toCaslResource } from '@/lib/ability/caslAbility';
import AddUserControls from '@/components/add-user-controls';
import { getProcess, getProcesses } from '@/lib/data/db/process';
-import { getRolesWithMembers } from '@/lib/data/db/iam/roles';
import { getProcessBPMN } from '@/lib/data/processes';
import BPMNTimeline from '@/components/bpmn-timeline';
import { UnauthorizedError } from '@/lib/ability/abilityHelper';
-import { RoleType, UserType } from './use-potentialOwner-store';
import type { Process } from '@/lib/data/process-schema';
import { redirect } from 'next/navigation';
import { spaceURL } from '@/lib/utils';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx
index 2cefb293b..466c4e6f4 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/@processAutomation/page.tsx
@@ -15,7 +15,6 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
return errorResponse(currentSpace);
}
const {
- ability,
activeEnvironment: { spaceId },
} = currentSpace.value;
diff --git a/src/management-system-v2/app/admin/systemadmins/page.tsx b/src/management-system-v2/app/admin/systemadmins/page.tsx
index 09daf7762..db8148687 100644
--- a/src/management-system-v2/app/admin/systemadmins/page.tsx
+++ b/src/management-system-v2/app/admin/systemadmins/page.tsx
@@ -121,7 +121,6 @@ export default async function ManageAdminsPage() {
if (!adminData.value) redirect('/');
if (adminData.value.role !== 'admin') return ;
- type aa = Promise<(AuthenticatedUser & { role: 'admin' })[]>;
const getFullSystemAdmins = async () => {
const admins = await getSystemAdmins();
if (admins.isErr()) return admins;
@@ -134,6 +133,8 @@ export default async function ManageAdminsPage() {
return user;
}
+ // TODO: handle that the user might not be found (can that happen?)
+
return ok({ ...(user.value as AuthenticatedUser), role: admin.role });
}),
),
diff --git a/src/management-system-v2/app/shared-viewer/page.tsx b/src/management-system-v2/app/shared-viewer/page.tsx
index 723604299..d02811140 100644
--- a/src/management-system-v2/app/shared-viewer/page.tsx
+++ b/src/management-system-v2/app/shared-viewer/page.tsx
@@ -23,13 +23,11 @@ import { SettingsOption } from './settings-modal';
import { asyncMap } from '@/lib/helpers/javascriptHelpers';
import { env } from '@/lib/ms-config/env-vars';
import {
- UserError,
getErrorMessage,
isUserErrorResponse,
userError,
} from '@/lib/server-error-handling/user-error';
import { Result } from 'neverthrow';
-import { ReactNode } from 'react';
interface PageProps {
searchParams: {
diff --git a/src/management-system-v2/components/auth.tsx b/src/management-system-v2/components/auth.tsx
index 76f71c72f..3230d6e2f 100644
--- a/src/management-system-v2/components/auth.tsx
+++ b/src/management-system-v2/components/auth.tsx
@@ -18,7 +18,6 @@ import { getMSConfig } from '@/lib/ms-config/ms-config';
import { packedStaticRules } from '@/lib/authorization/caslRules';
import { err, ok } from 'neverthrow';
import { UserFacingError } from '@/lib/server-error-handling/user-error';
-import { Prettify } from '@/lib/typescript-utils';
export const getCurrentUser = cache(async () => {
if (!env.PROCEED_PUBLIC_IAM_ACTIVE) {
@@ -80,7 +79,6 @@ export const getSystemAdminRules = cache((isOrganization: boolean) => {
}
});
-type a = Awaited>;
// TODO: To enable PPR move the session redirect into this function, so it will
// be called when the session is first accessed and everything above can PPR. For
// permissions, each server component should check its permissions anyway, for
@@ -97,7 +95,7 @@ export const getCurrentEnvironment = cache(
) => {
const currentUser = await getCurrentUser();
if (currentUser.isErr()) {
- return err('pepe');
+ return err('Could not get the current user');
}
const { userId, systemAdmin } = currentUser.value;
diff --git a/src/management-system-v2/lib/data/file-manager-facade.ts b/src/management-system-v2/lib/data/file-manager-facade.ts
index 31ec4010c..c302c29dd 100644
--- a/src/management-system-v2/lib/data/file-manager-facade.ts
+++ b/src/management-system-v2/lib/data/file-manager-facade.ts
@@ -7,13 +7,11 @@ import {
ArtifactType,
generateProcessFilePath,
} from '../helpers/fileManagerHelpers';
-import { contentTypeNotAllowed } from './content-upload-error';
-import { copyFile, deleteFile, retrieveFile, saveFile } from './file-manager/file-manager';
+import { deleteFile, retrieveFile, saveFile } from './file-manager/file-manager';
import db from '@/lib/data/db';
import { getProcessHtmlFormJSON } from './db/process';
-import { asyncMap, findKey } from '../helpers/javascriptHelpers';
+import { asyncMap } from '../helpers/javascriptHelpers';
import { Prisma } from '@prisma/client';
-import { use } from 'react';
import { checkValidity } from './processes';
import { env } from '@/lib/ms-config/env-vars';
import { getUsedImagesFromJson } from '@/components/html-form-editor/serialized-format-utils';
From fc39cd0b72de75329adf119b27542e661b98d018 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:14:47 +0100
Subject: [PATCH 16/43] Fixed a small typo
---
src/management-system-v2/app/admin/spaces/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/management-system-v2/app/admin/spaces/page.tsx b/src/management-system-v2/app/admin/spaces/page.tsx
index d275eb617..decbca83d 100644
--- a/src/management-system-v2/app/admin/spaces/page.tsx
+++ b/src/management-system-v2/app/admin/spaces/page.tsx
@@ -34,7 +34,7 @@ async function deleteSpace(spaceIds: string[]) {
}
export type deleteSpace = typeof deleteSpace;
-export default async function SysteAdminDashboard({ params }: { params?: { userId: string } }) {
+export default async function SystemAdminDashboard({ params }: { params?: { userId: string } }) {
const user = await getCurrentUser();
if (user.isErr()) return errorResponse(user);
if (!user.value.session) redirect('/');
From 71105f5fcd6dc1b7772fca51945660ed40bde63d Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:15:49 +0100
Subject: [PATCH 17/43] Fixed a typo
---
src/management-system-v2/lib/authorization/authorization.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/management-system-v2/lib/authorization/authorization.ts b/src/management-system-v2/lib/authorization/authorization.ts
index b20a4d0fc..05c885b6a 100644
--- a/src/management-system-v2/lib/authorization/authorization.ts
+++ b/src/management-system-v2/lib/authorization/authorization.ts
@@ -92,9 +92,9 @@ export async function getUserRules(userId: string, environmentId: string) {
// TODO: get bough features from db
- const getPurhasedFeatures = (_: string) => [];
+ const getPurchasedFeatures = (_: string) => [];
- const purchasedResources = getPurhasedFeatures(environmentId).filter((resource) =>
+ const purchasedResources = getPurchasedFeatures(environmentId).filter((resource) =>
MSEnabledResources.includes(resource as any),
);
From 9de1d034bd846db8713c7f03d7eba5f911c13aa6 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:26:10 +0100
Subject: [PATCH 18/43] Fixed: In some places the code was not correctly
updated to reflect that we now have to check if the returned Result object
contains a value instead of if the return value is falsy (which in these
cases would always be false since a Result object is always returned); added
some returns of empty Results in some places to make the functions always
return a result instead of a Result or undefined
---
.../[environmentId]/profile/page.tsx | 2 +-
.../app/transfer-processes/page.tsx | 2 +-
.../lib/data/db/process.ts | 23 ++++++++++++-------
.../lib/data/db/space-settings.ts | 2 +-
.../lib/data/html-forms.ts | 2 +-
.../lib/data/processes.tsx | 5 ++--
.../server-actions.ts | 6 ++---
7 files changed, 24 insertions(+), 18 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx
index f4be9a9e5..1e753eea4 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/profile/page.tsx
@@ -19,7 +19,7 @@ const ProfilePage = async () => {
if (userData.isErr()) return errorResponse(userData);
const userHasPassword = await getUserPassword(userId);
- if (userHasPassword && userHasPassword.isErr()) {
+ if (userHasPassword.isErr()) {
return errorResponse(userHasPassword);
}
diff --git a/src/management-system-v2/app/transfer-processes/page.tsx b/src/management-system-v2/app/transfer-processes/page.tsx
index 44af18ed1..9f53bdd12 100644
--- a/src/management-system-v2/app/transfer-processes/page.tsx
+++ b/src/management-system-v2/app/transfer-processes/page.tsx
@@ -56,7 +56,7 @@ export default async function TransferProcessesPage({
// accocunt, generating the token above, and before using it, he signed in with a new account.
// We only go further then this redirect, if the user signed in with an account that was
// already linked to an existing user
- if (!possibleGuest || !possibleGuest.value.isGuest) redirect(callbackUrl);
+ if (!possibleGuest.value?.isGuest) redirect(callbackUrl);
// NOTE: this ignores folders
const guestProcesses = await getProcesses(guestId);
diff --git a/src/management-system-v2/lib/data/db/process.ts b/src/management-system-v2/lib/data/db/process.ts
index d8977aa4b..70150ac1a 100644
--- a/src/management-system-v2/lib/data/db/process.ts
+++ b/src/management-system-v2/lib/data/db/process.ts
@@ -250,7 +250,7 @@ export async function _addProcess(
const folderData = await getFolderById(metadata.folderId);
if (folderData.isErr()) return folderData;
- if (!folderData) return err(new Error('Folder not found'));
+ if (!folderData.value) return err(new Error('Folder not found'));
// TODO check folder permissions here, they're checked in movefolder,
// but by then the folder was already created
@@ -351,12 +351,12 @@ export async function _updateProcess(
processDefinitionsId,
newFolderId: metaChanges.folderId,
});
- if (moveResult?.isErr()) return moveResult;
+ if (moveResult.isErr()) return moveResult;
//delete metaChanges.folderId;
}
const newMetaData = await updateProcessMetaData(processDefinitionsId, metaChanges);
- if (newMetaData?.isErr()) return newMetaData;
+ if (newMetaData.isErr()) return newMetaData;
if (newBpmn) {
try {
await db.process.update({
@@ -395,7 +395,7 @@ export async function moveProcess({
if (process.isErr()) {
return process;
}
- if (!process) {
+ if (!process.value) {
return err(new Error('Process not found'));
}
@@ -503,6 +503,8 @@ export async function _removeProcess(processDefinitionsId: string, _tx?: Prisma.
await tx.process.delete({ where: { id: processDefinitionsId } });
eventHandler.dispatch('processRemoved', { processDefinitionsId });
+
+ return ok();
} catch (error) {
console.error(error);
return err(error);
@@ -528,7 +530,7 @@ export async function addProcessVersion(
if (existingProcess.isErr()) {
return existingProcess;
}
- if (!existingProcess) {
+ if (!existingProcess.value) {
return err(new Error('The process for which you try to create a version does not exist'));
}
@@ -617,7 +619,7 @@ export async function addProcessVersion(
}
const versionResult = await versionProcessArtifactRefs(processDefinitionsId, version.id);
- if (versionResult?.isErr()) {
+ if (versionResult.isErr()) {
return versionResult;
}
} catch (error) {
@@ -1060,8 +1062,7 @@ export async function deleteProcessScriptTask(
return processExists;
}
- // Not sure what should be returned here
- if (!processExists.value) return;
+ if (!processExists.value) return ok(true);
try {
const res = await checkIfScriptTaskFileExists(processDefinitionsId, taskFileNameWithExtension);
@@ -1071,6 +1072,8 @@ export async function deleteProcessScriptTask(
if (res.value) {
return ok(await deleteProcessArtifact(res.value?.filePath, true));
}
+
+ return ok(true);
} catch (error) {
logger.debug(`Error removing script task file. Reason:\n${error}`);
return err(error);
@@ -1096,6 +1099,8 @@ export async function copyProcessArtifactReferences(
},
}),
);
+
+ return ok();
} catch (error) {
return err(new Error('error copying process artifact references'));
}
@@ -1117,6 +1122,8 @@ export async function versionProcessArtifactRefs(processId: string, versionId: s
},
}),
);
+
+ return ok();
} catch (error) {
return err(new Error('error copying process artifact references'));
}
diff --git a/src/management-system-v2/lib/data/db/space-settings.ts b/src/management-system-v2/lib/data/db/space-settings.ts
index f742d4d16..5351e8dfb 100644
--- a/src/management-system-v2/lib/data/db/space-settings.ts
+++ b/src/management-system-v2/lib/data/db/space-settings.ts
@@ -50,7 +50,7 @@ export async function populateSpaceSettingsGroup(
where: { environmentId: spaceId },
});
- if (!settings) return;
+ if (!settings) return ok();
Object.entries(settings.settings as Record).forEach(([key, value]) => {
const path = key.split('.');
diff --git a/src/management-system-v2/lib/data/html-forms.ts b/src/management-system-v2/lib/data/html-forms.ts
index 1dbfa68f8..7b3c2deb8 100644
--- a/src/management-system-v2/lib/data/html-forms.ts
+++ b/src/management-system-v2/lib/data/html-forms.ts
@@ -90,7 +90,7 @@ export const updateHtmlForm = async (formId: string, newData: Partial)
export const removeHtmlForms = async (formIds: string[]) => {
try {
const result = await _removeHtmlForms(formIds);
- if (result && result.isErr()) {
+ if (result.isErr()) {
return userError(getErrorMessage(result.error));
}
} catch (err) {
diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx
index 5ae69cd6b..777b38bbf 100644
--- a/src/management-system-v2/lib/data/processes.tsx
+++ b/src/management-system-v2/lib/data/processes.tsx
@@ -69,7 +69,6 @@ import { saveProcessArtifact } from './file-manager-facade';
import { getRootFolder } from './db/folders';
import { truthyFilter } from '../typescript-utils';
import { Result, ok } from 'neverthrow';
-import { isSupportedCountry } from 'libphonenumber-js';
// FIXME: Check abilities
@@ -198,7 +197,7 @@ export const deleteProcesses = async (definitionIds: string[], spaceId: string)
if (error) return error;
const result = await removeProcess(definitionId);
- if (result && result.isErr()) return userError(getErrorMessage(result.error));
+ if (result.isErr()) return userError(getErrorMessage(result.error));
}
};
@@ -620,7 +619,7 @@ export const createVersion = async (
const bpmn = await _getProcessBpmn(processId);
if (bpmn.isErr()) return userError(getErrorMessage(bpmn.error));
- if (!bpmn) return null;
+ if (!bpmn.value) return null;
const bpmnObj = await toBpmnObject(bpmn.value);
diff --git a/src/management-system-v2/lib/email-verification-tokens/server-actions.ts b/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
index b1f6a7737..09dfab366 100644
--- a/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
+++ b/src/management-system-v2/lib/email-verification-tokens/server-actions.ts
@@ -73,9 +73,9 @@ export async function changeEmail(token: string, identifier: string, cancel: boo
}
if (
- !verificationToken ||
- verificationToken.value?.type !== 'change_email' ||
- verificationToken.value?.userId !== userId ||
+ !verificationToken.value ||
+ verificationToken.value.type !== 'change_email' ||
+ verificationToken.value.userId !== userId ||
!(await notExpired(verificationToken.value))
)
return userError('Invalid token');
From 49819b703f433fa4c713e4265fef8267662d15b8 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:28:39 +0100
Subject: [PATCH 19/43] Fixed: the check for getUserById returning a falsy
value will always be false since the function always returns a Result
---
src/management-system-v2/instrumentation.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/management-system-v2/instrumentation.ts b/src/management-system-v2/instrumentation.ts
index 7db70a4c0..a8ac6433f 100644
--- a/src/management-system-v2/instrumentation.ts
+++ b/src/management-system-v2/instrumentation.ts
@@ -8,7 +8,8 @@ export async function register() {
// Register default admin user if IAM is not activated
const { userId, createUserArgs } = await import('./lib/no-iam-user');
const { addUser, getUserById } = await import('./lib/data/db/iam/users');
- if (!env.PROCEED_PUBLIC_IAM_ACTIVE && !(await getUserById(userId)))
+ const user = await getUserById(userId);
+ if (!env.PROCEED_PUBLIC_IAM_ACTIVE && (user.isErr() || !user.value))
await addUser(createUserArgs);
// Create personal spaces for users that don't have one
From 8ac1bf8cf6f6969dee0ba263790a5a558a805f28 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 27 Jan 2026 19:29:40 +0100
Subject: [PATCH 20/43] Fixed: the retry button that is shown on some pages
when something fails has no text
---
.../lib/server-error-handling/retry-button.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/management-system-v2/lib/server-error-handling/retry-button.tsx b/src/management-system-v2/lib/server-error-handling/retry-button.tsx
index 76cb73bba..3ae906cc2 100644
--- a/src/management-system-v2/lib/server-error-handling/retry-button.tsx
+++ b/src/management-system-v2/lib/server-error-handling/retry-button.tsx
@@ -6,5 +6,9 @@ import { useRouter } from 'next/navigation';
export default function RetryButton(props: ButtonProps) {
const router = useRouter();
- return
>
@@ -163,8 +163,9 @@ const SignIn: FC<{
color: '#434343',
}}
>
- By using the this Platform, you agree to the Terms of Service{' '}
- and the storage of functionally essential cookies on your device.
+ By using the PROCEED Platform, you agree to the{' '}
+ Terms of Service and the storage of functionally essential
+ cookies on your device.
From 93a49d854f746ba1ba8c1d02e42cc0383386a4e9 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 14:35:45 +0100
Subject: [PATCH 28/43] Small comment improvement
---
src/management-system-v2/app/(auth)/signin/page.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/management-system-v2/app/(auth)/signin/page.tsx b/src/management-system-v2/app/(auth)/signin/page.tsx
index 34eda7661..43901c6a2 100644
--- a/src/management-system-v2/app/(auth)/signin/page.tsx
+++ b/src/management-system-v2/app/(auth)/signin/page.tsx
@@ -14,7 +14,8 @@ const SignInPage = async (props: { searchParams: Promise<{ callbackUrl: string }
const searchParams = await props.searchParams;
const currentUser = await getCurrentUser();
if (currentUser.isErr()) {
- // this should be handled in the layout in the parent folder already
+ // this shouldn't really occur since it is handled in the layout file in the parent folder
+ // already
return notFound();
}
const { session } = currentUser.value;
From cd2087241016d317df8a7daefa906594ea09b12c Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 14:38:16 +0100
Subject: [PATCH 29/43] Fixed: the MS cannot be built due to an unused
typescript error directive leading to a typescript error
---
src/management-system-v2/components/share-modal/embed-in-web.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/management-system-v2/components/share-modal/embed-in-web.tsx b/src/management-system-v2/components/share-modal/embed-in-web.tsx
index d03521dc7..cf1447032 100644
--- a/src/management-system-v2/components/share-modal/embed-in-web.tsx
+++ b/src/management-system-v2/components/share-modal/embed-in-web.tsx
@@ -135,7 +135,6 @@ const ModelerShareModalOptionEmdedInWeb = ({
From 7d9bffea93903f494e2d339dba7df1c21590f7f5 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 16:08:51 +0100
Subject: [PATCH 30/43] Fixed: if the executions view encounters an error while
loading the header page header bar is displayed twice
---
.../(automation)/executions/page.tsx | 20 +++++++++----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx
index ad8e18e17..40b8060d8 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/page.tsx
@@ -92,12 +92,14 @@ async function Executions({ environmentId }: { environmentId: string }) {
const deployedProcesses = getDeploymentNames(deployedWithRemappedIds);
return (
-
+
+
+
);
}
@@ -105,9 +107,5 @@ export default async function ExecutionsPage(props: {
params: Promise<{ environmentId: string }>;
}) {
const params = await props.params;
- return (
-
-
-
- );
+ return ;
}
From 64fc1ed14a3b1ad32d7c1caacac7e693325b6309 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 16:58:50 +0100
Subject: [PATCH 31/43] Role Page returns neverthrow error when there is an
error accesing the parent folder
---
.../app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx
index 14e3ea5f7..8731ff9e7 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx
@@ -71,7 +71,7 @@ const Page = async (props: { params: Promise<{ roleId: string; environmentId: st
? await getFolderById(role.value.parentId, ability)
: undefined;
if (roleParentFolder && roleParentFolder.isErr()) {
- return roleParentFolder;
+ return errorResponse(roleParentFolder);
}
const tabs = [
From 50016f184bf0149d2beda1398d0ab970d3a44162 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 16:59:15 +0100
Subject: [PATCH 32/43] Small improvements
---
src/management-system-v2/lib/data/db/iam/memberships.ts | 2 ++
src/management-system-v2/lib/data/db/iam/role-mappings.ts | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/management-system-v2/lib/data/db/iam/memberships.ts b/src/management-system-v2/lib/data/db/iam/memberships.ts
index 53400e674..6a7c65b10 100644
--- a/src/management-system-v2/lib/data/db/iam/memberships.ts
+++ b/src/management-system-v2/lib/data/db/iam/memberships.ts
@@ -178,6 +178,8 @@ export async function addMember(
createdOn: new Date().toISOString(),
},
});
+
+ return ok();
}
export const removeMember = ensureTransactionWrapper(_removeMember, 2);
diff --git a/src/management-system-v2/lib/data/db/iam/role-mappings.ts b/src/management-system-v2/lib/data/db/iam/role-mappings.ts
index 0ce141dd2..9729ca2c7 100644
--- a/src/management-system-v2/lib/data/db/iam/role-mappings.ts
+++ b/src/management-system-v2/lib/data/db/iam/role-mappings.ts
@@ -188,6 +188,8 @@ export async function _addRoleMappings(
},
});
}
+
+ return ok();
}
// TODO: also check if user exists?
From db1affc1f0e02afab1a2bdc5cac98bedbdb77c61 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 17:04:32 +0100
Subject: [PATCH 33/43] Fixed: Trying to invite users to an organization will
silently fail
---
.../lib/data/environment-memberships.ts | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/management-system-v2/lib/data/environment-memberships.ts b/src/management-system-v2/lib/data/environment-memberships.ts
index 880e4756f..eb2de5d57 100644
--- a/src/management-system-v2/lib/data/environment-memberships.ts
+++ b/src/management-system-v2/lib/data/environment-memberships.ts
@@ -108,9 +108,10 @@ export async function inviteUsersToEnvironment(
// NOTE: technically not possible as guests cannot have an email or username
if (invitedUser?.isGuest) continue;
- if (invitedUser && (await isMember(environmentId, invitedUser.id))) continue;
-
if (invitedUser) {
+ const userIsMember = await isMember(environmentId, invitedUser.id);
+ if (userIsMember.isErr() || userIsMember.value) continue;
+
invitedUserEmail = invitedUser.email;
} else if ('email' in invitedUserIdentifier) {
invitedUserEmail = invitedUserIdentifier.email;
@@ -171,7 +172,8 @@ export async function removeUsersFromEnvironment(environmentId: string, userIdsI
);
for (const userId of userIds) {
- await removeMember(environmentId, userId, ability);
+ const res = await removeMember(environmentId, userId, ability);
+ if (res.isErr()) return userError('Error removing users from environment');
}
} catch (_) {
return userError('Error removing users from environment');
@@ -226,7 +228,7 @@ export async function createUserAndAddToOrganization(
if (passwordResult.isErr()) throw passwordResult.error;
const memberResult = await addMember(organizationId, user.id, ability, tx);
- if (memberResult?.isErr()) throw memberResult.error;
+ if (memberResult.isErr()) throw memberResult.error;
const roleMappingsResult = await addRoleMappings(
roles.map((roleId) => ({
@@ -237,7 +239,7 @@ export async function createUserAndAddToOrganization(
ability,
tx,
);
- if (roleMappingsResult?.isErr()) throw roleMappingsResult.error;
+ if (roleMappingsResult.isErr()) throw roleMappingsResult.error;
});
} catch (error) {
return userError(getErrorMessage(error));
From a13a7379c59c4cd78a2fb56da5c9ef0ed06332b3 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 17:21:08 +0100
Subject: [PATCH 34/43] Fixed: trying to update a role will result in an error
---
src/management-system-v2/lib/data/db/iam/roles.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/management-system-v2/lib/data/db/iam/roles.ts b/src/management-system-v2/lib/data/db/iam/roles.ts
index c3507347a..4ca2b9633 100644
--- a/src/management-system-v2/lib/data/db/iam/roles.ts
+++ b/src/management-system-v2/lib/data/db/iam/roles.ts
@@ -255,7 +255,11 @@ export async function updateRole(
// Casl isn't really built to check the value of input fields when updating, so we have to perform this two checks
if (
!(
- ability.checkInputFields(toCaslResource('Role', targetRole), 'update', roleRepresentation) &&
+ ability.checkInputFields(
+ toCaslResource('Role', targetRole.value),
+ 'update',
+ roleRepresentation,
+ ) &&
ability.can('create', toCaslResource('Role', roleRepresentation), {
environmentId: targetRole.value.environmentId,
})
From e403f055c52e8bb3e89468741c18600d25e5b60e Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 17:26:52 +0100
Subject: [PATCH 35/43] Fixed typo
---
src/management-system-v2/lib/data/users.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/management-system-v2/lib/data/users.tsx b/src/management-system-v2/lib/data/users.tsx
index 8970b4628..a35d81d53 100644
--- a/src/management-system-v2/lib/data/users.tsx
+++ b/src/management-system-v2/lib/data/users.tsx
@@ -150,7 +150,7 @@ export async function setUserPassword(newPassword: string) {
}
// To avoid this endpoint from being abused there's not much we can do, but we do the following:
-// - Encoforce the user to be an admin of an org
+// - Enforce the user to be an admin of an org
// - Search query has to be at least 4 characters long
// - We only return 10 users
export async function queryUsers(organizationId: string, searchQuery: string) {
From 78e4d6690d30e7270fb0cb1407e848758d39add2 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 17:39:20 +0100
Subject: [PATCH 36/43] Fixed: When there is an error loading the organization
management page or if the user is not allowed to see it the table of contents
of the page is still loaded next to the error screen
---
.../[environmentId]/management/layout.tsx | 18 ++++++++++++++++--
.../lib/data/db/iam/environments.ts | 3 +++
.../lib/data/environments.ts | 2 +-
3 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/management/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/management/layout.tsx
index 497095652..d5129ecb9 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/management/layout.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/management/layout.tsx
@@ -1,13 +1,27 @@
import Content from '@/components/content';
import SettingsPage from '../settings/settings-page';
+import { getCurrentEnvironment } from '@/components/auth';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
+import { UnauthorizedError } from '@/lib/ability/abilityHelper';
+import { err } from 'neverthrow';
-export default function Layout({
+export default async function Layout({
params,
...children
}: {
params: Promise<{ environmentId: string }>;
}) {
- // TODO: check if the user has the rights to change the settings
+ const currentSpace = await getCurrentEnvironment((await params).environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
+ const { ability, activeEnvironment } = currentSpace.value;
+ if (
+ !activeEnvironment.isOrganization ||
+ (!ability.can('update', 'Environment') && !ability.can('delete', 'Environment'))
+ ) {
+ return errorResponse(err(new UnauthorizedError()));
+ }
return (
diff --git a/src/management-system-v2/lib/data/db/iam/environments.ts b/src/management-system-v2/lib/data/db/iam/environments.ts
index 0c71917ab..1afb1fe08 100644
--- a/src/management-system-v2/lib/data/db/iam/environments.ts
+++ b/src/management-system-v2/lib/data/db/iam/environments.ts
@@ -202,9 +202,12 @@ export async function deleteEnvironment(environmentId: string, ability?: Ability
}
if (ability && !ability.can('delete', 'Environment')) return err(new UnauthorizedError());
+
await db.space.delete({
where: { id: environmentId },
});
+
+ return ok();
}
//TODO organisation logo in db logic
diff --git a/src/management-system-v2/lib/data/environments.ts b/src/management-system-v2/lib/data/environments.ts
index a35c7bdbf..55d692028 100644
--- a/src/management-system-v2/lib/data/environments.ts
+++ b/src/management-system-v2/lib/data/environments.ts
@@ -77,7 +77,7 @@ export async function deleteOrganizationEnvironments(environmentIds: string[]) {
return userError(`Environment ${environmentId} is not an organization environment`);
const deleteResult = await deleteEnvironment(environmentId, ability);
- if (deleteResult?.isErr()) {
+ if (deleteResult.isErr()) {
return userError(getErrorMessage(deleteResult.error));
}
}
From ead78d1f42f291807aaab8d2bc5f8374d4bceaf0 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 19:09:40 +0100
Subject: [PATCH 37/43] Fixed: Versioning processes with script tasks does not
work
---
.../script-task-editor/script-task-editor.tsx | 10 +-----
.../lib/helpers/processVersioning.ts | 32 +++++++++----------
2 files changed, 17 insertions(+), 25 deletions(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-task-editor/script-task-editor.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-task-editor/script-task-editor.tsx
index 31c7477eb..bdc0a17b6 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-task-editor/script-task-editor.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[mode]/[processId]/script-task-editor/script-task-editor.tsx
@@ -1,14 +1,6 @@
'use client';
-import {
- useCallback,
- useEffect,
- useImperativeHandle,
- useMemo,
- useRef,
- useState,
- forwardRef,
-} from 'react';
+import { useEffect, useImperativeHandle, useMemo, useRef, useState, forwardRef } from 'react';
import dynamic from 'next/dynamic';
import {
Button,
diff --git a/src/management-system-v2/lib/helpers/processVersioning.ts b/src/management-system-v2/lib/helpers/processVersioning.ts
index d5620d7ca..d752a5a3a 100644
--- a/src/management-system-v2/lib/helpers/processVersioning.ts
+++ b/src/management-system-v2/lib/helpers/processVersioning.ts
@@ -267,26 +267,26 @@ export async function versionScriptTasks(
try {
const scriptTaskTS = await getProcessScriptTaskScript(processInfo.id, fileName + '.ts');
- if (scriptTaskTS.isErr()) return userError(getErrorMessage(scriptTaskTS.error));
-
- await saveProcessScriptTask(
- processInfo.id,
- versionFileName + '.ts',
- scriptTaskTS.value,
- versionCreatedOn,
- );
+ if (scriptTaskTS.isOk()) {
+ await saveProcessScriptTask(
+ processInfo.id,
+ versionFileName + '.ts',
+ scriptTaskTS.value,
+ versionCreatedOn,
+ );
+ }
} catch (err) {}
try {
const scriptTaskXML = await getProcessScriptTaskScript(processInfo.id, fileName + '.xml');
- if (scriptTaskXML.isErr()) return userError(getErrorMessage(scriptTaskXML.error));
-
- await saveProcessScriptTask(
- processInfo.id,
- versionFileName + '.xml',
- scriptTaskXML.value,
- versionCreatedOn,
- );
+ if (scriptTaskXML.isOk()) {
+ await saveProcessScriptTask(
+ processInfo.id,
+ versionFileName + '.xml',
+ scriptTaskXML.value,
+ versionCreatedOn,
+ );
+ }
} catch (err) {}
}
From 3625b39bf98d01581b7a13137fe3e2cb1cff8169 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 19:18:21 +0100
Subject: [PATCH 38/43] Fixed: leaving a space is not possible
---
src/management-system-v2/lib/data/db/iam/memberships.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/management-system-v2/lib/data/db/iam/memberships.ts b/src/management-system-v2/lib/data/db/iam/memberships.ts
index 6a7c65b10..5bd69a630 100644
--- a/src/management-system-v2/lib/data/db/iam/memberships.ts
+++ b/src/management-system-v2/lib/data/db/iam/memberships.ts
@@ -182,7 +182,7 @@ export async function addMember(
return ok();
}
-export const removeMember = ensureTransactionWrapper(_removeMember, 2);
+export const removeMember = ensureTransactionWrapper(_removeMember, 3);
async function _removeMember(
environmentId: string,
userId: string,
From 9dedacfbb288dc9ddbc3b06b775f8fdd6083a272 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 19:19:06 +0100
Subject: [PATCH 39/43] Catch an error with accesing the environment in the
layout of the setting page instead of the specific setting to avoid rendering
the sidebar overview
---
.../app/(dashboard)/[environmentId]/settings/layout.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/layout.tsx
index e0802ecd3..b546b0864 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/settings/layout.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/settings/layout.tsx
@@ -2,14 +2,20 @@ import React from 'react';
import Content from '@/components/content';
import SettingsPage from './settings-page';
+import { getCurrentEnvironment } from '@/components/auth';
+import { errorResponse } from '@/lib/server-error-handling/page-error-response';
-export default function Layout({
+export default async function Layout({
params,
...children
}: {
params: Promise<{ environmentId: string }>;
}) {
// TODO: check if the user has the rights to change the settings
+ const currentSpace = await getCurrentEnvironment((await params).environmentId);
+ if (currentSpace.isErr()) {
+ return errorResponse(currentSpace);
+ }
return (
From 02976585b683859773c9e93cd4f977ca8a1e3d20 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 19:39:00 +0100
Subject: [PATCH 40/43] Fixed: When clicking on a row in the task editor table
to navigate to a specific task form inside a space the url is not constructed
correctly
---
.../app/(dashboard)/[environmentId]/tasks/form-list.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/form-list.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/form-list.tsx
index 97c96a8ed..754c90d28 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/form-list.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/tasks/form-list.tsx
@@ -66,7 +66,7 @@ const FormListEntryLink: React.FC<
}>
> = ({ children, data, style, className }) => {
return (
-
+
{children}
);
From ab85c6ea6baa7d789f734d3a2cef2c1bb6448e7d Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Fri, 30 Jan 2026 19:48:00 +0100
Subject: [PATCH 41/43] Fixed: getting the html for a local task leads to an
error
---
src/management-system-v2/lib/engines/server-actions.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/management-system-v2/lib/engines/server-actions.ts b/src/management-system-v2/lib/engines/server-actions.ts
index fd7043fc8..04ee953c2 100644
--- a/src/management-system-v2/lib/engines/server-actions.ts
+++ b/src/management-system-v2/lib/engines/server-actions.ts
@@ -274,7 +274,6 @@ export async function getTasklistEntryHTML(spaceId: string, userTaskId: string,
state: storedState,
} = storedUserTask;
const [taskId, instanceId, startTimeString] = userTaskId.split('|');
- const [definitionId] = instanceId.split('-_');
if (!html || !milestones || !initialVariables) {
const startTime = parseInt(startTimeString);
@@ -361,7 +360,8 @@ export async function getTasklistEntryHTML(spaceId: string, userTaskId: string,
// maps relative urls used to get resources on the engine to the MS api to allow them to work here as well
function mapResourceUrls(variables: Record) {
- if (!variables) return variables;
+ if (!instanceId || !variables) return variables;
+ const definitionId = instanceId.split('-_')[0];
return Object.fromEntries(
Object.entries(variables).map(([key, value]) => {
From a64bba07066ae8f940e9b189c8c6c9187e76c48d Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Mon, 2 Feb 2026 10:56:51 +0100
Subject: [PATCH 42/43] Small improvements
---
.../app/api/activateenvironment/route.ts | 6 +++++-
.../app/api/private/delete-inactive-guests/route.ts | 4 ++--
src/management-system-v2/lib/auth.ts | 9 ++++-----
src/management-system-v2/lib/data/db/iam/environments.ts | 3 +++
4 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/src/management-system-v2/app/api/activateenvironment/route.ts b/src/management-system-v2/app/api/activateenvironment/route.ts
index 2f77433bc..c1a2e0dad 100644
--- a/src/management-system-v2/app/api/activateenvironment/route.ts
+++ b/src/management-system-v2/app/api/activateenvironment/route.ts
@@ -17,7 +17,11 @@ export const GET = async (req: Request) => {
if (!activationId)
return Response.json({ message: 'No activationId provided' }, { status: 400 });
- await activateEnvrionment(activationId, session.user.id);
+ const res = await activateEnvrionment(activationId, session.user.id);
+
+ if (res.isErr()) {
+ return Response.json({ message: 'Error activating environment' }, { status: 500 });
+ }
} catch (e) {
console.error(e);
return Response.json({ message: 'Error activating environment' }, { status: 500 });
diff --git a/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts b/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
index 53df21f17..5eded320a 100644
--- a/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
+++ b/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import { deleteInactiveGuestUsers } from '@/lib/data/db/iam/users';
// TODO: get this time from the database
-const GUESET_INACTIVE_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days
+const GUEST_INACTIVE_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days
export async function POST(request: NextRequest) {
try {
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized: Invalid Bearer token' }, { status: 403 });
}
- const result = await deleteInactiveGuestUsers(GUESET_INACTIVE_TIME);
+ const result = await deleteInactiveGuestUsers(GUEST_INACTIVE_TIME);
if (result.isErr()) throw result.error;
return NextResponse.json(
diff --git a/src/management-system-v2/lib/auth.ts b/src/management-system-v2/lib/auth.ts
index 69c1df317..900f4582d 100644
--- a/src/management-system-v2/lib/auth.ts
+++ b/src/management-system-v2/lib/auth.ts
@@ -127,9 +127,8 @@ const nextAuthOptions: NextAuthConfig = {
if (!token.user.isGuest) return;
const user = await getUserById(token.user.id);
- if (user.isErr()) {
- throw user.error;
- }
+ if (user.isErr()) throw user.error;
+
if (user.value) {
if (!user.value.isGuest) {
console.warn('User with invalid session');
@@ -350,7 +349,6 @@ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE) {
}
if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE || env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
- //Vorname, Nachname und Username input feldern,
const credentials: Record = {
firstName: {
type: 'string',
@@ -431,7 +429,8 @@ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE || env.PROCEED_PUBLIC_IAM_
const userRegistrationToken = await createUserRegistrationToken(tokenParams, callbackUrl);
- await saveEmailVerificationToken(userRegistrationToken.verificationToken);
+ const res = await saveEmailVerificationToken(userRegistrationToken.verificationToken);
+ if (res.isErr()) throw new Error('Something went wrong');
const signinMail = renderSigninLinkEmail({
signInLink: userRegistrationToken.redirectUrl,
diff --git a/src/management-system-v2/lib/data/db/iam/environments.ts b/src/management-system-v2/lib/data/db/iam/environments.ts
index 1afb1fe08..6d92c3a56 100644
--- a/src/management-system-v2/lib/data/db/iam/environments.ts
+++ b/src/management-system-v2/lib/data/db/iam/environments.ts
@@ -18,6 +18,7 @@ import db from '@/lib/data/db';
import { Prisma } from '@prisma/client';
import { env } from '@/lib/ms-config/env-vars';
import { ensureTransactionWrapper } from '../util';
+import { on } from 'node:events';
export async function getEnvironments() {
//TODO : Ability check
@@ -88,6 +89,8 @@ export async function activateEnvrionment(environmentId: string, userId: string)
tx,
);
});
+
+ return ok();
}
export const addEnvironment = ensureTransactionWrapper(_addEnvironment, 2);
From 00eee2bf576f2ff3ff1ba796f602d164023a61d5 Mon Sep 17 00:00:00 2001
From: Janis Joderi Shoferi
Date: Tue, 3 Feb 2026 08:17:44 +0100
Subject: [PATCH 43/43] Extended error handling to more places
---
.../app/admin/systemadmins/page.tsx | 3 ++-
.../app/api/activateenvironment/route.ts | 4 ++--
src/management-system-v2/components/auth.tsx | 10 ++++++----
.../organizationEnvironmentRolesHelper.ts | 5 +++--
.../lib/custom-links/server-actions.tsx | 5 ++++-
src/management-system-v2/lib/data/db/engines.ts | 6 ++++--
src/management-system-v2/lib/data/db/html-forms.ts | 2 ++
.../lib/data/db/iam/environments.ts | 14 +++++++++-----
.../lib/data/db/iam/system-admins.ts | 2 ++
src/management-system-v2/lib/data/environments.ts | 6 ++++--
src/management-system-v2/lib/data/folders.ts | 3 ++-
src/management-system-v2/lib/data/html-forms.ts | 2 +-
src/management-system-v2/lib/data/processes.tsx | 3 ++-
src/management-system-v2/lib/data/role-mappings.ts | 3 ++-
src/management-system-v2/lib/data/roles.ts | 3 ++-
src/management-system-v2/lib/db-seed.ts | 7 +++++--
.../lib/helpers/processVersioning.ts | 7 +++++--
src/management-system-v2/lib/invitation-tokens.ts | 4 ++--
18 files changed, 59 insertions(+), 30 deletions(-)
diff --git a/src/management-system-v2/app/admin/systemadmins/page.tsx b/src/management-system-v2/app/admin/systemadmins/page.tsx
index db8148687..0474caea3 100644
--- a/src/management-system-v2/app/admin/systemadmins/page.tsx
+++ b/src/management-system-v2/app/admin/systemadmins/page.tsx
@@ -35,7 +35,8 @@ async function deleteAdmins(userIds: string[]) {
}
if (!adminMapping.value) return userError('Admin not found');
- deleteSystemAdmin(adminMapping.value.id);
+ const res = await deleteSystemAdmin(adminMapping.value.id);
+ if (res.isErr()) return userError('Failed to remove admin rights from the user');
}
} catch (e) {
return userError('Something went wrong');
diff --git a/src/management-system-v2/app/api/activateenvironment/route.ts b/src/management-system-v2/app/api/activateenvironment/route.ts
index c1a2e0dad..238ed8e28 100644
--- a/src/management-system-v2/app/api/activateenvironment/route.ts
+++ b/src/management-system-v2/app/api/activateenvironment/route.ts
@@ -1,5 +1,5 @@
import { auth } from '@/lib/auth';
-import { activateEnvrionment } from '@/lib/data/db/iam/environments';
+import { activateEnvironment } from '@/lib/data/db/iam/environments';
import { UnauthorizedError } from '@/lib/ability/abilityHelper';
import { redirect } from 'next/navigation';
@@ -17,7 +17,7 @@ export const GET = async (req: Request) => {
if (!activationId)
return Response.json({ message: 'No activationId provided' }, { status: 400 });
- const res = await activateEnvrionment(activationId, session.user.id);
+ const res = await activateEnvironment(activationId, session.user.id);
if (res.isErr()) {
return Response.json({ message: 'Error activating environment' }, { status: 500 });
diff --git a/src/management-system-v2/components/auth.tsx b/src/management-system-v2/components/auth.tsx
index 8e060b8bc..3b7d858b4 100644
--- a/src/management-system-v2/components/auth.tsx
+++ b/src/management-system-v2/components/auth.tsx
@@ -116,9 +116,9 @@ export const getCurrentEnvironment = cache(
if (userId && !isOrganization && !env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
// Note: will be undefined for not logged in users
const userOrgs = await getUserOrganizationEnvironments(userId);
- // if (userOrgs.isErr()) {
- // return userOrgs;
- // }
+ if (userOrgs.isErr()) {
+ return userOrgs;
+ }
if (userOrgs.value.length === 0) {
if (env.PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE) {
@@ -143,7 +143,9 @@ export const getCurrentEnvironment = cache(
});
}
- if (!userId || !isMember(decodeURIComponent(spaceIdParam), userId)) {
+ const checkIsMember = await isMember(decodeURIComponent(spaceIdParam), userId);
+ if (checkIsMember.isErr()) return checkIsMember;
+ if (!userId || !checkIsMember.value) {
switch (opts?.permissionErrorHandling.action) {
case 'throw-error':
return err(new Error('User does not have access to this environment'));
diff --git a/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts b/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts
index cec11005b..a53062b13 100644
--- a/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts
+++ b/src/management-system-v2/lib/authorization/organizationEnvironmentRolesHelper.ts
@@ -8,8 +8,9 @@ import { truthyFilter } from '../typescript-utils';
/** Returns all roles that are applied to a user in a given organization environment */
export async function getAppliedRolesForUser(userId: string, environmentId: string) {
// enforces environment to be an organization
- if (!isMember(environmentId, userId))
- return err(new Error('User is not a member of this environment'));
+ const checkIsMember = await isMember(environmentId, userId);
+ if (checkIsMember.isErr()) return checkIsMember;
+ if (!checkIsMember.value) return err(new Error('User is not a member of this environment'));
const environmentRoles = await getRoles(environmentId);
if (environmentRoles.isErr()) {
diff --git a/src/management-system-v2/lib/custom-links/server-actions.tsx b/src/management-system-v2/lib/custom-links/server-actions.tsx
index e25446fe0..195acf2a5 100644
--- a/src/management-system-v2/lib/custom-links/server-actions.tsx
+++ b/src/management-system-v2/lib/custom-links/server-actions.tsx
@@ -9,7 +9,10 @@ import { getErrorMessage, userError } from '../server-error-handling/user-error'
export async function getCustomLinksStatus(spaceId: string) {
// Check that the user is a member of the space
- getCurrentEnvironment(spaceId);
+ const res = await getCurrentEnvironment(spaceId);
+ if (res.isErr()) {
+ return userError(getErrorMessage(res.error));
+ }
const generalSettings = await getSpaceSettingsValues(spaceId, 'general-settings');
if (generalSettings.isErr()) {
diff --git a/src/management-system-v2/lib/data/db/engines.ts b/src/management-system-v2/lib/data/db/engines.ts
index f411eca86..a8ee77ad5 100644
--- a/src/management-system-v2/lib/data/db/engines.ts
+++ b/src/management-system-v2/lib/data/db/engines.ts
@@ -119,7 +119,8 @@ export async function updateDbEngine(
if (ability) {
const engine = await getDbEngineById(engineId, environmentId, ability, systemAdmin);
- if (!engine) return err(new Error('Engine not found'));
+ if (engine.isErr()) return engine;
+ if (!engine.value) return err(new Error('Engine not found'));
if (
!ability.can('update', toCaslResource('Machine', engine), { environmentId: environmentId! })
)
@@ -149,7 +150,8 @@ export async function deleteSpaceEngine(
if (ability) {
const engine = await getDbEngineById(engineId, environmentId, ability, systemAdmin);
- if (!engine) return err(new Error('Engine not found'));
+ if (engine.isErr()) return engine;
+ if (!engine.value) return err(new Error('Engine not found'));
if (
!ability.can('delete', toCaslResource('Machine', engine), { environmentId: environmentId! })
)
diff --git a/src/management-system-v2/lib/data/db/html-forms.ts b/src/management-system-v2/lib/data/db/html-forms.ts
index 13af37c89..ca41b894c 100644
--- a/src/management-system-v2/lib/data/db/html-forms.ts
+++ b/src/management-system-v2/lib/data/db/html-forms.ts
@@ -121,6 +121,8 @@ export async function updateHtmlForm(formId: string, newInfoInput: Partial) => {
try {
const result = await _updateHtmlForm(formId, newData);
- if (result && result.isErr()) {
+ if (result.isErr()) {
return userError(getErrorMessage(result.error));
}
revalidatePath(`/tasks/${formId}`);
diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx
index d6c477aa1..a7fe8e338 100644
--- a/src/management-system-v2/lib/data/processes.tsx
+++ b/src/management-system-v2/lib/data/processes.tsx
@@ -353,7 +353,8 @@ export const updateProcess = async (
revalidatePath(`/processes/editor/${definitionsId}`);
}
- await _updateProcess(definitionsId, { bpmn: newBpmn });
+ const res = await _updateProcess(definitionsId, { bpmn: newBpmn });
+ if (res.isErr()) return userError(getErrorMessage(res.error));
};
export const updateProcessMetaData = async (
diff --git a/src/management-system-v2/lib/data/role-mappings.ts b/src/management-system-v2/lib/data/role-mappings.ts
index 4a72fcb4a..238557852 100644
--- a/src/management-system-v2/lib/data/role-mappings.ts
+++ b/src/management-system-v2/lib/data/role-mappings.ts
@@ -46,7 +46,8 @@ export async function deleteRoleMappings(
for (const { userId, roleId } of roleMappings) {
try {
- await _deleteRoleMapping(userId, roleId, activeEnvironment.spaceId, ability);
+ const res = await _deleteRoleMapping(userId, roleId, activeEnvironment.spaceId, ability);
+ if (res.isErr()) return userError(getErrorMessage(res.error));
} catch (error) {
console.error(error);
errors.push(error);
diff --git a/src/management-system-v2/lib/data/roles.ts b/src/management-system-v2/lib/data/roles.ts
index bdb3d37e8..87e8fe9b5 100644
--- a/src/management-system-v2/lib/data/roles.ts
+++ b/src/management-system-v2/lib/data/roles.ts
@@ -20,7 +20,8 @@ export async function deleteRoles(envitonmentId: string, roleIds: string[]) {
try {
for (const roleId of roleIds) {
- await deleteRole(roleId, ability);
+ const res = await deleteRole(roleId, ability);
+ if (res.isErr()) return userError(getErrorMessage(res.error));
}
} catch (e) {
if (e instanceof UnauthorizedError)
diff --git a/src/management-system-v2/lib/db-seed.ts b/src/management-system-v2/lib/db-seed.ts
index 0b90583bf..4804920b6 100644
--- a/src/management-system-v2/lib/db-seed.ts
+++ b/src/management-system-v2/lib/db-seed.ts
@@ -269,8 +269,11 @@ async function writeSeedToDb(seed: DBSeed) {
const userRoleMappings = new Map();
for (const member of organization.members) {
const memberId = usernameToId.get(member)!;
- if (!(await isMember(org.id, memberId, tx))) {
- await addMember(org.id, memberId, undefined, tx);
+ const checkIsMember = await isMember(org.id, memberId, tx);
+ if (checkIsMember.isErr()) return checkIsMember;
+ if (!checkIsMember.value) {
+ const res = await addMember(org.id, memberId, undefined, tx);
+ if (res.isErr()) return res;
}
// get members role mappings
diff --git a/src/management-system-v2/lib/helpers/processVersioning.ts b/src/management-system-v2/lib/helpers/processVersioning.ts
index d752a5a3a..ed3e1eadf 100644
--- a/src/management-system-v2/lib/helpers/processVersioning.ts
+++ b/src/management-system-v2/lib/helpers/processVersioning.ts
@@ -164,13 +164,15 @@ export async function versionStartForm(
const startFormData = await getProcessHtmlFormJSON(processInfo.id, fileName);
if (startFormData.isErr()) return userError(getErrorMessage(startFormData.error));
- await saveProcessHtmlForm(
+ const res = await saveProcessHtmlForm(
processInfo.id,
versionFileName,
startFormData.value!,
startFormHtml.value!,
versionCreatedOn,
);
+
+ if (res.isErr()) return userError(getErrorMessage(res.error));
}
// update ref for the artifacts referenced by the versioned start form
@@ -213,13 +215,14 @@ export async function versionUserTasks(
const userTaskData = await getProcessHtmlFormJSON(processInfo.id, fileName);
if (userTaskData.isErr()) return userError(getErrorMessage(userTaskData.error));
- await saveProcessHtmlForm(
+ const res = await saveProcessHtmlForm(
processInfo.id,
versionFileName,
userTaskData.value!,
userTaskHtml.value!,
versionCreatedOn,
);
+ if (res.isErr()) return userError(getErrorMessage(res.error));
}
// update ref for the artifacts referenced by the versioned user task
diff --git a/src/management-system-v2/lib/invitation-tokens.ts b/src/management-system-v2/lib/invitation-tokens.ts
index 87e83271a..ca318bd12 100644
--- a/src/management-system-v2/lib/invitation-tokens.ts
+++ b/src/management-system-v2/lib/invitation-tokens.ts
@@ -69,7 +69,7 @@ export async function acceptInvitation(invite: Invitation, userIdAcceptingInvite
if (!userIsMember.value) {
const memberAdded = await addMember(invite.spaceId, userId);
- if (memberAdded?.isErr()) return userError(getErrorMessage(memberAdded.error));
+ if (memberAdded.isErr()) return userError(getErrorMessage(memberAdded.error));
if (invite.roleIds) {
const validRoles = [];
@@ -86,7 +86,7 @@ export async function acceptInvitation(invite: Invitation, userIdAcceptingInvite
userId,
})),
);
- if (result?.isErr()) return userError(getErrorMessage(result.error));
+ if (result.isErr()) return userError(getErrorMessage(result.error));
}
}
}