Repository: PROCEED-Labs/proceed
Pull request #513: Ms2/config db integration
Author: login: canptura
Created: 2025-03-14T13:05:29Z | Merged: 2025-03-26T20:18:02Z

## Summary
This implements usage of the Postgres DB for Tech Data Sets (configs). 

## Details
- prisma schema extended to store configs in DB
- `lib/data/db/machine-config.ts` created to handle storing/loading functionality
  - exports methods in the same fashion as `lib/data/legacy/machine-config.ts` 
- fixed bug: missing date in config-list
- function imports changed to use DB-methods


Diff:
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-content.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-content.tsx
index 1c0d6ee21..1e05ffb7b 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-content.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-content.tsx
@@ -2,7 +2,6 @@
 
 import { AbstractConfig, ParentConfig, StoredParameter } from '@/lib/data/machine-config-schema';
 import { useRouter } from 'next/navigation';
-
 import { KeyOutlined, EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
 import { useState } from 'react';
 import { Button, Input, Col, Row, Tooltip, Space } from 'antd';
@@ -10,8 +9,7 @@ import { defaultParameter } from '../configuration-helper';
 import AddButton from './add-button';
 import CreateParameterModal, { CreateParameterModalReturnType } from './create-parameter-modal';
 import CustomField from './custom-field';
-
-import { addParameter as backendAddParameter } from '@/lib/data/legacy/machine-config';
+import { addParameter as backendAddParameter } from '@/lib/data/db/machine-config';
 
 type MachineDataViewProps = {
   configId: string;
@@ -44,7 +42,6 @@ const Content: React.FC<MachineDataViewProps> = ({
       valuesFromModal.language,
       valuesFromModal.unit,
     );
-
     await backendAddParameter(
       configId,
       configType,
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-editor.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-editor.tsx
index da67487ff..60929bb8e 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-editor.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-editor.tsx
@@ -48,17 +48,17 @@ import { spaceURL } from '@/lib/utils';
 import VersionCreationButton from '@/components/version-creation-button';
 import AddButton from './add-button';
 import ConfigModal from '@/components/config-modal';
+import ActionButtons from './action-buttons';
 import {
-  addMachineConfig,
-  addParentConfigVersion,
   addTargetConfig,
+  addMachineConfig,
   removeTargetConfig,
-  setParentConfigVersionAsLatest,
   updateMachineConfig,
   updateParentConfig,
   updateTargetConfig,
-} from '@/lib/data/legacy/machine-config';
-import ActionButtons from './action-buttons';
+  addParentConfigVersion,
+  setParentConfigVersionAsLatest,
+} from '@/lib/data/db/machine-config';
 import ConfirmationButton from '@/components/confirmation-button';
 import { v4 } from 'uuid';
 import { wrapServerCall } from '@/lib/wrap-server-call';
@@ -160,7 +160,7 @@ const ConfigEditor: React.FC<MachineDataViewProps> = ({
 
   const makeConfigVersionLatest = async () => {
     wrapServerCall({
-      fn: () => setParentConfigVersionAsLatest(parentConfig),
+      fn: () => setParentConfigVersionAsLatest(parentConfig.version || ''),
       onSuccess: () => {
         const searchParams = new URLSearchParams(query);
         searchParams.delete('version');
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-page-content.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-page-content.tsx
index 6fa9bd88c..bc84768f9 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-page-content.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-page-content.tsx
@@ -245,7 +245,6 @@ const ConfigContent: React.FC<VariablesEditorProps> = ({ parentConfig, editingAl
               treeData={treeData}
               expandedKeys={expandedKeys}
               onExpandedChange={(newExpanded) => {
-                console.log(newExpanded);
                 setUserPreferences({
                   'tech-data-open-tree-items': [
                     ...openTreeItemsInConfigs.filter(({ id }) => id !== parentConfig.id),
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-tree-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-tree-view.tsx
index 253a7f682..9c511c5b5 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-tree-view.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/config-tree-view.tsx
@@ -21,7 +21,6 @@ import {
 } from '../configuration-helper';
 import ConfigModal from '@/components/config-modal';
 import CreateParameterModal, { CreateParameterModalReturnType } from './create-parameter-modal';
-
 import {
   addMachineConfig,
   addTargetConfig,
@@ -29,7 +28,7 @@ import {
   removeMachineConfig,
   removeParameter,
   removeTargetConfig,
-} from '@/lib/data/legacy/machine-config';
+} from '@/lib/data/db/machine-config';
 
 type ConfigurationTreeViewProps = {
   parentConfig: ParentConfig;
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/custom-field.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/custom-field.tsx
index 27de22ba2..14cdfc849 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/custom-field.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/custom-field.tsx
@@ -23,12 +23,11 @@ import { defaultParameter, findParameter, getAllParameters } from '../configurat
 import { useEffect, useMemo, useRef, useState } from 'react';
 import CreateParameterModal, { CreateParameterModalReturnType } from './create-parameter-modal';
 import styles from './page.module.scss';
-
 import {
   addParameter as backendAddParameter,
   removeParameter,
   updateParameter,
-} from '@/lib/data/legacy/machine-config';
+} from '@/lib/data/db/machine-config';
 import { useRouter } from 'next/navigation';
 
 type TagRender = SelectProps['tagRender'];
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/mach-config.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/mach-config.tsx
index aa52d922c..63cef2ea4 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/mach-config.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/mach-config.tsx
@@ -13,7 +13,7 @@ import {
   removeMachineConfig,
   updateMachineConfig,
   updateParentConfig,
-} from '@/lib/data/legacy/machine-config';
+} from '@/lib/data/db/machine-config';
 
 const { Text } = Typography;
 
@@ -96,7 +96,7 @@ const MachineConfigurations: React.FC<MachineDataViewProps> = ({
     const machineConfig = parentConfig.machineConfigs.find(({ id }) => id === configToCopy);
     if (!machineConfig) return;
 
-    const id = await copyConfig(configToCopy, 'machine-config');
+    const id = await copyConfig(configToCopy, 'machine-config', parentConfig.id);
 
     // TODO: update the copy with the correct name and description
     // the description update should be handled in the backend (its not just a field but a content entry of a metadata entry)
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/page.tsx
index 374a5f0bd..1230cc1fc 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/page.tsx
@@ -1,6 +1,6 @@
 import Content from '@/components/content';
 //import styles from './page.module.scss';
-import { getDeepParentConfigurationById } from '@/lib/data/legacy/machine-config';
+import { getDeepParentConfigurationById } from '@/lib/data/db/machine-config';
 import ConfigPage from './config-page-content';
 
 type MachineConfigProps = {
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/parameter.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/parameter.tsx
index 061d850a8..7ab566f86 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/parameter.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/[configId]/parameter.tsx
@@ -1,16 +1,13 @@
 'use client';
 
 import { useRouter } from 'next/navigation';
-
 import { CaretRightOutlined } from '@ant-design/icons';
 import { useState } from 'react';
 import { Space, Collapse, theme } from 'antd';
-
 import AddButton from './add-button';
 import CreateParameterModal, { CreateParameterModalReturnType } from './create-parameter-modal';
-
 import { Parameter, ParameterContent } from '@/lib/data/machine-config-schema';
-import { updateParameter } from '@/lib/data/legacy/machine-config';
+import { updateParameter } from '@/lib/data/db/machine-config';
 import { ParamContent, ParameterContentHeader } from './parameter-content';
 
 type MachineDataViewProps = {
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/configuration-helper.ts b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/configuration-helper.ts
index 5d389a804..e1924dc3e 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/configuration-helper.ts
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/configuration-helper.ts
@@ -68,9 +68,9 @@ export function defaultConfiguration(
 
 export const defaultParentConfiguration = (
   environmentId: string,
-  name: string,
-  description: string,
   folderId: string,
+  name?: string,
+  description?: string,
 ): ParentConfig => {
   return {
     ...defaultConfiguration(environmentId, name, description),
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/page.tsx
index f0d6b089c..813b51c92 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/page.tsx
@@ -4,7 +4,7 @@ import { ComponentProps } from 'react';
 import { Space } from 'antd';
 import { getCurrentEnvironment } from '@/components/auth';
 import { notFound } from 'next/navigation';
-import { getParentConfigurations } from '@/lib/data/legacy/machine-config';
+import { getParentConfigurations } from '@/lib/data/db/machine-config';
 import ParentConfigList from './parent-config-list';
 import { ParentConfig } from '@/lib/data/machine-config-schema';
 import { env } from '@/lib/env-vars';
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/parent-config-list.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/parent-config-list.tsx
index 592d2c13a..e816bbbad 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/parent-config-list.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/machine-config/parent-config-list.tsx
@@ -34,14 +34,13 @@ import {
   FileOutlined as FileFilled,
 } from '@ant-design/icons';
 import {
-  deleteParentConfigurations,
   addParentConfig,
   copyParentConfig,
   updateParentConfig,
   updateParameter,
   addParameter,
   removeParentConfiguration,
-} from '@/lib/data/legacy/machine-config';
+} from '@/lib/data/db/machine-config';
 
 import AddUserControls from '@/components/add-user-controls';
 import { useAddControlCallback } from '@/lib/controls-store';
@@ -320,7 +319,18 @@ const ParentConfigList: React.FC<ConfigListProps> = ({ data }) => {
       title: 'Last Edited',
       dataIndex: 'lastEdited',
       key: 'Last Edited',
-      render: (date: Date) => generateDateString(date, true),
+      render: (_, record) => (
+        <SpaceLink
+          href={`/machine-config/${record.id}`}
+          style={{
+            color: 'inherit',
+            textDecoration: 'none',
+            display: 'block',
+          }}
+        >
+          {generateDateString(record.lastEditedOn, true) ?? ''}
+        </SpaceLink>
+      ),
       sorter: (a, b) => new Date(b.lastEditedOn).getTime() - new Date(a.lastEditedOn).getTime(),
       responsive: ['md'],
     },
diff --git a/src/management-system-v2/components/config-creation-button.tsx b/src/management-system-v2/components/config-creation-button.tsx
index 7e86336f1..969722648 100644
--- a/src/management-system-v2/components/config-creation-button.tsx
+++ b/src/management-system-v2/components/config-creation-button.tsx
@@ -4,7 +4,7 @@ import React, { ComponentProps, ReactNode, useState } from 'react';
 import { Button } from 'antd';
 import type { ButtonProps } from 'antd';
 import MachineConfigModal from './config-modal'; //TODO refactoring not using term "machine config"
-import { addParentConfig } from '@/lib/data/legacy/machine-config'; //TODO refactoring not using term "machine config"
+import { addParentConfig } from '@/lib/data/db/machine-config'; //TODO refactoring not using term "machine config"
 import { useParams, useRouter } from 'next/navigation';
 import { useEnvironment } from './auth-can';
 import { useAddControlCallback } from '@/lib/controls-store';
@@ -36,9 +36,9 @@ export const ConfigCreationModal: React.FC<
       addParentConfig(
         defaultParentConfiguration(
           environment.spaceId,
+          folderId,
           values[0].name,
           values[0].description,
-          folderId,
         ),
         environment.spaceId,
       ).then((res) => (Array.isArray(res) ? res[0] : res)));
diff --git a/src/management-system-v2/lib/data/db/machine-config.ts b/src/management-system-v2/lib/data/db/machine-config.ts
index 70b786d12..ed7c8c7eb 100644
--- a/src/management-system-v2/lib/data/db/machine-config.ts
+++ b/src/management-system-v2/lib/data/db/machine-config.ts
@@ -1 +1,1169 @@
-// TODO
+'use server';
+
+import { v4 } from 'uuid';
+import {
+  AbstractConfig,
+  AbstractConfigInput,
+  AbstractConfigInputSchema,
+  MachineConfig,
+  Parameter,
+  ParentConfig,
+  StoredMachineConfig,
+  StoredParameter,
+  StoredParameterZod,
+  StoredParentConfig,
+  StoredTargetConfig,
+  TargetConfig,
+} from '../machine-config-schema';
+import { getFolderById, getRootFolder } from './folders';
+import db from '.';
+import { userError } from '@/lib/user-error';
+import { getCurrentUser } from '@/components/auth';
+import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
+import { asyncFilter, asyncForEach, asyncMap } from '@/lib/helpers/javascriptHelpers';
+import {
+  defaultConfiguration,
+  defaultParentConfiguration,
+} from '@/app/(dashboard)/[environmentId]/machine-config/configuration-helper';
+
+let dataToParentConfigTable: {
+  id: string;
+  environmentId: string;
+  creatorId: string;
+  createdOn: Date;
+  data: StoredParentConfig;
+}[] = [];
+
+let dataToConfigVersionTable: {
+  id: string;
+  parentId: string;
+  data: StoredParentConfig;
+}[] = [];
+
+let dataToTargetConfigTable: {
+  id: string;
+  data: StoredTargetConfig;
+}[] = [];
+
+let dataToMachineConfigTable: {
+  id: string;
+  data: StoredMachineConfig;
+}[] = [];
+
+let dataToConfigParameterTable: {
+  id: string;
+  data: StoredParameter;
+}[] = [];
+
+let dataRemovedFromParentConfigTable: string[] = [];
+let dataRemovedFromConfigVersionTable: string[] = [];
+let dataRemovedFromTargetConfigTable: string[] = [];
+let dataRemovedFromMachineConfigTable: string[] = [];
+let dataRemovedFromConfigParameterTable: string[] = [];
+
+// (parentId: string, parentType: StoredParameter["parentType"], parameters: Record<string, Parameter>, newId?: boolean)
+
+/**
+ * Creates a copy of a parameter and all its nested parameters to be pasted as child of a given parent. Stores the copied parameter.
+ * @param parameterId ID of the parameter which is to be copied.
+ * @param parentId ID of the parent where the parameter is to be copied to.
+ * @param parentType Type of the parent. (_'parameter'_ | _'machine-config'_ | _'target-config'_ | _'parent-config'_)
+ * @return Returns the newly generated ID for the parameter.
+ * @throws {Error} if the ID of the parameter does not exist.
+ */
+export async function copyParameter(
+  parameterId: string,
+  parentId: string,
+  parentType: StoredParameter['parentType'],
+) {
+  const storedParameterResult = await db.configParameter.findUnique({ where: { id: parameterId } });
+  const storedParameter = storedParameterResult?.data as StoredParameter;
+
+  if (!storedParameter) throw new Error(`Parameter with id ${parameterId} does not exist!`);
+
+  // this creates a deep clone of the element to prevent changes in the content of one to affect the other
+  const copy = StoredParameterZod.parse(storedParameter);
+
+  const newId = v4();
+  copy.id = newId;
+  copy.linkedParameters = [];
+  copy.parentId = parentId;
+  copy.parentType = parentType;
+
+  // recursively copy all referenced parameters
+  copy.parameters = await asyncMap(copy.parameters, (id) => copyParameter(id, newId, 'parameter'));
+
+  await db.configParameter.create({ data: { id: copy.id, data: copy } });
+
+  return newId;
+}
+
+/**
+ * Creates a copy of a config and all its nested parameters to be pasted as child of a given parent. Stores the copied config.
+ * @param configId ID of the config which is to be copied.
+ * @param configType Type of the config. (_'machine-config'_ | _'target-config'_)
+ * @param parentId ID of the parent where the config is to be copied to.
+ * @return Returns the newly generated ID for the config.
+ * @throws {Error} if the ID of the config does not exist.
+ */
+export async function copyConfig(
+  configId: string,
+  configType: Exclude<AbstractConfig['type'], 'config'>,
+  parentId: string,
+) {
+  let config;
+  if (configType === 'target-config') {
+    const targetResult = await db.targetConfig.findUnique({ where: { id: configId } });
+    config = targetResult?.data as unknown as StoredTargetConfig;
+  } else {
+    const machineResult = await db.machineConfig.findUnique({ where: { id: configId } });
+    config = machineResult?.data as unknown as StoredMachineConfig;
+  }
+
+  if (!config) throw new Error(`(Target/Machine)config with id ${configId} does not exist`);
+
+  // deep copy the config
+  const copy = JSON.parse(JSON.stringify(config)) as typeof config;
+
+  const newId = v4();
+  copy.id = newId;
+  copy.parentId = parentId;
+
+  [copy.parameters, copy.metadata] = await Promise.all([
+    asyncMap(copy.parameters, (id) => copyParameter(id, newId, configType)),
+    asyncMap(copy.metadata, (id) => copyParameter(id, newId, configType)),
+  ]);
+
+  if (configType === 'target-config') {
+    await db.targetConfig.create({ data: { id: copy.id, data: copy } });
+  } else {
+    await db.machineConfig.create({ data: { id: copy.id, data: copy } });
+  }
+
+  return newId;
+}
+
+/**
+ * @param originalId ID of the config to be copied.
+ * @param machineConfigInput Config the selected config is to be pasted into
+ * @param environmentId
+ * @throws {Error} in case:
+ * * config ID does not exist.
+ * * the folder is not found.
+ * * a parent configuration with the generated ID already exists.
+ */
+export async function copyParentConfig(
+  originalId: string,
+  machineConfigInput: AbstractConfigInput,
+  environmentId: string,
+) {
+  if (!originalId) return;
+
+  try {
+    const originalConfigResult = await db.config.findUnique({ where: { id: originalId } });
+    if (!originalConfigResult) {
+      throw new Error(`Config with id ${originalId} does not exist!`);
+    }
+    const originalConfig = originalConfigResult?.data as unknown as StoredParentConfig;
+
+    const parentConfigData = AbstractConfigInputSchema.parse(machineConfigInput);
+
+    const { userId } = await getCurrentUser();
+    const newId = v4();
+    const date = new Date();
+    const copy = {
+      ...(JSON.parse(JSON.stringify(originalConfig)) as typeof originalConfig),
+      id: newId,
+      environmentId,
+      createdBy: userId,
+      createdOn: date,
+      lastEditedOn: date,
+      ...parentConfigData,
+      name: parentConfigData.name || `${originalConfig.name} (Copy)`,
+      metadata: [],
+      targetConfig: undefined,
+      machineConfigs: [],
+      originalId,
+    } as StoredParentConfig;
+
+    [copy.metadata, copy.targetConfig, copy.machineConfigs] = await Promise.all([
+      asyncMap(originalConfig.metadata, (id) => copyParameter(id, newId, 'parent-config')),
+      originalConfig.targetConfig &&
+        copyConfig(originalConfig.targetConfig, 'target-config', newId),
+      asyncMap(originalConfig.machineConfigs, (id) => copyConfig(id, 'machine-config', newId)),
+    ]);
+
+    // if no folder ID is given, set ID to root folder's
+    if (!copy.folderId) {
+      copy.folderId = (await getRootFolder(environmentId)).id;
+    }
+
+    const folderData = await getFolderById(copy.folderId);
+    if (!folderData) throw new Error('Folder not found');
+
+    await db.config.create({
+      data: {
+        id: copy.id,
+        environmentId: environmentId,
+        creatorId: userId,
+        createdOn: date,
+        data: copy,
+      },
+    });
+
+    return machineConfigInput;
+  } catch (e) {
+    return userError("Couldn't save Config");
+  }
+}
+
+/*************************** Element Addition *****************************/
+/**
+ * Adds a parent config to to DB for a given environment ID.
+ *
+ * @param machineConfigInput  Config which is to be stored in the DB
+ * @param environmentId       Environment ID of the user's space
+ * @param throwCollisionError Determines if an error is thrown when a collision of IDs occurs. If not a new ID is generated for the config which is added. Default: false
+ * @returns ID of the stored config
+ */
+export async function addParentConfig(
+  machineConfigInput: ParentConfig,
+  environmentId: string,
+  throwCollisionError: boolean = false,
+) {
+  try {
+    const { userId } = await getCurrentUser();
+    // TODO this doesn't do anything since every property is optional
+    AbstractConfigInputSchema.parse(machineConfigInput);
+    const folderId = (await getRootFolder(environmentId)).id;
+    const defaultConfig = defaultParentConfiguration(environmentId, folderId);
+    const newConfig: ParentConfig = {
+      ...defaultConfig,
+      ...machineConfigInput,
+    };
+
+    newConfig.createdBy = userId;
+    newConfig.folderId = folderId;
+    newConfig.environmentId = environmentId;
+
+    const folderData = await getFolderById(newConfig.folderId);
+    if (!folderData) throw new Error('Folder not found');
+    let idCollision = false;
+    const { id: parentConfigId } = newConfig;
+
+    const existingConfig = await db.config.findUnique({ where: { id: parentConfigId } });
+    if (existingConfig) {
+      if (throwCollisionError) throw new Error(`Config with id ${parentConfigId} already exists!`);
+      idCollision = true;
+    }
+    let storeId = await parentConfigToStorage(newConfig, idCollision);
+    await storeAllCached();
+    return { storeId };
+  } catch (e: unknown) {
+    const error = e as Error;
+    return userError(error.message ?? "Couldn't create Config");
+  }
+}
+
+/**
+ * This adds a new version for a parent configuration to the db.
+ * @param machineConfigInput Config for which a version is created.
+ * @param environmentId Environment ID for the current space.
+ * @param versionId ID for the version that is to be created.
+ * @param versionName Name for the version that is to be created.
+ * @param versionDescription Description for the version that is to be created.
+ * @returns returns the newly created config version in the case of no errors. Otherwise returns UserError.
+ */
+export async function addParentConfigVersion(
+  machineConfigInput: ParentConfig,
+  environmentId: string,
+  versionId: string,
+  versionName: string,
+  versionDescription: string,
+) {
+  let versions = Array.from(machineConfigInput.versions);
+  versions.push({
+    id: versionId,
+    name: versionName,
+    description: versionDescription,
+    versionBasedOn: machineConfigInput.version,
+    createdOn: new Date(),
+  });
+
+  // update references to versions in main/latest config
+  await updateParentConfig(machineConfigInput.id, { versions });
+
+  // storing a copy as versioned config
+  const newVersion: ParentConfig = JSON.parse(JSON.stringify(machineConfigInput));
+  newVersion.id = newVersion.id;
+
+  try {
+    const newConfig: ParentConfig = {
+      ...(defaultConfiguration(environmentId) as ParentConfig),
+      ...newVersion,
+      version: versionId,
+    };
+
+    newConfig.folderId = (await getRootFolder(environmentId)).id;
+    newConfig.environmentId = environmentId;
+
+    const folderData = await getFolderById(newConfig.folderId);
+    if (!folderData) throw new Error('Folder not found');
+
+    // true to generate new IDs for data and create a version of a parentConfig instead of a regular one
+    await parentConfigToStorage(newConfig, true, versionId);
+    await storeAllCached();
+
+    return newConfig;
+  } catch (e: unknown) {
+    const error = e as Error;
+    return userError(error.message ?? "Couldn't create Machine Config");
+  }
+}
+
+/**
+ * Copies the data of a specific config-version into the editable "main-config" displayed as latest version.
+ * @param machineConfigInput The config-version that is to be set as latest version.
+ * @returns Returns the config if no errors occur. Otherwise a UserError is returned.
+ */
+export async function setParentConfigVersionAsLatest(versionId: string) {
+  try {
+    versionToParentConfigStorage(versionId);
+  } catch (e: unknown) {
+    const error = e as Error;
+    return userError(error.message ?? "Couldn't create Machine Config");
+  }
+}
+
+export async function addTargetConfig(parentConfigId: string, targetConfig: TargetConfig) {
+  const parentConfigResult = await db.config.findUnique({ where: { id: parentConfigId } });
+  const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+  if (!parentConfig)
+    throw new Error(`There is no parent configuration with the id ${parentConfigId}.`);
+
+  if (parentConfig.targetConfig)
+    throw new Error(`The parent configuration already has a target configuration.`);
+
+  await targetConfigToStorage(parentConfigId, targetConfig);
+  await storeAllCached();
+
+  parentConfig.targetConfig = targetConfig.id;
+  await db.config.update({
+    where: { id: parentConfig.id },
+    data: { data: parentConfig },
+  });
+}
+
+export async function addMachineConfig(
+  parentConfigId: string,
+  machineConfig: MachineConfig,
+  newId: boolean = false,
+) {
+  // const parentConfig = storedData.parentConfigs[parentConfigId];
+
+  const parentConfigResult = await db.config.findUnique({ where: { id: parentConfigId } });
+  const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+
+  if (!parentConfig)
+    throw new Error(`There is no parent configuration with the id ${parentConfigId}.`);
+
+  await machineConfigsToStorage(parentConfigId, [machineConfig], newId);
+  await storeAllCached();
+
+  parentConfig.machineConfigs.push(machineConfig.id);
+  await db.config.update({
+    where: { id: parentConfig.id },
+    data: { data: parentConfig },
+  });
+}
+
+export async function addParameter(
+  parentId: string,
+  parentType: StoredParameter['parentType'],
+  targetMember: 'parameters' | 'metadata',
+  key: string,
+  parameter: Parameter,
+) {
+  if (parentType === 'parent-config') {
+    const parentConfigResult = await db.config.findUnique({ where: { id: parentId } });
+    const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+    if (!parentConfig) throw new Error(`There is no parent configuration with the id ${parentId}.`);
+    if (targetMember === 'parameters') throw new Error('Parent Configurations have no parameters.');
+
+    parentConfig[targetMember].push(parameter.id!);
+    await parametersToStorage(parentConfig.id, parentType, { [key]: parameter });
+    await db.config.update({
+      where: { id: parentConfig.id },
+      data: { data: parentConfig },
+    });
+  } else if (parentType === 'machine-config') {
+    const parentConfigResult = await db.machineConfig.findUnique({ where: { id: parentId } });
+    const parentConfig = parentConfigResult?.data as unknown as StoredMachineConfig;
+    if (!parentConfig)
+      throw new Error(`There is no machine configuration with the id ${parentId}.`);
+
+    parentConfig[targetMember].push(parameter.id!);
+    await parametersToStorage(parentConfig.id, parentType, { [key]: parameter });
+    await db.machineConfig.update({
+      where: { id: parentConfig.id },
+      data: { data: parentConfig },
+    });
+  } else if (parentType === 'target-config') {
+    const parentConfigResult = await db.targetConfig.findUnique({ where: { id: parentId } });
+    const parentConfig = parentConfigResult?.data as unknown as StoredTargetConfig;
+    if (!parentConfig) throw new Error(`There is no target configuration with the id ${parentId}.`);
+
+    parentConfig[targetMember].push(parameter.id!);
+    await parametersToStorage(parentConfig.id, parentType, { [key]: parameter });
+    await db.targetConfig.update({
+      where: { id: parentConfig.id },
+      data: { data: parentConfig },
+    });
+  } else if (parentType === 'parameter') {
+    const parentParameterResult = await db.configParameter.findUnique({ where: { id: parentId } });
+    const parentParameter = parentParameterResult?.data as unknown as StoredParameter;
+    if (!parentParameter) throw new Error(`There is no parameter with the id ${parentId}.`);
+    if (targetMember === 'metadata') throw new Error('Parent Configurations have no metadata.');
+
+    parentParameter[targetMember].push(parameter.id!);
+    await parametersToStorage(parentParameter.id!, parentType, { [key]: parameter });
+    await db.configParameter.update({
+      where: { id: parentParameter.id },
+      data: { data: parentParameter },
+    });
+  }
+  await storeAllCached();
+}
+
+/**
+ * Stores a record of parameters and references to all nested parameters. Storing of nested parameters is done recursively.
+ * @param parentId ID of the parent object.
+ * @param parentType Information about the type of parent (_'target-config'_ | _'machine-config'_ | _'parameter'_ | _'parent-config'_)
+ * @param parameters Record of nested parameters.
+ * @param newId Boolean determining if new IDs are to be generated.
+ * @return References to nested parameter IDs.
+ */
+async function parametersToStorage(
+  parentId: string,
+  parentType: StoredParameter['parentType'],
+  parameters: Record<string, Parameter>,
+  newId: boolean = false,
+) {
+  Object.entries(parameters).forEach(async ([key, parameter]) => {
+    parameter.id = newId ? v4() : parameter.id;
+    let nestedParams = await parametersToStorage(
+      parameter.id!,
+      'parameter',
+      parameter.parameters,
+      newId,
+    );
+    dataToConfigParameterTable.push({
+      id: parameter.id!,
+      data: {
+        ...parameter,
+        key,
+        parentId,
+        parentType,
+        parameters: nestedParams,
+      },
+    });
+  });
+  return Object.values(parameters).map(({ id }) => id as string);
+}
+
+/**
+ * Stores a given targetConfig into the new storage referencing other elements by id instead of having them nested. If called without a TargetConfig the async function returns void.
+ * @param parentId ID of the parent object.
+ * @param targetConfig TargetConfig that is to be stored, able to contain ParameterConfigs
+ * @param newId Boolean determining if new IDs are to be generated.
+ * @return ID of the TargetConfig that was stored.
+ */
+async function targetConfigToStorage(
+  parentId: string,
+  targetConfig: TargetConfig,
+  newId: boolean = false,
+) {
+  if (targetConfig) {
+    targetConfig.id = newId ? v4() : targetConfig.id;
+    let configToStore = {
+      ...targetConfig,
+      parentId,
+      metadata: [],
+      parameters: [],
+    } as StoredTargetConfig;
+
+    [configToStore.metadata, configToStore.parameters] = await Promise.all([
+      parametersToStorage(targetConfig.id, 'target-config', targetConfig.metadata, newId),
+      parametersToStorage(targetConfig.id, 'target-config', targetConfig.parameters, newId),
+    ]);
+
+    dataToTargetConfigTable.push({
+      id: targetConfig.id,
+      data: configToStore,
+    });
+
+    return targetConfig.id;
+  }
+}
+
+/**
+ * Stores a given MachineConfig into the new storage referencing other elements by id instead of having them nested.
+ * @param parentId ID of the parent object.
+ * @param machineConfig MachineConfigs that are to be stored, able to contain ParameterConfigs
+ * @param newId Boolean determining if new IDs are to be generated.
+ * @return IDs of the MachineConfigs that were stored.
+ */
+function machineConfigsToStorage(
+  parentId: string,
+  machineConfigs: MachineConfig[],
+  newId: boolean = false,
+) {
+  machineConfigs.forEach(async (machineConfig) => {
+    machineConfig.id = newId ? v4() : machineConfig.id;
+
+    let configToStore = {
+      ...machineConfig,
+      parentId,
+      metadata: [],
+      parameters: [],
+    } as StoredMachineConfig;
+
+    [configToStore.metadata, configToStore.parameters] = await Promise.all([
+      parametersToStorage(machineConfig.id, 'target-config', machineConfig.metadata, newId),
+      parametersToStorage(machineConfig.id, 'target-config', machineConfig.parameters, newId),
+    ]);
+
+    dataToMachineConfigTable.push({
+      id: machineConfig.id,
+      data: configToStore,
+    });
+  });
+
+  return machineConfigs.map(({ id }) => id);
+}
+
+/**
+ * Stores a given ParentConfig (or ParentConfigVersion) into db referencing other elements by id instead of having them nested.
+ * @param parentConfig ParentConfig that is to be stored, able to contain TargetConfigs, MachineConfigs and ParameterConfigs
+ * @param newId Boolean determining if new IDs are to be generated.
+ * @param version Version-ID of the config if a versioned config is to be stored.
+ */
+async function parentConfigToStorage(
+  parentConfig: ParentConfig,
+  newId: boolean = false,
+  version: string = '',
+) {
+  const { targetConfig, metadata, machineConfigs } = parentConfig;
+  parentConfig.id = newId && !version ? v4() : parentConfig.id;
+  let creationDate = new Date(parentConfig.createdOn);
+  const configToStore = {
+    ...parentConfig,
+    targetConfig: undefined,
+    machineConfigs: [],
+    metadata: [],
+  } as StoredParentConfig;
+
+  [configToStore.targetConfig, configToStore.machineConfigs, configToStore.metadata] =
+    await Promise.all([
+      targetConfig && targetConfigToStorage(parentConfig.id, targetConfig, newId),
+      machineConfigsToStorage(parentConfig.id, machineConfigs, newId),
+      parametersToStorage(parentConfig.id, 'parent-config', metadata, newId),
+    ]);
+
+  if (version)
+    dataToConfigVersionTable.push({ id: version, parentId: parentConfig.id, data: configToStore });
+  else
+    dataToParentConfigTable.push({
+      id: parentConfig.id,
+      environmentId: parentConfig.environmentId,
+      creatorId: parentConfig.createdBy,
+      createdOn: creationDate,
+      data: configToStore,
+    });
+  return parentConfig.id;
+}
+
+async function versionToParentConfigStorage(versionId: string) {
+  let configVersionResult = await db.configVersions.findUnique({ where: { id: versionId } });
+  let configVersion = configVersionResult?.data as unknown as StoredParentConfig;
+
+  let originalConfigResult = await db.config.findUnique({ where: { id: configVersion.id } });
+  let originalConfig = originalConfigResult?.data as unknown as StoredParentConfig;
+  let versions = originalConfig.versions;
+  let environmentId = originalConfigResult?.environmentId || '';
+
+  // delete referenced parameters, machine- and targetconfigs - but keeps versions and parent config
+  removeParentConfiguration(configVersion.id, true);
+
+  const copy = {
+    ...(JSON.parse(JSON.stringify(configVersion)) as typeof configVersion),
+    ...configVersion,
+    metadata: [],
+    targetConfig: undefined,
+    machineConfigs: [],
+  } as StoredParentConfig;
+
+  [copy.metadata, copy.targetConfig, copy.machineConfigs] = await Promise.all([
+    asyncMap(configVersion.metadata, (id) => copyParameter(id, configVersion.id, 'parent-config')),
+    configVersion.targetConfig &&
+      copyConfig(configVersion.targetConfig, 'target-config', configVersion.id),
+    asyncMap(configVersion.machineConfigs, (id) =>
+      copyConfig(id, 'machine-config', configVersion.id),
+    ),
+  ]);
+
+  // if no folder ID is given, set ID to root folder's
+  if (!copy.folderId) {
+    copy.folderId = (await getRootFolder(environmentId)).id;
+  }
+
+  const folderData = await getFolderById(copy.folderId);
+  if (!folderData) throw new Error('Folder not found');
+
+  await db.config.update({
+    where: { id: configVersion.id },
+    data: { data: { ...copy, versions } },
+  });
+}
+
+async function storeCachedParentConfigs() {
+  const createMany = await db.config.createMany({
+    data: dataToParentConfigTable,
+    skipDuplicates: true,
+  });
+  dataToParentConfigTable = [];
+}
+
+async function storeCachedConfigVersions() {
+  const createMany = await db.configVersions.createMany({
+    data: dataToConfigVersionTable,
+    skipDuplicates: true,
+  });
+  dataToConfigVersionTable = [];
+}
+
+async function storeCachedTargetConfigs() {
+  const createMany = await db.targetConfig.createMany({
+    data: dataToTargetConfigTable,
+    skipDuplicates: true,
+  });
+  dataToTargetConfigTable = [];
+}
+
+async function storeCachedMachineConfigs() {
+  const createMany = await db.machineConfig.createMany({
+    data: dataToMachineConfigTable,
+    skipDuplicates: true,
+  });
+  dataToMachineConfigTable = [];
+}
+
+async function storeCachedConfigParameters() {
+  const createMany = await db.configParameter.createMany({
+    data: dataToConfigParameterTable,
+    skipDuplicates: true,
+  });
+  dataToConfigParameterTable = [];
+}
+
+async function storeAllCached() {
+  await Promise.all([
+    storeCachedParentConfigs(),
+    storeCachedConfigVersions(),
+    storeCachedTargetConfigs(),
+    storeCachedMachineConfigs(),
+    storeCachedConfigParameters(),
+  ]);
+}
+
+/********************** Read Elements ****************************/
+
+/**
+ * Reads parameters and referenced parameters from stored data to return a nested structure.
+ * @param parameterIds IDs of parameters that are to be returned
+ * @return Returns a record of parameters.
+ */
+async function nestedParametersFromStorage(parameterIds: string[]) {
+  const parameters: Record<string, Parameter> = {};
+
+  await asyncForEach(parameterIds, async (id, idx) => {
+    let storedParameterResult = await db.configParameter.findUnique({
+      where: { id: id },
+    });
+    const storedParameter = storedParameterResult?.data as StoredParameter;
+    if (storedParameter && storedParameter.key) {
+      parameters[storedParameter.key] = {
+        ...storedParameter,
+        parameters: await nestedParametersFromStorage(storedParameter.parameters),
+      };
+    }
+  });
+
+  return parameters;
+}
+
+/**
+ * Reads a single Machine- or Target-Config and its referenced parameters from stored data and returns a nested structure.
+ * @param configType (_'target-config'_ | _'machine-config'_) Type of the config.
+ * @param targetConfigId ID of Config that is to be returned
+ * @return Returns a Config.
+ */
+async function nestedConfigFromStorage(
+  configType: 'target-config',
+  configId?: string,
+): Promise<TargetConfig | undefined>;
+async function nestedConfigFromStorage(
+  configType: 'machine-config',
+  configId?: string,
+): Promise<MachineConfig | undefined>;
+async function nestedConfigFromStorage<T extends TargetConfig | MachineConfig>(
+  configType: T['type'],
+  configId?: string,
+) {
+  if (!configId) return;
+
+  let storedConfig;
+  if (configType === 'target-config') {
+    let targetResult = await db.targetConfig.findUnique({ where: { id: configId } });
+    storedConfig = targetResult?.data as unknown as StoredTargetConfig;
+  } else {
+    let machineResult = await db.machineConfig.findUnique({ where: { id: configId } });
+    storedConfig = machineResult?.data as unknown as StoredMachineConfig;
+  }
+
+  if (!storedConfig) return;
+
+  const config = {
+    ...storedConfig,
+    parameters: {},
+    metadata: {},
+  };
+
+  [config.parameters, config.metadata] = await Promise.all([
+    nestedParametersFromStorage(storedConfig.parameters),
+    nestedParametersFromStorage(storedConfig.metadata),
+  ]);
+
+  if (configType === 'target-config') return config as TargetConfig;
+  else return config as MachineConfig;
+}
+
+/**
+ * Reads MachineConfigs and referenced parameters from stored data to return nested structures.
+ * @param machineConfigIds ID of MachineConfigs that are to be returned
+ * @return Returns MachineConfigs.
+ */
+async function nestedMachineConfigsFromStorage(
+  machineConfigIds: string[],
+): Promise<MachineConfig[]> {
+  return await (
+    await asyncMap(
+      machineConfigIds,
+      async (e, i) => await nestedConfigFromStorage('machine-config', e),
+    )
+  ).filter((config): config is MachineConfig => !!config);
+}
+
+/**
+ * Returns a parentConfig based on Config id
+ *
+ * @throws {UnauthorizedError}
+ */
+export async function getDeepParentConfigurationById(
+  parentConfigId: string,
+  version: string = '',
+  ability?: Ability,
+): Promise<ParentConfig> {
+  let configResult = await db.config.findUnique({ where: { id: parentConfigId } });
+  let lastEdited = configResult?.lastEditedOn;
+  let config = configResult?.data as unknown as StoredParentConfig;
+  let versions = config.versions;
+
+  if (version) {
+    let configVersionResult = await db.configVersions.findUnique({
+      where: { id: version },
+    });
+    config = configVersionResult?.data as unknown as StoredParentConfig;
+  }
+
+  // TODO: check if the user can access the config
+  const parentConfig = {
+    ...config,
+    lastEditedOn: lastEdited,
+    metadata: {},
+    targetConfig: {},
+    machineConfigs: {},
+    versions: versions,
+  } as ParentConfig;
+
+  [parentConfig.metadata, parentConfig.targetConfig, parentConfig.machineConfigs] =
+    await Promise.all([
+      nestedParametersFromStorage(config.metadata),
+      nestedConfigFromStorage('target-config', config.targetConfig),
+      nestedMachineConfigsFromStorage(config.machineConfigs),
+    ]);
+
+  if (
+    parentConfig &&
+    false /*!ability.can('view', toCaslResource('MachineConfig', machineConfig))*/
+  )
+    throw new UnauthorizedError();
+
+  return parentConfig;
+}
+
+/** Returns all shallow Configs in form of an array */
+export async function getParentConfigurations(
+  environmentId: string,
+  ability?: Ability,
+): Promise<ParentConfig[]> {
+  const storedParentConfigs = await db.config.findMany({
+    where: { environmentId: environmentId },
+  });
+  //TODO remove redundancy
+  const parentConfigs = await asyncMap(storedParentConfigs, ({ id }) =>
+    getDeepParentConfigurationById(id),
+  );
+
+  // TODO: further filtering to only show what the user can see needed?
+
+  return ability
+    ? parentConfigs /*ability.filter('view', 'MachineConfig', machineConfig)*/
+    : parentConfigs;
+}
+
+/********************** Update Elements ****************************/
+
+/**
+ * Stores changes for a given parameter. The changes can be a partial of a parameter.
+ * @param parameterId ID of the parameter that receives changes.
+ * @param changes Partial of a Parameter containing the changes.
+ * @throws {Error} in case:
+ * * parameterId does not exist
+ * * changes contains an ID (IDs must not be changed)
+ */
+export async function updateParameter(parameterId: string, changes: Partial<StoredParameter>) {
+  const parameterResult = await db.configParameter.findUnique({ where: { id: parameterId } });
+  const parameter = parameterResult?.data as unknown as StoredParameter;
+  if (!parameter) throw new Error(`Parameter with id ${parameterId} does not exist!`);
+  if (changes.id) throw new Error('Invalid attempt to change the id of an existing parameter');
+  const changed = StoredParameterZod.partial().parse(changes);
+
+  // overwrite the current values with the changed ones
+  await db.configParameter.update({
+    where: { id: parameterId },
+    data: { data: { ...parameter, ...changed } },
+  });
+}
+
+export async function updateMachineConfig(configId: string, changes: Partial<StoredMachineConfig>) {
+  const machineConfigResult = await db.machineConfig.findUnique({ where: { id: configId } });
+  const machineConfig = machineConfigResult?.data as unknown as StoredMachineConfig;
+
+  if (!machineConfig) throw new Error(`Machine config with id ${configId} does not exist!`);
+  if (changes.id) throw new Error('Invalid attempt to change the id of an existing machine config');
+
+  // TODO maybe add schema for parsing the changes
+
+  await db.machineConfig.update({
+    where: { id: configId },
+    data: { data: { ...machineConfig, ...changes } },
+  });
+}
+
+export async function updateTargetConfig(configId: string, changes: Partial<StoredTargetConfig>) {
+  const targetConfigResult = await db.targetConfig.findUnique({ where: { id: configId } });
+  const targetConfig = targetConfigResult?.data as unknown as StoredTargetConfig;
+
+  if (!targetConfig) throw new Error(`Target config with id ${configId} does not exist!`);
+  if (changes.id) throw new Error('Invalid attempt to change the id of an existing target config');
+
+  // TODO maybe add schema for parsing the changes
+
+  await db.targetConfig.update({
+    where: { id: configId },
+    data: { data: { ...targetConfig, ...changes } },
+  });
+}
+
+export async function updateParentConfig(configId: string, changes: Partial<StoredParentConfig>) {
+  const configResult = await db.config.findUnique({ where: { id: configId } });
+  const config = configResult?.data as unknown as StoredParentConfig;
+
+  if (!config) throw new Error(`Parent config with id ${configId} does not exist!`);
+  if (changes.id) throw new Error('Invalid attempt to change the id of an existing parent config');
+
+  // TODO maybe add schema for parsing the changes
+
+  await db.config.update({
+    where: { id: configId },
+    data: { data: { ...config, ...changes } },
+  });
+}
+
+/*************************** Element Removal ******************************/
+
+/**
+ * Removes a parameter and all its nested parameters from the store
+ * will also remove the reference to the parameter from its parent element if it still exists
+ *
+ * //TODO: if we remove a whole tree we should not trigger a save on every change but only update the store after every change was done
+ *
+ * @param parameterId the id of the parameter to remove
+ */
+export async function removeParameter(parameterId: string) {
+  await deleteParameterFromStorage(parameterId);
+  await removeAllCached();
+}
+
+/**
+ * Removes a target config and all its nested parameters from the store
+ * will also remove the reference to the target config from its parent config if it still exists
+ *
+ * //TODO: if we remove a whole tree we should not trigger a save on every change but only update the store after every change was done
+ *
+ * @param targetConfigId the id of the target config to remove
+ */
+export async function removeTargetConfig(targetConfigId: string) {
+  await deleteTargetConfigFromStorage(targetConfigId);
+  await removeAllCached();
+}
+
+/**
+ * Removes a machine config and all its nested parameters from the store
+ * will also remove the reference to the machine config from its parent config if it still exists
+ *
+ * //TODO: if we remove a whole tree we should not trigger a save on every change but only update the store after every change was done
+ *
+ * @param machineConfigId the id of the machine config to remove
+ */
+export async function removeMachineConfig(machineConfigId: string) {
+  await deleteMachineConfigFromStorage(machineConfigId);
+  await removeAllCached();
+}
+
+/**
+ * Removes an existing parent config for a given ID from store.
+ *
+ * @param parentConfigId ID of the ParentConfig that is to be removed.
+ * @param keepVersions only removes referenced parameters and configs but keeps versioned configs and parent config itself. Used to overwrite config with data from a stored version
+ */
+export async function removeParentConfiguration(
+  parentConfigId: string,
+  keepVersions: boolean = false,
+) {
+  await deleteParentConfigurationFromStorage(parentConfigId, keepVersions);
+  await removeAllCached();
+}
+
+async function deleteParameterFromStorage(parameterId: string) {
+  const parameterResult = await db.configParameter.findUnique({ where: { id: parameterId } });
+  const parameter = parameterResult?.data as unknown as StoredParameter;
+
+  if (!parameter) return;
+
+  // remove the reference to the parameter
+  if (parameter.parentType === 'parameter') {
+    const parentParameterResult = await db.configParameter.findUnique({
+      where: { id: parameter.parentId },
+    });
+    const parentParameter = parentParameterResult?.data as unknown as StoredParameter;
+
+    if (parentParameter) {
+      parentParameter.parameters = parentParameter.parameters.filter((id) => id !== parameterId);
+      await db.configParameter.update({
+        where: { id: parameter.parentId },
+        data: { data: parentParameter },
+      });
+    }
+  } else if (parameter.parentType === 'parent-config') {
+    const parentConfigResult = await db.config.findUnique({ where: { id: parameter.parentId } });
+    const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+
+    if (parentConfig) {
+      parentConfig.metadata = parentConfig.metadata.filter((id) => id !== parameterId);
+      await db.config.update({
+        where: { id: parameter.parentId },
+        data: { data: parentConfig },
+      });
+    }
+  } else if (parameter.parentType === 'machine-config') {
+    const parentConfigResult = await db.config.findUnique({ where: { id: parameter.parentId } });
+    const parentConfig = parentConfigResult?.data as unknown as StoredMachineConfig;
+    if (parentConfig) {
+      parentConfig.metadata = parentConfig.metadata.filter((id) => id !== parameterId);
+      parentConfig.parameters = parentConfig.parameters.filter((id) => id !== parameterId);
+      await db.machineConfig.update({
+        where: { id: parameter.parentId },
+        data: { data: parentConfig },
+      });
+    }
+  } else if (parameter.parentType === 'target-config') {
+    const parentConfigResult = await db.config.findUnique({ where: { id: parameter.parentId } });
+    const parentConfig = parentConfigResult?.data as unknown as StoredTargetConfig;
+    if (parentConfig) {
+      parentConfig.metadata = parentConfig.metadata.filter((id) => id !== parameterId);
+      parentConfig.parameters = parentConfig.parameters.filter((id) => id !== parameterId);
+      await db.targetConfig.update({
+        where: { id: parameter.parentId },
+        data: { data: parentConfig },
+      });
+    }
+  }
+
+  // recursively remove all referenced parameters
+  await asyncForEach(parameter.parameters, async (id) => deleteParameterFromStorage(id));
+
+  // remove the parameter from db
+  // await db.configParameter.delete({ where: { id: parameterId } });
+  dataRemovedFromConfigParameterTable.push(parameterId);
+  // TODO: remove all backlinks from linked parameters
+}
+
+async function deleteTargetConfigFromStorage(targetConfigId: string) {
+  const targetConfigResult = await db.targetConfig.findUnique({ where: { id: targetConfigId } });
+  const targetConfig = targetConfigResult?.data as unknown as StoredTargetConfig;
+
+  if (!targetConfig) return;
+
+  // remove the reference to the target config from its the parent config
+  const parentConfigResult = await db.config.findUnique({
+    where: { id: targetConfig.parentId },
+  });
+  const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+  if (parentConfig) {
+    parentConfig.targetConfig = undefined;
+    await db.config.update({
+      where: { id: targetConfig.parentId },
+      data: { data: parentConfig },
+    });
+  }
+
+  // remove all referenced parameters
+  await Promise.all([
+    asyncForEach(targetConfig.metadata, async (id) => deleteParameterFromStorage(id)),
+    asyncForEach(targetConfig.parameters, async (id) => deleteParameterFromStorage(id)),
+  ]);
+
+  // remove the target config from db
+  // await db.targetConfig.delete({ where: { id: targetConfigId } });
+  dataRemovedFromTargetConfigTable.push(targetConfigId);
+}
+
+async function deleteMachineConfigFromStorage(machineConfigId: string) {
+  const machineConfigResult = await db.machineConfig.findUnique({ where: { id: machineConfigId } });
+  const machineConfig = machineConfigResult?.data as unknown as StoredMachineConfig;
+
+  if (!machineConfig) return;
+
+  // remove the reference to the machine config from its the parent config
+  const parentConfigResult = await db.config.findUnique({
+    where: { id: machineConfig.parentId },
+  });
+  const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+
+  if (parentConfig) {
+    parentConfig.machineConfigs = parentConfig.machineConfigs.filter(
+      (id) => id !== machineConfigId,
+    );
+    await db.config.update({
+      where: { id: machineConfig.parentId },
+      data: { data: parentConfig },
+    });
+  }
+
+  // remove all referenced parameters
+  await Promise.all([
+    asyncForEach(machineConfig.metadata, async (id) => deleteParameterFromStorage(id)),
+    asyncForEach(machineConfig.parameters, async (id) => deleteParameterFromStorage(id)),
+  ]);
+
+  // remove the machine config from db
+  // await db.machineConfig.delete({ where: { id: machineConfigId } });
+  dataRemovedFromMachineConfigTable.push(machineConfigId);
+}
+
+async function deleteParentConfigurationFromStorage(
+  parentConfigId: string,
+  keepVersions: boolean = false,
+) {
+  const parentConfigResult = await db.config.findUnique({ where: { id: parentConfigId } });
+  const parentConfig = parentConfigResult?.data as unknown as StoredParentConfig;
+
+  const parentConfigVersionsResult = await db.configVersions.findMany({
+    where: { parentId: parentConfigId },
+  });
+
+  if (!parentConfig) return;
+
+  await Promise.all([
+    parentConfig.targetConfig && deleteTargetConfigFromStorage(parentConfig.targetConfig),
+    asyncForEach(parentConfig.machineConfigs, (id) => deleteMachineConfigFromStorage(id)),
+    asyncForEach(parentConfig.metadata, (id) => deleteParameterFromStorage(id)),
+  ]);
+
+  // for each version removing TargetConfig, MachineConfigs and Parameters and finally removing the configVersion itself
+  if (!keepVersions) {
+    const parentConfigVersions = parentConfigVersionsResult.map(
+      (element) => element.data,
+    ) as unknown as StoredParentConfig[];
+
+    await asyncForEach(parentConfigVersions, async (configVersion) => {
+      await Promise.all([
+        configVersion.targetConfig && deleteTargetConfigFromStorage(configVersion.targetConfig),
+        asyncForEach(configVersion.machineConfigs, (id) => deleteMachineConfigFromStorage(id)),
+        asyncForEach(configVersion.metadata, (id) => deleteParameterFromStorage(id)),
+        db.configVersions.delete({ where: { id: configVersion.version } }),
+      ]);
+    });
+
+    // remove from db
+    // await db.config.delete({ where: { id: parentConfigId } });
+    dataRemovedFromParentConfigTable.push(parentConfigId);
+  }
+}
+
+async function removeCachedParentConfigs() {
+  const removeMany = await db.config.deleteMany({
+    where: { id: { in: dataRemovedFromParentConfigTable } },
+  });
+  dataRemovedFromParentConfigTable = [];
+}
+
+async function removeCachedConfigVersions() {
+  const removeMany = await db.configVersions.deleteMany({
+    where: { id: { in: dataRemovedFromConfigVersionTable } },
+  });
+  dataRemovedFromConfigVersionTable = [];
+}
+
+async function removeCachedTargetConfigs() {
+  const removeMany = await db.targetConfig.deleteMany({
+    where: { id: { in: dataRemovedFromTargetConfigTable } },
+  });
+  dataRemovedFromTargetConfigTable = [];
+}
+
+async function removeCachedMachineConfigs() {
+  const removeMany = await db.machineConfig.deleteMany({
+    where: { id: { in: dataRemovedFromMachineConfigTable } },
+  });
+  dataRemovedFromMachineConfigTable = [];
+}
+
+async function removeCachedConfigParameters() {
+  const removeMany = await db.configParameter.deleteMany({
+    where: { id: { in: dataRemovedFromConfigParameterTable } },
+  });
+  dataRemovedFromConfigParameterTable = [];
+}
+
+async function removeAllCached() {
+  await Promise.all([
+    removeCachedParentConfigs(),
+    removeCachedConfigVersions(),
+    removeCachedTargetConfigs(),
+    removeCachedMachineConfigs(),
+    removeCachedConfigParameters(),
+  ]);
+}
diff --git a/src/management-system-v2/lib/data/legacy/machine-config.ts b/src/management-system-v2/lib/data/legacy/machine-config.ts
index 9ebb7fa19..91ee13c0a 100644
--- a/src/management-system-v2/lib/data/legacy/machine-config.ts
+++ b/src/management-system-v2/lib/data/legacy/machine-config.ts
@@ -398,7 +398,7 @@ export async function updateParentConfig(configId: string, changes: Partial<Stor
  */
 export async function copyParameter(
   parameterId: string,
-  parentId?: string,
+  parentId: string,
   parentType?: StoredParameter['parentType'],
 ) {
   const storedParameter = storedData.parameters[parameterId];
diff --git a/src/management-system-v2/prisma/migrations/20250314125352_config_server_models/migration.sql b/src/management-system-v2/prisma/migrations/20250314125352_config_server_models/migration.sql
new file mode 100644
index 000000000..3dc95848f
--- /dev/null
+++ b/src/management-system-v2/prisma/migrations/20250314125352_config_server_models/migration.sql
@@ -0,0 +1,50 @@
+-- CreateTable
+CREATE TABLE "config" (
+    "id" TEXT NOT NULL,
+    "environmentId" TEXT NOT NULL,
+    "creatorId" TEXT NOT NULL,
+    "createdOn" TIMESTAMP(3) NOT NULL,
+    "lastEditedOn" TIMESTAMP(3) NOT NULL,
+    "data" JSONB NOT NULL,
+
+    CONSTRAINT "config_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "config_parameter" (
+    "id" TEXT NOT NULL,
+    "data" JSONB NOT NULL,
+
+    CONSTRAINT "config_parameter_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "machine_config" (
+    "id" TEXT NOT NULL,
+    "data" JSONB NOT NULL,
+
+    CONSTRAINT "machine_config_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "target_config" (
+    "id" TEXT NOT NULL,
+    "data" JSONB NOT NULL,
+
+    CONSTRAINT "target_config_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "config_version" (
+    "id" TEXT NOT NULL,
+    "parentId" TEXT NOT NULL,
+    "data" JSONB NOT NULL,
+
+    CONSTRAINT "config_version_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "config" ADD CONSTRAINT "config_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "space"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "config" ADD CONSTRAINT "config_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/src/management-system-v2/prisma/migrations/migration_lock.toml b/src/management-system-v2/prisma/migrations/migration_lock.toml
index fbffa92c2..648c57fd5 100644
--- a/src/management-system-v2/prisma/migrations/migration_lock.toml
+++ b/src/management-system-v2/prisma/migrations/migration_lock.toml
@@ -1,3 +1,3 @@
 # Please do not edit this file manually
-# It should be added in your version-control system (i.e. Git)
+# It should be added in your version-control system (e.g., Git)
 provider = "postgresql"
\ No newline at end of file
diff --git a/src/management-system-v2/prisma/schema.prisma b/src/management-system-v2/prisma/schema.prisma
index 2609032df..da5608aa1 100644
--- a/src/management-system-v2/prisma/schema.prisma
+++ b/src/management-system-v2/prisma/schema.prisma
@@ -36,6 +36,7 @@ model User {
   image           String?
   favourites      String[]
   processes       Process[]
+  configs         Config[]
   ownedSpaces     Space[]        @relation("spaceOwner")
   memberIn        Membership[]
   folders         Folder[]
@@ -140,6 +141,7 @@ model Space {
   processes          Process[]
   roles              Role[]
   engines            Engine[]
+  configs            Config[]
 
   @@map("space")
 }
@@ -227,3 +229,47 @@ model Engine {
 
   @@map("engine")
 }
+
+model Config {
+  id                    String    @id @default(uuid())
+  space                 Space     @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+  environmentId         String  
+  creator               User      @relation(fields: [creatorId], references: [id], onDelete: Cascade)
+  creatorId             String  
+  createdOn             DateTime
+  lastEditedOn          DateTime  @updatedAt
+
+  data                  Json      @db.JsonB
+  
+  @@map("config")
+}
+
+model ConfigParameter {
+  id                        String            @id @default(uuid())
+  data                      Json              @db.JsonB
+  
+  @@map("config_parameter")
+}
+
+model MachineConfig {
+  id                        String            @id @default(uuid())
+  data                      Json              @db.JsonB
+  
+  @@map("machine_config")
+}
+
+model TargetConfig {
+  id                        String            @id @default(uuid())
+  data                      Json              @db.JsonB
+
+  @@map("target_config")
+}
+
+model ConfigVersions {
+  id                        String            @id @default(uuid())
+  parentId                  String
+  data                      Json              @db.JsonB
+
+  @@map("config_version")
+}
+


Reviews:
user: login: anishsapkota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T22:01:56Z
user: login: anishsapkota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T12:53:58Z
user: login: anishsapkota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T12:54:21Z
user: login: anishsapkota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T12:54:27Z
user: login: anishsapkota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T12:54:34Z
user: login: anishsapkota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T12:54:41Z
user: login: anishsapkota
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:17:51Z

Review comments:
diff_hunk: @@ -1 +1,987 @@
-// TODO
+'use server';
+
+import { v4 } from 'uuid';
+import {
+  AbstractConfig,
+  AbstractConfigInput,
+  AbstractConfigInputSchema,
+  MachineConfig,
+  Parameter,
+  ParentConfig,
+  StoredMachineConfig,
+  StoredParameter,
+  StoredParameterZod,
+  StoredParentConfig,
+  StoredTargetConfig,
+  TargetConfig,
+} from '../machine-config-schema';
+import { getFolderById, getRootFolder } from './folders';
+import db from '.';
+import { userError } from '@/lib/user-error';
+import { getCurrentUser } from '@/components/auth';
+import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
+import { asyncFilter, asyncForEach, asyncMap } from '@/lib/helpers/javascriptHelpers';
+import {
+  defaultConfiguration,
+  defaultParentConfiguration,
+} from '@/app/(dashboard)/[environmentId]/machine-config/configuration-helper';
+
+/**
+ * Creates a copy of a parameter and all its nested parameters to be pasted as child of a given parent. Stores the copied parameter.
+ * @param parameterId ID of the parameter which is to be copied.
+ * @param parentId ID of the parent where the parameter is to be copied to.
+ * @param parentType Type of the parent. (_'parameter'_ | _'machine-config'_ | _'target-config'_ | _'parent-config'_)
+ * @return Returns the newly generated ID for the parameter.
+ * @throws {Error} if the ID of the parameter does not exist.
+ */
+export async function copyParameter(
+  parameterId: string,
+  parentId: string,
+  parentType: StoredParameter['parentType'],
+) {
+  const storedParameterResult = await db.configParameter.findUnique({ where: { id: parameterId } });
+  const storedParameter = storedParameterResult?.data as StoredParameter;
+
+  if (!storedParameter) throw new Error(`Parameter with id ${parameterId} does not exist!`);
+
+  // this creates a deep clone of the element to prevent changes in the content of one to affect the other
+  const copy = StoredParameterZod.parse(storedParameter);
+
+  const newId = v4();
+  copy.id = newId;
+  copy.linkedParameters = [];
+  copy.parentId = parentId;
+  copy.parentType = parentType;
+
+  // recursively copy all referenced parameters
+  copy.parameters = await asyncMap(copy.parameters, (id) => copyParameter(id, newId, 'parameter'));
+
+  await db.configParameter.create({ data: { id: copy.id, data: copy } });
+
+  return newId;
+}
+
+/**
+ * Creates a copy of a config and all its nested parameters to be pasted as child of a given parent. Stores the copied config.
+ * @param configId ID of the config which is to be copied.
+ * @param configType Type of the config. (_'machine-config'_ | _'target-config'_)
+ * @param parentId ID of the parent where the config is to be copied to.
+ * @return Returns the newly generated ID for the config.
+ * @throws {Error} if the ID of the config does not exist.
+ */
+export async function copyConfig(
+  configId: string,
+  configType: Exclude<AbstractConfig['type'], 'config'>,
+  parentId: string,
+) {
+  let config;
+  if (configType === 'target-config') {
+    const targetResult = await db.targetConfig.findUnique({ where: { id: configId } });
+    config = targetResult?.data as unknown as StoredTargetConfig;
+  } else {
+    const machineResult = await db.machineConfig.findUnique({ where: { id: configId } });
+    config = machineResult?.data as unknown as StoredMachineConfig;
+  }
+
+  if (!config) throw new Error(`(Target/Machine)config with id ${configId} does not exist`);
+
+  // deep copy the config
+  const copy = JSON.parse(JSON.stringify(config)) as typeof config;
+
+  const newId = v4();
+  copy.id = newId;
+  copy.parentId = parentId;
+
+  copy.parameters = await asyncMap(copy.parameters, (id) => copyParameter(id, newId, configType));
+  copy.metadata = await asyncMap(copy.metadata, (id) => copyParameter(id, newId, configType));
+
path: src/management-system-v2/lib/data/db/machine-config.ts
user: login: anishsapkota
body: you can use Promise.all() here probably, to execute both maps concurrently
```
[copy.parameters, copy.metadata] = await Promise.all([
  asyncMap(copy.parameters, (id) => copyParameter(id, newId, configType)),
  asyncMap(copy.metadata, (id) => copyParameter(id, newId, configType))
]);
```
created_at: 2025-03-24T22:01:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,3 +229,47 @@ model Engine {
 
   @@map("engine")
 }
+
+model Config {
+  id                    String    @id @default(uuid())
+  space                 Space     @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+  environmentId         String  
+  creator               User      @relation(fields: [creatorId], references: [id], onDelete: Cascade)
+  creatorId             String  
+  createdOn             DateTime
+  lastEditedOn          DateTime  @updatedAt
+
+  data                  Json
+  
path: src/management-system-v2/prisma/schema.prisma
user: login: anishsapkota
body: can we use @db.JsonB ?

In Jsonb, the JSON is stored as binary and more efficient for querying 
created_at: 2025-03-25T12:53:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,3 +229,47 @@ model Engine {
 
   @@map("engine")
 }
+
+model Config {
+  id                    String    @id @default(uuid())
+  space                 Space     @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+  environmentId         String  
+  creator               User      @relation(fields: [creatorId], references: [id], onDelete: Cascade)
+  creatorId             String  
+  createdOn             DateTime
+  lastEditedOn          DateTime  @updatedAt
+
+  data                  Json
+  
+  @@map("config")
+}
+
+model ConfigParameter {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
path: src/management-system-v2/prisma/schema.prisma
user: login: anishsapkota
body: same as above
created_at: 2025-03-25T12:54:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,3 +229,47 @@ model Engine {
 
   @@map("engine")
 }
+
+model Config {
+  id                    String    @id @default(uuid())
+  space                 Space     @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+  environmentId         String  
+  creator               User      @relation(fields: [creatorId], references: [id], onDelete: Cascade)
+  creatorId             String  
+  createdOn             DateTime
+  lastEditedOn          DateTime  @updatedAt
+
+  data                  Json
+  
+  @@map("config")
+}
+
+model ConfigParameter {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
+  @@map("config_parameter")
+}
+
+model MachineConfig {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
path: src/management-system-v2/prisma/schema.prisma
user: login: anishsapkota
body: same as above
created_at: 2025-03-25T12:54:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,3 +229,47 @@ model Engine {
 
   @@map("engine")
 }
+
+model Config {
+  id                    String    @id @default(uuid())
+  space                 Space     @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+  environmentId         String  
+  creator               User      @relation(fields: [creatorId], references: [id], onDelete: Cascade)
+  creatorId             String  
+  createdOn             DateTime
+  lastEditedOn          DateTime  @updatedAt
+
+  data                  Json
+  
+  @@map("config")
+}
+
+model ConfigParameter {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
+  @@map("config_parameter")
+}
+
+model MachineConfig {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
+  @@map("machine_config")
+}
+
+model TargetConfig {
+  id                        String            @id @default(uuid())
+  data                      Json
+
path: src/management-system-v2/prisma/schema.prisma
user: login: anishsapkota
body: same as above
created_at: 2025-03-25T12:54:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -227,3 +229,47 @@ model Engine {
 
   @@map("engine")
 }
+
+model Config {
+  id                    String    @id @default(uuid())
+  space                 Space     @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+  environmentId         String  
+  creator               User      @relation(fields: [creatorId], references: [id], onDelete: Cascade)
+  creatorId             String  
+  createdOn             DateTime
+  lastEditedOn          DateTime  @updatedAt
+
+  data                  Json
+  
+  @@map("config")
+}
+
+model ConfigParameter {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
+  @@map("config_parameter")
+}
+
+model MachineConfig {
+  id                        String            @id @default(uuid())
+  data                      Json
+  
+  @@map("machine_config")
+}
+
+model TargetConfig {
+  id                        String            @id @default(uuid())
+  data                      Json
+
+  @@map("target_config")
+}
+
+model ConfigVersions {
+  id                        String            @id @default(uuid())
+  parentId                  String
+  data                      Json
+
path: src/management-system-v2/prisma/schema.prisma
user: login: anishsapkota
body: same as above
created_at: 2025-03-25T12:54:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-14T13:13:31Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/13857200059)

✅ Successfully created Preview Deployment.

https://pr-513---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-25T15:46:34Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/14063911553)

✅ Successfully created Preview Deployment.

https://pr-513---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-26T15:07:41Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/14086558570)

✅ Successfully created Preview Deployment.

https://pr-513---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-26T17:31:07Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/14089618794)

✅ Successfully created Preview Deployment.

https://pr-513---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-26T20:22:29Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/14092637098)

✅ Successfully created Preview Deployment.

https://pr-513---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE