diff --git a/dbrepo-authentication-service/dbrepo-realm.json b/dbrepo-authentication-service/dbrepo-realm.json
index 0dbcd6bb725f9867788bfc442b033082d16bd672..99be5195d410b5b78f99d119b781df093d43425d 100644
--- a/dbrepo-authentication-service/dbrepo-realm.json
+++ b/dbrepo-authentication-service/dbrepo-realm.json
@@ -206,6 +206,14 @@
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
+    }, {
+      "id" : "e4cfdc4d-2373-477b-a8df-161db99aba00",
+      "name" : "create-foreign-identifier",
+      "description" : "${create-foreign-identifier}",
+      "composite" : false,
+      "clientRole" : false,
+      "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
+      "attributes" : { }
     }, {
       "id" : "09147c48-273b-450b-8b11-7ef9b9245244",
       "name" : "export-table-data",
@@ -280,10 +288,7 @@
       "id" : "abd2d9ee-ebc4-4d0a-839e-6b588a6d442a",
       "name" : "default-roles-dbrepo",
       "description" : "${role_default-roles}",
-      "composite" : true,
-      "composites" : {
-        "realm" : [ "default-researcher-roles" ]
-      },
+      "composite" : false,
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
@@ -341,7 +346,7 @@
       "description" : "${escalated-identifier-handling}",
       "composite" : true,
       "composites" : {
-        "realm" : [ "delete-identifier", "modify-identifier-metadata" ]
+        "realm" : [ "delete-identifier", "create-foreign-identifier", "modify-identifier-metadata" ]
       },
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
@@ -844,54 +849,39 @@
     }
   },
   "groups" : [ {
-    "id" : "16c0fda1-864b-4c27-8755-0fdffa577000",
-    "name" : "External",
-    "path" : "/External",
+    "id" : "f2ce17fe-7b15-47a4-bbf8-86f415298fa9",
+    "name" : "data-stewards",
+    "path" : "/data-stewards",
     "attributes" : { },
-    "realmRoles" : [ ],
+    "realmRoles" : [ "default-data-steward-roles" ],
     "clientRoles" : { },
     "subGroups" : [ ]
   }, {
-    "id" : "1d8e6a45-1c77-453b-a5a8-9096e81e8b9b",
-    "name" : "Internal",
-    "path" : "/Internal",
+    "id" : "124d9888-0b6e-46aa-8225-077dcedaf16e",
+    "name" : "developers",
+    "path" : "/developers",
     "attributes" : { },
-    "realmRoles" : [ ],
+    "realmRoles" : [ "default-developer-roles" ],
     "clientRoles" : { },
-    "subGroups" : [ {
-      "id" : "7fe5a587-d2bc-4d3d-980b-324c3336862c",
-      "name" : "Developers",
-      "path" : "/Internal/Developers",
-      "attributes" : { },
-      "realmRoles" : [ ],
-      "clientRoles" : { },
-      "subGroups" : [ ]
-    }, {
-      "id" : "cc357d61-bfbf-4ed7-93d3-122113f438e3",
-      "name" : "Researchers",
-      "path" : "/Internal/Researchers",
-      "attributes" : { },
-      "realmRoles" : [ ],
-      "clientRoles" : { },
-      "subGroups" : [ ]
-    }, {
-      "id" : "c33f23e6-f7d0-4dee-9af4-f68773bad280",
-      "name" : "Data Stewards",
-      "path" : "/Internal/Data Stewards",
-      "attributes" : { },
-      "realmRoles" : [ ],
-      "clientRoles" : { },
-      "subGroups" : [ ]
-    } ]
+    "subGroups" : [ ]
+  }, {
+    "id" : "f467c38e-9041-4faa-ae0b-39cec65ff4db",
+    "name" : "researchers",
+    "path" : "/researchers",
+    "attributes" : { },
+    "realmRoles" : [ "default-researcher-roles" ],
+    "clientRoles" : { },
+    "subGroups" : [ ]
   } ],
   "defaultRole" : {
     "id" : "abd2d9ee-ebc4-4d0a-839e-6b588a6d442a",
     "name" : "default-roles-dbrepo",
     "description" : "${role_default-roles}",
-    "composite" : true,
+    "composite" : false,
     "clientRole" : false,
     "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0"
   },
+  "defaultGroups" : [ "/researchers" ],
   "requiredCredentials" : [ "password" ],
   "otpPolicyType" : "totp",
   "otpPolicyAlgorithm" : "HmacSHA1",
@@ -900,7 +890,7 @@
   "otpPolicyLookAheadWindow" : 1,
   "otpPolicyPeriod" : 30,
   "otpPolicyCodeReusable" : false,
-  "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName", "totpAppFreeOTPName" ],
+  "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName" ],
   "webAuthnPolicyRpEntityName" : "keycloak",
   "webAuthnPolicySignatureAlgorithms" : [ "ES256" ],
   "webAuthnPolicyRpId" : "",
@@ -1890,7 +1880,7 @@
       "subType" : "authenticated",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ]
       }
     }, {
       "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979",
@@ -1899,7 +1889,7 @@
       "subType" : "anonymous",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper" ]
       }
     } ],
     "org.keycloak.keys.KeyProvider" : [ {
@@ -1951,7 +1941,7 @@
   "internationalizationEnabled" : false,
   "supportedLocales" : [ ],
   "authenticationFlows" : [ {
-    "id" : "2659228d-907e-4832-9478-93c1537361ad",
+    "id" : "e29d33f1-c44c-4f6b-b8ca-54eb4b468f1d",
     "alias" : "Account verification options",
     "description" : "Method with which to verity the existing account",
     "providerId" : "basic-flow",
@@ -1973,7 +1963,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "6f2ebe37-d1c9-4359-8516-05f7c435a09c",
+    "id" : "bc4badef-d637-4448-b345-3e79f24290b0",
     "alias" : "Authentication Options",
     "description" : "Authentication options.",
     "providerId" : "basic-flow",
@@ -2002,7 +1992,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "e8ecd6b9-5991-4a84-b52d-bc9961d05f9a",
+    "id" : "06ddc635-bb0d-443a-ab6b-7f56be7fd59a",
     "alias" : "Browser - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2024,7 +2014,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "91815128-e40c-4800-946d-09d2f33a1f39",
+    "id" : "a8230646-a4a8-40c2-a37a-e7ac4daf9a67",
     "alias" : "Direct Grant - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2046,7 +2036,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "90383773-dec6-4dc3-a6ff-5dfdde0ecda5",
+    "id" : "e84a6624-232f-401e-9937-6c5865b2a341",
     "alias" : "First broker login - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2068,7 +2058,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "ae5a2e94-c9b0-4851-b88e-4b0acacb645f",
+    "id" : "00094f7f-cdcb-4670-87b0-d75ed3fa986b",
     "alias" : "Handle Existing Account",
     "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
     "providerId" : "basic-flow",
@@ -2090,7 +2080,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "a754b5ae-bc9d-4a98-ad59-6cc8ff27d016",
+    "id" : "e8cea56d-d07c-4696-b389-0f70449ba26d",
     "alias" : "Reset - Conditional OTP",
     "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
     "providerId" : "basic-flow",
@@ -2112,7 +2102,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "bdf46041-56db-445a-b4f3-3d000b239b8b",
+    "id" : "505a65be-a4f8-4e6e-a1a0-21d20d3f07a5",
     "alias" : "User creation or linking",
     "description" : "Flow for the existing/non-existing user alternatives",
     "providerId" : "basic-flow",
@@ -2135,7 +2125,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "1915f7a6-19a4-4e54-8010-73c939f18afe",
+    "id" : "9c31dad3-3367-4f7e-9fd0-3c855e1da0b8",
     "alias" : "Verify Existing Account by Re-authentication",
     "description" : "Reauthentication of existing account",
     "providerId" : "basic-flow",
@@ -2157,7 +2147,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "b3d8705b-5235-43da-89c6-ce55fb304ae5",
+    "id" : "e6fdd307-ca87-43f0-9f74-2bc567cc02db",
     "alias" : "browser",
     "description" : "browser based authentication",
     "providerId" : "basic-flow",
@@ -2193,7 +2183,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "33efb232-46c9-4482-ba8e-7e452668bafa",
+    "id" : "b42d375b-1cb9-4cc7-be7d-fae32791d0a8",
     "alias" : "clients",
     "description" : "Base authentication for clients",
     "providerId" : "client-flow",
@@ -2229,7 +2219,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "f94e82fa-882d-4952-88c9-3aa37d9be1b6",
+    "id" : "3c534feb-1736-4ff4-8187-192e49f21fd9",
     "alias" : "direct grant",
     "description" : "OpenID Connect Resource Owner Grant",
     "providerId" : "basic-flow",
@@ -2258,7 +2248,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "15948c95-df50-4304-8695-f586e5351979",
+    "id" : "5f3c06ca-8691-4c6c-8892-8d29563da9d0",
     "alias" : "docker auth",
     "description" : "Used by Docker clients to authenticate against the IDP",
     "providerId" : "basic-flow",
@@ -2273,7 +2263,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "1983ebae-21b2-4a31-8517-c7a4746fedeb",
+    "id" : "76abf3cd-3635-494e-b75d-a3132a7933a5",
     "alias" : "first broker login",
     "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
     "providerId" : "basic-flow",
@@ -2296,7 +2286,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "f9893ead-c7a1-404b-aca2-d24e03eb5c16",
+    "id" : "d44f3e97-d11a-49ba-a3bf-7c8489de290d",
     "alias" : "forms",
     "description" : "Username, password, otp and other auth forms.",
     "providerId" : "basic-flow",
@@ -2318,7 +2308,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d830402e-475d-46b5-a9d7-1105436d9092",
+    "id" : "7e60e213-6272-484c-b620-d7cf4f98f950",
     "alias" : "http challenge",
     "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
     "providerId" : "basic-flow",
@@ -2340,7 +2330,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "0de6040b-5167-4ab1-8e40-f633419b5890",
+    "id" : "cce9e0e9-82f4-499d-a6f7-fc556248fc25",
     "alias" : "registration",
     "description" : "registration flow",
     "providerId" : "basic-flow",
@@ -2356,7 +2346,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "84a658d1-d9ca-4090-9a86-9d45844324e2",
+    "id" : "61f08b26-e87b-4126-ac7a-c704d01e54dc",
     "alias" : "registration form",
     "description" : "registration form",
     "providerId" : "form-flow",
@@ -2392,7 +2382,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "9cc594f8-9bb0-4557-bffb-4a66b2eb0f34",
+    "id" : "d9ce077f-16c2-4c96-aba5-dd60bb3aa536",
     "alias" : "reset credentials",
     "description" : "Reset credentials for a user if they forgot their password or something",
     "providerId" : "basic-flow",
@@ -2428,7 +2418,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d2b1442c-c5db-4e3f-94f6-48196fccd207",
+    "id" : "8d4383f0-ebd2-4e0b-8d3f-2c6e1793c05b",
     "alias" : "saml ecp",
     "description" : "SAML ECP Profile Authentication Flow",
     "providerId" : "basic-flow",
@@ -2444,13 +2434,13 @@
     } ]
   } ],
   "authenticatorConfig" : [ {
-    "id" : "aebc30c4-79b7-47c5-8d13-f4cfe66aba10",
+    "id" : "1206bd7a-0525-4e21-9bc7-d9ec62c1a4bc",
     "alias" : "create unique user config",
     "config" : {
       "require.password.update.after.registration" : "false"
     }
   }, {
-    "id" : "5c00357d-8701-4848-a920-0ae1db86fdd0",
+    "id" : "f5ff277d-b059-4dff-aaf8-8c5feeca1d4c",
     "alias" : "review profile config",
     "config" : {
       "update.profile.on.first.login" : "missing"
diff --git a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
index eb7f335e0e95a5ca8f149a17ebe4371b621d044f..43cc6674b9d106548b72b73c8b4aaccd815cdadb 100644
--- a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
+++ b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
@@ -91,7 +91,7 @@ public class IdentifierEndpoint {
     @PostMapping
     @Transactional
     @Timed(value = "identifier.create", description = "Time needed to create an identifier")
-    @PreAuthorize("hasAuthority('create-identifier')")
+    @PreAuthorize("hasAuthority('create-identifier') or hasAuthority('create-foreign-identifier')")
     @Operation(summary = "Create identifier", security = @SecurityRequirement(name = "bearerAuth"))
     @ApiResponses(value = {
             @ApiResponse(responseCode = "201",
@@ -104,8 +104,8 @@ public class IdentifierEndpoint {
                     content = {@Content(
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "404",
-                    description = "Query, database or user could not be found",
+            @ApiResponse(responseCode = "403",
+                    description = "Insufficient access rights or authorities",
                     content = {@Content(
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
@@ -134,7 +134,7 @@ public class IdentifierEndpoint {
                                                 @NotNull @RequestHeader(name = "Authorization") String authorization,
                                                 @NotNull Principal principal)
             throws IdentifierAlreadyExistsException, QueryNotFoundException, IdentifierPublishingNotAllowedException,
-            RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, IdentifierRequestException, AccessDeniedException {
+            RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, IdentifierRequestException, NotAllowedException {
         log.debug("endpoint create identifier, data={}, authorization=(hidden), principal={}", data, principal);
         if (data.getType().equals(IdentifierTypeDto.SUBSET) && data.getQid() == null) {
             log.error("Identifier of type subset need to have a qid present");
@@ -144,63 +144,16 @@ public class IdentifierEndpoint {
             throw new IdentifierRequestException("Identifier of type database must not have a qid present");
         }
         final User user = userService.findByUsername(principal.getName());
-        final DatabaseAccess access = accessService.find(data.getDbid(), user.getId());
+        try {
+            accessService.find(data.getDbid(), user.getId());
+        } catch (AccessDeniedException e) {
+            if (!User.hasRole(principal, "create-foreign-identifier")) {
+                log.error("Failed to create identifier: insufficient access");
+                throw new NotAllowedException("Failed to create identifier: insufficient access");
+            }
+        }
         final Identifier identifier = identifierService.create(data, principal, authorization);
         return ResponseEntity.status(HttpStatus.CREATED)
                 .body(identifierMapper.identifierToIdentifierDto(identifier));
     }
-
-    @PutMapping("/{id}")
-    @Transactional
-    @Timed(value = "identifier.update", description = "Time needed to update an identifier")
-    @PreAuthorize("hasAuthority('update-identifier')")
-    @Operation(summary = "Update some identifier", security = @SecurityRequirement(name = "bearerAuth"))
-    @ApiResponses(value = {
-            @ApiResponse(responseCode = "202",
-                    description = "Updated identifier",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = IdentifierDto.class))}),
-            @ApiResponse(responseCode = "404",
-                    description = "Identifier could not be found",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "406",
-                    description = "Updating identifier not allowed",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-    })
-    public ResponseEntity<IdentifierDto> update(@NotNull @PathVariable("id") Long id,
-                                                @NotNull @Valid @RequestBody IdentifierDto data)
-            throws IdentifierNotFoundException, IdentifierRequestException {
-        log.debug("endpoint update identifier, id={}, data={}", id, data);
-        final Identifier identifier = identifierService.update(id, data);
-        return ResponseEntity.accepted()
-                .body(identifierMapper.identifierToIdentifierDto(identifier));
-    }
-
-    @DeleteMapping("/{id}")
-    @Transactional
-    @Timed(value = "identifier.delete", description = "Time needed to delete an identifier")
-    @PreAuthorize("hasAuthority('delete-identifier')")
-    @Operation(summary = "Delete some identifier", security = @SecurityRequirement(name = "bearerAuth"))
-    @ApiResponses(value = {
-            @ApiResponse(responseCode = "202",
-                    description = "Deleted identifier",
-                    content = {@Content}),
-            @ApiResponse(responseCode = "404",
-                    description = "Identifier could not be found",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-    })
-    public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long id)
-            throws IdentifierNotFoundException, NotAllowedException {
-        log.debug("endpoint delete identifier, id={}", id);
-        identifierService.delete(id);
-        return ResponseEntity.accepted()
-                .build();
-    }
 }
diff --git a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
index 602ae69e091e35317d125a58a77da72deaa0b27f..6f30f79be88c9ece0f87d7c9ff1378ba7b0dd311 100644
--- a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
+++ b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
@@ -3,20 +3,22 @@ package at.tuwien.endpoints;
 import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.api.identifier.BibliographyTypeDto;
 import at.tuwien.api.identifier.IdentifierDto;
+import at.tuwien.api.identifier.IdentifierUpdateDto;
 import at.tuwien.config.EndpointConfig;
 import at.tuwien.entities.identifier.Identifier;
-import at.tuwien.exception.IdentifierNotFoundException;
-import at.tuwien.exception.IdentifierRequestException;
-import at.tuwien.exception.QueryNotFoundException;
-import at.tuwien.exception.RemoteUnavailableException;
+import at.tuwien.entities.user.User;
+import at.tuwien.exception.*;
 import at.tuwien.mapper.IdentifierMapper;
+import at.tuwien.service.AccessService;
 import at.tuwien.service.IdentifierService;
+import at.tuwien.service.UserService;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.InputStreamResource;
@@ -28,6 +30,8 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.security.Principal;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -37,13 +41,17 @@ import java.util.regex.Pattern;
 @RequestMapping("/api/pid")
 public class PersistenceEndpoint {
 
+    private final UserService userService;
+    private final AccessService accessService;
     private final EndpointConfig endpointConfig;
     private final IdentifierMapper identifierMapper;
     private final IdentifierService identifierService;
 
     @Autowired
-    public PersistenceEndpoint(EndpointConfig endpointConfig, IdentifierMapper identifierMapper,
-                               IdentifierService identifierService) {
+    public PersistenceEndpoint(UserService userService, AccessService accessService, EndpointConfig endpointConfig,
+                               IdentifierMapper identifierMapper, IdentifierService identifierService) {
+        this.userService = userService;
+        this.accessService = accessService;
         this.endpointConfig = endpointConfig;
         this.identifierMapper = identifierMapper;
         this.identifierService = identifierService;
@@ -141,4 +149,95 @@ public class PersistenceEndpoint {
                 .build();
     }
 
+    @PutMapping("/{id}")
+    @Transactional
+    @Timed(value = "identifier.update", description = "Time needed to update an identifier")
+    @PreAuthorize("hasAuthority('modify-identifier-metadata')")
+    @Operation(summary = "Update some identifier", security = @SecurityRequirement(name = "bearerAuth"))
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "202",
+                    description = "Updated identifier",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = IdentifierDto.class))}),
+            @ApiResponse(responseCode = "400",
+                    description = "Identifier data is not valid to the form",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "404",
+                    description = "Identifier or user could not be found",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "405",
+                    description = "Updating identifier not permitted",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "406",
+                    description = "Updating identifier not allowed",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+    })
+    public ResponseEntity<IdentifierDto> update(@NotNull @PathVariable("id") Long id,
+                                                @NotNull @Valid @RequestBody IdentifierUpdateDto data,
+                                                @NotNull Principal principal)
+            throws IdentifierNotFoundException, IdentifierRequestException, UserNotFoundException, NotAllowedException {
+        log.debug("endpoint update identifier, id={}, data={}", id, data);
+        final Identifier identifier = identifierService.find(id);
+        final User user = userService.findByUsername(principal.getName());
+        try {
+            accessService.find(identifier.getDatabaseId(), user.getId());
+        } catch (AccessDeniedException e) {
+            if (!User.hasRole(principal, "modify-identifier-metadata")) {
+                log.error("Failed to update identifier: insufficient access");
+                throw new NotAllowedException("Failed to update identifier: insufficient access");
+            }
+        }
+        /* check */
+        if (identifier.getDoi() != null && !identifier.getDoi().equals(data.getDoi())) {
+            log.error("Failed to update identifier: once attached the DOI cannot be changed");
+            throw new IdentifierRequestException("Failed to update identifier: once attached the DOI cannot be changed");
+        }
+        final IdentifierDto dto = identifierMapper.identifierToIdentifierDto(identifierService.update(id, data));
+        log.debug("update identifier resulted in dto={}", dto);
+        return ResponseEntity.accepted()
+                .body(dto);
+    }
+
+    @DeleteMapping("/{id}")
+    @Transactional
+    @Timed(value = "identifier.delete", description = "Time needed to delete an identifier")
+    @PreAuthorize("hasAuthority('delete-identifier')")
+    @Operation(summary = "Delete some identifier", security = @SecurityRequirement(name = "bearerAuth"))
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "202",
+                    description = "Deleted identifier",
+                    content = {@Content}),
+            @ApiResponse(responseCode = "404",
+                    description = "Identifier could not be found",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "405",
+                    description = "Deleting identifier not permitted",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))})
+    })
+    public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long id)
+            throws IdentifierNotFoundException, NotAllowedException {
+        log.debug("endpoint delete identifier, id={}", id);
+        final Identifier identifier = identifierService.find(id);
+        if (identifier.getDoi() != null) {
+            log.error("Failed to delete identifier: a DOI is already attached");
+            throw new NotAllowedException("Failed to delete identifier: a DOI is already attached");
+        }
+        identifierService.delete(id);
+        return ResponseEntity.accepted()
+                .build();
+    }
+
 }
diff --git a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointIntegrationTest.java b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointIntegrationTest.java
index 831bf171d56a2512a056a6f14fcc6fd0db43e56f..a3a0191d1afebd99dee10509164f63f6ffc2b1d1 100644
--- a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointIntegrationTest.java
+++ b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointIntegrationTest.java
@@ -6,6 +6,7 @@ import at.tuwien.api.identifier.IdentifierTypeDto;
 import at.tuwien.config.IndexInitializer;
 import at.tuwien.config.ReadyConfig;
 import at.tuwien.endpoints.IdentifierEndpoint;
+import at.tuwien.exception.NotAllowedException;
 import at.tuwien.repository.jpa.*;
 import lombok.extern.log4j.Log4j2;
 import org.junit.jupiter.api.BeforeEach;
@@ -87,7 +88,7 @@ public class IdentifierEndpointIntegrationTest extends BaseUnitTest {
     public void list_hasRole_succeeds() {
 
         /* mock */
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
 
         /* test */
         final List<IdentifierDto> response = this.generic_list(null, null, null);
@@ -103,7 +104,7 @@ public class IdentifierEndpointIntegrationTest extends BaseUnitTest {
     public void list_noRole_succeeds() {
 
         /* mock */
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
 
         /* test */
         final List<IdentifierDto> response = this.generic_list(null, null, null);
@@ -119,7 +120,7 @@ public class IdentifierEndpointIntegrationTest extends BaseUnitTest {
     public void list_databaseId_succeeds() {
 
         /* mock */
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
 
         /* test */
         final List<IdentifierDto> response = this.generic_list(DATABASE_1_ID, null, null);
@@ -139,10 +140,10 @@ public class IdentifierEndpointIntegrationTest extends BaseUnitTest {
         containerRepository.save(CONTAINER_4_SIMPLE);
         databaseRepository.save(DATABASE_3_SIMPLE);
         databaseRepository.save(DATABASE_4_SIMPLE);
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
-        identifierRepository.save(IDENTIFIER_2_SIMPLE);
-        identifierRepository.save(IDENTIFIER_3_SIMPLE);
-        identifierRepository.save(IDENTIFIER_4_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
+        identifierRepository.save(IDENTIFIER_2);
+        identifierRepository.save(IDENTIFIER_3);
+        identifierRepository.save(IDENTIFIER_4);
 
         /* test */
         final List<IdentifierDto> response = this.generic_list(DATABASE_4_ID, null, IdentifierTypeDto.DATABASE);
@@ -158,7 +159,7 @@ public class IdentifierEndpointIntegrationTest extends BaseUnitTest {
     public void list_subsetIdAndType_succeeds() {
 
         /* mock */
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
 
         /* test */
         final List<IdentifierDto> response = this.generic_list(DATABASE_1_ID, QUERY_1_ID, IdentifierTypeDto.SUBSET);
@@ -188,7 +189,7 @@ public class IdentifierEndpointIntegrationTest extends BaseUnitTest {
         databaseRepository.save(DATABASE_3_SIMPLE);
 
         /* test */
-        assertThrows(at.tuwien.exception.AccessDeniedException.class, () -> {
+        assertThrows(NotAllowedException.class, () -> {
             identifierEndpoint.create(IDENTIFIER_3_DTO_REQUEST, "ABC", USER_1_PRINCIPAL);
         });
     }
diff --git a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointUnitTest.java b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointUnitTest.java
index 0d1f7ce1cc92381d4f3de24101a43c44f851fd51..cab5bb1b8973ff40ded5eec41aa3699f46952a78 100644
--- a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointUnitTest.java
+++ b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/IdentifierEndpointUnitTest.java
@@ -10,12 +10,14 @@ import at.tuwien.config.ReadyConfig;
 import at.tuwien.endpoints.IdentifierEndpoint;
 import at.tuwien.endpoints.PersistenceEndpoint;
 import at.tuwien.entities.database.Database;
+import at.tuwien.entities.database.DatabaseAccess;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.entities.identifier.RelatedIdentifier;
 import at.tuwien.entities.user.User;
 import at.tuwien.exception.*;
 import at.tuwien.gateway.QueryServiceGateway;
 import at.tuwien.repository.jpa.*;
+import at.tuwien.service.AccessService;
 import at.tuwien.service.IdentifierService;
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.Test;
@@ -37,10 +39,11 @@ import java.io.IOException;
 import java.security.Principal;
 import java.util.List;
 import java.util.Optional;
+import java.util.UUID;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(SpringExtension.class)
@@ -62,6 +65,9 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
     @MockBean
     private IdentifierRepository identifierRepository;
 
+    @MockBean
+    private AccessService accessService;
+
     @MockBean
     private UserRepository userRepository;
 
@@ -148,7 +154,7 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
 
         /* test */
         assertThrows(AuthenticationCredentialsNotFoundException.class, () -> {
-            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, IDENTIFIER_1_DTO_REQUEST, null, null, null, null);
+            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, null, IDENTIFIER_1_DTO_REQUEST, null, null, null, null, null);
         });
     }
 
@@ -156,23 +162,23 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
     @WithMockUser(username = USER_1_USERNAME, authorities = {"create-identifier"})
     public void create_hasRoleDatabase_succeeds() throws IdentifierAlreadyExistsException,
             UserNotFoundException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException,
-            IdentifierPublishingNotAllowedException, IdentifierRequestException, at.tuwien.exception.AccessDeniedException {
+            IdentifierPublishingNotAllowedException, IdentifierRequestException, NotAllowedException, at.tuwien.exception.AccessDeniedException {
 
         /* mock */
         when(accessRepository.findByHdbidAndHuserid(DATABASE_1_ID, USER_1_ID))
                 .thenReturn(Optional.of(DATABASE_1_RESEARCHER_READ_ACCESS));
 
         /* test */
-        generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, IDENTIFIER_1_DTO_REQUEST, IDENTIFIER_1, USER_1_PRINCIPAL, USER_1_USERNAME, USER_1);
+        generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, DATABASE_1_RESEARCHER_READ_ACCESS, IDENTIFIER_1_DTO_REQUEST, IDENTIFIER_1, USER_1_PRINCIPAL, USER_1_ID, USER_1_USERNAME, USER_1);
     }
 
     @Test
-    @WithMockUser(username = USER_2_USERNAME, authorities = {"create-identifier"})
+    @WithMockUser(username = USER_3_USERNAME, authorities = {"create-identifier"})
     public void create_hasRoleDatabaseNoAccess_fails() {
 
         /* test */
-        assertThrows(at.tuwien.exception.AccessDeniedException.class, () -> {
-            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, IDENTIFIER_1_DTO_REQUEST, IDENTIFIER_1, USER_2_PRINCIPAL, USER_2_USERNAME, USER_2);
+        assertThrows(NotAllowedException.class, () -> {
+            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, null, IDENTIFIER_1_DTO_REQUEST, IDENTIFIER_1, USER_3_PRINCIPAL, USER_3_ID, USER_3_USERNAME, USER_3);
         });
     }
 
@@ -182,7 +188,7 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
 
         /* test */
         assertThrows(AccessDeniedException.class, () -> {
-            generic_create(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, IDENTIFIER_2_DTO_REQUEST, IDENTIFIER_2, null, null, null);
+            generic_create(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, null, IDENTIFIER_2_DTO_REQUEST, IDENTIFIER_2, null, null, null, null);
         });
     }
 
@@ -190,14 +196,11 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
     @WithMockUser(username = USER_2_USERNAME, authorities = {"create-identifier"})
     public void create_hasRoleReadAccessQuery_succeeds() throws IdentifierAlreadyExistsException,
             UserNotFoundException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException,
-            IdentifierPublishingNotAllowedException, IdentifierRequestException, at.tuwien.exception.AccessDeniedException {
-
-        /* mock */
-        when(accessRepository.findByHdbidAndHuserid(DATABASE_2_ID, USER_2_ID))
-                .thenReturn(Optional.of(DATABASE_2_RESEARCHER_READ_ACCESS));
+            IdentifierPublishingNotAllowedException, IdentifierRequestException, NotAllowedException,
+            at.tuwien.exception.AccessDeniedException {
 
         /* test */
-        generic_create(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, IDENTIFIER_2_DTO_REQUEST, IDENTIFIER_2, USER_2_PRINCIPAL, USER_2_USERNAME, USER_2);
+        generic_create(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, DATABASE_2_RESEARCHER_READ_ACCESS, IDENTIFIER_2_DTO_REQUEST, IDENTIFIER_2, USER_2_PRINCIPAL, USER_2_ID, USER_2_USERNAME, USER_2);
     }
 
     @Test
@@ -219,7 +222,7 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
 
         /* test */
         assertThrows(IdentifierRequestException.class, () -> {
-            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, request, null, USER_1_PRINCIPAL, USER_1_USERNAME, USER_1);
+            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, DATABASE_1_RESEARCHER_READ_ACCESS, request, null, USER_1_PRINCIPAL, USER_1_ID, USER_1_USERNAME, USER_1);
         });
     }
 
@@ -243,78 +246,30 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
 
         /* test */
         assertThrows(IdentifierRequestException.class, () -> {
-            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, request, null, USER_1_PRINCIPAL, USER_1_USERNAME, USER_1);
+            generic_create(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, DATABASE_1_RESEARCHER_READ_ACCESS, request, null, USER_1_PRINCIPAL, USER_1_ID, USER_1_USERNAME, USER_1);
         });
     }
 
     @Test
     @WithMockUser(username = USER_1_USERNAME, authorities = {"create-identifier"})
-    public void create_query_fails() {
+    public void create_queryForeign_fails() {
 
         /* test */
-        assertThrows(at.tuwien.exception.AccessDeniedException.class, () -> {
-            generic_create(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, IDENTIFIER_2_DTO_REQUEST, IDENTIFIER_2, USER_1_PRINCIPAL, USER_1_USERNAME, USER_1);
+        assertThrows(NotAllowedException.class, () -> {
+            generic_create(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, null, IDENTIFIER_2_DTO_REQUEST, IDENTIFIER_2, USER_1_PRINCIPAL, USER_1_ID, USER_1_USERNAME, USER_1);
         });
     }
 
-    @Test
-    public void update_anonymous_fails() {
-
-        /* test */
-        assertThrows(AuthenticationCredentialsNotFoundException.class, this::generic_update);
-    }
-
-    @Test
-    @WithMockUser(username = USER_1_USERNAME, authorities = {})
-    public void update_noRole_fails() {
-
-        /* test */
-        assertThrows(AccessDeniedException.class, this::generic_update);
-    }
-
-    @Test
-    @WithMockUser(username = USER_3_USERNAME, authorities = {"update-identifier"})
-    public void update_hasRole_succeeds() throws IdentifierPublishingNotAllowedException,
-            IdentifierNotFoundException, IdentifierRequestException {
-
-        /* test */
-        generic_update();
-    }
-
-    @Test
-    @WithAnonymousUser
-    public void delete_anonymous_fails() {
-
-        /* test */
-        assertThrows(AccessDeniedException.class, this::generic_delete);
-    }
-
-    @Test
-    @WithMockUser(username = USER_1_USERNAME, authorities = {})
-    public void delete_noRole_fails() {
-
-        /* test */
-        assertThrows(AccessDeniedException.class, this::generic_delete);
-    }
-
-    @Test
-    @WithMockUser(username = USER_2_USERNAME, authorities = {"delete-identifier"})
-    public void delete_hasRole_succeeds() throws NotAllowedException, IdentifierNotFoundException {
-
-        /* test */
-        this.generic_delete();
-    }
-
     /* ################################################################################################### */
     /* ## GENERIC TEST CASES                                                                            ## */
     /* ################################################################################################### */
 
-    protected void generic_create(Long containerId, Long databaseId, Database database, IdentifierCreateDto data,
-                                  Identifier identifier, Principal principal, String username, User user)
-            throws QueryNotFoundException, RemoteUnavailableException,
-            IdentifierAlreadyExistsException,
-            UserNotFoundException, DatabaseNotFoundException, IdentifierPublishingNotAllowedException,
-            IdentifierRequestException, at.tuwien.exception.AccessDeniedException {
+    protected void generic_create(Long containerId, Long databaseId, Database database, DatabaseAccess access,
+                                  IdentifierCreateDto data, Identifier identifier, Principal principal, UUID userId,
+                                  String username, User user) throws QueryNotFoundException, RemoteUnavailableException,
+            IdentifierAlreadyExistsException, UserNotFoundException, DatabaseNotFoundException,
+            IdentifierPublishingNotAllowedException, IdentifierRequestException, NotAllowedException,
+            at.tuwien.exception.AccessDeniedException {
 
         /* mock */
         when(databaseRepository.findById(databaseId))
@@ -326,6 +281,14 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
             when(userRepository.findByUsername(username))
                     .thenReturn(Optional.of(user));
         }
+        if (access != null) {
+            when(accessService.find(databaseId, userId))
+                    .thenReturn(access);
+        } else {
+            doThrow(at.tuwien.exception.AccessDeniedException.class)
+                    .when(accessService)
+                    .find(databaseId, userId);
+        }
         when(queryServiceGateway.find(containerId, databaseId, data, "ABC"))
                 .thenReturn(QUERY_1_DTO);
         when(identifierService.create(data, principal, "ABC"))
@@ -368,40 +331,4 @@ public class IdentifierEndpointUnitTest extends BaseUnitTest {
         return persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
     }
 
-    protected void generic_update()
-            throws IdentifierPublishingNotAllowedException, IdentifierNotFoundException, IdentifierRequestException {
-
-        /* mock */
-        when(identifierService.update(IDENTIFIER_3_ID, IDENTIFIER_3_DTO))
-                .thenReturn(IDENTIFIER_3);
-        when(identifierRepository.save(IDENTIFIER_3))
-                .thenReturn(IDENTIFIER_3);
-
-        /* test */
-        final ResponseEntity<IdentifierDto> response = identifierEndpoint.update(IDENTIFIER_3_ID, IDENTIFIER_3_DTO);
-        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
-        final IdentifierDto body = response.getBody();
-        assertNotNull(body);
-        assertEquals(IDENTIFIER_3_ID, body.getId());
-        assertEquals(IDENTIFIER_3_TITLE, body.getTitle());
-        assertEquals(IDENTIFIER_3_DESCRIPTION, body.getDescription());
-        assertEquals(IDENTIFIER_3_QUERY, body.getQuery());
-        assertEquals(IDENTIFIER_3_QUERY_HASH, body.getQueryHash());
-        assertEquals(IDENTIFIER_3_RESULT_NUMBER, body.getResultNumber());
-        assertEquals(IDENTIFIER_3_RESULT_HASH, body.getResultHash());
-    }
-
-    protected void generic_delete() throws IdentifierNotFoundException, NotAllowedException {
-
-        /* mock */
-        doNothing()
-                .when(identifierService)
-                .delete(IDENTIFIER_1_ID);
-
-        /* test */
-        final ResponseEntity<?> response = identifierEndpoint.delete(IDENTIFIER_1_ID);
-        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
-        assertNull(response.getBody());
-    }
-
 }
diff --git a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/PersistenceEndpointUnitTest.java b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/PersistenceEndpointUnitTest.java
index 0fe8adf13fae63b6aec2f3a2d92ee30c2453d1bb..470f11c7a85ba1f0dabb951114dbe01eaef6d9bd 100644
--- a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/PersistenceEndpointUnitTest.java
+++ b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/endpoint/PersistenceEndpointUnitTest.java
@@ -1,20 +1,19 @@
 package at.tuwien.endpoint;
 
 import at.tuwien.BaseUnitTest;
+import at.tuwien.api.identifier.BibliographyTypeDto;
 import at.tuwien.api.identifier.CreatorDto;
 import at.tuwien.api.identifier.IdentifierDto;
+import at.tuwien.api.identifier.IdentifierUpdateDto;
 import at.tuwien.config.IndexInitializer;
 import at.tuwien.config.ReadyConfig;
 import at.tuwien.endpoints.PersistenceEndpoint;
-import at.tuwien.entities.identifier.Creator;
-import at.tuwien.exception.IdentifierNotFoundException;
-import at.tuwien.exception.IdentifierRequestException;
-import at.tuwien.exception.QueryNotFoundException;
-import at.tuwien.exception.RemoteUnavailableException;
-import at.tuwien.gateway.QueryServiceGateway;
-import at.tuwien.repository.jpa.IdentifierRepository;
-import at.tuwien.repository.jpa.RealmRepository;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import at.tuwien.entities.identifier.Identifier;
+import at.tuwien.entities.user.User;
+import at.tuwien.exception.*;
+import at.tuwien.service.AccessService;
+import at.tuwien.service.IdentifierService;
+import at.tuwien.service.UserService;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
@@ -30,27 +29,22 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Primary;
 import org.springframework.core.io.InputStreamResource;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
-import org.springframework.web.client.RestTemplate;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.StringWriter;
-import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
+import java.security.Principal;
 import java.util.TimeZone;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 @ExtendWith(SpringExtension.class)
 @SpringBootTest
@@ -63,10 +57,13 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     private IndexInitializer indexInitializer;
 
     @MockBean
-    private IdentifierRepository identifierRepository;
+    private AccessService accessService;
 
     @MockBean
-    private QueryServiceGateway queryServiceGateway;
+    private IdentifierService identifierService;
+
+    @MockBean
+    private UserService userService;
 
     @Autowired
     private ObjectMapper objectMapper;
@@ -87,14 +84,15 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_json0_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "application/json";
         final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata0.json"), StandardCharsets.UTF_8), IdentifierDto.class);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_4_ID))
-                .thenReturn(Optional.of(IDENTIFIER_4));
+        when(identifierService.find(IDENTIFIER_4_ID))
+                .thenReturn(IDENTIFIER_4);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_4_ID, accept);
@@ -118,14 +116,15 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_json1_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "application/json";
         final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata1.json"), StandardCharsets.UTF_8), IdentifierDto.class);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -145,6 +144,8 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
         assertEquals(compare.getPublicationMonth(), body.getPublicationMonth());
         assertEquals(compare.getPublicationYear(), body.getPublicationYear());
         assertEquals(compare.getPublisher(), body.getPublisher());
+        assertNotNull(compare.getCreators());
+        assertNotNull(body.getCreators());
         assertEquals(compare.getCreators().size(), body.getCreators().size());
         final CreatorDto creator1 = body.getCreators().get(0);
         assertEquals(compare.getCreators().get(0).getFirstname(), creator1.getFirstname());
@@ -154,17 +155,18 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_csv_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/csv";
-        final byte[] stream = FileUtils.readFileToByteArray(new File("src/test/resources/csv/keyboard.csv"));
         final InputStreamResource compare = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv")));
+        final InputStreamResource mock = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv")));
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
-        when(queryServiceGateway.export(CONTAINER_1_ID, DATABASE_1_ID, QUERY_1_ID))
-                .thenReturn(stream);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
+        when(identifierService.exportResource(IDENTIFIER_1_ID))
+                .thenReturn(mock);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -175,15 +177,15 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
-    @Disabled
+    @Disabled("not testable with xml")
     public void find_xml0_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/xml";
         final InputStreamResource compare = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/xml/metadata0.xml")));
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -194,15 +196,15 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
-    @Disabled
+    @Disabled("not testable with xml")
     public void find_xml1_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/xml";
         final InputStreamResource compare = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/xml/metadata1.xml")));
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -214,6 +216,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliography_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography";
@@ -221,8 +224,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -233,6 +238,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyApa0_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=apa";
@@ -240,8 +246,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_4_ID))
-                .thenReturn(Optional.of(IDENTIFIER_4));
+        when(identifierService.exportBibliography(IDENTIFIER_4_ID, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_4_ID))
+                .thenReturn(IDENTIFIER_4);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_4_ID, accept);
@@ -252,6 +260,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyApa1_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=apa";
@@ -259,8 +268,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -271,6 +282,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyApa2_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=apa";
@@ -278,8 +290,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_2_ID))
-                .thenReturn(Optional.of(IDENTIFIER_2));
+        when(identifierService.exportBibliography(IDENTIFIER_2_ID, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_2_ID))
+                .thenReturn(IDENTIFIER_2);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_2_ID, accept);
@@ -290,6 +304,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyApa3_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=apa";
@@ -297,8 +312,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_3_ID))
-                .thenReturn(Optional.of(IDENTIFIER_3));
+        when(identifierService.exportBibliography(IDENTIFIER_3_ID, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_3_ID))
+                .thenReturn(IDENTIFIER_3);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_3_ID, accept);
@@ -309,6 +326,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyApa4_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=apa";
@@ -316,8 +334,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1_WITH_DOI));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1_WITH_DOI);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -328,6 +348,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyIeee0_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=ieee";
@@ -335,8 +356,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_4_ID))
-                .thenReturn(Optional.of(IDENTIFIER_4));
+        when(identifierService.exportBibliography(IDENTIFIER_4_ID, BibliographyTypeDto.IEEE))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_4_ID))
+                .thenReturn(IDENTIFIER_4);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_4_ID, accept);
@@ -347,6 +370,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyIeee1_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=ieee";
@@ -354,8 +378,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.IEEE))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -366,6 +392,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyIeee2_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=ieee";
@@ -373,8 +400,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_2_ID))
-                .thenReturn(Optional.of(IDENTIFIER_2));
+        when(identifierService.exportBibliography(IDENTIFIER_2_ID, BibliographyTypeDto.IEEE))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_2_ID))
+                .thenReturn(IDENTIFIER_2);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_2_ID, accept);
@@ -385,6 +414,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyIeee3_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=ieee";
@@ -392,8 +422,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1_WITH_DOI));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.IEEE))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1_WITH_DOI);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -404,6 +436,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyBibtex0_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=bibtex";
@@ -411,8 +444,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_4_ID))
-                .thenReturn(Optional.of(IDENTIFIER_4));
+        when(identifierService.exportBibliography(IDENTIFIER_4_ID, BibliographyTypeDto.BIBTEX))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_4_ID))
+                .thenReturn(IDENTIFIER_4);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_4_ID, accept);
@@ -423,6 +458,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyBibtex1_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=bibtex";
@@ -430,8 +466,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.BIBTEX))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -442,6 +480,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyBibtex2_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=bibtex";
@@ -449,8 +488,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_2_ID))
-                .thenReturn(Optional.of(IDENTIFIER_2));
+        when(identifierService.exportBibliography(IDENTIFIER_2_ID, BibliographyTypeDto.BIBTEX))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_2_ID))
+                .thenReturn(IDENTIFIER_2);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_2_ID, accept);
@@ -461,6 +502,7 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
     }
 
     @Test
+    @WithAnonymousUser
     public void find_bibliographyBibtex3_succeeds() throws IdentifierNotFoundException, QueryNotFoundException,
             RemoteUnavailableException, IdentifierRequestException, IOException {
         final String accept = "text/bibliography; style=bibtex";
@@ -468,8 +510,10 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
                 StandardCharsets.UTF_8);
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1_WITH_DOI));
+        when(identifierService.exportBibliography(IDENTIFIER_1_ID, BibliographyTypeDto.BIBTEX))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_1_ID))
+                .thenReturn(IDENTIFIER_1_WITH_DOI);
 
         /* test */
         final ResponseEntity<?> response = persistenceEndpoint.find(IDENTIFIER_1_ID, accept);
@@ -479,8 +523,158 @@ public class PersistenceEndpointUnitTest extends BaseUnitTest {
         assertEquals(compare, body);
     }
 
+    @Test
+    @WithAnonymousUser
+    public void update_anonymous_fails() {
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            generic_update(IDENTIFIER_3_ID, IDENTIFIER_3, IDENTIFIER_3_DTO_UPDATE_REQUEST, null, null, null);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {})
+    public void update_noRole_fails() {
+
+        /* test */
+        assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
+            generic_update(IDENTIFIER_3_ID, IDENTIFIER_3, IDENTIFIER_3_DTO_UPDATE_REQUEST, USER_4_USERNAME, USER_4, USER_4_PRINCIPAL);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_3_USERNAME, authorities = {"modify-identifier-metadata"})
+    public void update_hasRoleNoAccess_succeeds() throws at.tuwien.exception.AccessDeniedException {
+
+        /* mock */
+        doThrow(at.tuwien.exception.AccessDeniedException.class)
+                .when(accessService)
+                .find(IDENTIFIER_3_DATABASE_ID, USER_3_ID);
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            generic_update(IDENTIFIER_3_ID, IDENTIFIER_3, IDENTIFIER_3_DTO_UPDATE_REQUEST, USER_3_USERNAME, USER_3, USER_3_PRINCIPAL);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_3_USERNAME, authorities = {"modify-identifier-metadata"})
+    public void update_hasRoleHasAccess_succeeds() throws IdentifierNotFoundException, IdentifierRequestException,
+            UserNotFoundException, at.tuwien.exception.AccessDeniedException, NotAllowedException, IdentifierUpdateBadFormException {
+
+        /* mock */
+        when(accessService.find(IDENTIFIER_3_DATABASE_ID, USER_3_ID))
+                .thenReturn(DATABASE_3_DATA_STEWARD_READ_ACCESS);
+
+        /* test */
+        generic_update(IDENTIFIER_3_ID, IDENTIFIER_3, IDENTIFIER_3_DTO_UPDATE_REQUEST, USER_3_USERNAME, USER_3, USER_3_PRINCIPAL);
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-identifier-metadata"})
+    public void update_doiChange_fails() throws at.tuwien.exception.AccessDeniedException {
+        final IdentifierUpdateDto request = IdentifierUpdateDto.builder()
+                .doi("10.000/thisisadifferentdoi")
+                .build();
+
+        /* mock */
+        when(accessService.find(IDENTIFIER_1_DATABASE_ID, USER_1_ID))
+                .thenReturn(DATABASE_1_DATA_STEWARD_READ_ACCESS);
+
+        /* test */
+        assertThrows(IdentifierRequestException.class, () -> {
+            generic_update(IDENTIFIER_1_ID, IDENTIFIER_1_WITH_DOI, request, USER_1_USERNAME, USER_1, USER_1_PRINCIPAL);
+        });
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void delete_anonymous_fails() {
+
+        /* test */
+        assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
+            this.generic_delete(IDENTIFIER_1_ID, IDENTIFIER_1);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {})
+    public void delete_noRole_fails() {
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            this.generic_delete(IDENTIFIER_1_ID, IDENTIFIER_1);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_2_USERNAME, authorities = {"delete-identifier"})
+    public void delete_hasRole_succeeds() throws NotAllowedException, IdentifierNotFoundException {
+
+        /* test */
+        this.generic_delete(IDENTIFIER_1_ID, IDENTIFIER_1);
+    }
+
+    /* ################################################################################################### */
+    /* ## GENERIC TEST CASES                                                                            ## */
+    /* ################################################################################################### */
+
     protected static String inputStreamToString(InputStream inputStream) throws IOException {
         return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
     }
 
+    protected void generic_update(Long id, Identifier identifier, IdentifierUpdateDto data, String username, User user,
+                                  Principal principal) throws IdentifierNotFoundException, IdentifierRequestException,
+            UserNotFoundException, NotAllowedException, IdentifierUpdateBadFormException {
+
+        /* mock */
+        if (identifier != null) {
+            when(identifierService.update(id, data))
+                    .thenReturn(identifier);
+            when(identifierService.find(id))
+                    .thenReturn(identifier);
+        } else {
+            doThrow(IdentifierNotFoundException.class)
+                    .when(identifierService)
+                    .find(id);
+        }
+        if (user != null) {
+            when(userService.findByUsername(username))
+                    .thenReturn(user);
+        } else {
+            doThrow(UserNotFoundException.class)
+                    .when(userService)
+                    .findByUsername(username);
+        }
+
+        /* test */
+        final ResponseEntity<IdentifierDto> response = persistenceEndpoint.update(id, data, principal);
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        final IdentifierDto body = response.getBody();
+        assertNotNull(body);
+        assertEquals(IDENTIFIER_3_ID, body.getId());
+        assertEquals(IDENTIFIER_3_TITLE, body.getTitle());
+        assertEquals(IDENTIFIER_3_DESCRIPTION, body.getDescription());
+        assertEquals(IDENTIFIER_3_QUERY, body.getQuery());
+        assertEquals(IDENTIFIER_3_QUERY_HASH, body.getQueryHash());
+        assertEquals(IDENTIFIER_3_RESULT_NUMBER, body.getResultNumber());
+        assertEquals(IDENTIFIER_3_RESULT_HASH, body.getResultHash());
+    }
+
+    protected void generic_delete(Long id, Identifier identifier) throws IdentifierNotFoundException, NotAllowedException {
+
+        /* mock */
+        when(identifierService.find(id))
+                .thenReturn(identifier);
+        doNothing()
+                .when(identifierService)
+                .delete(id);
+
+        /* test */
+        final ResponseEntity<?> response = persistenceEndpoint.delete(id);
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        assertNull(response.getBody());
+    }
+
 }
diff --git a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServiceUnitTest.java b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServiceUnitTest.java
index 36e265abbadfc726a5f3498f068bd44d819f8009..368b48e24a58b07b31701e61a84efba8d6999cb2 100644
--- a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServiceUnitTest.java
+++ b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServiceUnitTest.java
@@ -5,7 +5,7 @@ import at.tuwien.api.datacite.DataCiteBody;
 import at.tuwien.api.datacite.DataCiteData;
 import at.tuwien.api.datacite.doi.DataCiteDoi;
 import at.tuwien.api.identifier.IdentifierCreateDto;
-import at.tuwien.api.identifier.IdentifierDto;
+import at.tuwien.api.identifier.IdentifierUpdateDto;
 import at.tuwien.config.DataCiteConfig;
 import at.tuwien.config.EndpointConfig;
 import at.tuwien.config.IndexInitializer;
@@ -90,7 +90,6 @@ public class DataCiteIdentifierServiceUnitTest extends BaseUnitTest {
         containerRepository.save(CONTAINER_1);
         databaseRepository.save(DATABASE_1);
         when(restTemplateBuilder.build()).thenReturn(restTemplate);
-        IDENTIFIER_1.setCreators(null);
     }
 
     @Test
@@ -162,29 +161,28 @@ public class DataCiteIdentifierServiceUnitTest extends BaseUnitTest {
 
     @Test
     public void update_existing_succeeds()
-            throws IdentifierRequestException, IdentifierNotFoundException {
+            throws IdentifierRequestException, IdentifierNotFoundException, IdentifierUpdateBadFormException {
         final DataCiteBody<DataCiteDoi> response =
                 new DataCiteBody<>(new DataCiteData<>(null, "dois", new DataCiteDoi(IDENTIFIER_1_DOI_NOT_NULL)));
 
         /* mock */
-        when(identifierService.update(eq(IDENTIFIER_1_ID), any(IdentifierDto.class)))
+        when(identifierService.update(eq(IDENTIFIER_1_ID), any(IdentifierUpdateDto.class)))
                 .thenAnswer((i) -> identifierRepository.save(IDENTIFIER_1_WITH_DOI));
         when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class),
                 any(ParameterizedTypeReference.class), eq(IDENTIFIER_1_DOI_NOT_NULL)))
                 .thenReturn(ResponseEntity.ok(response));
 
         /* test */
-        Identifier result = dataCiteIdentifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_WITH_DOI_DTO);
+        Identifier result = dataCiteIdentifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_DTO_UPDATE_REQUEST);
         assertTrue(identifierRepository.existsById(IDENTIFIER_1_ID));
         assertEquals(IDENTIFIER_1_DOI_NOT_NULL, result.getDoi());
     }
 
     @Test
-    public void update_invalidMetadata_fails()
-            throws IdentifierRequestException, IdentifierNotFoundException {
+    public void update_invalidMetadata_fails() throws IdentifierUpdateBadFormException, IdentifierNotFoundException {
 
         /* mock */
-        when(identifierService.update(eq(IDENTIFIER_1_ID), any(IdentifierDto.class)))
+        when(identifierService.update(eq(IDENTIFIER_1_ID), any(IdentifierUpdateDto.class)))
                 .thenAnswer((i) -> identifierRepository.save(IDENTIFIER_1_WITH_DOI));
         when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class),
                 any(ParameterizedTypeReference.class), eq(IDENTIFIER_1_DOI_NOT_NULL)))
@@ -192,17 +190,16 @@ public class DataCiteIdentifierServiceUnitTest extends BaseUnitTest {
 
         /* test */
         assertThrows(IdentifierRequestException.class, () -> {
-            dataCiteIdentifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_WITH_DOI_DTO);
+            dataCiteIdentifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_DTO_UPDATE_REQUEST);
         });
         assertEquals(0, identifierRepository.count());
     }
 
     @Test
-    public void update_restClientException_fails()
-            throws IdentifierRequestException, IdentifierNotFoundException {
+    public void update_restClientException_fails() throws IdentifierUpdateBadFormException, IdentifierNotFoundException {
 
         /* mock */
-        when(identifierService.update(eq(IDENTIFIER_1_ID), any(IdentifierDto.class)))
+        when(identifierService.update(eq(IDENTIFIER_1_ID), any(IdentifierUpdateDto.class)))
                 .thenAnswer((i) -> identifierRepository.save(IDENTIFIER_1_WITH_DOI));
         when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class),
                 any(ParameterizedTypeReference.class), eq(IDENTIFIER_1_DOI_NOT_NULL)))
@@ -210,7 +207,7 @@ public class DataCiteIdentifierServiceUnitTest extends BaseUnitTest {
 
         /* test */
         assertThrows(InternalError.class, () -> {
-            dataCiteIdentifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_WITH_DOI_DTO);
+            dataCiteIdentifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_DTO_UPDATE_REQUEST);
         });
         assertEquals(0, identifierRepository.count());
     }
diff --git a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
index 423c014e503edaa826f147ec0302479906af71f9..27bdb8c69b75578f56b203ec6f5fc9b0a570b136 100644
--- a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
+++ b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
@@ -23,11 +23,9 @@ import org.springframework.http.HttpMethod;
 import org.springframework.http.ResponseEntity;
 import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.client.RestTemplate;
 
 import java.util.List;
-import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
@@ -76,14 +74,14 @@ public class IdentifierServiceIntegrationTest extends BaseUnitTest {
 
     @BeforeEach
     public void beforeEach() {
-        imageRepository.save(IMAGE_1);
+        imageRepository.save(IMAGE_1_SIMPLE);
         realmRepository.save(REALM_DBREPO);
-        userRepository.save(USER_1);
-        userRepository.save(USER_2);
-        containerRepository.save(CONTAINER_1);
-        databaseRepository.save(DATABASE_1);
-        containerRepository.save(CONTAINER_2);
-        databaseRepository.save(DATABASE_2);
+        userRepository.save(USER_1_SIMPLE);
+        userRepository.save(USER_2_SIMPLE);
+        containerRepository.save(CONTAINER_1_SIMPLE);
+        databaseRepository.save(DATABASE_1_SIMPLE);
+        containerRepository.save(CONTAINER_2_SIMPLE);
+        databaseRepository.save(DATABASE_2_SIMPLE);
         identifierRepository.save(IDENTIFIER_1);
     }
 
@@ -134,42 +132,36 @@ public class IdentifierServiceIntegrationTest extends BaseUnitTest {
     }
 
     @Test
-    public void update_notFound_fails() {
-
-        /* test */
-        assertThrows(IdentifierNotFoundException.class, () -> {
-            identifierService.update(IDENTIFIER_2_ID, IDENTIFIER_1_DTO);
-        });
-    }
-
-    @Test
-    @Transactional(readOnly = true)
     public void update_succeeds()
-            throws IdentifierNotFoundException, IdentifierPublishingNotAllowedException, IdentifierRequestException {
+            throws IdentifierNotFoundException, IdentifierRequestException, IdentifierUpdateBadFormException {
 
         /* mock */
         when(identifierIdxRepository.save(any(IdentifierDto.class)))
                 .thenReturn(IDENTIFIER_1_DTO);
 
         /* test */
-        final Identifier response = identifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_DTO);
+        final Identifier response = identifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_DTO_UPDATE_REQUEST);
         assertEquals(IDENTIFIER_1_ID, response.getId());
         assertEquals(IDENTIFIER_1_DATABASE_ID, response.getDatabaseId());
+        assertEquals(IDENTIFIER_1_TITLE_MODIFY, response.getTitle());
         assertEquals(IDENTIFIER_1_PUBLICATION_YEAR, response.getPublicationYear());
         assertEquals(IDENTIFIER_1_PUBLICATION_MONTH, response.getPublicationMonth());
         assertEquals(IDENTIFIER_1_PUBLICATION_DAY, response.getPublicationDay());
     }
 
     @Test
-    public void delete_succeeds() throws IdentifierNotFoundException, NotAllowedException {
+    public void delete_succeeds() throws IdentifierNotFoundException {
 
         /* mock */
+        when(identifierIdxRepository.existsById(IDENTIFIER_1_ID))
+                .thenReturn(true);
         doNothing()
                 .when(identifierIdxRepository)
-                        .deleteById(IDENTIFIER_1_ID);
+                .deleteById(IDENTIFIER_1_ID);
 
         /* test */
         identifierService.delete(IDENTIFIER_1_ID);
+        assertTrue(userRepository.findById(IDENTIFIER_1_CREATED_BY).isPresent()) /* no cascade of delete */;
     }
 
     @Test
diff --git a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceUnitTest.java b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceUnitTest.java
index 4eaff8493e39161e29140f89a53a6473357de654..d7e8b28963278b32426509a2bffbe4816018a839 100644
--- a/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceUnitTest.java
+++ b/dbrepo-identifier-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceUnitTest.java
@@ -3,14 +3,13 @@ package at.tuwien.service;
 import at.tuwien.BaseUnitTest;
 import at.tuwien.api.database.query.QueryDto;
 import at.tuwien.api.identifier.IdentifierDto;
-import at.tuwien.api.identifier.VisibilityTypeDto;
+import at.tuwien.api.identifier.IdentifierUpdateDto;
 import at.tuwien.config.IndexInitializer;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.entities.identifier.IdentifierType;
 import at.tuwien.exception.*;
 import at.tuwien.repository.elastic.IdentifierIdxRepository;
 import at.tuwien.repository.jpa.IdentifierRepository;
-import org.apache.http.auth.BasicUserPrincipal;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -24,7 +23,6 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.web.client.RestTemplate;
 
-import java.security.Principal;
 import java.util.List;
 import java.util.Optional;
 
@@ -174,52 +172,6 @@ public class IdentifierServiceUnitTest extends BaseUnitTest {
         });
     }
 
-    @Test
-    public void update_notFound_fails() {
-
-        /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.empty());
-        when(identifierRepository.save(IDENTIFIER_1))
-                .thenReturn(IDENTIFIER_1);
-
-        /* test */
-        assertThrows(IdentifierNotFoundException.class, () -> {
-            identifierService.update(IDENTIFIER_1_ID, IDENTIFIER_1_DTO);
-        });
-    }
-
-    @Test
-    public void update_doiChange_fails() {
-
-        IdentifierDto identifierWithNewDoiDto = IdentifierDto.builder().id(IDENTIFIER_1_ID).visibility(VisibilityTypeDto.EVERYONE).doi("10.000/thisisadifferentdoi").build();
-
-        /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1_WITH_DOI));
-
-        /* test */
-        assertThrows(IdentifierRequestException.class, () -> {
-            identifierService.update(IDENTIFIER_1_ID, identifierWithNewDoiDto);
-        });
-    }
-
-    @Test
-    public void update_notVisibleByEveryone_fails() {
-        Identifier identifier = Identifier.builder().id(IDENTIFIER_1_ID).build();
-        IdentifierDto identifierDto = IdentifierDto.builder().id(IDENTIFIER_1_ID).visibility(VisibilityTypeDto.TRUSTED).build();
-        IDENTIFIER_1_DTO.setVisibility(VisibilityTypeDto.TRUSTED);
-
-        /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(identifier));
-
-        /* test */
-        assertThrows(IdentifierRequestException.class, () -> {
-            identifierService.update(IDENTIFIER_1_ID, identifierDto);
-        });
-    }
-
     @Test
     public void create_database_succeeds()
             throws DatabaseNotFoundException, UserNotFoundException, IdentifierAlreadyExistsException,
@@ -281,11 +233,13 @@ public class IdentifierServiceUnitTest extends BaseUnitTest {
     }
 
     @Test
-    public void delete_succeeds() throws IdentifierNotFoundException, NotAllowedException {
+    public void delete_succeeds() throws IdentifierNotFoundException {
 
         /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1));
+        when(identifierRepository.existsById(IDENTIFIER_1_ID))
+                .thenReturn(true);
+        when(identifierIdxRepository.existsById(IDENTIFIER_1_ID))
+                .thenReturn(true);
         doNothing()
                 .when(identifierRepository)
                 .delete(IDENTIFIER_1);
@@ -313,22 +267,6 @@ public class IdentifierServiceUnitTest extends BaseUnitTest {
         });
     }
 
-    @Test
-    public void delete_withDoi_fails() {
-
-        /* mock */
-        when(identifierRepository.findById(IDENTIFIER_1_ID))
-                .thenReturn(Optional.of(IDENTIFIER_1_WITH_DOI));
-        doNothing()
-                .when(identifierRepository)
-                .delete(IDENTIFIER_1);
-
-        /* test */
-        assertThrows(NotAllowedException.class, () -> {
-            identifierService.delete(IDENTIFIER_1_ID);
-        });
-    }
-
     @Test
     public void exportMetadata_succeeds() throws IdentifierNotFoundException {
 
diff --git a/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata0.json b/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata0.json
index ab803627d4a39d33b65d777a480709d4d18cff9d..d7bb152193d38d9a50d4ab162bbca8a2b23456df 100644
--- a/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata0.json
+++ b/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata0.json
@@ -6,7 +6,6 @@
   "title": "Sweden weather data",
   "description": "Selecting all from the weather Sweden table",
   "doi": null,
-  "visibility": "everyone",
   "publisher": "Swedish Government",
   "publication_day": 14,
   "publication_month": 7,
diff --git a/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata1.json b/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata1.json
index be2009147bae92bae99101194a405f5ad6d337e0..347b13ed828b33c4926c680489df7082711746d7 100644
--- a/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata1.json
+++ b/dbrepo-identifier-service/rest-service/src/test/resources/json/metadata1.json
@@ -13,7 +13,6 @@
   "execution": "2018-11-07T10:59:12.000+00:00",
   "result_hash": "def",
   "result_number": 2,
-  "visibility": "everyone",
   "publisher": "Austrian Government",
   "creators": [
     {
diff --git a/dbrepo-identifier-service/services/src/main/java/at/tuwien/config/IndexInitializer.java b/dbrepo-identifier-service/services/src/main/java/at/tuwien/config/IndexInitializer.java
index 0592364d2de1925f1f1298e635c3ddc58d0e0e06..68268cd3c26d62d6ca16b058c7155e02a0f7cbad 100644
--- a/dbrepo-identifier-service/services/src/main/java/at/tuwien/config/IndexInitializer.java
+++ b/dbrepo-identifier-service/services/src/main/java/at/tuwien/config/IndexInitializer.java
@@ -11,7 +11,6 @@ import org.springframework.context.event.EventListener;
 import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
 import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -35,7 +34,6 @@ public class IndexInitializer {
         this.elasticsearchOperations = elasticsearchOperations;
     }
 
-    @Transactional
     @EventListener(ApplicationReadyEvent.class)
     public void initIndex() {
         log.debug("creating identifierindex");
diff --git a/dbrepo-identifier-service/services/src/main/java/at/tuwien/exception/IdentifierUpdateBadFormException.java b/dbrepo-identifier-service/services/src/main/java/at/tuwien/exception/IdentifierUpdateBadFormException.java
new file mode 100644
index 0000000000000000000000000000000000000000..884a5d4bc5e0c3edf69eb98833405fd073f1582d
--- /dev/null
+++ b/dbrepo-identifier-service/services/src/main/java/at/tuwien/exception/IdentifierUpdateBadFormException.java
@@ -0,0 +1,21 @@
+package at.tuwien.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(code = HttpStatus.BAD_REQUEST)
+public class IdentifierUpdateBadFormException extends Exception {
+
+    public IdentifierUpdateBadFormException(String msg) {
+        super(msg);
+    }
+
+    public IdentifierUpdateBadFormException(String msg, Throwable thr) {
+        super(msg, thr);
+    }
+
+    public IdentifierUpdateBadFormException(Throwable thr) {
+        super(thr);
+    }
+
+}
diff --git a/dbrepo-identifier-service/services/src/main/java/at/tuwien/mapper/IdentifierMapper.java b/dbrepo-identifier-service/services/src/main/java/at/tuwien/mapper/IdentifierMapper.java
index 08fa62a8ec066cf57a1304a13e70e12ae27b9f68..74c61a567618e4c4dd1d55e4ab861b7c5d5f5cfa 100644
--- a/dbrepo-identifier-service/services/src/main/java/at/tuwien/mapper/IdentifierMapper.java
+++ b/dbrepo-identifier-service/services/src/main/java/at/tuwien/mapper/IdentifierMapper.java
@@ -6,31 +6,38 @@ import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.entities.identifier.IdentifierType;
 import at.tuwien.entities.identifier.RelatedIdentifier;
 import org.mapstruct.Mapper;
-import org.springframework.transaction.annotation.Transactional;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
 
 @Mapper(componentModel = "spring")
 public interface IdentifierMapper {
 
-    @Transactional
     IdentifierDto identifierToIdentifierDto(Identifier data);
 
-    @Transactional
+    @Mappings({
+            @Mapping(target = "containerId", source = "cid"),
+            @Mapping(target = "databaseId", source = "dbid"),
+            @Mapping(target = "queryId", source = "qid"),
+    })
     Identifier identifierCreateDtoToIdentifier(IdentifierCreateDto data);
 
+    @Mappings({
+            @Mapping(target = "containerId", source = "cid"),
+            @Mapping(target = "databaseId", source = "dbid"),
+            @Mapping(target = "queryId", source = "qid"),
+    })
+    Identifier identifierUpdateDtoToIdentifier(IdentifierUpdateDto data);
+
     /* keep */
-    @Transactional
     RelatedIdentifierDto relatedIdentifierToRelatedIdentifierDto(RelatedIdentifier data);
 
-    @Transactional
     Identifier identifierDtoToIdentifier(IdentifierDto data);
 
-    @Transactional
+    /* keep */
     Creator creatorDtoToCreator(CreatorDto data);
 
-    @Transactional
     Creator creatorCreateDtoToCreator(CreatorCreateDto data);
 
-    @Transactional
     RelatedIdentifier relatedIdentifierCreateDtoToRelatedIdentifier(RelatedIdentifierCreateDto data);
 
     IdentifierType identifierTypeDtoToIdentifierType(IdentifierTypeDto data);
diff --git a/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java b/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java
index 95071320600402cf97b75013593a429c237489a7..f7bab0d2c2c014670a500d5ce7a7772dc46da4e6 100644
--- a/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java
+++ b/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java
@@ -2,7 +2,7 @@ package at.tuwien.service;
 
 import at.tuwien.api.identifier.BibliographyTypeDto;
 import at.tuwien.api.identifier.IdentifierCreateDto;
-import at.tuwien.api.identifier.IdentifierDto;
+import at.tuwien.api.identifier.IdentifierUpdateDto;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.exception.*;
 import org.springframework.core.io.InputStreamResource;
@@ -108,16 +108,15 @@ public interface IdentifierService {
      * @param identifierId The identifier id.
      * @param data         The metadata.
      * @return The updated identifier if successful.
-     * @throws IdentifierNotFoundException             TThe identifier was not found in the metadata database or was deleted.
+     * @throws IdentifierNotFoundException TThe identifier was not found in the metadata database or was deleted.
      */
-    Identifier update(Long identifierId, IdentifierDto data) throws IdentifierNotFoundException, IdentifierRequestException;
+    Identifier update(Long identifierId, IdentifierUpdateDto data) throws IdentifierNotFoundException, IdentifierRequestException;
 
     /**
      * Soft-deletes an identifier for a given id in the metadata database. Does not actually remove the entity from the database, but sets it as deleted.
      *
      * @param identifierId The identifier id.
      * @throws IdentifierNotFoundException The identifier was not found in the metadata database or was deleted.
-     * @throws NotAllowedException Identifiers with a valid DOI cannot be deleted.
      */
-    void delete(Long identifierId) throws IdentifierNotFoundException, NotAllowedException;
+    void delete(Long identifierId) throws IdentifierNotFoundException;
 }
diff --git a/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java b/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java
index 908eadeefe900044b12e77626196204f9a33f993..a6568c82b87088e78d87fadd73d8973c16b02ecc 100644
--- a/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java
+++ b/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java
@@ -6,7 +6,7 @@ import at.tuwien.api.datacite.doi.DataCiteCreateDoi;
 import at.tuwien.api.datacite.doi.DataCiteDoi;
 import at.tuwien.api.identifier.BibliographyTypeDto;
 import at.tuwien.api.identifier.IdentifierCreateDto;
-import at.tuwien.api.identifier.IdentifierDto;
+import at.tuwien.api.identifier.IdentifierUpdateDto;
 import at.tuwien.config.DataCiteConfig;
 import at.tuwien.config.EndpointConfig;
 import at.tuwien.entities.identifier.Identifier;
@@ -107,17 +107,17 @@ public class DataCiteIdentifierServiceImpl implements IdentifierService {
                     }
             );
 
-            if(response.getStatusCode() != HttpStatus.CREATED || response.getBody() == null) {
+            if (response.getStatusCode() != HttpStatus.CREATED || response.getBody() == null) {
                 log.error("Could not successfully create DOI. Response: {}", response);
                 throw new IdentifierRequestException("Could not successfully create DOI.");
             }
 
             identifier.setDoi(response.getBody().getData().getAttributes().getDoi());
             this.identifierRepository.save(identifier);
-        } catch(HttpClientErrorException e) {
+        } catch (HttpClientErrorException e) {
             log.error("Invalid DOI metadata.", e);
             throw new IdentifierRequestException("Invalid DOI metadata.", e);
-        } catch(RestClientException e) {
+        } catch (RestClientException e) {
             log.error("Could not fulfil request to DataCite server.", e);
             throw new InternalError("Could not fulfil request to DataCite server.", e);
         }
@@ -155,10 +155,10 @@ public class DataCiteIdentifierServiceImpl implements IdentifierService {
     @Override
     @Transactional
     @javax.transaction.Transactional(rollbackOn = {Exception.class})
-    public Identifier update(Long identifierId, IdentifierDto data)
+    public Identifier update(Long identifierId, IdentifierUpdateDto data)
             throws IdentifierNotFoundException, IdentifierRequestException {
         Identifier identifier = identifierService.update(identifierId, data);
-        if(identifier.getDoi() == null) {
+        if (identifier.getDoi() == null) {
             return identifier;
         }
 
@@ -187,17 +187,17 @@ public class DataCiteIdentifierServiceImpl implements IdentifierService {
                     identifier.getDoi()
             );
 
-            if(response.getStatusCode() != HttpStatus.OK || response.getBody() == null) {
+            if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) {
                 log.error("Could not successfully create DOI. Response: {}", response);
                 throw new IdentifierRequestException("Could not successfully create DOI.");
             }
 
             identifier.setDoi(response.getBody().getData().getAttributes().getDoi());
             this.identifierRepository.save(identifier);
-        } catch(HttpClientErrorException e) {
+        } catch (HttpClientErrorException e) {
             log.error("Invalid DOI metadata.", e);
             throw new IdentifierRequestException("Invalid DOI metadata.", e);
-        } catch(RestClientException e) {
+        } catch (RestClientException e) {
             log.error("Could not fulfil request to DataCite server.", e);
             throw new InternalError("Could not fulfil request to DataCite server.", e);
         }
@@ -207,7 +207,7 @@ public class DataCiteIdentifierServiceImpl implements IdentifierService {
 
     @Override
     @Transactional
-    public void delete(Long identifierId) throws IdentifierNotFoundException, NotAllowedException {
+    public void delete(Long identifierId) throws IdentifierNotFoundException {
         identifierService.delete(identifierId);
     }
 
diff --git a/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
index 00b639448bdf491fa20991cacb83ad36cb089b6a..4a3aa4437bc9d982f9dd3754b5599057062ad6e5 100644
--- a/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
+++ b/dbrepo-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
@@ -112,10 +112,8 @@ public class IdentifierServiceImpl implements IdentifierService {
         }
         /* identifier */
         final Identifier tmp = identifierMapper.identifierCreateDtoToIdentifier(data);
-        tmp.setContainerId(data.getCid());
-        tmp.setDatabaseId(data.getDbid());
         final User creator = userService.findByUsername(principal.getName());
-        tmp.setCreatedBy(creator.getId());
+        tmp.setCreator(creator);
         tmp.setCreators(List.of());
         if (data.getType().equals(IdentifierTypeDto.SUBSET)) {
             log.debug("identifier describes a subset");
@@ -135,7 +133,7 @@ public class IdentifierServiceImpl implements IdentifierService {
                 .map(c -> {
                     final Creator creatorDto = identifierMapper.creatorCreateDtoToCreator(c);
                     creatorDto.setPid(entity.getId());
-                    creatorDto.setCreatedBy(creator.getId());
+                    creatorDto.setCreator(creator);
                     return creatorDto;
                 })
                 .collect(Collectors.toList()));
@@ -145,7 +143,7 @@ public class IdentifierServiceImpl implements IdentifierService {
                     .forEach(r -> {
                         final RelatedIdentifier id = identifierMapper.relatedIdentifierCreateDtoToRelatedIdentifier(r);
                         id.setIid(entity.getId());
-                        id.setCreatedBy(creator.getId());
+                        id.setCreator(creator);
                         final RelatedIdentifier relatedIdentifier = relatedIdentifierRepository.save(id);
                         log.debug("identifier add related with id {}", relatedIdentifier.getId());
                         entity.getRelated().add(relatedIdentifier);
@@ -177,7 +175,7 @@ public class IdentifierServiceImpl implements IdentifierService {
         final Identifier identifier = find(id);
         /* context */
         final Context context = new Context();
-        if(identifier.getDoi() != null) {
+        if (identifier.getDoi() != null) {
             context.setVariable("identifierType", "DOI");
             context.setVariable("identifier", identifier.getDoi());
         } else {
@@ -207,7 +205,7 @@ public class IdentifierServiceImpl implements IdentifierService {
         final Identifier identifier = find(id);
         /* context */
         final Context context = new Context();
-        if(identifier.getDoi() != null) {
+        if (identifier.getDoi() != null) {
             context.setVariable("identifierType", "doi");
             context.setVariable("identifier", identifier.getDoi());
         } else {
@@ -252,20 +250,16 @@ public class IdentifierServiceImpl implements IdentifierService {
 
     @Override
     @Transactional
-    public Identifier update(Long identifierId, IdentifierDto data)
-            throws IdentifierNotFoundException, IdentifierRequestException {
-        /* check */
-        Identifier old = find(identifierId);
-        if(data.getVisibility() != VisibilityTypeDto.EVERYONE) {
-            throw new IdentifierRequestException("Cannot set visibility to other value than \"EVERYONE\".");
-        }
-        if(data.getDoi() != null && !data.getDoi().equals(old.getDoi())) {
-            throw new IdentifierRequestException("The DOI of an identifier cannot be changed.");
-        }
+    public Identifier update(Long identifierId, IdentifierUpdateDto data) throws IdentifierNotFoundException {
         /* map */
-        final Identifier entity = identifierMapper.identifierDtoToIdentifier(data);
-        entity.getCreators()
-                .forEach(creator -> creator.setPid(identifierId));
+        final Identifier old = find(identifierId);
+        final Identifier entity = identifierMapper.identifierUpdateDtoToIdentifier(data);
+        entity.setId(identifierId);
+        entity.setCreator(old.getCreator());
+        entity.getCreators().forEach(c -> {
+            c.setPid(identifierId);
+            c.setCreator(old.getCreator());
+        });
         /* update */
         final Identifier identifier = identifierRepository.save(entity);
         log.info("Updated identifier with id {}", identifierId);
@@ -278,17 +272,17 @@ public class IdentifierServiceImpl implements IdentifierService {
 
     @Override
     @Transactional
-    public void delete(Long identifierId) throws IdentifierNotFoundException, NotAllowedException {
-        /* check */
-        final Identifier identifier = find(identifierId);
-        if(identifier.getDoi() != null) {
-            throw new NotAllowedException("Identifiers with a DOI cannot be deleted.");
+    public void delete(Long identifierId) throws IdentifierNotFoundException {
+        /* delete in metadata database */
+        if (!identifierRepository.existsById(identifierId)) {
+            throw new IdentifierNotFoundException("Identifier not found in metadata database");
         }
-        /* delete */
-        identifierRepository.delete(identifier);
+        identifierRepository.deleteById(identifierId);
         log.info("Deleted identifier with id {}", identifierId);
-        log.trace("deleted identifier {}", identifier);
-        /* elastic search */
+        /* delete in elastic search */
+        if (!identifierIdxRepository.existsById(identifierId)) {
+            throw new IdentifierNotFoundException("Identifier not found in metadata database");
+        }
         identifierIdxRepository.deleteById(identifierId);
         log.info("Deleted identifier with id {} in elastic search", identifierId);
     }
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java
index 1234beec9630665c55479a7c7929e3ffca2a86d9..69e448003e42fcf4be8e0f40ead5b7a62ec7055d 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java
@@ -80,10 +80,6 @@ public class IdentifierDto {
     @Schema(example = "1")
     private Long resultNumber;
 
-    @NotNull
-    @Schema(example = "everyone")
-    private VisibilityTypeDto visibility;
-
     @Schema(example = "10.1038/nphys1170")
     private String doi;
 
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierUpdateDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierUpdateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f1f64b1dfd8b0a62755fe4a12a3494a358497bc
--- /dev/null
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierUpdateDto.java
@@ -0,0 +1,71 @@
+package at.tuwien.api.identifier;
+
+import at.tuwien.api.database.LanguageTypeDto;
+import at.tuwien.api.database.LicenseDto;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+@Data
+@Getter
+@Setter
+@Builder
+public class IdentifierUpdateDto {
+
+    @NotNull
+    private Long cid;
+
+    @NotNull
+    private Long dbid;
+
+    private Long qid;
+
+    @Schema(example = "10.1038/nphys1170")
+    private String doi;
+
+    @NotNull
+    private IdentifierTypeDto type;
+
+    @NotBlank
+    @Schema(example = "Airquality Stephansplatz, Vienna, Austria")
+    private String title;
+
+    @Schema(example = "Air quality reports at Stephansplatz, Vienna")
+    private String description;
+
+    @JsonProperty("publication_day")
+    @Schema(example = "15")
+    private Integer publicationDay;
+
+    @JsonProperty("publication_month")
+    @Schema(example = "12")
+    private Integer publicationMonth;
+
+    @Schema(example = "TU Wien")
+    private String publisher;
+
+    private LanguageTypeDto language;
+
+    private LicenseDto license;
+
+    @NotNull
+    @JsonProperty("publication_year")
+    @Schema(example = "2022")
+    private Integer publicationYear;
+
+    @NotNull
+    @NotEmpty
+    private List<CreatorDto> creators;
+
+    @JsonProperty("related_identifiers")
+    private List<RelatedIdentifierCreateDto> relatedIdentifiers;
+
+}
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/VisibilityTypeDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/VisibilityTypeDto.java
deleted file mode 100644
index 25739d3d4d45cabcc32504807338dfb2d2252098..0000000000000000000000000000000000000000
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/VisibilityTypeDto.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package at.tuwien.api.identifier;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Getter;
-import lombok.ToString;
-
-@Getter
-public enum VisibilityTypeDto {
-
-    @JsonProperty("everyone")
-    EVERYONE("everyone"),
-
-    @JsonProperty("trusted")
-    TRUSTED("trusted"),
-
-    @JsonProperty("self")
-    SELF("self");
-
-    private String name;
-
-    VisibilityTypeDto(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public String toString() {
-        return this.name;
-    }
-}
\ No newline at end of file
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java
index 6b9ee532fcbd3685382a6ea1ba4f877547836fd0..e05a1e03ae060f17bfec5da1027d3141104fa096 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java
@@ -7,16 +7,13 @@ import at.tuwien.entities.user.User;
 import lombok.*;
 import net.sf.jsqlparser.statement.select.FromItem;
 import org.hibernate.annotations.GenericGenerator;
-import org.hibernate.annotations.Type;
 import org.springframework.data.annotation.CreatedDate;
 import org.springframework.data.annotation.LastModifiedDate;
-import org.springframework.data.elasticsearch.annotations.Document;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
 import javax.persistence.*;
 import java.time.Instant;
 import java.util.List;
-import java.util.UUID;
 
 @Data
 @Entity
@@ -42,14 +39,9 @@ public class Table {
     @EqualsAndHashCode.Include
     private Long tdbid;
 
-    @ToString.Exclude
-    @Column(name = "createdBy", nullable = false, columnDefinition = "VARCHAR(36)")
-    @Type(type = "uuid-char")
-    private UUID createdBy;
-
-    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
     @JoinColumns({
-            @JoinColumn(name = "createdBy", referencedColumnName = "ID", updatable = false, insertable = false)
+            @JoinColumn(name = "createdBy", referencedColumnName = "ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false)
     })
     private User creator;
 
@@ -69,11 +61,11 @@ public class Table {
     private String description;
 
     @ToString.Exclude
-    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
+    @ManyToOne(fetch = FetchType.LAZY, cascade = {})
     @JoinColumn(name = "tdbid", insertable = false, updatable = false)
     private Database database;
 
-    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE, mappedBy = "table")
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "table")
     @OrderBy("ordinalPosition")
     private List<TableColumn> columns;
 
@@ -90,7 +82,8 @@ public class Table {
 
     @PreRemove
     public void preRemove() {
-        this.database = null;
+        this.creator = null;
+        this.columns.forEach(c -> c.setCreator(null));
     }
 
     /**
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java
index 1c950898300c86d461518a7fe1dccf82ad5d5d43..02ad037bd67f2247460bd268af91d674e8b81c70 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java
@@ -60,14 +60,9 @@ public class TableColumn implements Comparable<TableColumn> {
     })
     private Table table;
 
-    @ToString.Exclude
-    @Column(name = "createdBy", nullable = false, columnDefinition = "VARCHAR(36)")
-    @Type(type = "uuid-char")
-    private UUID createdBy;
-
-    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
     @JoinColumns({
-            @JoinColumn(name = "createdBy", referencedColumnName = "ID", insertable = false, updatable = false)
+            @JoinColumn(name = "createdBy", referencedColumnName = "ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false)
     })
     private User creator;
 
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Creator.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Creator.java
index 2b0a720cb7f0acc71a9a946d950e792e4af4faea..740fe5a4e5cbf278d49e56942287b4bcbe0e17e4 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Creator.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Creator.java
@@ -56,14 +56,9 @@ public class Creator {
     @CreatedDate
     private Instant created;
 
-    @ToString.Exclude
-    @Column(name = "createdBy", nullable = false, columnDefinition = "VARCHAR(36)")
-    @Type(type = "uuid-char")
-    private UUID createdBy;
-
     @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
     @JoinColumns({
-            @JoinColumn(name = "createdBy", referencedColumnName = "ID", insertable = false, updatable = false)
+            @JoinColumn(name = "createdBy", referencedColumnName = "ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false)
     })
     private User creator;
 
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java
index 66a68c8ff7a1df41c0ce13ef7773ee530cf2afa6..294a9c954db51f0144222de26f07e38e458be02e 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java
@@ -6,7 +6,6 @@ import at.tuwien.entities.database.License;
 import at.tuwien.entities.user.User;
 import lombok.*;
 import org.hibernate.annotations.GenericGenerator;
-import org.hibernate.annotations.Type;
 import org.springframework.data.annotation.CreatedDate;
 import org.springframework.data.annotation.LastModifiedDate;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@@ -16,7 +15,6 @@ import javax.validation.constraints.NotBlank;
 import java.io.Serializable;
 import java.time.Instant;
 import java.util.List;
-import java.util.UUID;
 
 @Data
 @Entity
@@ -46,14 +44,9 @@ public class Identifier implements Serializable {
     @Column(name = "qid")
     private Long queryId;
 
-    @ToString.Exclude
-    @Column(name = "createdBy", nullable = false, columnDefinition = "VARCHAR(36)")
-    @Type(type = "uuid-char")
-    private UUID createdBy;
-
-    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
+    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
     @JoinColumns({
-            @JoinColumn(name = "createdBy", referencedColumnName = "ID", insertable = false, updatable = false)
+            @JoinColumn(name = "createdBy", referencedColumnName = "ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false)
     })
     private User creator;
 
@@ -108,18 +101,13 @@ public class Identifier implements Serializable {
     @Column
     private Integer publicationDay;
 
-    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
+    @OneToOne(fetch = FetchType.LAZY, cascade = {})
     @JoinColumns({
             @JoinColumn(name = "dbid", referencedColumnName = "id", insertable = false, updatable = false)
     })
     private Database database;
 
-    @Column(nullable = false, columnDefinition = "enum('EVERYONE', 'TRUSTED', 'SELF')")
-    @Enumerated(EnumType.STRING)
-    @Builder.Default
-    private VisibilityType visibility = VisibilityType.EVERYONE;
-
-    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
     @JoinColumns({
             @JoinColumn(name = "iid", referencedColumnName = "id", insertable = false, updatable = false)
     })
@@ -128,7 +116,7 @@ public class Identifier implements Serializable {
     @Column
     private String doi;
 
-    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "identifier")
+    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "identifier")
     private List<Creator> creators;
 
     @Column(nullable = false, updatable = false)
@@ -139,6 +127,13 @@ public class Identifier implements Serializable {
     @LastModifiedDate
     private Instant lastModified;
 
+    @PreRemove
+    private void preRemove() {
+        this.creator = null;
+        this.related.forEach(r -> r.setCreator(null));
+        this.creators.forEach(c -> c.setCreator(null));
+    }
+
 }
 
 
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/RelatedIdentifier.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/RelatedIdentifier.java
index aed25776562e7f858629750f58af2c41fd517e42..54b90615a794dba57c251e1fe622704507f1d448 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/RelatedIdentifier.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/RelatedIdentifier.java
@@ -50,14 +50,9 @@ public class RelatedIdentifier {
     @Enumerated(EnumType.STRING)
     private RelationType relation;
 
-    @ToString.Exclude
-    @Column(name = "created_by", nullable = false, columnDefinition = "VARCHAR(36)")
-    @Type(type = "uuid-char")
-    private UUID createdBy;
-
     @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
     @JoinColumns({
-            @JoinColumn(name = "created_by", referencedColumnName = "ID", insertable = false, updatable = false)
+            @JoinColumn(name = "created_by", referencedColumnName = "ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false)
     })
     private User creator;
 
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/VisibilityType.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/VisibilityType.java
deleted file mode 100644
index 029ccb3994475eb9f934e2e568e6b8371e9261ae..0000000000000000000000000000000000000000
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/VisibilityType.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package at.tuwien.entities.identifier;
-
-import lombok.Getter;
-import lombok.ToString;
-
-@Getter
-@ToString
-public enum VisibilityType {
-    EVERYONE,
-    TRUSTED,
-    SELF;
-}
\ No newline at end of file
diff --git a/dbrepo-metadata-db/setup-schema.sql b/dbrepo-metadata-db/setup-schema.sql
index b78ca3ab39f8360b1fbac1399ae98c83e7c64bd0..31eefbbbd3700c3be6724c81caac9b7c4cb643a1 100644
--- a/dbrepo-metadata-db/setup-schema.sql
+++ b/dbrepo-metadata-db/setup-schema.sql
@@ -357,7 +357,6 @@ CREATE TABLE IF NOT EXISTS `fda`.`mdb_identifiers`
     language          VARCHAR(50),
     license           VARCHAR(50),
     description       TEXT,
-    visibility        ENUM ('SELF', 'TRUSTED', 'EVERYONE') NOT NULL,
     publication_year  INTEGER                              NOT NULL,
     publication_month INTEGER,
     publication_day   INTEGER,
diff --git a/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java
index 56bdd5503d8a2ac0ed37cd216a1dd9cad8273de3..43b2d75a3aebe49d85f0d5197539597bee790cc0 100644
--- a/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java
+++ b/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java
@@ -116,7 +116,7 @@ public abstract class BaseTest {
             "create-identifier", "find-identifier", "list-identifiers"};
 
     public final static String[] ESCALATED_IDENTIFIER_HANDLING = new String[]{"escalated-identifier-handling",
-            "modify-identifier-metadata", "delete-identifier"};
+            "modify-identifier-metadata", "delete-identifier", "update-foreign-identifier", "create-foreign-identifier"};
 
     public final static String[] DEFAULT_QUERY_HANDLING = new String[]{"default-query-handling", "view-table-data",
             "execute-query", "view-table-history", "list-database-views", "list-queries", "view-database-view-data",
@@ -1496,6 +1496,8 @@ public abstract class BaseTest {
     public final static String TABLE_1_DESCRIPTION = "Weather in the world";
     public final static String TABLE_1_QUEUE_NAME = DATABASE_1_EXCHANGE + "." + TABLE_1_INTERNALNAME;
     public final static String TABLE_1_ROUTING_KEY = TABLE_1_QUEUE_NAME;
+    public final static UUID TABLE_1_CREATED_BY = USER_1_ID;
+    public final static Long TABLE_1_DATABASE_ID = DATABASE_1_ID;
     public final static Instant TABLE_1_CREATED = Instant.ofEpochSecond(1677399975) /* 2023-02-26 08:26:15 (UTC) */;
     public final static Instant TABLE_1_LAST_MODIFIED = Instant.ofEpochSecond(1677399975) /* 2023-02-26 08:26:15 (UTC) */;
 
@@ -1505,6 +1507,8 @@ public abstract class BaseTest {
     public final static String TABLE_2_DESCRIPTION = "Weather location";
     public final static String TABLE_2_QUEUE_NAME = DATABASE_1_EXCHANGE + "." + TABLE_2_INTERNALNAME;
     public final static String TABLE_2_ROUTING_KEY = TABLE_2_QUEUE_NAME;
+    public final static UUID TABLE_2_CREATED_BY = USER_1_ID;
+    public final static Long TABLE_2_DATABASE_ID = DATABASE_1_ID;
     public final static Instant TABLE_2_CREATED = Instant.ofEpochSecond(1677400007) /* 2023-02-26 08:26:47 (UTC) */;
     public final static Instant TABLE_2_LAST_MODIFIED = Instant.ofEpochSecond(1677400007) /* 2023-02-26 08:26:47 (UTC) */;
 
@@ -1514,6 +1518,8 @@ public abstract class BaseTest {
     public final static String TABLE_3_DESCRIPTION = "https://www.kaggle.com/laa283/zurich-public-transport/version/2";
     public final static String TABLE_3_QUEUE_NAME = DATABASE_1_EXCHANGE + "." + TABLE_3_INTERNALNAME;
     public final static String TABLE_3_ROUTING_KEY = TABLE_3_QUEUE_NAME;
+    public final static UUID TABLE_3_CREATED_BY = USER_1_ID;
+    public final static Long TABLE_3_DATABASE_ID = DATABASE_1_ID;
     public final static Instant TABLE_3_CREATED = Instant.ofEpochSecond(1677400031) /* 2023-02-26 08:27:11 (UTC) */;
     public final static Instant TABLE_3_LAST_MODIFIED = Instant.ofEpochSecond(1677400031) /* 2023-02-26 08:27:11 (UTC) */;
 
@@ -1589,7 +1595,7 @@ public abstract class BaseTest {
                     .id(1L)
                     .ordinalPosition(0)
                     .cdbid(DATABASE_1_ID)
-                    .tid(TABLE_1_ID)
+                    .tid(TABLE_7_ID)
                     .name("Timestamp")
                     .internalName("timestamp")
                     .columnType(TableColumnType.TIMESTAMP)
@@ -1597,12 +1603,13 @@ public abstract class BaseTest {
                     .isNullAllowed(false)
                     .autoGenerated(false)
                     .isPrimaryKey(true)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(2L)
                     .ordinalPosition(1)
                     .cdbid(DATABASE_1_ID)
-                    .tid(TABLE_1_ID)
+                    .tid(TABLE_7_ID)
                     .name("Value")
                     .internalName("value")
                     .columnType(TableColumnType.DECIMAL)
@@ -1610,6 +1617,7 @@ public abstract class BaseTest {
                     .isNullAllowed(true)
                     .autoGenerated(false)
                     .isPrimaryKey(false)
+                    .creator(USER_1)
                     .build());
 
     public final static Table TABLE_7 = Table.builder()
@@ -1621,7 +1629,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_7_QUEUE_NAME)
             .routingKey(TABLE_7_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(TABLE_7_COLUMNS)
             .creator(USER_1)
             .created(TABLE_7_CREATED)
@@ -1637,7 +1644,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_7_QUEUE_NAME)
             .routingKey(TABLE_7_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(List.of() /* for jpa */)
             .creator(null /* for jpa */)
             .created(TABLE_7_CREATED)
@@ -2236,6 +2242,7 @@ public abstract class BaseTest {
                     .isNullAllowed(COLUMN_8_1_NULL)
                     .autoGenerated(COLUMN_8_1_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_8_1_PRIMARY)
+                    .creator(USER_3)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_8_2_ID)
@@ -2249,6 +2256,7 @@ public abstract class BaseTest {
                     .isNullAllowed(COLUMN_8_2_NULL)
                     .autoGenerated(COLUMN_8_2_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_8_2_PRIMARY)
+                    .creator(USER_3)
                     .build());
 
     public final static Table TABLE_8 = Table.builder()
@@ -2261,7 +2269,6 @@ public abstract class BaseTest {
             .queueName(TABLE_8_QUEUE_NAME)
             .routingKey(TABLE_8_ROUTING_KEY)
             .columns(TABLE_8_COLUMNS)
-            .createdBy(USER_1_ID)
             .creator(USER_1)
             .created(TABLE_8_CREATED)
             .lastModified(TABLE_8_LAST_MODIFIED)
@@ -2560,6 +2567,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_1_1_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_1_1_PRIMARY)
                     .enumValues(COLUMN_1_1_ENUM_VALUES)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_1_2_ID)
@@ -2575,6 +2583,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_1_2_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_1_2_PRIMARY)
                     .enumValues(COLUMN_1_2_ENUM_VALUES)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_1_3_ID)
@@ -2589,6 +2598,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_1_3_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_1_3_PRIMARY)
                     .enumValues(COLUMN_1_3_ENUM_VALUES)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_1_4_ID)
@@ -2603,6 +2613,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_1_4_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_1_4_PRIMARY)
                     .enumValues(COLUMN_1_4_ENUM_VALUES)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_1_5_ID)
@@ -2617,6 +2628,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_1_5_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_1_5_PRIMARY)
                     .enumValues(COLUMN_1_5_ENUM_VALUES)
+                    .creator(USER_1)
                     .build());
 
     public final static Table TABLE_1 = Table.builder()
@@ -2630,7 +2642,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_1_QUEUE_NAME)
             .routingKey(TABLE_1_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(TABLE_1_COLUMNS)
             .constraints(null) /* TABLE_1_CONSTRAINTS */
             .creator(USER_1)
@@ -2649,7 +2660,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_1_QUEUE_NAME)
             .routingKey(TABLE_1_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(List.of() /* for jpa */)
             .constraints(null /* for jpa */) /* TABLE_1_CONSTRAINTS */
             .creator(null /* for jpa */)
@@ -2670,6 +2680,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_2_1_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_2_1_PRIMARY)
                     .enumValues(COLUMN_2_1_ENUM_VALUES)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_2_2_ID)
@@ -2684,6 +2695,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_2_2_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_2_2_PRIMARY)
                     .enumValues(COLUMN_2_2_ENUM_VALUES)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_2_3_ID)
@@ -2698,6 +2710,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_2_3_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_2_3_PRIMARY)
                     .enumValues(COLUMN_2_3_ENUM_VALUES)
+                    .creator(USER_1)
                     .build());
 
     public final static Table TABLE_2 = Table.builder()
@@ -2711,7 +2724,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_2_QUEUE_NAME)
             .routingKey(TABLE_2_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(TABLE_2_COLUMNS)
             .creator(USER_1)
             .created(TABLE_2_CREATED)
@@ -2729,7 +2741,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_2_QUEUE_NAME)
             .routingKey(TABLE_2_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(List.of() /* for jpa */)
             .creator(null /* for jpa */)
             .created(TABLE_2_CREATED)
@@ -2763,6 +2774,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(true)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2776,6 +2788,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2789,6 +2802,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2802,6 +2816,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dfid(IMAGE_DATE_2_ID)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2815,6 +2830,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2828,6 +2844,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2841,6 +2858,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2854,6 +2872,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2867,6 +2886,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2880,6 +2900,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2893,6 +2914,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dfid(IMAGE_DATE_2_ID)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2906,6 +2928,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2919,6 +2942,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2932,6 +2956,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2945,6 +2970,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2958,6 +2984,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2971,6 +2998,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2984,6 +3012,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -2997,6 +3026,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3010,6 +3040,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dfid(IMAGE_DATE_2_ID)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3023,6 +3054,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3036,6 +3068,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3049,6 +3082,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3062,6 +3096,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3075,6 +3110,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3088,6 +3124,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3101,6 +3138,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3114,6 +3152,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3127,6 +3166,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3140,6 +3180,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3153,6 +3194,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3166,6 +3208,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3179,6 +3222,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3192,6 +3236,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build(),
             TableColumn.builder()
                     .tid(TABLE_3_ID)
@@ -3205,6 +3250,7 @@ public abstract class BaseTest {
                     .isPrimaryKey(false)
                     .dateFormat(null)
                     .enumValues(null)
+                    .creator(USER_1)
                     .build());
 
     public final static Constraints TABLE_3_CONSTRAINTS = Constraints.builder()
@@ -3224,7 +3270,6 @@ public abstract class BaseTest {
             .routingKey(TABLE_3_ROUTING_KEY)
             .columns(TABLE_3_COLUMNS)
             .constraints(TABLE_3_CONSTRAINTS)
-            .createdBy(USER_1_ID)
             .creator(USER_1)
             .created(TABLE_3_CREATED)
             .lastModified(TABLE_3_LAST_MODIFIED)
@@ -3243,7 +3288,6 @@ public abstract class BaseTest {
             .routingKey(TABLE_3_ROUTING_KEY)
             .columns(List.of() /* for jpa */)
             .constraints(TABLE_3_CONSTRAINTS)
-            .createdBy(USER_1_ID)
             .creator(null /* for jpa */)
             .created(TABLE_3_CREATED)
             .lastModified(TABLE_3_LAST_MODIFIED)
@@ -3262,6 +3306,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_1_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_1_PRIMARY)
                     .enumValues(COLUMN_4_1_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_2_ID)
@@ -3276,6 +3321,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_2_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_2_PRIMARY)
                     .enumValues(COLUMN_4_2_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_3_ID)
@@ -3290,6 +3336,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_3_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_3_PRIMARY)
                     .enumValues(COLUMN_4_3_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_4_ID)
@@ -3304,6 +3351,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_4_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_4_PRIMARY)
                     .enumValues(COLUMN_4_4_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_5_ID)
@@ -3318,6 +3366,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_5_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_5_PRIMARY)
                     .enumValues(COLUMN_4_5_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_6_ID)
@@ -3332,6 +3381,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_6_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_6_PRIMARY)
                     .enumValues(COLUMN_4_6_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_7_ID)
@@ -3346,6 +3396,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_7_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_7_PRIMARY)
                     .enumValues(COLUMN_4_7_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_8_ID)
@@ -3360,6 +3411,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_8_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_8_PRIMARY)
                     .enumValues(COLUMN_4_8_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_9_ID)
@@ -3374,6 +3426,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_9_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_9_PRIMARY)
                     .enumValues(COLUMN_4_9_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_10_ID)
@@ -3388,6 +3441,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_10_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_10_PRIMARY)
                     .enumValues(COLUMN_4_10_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_11_ID)
@@ -3402,6 +3456,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_11_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_11_PRIMARY)
                     .enumValues(COLUMN_4_11_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_12_ID)
@@ -3416,6 +3471,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_12_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_12_PRIMARY)
                     .enumValues(COLUMN_4_12_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_13_ID)
@@ -3430,6 +3486,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_13_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_13_PRIMARY)
                     .enumValues(COLUMN_4_13_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_14_ID)
@@ -3444,6 +3501,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_14_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_14_PRIMARY)
                     .enumValues(COLUMN_4_14_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_15_ID)
@@ -3458,6 +3516,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_15_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_15_PRIMARY)
                     .enumValues(COLUMN_4_15_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_16_ID)
@@ -3472,6 +3531,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_16_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_16_PRIMARY)
                     .enumValues(COLUMN_4_16_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_17_ID)
@@ -3486,6 +3546,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_17_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_17_PRIMARY)
                     .enumValues(COLUMN_4_17_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_18_ID)
@@ -3500,6 +3561,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_18_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_18_PRIMARY)
                     .enumValues(COLUMN_4_18_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_19_ID)
@@ -3514,6 +3576,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_19_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_19_PRIMARY)
                     .enumValues(COLUMN_4_19_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_20_ID)
@@ -3528,6 +3591,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_20_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_20_PRIMARY)
                     .enumValues(COLUMN_4_20_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_4_21_ID)
@@ -3542,6 +3606,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_4_21_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_4_21_PRIMARY)
                     .enumValues(COLUMN_4_21_ENUM_VALUES)
+                    .creator(USER_2)
                     .build());
 
     public final static Constraints TABLE_4_CONSTRAINTS = Constraints.builder()
@@ -3560,7 +3625,6 @@ public abstract class BaseTest {
             .routingKey(TABLE_4_ROUTING_KEY)
             .columns(TABLE_4_COLUMNS)
             .constraints(TABLE_4_CONSTRAINTS)
-            .createdBy(USER_1_ID)
             .creator(USER_1)
             .build();
 
@@ -3627,6 +3691,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_5_1_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_5_1_PRIMARY)
                     .enumValues(COLUMN_5_1_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_5_2_ID)
@@ -3641,6 +3706,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_5_2_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_5_2_PRIMARY)
                     .enumValues(COLUMN_5_2_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_5_3_ID)
@@ -3655,6 +3721,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_5_3_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_5_3_PRIMARY)
                     .enumValues(COLUMN_5_3_ENUM_VALUES)
+                    .creator(USER_2)
                     .build());
 
     public final static Constraints TABLE_5_CONSTRAINTS = Constraints.builder()
@@ -3673,7 +3740,6 @@ public abstract class BaseTest {
             .routingKey(TABLE_5_ROUTING_KEY)
             .columns(TABLE_5_COLUMNS)
             .constraints(TABLE_5_CONSTRAINTS)
-            .createdBy(USER_1_ID)
             .creator(USER_1)
             .created(TABLE_5_CREATED)
             .lastModified(TABLE_5_LAST_MODIFIED)
@@ -3778,6 +3844,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_6_1_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_6_1_PRIMARY)
                     .enumValues(COLUMN_6_1_ENUM_VALUES)
+                    .creator(USER_2)
                     .build(),
             TableColumn.builder()
                     .id(COLUMN_6_2_ID)
@@ -3792,6 +3859,7 @@ public abstract class BaseTest {
                     .autoGenerated(COLUMN_6_2_AUTO_GENERATED)
                     .isPrimaryKey(COLUMN_6_2_PRIMARY)
                     .enumValues(COLUMN_6_2_ENUM_VALUES)
+                    .creator(USER_2)
                     .build());
 
     public final static Table TABLE_6 = Table.builder()
@@ -3804,7 +3872,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_2_ID)
             .queueName(TABLE_6_QUEUE_NAME)
             .routingKey(TABLE_6_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(TABLE_6_COLUMNS)
             .creator(USER_1)
             .created(TABLE_6_CREATED)
@@ -3821,7 +3888,6 @@ public abstract class BaseTest {
             .tdbid(DATABASE_1_ID)
             .queueName(TABLE_7_QUEUE_NAME)
             .routingKey(TABLE_7_ROUTING_KEY)
-            .createdBy(USER_1_ID)
             .columns(List.of())
             .creator(USER_1)
             .build();
@@ -4056,8 +4122,6 @@ public abstract class BaseTest {
     public final static String IDENTIFIER_1_TITLE_MODIFY = "Austrian weather some data";
     public final static String IDENTIFIER_1_DOI = null;
     public final static String IDENTIFIER_1_DOI_NOT_NULL = "10.1000/183";
-    public final static VisibilityType IDENTIFIER_1_VISIBILITY = VisibilityType.EVERYONE;
-    public final static VisibilityTypeDto IDENTIFIER_1_VISIBILITY_DTO = VisibilityTypeDto.EVERYONE;
     public final static Instant IDENTIFIER_1_CREATED = Instant.ofEpochSecond(1641588352) /* 2022-01-07 20:45:52 */;
     public final static Instant IDENTIFIER_1_MODIFIED = Instant.ofEpochSecond(1541588352) /* 2022-01-07 20:45:52 */;
     public final static Instant IDENTIFIER_1_EXECUTION = Instant.ofEpochSecond(1541588352) /* 2022-01-07 20:45:52 */;
@@ -4082,7 +4146,6 @@ public abstract class BaseTest {
             .lastname(CREATOR_1_LASTNAME)
             .orcid(CREATOR_1_ORCID)
             .affiliation(CREATOR_1_AFFIL)
-            .createdBy(IDENTIFIER_1_CREATED_BY)
             .creator(IDENTIFIER_1_CREATOR)
             .build();
 
@@ -4102,7 +4165,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_1_DESCRIPTION)
             .title(IDENTIFIER_1_TITLE)
             .doi(IDENTIFIER_1_DOI)
-            .visibility(IDENTIFIER_1_VISIBILITY)
             .created(IDENTIFIER_1_CREATED)
             .lastModified(IDENTIFIER_1_MODIFIED)
             .execution(IDENTIFIER_1_EXECUTION)
@@ -4115,7 +4177,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_1_RESULT_NUMBER)
             .publisher(IDENTIFIER_1_PUBLISHER)
             .type(IDENTIFIER_1_TYPE)
-            .createdBy(USER_1_ID)
             .creator(USER_1)
             .creators(List.of(IDENTIFIER_1_CREATOR_1))
             .build();
@@ -4128,7 +4189,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_1_DESCRIPTION)
             .title(IDENTIFIER_1_TITLE)
             .doi(IDENTIFIER_1_DOI)
-            .visibility(IDENTIFIER_1_VISIBILITY)
             .created(IDENTIFIER_1_CREATED)
             .lastModified(IDENTIFIER_1_MODIFIED)
             .execution(IDENTIFIER_1_EXECUTION)
@@ -4141,7 +4201,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_1_RESULT_NUMBER)
             .publisher(IDENTIFIER_1_PUBLISHER)
             .type(IDENTIFIER_1_TYPE)
-            .createdBy(USER_1_ID)
             .creator(null /* for jpa */)
             .creators(List.of() /* for jpa */)
             .build();
@@ -4154,7 +4213,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_1_DESCRIPTION)
             .title(IDENTIFIER_1_TITLE)
             .doi(IDENTIFIER_1_DOI_NOT_NULL)
-            .visibility(IDENTIFIER_1_VISIBILITY)
             .created(IDENTIFIER_1_CREATED)
             .lastModified(IDENTIFIER_1_MODIFIED)
             .execution(IDENTIFIER_1_EXECUTION)
@@ -4168,7 +4226,6 @@ public abstract class BaseTest {
             .publisher(IDENTIFIER_1_PUBLISHER)
             .type(IDENTIFIER_1_TYPE)
             .creator(USER_1)
-            .createdBy(USER_1_ID)
             .creators(List.of(IDENTIFIER_1_CREATOR_1))
             .build();
 
@@ -4180,7 +4237,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_1_DESCRIPTION)
             .title(IDENTIFIER_1_TITLE)
             .doi(IDENTIFIER_1_DOI)
-            .visibility(IDENTIFIER_1_VISIBILITY_DTO)
             .created(IDENTIFIER_1_CREATED)
             .lastModified(IDENTIFIER_1_MODIFIED)
             .execution(IDENTIFIER_1_EXECUTION)
@@ -4205,7 +4261,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_1_DESCRIPTION)
             .title(IDENTIFIER_1_TITLE)
             .doi(IDENTIFIER_1_DOI_NOT_NULL)
-            .visibility(IDENTIFIER_1_VISIBILITY_DTO)
             .created(IDENTIFIER_1_CREATED)
             .lastModified(IDENTIFIER_1_MODIFIED)
             .execution(IDENTIFIER_1_EXECUTION)
@@ -4229,8 +4284,6 @@ public abstract class BaseTest {
     public final static String IDENTIFIER_2_DESCRIPTION = "Selecting all from the weather Austria table";
     public final static String IDENTIFIER_2_TITLE = "Australian weather data";
     public final static String IDENTIFIER_2_DOI = null;
-    public final static VisibilityType IDENTIFIER_2_VISIBILITY = VisibilityType.EVERYONE;
-    public final static VisibilityTypeDto IDENTIFIER_2_VISIBILITY_DTO = VisibilityTypeDto.EVERYONE;
     public final static Instant IDENTIFIER_2_CREATED = Instant.ofEpochSecond(1641588352);
     public final static Instant IDENTIFIER_2_MODIFIED = Instant.ofEpochSecond(1541588352);
     public final static Instant IDENTIFIER_2_EXECUTION = Instant.ofEpochSecond(1541588352);
@@ -4248,38 +4301,40 @@ public abstract class BaseTest {
     public final static UUID IDENTIFIER_2_CREATED_BY = USER_2_ID;
     public final static User IDENTIFIER_2_CREATOR = USER_2;
 
+    public final static Long IDENTIFIER_2_CREATOR_1_ID = 2L;
+
     public final static Creator IDENTIFIER_2_CREATOR_1 = Creator.builder()
-            .id(CREATOR_1_ID)
+            .id(IDENTIFIER_2_CREATOR_1_ID)
             .pid(IDENTIFIER_2_ID)
             .firstname(CREATOR_1_FIRSTNAME)
             .lastname(CREATOR_1_LASTNAME)
             .orcid(CREATOR_1_ORCID)
             .affiliation(CREATOR_1_AFFIL)
-            .createdBy(IDENTIFIER_2_CREATED_BY)
             .creator(IDENTIFIER_2_CREATOR)
             .build();
 
     public final static CreatorDto IDENTIFIER_2_CREATOR_1_DTO = CreatorDto.builder()
-            .id(CREATOR_1_ID)
+            .id(IDENTIFIER_2_CREATOR_1_ID)
             .firstname(CREATOR_1_FIRSTNAME)
             .lastname(CREATOR_1_LASTNAME)
             .orcid(CREATOR_1_ORCID)
             .affiliation(CREATOR_1_AFFIL)
             .build();
 
+    public final static Long IDENTIFIER_2_CREATOR_2_ID = 3L;
+
     public final static Creator IDENTIFIER_2_CREATOR_2 = Creator.builder()
-            .id(CREATOR_2_ID)
+            .id(IDENTIFIER_2_CREATOR_2_ID)
             .pid(IDENTIFIER_2_ID)
             .firstname(CREATOR_2_FIRSTNAME)
             .lastname(CREATOR_2_LASTNAME)
             .orcid(CREATOR_2_ORCID)
             .affiliation(CREATOR_2_AFFIL)
-            .createdBy(IDENTIFIER_2_CREATED_BY)
             .creator(IDENTIFIER_2_CREATOR)
             .build();
 
     public final static CreatorDto IDENTIFIER_2_CREATOR_2_DTO = CreatorDto.builder()
-            .id(CREATOR_2_ID)
+            .id(IDENTIFIER_2_CREATOR_2_ID)
             .firstname(CREATOR_2_FIRSTNAME)
             .lastname(CREATOR_2_LASTNAME)
             .orcid(CREATOR_2_ORCID)
@@ -4294,7 +4349,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_2_DESCRIPTION)
             .title(IDENTIFIER_2_TITLE)
             .doi(IDENTIFIER_2_DOI)
-            .visibility(IDENTIFIER_2_VISIBILITY)
             .created(IDENTIFIER_2_CREATED)
             .lastModified(IDENTIFIER_2_MODIFIED)
             .execution(IDENTIFIER_2_EXECUTION)
@@ -4308,7 +4362,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_2_RESULT_NUMBER)
             .publisher(IDENTIFIER_2_PUBLISHER)
             .type(IDENTIFIER_2_TYPE)
-            .createdBy(USER_2_ID)
             .creator(USER_2)
             .creators(List.of(IDENTIFIER_2_CREATOR_1, IDENTIFIER_2_CREATOR_2))
             .build();
@@ -4321,7 +4374,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_2_DESCRIPTION)
             .title(IDENTIFIER_2_TITLE)
             .doi(IDENTIFIER_2_DOI)
-            .visibility(IDENTIFIER_2_VISIBILITY)
             .created(IDENTIFIER_2_CREATED)
             .lastModified(IDENTIFIER_2_MODIFIED)
             .execution(IDENTIFIER_2_EXECUTION)
@@ -4335,7 +4387,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_2_RESULT_NUMBER)
             .publisher(IDENTIFIER_2_PUBLISHER)
             .type(IDENTIFIER_2_TYPE)
-            .createdBy(USER_2_ID)
             .creator(null /* for jpa */)
             .creators(List.of() /* for jpa */)
             .build();
@@ -4348,7 +4399,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_2_DESCRIPTION)
             .title(IDENTIFIER_2_TITLE)
             .doi(IDENTIFIER_2_DOI)
-            .visibility(IDENTIFIER_2_VISIBILITY_DTO)
             .created(IDENTIFIER_2_CREATED)
             .lastModified(IDENTIFIER_2_MODIFIED)
             .execution(IDENTIFIER_2_EXECUTION)
@@ -4444,7 +4494,6 @@ public abstract class BaseTest {
             .publicationYear(IDENTIFIER_1_PUBLICATION_YEAR)
             .publicationMonth(IDENTIFIER_1_PUBLICATION_MONTH)
             .type(IDENTIFIER_1_TYPE_DTO)
-            .visibility(IDENTIFIER_1_VISIBILITY_DTO)
             .created(IDENTIFIER_1_CREATED)
             .lastModified(IDENTIFIER_1_MODIFIED)
             .creators(List.of(CREATOR_1_DTO))
@@ -4463,28 +4512,16 @@ public abstract class BaseTest {
             .type(IDENTIFIER_1_TYPE_DTO)
             .build();
 
-    public final static IdentifierCreateDto IDENTIFIER_1_DTO_TRUSTED_REQUEST = IdentifierCreateDto.builder()
+    public final static IdentifierUpdateDto IDENTIFIER_1_DTO_UPDATE_REQUEST = IdentifierUpdateDto.builder()
             .cid(IDENTIFIER_1_CONTAINER_ID)
             .dbid(IDENTIFIER_1_DATABASE_ID)
             .description(IDENTIFIER_1_DESCRIPTION)
-            .title(IDENTIFIER_1_TITLE)
-            .relatedIdentifiers(List.of())
-            .publicationMonth(IDENTIFIER_1_PUBLICATION_MONTH)
-            .publicationYear(IDENTIFIER_1_PUBLICATION_YEAR)
-            .creators(List.of(CREATOR_1_CREATE_DTO))
-            .publisher(IDENTIFIER_1_PUBLISHER)
-            .type(IDENTIFIER_1_TYPE_DTO)
-            .build();
-
-    public final static IdentifierCreateDto IDENTIFIER_1_DTO_SELF_REQUEST = IdentifierCreateDto.builder()
-            .cid(IDENTIFIER_1_CONTAINER_ID)
-            .dbid(IDENTIFIER_1_DATABASE_ID)
-            .description(IDENTIFIER_1_DESCRIPTION)
-            .title(IDENTIFIER_1_TITLE)
+            .title(IDENTIFIER_1_TITLE_MODIFY)
+            .doi(IDENTIFIER_1_DOI)
             .relatedIdentifiers(List.of())
             .publicationMonth(IDENTIFIER_1_PUBLICATION_MONTH)
             .publicationYear(IDENTIFIER_1_PUBLICATION_YEAR)
-            .creators(List.of(CREATOR_1_CREATE_DTO))
+            .creators(List.of(CREATOR_1_DTO))
             .publisher(IDENTIFIER_1_PUBLISHER)
             .type(IDENTIFIER_1_TYPE_DTO)
             .build();
@@ -4527,6 +4564,23 @@ public abstract class BaseTest {
             .type(IDENTIFIER_2_TYPE_DTO)
             .build();
 
+    public final static IdentifierUpdateDto IDENTIFIER_2_DTO_UPDATE_REQUEST = IdentifierUpdateDto.builder()
+            .qid(IDENTIFIER_2_QUERY_ID)
+            .cid(IDENTIFIER_2_CONTAINER_ID)
+            .qid(IDENTIFIER_2_QUERY_ID)
+            .dbid(IDENTIFIER_2_DATABASE_ID)
+            .description(IDENTIFIER_2_DESCRIPTION)
+            .title(IDENTIFIER_2_TITLE)
+            .doi(IDENTIFIER_2_DOI)
+            .relatedIdentifiers(List.of(IDENTIFIER_1_RELATED_IDENTIFIER_2_CREATE_DTO))
+            .publicationDay(IDENTIFIER_2_PUBLICATION_DAY)
+            .publicationMonth(IDENTIFIER_2_PUBLICATION_MONTH)
+            .publicationYear(IDENTIFIER_2_PUBLICATION_YEAR)
+            .creators(List.of(CREATOR_1_DTO, CREATOR_2_DTO))
+            .publisher(IDENTIFIER_2_PUBLISHER)
+            .type(IDENTIFIER_2_TYPE_DTO)
+            .build();
+
     public final static Long IDENTIFIER_3_ID = 3L;
     public final static Long IDENTIFIER_3_QUERY_ID = QUERY_3_ID;
     public final static Long IDENTIFIER_3_CONTAINER_ID = CONTAINER_3_ID;
@@ -4534,8 +4588,6 @@ public abstract class BaseTest {
     public final static String IDENTIFIER_3_DESCRIPTION = "Selecting all from the weather Norwegian table";
     public final static String IDENTIFIER_3_TITLE = "Norwegian weather data";
     public final static String IDENTIFIER_3_DOI = null;
-    public final static VisibilityType IDENTIFIER_3_VISIBILITY = VisibilityType.EVERYONE;
-    public final static VisibilityTypeDto IDENTIFIER_3_VISIBILITY_DTO = VisibilityTypeDto.EVERYONE;
     public final static Instant IDENTIFIER_3_CREATED = Instant.ofEpochSecond(1641588352);
     public final static Instant IDENTIFIER_3_MODIFIED = Instant.ofEpochSecond(1541588352);
     public final static Instant IDENTIFIER_3_EXECUTION = Instant.ofEpochSecond(1541588352);
@@ -4553,57 +4605,60 @@ public abstract class BaseTest {
     public final static UUID IDENTIFIER_3_CREATOR_ID = USER_3_ID;
     public final static User IDENTIFIER_3_CREATOR = USER_3;
 
+    private final static Long IDENTIFIER_3_CREATOR_1_ID = 4L;
+
     public final static Creator IDENTIFIER_3_CREATOR_1 = Creator.builder()
-            .id(CREATOR_1_ID)
+            .id(IDENTIFIER_3_CREATOR_1_ID)
             .pid(IDENTIFIER_3_ID)
             .firstname(CREATOR_1_FIRSTNAME)
             .lastname(CREATOR_1_LASTNAME)
             .orcid(CREATOR_1_ORCID)
             .affiliation(CREATOR_1_AFFIL)
-            .createdBy(IDENTIFIER_3_CREATOR_ID)
             .creator(IDENTIFIER_3_CREATOR)
             .build();
 
     public final static CreatorDto IDENTIFIER_3_CREATOR_1_DTO = CreatorDto.builder()
-            .id(CREATOR_1_ID)
+            .id(IDENTIFIER_3_CREATOR_1_ID)
             .firstname(CREATOR_1_FIRSTNAME)
             .lastname(CREATOR_1_LASTNAME)
             .orcid(CREATOR_1_ORCID)
             .affiliation(CREATOR_1_AFFIL)
             .build();
 
+    private final static Long IDENTIFIER_3_CREATOR_2_ID = 5L;
+
     public final static Creator IDENTIFIER_3_CREATOR_2 = Creator.builder()
-            .id(CREATOR_2_ID)
+            .id(IDENTIFIER_3_CREATOR_2_ID)
             .pid(IDENTIFIER_3_ID)
             .firstname(CREATOR_2_FIRSTNAME)
             .lastname(CREATOR_2_LASTNAME)
             .orcid(CREATOR_2_ORCID)
             .affiliation(CREATOR_2_AFFIL)
-            .createdBy(IDENTIFIER_3_CREATOR_ID)
             .creator(IDENTIFIER_3_CREATOR)
             .build();
 
     public final static CreatorDto IDENTIFIER_3_CREATOR_2_DTO = CreatorDto.builder()
-            .id(CREATOR_2_ID)
+            .id(IDENTIFIER_3_CREATOR_2_ID)
             .firstname(CREATOR_2_FIRSTNAME)
             .lastname(CREATOR_2_LASTNAME)
             .orcid(CREATOR_2_ORCID)
             .affiliation(CREATOR_2_AFFIL)
             .build();
 
+    private final static Long IDENTIFIER_3_CREATOR_3_ID = 6L;
+
     public final static Creator IDENTIFIER_3_CREATOR_3 = Creator.builder()
-            .id(CREATOR_3_ID)
+            .id(IDENTIFIER_3_CREATOR_3_ID)
             .pid(IDENTIFIER_3_ID)
             .firstname(CREATOR_3_FIRSTNAME)
             .lastname(CREATOR_3_LASTNAME)
             .orcid(CREATOR_3_ORCID)
             .affiliation(CREATOR_3_AFFIL)
-            .createdBy(IDENTIFIER_3_CREATOR_ID)
             .creator(IDENTIFIER_3_CREATOR)
             .build();
 
     public final static CreatorDto IDENTIFIER_3_CREATOR_3_DTO = CreatorDto.builder()
-            .id(CREATOR_3_ID)
+            .id(IDENTIFIER_3_CREATOR_3_ID)
             .firstname(CREATOR_3_FIRSTNAME)
             .lastname(CREATOR_3_LASTNAME)
             .orcid(CREATOR_3_ORCID)
@@ -4618,7 +4673,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_3_DESCRIPTION)
             .title(IDENTIFIER_3_TITLE)
             .doi(IDENTIFIER_3_DOI)
-            .visibility(IDENTIFIER_3_VISIBILITY)
             .created(IDENTIFIER_3_CREATED)
             .lastModified(IDENTIFIER_3_MODIFIED)
             .execution(IDENTIFIER_3_EXECUTION)
@@ -4632,7 +4686,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_3_RESULT_NUMBER)
             .publisher(IDENTIFIER_3_PUBLISHER)
             .type(IDENTIFIER_3_TYPE)
-            .createdBy(IDENTIFIER_3_CREATOR_ID)
             .creator(IDENTIFIER_3_CREATOR)
             .creators(List.of(IDENTIFIER_3_CREATOR_1, IDENTIFIER_3_CREATOR_2, IDENTIFIER_3_CREATOR_3))
             .build();
@@ -4645,7 +4698,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_3_DESCRIPTION)
             .title(IDENTIFIER_3_TITLE)
             .doi(IDENTIFIER_3_DOI)
-            .visibility(IDENTIFIER_3_VISIBILITY)
             .created(IDENTIFIER_3_CREATED)
             .lastModified(IDENTIFIER_3_MODIFIED)
             .execution(IDENTIFIER_3_EXECUTION)
@@ -4659,7 +4711,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_3_RESULT_NUMBER)
             .publisher(IDENTIFIER_3_PUBLISHER)
             .type(IDENTIFIER_3_TYPE)
-            .createdBy(USER_3_ID)
             .creator(null /* for jpa */)
             .creators(List.of() /* for jpa */)
             .build();
@@ -4672,7 +4723,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_3_DESCRIPTION)
             .title(IDENTIFIER_3_TITLE)
             .doi(IDENTIFIER_3_DOI)
-            .visibility(IDENTIFIER_3_VISIBILITY_DTO)
             .created(IDENTIFIER_3_CREATED)
             .lastModified(IDENTIFIER_3_MODIFIED)
             .execution(IDENTIFIER_3_EXECUTION)
@@ -4704,28 +4754,17 @@ public abstract class BaseTest {
             .type(IDENTIFIER_3_TYPE_DTO)
             .build();
 
-    public final static IdentifierCreateDto IDENTIFIER_3_DTO_TRUSTED_REQUEST = IdentifierCreateDto.builder()
-            .cid(IDENTIFIER_3_CONTAINER_ID)
-            .dbid(IDENTIFIER_3_DATABASE_ID)
-            .description(IDENTIFIER_3_DESCRIPTION)
-            .title(IDENTIFIER_3_TITLE)
-            .relatedIdentifiers(List.of())
-            .publicationMonth(IDENTIFIER_3_PUBLICATION_MONTH)
-            .publicationYear(IDENTIFIER_3_PUBLICATION_YEAR)
-            .creators(List.of(CREATOR_1_CREATE_DTO))
-            .publisher(IDENTIFIER_3_PUBLISHER)
-            .type(IDENTIFIER_3_TYPE_DTO)
-            .build();
-
-    public final static IdentifierCreateDto IDENTIFIER_3_DTO_SELF_REQUEST = IdentifierCreateDto.builder()
+    public final static IdentifierUpdateDto IDENTIFIER_3_DTO_UPDATE_REQUEST = IdentifierUpdateDto.builder()
             .cid(IDENTIFIER_3_CONTAINER_ID)
             .dbid(IDENTIFIER_3_DATABASE_ID)
+            .qid(IDENTIFIER_3_QUERY_ID)
             .description(IDENTIFIER_3_DESCRIPTION)
             .title(IDENTIFIER_3_TITLE)
+            .doi(IDENTIFIER_3_DOI)
             .relatedIdentifiers(List.of())
             .publicationMonth(IDENTIFIER_3_PUBLICATION_MONTH)
             .publicationYear(IDENTIFIER_3_PUBLICATION_YEAR)
-            .creators(List.of(CREATOR_1_CREATE_DTO))
+            .creators(List.of(CREATOR_1_DTO))
             .publisher(IDENTIFIER_3_PUBLISHER)
             .type(IDENTIFIER_3_TYPE_DTO)
             .build();
@@ -4736,7 +4775,6 @@ public abstract class BaseTest {
     public final static String IDENTIFIER_4_DESCRIPTION = "Selecting all from the weather Sweden table";
     public final static String IDENTIFIER_4_TITLE = "Sweden weather data";
     public final static String IDENTIFIER_4_DOI = null;
-    public final static VisibilityType IDENTIFIER_4_VISIBILITY = VisibilityType.EVERYONE;
     public final static Instant IDENTIFIER_4_CREATED = Instant.ofEpochSecond(1641588352);
     public final static Instant IDENTIFIER_4_MODIFIED = Instant.ofEpochSecond(1541588352);
     public final static Instant IDENTIFIER_4_EXECUTION = Instant.ofEpochSecond(1541588352);
@@ -4760,7 +4798,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_4_DESCRIPTION)
             .title(IDENTIFIER_4_TITLE)
             .doi(IDENTIFIER_4_DOI)
-            .visibility(IDENTIFIER_4_VISIBILITY)
             .created(IDENTIFIER_4_CREATED)
             .lastModified(IDENTIFIER_4_MODIFIED)
             .execution(IDENTIFIER_4_EXECUTION)
@@ -4774,7 +4811,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_4_RESULT_NUMBER)
             .publisher(IDENTIFIER_4_PUBLISHER)
             .type(IDENTIFIER_4_TYPE)
-            .createdBy(USER_3_ID)
             .creator(USER_3)
             .creators(List.of())
             .build();
@@ -4786,7 +4822,6 @@ public abstract class BaseTest {
             .description(IDENTIFIER_4_DESCRIPTION)
             .title(IDENTIFIER_4_TITLE)
             .doi(IDENTIFIER_4_DOI)
-            .visibility(IDENTIFIER_4_VISIBILITY)
             .created(IDENTIFIER_4_CREATED)
             .lastModified(IDENTIFIER_4_MODIFIED)
             .execution(IDENTIFIER_4_EXECUTION)
@@ -4800,7 +4835,6 @@ public abstract class BaseTest {
             .resultNumber(IDENTIFIER_4_RESULT_NUMBER)
             .publisher(IDENTIFIER_4_PUBLISHER)
             .type(IDENTIFIER_4_TYPE)
-            .createdBy(USER_3_ID)
             .creator(null /* for jpa */)
             .creators(List.of() /* for jpa */)
             .build();
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
index 54f9367e07cd0dd7040ea75a201e7d36fcc0c4ba..696f3494cce67e895d8a3f7c2a41e221cd32d82d 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServiceIntegrationTest.java
@@ -59,7 +59,7 @@ public class IdentifierServiceIntegrationTest extends BaseUnitTest {
         userRepository.save(USER_1_SIMPLE);
         containerRepository.save(CONTAINER_1_SIMPLE);
         databaseRepository.save(DATABASE_1_SIMPLE);
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
     }
 
     @Test
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java
index 3fcaa1a83813f1ee73414a4089197c7dc598fb98..743505dfb6844aa24b63ca6d03f8059ff251fa71 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceIntegrationTest.java
@@ -63,7 +63,7 @@ public class MetadataServiceIntegrationTest extends BaseUnitTest {
         userRepository.save(USER_1_SIMPLE);
         containerRepository.save(CONTAINER_1_SIMPLE);
         databaseRepository.save(DATABASE_1_SIMPLE);
-        identifierRepository.save(IDENTIFIER_1_SIMPLE);
+        identifierRepository.save(IDENTIFIER_1);
     }
 
     @Test
diff --git a/dbrepo-semantics-service/Dockerfile b/dbrepo-semantics-service/Dockerfile
index 64a5a2844ba315cd331b97048a9989656493c071..bb9426c7b036a80e64590e0392b63016efa005df 100644
--- a/dbrepo-semantics-service/Dockerfile
+++ b/dbrepo-semantics-service/Dockerfile
@@ -14,6 +14,8 @@ ENV LOG_LEVEL=debug
 ENV METADATA_DB=fda
 ENV METADATA_USERNAME=root
 ENV METADATA_PASSWORD=dbrepo
+ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo
+ENV JWT_PUBKEY=public-key
 
 WORKDIR /app
 
diff --git a/dbrepo-semantics-service/app.py b/dbrepo-semantics-service/app.py
index 573e74fbf6d9c5d8dec2a90c2e21d880a6a1812e..4e9c167c75b3d52fc609208edc7dfea94df178bc 100644
--- a/dbrepo-semantics-service/app.py
+++ b/dbrepo-semantics-service/app.py
@@ -13,6 +13,7 @@ from gevent.pywsgi import WSGIServer
 from save import insert_mdb_concepts, insert_mdb_units
 from onto_feat import list_ontologies, get_ontology
 from prometheus_flask_exporter import PrometheusMetrics
+from flask_jwt_extended import jwt_required, JWTManager
 
 dictConfig({
     'version': 1,
@@ -34,7 +35,11 @@ app = Flask(__name__)
 metrics = PrometheusMetrics(app)
 metrics.info('app_info', 'Application info', version='1.2.0')
 app.config['SWAGGER'] = {'openapi': '3.0.0', 'title': 'Swagger UI', 'uiversion': 3}
-
+# https://flask-jwt-extended.readthedocs.io/en/stable/options/
+app.config['JWT_ALGORITHM'] = 'HS256'
+app.config['JWT_DECODE_ISSUER'] = os.getenv('JWT_ISSUER')
+app.config['JWT_PUBLIC_KEY'] = os.getenv('JWT_PUBKEY')
+jwt = JWTManager(app)
 list = List(offline=False)
 
 swagger_config = {
@@ -194,6 +199,7 @@ def get_concept_label():
 
 @app.route('/api/semantics/concept', methods=['POST'], endpoint='concepts_save')
 @swag_from('us-yml/post_concept.yml')
+@jwt_required()
 def save_concept():
     input_json = request.get_json()
     logging.debug('endpoint save concept, body=%s', input_json)
@@ -216,7 +222,8 @@ def save_concept():
 
 @app.route('/api/semantics/unit', methods=['POST'], endpoint='units_save')
 @swag_from('us-yml/post_unit.yml')
-def save_concept():
+@jwt_required()
+def save_unit():
     input_json = request.get_json()
     logging.debug('endpoint save unit, body=%s', input_json)
     try:
diff --git a/dbrepo-semantics-service/requirements.txt b/dbrepo-semantics-service/requirements.txt
index 403f546c43e095d88d16d44b111332c189fa20c4..bf2f5a864c50536632b0af24dad167b4b3c8783a 100644
--- a/dbrepo-semantics-service/requirements.txt
+++ b/dbrepo-semantics-service/requirements.txt
@@ -36,4 +36,5 @@ zope.event==4.6
 zope.interface==5.5.2
 html5lib==1.1
 pytest==7.2.1
-coverage==7.1.0
\ No newline at end of file
+coverage==7.1.0
+Flask-JWT-Extended==4.4.4
\ No newline at end of file
diff --git a/dbrepo-semantics-service/test.sh b/dbrepo-semantics-service/test.sh
index c12f137c54a8ff2f25f108efcb888d96cf611ec8..ece11dc3c1799be6e4da437d1142d119a8727773 100755
--- a/dbrepo-semantics-service/test.sh
+++ b/dbrepo-semantics-service/test.sh
@@ -1,3 +1,4 @@
 #!/bin/bash
 source ./dbrepo-semantics-service/venv/bin/activate
-cd ./dbrepo-semantics-service/ && coverage run -m pytest test/test_validate.py test/test_list.py test/test_app.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
\ No newline at end of file
+#cd ./dbrepo-semantics-service/ && coverage run -m pytest test/test_validate.py test/test_list.py test/test_app.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
+cd ./dbrepo-semantics-service/ && coverage run -m pytest test/test_validate.py --junitxml=report.xml && coverage html && coverage report > ./coverage.txt
\ No newline at end of file
diff --git a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationReadTest.java b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationReadTest.java
index d6e09d16241553b574ee5061546f32111891ade9..7f209d1d864efc8b8853e0dde409a2650113a1e9 100644
--- a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationReadTest.java
+++ b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationReadTest.java
@@ -100,8 +100,8 @@ public class TableServiceIntegrationReadTest extends BaseUnitTest {
         containerRepository.save(CONTAINER_1_SIMPLE);
         containerRepository.save(CONTAINER_2_SIMPLE);
         databaseRepository.save(DATABASE_1_SIMPLE);
-        tableRepository.save(TABLE_1_SIMPLE);
-        tableRepository.save(TABLE_2_SIMPLE);
+        tableRepository.save(TABLE_1);
+        tableRepository.save(TABLE_2);
     }
 
     @Test
diff --git a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationWriteTest.java b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationWriteTest.java
index 4fab7905dd319bed297ef679784c55092b0e66cd..2bdbfed18a07d723f9e49dc8c0488d2f82826b30 100644
--- a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationWriteTest.java
+++ b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationWriteTest.java
@@ -29,6 +29,7 @@ import java.security.Principal;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.doNothing;
@@ -99,8 +100,8 @@ public class TableServiceIntegrationWriteTest extends BaseUnitTest {
         containerRepository.save(CONTAINER_1_SIMPLE);
         containerRepository.save(CONTAINER_2_SIMPLE);
         databaseRepository.save(DATABASE_1_SIMPLE);
-        tableRepository.save(TABLE_1_SIMPLE);
-        tableRepository.save(TABLE_2_SIMPLE);
+        tableRepository.save(TABLE_1);
+        tableRepository.save(TABLE_2);
     }
 
     @AfterEach
@@ -181,6 +182,22 @@ public class TableServiceIntegrationWriteTest extends BaseUnitTest {
 
         /* test */
         tableService.deleteTable(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID);
+        assertTrue(userRepository.findById(TABLE_1_CREATED_BY).isPresent());
+        assertTrue(databaseRepository.findById(TABLE_1_DATABASE_ID).isPresent());
+    }
+
+    @Test
+    public void delete_notFound_fails() {
+
+        /* mock */
+        doNothing()
+                .when(tableidxRepository)
+                .delete(any(TableDto.class));
+
+        /* test */
+        assertThrows(TableNotFoundException.class, () -> {
+            tableService.deleteTable(CONTAINER_1_ID, DATABASE_1_ID, 9999L);
+        });
     }
 
 }
diff --git a/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java b/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
index 311067b40c6c5dc5c434fb713dc0257ba22f49dd..d12f4c9e432f0cc081abca833905d6fcda59e266 100644
--- a/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
+++ b/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
@@ -208,7 +208,6 @@ public interface TableMapper {
             @Mapping(source = "table.creator", target = "creator"),
             @Mapping(source = "table", target = "table"),
             @Mapping(target = "id", ignore = true),
-            @Mapping(target = "createdBy", source = "table.createdBy"),
             @Mapping(target = "autoGenerated", expression = "java(data.getInternalName() == \"id\" && query.getGenerated())"),
             @Mapping(source = "data.name", target = "name"),
             @Mapping(source = "data.internalName", target = "internalName"),
diff --git a/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java
index 4e27bb9b516159f1abcedead47d6506ee45c04cf..8523187a3ae8c0e28689f7d9b9cd13ea985c0732 100644
--- a/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java
+++ b/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java
@@ -2,7 +2,6 @@ package at.tuwien.service.impl;
 
 import at.tuwien.api.database.table.TableCreateDto;
 import at.tuwien.api.database.table.TableCreateRawQuery;
-import at.tuwien.api.database.table.TableDto;
 import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto;
 import at.tuwien.entities.container.Container;
 import at.tuwien.entities.database.Database;
@@ -98,14 +97,10 @@ public class TableServiceImpl extends HibernateConnector implements TableService
         } finally {
             dataSource.close();
         }
-        log.info("Deleted table with id {}", table.getId());
-        log.trace("deleted table {}", table);
-        /* delete in database_index - elastic search */
-        tableIdxRepository.deleteById(tableId);
-        /* delete in column_index - elastic search */
-        table.getColumns()
-                .forEach(column -> tableColumnIdxRepository.deleteById(column.getId()));
-        log.info("Deleted columns in elastic search with id {}", databaseId);
+        tableRepository.delete(table);
+        log.info("Deleted table with id {} in metadata database", table.getId());
+        tableIdxRepository.delete(tableMapper.tableToTableDto(table));
+        log.info("Deleted table with id {} in search service", table.getId());
     }
 
     @Override
@@ -174,7 +169,7 @@ public class TableServiceImpl extends HibernateConnector implements TableService
         tmp.setColumns(List.of());
         tmp.setConstraints(null);
         final User creator = userService.findByUsername(principal.getName());
-        tmp.setCreatedBy(creator.getId());
+        tmp.setCreator(creator);
         /* save in metadata database */
         final Table entity = tableRepository.save(tmp);
         entity.setColumns(createDto.getColumns()
diff --git a/dbrepo-ui/api/container.service.js b/dbrepo-ui/api/container.service.js
index 77f56972b5e85df9c3a5a844551c349645f7c1e6..c24ffbd9d57aebc5d31f872d982e81b60e169727 100644
--- a/dbrepo-ui/api/container.service.js
+++ b/dbrepo-ui/api/container.service.js
@@ -19,6 +19,23 @@ class ContainerService {
     })
   }
 
+  findAllImages () {
+    return new Promise((resolve, reject) => {
+      api.get('/api/image', { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const images = response.data
+          console.debug('response images', images)
+          resolve(images)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load images', error)
+          Vue.$toast.error(`[${code}] Failed to load images: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
   findOne (id) {
     return new Promise((resolve, reject) => {
       api.get(`/api/container/${id}`, { headers: { Accept: 'application/json' } })
diff --git a/dbrepo-ui/api/database.service.js b/dbrepo-ui/api/database.service.js
index 50aefa4251d0bfdd58dcde55d9b356f6c7000770..85756eaca0db53e7b963558abcb68af4a8a51de4 100644
--- a/dbrepo-ui/api/database.service.js
+++ b/dbrepo-ui/api/database.service.js
@@ -51,6 +51,19 @@ class DatabaseService {
     })
   }
 
+  delete (id, databaseId) {
+    return new Promise((resolve, reject) => {
+      api.delete(`/api/container/${id}/database/${databaseId}`, { headers: { Accept: 'application/json' } })
+        .then(() => resolve())
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to delete database', error)
+          Vue.$toast.error(`[${code}] Failed to delete database: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
   modifyVisibility (id, databaseId, isPublic) {
     return new Promise((resolve, reject) => {
       api.put(`/api/container/${id}/database/${databaseId}/visibility`, { is_public: isPublic }, { headers: { Accept: 'application/json' } })
@@ -92,10 +105,13 @@ class DatabaseService {
           resolve(databases)
         })
         .catch((error) => {
-          const { code, message } = error
-          console.error('Failed to check database access', error)
-          Vue.$toast.error(`[${code}] Failed to check database access: ${message}`)
-          reject(error)
+          const { code, message, response } = error
+          const { status } = response
+          if (status !== 403 && status !== 405) { /* ignore no access errors */
+            console.error('Failed to check database access', error)
+            Vue.$toast.error(`[${code}] Failed to check database access: ${message}`)
+            reject(error)
+          }
         })
     })
   }
@@ -160,6 +176,23 @@ class DatabaseService {
     })
   }
 
+  findView (id, databaseId, viewId) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/view/${viewId}`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const view = response.data
+          console.debug('response view', view)
+          resolve(view)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to find view', error)
+          Vue.$toast.error(`[${code}] Failed to find view: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
   createView (id, databaseId, data) {
     return new Promise((resolve, reject) => {
       api.post(`/api/container/${id}/database/${databaseId}/view`, data, { headers: { Accept: 'application/json' } })
diff --git a/dbrepo-ui/api/identifier.service.js b/dbrepo-ui/api/identifier.service.js
index 51181e9c590de56c1d69856e6d61585848579281..5372e0711d70d4d1fce4f25a2763f0458ff86c8c 100644
--- a/dbrepo-ui/api/identifier.service.js
+++ b/dbrepo-ui/api/identifier.service.js
@@ -20,9 +20,13 @@ class IdentifierService {
     })
   }
 
-  findOne (id) {
+  find (id) {
+    return this.findAccept(id, 'application/json')
+  }
+
+  findAccept (id, accept) {
     return new Promise((resolve, reject) => {
-      api.get(`/api/pid/${id}`, { headers: { Accept: 'application/json' } })
+      api.get(`/api/pid/${id}`, { headers: { Accept: accept } })
         .then((response) => {
           const identifier = response.data
           console.debug('response identifier', identifier)
@@ -47,8 +51,25 @@ class IdentifierService {
         })
         .catch((error) => {
           const { code, message } = error
-          console.error('Failed to load identifier', error)
-          Vue.$toast.error(`[${code}] Failed to load identifier: ${message}`)
+          console.error('Failed to create identifier', error)
+          Vue.$toast.error(`[${code}] Failed to create identifier: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  update (id, data) {
+    return new Promise((resolve, reject) => {
+      api.put(`/api/pid/${id}`, data, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const identifier = response.data
+          console.debug('response identifier', identifier)
+          resolve(identifier)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to update identifier', error)
+          Vue.$toast.error(`[${code}] Failed to update identifier: ${message}`)
           reject(error)
         })
     })
diff --git a/dbrepo-ui/api/query.service.js b/dbrepo-ui/api/query.service.js
index 09db2312e76e49ebf8cdf11f40a2fa2664c4fdb4..a7900546e0cd3cf7a7c99db12c7a677296673bc3 100644
--- a/dbrepo-ui/api/query.service.js
+++ b/dbrepo-ui/api/query.service.js
@@ -119,7 +119,9 @@ class QueryService {
     return new Promise((resolve, reject) => {
       api.get(`/api/container/${id}/database/${databaseId}/query/${queryId}/export`, { headers: { Accept: 'text/csv' } })
         .then((response) => {
-          resolve(response.data)
+          const subset = response.data
+          console.debug('response subset', subset)
+          resolve(subset)
         })
         .catch((error) => {
           const { code, message } = error
@@ -134,12 +136,99 @@ class QueryService {
     return new Promise((resolve, reject) => {
       api.get(`/api/pid/${id}`, { headers: { Accept: mime } })
         .then((response) => {
-          resolve(response.data)
+          const metadata = response.data
+          console.debug('response metadata', metadata)
+          resolve(metadata)
         })
         .catch((error) => {
           const { code, message } = error
-          console.error('Failed to export query', error)
-          Vue.$toast.error(`[${code}] Failed to export query: ${message}`)
+          console.error('Failed to export metadata', error)
+          Vue.$toast.error(`[${code}] Failed to export metadata: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  execute (id, databaseId, data, page, size) {
+    return new Promise((resolve, reject) => {
+      api.post(`/api/container/${id}/database/${databaseId}?page=${page}&size=${size}`, data, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const result = response.data
+          console.debug('response result', result)
+          resolve(result)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to execute statement', error)
+          Vue.$toast.error(`[${code}] Failed to execute statement: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  reExecuteQuery (id, databaseId, queryId, page, size) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/query/${queryId}/data?page=${page}&size=${size}`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const result = response.data
+          console.debug('response result', result)
+          resolve(result)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to re-execute query', error)
+          Vue.$toast.error(`[${code}] Failed to re-execute query: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  reExecuteQueryCount (id, databaseId, queryId) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/query/${queryId}/data/count`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const count = response.data
+          console.debug('response count', count)
+          resolve(count)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to re-execute query count', error)
+          Vue.$toast.error(`[${code}] Failed to re-execute query count: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  reExecuteView (id, databaseId, viewId, page, size) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/view/${viewId}/data?page=${page}&size=${size}`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const result = response.data
+          console.debug('response result', result)
+          resolve(result)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to re-execute view', error)
+          Vue.$toast.error(`[${code}] Failed to re-execute view: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  reExecuteViewCount (id, databaseId, viewId) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/view/${viewId}/data/count`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const count = response.data
+          console.debug('response count', count)
+          resolve(count)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to re-execute view count', error)
+          Vue.$toast.error(`[${code}] Failed to re-execute view count: ${message}`)
           reject(error)
         })
     })
diff --git a/dbrepo-ui/api/search.service.js b/dbrepo-ui/api/search.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..4cdd577bb8094a210d3171e842c190281f7102b8
--- /dev/null
+++ b/dbrepo-ui/api/search.service.js
@@ -0,0 +1,24 @@
+import Vue from 'vue'
+import axios from 'axios'
+import { elasticPassword } from '../config'
+
+class SearchService {
+  search (query) {
+    return new Promise((resolve, reject) => {
+      axios.get(`/retrieve/_all/_search?q=${query}*&terminate_after=50`, { headers: { Accept: 'application/json' }, auth: { username: 'elastic', password: elasticPassword } })
+        .then((response) => {
+          const hits = response.data.hits.hits
+          console.debug('response hits', hits)
+          resolve(hits)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load search results', error)
+          Vue.$toast.error(`[${code}] Failed to load search results: ${message}`)
+          reject(error)
+        })
+    })
+  }
+}
+
+export default new SearchService()
diff --git a/dbrepo-ui/api/table.service.js b/dbrepo-ui/api/table.service.js
index d5fc87b38bdb0927fb435e5306871a23df7e64ad..930aae5a8ee79ef8f008860fbae3cd1f7de94cc0 100644
--- a/dbrepo-ui/api/table.service.js
+++ b/dbrepo-ui/api/table.service.js
@@ -41,6 +41,95 @@ class TableService {
     })
   }
 
+  updateColumn (id, databaseId, tableId, columnId, data) {
+    return new Promise((resolve, reject) => {
+      api.put(`/api/container/${id}/database/${databaseId}/table/${tableId}/column/${columnId}`, data, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const column = response.data
+          console.debug('response column', column)
+          resolve(column)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to update column', error)
+          Vue.$toast.error(`[${code}] Failed to update column: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  data (id, databaseId, tableId, page, size, timestamp) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/data?page=${page}&size=${size}&timestamp=${timestamp}`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const data = response.data
+          console.debug('response data', data)
+          resolve(data)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load table data', error)
+          Vue.$toast.error(`[${code}] Failed to load table data: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  dataCount (id, databaseId, tableId, timestamp) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/data/count?timestamp=${timestamp}`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const count = response.data
+          console.debug('response count', count)
+          resolve(count)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load table count', error)
+          Vue.$toast.error(`[${code}] Failed to load table count: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  findHistory (id, databaseId, tableId) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/history`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const history = response.data
+          console.debug('response history', history)
+          resolve(history)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load table history', error)
+          Vue.$toast.error(`[${code}] Failed to load table history: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  exportData (id, databaseId, tableId) {
+    return this.exportDataTimestamp(id, databaseId, tableId, null)
+  }
+
+  exportDataTimestamp (id, databaseId, tableId, timestamp) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/export?timestamp=${timestamp}`, { responseType: 'text' })
+        .then((response) => {
+          const data = response.data
+          console.debug('response data', data)
+          resolve(data)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to export table data', error)
+          Vue.$toast.error(`[${code}] Failed to export table data: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
   create (id, databaseId, data) {
     return new Promise((resolve, reject) => {
       api.post(`/api/container/${id}/database/${databaseId}/table`, data, { headers: { Accept: 'application/json' } })
diff --git a/dbrepo-ui/components/TableList.vue b/dbrepo-ui/components/TableList.vue
index 86aed98663b9b03ce9f2f121342b87fa302a2f8c..f627f9387e4e329e73c7ff922e55fe2387c26c0a 100644
--- a/dbrepo-ui/components/TableList.vue
+++ b/dbrepo-ui/components/TableList.vue
@@ -1,6 +1,6 @@
 <template>
   <div>
-    <v-progress-linear v-if="loading" :color="loadingColor" indeterminate />
+    <v-progress-linear v-if="loading" indeterminate />
     <v-card v-if="!loading && tables && tables.length === 0" flat>
       <v-card-text>
         (no tables)
@@ -24,7 +24,6 @@
 
 <script>
 import { formatTimestampUTCLabel } from '@/utils'
-import TableService from '@/api/table.service'
 
 export default {
   data () {
@@ -79,17 +78,6 @@ export default {
     token () {
       return this.$store.state.token
     },
-    loadingColor () {
-      return this.error ? 'red lighten-2' : 'primary'
-    },
-    config () {
-      if (this.token === null) {
-        return {}
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` }
-      }
-    },
     user () {
       return this.$store.state.user
     },
@@ -144,53 +132,12 @@ export default {
       }
       return column.column_type
     },
-    details (table) {
-      /* use cache */
-      this.tableDetails = table
-      /* load remaining info */
-      if (this.canRead) {
-        this.loadingDetails = true
-        TableService.findOne(this.$route.params.container_id, this.$route.params.database_id, table.id)
-          .then((table) => {
-            this.tableDetails = table
-            if (table.id) {
-              this.openPanelByTableId(table.id)
-            }
-          })
-          .finally(() => {
-            this.loadingDetails = false
-          })
-      }
-    },
-    is_owner (table) {
-      if (!this.user) {
-        return false
-      }
-      return table.creator.username === this.user.username
-    },
     closed (data) {
       console.debug('closed dialog', data)
       this.dialogSemantic = false
     },
     created (created) {
       return formatTimestampUTCLabel(created)
-    },
-    async deleteTable () {
-      try {
-        this.loading = true
-        await this.$axios.delete(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.deleteTableId}`, this.config)
-        this.loading = false
-        this.refresh()
-      } catch (err) {
-        this.$toast.error('Could not delete table')
-      }
-      this.dialogDelete = false
-    },
-    /**
-     * open up the accordion with the table that has been updated (by the ColumnUnit dialog)
-     */
-    openPanelByTableId (id) {
-      this.panel = this.tables.findIndex(t => t.id === id)
     }
   }
 }
diff --git a/dbrepo-ui/components/dialogs/CreateDB.vue b/dbrepo-ui/components/dialogs/CreateDB.vue
index e73cd6f336e9b332c0c2c127d5c3394bfb6cdd19..0259ba127e6470ca28562c0c97536d3ba7cfc263 100644
--- a/dbrepo-ui/components/dialogs/CreateDB.vue
+++ b/dbrepo-ui/components/dialogs/CreateDB.vue
@@ -65,7 +65,6 @@ export default {
     return {
       valid: false,
       loading: false,
-      error: false,
       engine: {
         repository: null,
         tag: null
@@ -90,23 +89,9 @@ export default {
     }
   },
   computed: {
-    loadingColor () {
-      return this.error ? 'red lighten-2' : 'primary'
-    },
     token () {
       return this.$store.state.token
     },
-    config () {
-      if (this.token === null) {
-        return {
-          headers: {}
-        }
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` },
-        progress: false
-      }
-    },
     user () {
       return this.$store.state.user
     }
@@ -121,20 +106,18 @@ export default {
     cancel () {
       this.$emit('close', { success: false })
     },
-    async getImages () {
-      try {
-        this.loading = true
-        const res = await this.$axios.get('/api/image')
-        this.engines = res.data
-        console.debug('engines', this.engines)
-        if (this.engines.length > 0) {
-          this.engine = this.engines[0]
-        }
-      } catch (err) {
-        this.error = true
-        this.$toast.error('Failed to fetch supported engines. Try reload the page')
-      }
-      this.loading = false
+    getImages () {
+      this.loading = true
+      ContainerService.findAllImages()
+        .then((images) => {
+          this.engines = images
+          if (this.engines.length > 0) {
+            this.engine = this.engines[0]
+          }
+        })
+        .finally(() => {
+          this.loading = false
+        })
     },
     async create () {
       await this.createContainer()
diff --git a/dbrepo-ui/components/dialogs/DeleteIdentifier.vue b/dbrepo-ui/components/dialogs/DeleteIdentifier.vue
new file mode 100644
index 0000000000000000000000000000000000000000..373f2b86c62ce0baa2fef230cd7b2ccea6119cfe
--- /dev/null
+++ b/dbrepo-ui/components/dialogs/DeleteIdentifier.vue
@@ -0,0 +1,108 @@
+<template>
+  <div>
+    <v-form ref="form" v-model="valid" autocomplete="off" @submit.prevent="submit">
+      <v-card>
+        <v-card-title v-text="title" />
+        <v-card-text>
+          <v-row dense>
+            <v-col>
+              This action cannot be undone! Type the identifier <strong>{{ confirmText }}</strong> below if you really want to delete it.
+            </v-col>
+          </v-row>
+          <v-row dense>
+            <v-col>
+              <v-text-field
+                id="confirm"
+                v-model="confirm"
+                name="confirm"
+                label="Identifier *"
+                autofocus
+                required />
+            </v-col>
+          </v-row>
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer />
+          <v-btn
+            class="mb-2"
+            @click="cancel">
+            Cancel
+          </v-btn>
+          <v-btn
+            class="mb-2 mr-1"
+            color="error"
+            :loading="loadingDelete"
+            :disabled="confirm !== confirmText"
+            @click="deleteIdentifier">
+            Delete
+          </v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-form>
+  </div>
+</template>
+
+<script>
+import IdentifierService from '@/api/identifier.service'
+
+export default {
+  props: {
+    identifier: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  data () {
+    return {
+      confirm: null,
+      loadingDelete: false,
+      valid: false
+    }
+  },
+  computed: {
+    title () {
+      if (!this.identifier || !('doi' in this.identifier)) {
+        return null
+      }
+      if (this.identifier.doi) {
+        return `DOI ${this.identifier.doi}`
+      }
+      return `Identifier with id ${this.identifier.id}`
+    },
+    confirmText () {
+      if (!this.identifier || !('doi' in this.identifier)) {
+        return null
+      }
+      if (this.identifier.doi) {
+        return this.identifier.doi
+      }
+      return `/pid/${this.identifier.id}`
+    }
+  },
+  methods: {
+    submit () {
+      this.$refs.form.validate()
+    },
+    cancel () {
+      this.$emit('close', { action: 'closed' })
+    },
+    deleteIdentifier () {
+      if (!this.identifier.id) {
+        return
+      }
+      this.loadingDelete = true
+      IdentifierService.delete(this.identifier.id)
+        .then(() => {
+          console.info('Deleted identifier with id ', this.identifier.id)
+          this.$toast.success('Successfully deleted identifier with id ' + this.identifier.id)
+          this.$emit('close', { action: 'deleted' })
+        })
+        .finally(() => {
+          this.loadingDelete = false
+        })
+    }
+  }
+}
+</script>
diff --git a/dbrepo-ui/components/dialogs/EditAccess.vue b/dbrepo-ui/components/dialogs/EditAccess.vue
index 4f30ef9d85c74d9efbd52f6181a00840e999f9d7..1ba70270745992a29236b81d58b561d1cfc140b0 100644
--- a/dbrepo-ui/components/dialogs/EditAccess.vue
+++ b/dbrepo-ui/components/dialogs/EditAccess.vue
@@ -73,6 +73,8 @@
 </template>
 
 <script>
+import DatabaseService from '@/api/database.service'
+import UserService from '@/api/user.service'
 export default {
   props: {
     username: {
@@ -195,69 +197,49 @@ export default {
         await this.giveAccess()
       }
     },
-    async revokeAccess () {
+    revokeAccess () {
       this.loading = true
-      try {
-        const res = await this.$axios.delete(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/access/${this.username}`, this.config)
-        console.debug('revoke access', res.data)
-        this.$toast.success(`Successfully revoked access of ${this.username}`)
-        this.$emit('close-dialog', { success: true })
-      } catch (err) {
-        console.log('revoke access', err)
-        this.$toast.error('Could not revoke access to database')
-      }
-      this.loading = false
+      DatabaseService.revokeAccess(this.$route.params.container_id, this.$route.params.database_id, this.username)
+        .then(() => {
+          this.$toast.success(`Successfully revoked access of ${this.username}`)
+          this.$emit('close-dialog', { success: true })
+        })
+        .finally(() => {
+          this.loading = false
+        })
     },
-    async modifyAccess () {
+    modifyAccess () {
       this.loading = true
-      try {
-        const res = await this.$axios.put(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/access/${this.username}`, {
-          type: this.modify.type
-        }, this.config)
-        console.debug('give access', res.data)
-        this.$toast.success('Successfully modified access')
-        this.$emit('close-dialog', { success: true })
-      } catch (err) {
-        console.log('modify access', err)
-        this.$toast.error('Could not modify access to database')
-      }
-      this.loading = false
+      DatabaseService.modifyAccess(this.$route.params.container_id, this.$route.params.database_id, this.username, this.modify.type)
+        .then(() => {
+          this.$toast.success('Successfully modified access')
+          this.$emit('close-dialog', { success: true })
+        })
+        .finally(() => {
+          this.loading = false
+        })
     },
-    async giveAccess () {
+    giveAccess () {
       const username = this.modify.username
       this.loading = true
-      try {
-        const res = await this.$axios.post(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/access`, this.modify, this.config)
-        console.debug('give access', res.data)
-        this.$toast.success(`Successfully gave ${username} access`)
-        this.$emit('close-dialog', { success: true })
-      } catch (err) {
-        if (err.response.status === 405) {
-          this.$toast.error(`User ${username} already has access`)
-          this.loading = false
-          return
-        } else if (err.response.status === 404) {
-          this.$toast.error(`User ${username} does not exist`)
+      DatabaseService.giveAccess(this.$route.params.container_id, this.$route.params.database_id, this.username, this.modify.type)
+        .then(() => {
+          this.$toast.success(`Successfully gave ${username} access`)
+          this.$emit('close-dialog', { success: true })
+        })
+        .finally(() => {
           this.loading = false
-          return
-        }
-        console.log('give access', err)
-        this.$toast.error('Could not give access to database')
-      }
-      this.loading = false
+        })
     },
-    async loadUsers () {
+    loadUsers () {
       this.loadingUsers = true
-      try {
-        const res = await this.$axios.get('/api/user', this.config)
-        this.users = res.data.filter(u => u.username !== this.database.creator.username)
-        console.debug('users', this.users)
-      } catch (error) {
-        console.error('Failed to load users', error)
-        const { message } = error.response.data
-        this.$toast.error(`Failed to load users: ${message}`)
-      }
-      this.loadingUsers = false
+      UserService.findAll()
+        .then((users) => {
+          this.users = users.filter(u => u.username !== this.database.creator.username)
+        })
+        .finally(() => {
+          this.loading = false
+        })
     },
     init () {
       if (!this.username) {
diff --git a/dbrepo-ui/components/dialogs/EditRoles.vue b/dbrepo-ui/components/dialogs/EditRoles.vue
deleted file mode 100644
index 81860aa010978cfab86fdb329c6cb85e8089657f..0000000000000000000000000000000000000000
--- a/dbrepo-ui/components/dialogs/EditRoles.vue
+++ /dev/null
@@ -1,174 +0,0 @@
-<template>
-  <div>
-    <v-form ref="form" v-model="valid" autocomplete="off" @submit.prevent="submit">
-      <v-card>
-        <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" />
-        <v-card-title>
-          User Role
-        </v-card-title>
-        <v-card-subtitle>
-          Modify user role
-        </v-card-subtitle>
-        <v-card-text>
-          <v-alert
-            v-if="becomeDeveloper"
-            border="left"
-            color="warning">
-            <strong>Dangerous operation:</strong> you are giving this user developer access. This cannot be undone.
-          </v-alert>
-          <v-row>
-            <v-col>
-              <v-autocomplete
-                v-model="selectedUser"
-                :items="users"
-                :loading="loadingUsers"
-                :rules="[v => !!v || $t('Required')]"
-                required
-                hide-no-data
-                hide-selected
-                hide-details
-                return-object
-                item-text="username"
-                item-value="username"
-                single-line
-                label="Username" />
-            </v-col>
-          </v-row>
-          <v-row>
-            <v-col>
-              <v-select
-                v-model="modify.roles"
-                :items="roles"
-                multiple
-                item-value="code"
-                item-text="text"
-                :rules="[v => !!v || $t('Required')]"
-                required
-                label="Role type" />
-            </v-col>
-          </v-row>
-        </v-card-text>
-        <v-card-actions>
-          <v-spacer />
-          <v-btn
-            class="mb-2"
-            @click="cancel">
-            Cancel
-          </v-btn>
-          <v-btn
-            id="database"
-            class="mb-2 ml-3 mr-2"
-            :disabled="!valid || loading"
-            color="primary"
-            type="submit"
-            :loading="loading"
-            @click="updateRoles">
-            Save
-          </v-btn>
-        </v-card-actions>
-      </v-card>
-    </v-form>
-  </div>
-</template>
-
-<script>
-export default {
-  props: {
-    user: {
-      type: Object,
-      default () {
-        return {}
-      }
-    }
-  },
-  data () {
-    return {
-      valid: false,
-      loading: false,
-      loadingUsers: false,
-      selectedUser: null,
-      users: [],
-      error: false,
-      roles: [
-        { text: 'Researcher', value: 'researcher', code: 'ROLE_RESEARCHER' },
-        { text: 'Data Steward', value: 'data_steward', code: 'ROLE_DATA_STEWARD' },
-        { text: 'Developer', value: 'developer', code: 'ROLE_DEVELOPER' }
-      ],
-      modify: {
-        roles: []
-      }
-    }
-  },
-  computed: {
-    loadingColor () {
-      return this.error ? 'red lighten-2' : 'primary'
-    },
-    token () {
-      return this.$store.state.token
-    },
-    config () {
-      if (this.token === null) {
-        return {}
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` }
-      }
-    },
-    becomeDeveloper () {
-      return this.modify.roles.filter(r => r === 'ROLE_DEVELOPER').length > 0
-    }
-  },
-  watch: {
-    user (newVal, oldVal) {
-      this.modify.roles = newVal.roles
-      this.selectedUser = newVal
-    }
-  },
-  mounted () {
-    this.loadUsers()
-    this.modify.roles = this.user.roles
-    this.selectedUser = this.user
-  },
-  methods: {
-    submit () {
-      this.$refs.form.validate()
-    },
-    cancel () {
-      this.$emit('close-dialog', { success: false })
-    },
-    async updateRoles () {
-      this.loading = true
-      const roles = {
-        roles: this.modify.roles.map(role => this.roles.filter(r => r.code === role)[0].value)
-      }
-      try {
-        const res = await this.$axios.put(`/api/user/${this.selectedUser.id}/roles`, roles, this.config)
-        console.debug('roles', res.data)
-        this.$toast.success('Updated user roles')
-        this.$emit('close-dialog', { success: true })
-      } catch (error) {
-        const { message } = error.response
-        this.$toast.error('Failed to update user roles: ' + message)
-        console.error('Failed to update user roles', error)
-      }
-      this.loading = false
-    },
-    async loadUsers () {
-      this.loading = true
-      try {
-        const res = await this.$axios.get('/api/user', this.config)
-        this.users = res.data.map((user) => {
-          user.roles_pretty = user.roles.join(',')
-          return user
-        })
-        console.debug('users', this.users)
-      } catch (error) {
-        const { message } = error.response
-        this.$toast.error('Failed to load users: ' + message)
-        console.error('Failed to load users', error)
-      }
-      this.loading = false
-    }
-  }
-}
-</script>
diff --git a/dbrepo-ui/components/dialogs/Persist.vue b/dbrepo-ui/components/dialogs/Persist.vue
index 1f87e7ef2d30383c52d8cdf75b6eed62af5728bb..704a8b342cb8e5914beea4484d87a8d9f8841689 100644
--- a/dbrepo-ui/components/dialogs/Persist.vue
+++ b/dbrepo-ui/components/dialogs/Persist.vue
@@ -1,8 +1,7 @@
 <template>
   <div>
     <v-card>
-      <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" />
-      <v-card-title v-text="`Persist ${title}`" />
+      <v-card-title v-text="title" />
       <v-card-text>
         <v-alert
           v-if="isSubset"
@@ -17,6 +16,7 @@
                 id="title"
                 v-model="identifier.title"
                 name="title"
+                autofocus
                 :label="`${prefix} title *`"
                 :rules="[v => !!v || $t('Required')]"
                 required />
@@ -93,7 +93,8 @@
               <v-text-field
                 v-model="creator.orcid"
                 name="orcid"
-                label="ORCID" />
+                label="ORCID"
+                :rules="[v => !validateOrcidInput(v) || $t('Please remove the https:// part')]" />
             </v-col>
             <v-col v-if="i > 0" cols="1" class="mt-5">
               <v-btn icon x-small @click="deleteCreator(i)">
@@ -168,7 +169,18 @@
           Cancel
         </v-btn>
         <v-btn
+          v-if="isUpdate"
           class="mb-2"
+          :loading="loading"
+          :disabled="!formValid || loading"
+          color="primary"
+          @click="update">
+          Update
+        </v-btn>
+        <v-btn
+          v-if="!isUpdate"
+          class="mb-2"
+          :loading="loading"
           :disabled="!formValid || loading"
           color="primary"
           @click="persist">
@@ -183,6 +195,7 @@
 import { formatYearUTC, formatMonthUTC, formatDayUTC } from '@/utils'
 import IdentifierService from '@/api/identifier.service'
 import DatabaseService from '@/api/database.service'
+
 export default {
   props: {
     type: {
@@ -208,7 +221,7 @@ export default {
         qid: parseInt(this.$route.params.query_id),
         title: null,
         description: null,
-        publisher: null,
+        publisher: this.$config.defaultPublisher,
         publication_year: formatYearUTC(Date.now()),
         publication_month: formatMonthUTC(Date.now()),
         publication_day: formatDayUTC(Date.now()),
@@ -298,13 +311,20 @@ export default {
     isDatabase () {
       return this.type === 'database'
     },
+    isUpdate () {
+      if (!this.database.identifier) {
+        return false
+      }
+      return this.database.identifier.id !== null
+    },
     title () {
+      let title = (this.isUpdate ? 'Update' : 'Get') + ' '
       if (this.isSubset) {
-        return 'subset'
+        title += 'subset'
       } else if (this.isDatabase) {
-        return 'database'
+        title += 'database'
       }
-      return ''
+      return (title + ' identifier')
     },
     prefix () {
       if (this.isSubset) {
@@ -315,13 +335,22 @@ export default {
       return ''
     }
   },
+  watch: {
+    database () {
+      if (!this.database.identifier) {
+        return
+      }
+      this.identifier = Object.assign(this.database.identifier, {})
+    }
+  },
   mounted () {
     this.loadLicenses()
-    this.identifier.publisher = this.$config.defaultPublisher
+    if (this.database.identifier) {
+      this.identifier = Object.assign(this.database.identifier, {})
+    }
   },
   methods: {
     cancel () {
-      this.$parent.$parent.$parent.persistQueryDialog = false
       this.$emit('close', { action: 'closed' })
     },
     addCreator () {
@@ -352,7 +381,6 @@ export default {
       this.loading = true
       IdentifierService.create(this.identifier)
         .then(() => {
-          this.$store.dispatch('reloadDatabase')
           this.$toast.success(this.prefix + ' successfully persisted')
           this.$emit('close', { action: 'persisted' })
         })
@@ -360,6 +388,32 @@ export default {
           this.loading = false
         })
     },
+    update () {
+      this.loading = true
+      const payload = {
+        cid: parseInt(this.$route.params.container_id),
+        dbid: parseInt(this.$route.params.database_id),
+        qid: parseInt(this.$route.params.query_id),
+        title: this.identifier.title,
+        description: this.identifier.description,
+        publisher: this.identifier.publisher,
+        publication_year: this.identifier.publication_year,
+        publication_month: this.identifier.publication_month,
+        publication_day: this.identifier.publication_day,
+        license: this.identifier.license,
+        type: this.identifier.type,
+        creators: this.identifier.creators,
+        related_identifiers: this.identifier.related_identifiers
+      }
+      IdentifierService.update(this.identifier.id, payload)
+        .then(() => {
+          this.$toast.success(this.prefix + ' identifier successfully updated')
+          this.$emit('close', { action: 'persisted' })
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
     loadLicenses () {
       if (!this.token) {
         return
@@ -372,6 +426,12 @@ export default {
         .finally(() => {
           this.loading = false
         })
+    },
+    validateOrcidInput (val) {
+      if (!val) {
+        return false
+      }
+      return val.startsWith('http')
     }
   }
 }
diff --git a/dbrepo-ui/components/dialogs/TimeTravel.vue b/dbrepo-ui/components/dialogs/TimeTravel.vue
index 4866c7974cf588dab26c27af9b8611cb37888fd5..1007db8b50784920d627063bc05db023c86b9d4f 100644
--- a/dbrepo-ui/components/dialogs/TimeTravel.vue
+++ b/dbrepo-ui/components/dialogs/TimeTravel.vue
@@ -1,7 +1,7 @@
 <template>
   <div>
     <v-card>
-      <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" />
+      <v-progress-linear v-if="loading" color="primary" />
       <v-card-title>
         Versioning
       </v-card-title>
@@ -55,6 +55,7 @@
 </template>
 
 <script>
+import TableService from '@/api/table.service'
 import { Bar } from 'vue-chartjs/legacy'
 import { Chart as ChartJS, Title, Tooltip, BarElement, CategoryScale, LinearScale, LogarithmicScale } from 'chart.js'
 import { formatTimestampUTC, formatTimestampUTCLabel } from '@/utils'
@@ -69,7 +70,6 @@ export default {
     return {
       formValid: false,
       loading: false,
-      error: false,
       datetime: null,
       chartData: {
         labels: [],
@@ -89,22 +89,6 @@ export default {
       totalChanges: 0
     }
   },
-  computed: {
-    loadingColor () {
-      return this.error ? 'error' : 'primary'
-    },
-    token () {
-      return this.$store.state.token
-    },
-    config () {
-      if (this.token === null) {
-        return {}
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` }
-      }
-    }
-  },
   mounted () {
     this.loadHistory()
   },
@@ -129,29 +113,25 @@ export default {
         time: this.datetime
       })
     },
-    async loadHistory () {
-      try {
-        this.loading = true
-        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/history`, this.config)
-        this.error = false
-        this.chartData.labels = res.data.map(function (d, idx) {
-          if (idx === 0) {
-            return 'Origin'
-          }
-          return formatTimestampUTCLabel(d.timestamp)
+    loadHistory () {
+      this.loading = true
+      TableService.findHistory(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id)
+        .then((history) => {
+          this.chartData.labels = history.map(function (d, idx) {
+            if (idx === 0) {
+              return 'Origin'
+            }
+            return formatTimestampUTCLabel(d.timestamp)
+          })
+          this.chartData.dates = history.map(d => formatTimestampUTC(d.timestamp))
+          this.chartData.datasets = [{
+            backgroundColor: this.$vuetify.theme.themes.light.primary,
+            data: history.map(d => d.total)
+          }]
+        })
+        .finally(() => {
+          this.loading = false
         })
-        this.chartData.dates = res.data.map(d => formatTimestampUTC(d.timestamp))
-        this.chartData.datasets = [{
-          backgroundColor: this.$vuetify.theme.themes.light.primary,
-          data: res.data.map(d => d.total)
-        }]
-        // this.totalChanges = this.res.data.length
-        console.debug('history', this.chartData)
-      } catch (err) {
-        this.error = true
-        console.error('failed to load table history', err)
-      }
-      this.loading = false
     }
   }
 }
diff --git a/dbrepo-ui/components/identifier/Banner.vue b/dbrepo-ui/components/identifier/Banner.vue
index ece371663f1d888d8c4ee90c316cc45a163c1eb0..990bf61f5c6dda27ee8998dddc5aee261be368a6 100644
--- a/dbrepo-ui/components/identifier/Banner.vue
+++ b/dbrepo-ui/components/identifier/Banner.vue
@@ -15,7 +15,7 @@ export default {
   },
   computed: {
     baseUrl () {
-      return `${location.protocol}//${location.host}`
+      return `${this.$config.baseUrl}`
     },
     baseDoi () {
       return this.$config.doiUrl
diff --git a/dbrepo-ui/components/identifier/Citation.vue b/dbrepo-ui/components/identifier/Citation.vue
index 5701cfa672deef809690770509286455bff023c3..479243cc458e930387e90cbacb37287607136248 100644
--- a/dbrepo-ui/components/identifier/Citation.vue
+++ b/dbrepo-ui/components/identifier/Citation.vue
@@ -35,6 +35,7 @@
 </template>
 
 <script>
+import IdentifierService from '@/api/identifier.service'
 
 export default {
   props: {
@@ -57,17 +58,9 @@ export default {
       citation: null
     }
   },
-  computed: {
-    config () {
-      return {
-        headers: { Accept: 'text/bibliography;style=apa' },
-        progress: false
-      }
-    }
-  },
   watch: {
-    style (newVal, _) {
-      this.loadCitation(newVal)
+    style () {
+      this.loadCitation(this.style)
     },
     pid () {
       this.loadCitation(this.style)
@@ -78,25 +71,18 @@ export default {
     this.loadCitation(null)
   },
   methods: {
-    async loadCitation (accept) {
+    loadCitation (accept) {
       if (!this.pid) {
         return
       }
       this.loading = true
-      try {
-        const config = this.config
-        if (accept != null) {
-          config.headers.Accept = accept
-        }
-        const res = await this.$axios.get(`/api/pid/${this.pid}`, config)
-        this.citation = res.data
-        console.debug('citation', this.citation)
-      } catch (err) {
-        console.error('Could not cite identifier', err)
-        this.$toast.error('Could not cite identifier')
-        this.error = true
-      }
-      this.loading = false
+      IdentifierService.findAccept(this.pid, accept)
+        .then((citation) => {
+          this.citation = citation
+        })
+        .finally(() => {
+          this.loading = false
+        })
     }
   }
 }
diff --git a/dbrepo-ui/components/identifier/DownloadButton.vue b/dbrepo-ui/components/identifier/DownloadButton.vue
index 0ccc07b8afc299d1f1626ddc0ed74e24bd7f49b4..495d2b0a8421da07f783dea5e29ddd8554a0c9dc 100644
--- a/dbrepo-ui/components/identifier/DownloadButton.vue
+++ b/dbrepo-ui/components/identifier/DownloadButton.vue
@@ -8,6 +8,7 @@
 </template>
 
 <script>
+import IdentifierService from '@/api/identifier.service'
 
 export default {
   props: {
@@ -35,41 +36,21 @@ export default {
       loading: false
     }
   },
-  computed: {
-    token () {
-      return this.$store.state.token
-    },
-    config () {
-      if (this.token === null) {
-        return {
-          headers: { Accept: 'application/json' }
-        }
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}`, Accept: 'application/json' }
-      }
-    }
-  },
   methods: {
-    async download () {
+    download () {
       this.loading = true
-      try {
-        const config = this.config
-        config.headers.Accept = this.contentType
-        const res = await this.$axios.get(`/api/pid/${this.pid}`, config)
-        console.debug('export identifier', res)
-        const url = window.URL.createObjectURL(new Blob([res.data]))
-        const link = document.createElement('a')
-        link.href = url
-        link.setAttribute('download', this.filename)
-        document.body.appendChild(link)
-        link.click()
-      } catch (err) {
-        console.error('Could not export identifier', err)
-        this.$toast.error('Could not export identifier')
-        this.error = true
-      }
-      this.loading = false
+      IdentifierService.export(this.pid)
+        .then((data) => {
+          const url = window.URL.createObjectURL(new Blob([data]))
+          const link = document.createElement('a')
+          link.href = url
+          link.setAttribute('download', this.filename)
+          document.body.appendChild(link)
+          link.click()
+        })
+        .finally(() => {
+          this.loading = false
+        })
     }
   }
 }
diff --git a/dbrepo-ui/components/query/Results.vue b/dbrepo-ui/components/query/Results.vue
index d04b59378614effed3b3a6595bbb912f13c164bc..1f30ee22566bf88f9184576f86ffac03c42c8967 100644
--- a/dbrepo-ui/components/query/Results.vue
+++ b/dbrepo-ui/components/query/Results.vue
@@ -9,6 +9,7 @@
 </template>
 
 <script>
+import QueryService from '@/api/query.service'
 export default {
   props: {
     type: {
@@ -32,24 +33,6 @@ export default {
       total: -1
     }
   },
-  computed: {
-    token () {
-      return this.$store.state.token
-    },
-    config () {
-      if (this.token === null) {
-        return {}
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` }
-      }
-    },
-    executeUrl () {
-      const page = 0
-      const urlParams = `page=${page}&size=${this.options.itemsPerPage}`
-      return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/query?${urlParams}`
-    }
-  },
   watch: {
     options: { /* keep */
       handler () {
@@ -59,28 +42,20 @@ export default {
     }
   },
   methods: {
-    async executeFirstTime (parent, sql, timestamp) {
+    executeFirstTime (parent, sql, timestamp) {
       this.loading++
-      try {
-        const res = await this.$axios.post(this.executeUrl, { statement: sql, timestamp }, this.config)
-        console.debug('query result', res.data)
-        this.$toast.success('Successfully executed query')
-        this.mapResults(res.data)
-        parent.resultId = res.data.id
-      } catch (error) {
-        console.error('Failed to execute query', error)
-        const { status, data } = error.response
-        const { message, code } = data
-        if (status === 504) {
-          console.error('Failed to execute query: container not online', code)
-          this.$toast.error('Failed to execute query: container not online')
-        } else {
-          console.error('Failed to execute query', code)
-          this.$toast.error('Failed to execute query: ' + message)
-        }
-        this.error = true
+      const payload = {
+        statement: sql,
+        timestamp
       }
-      this.loading--
+      QueryService.execute(this.$route.params.container_id, this.$route.params.database_id, payload, 0, this.options.itemsPerPage)
+        .then((result) => {
+          this.mapResults(result)
+          parent.resultId = result.id
+        })
+        .finally(() => {
+          this.loading--
+        })
     },
     buildHeaders (firstLine) {
       return Object.keys(firstLine).map(k => ({
@@ -89,42 +64,53 @@ export default {
         sortable: false
       }))
     },
-    reExecuteUrl (resultId) {
-      const page = this.options.page - 1
-      const urlParams = `page=${page}&size=${this.options.itemsPerPage}`
-      return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/${this.type}/${resultId}/data?${urlParams}`
-    },
-    reExecuteCountUrl (resultId) {
-      return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/${this.type}/${resultId}/data/count`
-    },
-    async reExecute (id) {
+    reExecute (id) {
       if (id === null) {
         return
       }
       this.loading++
-      try {
-        const res = await this.$axios.get(this.reExecuteUrl(id), this.config)
-        this.mapResults(res.data)
-        this.id = id
-      } catch (error) {
-        console.error('failed to execute query', error)
-        this.error = true
+      if (this.type === 'query') {
+        QueryService.reExecuteQuery(this.$route.params.container_id, this.$route.params.database_id, this.resultId, 0, this.options.itemsPerPage)
+          .then((result) => {
+            this.mapResults(result)
+            this.id = id
+          })
+          .finally(() => {
+            this.loading--
+          })
+      } else {
+        QueryService.reExecuteView(this.$route.params.container_id, this.$route.params.database_id, this.resultId, 0, this.options.itemsPerPage)
+          .then((result) => {
+            this.mapResults(result)
+            this.id = id
+          })
+          .finally(() => {
+            this.loading--
+          })
       }
-      this.loading--
     },
-    async reExecuteCount (id) {
+    reExecuteCount (id) {
       if (id === null) {
         return
       }
       this.loading++
-      try {
-        const res = await this.$axios.get(this.reExecuteCountUrl(id), this.config)
-        this.total = res.data
-      } catch (error) {
-        console.error('failed to execute query count', error)
-        this.error = true
+      if (this.type === 'query') {
+        QueryService.reExecuteQueryCount(this.$route.params.container_id, this.$route.params.database_id, this.resultId)
+          .then((count) => {
+            this.total = count
+          })
+          .finally(() => {
+            this.loading--
+          })
+      } else {
+        QueryService.reExecuteViewCount(this.$route.params.container_id, this.$route.params.database_id, this.resultId)
+          .then((count) => {
+            this.total = count
+          })
+          .finally(() => {
+            this.loading--
+          })
       }
-      this.loading--
     },
     mapResults (data) {
       if (data.result.length) {
diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue
index 6c32c271c6cb43916edf25eb1338d0321d547d30..82ed12293a3f0a75108dff7f901ac7747ce69e65 100644
--- a/dbrepo-ui/layouts/default.vue
+++ b/dbrepo-ui/layouts/default.vue
@@ -301,7 +301,7 @@ export default {
         return
       }
       this.loading = true
-      IdentifierService.findOne(this.database.identifier.id)
+      IdentifierService.find(this.database.identifier.id)
         .then((identifier) => {
           this.database.identifier = identifier
           this.$store.commit('SET_DATABASE', this.database)
diff --git a/dbrepo-ui/nuxt.config.js b/dbrepo-ui/nuxt.config.js
index 5cf3275c2760b0410c480a6c20105749327b25a5..9c06aff0c1b6dadcfebcf8354041a1a89ccb1562 100644
--- a/dbrepo-ui/nuxt.config.js
+++ b/dbrepo-ui/nuxt.config.js
@@ -1,6 +1,6 @@
 import path from 'path'
 import colors from 'vuetify/es5/util/colors'
-import { api, icon, search, clientSecret, title, sandbox, logo, version, defaultPublisher, doiUrl } from './config'
+import { api, icon, search, clientSecret, title, sandbox, logo, version, defaultPublisher, doiUrl, baseUrl } from './config'
 
 const proxy = {}
 
@@ -93,7 +93,8 @@ export default {
     logo,
     clientSecret,
     defaultPublisher,
-    doiUrl
+    doiUrl,
+    baseUrl
   },
 
   serverMiddleware: [
@@ -109,7 +110,7 @@ export default {
           primary: colors.blue.darken2,
           accent: colors.amber.darken3,
           secondary: colors.blueGrey.base,
-          info: colors.amber.lighten4,
+          info: colors.amber.lighten1,
           code: colors.grey.lighten4,
           warning: colors.orange.lighten2,
           error: colors.red.base /* is used by forms */,
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/admin.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/admin.vue
index 6b9a9ba6cb5abceeb7e4d5cc0d2b38251efbdafc..c3d1f0f47fedc994be074e05d0d3d6c5fa3f88c8 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/admin.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/admin.vue
@@ -38,6 +38,7 @@
 
 <script>
 import DBToolbar from '@/components/DBToolbar'
+import DatabaseService from '@/api/database.service'
 
 export default {
   components: {
@@ -71,34 +72,14 @@ export default {
       return this.confirm !== this.db.internalName
     }
   },
-  mounted () {
-    this.init()
-  },
   methods: {
-    async deleteDatabase () {
-      try {
-        await this.$axios.delete(`/api/database/${this.$route.params.database_id}`)
-        this.$router.push({ path: '/databases' })
-        this.$toast.success(`Database "${this.db.name}" deleted.`)
-      } catch (err) {
-        this.$toast.error('Could not delete database')
-      }
-      this.dialogDelete = false
-    },
-    async init () {
-      if (this.db != null) {
-        return
-      }
-      try {
-        this.loading = true
-        const res = await this.$axios.get(`/api/database/${this.$route.params.database_id}`)
-        console.debug('database', res.data)
-        this.$store.commit('SET_DATABASE', res.data)
-        this.loading = false
-      } catch (err) {
-        this.$toast.error('Could not load database')
-        this.loading = false
-      }
+    deleteDatabase () {
+      DatabaseService.delete(this.$route.params.container_id, this.$route.params.database_id)
+        .then(async () => {
+          this.$toast.success(`Database "${this.db.name}" deleted.`)
+          await this.$router.push({ path: '/databases' })
+          this.dialogDelete = false
+        })
     }
   }
 }
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue
index dd4a71e482aa248464033918fdbec02b6b2f62bc..1940e0bc089adb839b35e5e0355dfdadb5916d6e 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue
@@ -113,15 +113,22 @@
                 v-if="canCreateIdentifier"
                 small
                 color="primary"
-                @click="editDbDialog = true">
+                @click="persistDialog = true">
                 Get Database PID
               </v-btn>
               <v-btn
-                v-if="canDeleteIdentifier"
+                v-if="canEditIdentifier"
+                small
+                color="secondary"
+                @click="persistDialog = true">
+                Edit Database PID
+              </v-btn>
+              <v-btn
+                v-if="canDeleteIdentifier && hasIdentifier"
                 small
                 :loading="loadingDelete"
                 color="error"
-                @click="deleteIdentifier">
+                @click="deleteDialog = true">
                 Delete Database PID
               </v-btn>
             </v-card-actions>
@@ -214,10 +221,16 @@
       </v-tab-item>
     </v-tabs-items>
     <v-dialog
-      v-model="editDbDialog"
+      v-model="persistDialog"
+      persistent
+      max-width="1080">
+      <Persist type="database" :database="database" @close="closePersistDialog" />
+    </v-dialog>
+    <v-dialog
+      v-model="deleteDialog"
       persistent
-      max-width="860">
-      <Persist type="database" :database="database" @close="closeDialog" />
+      max-width="480">
+      <DeleteIdentifier :identifier="identifier" @close="closeDeleteDialog" />
     </v-dialog>
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
@@ -231,9 +244,11 @@ import Citation from '@/components/identifier/Citation'
 import { formatTimestampUTCLabel } from '@/utils'
 import Banner from '@/components/identifier/Banner'
 import DatabaseMapper from '@/api/database.mapper'
+import DeleteIdentifier from '@/components/dialogs/DeleteIdentifier.vue'
 
 export default {
   components: {
+    DeleteIdentifier,
     DBToolbar,
     Persist,
     OrcidIcon,
@@ -244,7 +259,9 @@ export default {
     return {
       loading: false,
       loadingDelete: false,
-      editDbDialog: false,
+      editDialog: false,
+      deleteDialog: false,
+      persistDialog: false,
       items: [
         { text: 'Databases', to: '/container', activeClass: '' },
         {
@@ -287,7 +304,7 @@ export default {
       if (!this.database) {
         return null
       }
-      return this.$store.state?.database.identifier
+      return this.database.identifier
     },
     access () {
       return this.$store.state.access
@@ -320,7 +337,7 @@ export default {
       if (!this.user) {
         return false
       }
-      return this.canCreateIdentifier || this.canDeleteIdentifier || this.roles.includes('modify-identifier-metadata')
+      return this.canCreateIdentifier || this.hasIdentifier
     },
     canCreateIdentifier () {
       if (!this.roles) {
@@ -329,10 +346,19 @@ export default {
       if (this.hasIdentifier) {
         return false
       }
-      return this.roles.includes('create-identifier')
+      return this.roles.includes('create-identifier') || this.roles.includes('create-foreign-identifier')
+    },
+    canEditIdentifier () {
+      if (!this.roles) {
+        return false
+      }
+      if (!this.hasIdentifier) {
+        return false
+      }
+      return this.roles.includes('modify-identifier-metadata')
     },
     canDeleteIdentifier () {
-      if (!this.user) {
+      if (!this.user || this.hasDoi) {
         return false
       }
       return this.roles.includes('delete-identifier')
@@ -361,6 +387,12 @@ export default {
       }
       return false
     },
+    hasDoi () {
+      if (!this.hasIdentifier || !('doi' in this.database.identifier)) {
+        return false
+      }
+      return this.database.identifier.doi !== null
+    },
     accessDescription () {
       if (!this.access) {
         return
@@ -378,44 +410,18 @@ export default {
     }
   },
   methods: {
-    async closeDialog (event) {
+    async closePersistDialog (event) {
       if (event.action === 'persisted') {
-        await this.loadDatabase()
+        await this.$store.dispatch('reloadDatabase')
       }
-      this.editDbDialog = false
+      this.persistDialog = false
       this.editVisibilityDialog = false
     },
-    async deleteIdentifier () {
-      if (!this.database.identifier.id) {
-        return
-      }
-      this.loadingDelete = true
-      try {
-        await this.$axios.delete(`/api/identifier/${this.database.identifier.id}`, this.config)
-        console.info('Deleted identifier with id ', this.database.identifier.id)
-        this.$toast.success('Successfully deleted identifier with id ' + this.database.identifier.id)
-        await this.loadDatabase()
-      } catch (error) {
-        const { message } = error.response
-        console.error('Failed to delete identifier', error)
-        this.$toast.error('Failed to delete identifier: ' + message)
-      }
-      this.loadingDelete = false
-    },
-    async loadDatabase () {
-      if (!this.$route.params.container_id || !this.$route.params.database_id) {
-        return
-      }
-      try {
-        this.loading = true
-        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}`, this.config)
-        this.$store.commit('SET_DATABASE', res.data)
-        console.debug('database', this.database)
-      } catch (err) {
-        console.error('Could not load database', err)
-        this.$toast.error('Could not load database')
+    async closeDeleteDialog (event) {
+      if (event.action === 'deleted') {
+        await this.$store.dispatch('reloadDatabase')
       }
-      this.loading = false
+      this.deleteDialog = false
     }
   }
 }
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue
index 59e8aa9acf679c91e21f5f02161e790a4ace0cb5..dfd27fc763f40c273408f55263ea0ecd76647700 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue
@@ -41,6 +41,7 @@
 <script>
 import TimeTravel from '@/components/dialogs/TimeTravel'
 import TableToolbar from '@/components/TableToolbar'
+import TableService from '@/api/table.service'
 import { formatTimestampUTC, formatDateUTC, formatTimestamp } from '@/utils'
 
 export default {
@@ -98,17 +99,6 @@ export default {
     table () {
       return this.$store.state.table
     },
-    config () {
-      if (this.token === null) {
-        return {
-          headers: {},
-          progress: false
-        }
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` }
-      }
-    },
     user () {
       return this.$store.state.user
     },
@@ -118,17 +108,6 @@ export default {
     access () {
       return this.$store.state.access
     },
-    downloadConfig () {
-      if (this.token === null) {
-        return {
-          responseType: 'text'
-        }
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` },
-        responseType: 'text'
-      }
-    },
     versionColor () {
       if (this.version === null) {
         return 'secondary white--text'
@@ -193,28 +172,35 @@ export default {
     this.loadProperties()
   },
   methods: {
-    async download () {
+    download () {
       this.downloadLoading = true
-      try {
-        let exportUrl = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/export`
-        if (this.version) {
-          exportUrl += `?timestamp=${this.versionISO}`
-        }
-        const res = await this.$axios.get(exportUrl, this.downloadConfig)
-        console.debug('export table', res)
-        const url = window.URL.createObjectURL(new Blob([res.data]))
-        const link = document.createElement('a')
-        link.href = url
-        link.setAttribute('download', 'table.csv')
-        document.body.appendChild(link)
-        link.click()
-      } catch (error) {
-        console.error('Failed to export table', error)
-        const { message } = error.response
-        this.$toast.error('Failed to export table: ' + message)
-        this.error = true
+      if (!this.version) {
+        TableService.exportData(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id)
+          .then((data) => {
+            const url = window.URL.createObjectURL(new Blob([data]))
+            const link = document.createElement('a')
+            link.href = url
+            link.setAttribute('download', 'table.csv')
+            document.body.appendChild(link)
+            link.click()
+          })
+          .finally(() => {
+            this.downloadLoading = false
+          })
+      } else {
+        TableService.exportData(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id, this.versionISO)
+          .then((data) => {
+            const url = window.URL.createObjectURL(new Blob([data]))
+            const link = document.createElement('a')
+            link.href = url
+            link.setAttribute('download', `table_${this.versionISO}.csv`)
+            document.body.appendChild(link)
+            link.click()
+          })
+          .finally(() => {
+            this.downloadLoading = false
+          })
       }
-      this.downloadLoading = false
     },
     pick () {
       if (this.$refs.timeTravel !== undefined) {
@@ -273,71 +259,37 @@ export default {
       this.loadData()
       this.loadCount()
     },
-    async loadData () {
-      try {
-        this.loadingData++
-        const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data?page=${this.options.page - 1}&size=${this.options.itemsPerPage}&timestamp=${this.versionISO || this.lastReload.toISOString()}`
-        if (this.version !== null) {
-          console.info('versioning active', this.version)
-        }
-        const res = await this.$axios.get(url, this.config)
-        this.rows = res.data.result.map((row) => {
-          for (const col in row) {
-            const columnDefinition = this.dateColumns.filter(c => c.internal_name === col)
-            if (columnDefinition.length > 0) {
-              if (columnDefinition[0].column_type === 'date') {
-                row[col] = formatDateUTC(row[col])
-              } else if (columnDefinition[0].column_type === 'timestamp') {
-                row[col] = formatTimestampUTC(row[col])
+    loadData () {
+      this.loadingData++
+      TableService.data(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id, (this.options.page - 1), this.options.itemsPerPage, (this.versionISO || this.lastReload.toISOString()))
+        .then((data) => {
+          this.rows = data.result.map((row) => {
+            for (const col in row) {
+              const columnDefinition = this.dateColumns.filter(c => c.internal_name === col)
+              if (columnDefinition.length > 0) {
+                if (columnDefinition[0].column_type === 'date') {
+                  row[col] = formatDateUTC(row[col])
+                } else if (columnDefinition[0].column_type === 'timestamp') {
+                  row[col] = formatTimestampUTC(row[col])
+                }
               }
             }
-          }
-          return row
+            return row
+          })
+        })
+        .finally(() => {
+          this.loadingData--
         })
-        console.debug('rows', this.rows)
-      } catch (error) {
-        console.error('Failed to load data', error)
-        this.error = true
-        this.loadProgress = 100
-        const { status, data } = error.response
-        const { message, code } = data
-        if (status === 423) {
-          console.error('Database is offline', code)
-          this.$toast.error('Database is offline: ' + message)
-        } else {
-          console.error('Failed to load data', code)
-          this.$toast.error('Failed to load data: ' + message)
-        }
-      } finally {
-        this.loadingData--
-      }
     },
-    async loadCount () {
-      try {
-        this.loadingData++
-        const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data/count?timestamp=${this.versionISO || this.lastReload.toISOString()}`
-        if (this.version !== null) {
-          console.info('versioning active', this.version)
-        }
-        const res = await this.$axios.get(url, this.config)
-        this.total = res.data
-        console.info('total', this.total)
-      } catch (error) {
-        console.error('Failed to load count', error)
-        this.error = true
-        this.loadProgress = 100
-        const { status, data } = error.response
-        const { message, code } = data
-        if (status === 423) {
-          console.error('Database is offline', code)
-          this.$toast.error('Database is offline: ' + message)
-        } else {
-          console.error('Failed to load data', code)
-          this.$toast.error('Failed to load data: ' + message)
-        }
-      } finally {
-        this.loadingData--
-      }
+    loadCount () {
+      this.loadingData++
+      TableService.dataCount(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id, (this.versionISO || this.lastReload.toISOString()))
+        .then((count) => {
+          this.total = count
+        })
+        .finally(() => {
+          this.loadingData--
+        })
     },
     simulateProgress () {
       if (this.loadProgress !== 0) {
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue
index 55930ae93e6856d48598213f02e4ded25c69ba24..9d95d70e3df1c8fdf6bbdd1d11e97379f4cd8ae8 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue
@@ -103,7 +103,9 @@
   </div>
 </template>
 <script>
-import { formatTimestampUTCLabel, formatUser } from '@/utils'
+import { formatTimestampUTCLabel } from '@/utils'
+import DatabaseService from '@/api/database.service'
+import UserMapper from '@/api/user.mapper'
 
 export default {
   data () {
@@ -163,7 +165,7 @@ export default {
       if (!this.view) {
         return null
       }
-      return formatUser(this.view.creator)
+      return UserMapper.userToFullName(this.view.creator)
     }
   },
   mounted () {
@@ -171,20 +173,15 @@ export default {
     this.loadResult(this.$route.params.view_id)
   },
   methods: {
-    async loadView () {
+    loadView () {
       this.loadingView = true
-      try {
-        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/view/${this.$route.params.view_id}`, this.config)
-        console.debug('view', res.data)
-        this.view = res.data
-      } catch (err) {
-        if (err.response.status !== 401 && err.response.status !== 405) {
-          console.error('Could not load view', err)
-          this.$toast.error('Could not load view')
-        }
-        this.error = true
-      }
-      this.loadingView = false
+      DatabaseService.findView(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.view_id)
+        .then((view) => {
+          this.view = view
+        })
+        .then(() => {
+          this.loadingView = false
+        })
     },
     loadResult (viewId) {
       if (!viewId) {
diff --git a/dbrepo-ui/pages/login.vue b/dbrepo-ui/pages/login.vue
index d7d26e64a919a0c98d63db1a0973502f903eca92..23944a38039347ecd81d7cb6834750c4785c6a7c 100644
--- a/dbrepo-ui/pages/login.vue
+++ b/dbrepo-ui/pages/login.vue
@@ -1,12 +1,12 @@
 <template>
   <div>
-    <v-toolbar flat>
+    <v-toolbar v-if="!token" flat>
       <v-toolbar-title>
         Login
       </v-toolbar-title>
     </v-toolbar>
-    <v-form ref="form" v-model="valid" @submit.prevent="submit">
-      <v-card v-if="!token" flat tile>
+    <v-form v-if="!token" ref="form" v-model="valid" @submit.prevent="submit">
+      <v-card flat tile>
         <v-card-text>
           <v-alert
             border="left"
@@ -53,7 +53,6 @@
         </v-card-actions>
       </v-card>
     </v-form>
-    <p v-if="token">Already logged-in</p>
   </div>
 </template>
 
@@ -72,9 +71,6 @@ export default {
     }
   },
   computed: {
-    loadingColor () {
-      return this.error ? 'red lighten-2' : 'primary'
-    },
     token () {
       return this.$store.state.token
     },
@@ -83,17 +79,11 @@ export default {
     },
     user () {
       return this.$store.state.user
-    },
-    clientSecret () {
-      return this.$config.clientSecret
-    },
-    config () {
-      if (this.token === null) {
-        return {}
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` }
-      }
+    }
+  },
+  mounted () {
+    if (this.token) {
+      this.$router.push('/container')
     }
   },
   methods: {
@@ -106,10 +96,10 @@ export default {
         .then(() => {
           const userId = UserMapper.tokenToUserId(this.token)
           UserService.findOne(userId)
-            .then((user) => {
+            .then(async (user) => {
               this.$store.commit('SET_USER', user)
               this.$vuetify.theme.dark = user.attributes.theme_dark
-              this.$router.push('/container')
+              await this.$router.push('/container')
             })
         })
         .catch(() => {
@@ -118,9 +108,6 @@ export default {
     },
     signup () {
       this.$router.push('/signup')
-    },
-    forgot () {
-      this.$router.push('/forgot')
     }
   }
 }
diff --git a/dbrepo-ui/pages/search/index.vue b/dbrepo-ui/pages/search/index.vue
index d13f084ad5eaee140ee3a3503b7eb9c66aa46757..b812900945a5d81897cddc251086bebdc311db9c 100644
--- a/dbrepo-ui/pages/search/index.vue
+++ b/dbrepo-ui/pages/search/index.vue
@@ -33,6 +33,7 @@
 </template>
 
 <script>
+import SearchService from '@/api/search.service'
 export default {
   data () {
     return {
@@ -58,14 +59,6 @@ export default {
         return `${this.results.length} results`
       }
       return `${this.results.length} result`
-    },
-    elasticConfig () {
-      return {
-        auth: {
-          username: 'elastic',
-          password: this.$config.elasticPassword
-        }
-      }
     }
   },
   watch: {
@@ -90,20 +83,18 @@ export default {
     }
   },
   methods: {
-    async retrieve () {
+    retrieve () {
       if (this.loading) {
         return
       }
       this.loading = true
-      try {
-        const res = await this.$axios.get(`/retrieve/_all/_search?q=${this.query}*&terminate_after=50`, this.elasticConfig)
-        console.info('search results', res.data.hits.total.value)
-        console.debug('search results for', this.$route.query.q, 'are', res.data.hits.hits)
-        this.results = res.data.hits.hits.map(h => h._source)
-      } catch (err) {
-        console.error('Failed to load search results', err)
-      }
-      this.loading = false
+      SearchService.search(this.query)
+        .then((hits) => {
+          this.results = hits.map(h => h._source)
+        })
+        .finally(() => {
+          this.loading = false
+        })
     },
     isDatabase (item) {
       if (!item) {
diff --git a/dbrepo-ui/plugins/axios.js b/dbrepo-ui/plugins/axios.js
index bdfd8d1aca860e614a2d7b5fa5504104451a990b..8f11563b46fabf9ae276746ad1d729ff7b2860df 100644
--- a/dbrepo-ui/plugins/axios.js
+++ b/dbrepo-ui/plugins/axios.js
@@ -26,7 +26,7 @@ api.interceptors.request.use((config) => {
         return config
       })
   }
-  console.debug('interceptor inject authorization header')
+  console.debug('interceptor inject authorization header for url', config.url)
   config.headers.Authorization = `Bearer ${token}`
   return config
 })
diff --git a/dbrepo-ui/utils/index.js b/dbrepo-ui/utils/index.js
index cc1ee56b61df97eb94ddaea8f6f577f7634fee6f..01ed03767c643e6b57c5bf64c5c0c62710c3781e 100644
--- a/dbrepo-ui/utils/index.js
+++ b/dbrepo-ui/utils/index.js
@@ -83,6 +83,22 @@ function formatTimestampUTC (str) {
   return format(new Date(date), 'yyyy-MM-dd HH:mm:ss')
 }
 
+function isOrcid (orcid) {
+  if (!orcid || orcid.startsWith('http')) {
+    return false
+  }
+  const input = orcid.replace('-', '')
+  let total = 0
+  for (let i = 0; i < input.length; i++) {
+    const digit = Number(input.charAt(i))
+    total = (total + digit) * 2
+  }
+  const remainder = total % 11
+  const result = (12 - remainder) % 11
+  const checksum = result === 10 ? 'X' : String(result)
+  return orcid.charAt(orcid.length - 1) === checksum
+}
+
 module.exports = {
   notEmpty,
   formatTimestamp,
@@ -92,5 +108,6 @@ module.exports = {
   isNonNegativeInteger,
   formatYearUTC,
   formatMonthUTC,
-  formatDayUTC
+  formatDayUTC,
+  isOrcid
 }
diff --git a/docker-compose.yml b/docker-compose.yml
index 6f1c13cbb56724956cc7ee0fb78a66df7195fb6f..f4364d1bb4c3212510867011ef3f6a753608fb4f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -213,8 +213,6 @@ services:
       - "9096:9096"
     env_file:
       - .env
-    environment:
-      - SPRING_PROFILES_ACTIVE=doi
     depends_on:
       dbrepo-query-service:
         condition: service_healthy