diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
index 334128637ec5113e6478567d30e44b2f61ea3f5b..87a4d32532c586c0f2517862f0cd3cc104f4f054 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
@@ -1,14 +1,50 @@
 package at.tuwien.endpoints;
 
+import at.tuwien.api.user.UserDetailsDto;
 import org.apache.spark.sql.Dataset;
 import org.apache.spark.sql.Row;
+import org.springframework.security.core.Authentication;
 
+import java.security.Principal;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 public abstract class AbstractEndpoint {
 
+    public boolean hasRole(Principal principal, String role) {
+        if (principal == null || role == null) {
+            return false;
+        }
+        final Authentication authentication = (Authentication) principal;
+        return authentication.getAuthorities()
+                .stream()
+                .anyMatch(a -> a.getAuthority().equals(role));
+    }
+
+    public boolean isSystem(Principal principal) {
+        if (principal == null) {
+            return false;
+        }
+        final Authentication authentication = (Authentication) principal;
+        return authentication.getAuthorities()
+                .stream()
+                .anyMatch(a -> a.getAuthority().equals("system"));
+    }
+
+    public UUID getId(Principal principal) {
+        if (principal == null) {
+            return null;
+        }
+        final Authentication authentication = (Authentication) principal;
+        final UserDetailsDto user = (UserDetailsDto) authentication.getPrincipal();
+        if (user.getId() == null) {
+            return null;
+        }
+        return UUID.fromString(user.getId());
+    }
+
     public List<Map<String, Object>> transform(Dataset<Row> dataset) {
         return dataset.collectAsList()
                 .stream()
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
index 64d69a9013647c3bd886a690172994fcc382de95..5ca740f1cd32bae2a362cee03a81b7e9be9d6fba 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
@@ -29,7 +29,7 @@ import java.util.UUID;
 @RestController
 @CrossOrigin(origins = "*")
 @RequestMapping(path = "/api/database/{databaseId}/access")
-public class AccessEndpoint {
+public class AccessEndpoint extends AbstractEndpoint {
 
     private final AccessService accessService;
     private final CredentialService credentialService;
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
index 0043b64dfe742fa97ea8a9bdbef671b5cc6118c1..d101a8c97393a41ecf4e76ea4e4fb4aa3c1f4993 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
@@ -35,7 +35,7 @@ import java.sql.SQLException;
 @RestController
 @CrossOrigin(origins = "*")
 @RequestMapping(path = "/api/database")
-public class DatabaseEndpoint {
+public class DatabaseEndpoint extends AbstractEndpoint {
 
     private final SubsetService queryService;
     private final AccessService accessService;
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
index 4f1b5d59c94cee09b4666a8171367cd9d737ea27..8194ca624cde4b55f407e01ed716b899eb6c69b7 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
@@ -9,9 +9,9 @@ import at.tuwien.api.database.query.QueryDto;
 import at.tuwien.api.database.query.QueryPersistDto;
 import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.exception.*;
+import at.tuwien.gateway.MetadataServiceGateway;
 import at.tuwien.mapper.MetadataMapper;
 import at.tuwien.service.*;
-import at.tuwien.utils.UserUtil;
 import at.tuwien.validation.EndpointValidator;
 import io.micrometer.observation.annotation.Observed;
 import io.swagger.v3.oas.annotations.Operation;
@@ -56,11 +56,13 @@ public class SubsetEndpoint extends AbstractEndpoint {
     private final StorageService storageService;
     private final CredentialService credentialService;
     private final EndpointValidator endpointValidator;
+    private final MetadataServiceGateway metadataServiceGateway;
 
     @Autowired
     public SubsetEndpoint(SchemaService schemaService, SubsetService subsetService, MetadataMapper metadataMapper,
                           MetricsService metricsService, StorageService storageService,
-                          CredentialService credentialService, EndpointValidator endpointValidator) {
+                          CredentialService credentialService, EndpointValidator endpointValidator,
+                          MetadataServiceGateway metadataServiceGateway) {
         this.schemaService = schemaService;
         this.subsetService = subsetService;
         this.metadataMapper = metadataMapper;
@@ -68,6 +70,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
         this.storageService = storageService;
         this.credentialService = credentialService;
         this.endpointValidator = endpointValidator;
+        this.metadataServiceGateway = metadataServiceGateway;
     }
 
     @GetMapping
@@ -98,11 +101,19 @@ public class SubsetEndpoint extends AbstractEndpoint {
                             schema = @Schema(implementation = ApiErrorDto.class))}),
     })
     public ResponseEntity<List<QueryDto>> list(@NotNull @PathVariable("databaseId") Long databaseId,
-                                               @RequestParam(name = "persisted", required = false) Boolean filterPersisted)
+                                               @RequestParam(name = "persisted", required = false) Boolean filterPersisted,
+                                               Principal principal)
             throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException,
             QueryNotFoundException, NotAllowedException, MetadataServiceException {
         log.debug("endpoint find subsets in database, databaseId={}, filterPersisted={}", databaseId, filterPersisted);
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
+        if (!database.getIsPublic() || !database.getIsSchemaPublic()) {
+            if (principal == null) {
+                log.error("Failed to find subset: database is private & missing authentication");
+                throw new NotAllowedException("Failed to find subset: database is private & missing authentication");
+            }
+            metadataServiceGateway.getAccess(databaseId, getId(principal));
+        }
         final List<QueryDto> queries;
         try {
             queries = subsetService.findAll(database, filterPersisted);
@@ -154,15 +165,23 @@ public class SubsetEndpoint extends AbstractEndpoint {
     })
     public ResponseEntity<?> findById(@NotNull @PathVariable("databaseId") Long databaseId,
                                       @NotNull @PathVariable("subsetId") Long subsetId,
-                                      @NotNull HttpServletRequest httpServletRequest,
-                                      @RequestParam(required = false) Instant timestamp)
+                                      @NotNull @RequestHeader("Accept") String accept,
+                                      @RequestParam(required = false) Instant timestamp,
+                                      Principal principal)
             throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException,
             QueryNotFoundException, FormatNotAvailableException, StorageUnavailableException, UserNotFoundException,
-            MetadataServiceException, TableNotFoundException, ViewMalformedException, QueryMalformedException {
-        String accept = httpServletRequest.getHeader("Accept");
+            MetadataServiceException, TableNotFoundException, ViewMalformedException, QueryMalformedException,
+            NotAllowedException {
         log.debug("endpoint find subset in database, databaseId={}, subsetId={}, accept={}, timestamp={}", databaseId,
                 subsetId, accept, timestamp);
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
+        if (!database.getIsPublic() || !database.getIsSchemaPublic()) {
+            if (principal == null) {
+                log.error("Failed to find subset: database is private & missing authentication");
+                throw new NotAllowedException("Failed to find subset: database is private & missing authentication");
+            }
+            metadataServiceGateway.getAccess(databaseId, getId(principal));
+        }
         final QueryDto subset;
         try {
             subset = subsetService.findById(database, subsetId);
@@ -206,7 +225,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
     @PostMapping
     @Observed(name = "dbrepo_subset_create")
     @Operation(summary = "Create subset",
-            description = "Creates a subset in the query store of the data database. Requires role `execute-query` for private databases.",
+            description = "Creates a subset in the query store of the data database. Can also be used without authentication if (and only if) the database is marked as public (i.e. when `is_public` = `is_schema_public` is set to `true`). Otherwise at least read access is required.",
             security = {@SecurityRequirement(name = "basicAuth"), @SecurityRequirement(name = "bearerAuth")})
     @ApiResponses(value = {
             @ApiResponse(responseCode = "201",
@@ -264,7 +283,12 @@ public class SubsetEndpoint extends AbstractEndpoint {
         endpointValidator.validateDataParams(page, size);
         endpointValidator.validateForbiddenStatements(data.getStatement());
         /* parameters */
-        final UUID userId = principal != null ? UserUtil.getId(principal) : null;
+        final UUID userId;
+        if (principal != null) {
+            userId = getId(principal);
+        } else {
+            userId = null;
+        }
         if (page == null) {
             page = 0L;
             log.debug("page not set: default to {}", page);
@@ -279,6 +303,13 @@ public class SubsetEndpoint extends AbstractEndpoint {
         }
         /* create */
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
+        if (!database.getIsPublic() || !database.getIsSchemaPublic()) {
+            if (principal == null) {
+                log.error("Failed to find subset: database is private & missing authentication");
+                throw new NotAllowedException("Failed to find subset: database is private & missing authentication");
+            }
+            metadataServiceGateway.getAccess(databaseId, getId(principal));
+        }
         try {
             final Long subsetId = subsetService.create(database, data.getStatement(), timestamp, userId);
             return getData(databaseId, subsetId, principal, request, page, size);
@@ -342,7 +373,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
                 log.error("Failed to re-execute query: no authentication found");
                 throw new NotAllowedException("Failed to re-execute query: no authentication found");
             }
-            credentialService.getAccess(databaseId, UserUtil.getId(principal));
+            credentialService.getAccess(databaseId, getId(principal));
         }
         /* parameters */
         if (page == null) {
@@ -426,7 +457,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
         log.debug("endpoint persist query, databaseId={}, queryId={}, data.persist={}, principal.name={}", databaseId,
                 queryId, data.getPersist(), principal.getName());
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
-        credentialService.getAccess(databaseId, UserUtil.getId(principal));
+        credentialService.getAccess(databaseId, getId(principal));
         try {
             subsetService.persist(database, queryId, data.getPersist());
             final QueryDto dto = subsetService.findById(database, queryId);
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
index 11720b49063e9ba0c1dbc1b1f844aa215a6b3b4d..f0ec00a035e729157726b65af9f3c85949581c4e 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
@@ -13,7 +13,6 @@ import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.exception.*;
 import at.tuwien.gateway.MetadataServiceGateway;
 import at.tuwien.service.*;
-import at.tuwien.utils.UserUtil;
 import at.tuwien.validation.EndpointValidator;
 import io.micrometer.observation.annotation.Observed;
 import io.swagger.v3.oas.annotations.Operation;
@@ -277,7 +276,7 @@ public class TableEndpoint extends AbstractEndpoint {
                 log.error("Failed find table data: authentication required");
                 throw new NotAllowedException("Failed to find table data: authentication required");
             }
-            credentialService.getAccess(databaseId, UserUtil.getId(principal));
+            credentialService.getAccess(databaseId, getId(principal));
         }
         try {
             final HttpHeaders headers = new HttpHeaders();
@@ -335,17 +334,18 @@ public class TableEndpoint extends AbstractEndpoint {
     public ResponseEntity<Void> insertRawTuple(@NotNull @PathVariable("databaseId") Long databaseId,
                                                @NotNull @PathVariable("tableId") Long tableId,
                                                @Valid @RequestBody TupleDto data,
-                                               @NotNull Principal principal)
+                                               @NotNull Principal principal,
+                                               @RequestHeader("Authorization") String authorization)
             throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException,
             TableMalformedException, QueryMalformedException, NotAllowedException, StorageUnavailableException,
             StorageNotFoundException, MetadataServiceException {
         log.debug("endpoint insert raw table data, databaseId={}, tableId={}", databaseId, tableId);
         final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId);
-        final DatabaseAccessDto access = credentialService.getAccess(databaseId, UserUtil.getId(principal));
-        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), UserUtil.getId(principal));
+        final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal));
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal));
         try {
             tableService.createTuple(table, data);
-            metadataServiceGateway.updateTableStatistics(databaseId, tableId);
+            metadataServiceGateway.updateTableStatistics(databaseId, tableId, authorization);
             return ResponseEntity.status(HttpStatus.CREATED)
                     .build();
         } catch (SQLException e) {
@@ -387,17 +387,18 @@ public class TableEndpoint extends AbstractEndpoint {
     public ResponseEntity<Void> updateRawTuple(@NotNull @PathVariable("databaseId") Long databaseId,
                                                @NotNull @PathVariable("tableId") Long tableId,
                                                @Valid @RequestBody TupleUpdateDto data,
-                                               @NotNull Principal principal)
+                                               @NotNull Principal principal,
+                                               @RequestHeader("Authorization") String authorization)
             throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException,
             TableMalformedException, QueryMalformedException, NotAllowedException, MetadataServiceException {
         log.debug("endpoint update raw table data, databaseId={}, tableId={}, data.keys={}", databaseId, tableId,
                 data.getKeys());
         final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId);
-        final DatabaseAccessDto access = credentialService.getAccess(databaseId, UserUtil.getId(principal));
-        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), UserUtil.getId(principal));
+        final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal));
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal));
         try {
             tableService.updateTuple(table, data);
-            metadataServiceGateway.updateTableStatistics(databaseId, tableId);
+            metadataServiceGateway.updateTableStatistics(databaseId, tableId, authorization);
             return ResponseEntity.status(HttpStatus.ACCEPTED)
                     .build();
         } catch (SQLException e) {
@@ -439,17 +440,18 @@ public class TableEndpoint extends AbstractEndpoint {
     public ResponseEntity<Void> deleteRawTuple(@NotNull @PathVariable("databaseId") Long databaseId,
                                                @NotNull @PathVariable("tableId") Long tableId,
                                                @Valid @RequestBody TupleDeleteDto data,
-                                               @NotNull Principal principal)
+                                               @NotNull Principal principal,
+                                               @RequestHeader("Authorization") String authorization)
             throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException,
             TableMalformedException, QueryMalformedException, NotAllowedException, MetadataServiceException {
         log.debug("endpoint delete raw table data, databaseId={}, tableId={}, data.keys={}", databaseId, tableId,
                 data.getKeys());
         final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId);
-        final DatabaseAccessDto access = credentialService.getAccess(databaseId, UserUtil.getId(principal));
-        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), UserUtil.getId(principal));
+        final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal));
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal));
         try {
             tableService.deleteTuple(table, data);
-            metadataServiceGateway.updateTableStatistics(databaseId, tableId);
+            metadataServiceGateway.updateTableStatistics(databaseId, tableId, authorization);
             return ResponseEntity.status(HttpStatus.ACCEPTED)
                     .build();
         } catch (SQLException e) {
@@ -510,7 +512,7 @@ public class TableEndpoint extends AbstractEndpoint {
                 log.error("Failed to find table history: no authentication found");
                 throw new NotAllowedException("Failed to find table history: no authentication found");
             }
-            credentialService.getAccess(databaseId, UserUtil.getId(principal));
+            credentialService.getAccess(databaseId, getId(principal));
         }
         try {
             final List<TableHistoryDto> dto = tableService.history(table, size);
@@ -622,7 +624,7 @@ public class TableEndpoint extends AbstractEndpoint {
                 log.error("Failed to export private table: principal is null");
                 throw new NotAllowedException("Failed to export private table: principal is null");
             }
-            credentialService.getAccess(databaseId, UserUtil.getId(principal));
+            credentialService.getAccess(databaseId, getId(principal));
         }
         final Dataset<Row> dataset = tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, null,
                 null, null, null);
@@ -669,14 +671,15 @@ public class TableEndpoint extends AbstractEndpoint {
     public ResponseEntity<Void> importDataset(@NotNull @PathVariable("databaseId") Long databaseId,
                                               @NotNull @PathVariable("tableId") Long tableId,
                                               @Valid @RequestBody ImportDto data,
-                                              @NotNull Principal principal)
+                                              @NotNull Principal principal,
+                                              @RequestHeader("Authorization") String authorization)
             throws RemoteUnavailableException, TableNotFoundException, NotAllowedException, MetadataServiceException,
             StorageNotFoundException, MalformedException, StorageUnavailableException, QueryMalformedException,
             DatabaseUnavailableException {
         log.debug("endpoint insert table data, databaseId={}, tableId={}, data.location={}", databaseId, tableId, data.getLocation());
         final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId);
-        final DatabaseAccessDto access = credentialService.getAccess(databaseId, UserUtil.getId(principal));
-        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), UserUtil.getId(principal));
+        final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal));
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal));
         if (data.getLineTermination() == null) {
             data.setLineTermination("\\r\\n");
             log.debug("line termination not present, default to {}", data.getLineTermination());
@@ -687,7 +690,7 @@ public class TableEndpoint extends AbstractEndpoint {
             log.error("Failed to establish connection to database: {}", e.getMessage());
             throw new DatabaseUnavailableException("Failed to establish connection to database", e);
         }
-        metadataServiceGateway.updateTableStatistics(databaseId, tableId);
+        metadataServiceGateway.updateTableStatistics(databaseId, tableId, authorization);
         return ResponseEntity.accepted()
                 .build();
     }
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java
index c8da4239861264539b2140c64fde2b95a8fb8bc4..2b0463483d66429dfb0afd942bbc80ccdfb2fd26 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java
@@ -9,7 +9,6 @@ import at.tuwien.api.database.internal.PrivilegedViewDto;
 import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.exception.*;
 import at.tuwien.service.*;
-import at.tuwien.utils.UserUtil;
 import at.tuwien.validation.EndpointValidator;
 import io.micrometer.observation.annotation.Observed;
 import io.swagger.v3.oas.annotations.Operation;
@@ -275,7 +274,7 @@ public class ViewEndpoint extends AbstractEndpoint {
                 log.error("Failed to get data from view: unauthorized");
                 throw new NotAllowedException("Failed to get data from view: unauthorized");
             }
-            credentialService.getAccess(databaseId, UserUtil.getId(principal));
+            credentialService.getAccess(databaseId, getId(principal));
         }
         try {
             final HttpHeaders headers = new HttpHeaders();
@@ -351,7 +350,7 @@ public class ViewEndpoint extends AbstractEndpoint {
                 log.error("Failed to export private view: principal is null");
                 throw new NotAllowedException("Failed to export private view: principal is null");
             }
-            credentialService.getAccess(databaseId, UserUtil.getId(principal));
+            credentialService.getAccess(databaseId, getId(principal));
         }
         final Dataset<Row> dataset = tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, null,
                 null, null, null);
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/utils/UserUtil.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/utils/UserUtil.java
deleted file mode 100644
index 7a99e839edd3b97758e713260f798ae5357c53c6..0000000000000000000000000000000000000000
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/utils/UserUtil.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package at.tuwien.utils;
-
-import at.tuwien.api.user.UserDetailsDto;
-import org.springframework.security.core.Authentication;
-
-import java.security.Principal;
-import java.util.UUID;
-
-public class UserUtil {
-
-    public static boolean hasRole(Principal principal, String role) {
-        if (principal == null || role == null) {
-            return false;
-        }
-        final Authentication authentication = (Authentication) principal;
-        return authentication.getAuthorities()
-                .stream()
-                .anyMatch(a -> a.getAuthority().equals(role));
-    }
-
-    public static UUID getId(Principal principal) {
-        if (principal == null) {
-            return null;
-        }
-        final Authentication authentication = (Authentication) principal;
-        final UserDetailsDto user = (UserDetailsDto) authentication.getPrincipal();
-        if (user.getId() == null) {
-            return null;
-        }
-        return UUID.fromString(user.getId());
-    }
-
-}
diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java
index e7de4a04b3b48d089d254d4da2d442ba4a5334f7..1d5e35a41d78b4b812c792b176c447dd00f10da6 100644
--- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java
+++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java
@@ -23,13 +23,13 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.mock.web.MockHttpServletRequest;
 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 java.security.Principal;
 import java.sql.SQLException;
 import java.time.Instant;
 import java.util.List;
@@ -82,7 +82,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(List.of(QUERY_1_DTO, QUERY_2_DTO, QUERY_3_DTO, QUERY_4_DTO, QUERY_5_DTO, QUERY_6_DTO));
 
         /* test */
-        final List<QueryDto> response = generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO);
+        final List<QueryDto> response = generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO, null);
         assertEquals(6, response.size());
     }
 
@@ -92,7 +92,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseNotFoundException.class, () -> {
-            generic_list(null, null);
+            generic_list(null, null, null);
         });
     }
 
@@ -110,7 +110,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO);
+            generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO, null);
         });
     }
 
@@ -119,7 +119,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
     public void findById_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException,
             DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotFoundException,
             FormatNotAvailableException, TableNotFoundException, MetadataServiceException, SQLException,
-            ViewMalformedException {
+            ViewMalformedException, NotAllowedException {
 
         /* mock */
         when(credentialService.getDatabase(DATABASE_3_ID))
@@ -128,7 +128,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(QUERY_5_DTO);
 
         /* test */
-        generic_findById(QUERY_5_ID, MediaType.APPLICATION_JSON, null);
+        generic_findById(QUERY_5_ID, "application/json", null, null);
     }
 
     @Test
@@ -144,7 +144,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(FormatNotAvailableException.class, () -> {
-            generic_findById(QUERY_5_ID, MediaType.APPLICATION_PDF, null);
+            generic_findById(QUERY_5_ID, "application/pdf", null, null);
         });
     }
 
@@ -153,7 +153,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
     public void findById_acceptCsv_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException,
             UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException,
             QueryNotFoundException, FormatNotAvailableException, SQLException, MetadataServiceException,
-            TableNotFoundException, ViewMalformedException {
+            TableNotFoundException, ViewMalformedException, NotAllowedException {
         final Dataset<Row> mock = sparkSession.emptyDataFrame();
 
         /* mock */
@@ -167,7 +167,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(mock);
 
         /* test */
-        generic_findById(QUERY_5_ID, MediaType.parseMediaType("text/csv"), null);
+        generic_findById(QUERY_5_ID,"text/csv", null, null);
     }
 
     @Test
@@ -175,7 +175,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
     public void findById_timestamp_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException,
             UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException,
             QueryNotFoundException, FormatNotAvailableException, SQLException, MetadataServiceException,
-            TableNotFoundException, ViewMalformedException {
+            TableNotFoundException, ViewMalformedException, NotAllowedException {
         final Dataset<Row> mock = sparkSession.emptyDataFrame();
 
         /* mock */
@@ -189,7 +189,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(EXPORT_RESOURCE_DTO);
 
         /* test */
-        generic_findById(QUERY_5_ID, MediaType.parseMediaType("text/csv"), Instant.now());
+        generic_findById(QUERY_5_ID, "text/csv", Instant.now(), null);
     }
 
     @Test
@@ -203,7 +203,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseNotFoundException.class, () -> {
-            generic_findById(QUERY_5_ID, MediaType.APPLICATION_JSON, null);
+            generic_findById(QUERY_5_ID, "application/json", null, null);
         });
     }
 
@@ -221,7 +221,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            generic_findById(QUERY_5_ID, MediaType.APPLICATION_JSON, null);
+            generic_findById(QUERY_5_ID, "application/json", null, null);
         });
     }
 
@@ -244,7 +244,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            generic_findById(QUERY_5_ID, MediaType.parseMediaType("text/csv"), null);
+            generic_findById(QUERY_5_ID, "text/csv", null, null);
         });
     }
 
@@ -687,9 +687,9 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
         });
     }
 
-    protected List<QueryDto> generic_list(Long databaseId, PrivilegedDatabaseDto database) throws NotAllowedException,
-            DatabaseUnavailableException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException,
-            MetadataServiceException {
+    protected List<QueryDto> generic_list(Long databaseId, PrivilegedDatabaseDto database, Principal principal)
+            throws NotAllowedException, DatabaseUnavailableException, QueryNotFoundException, DatabaseNotFoundException,
+            RemoteUnavailableException, MetadataServiceException {
 
         /* mock */
         if (database != null) {
@@ -702,22 +702,19 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest {
         }
 
         /* test */
-        final ResponseEntity<List<QueryDto>> response = subsetEndpoint.list(databaseId, null);
+        final ResponseEntity<List<QueryDto>> response = subsetEndpoint.list(databaseId, null, principal);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         return response.getBody();
     }
 
-    protected void generic_findById(Long subsetId, MediaType accept, Instant timestamp) throws UserNotFoundException,
-            DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotFoundException,
-            DatabaseNotFoundException, RemoteUnavailableException, FormatNotAvailableException,
-            MetadataServiceException, TableNotFoundException, ViewMalformedException, SQLException {
-
-        /* mock */
-        when(mockHttpServletRequest.getHeader("Accept"))
-                .thenReturn(accept.toString());
+    protected void generic_findById(Long subsetId, String accept, Instant timestamp, Principal principal)
+            throws UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException,
+            NotAllowedException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException,
+            RemoteUnavailableException, FormatNotAvailableException, MetadataServiceException, TableNotFoundException,
+            ViewMalformedException {
 
         /* test */
-        final ResponseEntity<?> response = subsetEndpoint.findById(DATABASE_3_ID, subsetId, mockHttpServletRequest, timestamp);
+        final ResponseEntity<?> response = subsetEndpoint.findById(DATABASE_3_ID, subsetId, accept, timestamp, principal);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         assertNotNull(response.getBody());
     }
diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java
index ed720ac930565c0c1f97b291058d4aa293ba0061..e3171892a0be41f816feb749990a70aafc252f3d 100644
--- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java
+++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java
@@ -165,9 +165,8 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void statistic_succeeds() throws DatabaseUnavailableException, TableNotFoundException,
-            TableMalformedException, DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException,
-            SQLException {
+    public void statistic_succeeds() throws DatabaseUnavailableException, TableNotFoundException, SQLException,
+            TableMalformedException, RemoteUnavailableException, MetadataServiceException {
 
         /* mock */
         when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID))
@@ -454,10 +453,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .createTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.CREATED, response.getStatusCode());
     }
 
@@ -473,7 +472,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
-            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -495,7 +494,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(TableNotFoundException.class, () -> {
-            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -518,7 +517,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -545,7 +544,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -568,7 +567,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO);
 
         /* test */
-        tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -590,7 +589,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -613,7 +612,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO);
 
         /* test */
-        tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+        tableEndpoint.insertRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -641,10 +640,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .updateTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -663,7 +662,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
-            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -688,7 +687,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(TableNotFoundException.class, () -> {
-            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -714,7 +713,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -743,7 +742,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -772,10 +771,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .updateTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -801,7 +800,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -830,10 +829,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .updateTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.updateRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -858,10 +857,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .deleteTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -876,7 +875,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
-            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -897,7 +896,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(TableNotFoundException.class, () -> {
-            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -919,7 +918,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -944,7 +943,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -969,10 +968,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .deleteTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -994,7 +993,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1019,10 +1018,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .deleteTuple(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.deleteRawTuple(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -1269,10 +1268,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .importDataset(TABLE_8_PRIVILEGED_DTO, request);
         doNothing()
                 .when(metadataServiceGateway)
-                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID);
+                .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN);
 
         /* test */
-        final ResponseEntity<Void> response = tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        final ResponseEntity<Void> response = tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
     }
 
@@ -1287,7 +1286,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_4_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_4_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1308,7 +1307,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(TableNotFoundException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1334,7 +1333,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(DatabaseUnavailableException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1360,7 +1359,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1382,7 +1381,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1404,7 +1403,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO);
 
         /* test */
-        tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -1425,7 +1424,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_3_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1447,7 +1446,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO);
 
         /* test */
-        tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL);
+        tableEndpoint.importDataset(DATABASE_3_ID, TABLE_8_ID, request, USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -1468,7 +1467,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(DATABASE_1_USER_2_WRITE_ALL_ACCESS_DTO);
 
         /* test */
-        tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, request, USER_2_PRINCIPAL);
+        tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, request, USER_2_PRINCIPAL, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -1489,7 +1488,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(DATABASE_1_USER_2_WRITE_OWN_ACCESS_DTO);
 
         /* test */
-        tableEndpoint.importDataset(DATABASE_1_ID, TABLE_2_ID, request, USER_2_PRINCIPAL);
+        tableEndpoint.importDataset(DATABASE_1_ID, TABLE_2_ID, request, USER_2_PRINCIPAL, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -1510,7 +1509,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, request, USER_2_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, request, USER_2_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -1532,7 +1531,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            tableEndpoint.importDataset(DATABASE_1_ID, TABLE_2_ID, request, USER_2_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_1_ID, TABLE_2_ID, request, USER_2_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         });
     }
 
diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java
index 720e0652b3ee7f9dc0f30eb9ec014efe76275009..f1d8e0f9fc3e19aea2e4f435c0051e629b1a892a 100644
--- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java
+++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java
@@ -817,7 +817,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest {
                         .build());
 
         /* test */
-        metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID);
+        metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID, TOKEN_ACCESS_TOKEN);
     }
 
     @Test
@@ -830,7 +830,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(RemoteUnavailableException.class, () -> {
-            metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID);
+            metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -844,7 +844,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(TableNotFoundException.class, () -> {
-            metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID);
+            metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID, TOKEN_ACCESS_TOKEN);
         });
     }
 
@@ -858,7 +858,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest {
 
         /* test */
         assertThrows(MetadataServiceException.class, () -> {
-            metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID);
+            metadataServiceGateway.updateTableStatistics(DATABASE_1_ID, TABLE_1_ID, TOKEN_ACCESS_TOKEN);
         });
     }
 
diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java
index 01bce16b08f84e7ad3901beb143ef0737d9ad571..e48f0c048da2dd6c83934566e6e5d9956838ba62 100644
--- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java
+++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java
@@ -27,7 +27,6 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.TestConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
-import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.web.servlet.MockMvc;
@@ -131,7 +130,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest {
 
         /* mock */
         try {
-            subsetEndpoint.list(DATABASE_1_ID, null);
+            subsetEndpoint.list(DATABASE_1_ID, null, USER_1_PRINCIPAL);
         } catch (Exception e) {
             /* ignore */
         }
@@ -151,7 +150,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest {
             /* ignore */
         }
         try {
-            subsetEndpoint.findById(DATABASE_1_ID, QUERY_1_ID, new MockHttpServletRequest(), null);
+            subsetEndpoint.findById(DATABASE_1_ID, QUERY_1_ID, "application/json", null, USER_1_PRINCIPAL);
         } catch (Exception e) {
             /* ignore */
         }
@@ -176,17 +175,17 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest {
             /* ignore */
         }
         try {
-            tableEndpoint.insertRawTuple(DATABASE_1_ID, TABLE_1_ID, TupleDto.builder().build(), USER_1_PRINCIPAL);
+            tableEndpoint.insertRawTuple(DATABASE_1_ID, TABLE_1_ID, TupleDto.builder().build(), USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         } catch (Exception e) {
             /* ignore */
         }
         try {
-            tableEndpoint.updateRawTuple(DATABASE_1_ID, TABLE_1_ID, TupleUpdateDto.builder().build(), USER_1_PRINCIPAL);
+            tableEndpoint.updateRawTuple(DATABASE_1_ID, TABLE_1_ID, TupleUpdateDto.builder().build(), USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         } catch (Exception e) {
             /* ignore */
         }
         try {
-            tableEndpoint.deleteRawTuple(DATABASE_1_ID, TABLE_1_ID, TupleDeleteDto.builder().build(), USER_1_PRINCIPAL);
+            tableEndpoint.deleteRawTuple(DATABASE_1_ID, TABLE_1_ID, TupleDeleteDto.builder().build(), USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         } catch (Exception e) {
             /* ignore */
         }
@@ -201,7 +200,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest {
             /* ignore */
         }
         try {
-            tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, ImportDto.builder().build(), USER_1_PRINCIPAL);
+            tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, ImportDto.builder().build(), USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN);
         } catch (Exception e) {
             /* ignore */
         }
diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/utils/UserUtilTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/utils/UserUtilTest.java
deleted file mode 100644
index 13ddfce8d3c171b79096d2e0d1d05948848a8c86..0000000000000000000000000000000000000000
--- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/utils/UserUtilTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package at.tuwien.utils;
-
-import at.tuwien.test.BaseTest;
-import org.junit.jupiter.api.Test;
-
-
-import static org.junit.jupiter.api.Assertions.*;
-
-public class UserUtilTest extends BaseTest {
-
-    @Test
-    public void constructor_succeeds() {
-
-        /* test */
-        new UserUtil();
-    }
-
-    @Test
-    public void hasRole_succeeds() {
-        assertTrue(UserUtil.hasRole(USER_1_PRINCIPAL, "find-container"));
-    }
-
-    @Test
-    public void hasRole_principalMissing_fails() {
-        assertFalse(UserUtil.hasRole(null, "find-container"));
-    }
-
-    @Test
-    public void hasRole_roleMissing_fails() {
-        assertFalse(UserUtil.hasRole(USER_1_PRINCIPAL, null));
-    }
-
-    @Test
-    public void getId_succeeds() {
-        assertEquals(USER_1_ID, UserUtil.getId(USER_1_PRINCIPAL));
-    }
-
-    @Test
-    public void getId_principalMissing_fails() {
-        assertNull(UserUtil.getId(null));
-    }
-
-    @Test
-    public void getId_roleMissing_fails() {
-        assertNull(UserUtil.getId(USER_LOCAL_ADMIN_PRINCIPAL));
-    }
-}
diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java
index 8c86d26bf4d7eb66d496713e2f286fade575aee0..e2851071dad176ca44a9f2d0952a0392101ad2eb 100644
--- a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java
+++ b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java
@@ -118,11 +118,13 @@ public interface MetadataServiceGateway {
     /**
      * Update the table statistics in the metadata service.
      *
-     * @param databaseId The database id.
-     * @param tableId    The table id.
+     * @param databaseId    The database id.
+     * @param tableId       The table id.
+     * @param authorization The authorization header.
      * @throws RemoteUnavailableException The remote service is not available and invalid data was returned.
      * @throws TableNotFoundException     The table was not found.
      * @throws MetadataServiceException   The remote service returned invalid data.
      */
-    void updateTableStatistics(Long databaseId, Long tableId) throws TableNotFoundException, MetadataServiceException, RemoteUnavailableException;
+    void updateTableStatistics(Long databaseId, Long tableId, String authorization) throws TableNotFoundException,
+            MetadataServiceException, RemoteUnavailableException;
 }
diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java
index 8e63bbb7b6aac5eee92a87c3d1221ff07b00c7e5..a39b93ef1d8133e9e27d14eeb6c0875d3b1ce280 100644
--- a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java
+++ b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java
@@ -11,21 +11,20 @@ import at.tuwien.api.database.table.internal.PrivilegedTableDto;
 import at.tuwien.api.identifier.IdentifierBriefDto;
 import at.tuwien.api.user.UserDto;
 import at.tuwien.api.user.internal.PrivilegedUserDto;
+import at.tuwien.config.GatewayConfig;
 import at.tuwien.exception.*;
 import at.tuwien.gateway.MetadataServiceGateway;
 import at.tuwien.mapper.MetadataMapper;
 import jakarta.validation.constraints.NotNull;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.*;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.HttpServerErrorException;
 import org.springframework.web.client.ResourceAccessException;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
 
 import java.time.Instant;
 import java.util.List;
@@ -36,11 +35,14 @@ import java.util.UUID;
 public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
 
     private final RestTemplate restTemplate;
+    private final GatewayConfig gatewayConfig;
     private final MetadataMapper metadataMapper;
 
     @Autowired
-    public MetadataServiceGatewayImpl(RestTemplate restTemplate, MetadataMapper metadataMapper) {
+    public MetadataServiceGatewayImpl(RestTemplate restTemplate, GatewayConfig gatewayConfig,
+                                      MetadataMapper metadataMapper) {
         this.restTemplate = restTemplate;
+        this.gatewayConfig = gatewayConfig;
         this.metadataMapper = metadataMapper;
     }
 
@@ -48,8 +50,10 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
     public PrivilegedContainerDto getContainerById(Long containerId) throws RemoteUnavailableException,
             ContainerNotFoundException, MetadataServiceException {
         final ResponseEntity<ContainerDto> response;
+        final String url = "/api/container/" + containerId;
+        log.debug("get privileged container info from metadata service: {}", url);
         try {
-            response = restTemplate.exchange("/api/container/" + containerId, HttpMethod.GET, HttpEntity.EMPTY,
+            response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY,
                     ContainerDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
             log.error("Failed to find container with id {}: {}", containerId, e.getMessage());
@@ -85,10 +89,9 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
             MetadataServiceException {
         final ResponseEntity<PrivilegedDatabaseDto> response;
         final String url = "/api/database/" + id;
-        log.debug("find privileged database from url: {}", url);
+        log.debug("get privileged database info from metadata service: {}", url);
         try {
-            response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY,
-                    PrivilegedDatabaseDto.class);
+            response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, PrivilegedDatabaseDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
             log.error("Failed to find database with id {}: {}", id, e.getMessage());
             throw new RemoteUnavailableException("Failed to find database: " + e.getMessage(), e);
@@ -125,7 +128,7 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
             RemoteUnavailableException, MetadataServiceException {
         final ResponseEntity<TableDto> response;
         final String url = "/api/database/" + databaseId + "/table/" + id;
-        log.debug("find privileged table from url: {}", url);
+        log.debug("get privileged table info from metadata service: {}", url);
         try {
             response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, TableDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
@@ -167,7 +170,7 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
             ViewNotFoundException, MetadataServiceException {
         final ResponseEntity<ViewDto> response;
         final String url = "/api/database/" + databaseId + "/view/" + id;
-        log.debug("find privileged view from url: {}", url);
+        log.debug("get privileged view info from metadata service: {}", url);
         try {
             response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, ViewDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
@@ -208,8 +211,10 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
     public UserDto getUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException,
             MetadataServiceException {
         final ResponseEntity<UserDto> response;
+        final String url = "/api/user/" + userId;
+        log.debug("get user info from metadata service: {}", url);
         try {
-            response = restTemplate.exchange("/api/user/" + userId, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class);
+            response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
             log.error("Failed to find user with id {}: {}", userId, e.getMessage());
             throw new RemoteUnavailableException("Failed to find user: " + e.getMessage(), e);
@@ -232,8 +237,10 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
     public PrivilegedUserDto getPrivilegedUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException,
             MetadataServiceException {
         final ResponseEntity<UserDto> response;
+        final String url = "/api/user/" + userId;
+        log.debug("get privileged user info from metadata service: {}", url);
         try {
-            response = restTemplate.exchange("/api/user/" + userId, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class);
+            response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
             log.error("Failed to find user with id {}: {}", userId, e.getMessage());
             throw new RemoteUnavailableException("Failed to find user: " + e.getMessage(), e);
@@ -267,8 +274,10 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
     public DatabaseAccessDto getAccess(Long databaseId, UUID userId) throws RemoteUnavailableException,
             NotAllowedException, MetadataServiceException {
         final ResponseEntity<DatabaseAccessDto> response;
+        final String url = "/api/database/" + databaseId + "/access/" + userId;
+        log.debug("get database access from metadata service: {}", url);
         try {
-            response = restTemplate.exchange("/api/database/" + databaseId + "/access/" + userId, HttpMethod.GET, HttpEntity.EMPTY, DatabaseAccessDto.class);
+            response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, DatabaseAccessDto.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
             log.error("Failed to find database access for user with id {}: {}", userId, e.getMessage());
             throw new RemoteUnavailableException("Failed to find database access: " + e.getMessage(), e);
@@ -292,7 +301,7 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
             RemoteUnavailableException, DatabaseNotFoundException {
         final ResponseEntity<IdentifierBriefDto[]> response;
         final String url = "/api/identifier?dbid=" + databaseId + (subsetId != null ? ("&qid=" + subsetId) : "");
-        log.trace("mapped url: {}", url);
+        log.debug("get identifiers from metadata service: {}", url);
         try {
             response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, IdentifierBriefDto[].class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
@@ -314,13 +323,17 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway {
     }
 
     @Override
-    public void updateTableStatistics(Long databaseId, Long tableId) throws TableNotFoundException, MetadataServiceException,
-            RemoteUnavailableException {
+    public void updateTableStatistics(Long databaseId, Long tableId, String authorization) throws TableNotFoundException,
+            MetadataServiceException, RemoteUnavailableException {
         final ResponseEntity<Void> response;
         final String url = "/api/database/" + databaseId + "/table/" + tableId + "/statistic";
-        log.trace("mapped url: {}", url);
+        log.debug("update table statistics in metadata service: {}", url);
+        final RestTemplate restTemplate = new RestTemplate();
+        restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(gatewayConfig.getMetadataEndpoint()));
+        final HttpHeaders headers = new HttpHeaders();
+        headers.set("Authorization", authorization);
         try {
-            response = restTemplate.exchange(url, HttpMethod.PUT, HttpEntity.EMPTY, Void.class);
+            response = restTemplate.exchange(url, HttpMethod.PUT, new HttpEntity<>(null, headers), Void.class);
         } catch (ResourceAccessException | HttpServerErrorException e) {
             log.error("Failed to update table statistic for table with id {}: {}", tableId, e.getMessage());
             throw new RemoteUnavailableException("Failed to update table statistic: " + e.getMessage(), e);
diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java
index e8456c42ef8d0dc6a464a43be64c940faa46c792..2d01129fefc8ca18d9e4d16d9ebbef6b0ad04f59 100644
--- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java
+++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java
@@ -43,6 +43,11 @@ public class DatabaseBriefDto {
     @Schema(example = "true")
     private Boolean isPublic;
 
+    @NotNull
+    @JsonProperty("is_schema_public")
+    @Schema(example = "true")
+    private Boolean isSchemaPublic;
+
     private List<IdentifierBriefDto> identifiers;
 
     @ToString.Exclude
diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java
index a692cce3297525771471d60a28ed83c9df4b804d..aef7ebd7b8e5cfe718814f18c30ea0e5b157e37a 100644
--- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java
+++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java
@@ -33,10 +33,10 @@ import static jakarta.persistence.GenerationType.IDENTITY;
 })
 @NamedQueries({
         @NamedQuery(name = "Database.findAllDesc", query = "select distinct d from Database d order by d.id desc"),
-        @NamedQuery(name = "Database.findAllPublicDesc", query = "select distinct d from Database d where d.isPublic = true order by d.id desc"),
-        @NamedQuery(name = "Database.findAllPublicOrReadAccessDesc", query = "select distinct d from Database d where d.isPublic = true or exists(select a.hdbid from DatabaseAccess a where a.huserid = ?1 and a.hdbid = d.id) order by d.id desc"),
-        @NamedQuery(name = "Database.findAllPublicOrReadAccessByInternalNameDesc", query = "select distinct d from Database d where d.isPublic = true and d.internalName = ?2 or exists(select a.hdbid from DatabaseAccess a where a.huserid = ?1 and a.hdbid = d.id) order by d.id desc"),
-        @NamedQuery(name = "Database.findAllPublicByInternalNameDesc", query = "select distinct d from Database d where d.isPublic = true and d.internalName = ?1 order by d.id desc"),
+        @NamedQuery(name = "Database.findAllPublicDesc", query = "select distinct d from Database d where d.isPublic = true or d.isSchemaPublic = true order by d.id desc"),
+        @NamedQuery(name = "Database.findAllPublicOrReadAccessDesc", query = "select distinct d from Database d where d.isPublic = true or d.isSchemaPublic = true or exists(select a.hdbid from DatabaseAccess a where a.huserid = ?1 and a.hdbid = d.id) order by d.id desc"),
+        @NamedQuery(name = "Database.findAllPublicOrReadAccessByInternalNameDesc", query = "select distinct d from Database d where (d.isPublic = true or d.isSchemaPublic = true) and d.internalName = ?2 or exists(select a.hdbid from DatabaseAccess a where a.huserid = ?1 and a.hdbid = d.id) order by d.id desc"),
+        @NamedQuery(name = "Database.findAllPublicByInternalNameDesc", query = "select distinct d from Database d where (d.isPublic = true or d.isSchemaPublic = true) and d.internalName = ?1 order by d.id desc"),
 })
 public class Database implements Serializable {
 
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
index c4ad192eec938eb0d115d0bb469dd5fd0a46dff7..d1f40b29bac3fe2c88c017f2635ab0748f72f5ed 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
@@ -70,7 +70,7 @@ public class TableEndpoint extends AbstractEndpoint {
     @Transactional(readOnly = true)
     @Observed(name = "dbrepo_tables_findall")
     @Operation(summary = "List tables",
-            description = "Lists all tables known to the metadata database.",
+            description = "Lists all tables known to the metadata database. When a database has a hidden schema (i.e. when `is_schema_public` is `false`), then the user needs to have at least read access and the role `list-tables`.",
             security = {@SecurityRequirement(name = "bearerAuth"), @SecurityRequirement(name = "basicAuth")})
     @ApiResponses(value = {
             @ApiResponse(responseCode = "200",
@@ -94,10 +94,11 @@ public class TableEndpoint extends AbstractEndpoint {
             DatabaseNotFoundException, UserNotFoundException, AccessNotFoundException {
         log.debug("endpoint list tables, databaseId={}", databaseId);
         final Database database = databaseService.findById(databaseId);
-        endpointValidator.validateOnlyPrivateAccess(database, principal);
-        endpointValidator.validateOnlyPrivateHasRole(database, principal, "list-tables");
+        endpointValidator.validateOnlyPrivateSchemaAccess(database, principal);
+        endpointValidator.validateOnlyPrivateSchemaHasRole(database, principal, "list-tables");
         return ResponseEntity.ok(database.getTables()
                 .stream()
+                .filter(Table::getIsPublic)
                 .map(metadataMapper::tableToTableBriefDto)
                 .collect(Collectors.toList()));
     }
@@ -435,7 +436,7 @@ public class TableEndpoint extends AbstractEndpoint {
     @Transactional(readOnly = true)
     @Observed(name = "dbrepo_tables_find")
     @Operation(summary = "Find table",
-            description = "Finds a table with id. When the `system` role is present, the endpoint responds with additional connection metadata in the header.",
+            description = "Finds a table with id. When a table is hidden (i.e. when `is_public` is `false`), then the user needs to have at least read access and the role `find-table`. When the `system` role is present, the endpoint responds with additional connection metadata in the header.",
             security = {@SecurityRequirement(name = "bearerAuth"), @SecurityRequirement(name = "basicAuth")})
     @ApiResponses(value = {
             @ApiResponse(responseCode = "200",
@@ -475,9 +476,12 @@ public class TableEndpoint extends AbstractEndpoint {
     public ResponseEntity<TableDto> findById(@NotNull @PathVariable("databaseId") Long databaseId,
                                              @NotNull @PathVariable("tableId") Long tableId,
                                              Principal principal) throws DataServiceException,
-            DataServiceConnectionException, TableNotFoundException, DatabaseNotFoundException, QueueNotFoundException {
+            DataServiceConnectionException, TableNotFoundException, DatabaseNotFoundException, QueueNotFoundException,
+            UserNotFoundException, NotAllowedException, AccessNotFoundException {
         log.debug("endpoint find table, databaseId={}, tableId={}", databaseId, tableId);
         final Database database = databaseService.findById(databaseId);
+        endpointValidator.validateOnlyPrivateDataAccess(database, principal);
+        endpointValidator.validateOnlyPrivateDataHasRole(database, principal, "find-table");
         final Table table = tableService.findById(database, tableId);
         boolean hasAccess = isSystem(principal);
         boolean isOwner = false;
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
index a748c0d82dab0032ea0b75f6e649f1d1973469d4..b8716532b022ebc0602cb20dc8119a3ffbcc8504 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
@@ -43,7 +43,7 @@ public class EndpointValidator extends AbstractEndpoint {
         this.accessService = accessService;
     }
 
-    public void validateOnlyPrivateAccess(Database database, Principal principal, boolean writeAccessOnly)
+    public void validateOnlyPrivateDataAccess(Database database, Principal principal, boolean writeAccessOnly)
             throws NotAllowedException, UserNotFoundException, AccessNotFoundException {
         if (database.getIsPublic()) {
             log.trace("database with id {} is public: no access needed", database.getId());
@@ -52,9 +52,23 @@ public class EndpointValidator extends AbstractEndpoint {
         validateOnlyAccess(database, principal, writeAccessOnly);
     }
 
-    public void validateOnlyPrivateAccess(Database database, Principal principal) throws NotAllowedException,
+    public void validateOnlyPrivateSchemaAccess(Database database, Principal principal, boolean writeAccessOnly)
+            throws NotAllowedException, UserNotFoundException, AccessNotFoundException {
+        if (database.getIsSchemaPublic()) {
+            log.trace("database schema with id {} is public: no access needed", database.getId());
+            return;
+        }
+        validateOnlyAccess(database, principal, writeAccessOnly);
+    }
+
+    public void validateOnlyPrivateDataAccess(Database database, Principal principal) throws NotAllowedException,
             UserNotFoundException, AccessNotFoundException {
-        validateOnlyPrivateAccess(database, principal, false);
+        validateOnlyPrivateDataAccess(database, principal, false);
+    }
+
+    public void validateOnlyPrivateSchemaAccess(Database database, Principal principal) throws NotAllowedException,
+            UserNotFoundException, AccessNotFoundException {
+        validateOnlyPrivateSchemaAccess(database, principal, false);
     }
 
     public void validateOnlyAccess(Database database, Principal principal, boolean writeAccessOnly)
@@ -221,7 +235,7 @@ public class EndpointValidator extends AbstractEndpoint {
         throw new NotAllowedException("Access not allowed: insufficient access (neither owner nor write-all access)");
     }
 
-    public void validateOnlyPrivateHasRole(Database database, Principal principal, String role)
+    public void validateOnlyPrivateDataHasRole(Database database, Principal principal, String role)
             throws NotAllowedException {
         if (database.getIsPublic()) {
             log.trace("database with id {} is public: no access needed", database.getId());
@@ -240,6 +254,25 @@ public class EndpointValidator extends AbstractEndpoint {
         log.trace("principal has role '{}': access granted", role);
     }
 
+    public void validateOnlyPrivateSchemaHasRole(Database database, Principal principal, String role)
+            throws NotAllowedException {
+        if (database.getIsSchemaPublic()) {
+            log.trace("database with id {} has public schema: no access needed", database.getId());
+            return;
+        }
+        log.trace("database with id {} has private schema", database.getId());
+        if (principal == null) {
+            log.error("Access not allowed: no authorization provided");
+            throw new NotAllowedException("Access not allowed: no authorization provided");
+        }
+        log.trace("principal: {}", principal.getName());
+        if (!hasRole(principal, role)) {
+            log.error("Access not allowed: role {} missing", role);
+            throw new NotAllowedException("Access not allowed: role " + role + " missing");
+        }
+        log.trace("principal has role '{}': access granted", role);
+    }
+
     public void validateDataParams(Long page, Long size) throws PaginationException {
         log.trace("validate data params, page={}, size={}", page, size);
         if ((page == null && size != null) || (page != null && size == null)) {
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java
index dcc10f61a70ef663f0b4427c60f1acefba8f58ed..8f72fe57dc24b2a7f50201df3737f786aad47ee7 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java
@@ -513,7 +513,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void findById_publicAnonymous_succeeds() throws DataServiceException, DataServiceConnectionException,
             TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException, QueueNotFoundException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
 
         /* test */
         generic_findById(DATABASE_3_ID, DATABASE_3, TABLE_8_ID, TABLE_8, null, null, null);
@@ -543,7 +543,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
     @WithMockUser(username = USER_1_USERNAME, authorities = "find-table")
     public void findById_publicHasRole_succeeds() throws DataServiceException, DataServiceConnectionException,
             TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException, QueueNotFoundException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
 
         /* test */
         final ResponseEntity<TableDto> response = generic_findById(DATABASE_3_ID, DATABASE_3, TABLE_8_ID, TABLE_8, USER_1_PRINCIPAL, USER_1, DATABASE_1_USER_1_READ_ACCESS);
@@ -556,7 +556,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
     @WithMockUser(username = USER_4_USERNAME)
     public void findById_publicNoRole_succeeds() throws DataServiceException, DataServiceConnectionException,
             TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException, QueueNotFoundException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
 
         /* test */
         generic_findById(DATABASE_3_ID, DATABASE_3, TABLE_8_ID, TABLE_8, USER_1_PRINCIPAL, USER_1, null);
@@ -900,7 +900,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void findById_privateAnonymous_succeeds() throws DataServiceException, DataServiceConnectionException,
             TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException, QueueNotFoundException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
 
         /* test */
         generic_findById(DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, null, null, null);
@@ -930,7 +930,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
     @WithMockUser(username = USER_1_USERNAME, authorities = "find-table")
     public void findById_privateHasRole_succeeds() throws DataServiceException, DataServiceConnectionException,
             TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException, QueueNotFoundException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
         /* test */
         final ResponseEntity<TableDto> response = generic_findById(DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1,
                 USER_1_PRINCIPAL, USER_1, DATABASE_1_USER_1_READ_ACCESS);
@@ -943,7 +943,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
     @WithMockUser(username = USER_4_USERNAME)
     public void findById_privateNoRole_succeeds() throws DataServiceException, DataServiceConnectionException,
             TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException, QueueNotFoundException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
 
         /* test */
         generic_findById(DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, USER_4_PRINCIPAL, USER_4, null);
@@ -1160,7 +1160,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest {
                                                         Table table, Principal principal, User user,
                                                         DatabaseAccess access) throws DataServiceException,
             DataServiceConnectionException, TableNotFoundException, DatabaseNotFoundException, AccessNotFoundException,
-            QueueNotFoundException, UserNotFoundException {
+            QueueNotFoundException, UserNotFoundException, NotAllowedException {
 
         /* mock */
         if (database != null) {
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java
index 29718b0962c76b8b1ad01ef003de092459d92714..342a9e328ecc8e69abe7bfedf1827bdc7777fff3 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java
@@ -324,20 +324,38 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest {
     }
 
     @Test
-    public void validateOnlyPrivateHasRole_privatePrincipalMissing_fails() {
+    public void validateOnlyPrivateDataHasRole_privatePrincipalMissing_fails() {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            endpointValidator.validateOnlyPrivateHasRole(DATABASE_1, null, "list-tables");
+            endpointValidator.validateOnlyPrivateDataHasRole(DATABASE_1, null, "list-tables");
         });
     }
 
     @Test
-    public void validateOnlyPrivateHasRole_privateRoleMissing_fails() {
+    public void validateOnlyPrivateDataHasRole_privateRoleMissing_fails() {
 
         /* test */
         assertThrows(NotAllowedException.class, () -> {
-            endpointValidator.validateOnlyPrivateHasRole(DATABASE_1, USER_4_PRINCIPAL, "list-tables");
+            endpointValidator.validateOnlyPrivateDataHasRole(DATABASE_1, USER_4_PRINCIPAL, "list-tables");
+        });
+    }
+
+    @Test
+    public void validateOnlyPrivateSchemaHasRole_privatePrincipalMissing_fails() {
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            endpointValidator.validateOnlyPrivateSchemaHasRole(DATABASE_1, null, "list-tables");
+        });
+    }
+
+    @Test
+    public void validateOnlyPrivateSchemaHasRole_privateRoleMissing_fails() {
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            endpointValidator.validateOnlyPrivateSchemaHasRole(DATABASE_1, USER_4_PRINCIPAL, "list-tables");
         });
     }
 
diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java
index 5e5b653852782dd6fab81dd39d1d7e5762396bfa..2859a30a7134109b7db625d491b353a39ff9e5e2 100644
--- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java
+++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java
@@ -283,9 +283,12 @@ public abstract class BaseTest {
             .type(AccessTypeDto.WRITE_ALL)
             .build();
 
+    public final static String TOKEN_ACCESS_TOKEN = "ey.yee.skrr";
+    public final static String TOKEN_ACCESS_SCOPE = "openid";
+
     public final static TokenDto TOKEN_DTO = TokenDto.builder()
-            .accessToken("ey.yee.skrr")
-            .scope("openid")
+            .accessToken(TOKEN_ACCESS_TOKEN)
+            .scope(TOKEN_ACCESS_SCOPE)
             .build();
 
     public final static RefreshTokenRequestDto REFRESH_TOKEN_REQUEST_DTO = RefreshTokenRequestDto.builder()
diff --git a/dbrepo-ui/components/JumboBox.vue b/dbrepo-ui/components/JumboBox.vue
index 10b1a8a6697d44bda97e402ef056dc1ba093a8ff..d2b804f819f22782ba4895c601c8b2ea118ce4e3 100644
--- a/dbrepo-ui/components/JumboBox.vue
+++ b/dbrepo-ui/components/JumboBox.vue
@@ -1,11 +1,12 @@
 <template>
   <div>
     <v-row>
-      <v-col
-        offset-md="2"
-        md="8">
+      <v-col>
         <v-card
+          elevation="0"
+          variant="flat"
           class="mx-auto"
+          max-width="600"
           :title="title"
           :subtitle="subtitle"
           :text="text">
diff --git a/dbrepo-ui/components/ResourceStatus.vue b/dbrepo-ui/components/ResourceStatus.vue
new file mode 100644
index 0000000000000000000000000000000000000000..50c70899991a0f6c3e9210939a41065f14f6f239
--- /dev/null
+++ b/dbrepo-ui/components/ResourceStatus.vue
@@ -0,0 +1,62 @@
+<template>
+  <span
+    v-if="mode">
+    <v-chip
+      v-if="!inline"
+      size="small"
+      :color="color"
+      variant="outlined">
+      {{ status }}
+    </v-chip>
+    <span
+      v-else>
+      {{ status }}
+    </span>
+  </span>
+</template>
+<script>
+export default {
+  props: {
+    resource: {
+      default: () => {
+        return null
+      }
+    },
+    inline: {
+      default: () => {
+        return false
+      }
+    }
+  },
+  computed: {
+    mode () {
+      if (!this.resource) {
+        return null
+      }
+      if (!this.resource.is_public) {
+        if (!this.resource.is_schema_public) {
+          return 'draft'
+        }
+        return 'private'
+      }
+      return 'public'
+    },
+    status () {
+      if (!this.resource) {
+        return null
+      }
+      return this.$t(`pages.database.status.${this.mode}`)
+    },
+    color () {
+      switch (this.mode) {
+        case 'private':
+          return 'secondary'
+        case 'draft':
+          return 'warning'
+        case 'public':
+          return 'success'
+      }
+    }
+  }
+}
+</script>
diff --git a/dbrepo-ui/components/database/DatabaseCard.vue b/dbrepo-ui/components/database/DatabaseCard.vue
index fba3853a31cf25649f915d4c0a3e422f5ec879bf..c31d288c6fde7ed711ab8d1cab5e5da22a38876e 100644
--- a/dbrepo-ui/components/database/DatabaseCard.vue
+++ b/dbrepo-ui/components/database/DatabaseCard.vue
@@ -27,21 +27,8 @@
         {{ identifierDescription(database) }}
       </div>
       <div class="mt-2 db-tags">
-        <v-chip
-          v-if="database.is_public"
-          size="small"
-          color="success"
-          variant="outlined">
-          {{ $t('toolbars.database.public') }}
-        </v-chip>
-        <v-chip
-          v-if="!database.is_public"
-          size="small"
-          :color="colorVariant"
-          variant="outlined"
-          flat>
-          {{ $t('toolbars.database.private') }}
-        </v-chip>
+        <ResourceStatus
+          :resource="database" />
         <v-chip
           v-if="identifierYear(database)"
           size="small"
@@ -86,8 +73,12 @@
 
 <script>
 import { formatLanguage } from '@/utils'
+import ResourceStatus from '@/components/ResourceStatus.vue'
 
 export default {
+  components: {
+    ResourceStatus
+  },
   data() {
     return {
       loading: false
@@ -175,7 +166,7 @@ export default {
         return null
       }
       return this.identifiers[0]
-    },
+    }
   }
 }
 </script>
diff --git a/dbrepo-ui/components/database/DatabaseCreate.vue b/dbrepo-ui/components/database/DatabaseCreate.vue
index d0f386b08899b2c3f911958297e88d7d67bba4f8..1797dbdad333a5c16ff58f2cf673f2d615b12b4d 100644
--- a/dbrepo-ui/components/database/DatabaseCreate.vue
+++ b/dbrepo-ui/components/database/DatabaseCreate.vue
@@ -55,33 +55,12 @@
           </v-row>
           <v-row>
             <v-col>
-              <v-select
-                v-model="mode"
-                name="mode"
-                :label="$t('pages.database.subpages.create.visibility.label')"
-                :hint="$t('pages.database.subpages.create.visibility.hint')"
-                persistent-hint
-                :variant="inputVariant"
-                :items="visibilityOptions"
-                item-title="name"
-                item-value="value"
-                :rules="[v => !!v || $t('validation.required')]"
-                return-object
-                required>
-                <template
-                  v-slot:append-inner>
-                  <v-tooltip
-                    location="bottom">
-                    <template
-                      v-slot:activator="{ props }">
-                      <v-icon
-                        v-bind="props"
-                        icon="mdi-help-circle-outline" />
-                    </template>
-                    {{ mode.hint }}
-                  </v-tooltip>
-                </template>
-              </v-select>
+              <v-checkbox
+                v-model="draft"
+                name="draft"
+                :label="$t('pages.database.subpages.create.draft.label')"
+                :hint="$t('pages.database.subpages.create.draft.hint')"
+                persistent-hint />
             </v-col>
           </v-row>
         </v-card-text>
@@ -130,7 +109,7 @@ export default {
           value: false
         }
       ],
-      mode: true,
+      draft: true,
       payload: {
         name: null,
         is_public: true,
diff --git a/dbrepo-ui/components/database/DatabaseToolbar.vue b/dbrepo-ui/components/database/DatabaseToolbar.vue
index 65363c3467067aa4dd3eca9e2d5595a913723684..3f7412d5cb538972f5c21af2b155259738a74d7b 100644
--- a/dbrepo-ui/components/database/DatabaseToolbar.vue
+++ b/dbrepo-ui/components/database/DatabaseToolbar.vue
@@ -11,21 +11,9 @@
           v-if="database && $vuetify.display.lgAndUp">
           {{ database.name }}
         </span>
-        <v-chip
-          v-if="database && database.is_public"
-          size="small"
+        <ResourceStatus
           class="ml-2"
-          color="success"
-          :text="$t('toolbars.database.public')"
-          variant="outlined" />
-        <v-chip
-          v-if="database && !database.is_public"
-          size="small"
-          class="ml-2"
-          :color="colorVariant"
-          variant="outlined"
-          :text="$t('toolbars.database.private')"
-          flat />
+          :resource="database" />
       </v-toolbar-title>
       <v-spacer />
       <v-btn
@@ -95,8 +83,12 @@
 <script>
 import { useCacheStore } from '@/stores/cache'
 import { useUserStore } from '@/stores/user'
+import ResourceStatus from '@/components/ResourceStatus.vue'
 
 export default {
+  components: {
+    ResourceStatus
+  },
   data () {
     return {
       tab: null,
diff --git a/dbrepo-ui/components/dialogs/UpdateTable.vue b/dbrepo-ui/components/dialogs/UpdateTable.vue
index 6e4e07182c12c480b1f4c5d4da3756d3e111164a..8defb2669e68edd7c36af6b528cec0d252e4d12f 100644
--- a/dbrepo-ui/components/dialogs/UpdateTable.vue
+++ b/dbrepo-ui/components/dialogs/UpdateTable.vue
@@ -31,29 +31,29 @@
               md="6">
               <v-select
                 v-model="modify.is_public"
-                :items="visibilities"
+                :items="dataOptions"
                 persistent-hint
                 :variant="inputVariant"
                 required
                 :rules="[
                   v => v !== null || $t('validation.required')
                 ]"
-                :label="$t('pages.database.subpages.create.data.label')"
-                :hint="$t('pages.database.subpages.create.data.hint')" />
+                :label="$t('pages.database.resource.data.label')"
+                :hint="$t('pages.database.resource.data.hint')" />
             </v-col>
             <v-col
               md="6">
               <v-select
                 v-model="modify.is_schema_public"
-                :items="visibilities"
+                :items="schemaOptions"
                 persistent-hint
                 :variant="inputVariant"
                 required
                 :rules="[
                   v => v !== null || $t('validation.required')
                 ]"
-                :label="$t('pages.database.subpages.create.schema.label')"
-                :hint="$t('pages.database.subpages.create.schema.hint')" />
+                :label="$t('pages.database.resource.schema.label')"
+                :hint="$t('pages.database.resource.schema.hint')" />
             </v-col>
           </v-row>
         </v-card-text>
@@ -98,9 +98,13 @@ export default {
     return {
       valid: false,
       loading: false,
-      visibilities: [
-        { title: this.$t('toolbars.database.public'), value: true },
-        { title: this.$t('toolbars.database.private'), value: false },
+      dataOptions: [
+        { title: this.$t('pages.database.resource.data.enabled'), value: true },
+        { title: this.$t('pages.database.resource.data.disabled'), value: false },
+      ],
+      schemaOptions: [
+        { title: this.$t('pages.database.resource.schema.enabled'), value: true },
+        { title: this.$t('pages.database.resource.schema.disabled'), value: false },
       ],
       modify: {
         description: this.table.description,
@@ -153,7 +157,7 @@ export default {
           this.$emit('close', { success: true })
           this.cacheStore.reloadTable()
         })
-        .catch(({code}) => {
+        .catch(({ code }) => {
           this.loading = false
           const toast = useToastInstance()
           toast.error(this.$t(code))
diff --git a/dbrepo-ui/components/dialogs/ViewVisibility.vue b/dbrepo-ui/components/dialogs/ViewVisibility.vue
index 226772edd922a9d71e40a312d461c666c50e8893..d8cc01790eb82e52e5fb320baf3800afdbd031d0 100644
--- a/dbrepo-ui/components/dialogs/ViewVisibility.vue
+++ b/dbrepo-ui/components/dialogs/ViewVisibility.vue
@@ -14,29 +14,29 @@
               md="6">
               <v-select
                 v-model="modify.is_public"
-                :items="visibilities"
+                :items="dataOptions"
                 persistent-hint
                 :variant="inputVariant"
                 required
                 :rules="[
                   v => v !== null || $t('validation.required')
                 ]"
-                :label="$t('pages.database.subpages.create.data.label')"
-                :hint="$t('pages.database.subpages.create.data.hint')" />
+                :label="$t('pages.database.resource.data.label')"
+                :hint="$t('pages.database.resource.data.hint')" />
             </v-col>
             <v-col
               md="6">
               <v-select
                 v-model="modify.is_schema_public"
-                :items="visibilities"
+                :items="schemaOptions"
                 persistent-hint
                 :variant="inputVariant"
                 required
                 :rules="[
                   v => v !== null || $t('validation.required')
                 ]"
-                :label="$t('pages.database.subpages.create.schema.label')"
-                :hint="$t('pages.database.subpages.create.schema.hint')" />
+                :label="$t('pages.database.resource.schema.label')"
+                :hint="$t('pages.database.resource.schema.hint')" />
             </v-col>
           </v-row>
         </v-card-text>
@@ -82,9 +82,13 @@ export default {
       loadingUsers: false,
       users: [],
       error: false,
-      visibilities: [
-        { title: this.$t('toolbars.database.public'), value: true },
-        { title: this.$t('toolbars.database.private'), value: false },
+      dataOptions: [
+        { title: this.$t('pages.database.resource.data.enabled'), value: true },
+        { title: this.$t('pages.database.resource.data.disabled'), value: false },
+      ],
+      schemaOptions: [
+        { title: this.$t('pages.database.resource.schema.enabled'), value: true },
+        { title: this.$t('pages.database.resource.schema.disabled'), value: false },
       ],
       modify: {
         is_public: this.view.is_public,
diff --git a/dbrepo-ui/components/subset/Builder.vue b/dbrepo-ui/components/subset/Builder.vue
index 2a14b8f474a8f7a3cc0e38302e3ebad0e0df5a7d..9b85162457aa8e095caff187b6dbeed08490a65a 100644
--- a/dbrepo-ui/components/subset/Builder.vue
+++ b/dbrepo-ui/components/subset/Builder.vue
@@ -68,7 +68,7 @@
               md="4">
               <v-select
                 v-model="view.is_public"
-                :items="visibilities"
+                :items="dataOptions"
                 persistent-hint
                 :variant="inputVariant"
                 required
@@ -76,14 +76,14 @@
                 :rules="[
                   v => !!v || $t('validation.required')
                 ]"
-                :label="$t('pages.database.subpages.create.data.label')"
-                :hint="$t('pages.database.subpages.create.data.hint')" />
+                :label="$t('pages.database.resource.data.label')"
+                :hint="$t('pages.database.resource.data.hint')" />
             </v-col>
             <v-col
               md="4">
               <v-select
                 v-model="view.is_schema_public"
-                :items="visibilities"
+                :items="schemaOptions"
                 persistent-hint
                 :variant="inputVariant"
                 required
@@ -91,8 +91,8 @@
                 :rules="[
                   v => !!v || $t('validation.required')
                 ]"
-                :label="$t('pages.database.subpages.create.schema.label')"
-                :hint="$t('pages.database.subpages.create.schema.hint')" />
+                :label="$t('pages.database.resource.schema.label')"
+                :hint="$t('pages.database.resource.schema.hint')" />
             </v-col>
           </v-row>
           <v-window
@@ -332,9 +332,13 @@ export default {
       columns: [],
       timestamp: null,
       executeDifferentTimestamp: false,
-      visibilities: [
-        { title: this.$t('toolbars.database.public'), value: true },
-        { title: this.$t('toolbars.database.private'), value: false },
+      dataOptions: [
+        { title: this.$t('pages.database.resource.data.enabled'), value: true },
+        { title: this.$t('pages.database.resource.data.disabled'), value: false },
+      ],
+      schemaOptions: [
+        { title: this.$t('pages.database.resource.schema.enabled'), value: true },
+        { title: this.$t('pages.database.resource.schema.disabled'), value: false },
       ],
       tableDetails: null,
       resultId: null,
diff --git a/dbrepo-ui/components/subset/SubsetList.vue b/dbrepo-ui/components/subset/SubsetList.vue
index f57dc68a88fd77c06b858b423565b4ccac7d46f0..ae8f2bf448753d4d7b6b4454f0b7e1671d7eb25d 100644
--- a/dbrepo-ui/components/subset/SubsetList.vue
+++ b/dbrepo-ui/components/subset/SubsetList.vue
@@ -26,21 +26,8 @@
             :to="link(item)"
             :href="link(item)">
             <template v-slot:append>
-              <v-chip
-                v-if="database.is_public"
-                size="small"
-                class="ml-2"
-                color="success"
-                :text="$t('toolbars.database.public')"
-                variant="outlined" />
-              <v-chip
-                v-if="!database.is_public"
-                size="small"
-                class="ml-2"
-                :color="colorVariant"
-                variant="outlined"
-                :text="$t('toolbars.database.private')"
-                flat />
+              <ResourceStatus
+                :resource="item" />
               <v-tooltip
                 v-if="hasPublishedIdentifier(item)"
                 :text="$t('pages.identifier.pid.title')"
diff --git a/dbrepo-ui/components/table/TableList.vue b/dbrepo-ui/components/table/TableList.vue
index 8bb77b2b5a8971e3a0d8dbe557e9d5d849f492c1..2cb42a1633e47ca9d117e4449318582666975d35 100644
--- a/dbrepo-ui/components/table/TableList.vue
+++ b/dbrepo-ui/components/table/TableList.vue
@@ -19,21 +19,8 @@
           :subtitle="table.description ? table.description : ''"
           :to="`/database/${$route.params.database_id}/table/${table.id}/info`">
           <template v-slot:append>
-            <v-chip
-              v-if="table && table.is_public"
-              size="small"
-              class="ml-2"
-              color="success"
-              :text="$t('toolbars.database.public')"
-              variant="outlined" />
-            <v-chip
-              v-if="table && !table.is_public"
-              size="small"
-              class="ml-2"
-              :color="colorVariant"
-              variant="outlined"
-              :text="$t('toolbars.database.private')"
-              flat />
+            <ResourceStatus
+              :resource="table" />
             <v-tooltip
               v-if="hasPublishedIdentifier(table)"
               :text="$t('pages.identifier.pid.title')"
diff --git a/dbrepo-ui/components/table/TableToolbar.vue b/dbrepo-ui/components/table/TableToolbar.vue
index b7d358d2492bd01aa8eddc479405c660784f66c6..2840e2e4c43e545a98385bb42b993650940dc4a2 100644
--- a/dbrepo-ui/components/table/TableToolbar.vue
+++ b/dbrepo-ui/components/table/TableToolbar.vue
@@ -16,21 +16,9 @@
           v-if="table && $vuetify.display.lgAndUp">
           {{ table.name }}
         </span>
-        <v-chip
-          v-if="table && table.is_public"
-          size="small"
+        <ResourceStatus
           class="ml-2"
-          color="success"
-          :text="$t('toolbars.database.public')"
-          variant="outlined" />
-        <v-chip
-          v-if="table && !table.is_public"
-          size="small"
-          class="ml-2"
-          :color="colorVariant"
-          variant="outlined"
-          :text="$t('toolbars.database.private')"
-          flat />
+          :resource="table" />
       </v-toolbar-title>
       <v-spacer />
       <v-btn
diff --git a/dbrepo-ui/components/view/ViewList.vue b/dbrepo-ui/components/view/ViewList.vue
index 543a8746affd8cbbed495647fc963c8db1534072..1b278a555c1154492882e92773b0f9203057c799 100644
--- a/dbrepo-ui/components/view/ViewList.vue
+++ b/dbrepo-ui/components/view/ViewList.vue
@@ -14,21 +14,8 @@
           :class="clazz(view)"
           :to="`/database/${$route.params.database_id}/view/${view.id}/info`">
           <template v-slot:append>
-            <v-chip
-              v-if="view && view.is_public"
-              size="small"
-              class="ml-2"
-              color="success"
-              :text="$t('toolbars.database.public')"
-              variant="outlined" />
-            <v-chip
-              v-if="view && !view.is_public"
-              size="small"
-              class="ml-2"
-              :color="colorVariant"
-              variant="outlined"
-              :text="$t('toolbars.database.private')"
-              flat />
+            <ResourceStatus
+              :resource="view" />
             <v-tooltip
               v-if="hasPublishedIdentifier(view)"
               :text="$t('pages.identifier.pid.title')"
diff --git a/dbrepo-ui/components/view/ViewToolbar.vue b/dbrepo-ui/components/view/ViewToolbar.vue
index 64ea3f1029407e3143bc6dd9b5cdfcb634aa9b94..9e980e7a3b502109db67f7a369197af1c5441cd4 100644
--- a/dbrepo-ui/components/view/ViewToolbar.vue
+++ b/dbrepo-ui/components/view/ViewToolbar.vue
@@ -6,26 +6,14 @@
       icon="mdi-arrow-left"
       :to="`/database/${$route.params.database_id}/view`" />
     <v-toolbar-title
-      v-if="cachedView">
+      v-if="view">
       <span
         v-if="$vuetify.display.lgAndUp">
         {{ title }}
       </span>
-      <v-chip
-        v-if="cachedView.is_public"
-        size="small"
+      <ResourceStatus
         class="ml-2"
-        color="success"
-        :text="$t('toolbars.database.public')"
-        variant="outlined" />
-      <v-chip
-        v-if="!cachedView.is_public"
-        size="small"
-        class="ml-2"
-        :color="colorVariant"
-        variant="outlined"
-        :text="$t('toolbars.database.private')"
-        flat />
+        :resource="view" />
     </v-toolbar-title>
     <v-spacer />
     <v-btn
@@ -58,7 +46,7 @@
       persistent
       max-width="640">
       <ViewVisibility
-        :view="cachedView"
+        :view="view"
         @close="close" />
     </v-dialog>
     <template v-slot:extension>
@@ -69,11 +57,11 @@
           :text="$t('navigation.info')"
           :to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/info`" />
         <v-tab
-          v-if="canReadData"
+          v-if="canViewData"
           :text="$t('navigation.data')"
           :to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/data`" />
         <v-tab
-          v-if="canReadData"
+          v-if="canViewSchema"
           :text="$t('navigation.schema')"
           :to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/schema`" />
       </v-tabs>
@@ -113,59 +101,47 @@ export default {
       const runtimeConfig = useRuntimeConfig()
       return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.button.contrast : runtimeConfig.public.variant.button.normal
     },
-    cachedView () {
-      if (!this.database) {
-        return null
-      }
-      return this.database.views.filter(v => v.id === Number(this.$route.params.view_id))[0]
+    view () {
+      return this.cacheStore.getView
     },
     canViewData () {
-      if (!this.cachedView) {
+      if (!this.view) {
         return false
       }
-      if (this.cachedView.is_public) {
+      if (this.view.is_public) {
         return true
       }
       if (!this.user) {
         return false
       }
-      return this.hasReadAccess || this.cachedView.owned_by === this.user.id || this.database.owner.id === this.user.id
+      return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id
     },
     canViewSchema () {
-      if (!this.cachedView) {
+      if (!this.view) {
         return false
       }
-      if (this.cachedView.is_schema_public) {
+      if (this.view.is_schema_public) {
         return true
       }
       if (!this.user) {
         return false
       }
-      return this.hasReadAccess || this.cachedView.owned_by === this.user.id || this.database.owner.id === this.user.id
+      return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id
     },
     canDeleteView () {
-      if (!this.roles || !this.user || !this.cachedView) {
+      if (!this.roles || !this.user || !this.view) {
         return false
       }
-      return this.roles.includes('delete-database-view') && this.cachedView.owned_by === this.user.id
+      return this.roles.includes('delete-database-view') && this.view.owner.id === this.user.id
     },
     canUpdateVisibility () {
-      if (!this.roles || !this.user || !this.cachedView) {
+      if (!this.roles || !this.user || !this.view) {
         return false
       }
-      return this.roles.includes('modify-view-visibility') && this.cachedView.owned_by === this.user.id
-    },
-    isContrastTheme () {
-      return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast')
-    },
-    isDarkTheme () {
-      return this.$vuetify.theme.global.name.toLowerCase().startsWith('dark')
-    },
-    colorVariant () {
-      return this.isContrastTheme ? '' : (this.isDarkTheme ? 'tertiary' : 'secondary')
+      return this.roles.includes('modify-view-visibility') && this.view.owner.id === this.user.id
     },
     canCreatePid () {
-      if (!this.roles || !this.user || !this.cachedView) {
+      if (!this.roles || !this.user || !this.view) {
         return false
       }
       const userService = useUserService()
@@ -186,23 +162,11 @@ export default {
       }
       return this.access.type === 'read' ||  this.access.type === 'write_own' ||  this.access.type === 'write_all'
     },
-    canReadData () {
-      if (!this.cachedView) {
-        return false
-      }
-      if (this.cachedView.is_public) {
-        return true
-      }
-      if (!this.user) {
-        return false
-      }
-      return this.cachedView.owner.id === this.user.id || this.hasReadAccess
-    },
     identifiers () {
-      if (!this.cachedView) {
+      if (!this.view) {
         return []
       }
-      return this.cachedView.identifiers.filter(s => s.view_id === Number(this.$route.params.view_id))
+      return this.view.identifiers.filter(s => s.view_id === Number(this.$route.params.view_id))
     },
     identifier () {
       /* mount pid */
@@ -217,10 +181,10 @@ export default {
       return this.identifiers[0]
     },
     title () {
-      if (!this.cachedView) {
+      if (!this.view) {
         return null
       }
-      return this.cachedView.name
+      return this.view.name
     }
   },
   methods: {
diff --git a/dbrepo-ui/composables/identifier-service.ts b/dbrepo-ui/composables/identifier-service.ts
index f85c48dc21f1428ea0656a00861eb260e48c0cf5..3853d9df751aef86b62e2cb30394cf0be1896efb 100644
--- a/dbrepo-ui/composables/identifier-service.ts
+++ b/dbrepo-ui/composables/identifier-service.ts
@@ -328,6 +328,9 @@ export const useIdentifierService = (): any => {
   }
 
   function databaseToServerHead(database: DatabaseDto) {
+    if (!database) {
+      return
+    }
     const config = useRuntimeConfig()
     /* Google Rich Results */
     const json: any = {
diff --git a/dbrepo-ui/composables/view-service.ts b/dbrepo-ui/composables/view-service.ts
index 4c948a57f10b5637cb441baa83e42bb9c3850a46..417f5a645e978cc8f93e7fc524df22756c5b093e 100644
--- a/dbrepo-ui/composables/view-service.ts
+++ b/dbrepo-ui/composables/view-service.ts
@@ -24,7 +24,7 @@ export const useViewService = (): any => {
     return new Promise<ViewDto>((resolve, reject) => {
       axios.get<ViewDto>(`/api/database/${databaseId}/view/${viewId}`)
         .then((response) => {
-          console.info('Deleted view with id', viewId, 'in database with id', databaseId)
+          console.info('Found view with id', viewId, 'in database with id', databaseId)
           resolve(response.data)
         })
         .catch((error) => {
diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue
index 8816ab5c9190662331e8ff86b85984cb92eec37f..952b66a00a3f6924c4cd3a338e67e6de62f33688 100644
--- a/dbrepo-ui/layouts/default.vue
+++ b/dbrepo-ui/layouts/default.vue
@@ -251,11 +251,14 @@ export default {
         if (this.user) {
           this.userStore.setRouteAccess(newObj.database_id)
         }
-        if (!newObj.table_id) {
-          return
-        }
         /* load table */
-        this.cacheStore.setRouteTable(newObj.database_id, newObj.table_id)
+        if (newObj.table_id) {
+          this.cacheStore.setRouteTable(newObj.database_id, newObj.table_id)
+        }
+        /* load view */
+        if (newObj.view_id) {
+          this.cacheStore.setRouteView(newObj.database_id, newObj.view_id)
+        }
       },
       deep: true,
       immediate: true
diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json
index 102ba3fd0a01f24cde64c070ec6fb51ee5efc68f..53fec12c18ffac36b71817dae7d5f404323453d1 100644
--- a/dbrepo-ui/locales/en-US.json
+++ b/dbrepo-ui/locales/en-US.json
@@ -293,10 +293,6 @@
       },
       "visibility": {
         "title": "Visibility",
-        "open": "Open",
-        "data": "Data is public",
-        "schema": "Schema is public",
-        "closed": "Closed"
       },
       "description": {
         "title": "Description",
@@ -599,16 +595,29 @@
       "internal-name": {
         "title": "Internal Name"
       },
-      "visibility": {
-        "title": "Visibility",
-        "open": "Open",
-        "data": "Data is public",
-        "schema": "Schema is public",
-        "closed": "Closed"
-      },
       "size": {
         "title": "Size"
       },
+      "status": {
+        "title": "Status",
+        "public": "Public",
+        "private": "Private",
+        "draft": "Draft"
+      },
+      "resource": {
+        "data": {
+          "label": "Transparency",
+          "hint": "Required, e.g. can hide the resource so it is hidden.",
+          "enabled": "Visible",
+          "disabled": "Hidden"
+        },
+        "schema": {
+          "label": "Insights",
+          "hint": "Required, e.g. can show metadata on resources.",
+          "enabled": "Visible",
+          "disabled": "Hidden"
+        }
+      },
       "owner": {
         "title": "Owner"
       },
@@ -649,22 +658,14 @@
             "hint": "Required. The internal database name will be lowercase alphanumeric, others will be replaced with _",
             "placeholder": "e.g. my_database, air_quality"
           },
+          "draft": {
+            "label": "Draft",
+            "hint": "Hides the database, only users with access can see it"
+          },
           "engine": {
             "label": "Engine",
             "hint": "Required"
           },
-          "visibility": {
-            "label": "Visibility",
-            "hint": "Required",
-            "public": {
-              "label": "Public",
-              "hint": "Everything is visible to the public, e.g. to publish data used in a open-access paper. You can later update the visibility of data."
-            },
-            "private": {
-              "label": "Private",
-              "hint": "Nothing is visible to anyone without dedicated read-access, e.g. to work on a dataset. You can later update the visibility of data."
-            }
-          },
           "submit": {
             "text": "Create"
           },
@@ -727,11 +728,6 @@
               "hint": "Required",
               "help": "Public = visible to anyone, Private = visible only to designated users"
             },
-            "schema": {
-              "label": "Schema Visibility",
-              "hint": "Required",
-              "help": "Public = visible to anyone, Private = visible only to designated users"
-            },
             "submit": {
               "text": "Modify"
             }
@@ -1357,8 +1353,6 @@
     "database": {
       "recent": "Recent Databases",
       "links": "Important Links",
-      "public": "Public",
-      "private": "Private",
       "current": "Current Data",
       "history": "Historic Data",
       "create": {
diff --git a/dbrepo-ui/pages/database/[database_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/info.vue
index f5c68e328f603d06dffb0a5a8d8a06999297c77a..20ba2dbe16d064493ab0b36f79048cf440a90bc6 100644
--- a/dbrepo-ui/pages/database/[database_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/info.vue
@@ -1,9 +1,7 @@
 <template>
   <div>
-    <DatabaseToolbar
-      v-if="!isError" />
+    <DatabaseToolbar />
     <v-window
-      v-if="!isError"
       v-model="tab">
       <v-window-item value="1">
         <Summary
@@ -59,11 +57,6 @@
                   {{ database.internal_name }}
                 </div>
               </v-list-item>
-              <v-list-item
-                :title="$t('pages.database.visibility.title')"
-                density="compact">
-                {{ databaseVisibility }}
-              </v-list-item>
               <v-list-item
                 :title="$t('pages.database.size.title')"
                 density="compact">
@@ -102,13 +95,6 @@
                   </span>
                 </div>
               </v-list-item>
-              <v-list-item
-                v-if="access"
-                :title="$t('pages.database.connection.title')"
-                density="compact">
-                <pre
-                  class="pb-1">{{ jdbcString }}</pre>
-              </v-list-item>
               <v-list-item
                 v-if="database.contact"
                 :title="$t('pages.database.contact.title')"
@@ -169,16 +155,12 @@
         </v-card>
       </v-window-item>
     </v-window>
-    <JumboBox
-      v-if="isError"
-      :title="errorTitle"
-      :subtitle="errorSubtitle"
-      :text="errorText" />
     <v-breadcrumbs
       :items="items"
       class="pa-0 mt-2" />
   </div>
 </template>
+
 <script>
 import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue'
 import Summary from '@/components/identifier/Summary.vue'
@@ -199,10 +181,15 @@ export default {
   },
   setup () {
     const config = useRuntimeConfig()
+    const userStore = useUserStore()
     const { database_id } = useRoute().params
     const { error, data } = useFetch(`${config.public.api.server}/api/database/${database_id}`, {
       immediate: true,
-      timeout: 90_000
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
     })
     if (data.value) {
       const identifierService = useIdentifierService()
@@ -260,6 +247,9 @@ export default {
     user () {
       return this.userStore.getUser
     },
+    database () {
+      return this.cacheStore.getDatabase
+    },
     roles () {
       return this.userStore.getRoles
     },
@@ -349,13 +339,6 @@ export default {
           return { text: null, class: null }
       }
     },
-    jdbcString () {
-      if (!this.database || !this.user) {
-        return
-      }
-      const flags = this.database.container.ui_additional_flags ? this.database.container.ui_additional_flags : ''
-      return `jdbc:${this.database.container.image.jdbc_method}://${this.database.container.ui_host}:${this.database.container.ui_port}/${this.database.internal_name}${flags} (${this.$t('pages.database.connection.username')}=${this.user.username}, ${this.$t('pages.database.connection.password')}=yourpassword)`
-    },
     databaseExtraInfo () {
       return this.$config.public.database.extra
     },
@@ -367,50 +350,11 @@ export default {
       this.database.tables.forEach((t) => { sum += t.data_length })
       return sizeToHumanLabel(sum)
     },
-    databaseVisibility () {
-      if (!this.database) {
-        return null
-      }
-      if (this.database.is_public && this.database.is_schema_public) {
-        return this.$t('pages.database.visibility.open')
-      }
-      if (!this.database.is_public && !this.database.is_schema_public) {
-        return this.$t('pages.database.visibility.closed')
-      }
-      return this.database.is_public ? this.$t('pages.database.visibility.data') : this.$t('pages.database.visibility.schema')
-    },
     previewImage () {
       if (!this.database) {
         return null
       }
       return this.database.preview_image
-    },
-    isError () {
-      return this.error
-    },
-    errorTitle () {
-      switch (this.error.statusCode) {
-        case 404:
-          return this.$t('error.missing.title')
-        default:
-          return this.$t('error.permission.title')
-      }
-    },
-    errorSubtitle () {
-      switch (this.error.statusCode) {
-        case 404:
-          return 'ERROR_NOT_FOUND'
-        default:
-          return 'ERROR_NOT_AUTHORIZED'
-      }
-    },
-    errorText () {
-      switch (this.error.statusCode) {
-        case 404:
-          return this.$t('error.missing.text')
-        default:
-          return this.$t('error.permission.text')
-      }
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue
index 3ac8f40d645b44449f5607b9f3b031d06fa463d6..1346f7d4e8988acf01330f996a654bcb40744a76 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue
@@ -51,12 +51,30 @@ import QueryResults from '@/components/subset/Results.vue'
 import SubsetToolbar from '@/components/subset/SubsetToolbar.vue'
 import { formatTimestampUTCLabel } from '@/utils'
 import { useCacheStore } from '@/stores/cache'
+import {useUserStore} from "~/stores/user.js";
 
 export default {
   components: {
     SubsetToolbar,
     QueryResults
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id, subset_id } = useRoute().params
+    const { error, data } = useFetch(`${config.public.api.server}/api/database/${database_id}/subset/${subset_id}`, {
+      immediate: true,
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    return {
+      subset: data,
+      error
+    }
+  },
   data () {
     return {
       loadingSubset: false,
@@ -84,9 +102,6 @@ export default {
           disabled: true
         }
       ],
-      subset: {
-        id: this.$route.params.subset_id
-      },
       cacheStore: useCacheStore()
     }
   },
@@ -113,11 +128,11 @@ export default {
       if (this.database.is_public) {
         return true
       }
-      return this.subset.creator.username === this.username
+      return this.subset.owner.username === this.username
     },
   },
   mounted () {
-    this.loadSubset()
+    this.loadResult()
   },
   methods: {
     loadSubset () {
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue
index 764d1b55ffa6b134c07e86290325525caa8919b2..ece7cde1357f4801abd3b7ccd70b6f2067a2b90d 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue
@@ -37,7 +37,9 @@
             v-if="database"
             :title="$t('pages.subset.visibility.title')"
             density="compact">
-            {{ database.is_public ? $t('toolbars.database.public') : $t('toolbars.database.private') }}
+            <ResourceStatus
+              :inline="true"
+              :resource="subset" />
           </v-list-item>
           <v-list-item
             v-if="subset.creator"
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue
index 5f322fa498b4b05607db6422cc7964d98515cb74..9b1e053abe4a0bc07f016b69d1e200bcf67da511 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue
@@ -1,5 +1,6 @@
 <template>
-  <div>
+  <div
+    v-if="table">
     <TableToolbar
       :selection="selection" />
     <v-card
@@ -21,43 +22,32 @@
       rounded="0"
       :title="$t('pages.table.title')">
       <v-card-text>
-        <v-skeleton-loader
-          v-if="!cachedTable"
-          type="list-item-three-line"
-          width="50%" />
         <v-list
-          v-if="cachedTable"
           dense>
           <v-list-item
             :title="$t('pages.table.id.title')">
-            {{ cachedTable.id }}
+            {{ table.id }}
           </v-list-item>
           <v-list-item
             :title="$t('pages.table.name.title')">
-            {{ cachedTable.internal_name }}
+            {{ table.internal_name }}
           </v-list-item>
           <v-list-item
-            :title="$t('pages.table.visibility.title')">
-            {{ databaseVisibility }}
-          </v-list-item>
-          <v-list-item
-            v-if="table"
             :title="$t('pages.table.size.title')">
             {{ sizeToHumanLabel(table.data_length) }}
           </v-list-item>
           <v-list-item
-            v-if="table"
+            v-if="canRead && table.num_rows"
             :title="$t('pages.table.rows.title')">
             {{ table.num_rows }}
           </v-list-item>
           <v-list-item
             :title="$t('pages.table.description.title')">
-            {{ hasDescription ? cachedTable.description : $t('pages.table.description.empty') }}
+            {{ hasDescription ? table.description : $t('pages.table.description.empty') }}
           </v-list-item>
           <v-list-item
             :title="$t('pages.table.owner.title')">
             <UserBadge
-              v-if="table"
               :user="table.owner"
               :other-user="user" />
           </v-list-item>
@@ -128,49 +118,15 @@
         </v-list>
       </v-card-text>
     </v-card>
-    <v-divider />
-    <v-card
-      :title="$t('pages.database.title')"
-      variant="flat">
-      <v-card-text>
-        <v-list dense>
-          <v-list-item
-            v-if="database"
-            :title="$t('pages.database.visibility.title')">
-            {{ database.is_public ? $t('toolbars.database.public') : $t('toolbars.database.private') }}
-          </v-list-item>
-          <v-list-item
-            v-if="database"
-            :title="$t('pages.database.name.title')">
-            <NuxtLink
-              class="text-primary"
-              :to="`/database/${database.id}`">
-              {{ database.internal_name }}
-            </NuxtLink>
-          </v-list-item>
-        </v-list>
-      </v-card-text>
-    </v-card>
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
 </template>
 
-<script setup>
-const config = useRuntimeConfig()
-const { database_id, table_id } = useRoute().params
-const { data } = await useFetch(`${config.public.api.server}/api/database/${database_id}/table/${table_id}`)
-if (data.value) {
-  const identifierService = useIdentifierService()
-  useServerHead(identifierService.tableToServerHead(data.value))
-  useServerSeoMeta(identifierService.tableToServerSeoMeta(data.value))
-}
-</script>
 <script>
 import TableToolbar from '@/components/table/TableToolbar.vue'
 import Select from '@/components/identifier/Select.vue'
 import Summary from '@/components/identifier/Summary.vue'
 import UserBadge from '@/components/user/UserBadge.vue'
-import { formatTimestampUTCLabel, sizeToHumanLabel } from '@/utils'
 import { useUserStore } from '@/stores/user'
 import { useCacheStore } from '@/stores/cache'
 
@@ -181,11 +137,32 @@ export default {
     TableToolbar,
     UserBadge
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id, table_id } = useRoute().params
+    const { error, data } = useFetch(`${config.public.api.server}/api/database/${database_id}/table/${table_id}`, {
+      immediate: true,
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    if (data.value) {
+      const identifierService = useIdentifierService()
+      useServerHead(identifierService.databaseToServerHead(data.value))
+      useServerSeoMeta(identifierService.databaseToServerSeoMeta(data.value))
+    }
+    return {
+      table: data,
+      error
+    }
+  },
   data () {
     return {
       selection: [],
       consumers: [],
-      table: null,
       items: [
         {
           title: this.$t('navigation.databases'),
@@ -228,9 +205,6 @@ export default {
     database () {
       return this.cacheStore.getDatabase
     },
-    cachedTable () {
-      return this.cacheStore.getTable
-    },
     roles () {
       return this.userStore.getRoles
     },
@@ -247,13 +221,13 @@ export default {
       if (!this.table || !this.user || !this.access) {
         return false
       }
-      return (this.access.type === 'write_own' && this.cachedTable.owned_by === this.user.id) || this.access.type === 'write_all'
+      return (this.access.type === 'write_own' && this.table.owned_by === this.user.id) || this.access.type === 'write_all'
     },
     access () {
       return this.userStore.getAccess
     },
     hasDescription () {
-      return this.table && this.cachedTable.description
+      return this.table && this.table.description
     },
     canWriteQueues () {
       if (!this.roles) {
@@ -317,40 +291,6 @@ export default {
       } else if (this.canRead) {
         return this.$t('pages.table.connection.permissions.read')
       }
-    },
-    databaseVisibility () {
-      if (!this.database) {
-        return null
-      }
-      if (this.database.is_public && this.cachedTable.is_schema_public) {
-        return this.$t('pages.table.visibility.open')
-      }
-      if (!this.database.is_public && !this.cachedTable.is_schema_public) {
-        return this.$t('pages.table.visibility.closed')
-      }
-      return this.database.is_public ? this.$t('pages.database.visibility.data') : this.$t('pages.database.visibility.schema')
-    }
-  },
-  mounted () {
-    this.fetchTable()
-  },
-  methods: {
-    fetchTable () {
-      this.loading = true
-      const tableService = useTableService()
-      tableService.findOne(this.$route.params.database_id, this.$route.params.table_id)
-        .then((table) => {
-          this.loading = false
-          this.table = table
-        })
-        .catch(({code}) => {
-          this.loading = false
-          const toast = useToastInstance()
-          toast.error(this.$t(code))
-        })
-        .finally(() => {
-          this.loading = false
-        })
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue
index 29773c24d98801f1c710a87e2cf513a842f562eb..774f11907ef9fa31bc7ea4ecf440f46ae4d7c904 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue
@@ -106,12 +106,12 @@
                     <v-select
                       v-model="tableCreate.is_public"
                       name="public"
-                      :label="$t('pages.database.subpages.create.data.label')"
-                      :hint="$t('pages.database.subpages.create.data.hint')"
+                      :label="$t('pages.database.resource.data.label')"
+                      :hint="$t('pages.database.resource.data.hint')"
                       persistent-hint
                       :variant="inputVariant"
-                      :items="visibilityOptions"
-                      item-title="name"
+                      :items="dataOptions"
+                      item-title="title"
                       item-value="value"
                       :rules="[v => v !== null || $t('validation.required')]"
                       required>
@@ -122,12 +122,12 @@
                     <v-select
                       v-model="tableCreate.is_schema_public"
                       name="schema-public"
-                      :label="$t('pages.database.subpages.create.schema.label')"
-                      :hint="$t('pages.database.subpages.create.schema.hint')"
+                      :label="$t('pages.database.resource.schema.label')"
+                      :hint="$t('pages.database.resource.schema.hint')"
                       persistent-hint
                       :variant="inputVariant"
-                      :items="visibilityOptions"
-                      item-title="name"
+                      :items="schemaOptions"
+                      item-title="title"
                       item-value="value"
                       :rules="[v => v !== null || $t('validation.required')]"
                       required>
@@ -235,15 +235,13 @@ export default {
       loadingImport: false,
       fileModel: null,
       rowCount: null,
-      visibilityOptions: [
-        {
-          name: this.$t('toolbars.database.public'),
-          value: true
-        },
-        {
-          name: this.$t('toolbars.database.private'),
-          value: false
-        }
+      dataOptions: [
+        { title: this.$t('pages.database.resource.data.enabled'), value: true },
+        { title: this.$t('pages.database.resource.data.disabled'), value: false },
+      ],
+      schemaOptions: [
+        { title: this.$t('pages.database.resource.schema.enabled'), value: true },
+        { title: this.$t('pages.database.resource.schema.disabled'), value: false },
       ],
       file: {
         filename: null,
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue
index 9751fce5b7843b27043ea9e103003a01a3dcfbb4..935e26314f91054712ebcb1a9375428a077b842f 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue
@@ -2,7 +2,7 @@
   <div
     v-if="canReadData">
     <ViewToolbar
-      v-if="cachedView" />
+      v-if="view" />
     <v-toolbar
       color="secondary"
       :title="$t('toolbars.database.current')"
@@ -80,11 +80,8 @@ export default {
     database () {
       return this.cacheStore.getDatabase
     },
-    cachedView () {
-      if (!this.database) {
-        return null
-      }
-      return this.database.views.filter(v => v.id === Number(this.$route.params.view_id))[0]
+    view () {
+      return this.cacheStore.getView
     },
     access () {
       return this.userStore.getAccess
@@ -96,10 +93,10 @@ export default {
       return this.access.type === 'read' ||  this.access.type === 'write_own' ||  this.access.type === 'write_all'
     },
     canReadData () {
-      if (!this.cachedView) {
+      if (!this.view) {
         return false
       }
-      if (this.cachedView.is_public) {
+      if (this.view.is_public) {
         return true
       }
       if (!this.user) {
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue
index 364516f45f29209c56748add8bff0da310de7d75..f7b025e98298fd37181d551822e9a4b18b849e16 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/info.vue
@@ -4,7 +4,7 @@
     <v-window
       v-model="tab">
       <v-window-item
-        v-if="cachedView">
+        v-if="view">
         <v-card variant="flat">
           <Summary
             v-if="hasIdentifier"
@@ -23,15 +23,15 @@
           variant="flat">
           <v-card-text>
             <v-list
-              v-if="cachedView"
+              v-if="view"
               dense>
               <v-list-item
                 :title="$t('pages.view.name.title')">
-                {{ cachedView.internal_name }}
+                {{ view.internal_name }}
               </v-list-item>
               <v-list-item
                 :title="$t('pages.view.query.title')">
-                <pre>{{ cachedView.query }}</pre>
+                <pre>{{ view.query }}</pre>
               </v-list-item>
               <v-list-item
                 :title="$t('pages.view.owner.title')">
@@ -45,34 +45,9 @@
                   width="200" />
               </v-list-item>
               <v-list-item
-                v-if="cachedView.created"
+                v-if="view.created"
                 :title="$t('pages.view.creation.title')">
-                {{ formatUTC(cachedView.created) }}
-              </v-list-item>
-              <v-list-item
-                :title="$t('pages.view.visibility.title')">
-                {{ viewVisibility }}
-              </v-list-item>
-            </v-list>
-          </v-card-text>
-        </v-card>
-        <v-divider />
-        <v-card
-          :title="$t('pages.database.title')"
-          variant="flat">
-          <v-card-text>
-            <v-list dense>
-              <v-list-item
-                :title="$t('pages.database.visibility.title')">
-                {{ database.is_public ? $t('toolbars.database.public') : $t('toolbars.database.private') }}
-              </v-list-item>
-              <v-list-item
-                :title="$t('pages.database.name.title')">
-                <NuxtLink
-                  class="text-primary"
-                  :to="`/database/${database.id}`">
-                  {{ database.internal_name }}
-                </NuxtLink>
+                {{ formatUTC(view.created) }}
               </v-list-item>
             </v-list>
           </v-card-text>
@@ -83,16 +58,6 @@
   </div>
 </template>
 
-<script setup>
-const config = useRuntimeConfig()
-const { database_id, view_id } = useRoute().params
-const { data } = await useFetch(`${config.public.api.server}/api/database/${database_id}/view/${view_id}`)
-if (data.value) {
-  const identifierService = useIdentifierService()
-  useServerHead(identifierService.viewToServerHead(data.value))
-  useServerSeoMeta(identifierService.viewToServerSeoMeta(data.value))
-}
-</script>
 <script>
 import ViewToolbar from '@/components/view/ViewToolbar.vue'
 import Summary from '@/components/identifier/Summary.vue'
@@ -109,11 +74,32 @@ export default {
     ViewToolbar,
     UserBadge
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id, view_id } = useRoute().params
+    const { error, data } = useFetch(`${config.public.api.server}/api/database/${database_id}/view/${view_id}`, {
+      immediate: true,
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    if (data.value) {
+      const identifierService = useIdentifierService()
+      useServerHead(identifierService.viewToServerHead(data.value))
+      useServerSeoMeta(identifierService.viewToServerSeoMeta(data.value))
+    }
+    return {
+      view: data,
+      error
+    }
+  },
   data () {
     return {
       tab: 0,
       loadingView: false,
-      view: null,
       items: [
         {
           title: this.$t('navigation.databases'),
@@ -152,15 +138,12 @@ export default {
     database () {
       return this.cacheStore.getDatabase
     },
-    cachedView () {
-      if (!this.database) {
-        return null
-      }
-      return this.database.views.filter(v => v.id === Number(this.$route.params.view_id))[0]
-    },
     access () {
       return this.userStore.getAccess
     },
+    view () {
+      return this.cacheStore.getView
+    },
     identifiers () {
       if (!this.view) {
         return []
@@ -203,43 +186,11 @@ export default {
       }
       const userService = useUserService()
       return userService.userToFullName(this.view.creator)
-    },
-    viewVisibility () {
-      if (!this.cachedView) {
-        return null
-      }
-      if (this.cachedView.is_public && this.cachedView.is_schema_public) {
-        return this.$t('pages.database.visibility.open')
-      }
-      if (!this.cachedView.is_public && !this.cachedView.is_schema_public) {
-        return this.$t('pages.database.visibility.closed')
-      }
-      return this.cachedView.is_public ? this.$t('pages.database.visibility.data') : this.$t('pages.database.visibility.schema')
     }
   },
-  mounted () {
-    this.fetchView()
-  },
   methods: {
     formatUTC (timestamp) {
       return formatTimestampUTCLabel(timestamp)
-    },
-    fetchView () {
-      this.loadingView = true
-      const viewService = useViewService()
-      viewService.findOne(this.$route.params.database_id, this.$route.params.view_id)
-        .then((view) => {
-          this.view = view
-          this.loadingView = false
-        })
-        .catch(({code}) => {
-          this.loadingView = false
-          const toast = useToastInstance()
-          toast.error(this.$t(code))
-        })
-        .finally(() => {
-          this.loadingView = false
-        })
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue
index b3352010b26653c7b3cb766734b6eb0a2e64dc44..7426d50468ee42e4b8459a123286a83c27f91a57 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue
@@ -119,23 +119,20 @@ export default {
       }
       return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own'
     },
-    cachedView () {
-      if (!this.database) {
-        return null
-      }
-      return this.database.views.filter(v => v.id === Number(this.$route.params.view_id))[0]
+    view () {
+      return this.cacheStore.getView
     },
     canViewSchema () {
-      if (!this.cachedView) {
+      if (!this.view) {
         return false
       }
-      if (this.cachedView.is_schema_public) {
+      if (this.view.is_schema_public) {
         return true
       }
       if (!this.user) {
         return false
       }
-      return this.hasReadAccess || this.cachedView.owned_by === this.user.id || this.database.owner.id === this.user.id
+      return this.hasReadAccess || this.view.owner.id === this.user.id || this.database.owner.id === this.user.id
     },
     roles () {
       return this.userStore.getRoles
diff --git a/dbrepo-ui/pages/index.vue b/dbrepo-ui/pages/index.vue
index 037f5b9410f316cce4e24ec2a9d45c6d6615a741..6e71854c0dc72ce8f68f75772befc72fcbbf3279 100644
--- a/dbrepo-ui/pages/index.vue
+++ b/dbrepo-ui/pages/index.vue
@@ -7,7 +7,7 @@
       <v-spacer />
       <v-btn
         v-if="canCreateDatabase"
-        class="mr-4"
+        class="mr-2"
         prepend-icon="mdi-plus"
         variant="flat"
         :text="$t('toolbars.database.create.text')"
diff --git a/dbrepo-ui/pages/search.vue b/dbrepo-ui/pages/search.vue
index ebe16ecec1c159de639a6c735b8f806dda1da21f..c437dff5564557a1e29f1c05289e7e1a7d42ad0b 100644
--- a/dbrepo-ui/pages/search.vue
+++ b/dbrepo-ui/pages/search.vue
@@ -8,7 +8,7 @@
       <v-spacer />
       <v-btn
         v-if="canCreateDatabase"
-        class="mr-4"
+        class="mr-2"
         prepend-icon="mdi-plus"
         color="secondary"
         variant="flat"
diff --git a/dbrepo-ui/stores/cache.js b/dbrepo-ui/stores/cache.js
index bbe4f966c2577724e6c9a8f08f5a1143794b7497..b19658d08d2bc4531e329562d48e2c894e8a6061 100644
--- a/dbrepo-ui/stores/cache.js
+++ b/dbrepo-ui/stores/cache.js
@@ -6,6 +6,7 @@ export const useCacheStore = defineStore('cache', {
     return {
       database: null,
       table: null,
+      view: null,
       ontologies: [],
       messages: [],
       uploadProgress: null
@@ -14,6 +15,7 @@ export const useCacheStore = defineStore('cache', {
   getters: {
     getDatabase: (state) => state.database,
     getTable: (state) => state.table,
+    getView: (state) => state.view,
     getOntologies: (state) => state.ontologies,
     getMessages: (state) => state.messages,
     getUploadProgress: (state) => state.uploadProgress,
@@ -25,6 +27,9 @@ export const useCacheStore = defineStore('cache', {
     setTable (table) {
       this.table = table
     },
+    setView (view) {
+      this.view = view
+    },
     setOntologies (ontologies) {
       this.ontologies = ontologies
     },
@@ -88,6 +93,19 @@ export const useCacheStore = defineStore('cache', {
         .catch((error) => {
           console.error('Failed to set route table', error)
         })
+    },
+    setRouteView (databaseId, view_id) {
+      if (!databaseId || !view_id) {
+        this.view = null
+        console.error('Cannot set route view: missing view id', databaseId, 'or view id', view_id)
+        return
+      }
+      const viewService = useViewService()
+      viewService.findOne(databaseId, view_id)
+        .then(view => this.view = view)
+        .catch((error) => {
+          console.error('Failed to set route view', error)
+        })
     }
   },
 })
diff --git a/dbrepo-ui/utils/index.ts b/dbrepo-ui/utils/index.ts
index 4f30d8953405445c152dee0b6afd4dc6b44cba3a..3946a709387901f2767d054b2d60c3a8c44340f5 100644
--- a/dbrepo-ui/utils/index.ts
+++ b/dbrepo-ui/utils/index.ts
@@ -1,4 +1,4 @@
-import { format } from 'date-fns'
+import {format} from 'date-fns'
 import moment from 'moment'
 import type {AxiosError} from 'axios'