From 7019a7b98bf5250809268a97d12669ca5813013c Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Wed, 29 Jan 2025 14:33:52 +0100
Subject: [PATCH] Fixed UI

Signed-off-by: Martin Weise <martin.weise@tuwien.ac.at>
---
 dbrepo-data-service/Dockerfile                |   4 +-
 dbrepo-metadata-service/Dockerfile            |   4 +-
 .../api/identifier/IdentifierBriefDto.java    |   3 +
 .../java/at/tuwien/mapper/MetadataMapper.java |   5 +-
 .../main/java/at/tuwien/config/MvcConfig.java |  16 +
 .../IdentifierStatusTypeDtoConverter.java     |  14 +
 ...r.java => IdentifierTypeDtoConverter.java} |   2 +-
 .../tuwien/endpoints/IdentifierEndpoint.java  |  67 ++--
 .../IdentifierTypeConverterUnitTest.java      |   2 +-
 .../endpoints/IdentifierEndpointUnitTest.java | 260 ++++++++++----
 .../tuwien/mvc/PrometheusEndpointMvcTest.java |   3 +-
 dbrepo-ui/components/ResourceStatus.vue       |  17 +-
 dbrepo-ui/components/identifier/Banner.vue    |   2 +-
 dbrepo-ui/components/identifier/Persist.vue   |  19 +-
 dbrepo-ui/components/identifier/Select.vue    |  23 +-
 dbrepo-ui/components/subset/Builder.vue       |   9 +-
 dbrepo-ui/components/subset/SubsetList.vue    |  25 +-
 dbrepo-ui/components/subset/SubsetToolbar.vue |   4 +-
 dbrepo-ui/components/table/TableList.vue      |  10 -
 dbrepo-ui/components/view/ViewList.vue        |  10 -
 dbrepo-ui/composables/identifier-service.ts   | 340 +++---------------
 dbrepo-ui/dto/index.ts                        |  75 +++-
 dbrepo-ui/layouts/default.vue                 |  32 +-
 dbrepo-ui/locales/en-US.json                  |   4 +-
 .../pages/database/[database_id]/info.vue     |  77 ++--
 .../persist/[identifier_id]/index.vue         |  23 +-
 .../database/[database_id]/persist/index.vue  |   8 +-
 .../pages/database/[database_id]/settings.vue |   2 +-
 .../[database_id]/subset/[subset_id]/data.vue |  22 +-
 .../[database_id]/subset/[subset_id]/info.vue |  64 ++--
 .../persist/[identifier_id]/index.vue         |  26 +-
 .../subset/[subset_id]/persist/index.vue      |  43 +--
 .../database/[database_id]/subset/create.vue  |   3 +
 .../[database_id]/table/[table_id]/import.vue |   3 +
 .../[database_id]/table/[table_id]/info.vue   |  55 ++-
 .../persist/[identifier_id]/index.vue         |  26 +-
 .../table/[table_id]/persist/index.vue        |  14 +-
 .../[database_id]/table/[table_id]/schema.vue |   2 -
 .../table/[table_id]/settings.vue             |   9 +-
 .../[database_id]/table/create/dataset.vue    |   3 -
 .../[database_id]/table/create/schema.vue     |   3 -
 .../[database_id]/view/[view_id]/data.vue     |   6 -
 .../[database_id]/view/[view_id]/info.vue     |  59 ++-
 .../persist/[identifier_id]/index.vue         |  26 +-
 .../view/[view_id]/persist/index.vue          |  11 +-
 .../database/[database_id]/view/create.vue    |   8 +-
 dbrepo-ui/stores/cache.js                     |   5 +
 docker-compose.yml                            |   2 +-
 make/build.mk                                 |   4 +
 make/dev.mk                                   |   2 +-
 50 files changed, 724 insertions(+), 732 deletions(-)
 create mode 100644 dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java
 create mode 100644 dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java
 rename dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/{IdentifierTypeConverter.java => IdentifierTypeDtoConverter.java} (79%)

diff --git a/dbrepo-data-service/Dockerfile b/dbrepo-data-service/Dockerfile
index 4b45e94290..9edf1375fb 100644
--- a/dbrepo-data-service/Dockerfile
+++ b/dbrepo-data-service/Dockerfile
@@ -8,7 +8,7 @@ LABEL org.opencontainers.image.authors="martin.weise@tuwien.ac.at"
 
 COPY ./pom.xml ./
 
-RUN mvn -fn -B -q dependency:go-offline
+RUN mvn -fn dependency:go-offline
 
 COPY --from=dependency /root/.m2/repository/at/tuwien /root/.m2/repository/at/tuwien
 
@@ -18,7 +18,7 @@ COPY ./rest-service ./rest-service
 COPY ./services ./services
 
 # Make sure it compiles
-RUN mvn -fn -B -q clean package -DskipTests
+RUN mvn -fn clean package -DskipTests
 
 ###### THIRD STAGE ######
 FROM amazoncorretto:17-alpine3.19 AS runtime
diff --git a/dbrepo-metadata-service/Dockerfile b/dbrepo-metadata-service/Dockerfile
index ddc20cb420..fa92b799ee 100644
--- a/dbrepo-metadata-service/Dockerfile
+++ b/dbrepo-metadata-service/Dockerfile
@@ -12,7 +12,7 @@ COPY ./rest-service/pom.xml ./rest-service/
 COPY ./services/pom.xml ./services/
 COPY ./test/pom.xml ./test/
 
-RUN mvn -fn -B dependency:go-offline
+RUN mvn dependency:go-offline
 
 COPY ./api ./api
 COPY ./entities ./entities
@@ -24,7 +24,7 @@ COPY ./services ./services
 COPY ./test ./test
 
 # Make sure it compiles
-RUN mvn -fn -B clean install -DskipTests
+RUN mvn clean install -DskipTests
 
 ###### SECOND STAGE ######
 FROM amazoncorretto:17-alpine3.19 AS runtime
diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java
index 97f3502674..f94edc2cf7 100644
--- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java
+++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java
@@ -50,6 +50,9 @@ public class IdentifierBriefDto {
     @NotNull
     private List<IdentifierTitleDto> titles;
 
+    @NotNull
+    private List<IdentifierDescriptionDto> descriptions;
+
     @Schema(example = "10.1038/nphys1170")
     private String doi;
 
diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java
index 1d9bc28c6a..97c0d0b903 100644
--- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java
+++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java
@@ -393,6 +393,8 @@ public interface MetadataMapper {
 
     IdentifierType identifierTypeDtoToIdentifierType(IdentifierTypeDto data);
 
+    IdentifierStatusType identifierStatusTypeDtoToIdentifierStatusType(IdentifierStatusTypeDto data);
+
     default String identifierToLocationUrl(String baseUrl, Identifier data) {
         if (data.getType().equals(IdentifierType.SUBSET)) {
             return baseUrl + "/database/" + data.getDatabase().getId() + "/subset/" + data.getQueryId() + "/info?pid=" + data.getId();
@@ -823,7 +825,8 @@ public interface MetadataMapper {
     }
 
     @Mappings({
-            @Mapping(target = "database.views", ignore = true)
+            @Mapping(target = "database.views", ignore = true),
+            @Mapping(target = "database.tables", ignore = true)
     })
     ViewDto viewToViewDto(View data);
 
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java
new file mode 100644
index 0000000000..6bdb809731
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/config/MvcConfig.java
@@ -0,0 +1,16 @@
+package at.tuwien.config;
+
+import at.tuwien.converters.IdentifierStatusTypeDtoConverter;
+import at.tuwien.converters.IdentifierTypeDtoConverter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class MvcConfig implements WebMvcConfigurer {
+    @Override
+    public void addFormatters(FormatterRegistry registry) {
+        registry.addConverter(new IdentifierStatusTypeDtoConverter());
+        registry.addConverter(new IdentifierTypeDtoConverter());
+    }
+}
\ No newline at end of file
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java
new file mode 100644
index 0000000000..96e67f63d2
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierStatusTypeDtoConverter.java
@@ -0,0 +1,14 @@
+package at.tuwien.converters;
+
+import at.tuwien.api.identifier.IdentifierStatusTypeDto;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class IdentifierStatusTypeDtoConverter implements Converter<String, IdentifierStatusTypeDto> {
+
+    @Override
+    public IdentifierStatusTypeDto convert(String source) {
+        return IdentifierStatusTypeDto.valueOf(source.toUpperCase());
+    }
+}
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeConverter.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeDtoConverter.java
similarity index 79%
rename from dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeConverter.java
rename to dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeDtoConverter.java
index b3f52c4377..61e169604f 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeConverter.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/converters/IdentifierTypeDtoConverter.java
@@ -5,7 +5,7 @@ import org.springframework.core.convert.converter.Converter;
 import org.springframework.stereotype.Component;
 
 @Component
-public class IdentifierTypeConverter implements Converter<String, IdentifierTypeDto> {
+public class IdentifierTypeDtoConverter implements Converter<String, IdentifierTypeDto> {
 
     @Override
     public IdentifierTypeDto convert(String source) {
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
index b70516fa66..b3d699086e 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
@@ -73,7 +73,7 @@ public class IdentifierEndpoint extends AbstractEndpoint {
         this.identifierService = identifierService;
     }
 
-    @GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, "application/ld+json"})
+    @GetMapping
     @Transactional(readOnly = true)
     @Observed(name = "dbrepo_identifier_list")
     @Operation(summary = "List identifiers",
@@ -87,48 +87,41 @@ public class IdentifierEndpoint extends AbstractEndpoint {
                             @Content(mediaType = "application/ld+json",
                                     array = @ArraySchema(schema = @Schema(implementation = LdDatasetDto.class)))
                     }),
-            @ApiResponse(responseCode = "406",
-                    description = "Identifier could not be exported, the requested style is not known",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
     })
-    public ResponseEntity<?> findAll(@Valid @RequestParam(value = "dbid", required = false) Long dbid,
+    public ResponseEntity<?> findAll(@Valid @RequestParam(value = "type", required = false) IdentifierTypeDto type,
+                                     @Valid @RequestParam(value = "status", required = false) IdentifierStatusTypeDto status,
+                                     @Valid @RequestParam(value = "dbid", required = false) Long dbid,
                                      @Valid @RequestParam(value = "qid", required = false) Long qid,
                                      @Valid @RequestParam(value = "vid", required = false) Long vid,
                                      @Valid @RequestParam(value = "tid", required = false) Long tid,
-                                     @RequestHeader(HttpHeaders.ACCEPT) String accept)
-            throws FormatNotAvailableException {
-        log.debug("endpoint find identifiers, dbid={}, qid={}, vid={}, tid={}, accept={}", dbid, qid, vid, tid, accept);
+                                     @RequestHeader(HttpHeaders.ACCEPT) String accept,
+                                     Principal principal) {
+        log.debug("endpoint find identifiers, type={}, status={}, dbid={}, qid={}, vid={}, tid={}, accept={}", type,
+                status, dbid, qid, vid, tid, accept);
         final List<Identifier> identifiers = identifierService.findAll()
                 .stream()
+                .filter(i -> !Objects.nonNull(type) || metadataMapper.identifierTypeDtoToIdentifierType(type).equals(i.getType()))
+                .filter(i -> !Objects.nonNull(status) || metadataMapper.identifierStatusTypeDtoToIdentifierStatusType(status).equals(i.getStatus()))
                 .filter(i -> !Objects.nonNull(dbid) || dbid.equals(i.getDatabase().getId()))
                 .filter(i -> !Objects.nonNull(qid) || qid.equals(i.getQueryId()))
                 .filter(i -> !Objects.nonNull(vid) || vid.equals(i.getViewId()))
                 .filter(i -> !Objects.nonNull(tid) || tid.equals(i.getTableId()))
+                .filter(i -> principal != null && i.getStatus().equals(IdentifierStatusType.DRAFT) ? i.getOwnedBy().equals(getId(principal)) : i.getStatus().equals(IdentifierStatusType.PUBLISHED))
                 .toList();
         if (identifiers.isEmpty()) {
             return ResponseEntity.ok(List.of());
         }
         log.trace("found persistent identifiers {}", identifiers);
-        return switch (accept) {
-            case "application/json" -> {
-                log.trace("accept header matches json");
-                yield ResponseEntity.ok(identifiers.stream()
-                        .map(metadataMapper::identifierToIdentifierBriefDto)
-                        .toList());
-            }
-            case "application/ld+json" -> {
-                log.trace("accept header matches json-ld");
-                yield ResponseEntity.ok(identifiers.stream()
-                        .map(i -> metadataMapper.identifierToLdDatasetDto(i, endpointConfig.getWebsiteUrl()))
-                        .toList());
-            }
-            default -> {
-                log.error("accept header {} is not supported", accept);
-                throw new FormatNotAvailableException("Must provide either application/json or application/ld+json headers");
-            }
-        };
+        if (accept.equals("application/ld+json")) {
+            log.trace("accept header matches json-ld");
+            return ResponseEntity.ok(identifiers.stream()
+                    .map(i -> metadataMapper.identifierToLdDatasetDto(i, endpointConfig.getWebsiteUrl()))
+                    .toList());
+        }
+        log.trace("default to json");
+        return ResponseEntity.ok(identifiers.stream()
+                .map(metadataMapper::identifierToIdentifierBriefDto)
+                .toList());
     }
 
     @GetMapping(value = "/{identifierId}", produces = {MediaType.APPLICATION_JSON_VALUE, "application/ld+json",
@@ -156,6 +149,11 @@ public class IdentifierEndpoint extends AbstractEndpoint {
                     content = {@Content(
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "403",
+                    description = "Not allowed to view identifier",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
             @ApiResponse(responseCode = "404",
                     description = "Identifier could not be found",
                     content = {@Content(
@@ -188,14 +186,23 @@ public class IdentifierEndpoint extends AbstractEndpoint {
                             schema = @Schema(implementation = ApiErrorDto.class))}),
     })
     public ResponseEntity<?> find(@Valid @PathVariable("identifierId") Long identifierId,
-                                  @RequestHeader(HttpHeaders.ACCEPT) String accept) throws IdentifierNotFoundException,
+                                  @RequestHeader(HttpHeaders.ACCEPT) String accept,
+                                  Principal principal) throws IdentifierNotFoundException,
             DataServiceException, DataServiceConnectionException, MalformedException, FormatNotAvailableException,
-            QueryNotFoundException {
+            QueryNotFoundException, NotAllowedException {
         log.debug("endpoint find identifier, identifierId={}, accept={}", identifierId, accept);
         if (accept == null) {
             accept = "";
         }
         final Identifier identifier = identifierService.find(identifierId);
+        if (identifier.getStatus().equals(IdentifierStatusType.DRAFT)) {
+            if (principal == null) {
+                throw new NotAllowedException("Draft identifier: authentication required");
+            }
+            if (!identifier.getOwnedBy().equals(getId(principal))) {
+                throw new NotAllowedException("Draft identifier: not authorized");
+            }
+        }
         log.info("Found persistent identifier with id: {}", identifier.getId());
         switch (accept) {
             case "application/json":
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java
index 7215e5db91..b61a39dc0e 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/converters/IdentifierTypeConverterUnitTest.java
@@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.*;
 public class IdentifierTypeConverterUnitTest extends AbstractUnitTest {
 
     @Autowired
-    private IdentifierTypeConverter identifierTypeConverter;
+    private IdentifierTypeDtoConverter identifierTypeConverter;
 
     @BeforeEach
     public void beforeEach() {
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java
index 74a252c5a6..3b06b974d8 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java
@@ -122,12 +122,49 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
         );
     }
 
+    public static Stream<Arguments> findAll_anonymousFilterDatabase_parameters() {
+        return Stream.of(
+                Arguments.arguments("dbid", DATABASE_1_ID, null, null, null, 1),
+                Arguments.arguments("qid", DATABASE_1_ID, QUERY_1_ID, null, null, 0),
+                Arguments.arguments("vid", DATABASE_1_ID, null, VIEW_1_ID, null, 0),
+                Arguments.arguments("tid", DATABASE_1_ID, null, null, TABLE_1_ID, 0)
+        );
+    }
+
     public static Stream<Arguments> findAll_filterDatabase_parameters() {
         return Stream.of(
-                Arguments.arguments("dbid", DATABASE_1_ID, null, null, null, 4),
-                Arguments.arguments("qid", DATABASE_1_ID, QUERY_1_ID, null, null, 1),
-                Arguments.arguments("vid", DATABASE_1_ID, null, VIEW_1_ID, null, 1),
-                Arguments.arguments("tid", DATABASE_1_ID, null, null, TABLE_1_ID, 1)
+                Arguments.arguments("database_dbid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("database_qid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("database_vid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("database_tid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("subset_dbid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("subset_qid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, QUERY_1_ID, null, null, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("subset_vid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("subset_tid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("view_dbid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("view_qid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("view_vid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, VIEW_1_ID, null, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("view_tid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("table_dbid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, null, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("table_qid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("table_vid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, USER_1_PRINCIPAL),
+                Arguments.arguments("table_tid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 1, USER_1_PRINCIPAL),
+                Arguments.arguments("anon_database_dbid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, null, 1, null),
+                Arguments.arguments("anon_database_qid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, null),
+                Arguments.arguments("anon_database_vid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, null),
+                Arguments.arguments("anon_database_tid", IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, null),
+                Arguments.arguments("anon_subset_dbid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, null, 1, null),
+                Arguments.arguments("anon_subset_qid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, QUERY_1_ID, null, null, 1, null),
+                Arguments.arguments("anon_subset_vid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, null),
+                Arguments.arguments("anon_subset_tid", IdentifierTypeDto.SUBSET, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, null),
+                Arguments.arguments("anon_view_dbid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, null, 1, null),
+                Arguments.arguments("anon_view_qid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, null),
+                Arguments.arguments("anon_view_vid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, VIEW_1_ID, null, 1, null),
+                Arguments.arguments("anon_view_tid", IdentifierTypeDto.VIEW, null, DATABASE_1_ID, null, null, TABLE_1_ID, 0, null),
+                Arguments.arguments("anon_table_dbid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, null, 1, null),
+                Arguments.arguments("anon_table_qid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, QUERY_1_ID, null, null, 0, null),
+                Arguments.arguments("anon_table_vid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, VIEW_1_ID, null, 0, null),
+                Arguments.arguments("anon_table_tid", IdentifierTypeDto.TABLE, null, DATABASE_1_ID, null, null, TABLE_1_ID, 1, null)
         );
     }
 
@@ -146,14 +183,14 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void findAll_empty_succeeds() throws FormatNotAvailableException {
+    public void findAll_empty_succeeds() {
 
         /* mock */
         when(identifierService.findAll())
                 .thenReturn(List.of());
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, "application/json");
+        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "application/json", null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         assertNotNull(response.getBody());
         final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody();
@@ -161,12 +198,69 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
         assertEquals(0, identifiers.size());
     }
 
+    @ParameterizedTest
+    @MethodSource("findAll_anonymousFilterDatabase_parameters")
+    @WithAnonymousUser
+    public void findAll_anonymousFilterDatabase_succeeds(String name, Long databaseId, Long queryId, Long viewId, Long tableId,
+                                                         Integer expectedSize) throws ViewNotFoundException,
+            TableNotFoundException, DatabaseNotFoundException {
+
+        /* mock */
+        when(identifierService.findAll())
+                .thenReturn(List.of(IDENTIFIER_1, IDENTIFIER_2, IDENTIFIER_3, IDENTIFIER_4, IDENTIFIER_5, IDENTIFIER_6, IDENTIFIER_7));
+        if (viewId != null) {
+            when(viewService.findById(DATABASE_1, VIEW_1_ID))
+                    .thenReturn(VIEW_1);
+        }
+        if (tableId != null) {
+            when(tableService.findById(DATABASE_1, TABLE_1_ID))
+                    .thenReturn(TABLE_1);
+        }
+
+        /* test */
+        final ResponseEntity<?> response = identifierEndpoint.findAll(IdentifierTypeDto.DATABASE, null, databaseId, queryId, viewId, tableId, "application/json", null);
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        assertNotNull(response.getBody());
+        final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody();
+        assertNotNull(identifiers);
+        assertEquals(expectedSize, identifiers.size());
+    }
+
+    @ParameterizedTest
+    @MethodSource("findAll_anonymousFilterDatabase_parameters")
+    @WithAnonymousUser
+    public void findAll_wrongPrincipalFilterDatabase_succeeds(String name, Long databaseId, Long queryId, Long viewId,
+                                                              Long tableId, Integer expectedSize)
+            throws ViewNotFoundException, TableNotFoundException, DatabaseNotFoundException {
+
+        /* mock */
+        when(identifierService.findAll())
+                .thenReturn(List.of(IDENTIFIER_1, IDENTIFIER_2, IDENTIFIER_3, IDENTIFIER_4, IDENTIFIER_5, IDENTIFIER_6, IDENTIFIER_7));
+        if (viewId != null) {
+            when(viewService.findById(DATABASE_1, VIEW_1_ID))
+                    .thenReturn(VIEW_1);
+        }
+        if (tableId != null) {
+            when(tableService.findById(DATABASE_1, TABLE_1_ID))
+                    .thenReturn(TABLE_1);
+        }
+
+        /* test */
+        final ResponseEntity<?> response = identifierEndpoint.findAll(IdentifierTypeDto.DATABASE, null, databaseId, queryId, viewId, tableId, "application/json", USER_2_PRINCIPAL);
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        assertNotNull(response.getBody());
+        final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody();
+        assertNotNull(identifiers);
+        assertEquals(expectedSize, identifiers.size());
+    }
+
     @ParameterizedTest
     @MethodSource("findAll_filterDatabase_parameters")
     @WithAnonymousUser
-    public void findAll_filterDatabase_succeeds(String name, Long databaseId, Long queryId, Long viewId, Long tableId,
-                                                Integer expectedSize) throws FormatNotAvailableException,
-            ViewNotFoundException, TableNotFoundException, DatabaseNotFoundException {
+    public void findAll_filterDatabase_succeeds(String name, IdentifierTypeDto type, IdentifierStatusTypeDto status,
+                                                Long databaseId, Long queryId, Long viewId, Long tableId,
+                                                Integer expectedSize, Principal principal) throws ViewNotFoundException,
+            TableNotFoundException, DatabaseNotFoundException {
 
         /* mock */
         when(identifierService.findAll())
@@ -181,7 +275,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
         }
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.findAll(databaseId, queryId, viewId, tableId, "application/json");
+        final ResponseEntity<?> response = identifierEndpoint.findAll(type, status, databaseId, queryId, viewId, tableId, "application/json", principal);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         assertNotNull(response.getBody());
         final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody();
@@ -191,14 +285,14 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void findAll_json_succeeds() throws FormatNotAvailableException {
+    public void findAll_json_succeeds() {
 
         /* mock */
         when(identifierService.findAll())
                 .thenReturn(List.of(IDENTIFIER_1));
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, "application/json");
+        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "application/json", null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         assertNotNull(response.getBody());
         final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody();
@@ -208,14 +302,14 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void findAll_jsonLd_succeeds() throws FormatNotAvailableException {
+    public void findAll_jsonLd_succeeds() {
 
         /* mock */
         when(identifierService.findAll())
                 .thenReturn(List.of(IDENTIFIER_1));
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, "application/ld+json");
+        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "application/ld+json", null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         assertNotNull(response.getBody());
         final List<LdDatasetDto> identifiers = (List<LdDatasetDto>) response.getBody();
@@ -225,23 +319,26 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void findAll_format_fails() {
+    public void findAll_format_succeeds() {
 
         /* mock */
         when(identifierService.findAll())
                 .thenReturn(List.of(IDENTIFIER_1));
 
         /* test */
-        assertThrows(FormatNotAvailableException.class, () -> {
-            identifierEndpoint.findAll(null, null, null, null, "text/csv");
-        });
+        final ResponseEntity<?> response = identifierEndpoint.findAll(null, null, null, null, null, null, "text/html", null);
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        assertNotNull(response.getBody());
+        final List<IdentifierBriefDto> identifiers = (List<IdentifierBriefDto>) response.getBody();
+        assertNotNull(identifiers);
+        assertEquals(1, identifiers.size());
     }
 
     @Test
-    @WithAnonymousUser
+    @WithMockUser(username = USER_4_USERNAME)
     public void find_json0_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "application/json";
         final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata0.json"), StandardCharsets.UTF_8), IdentifierDto.class);
 
@@ -250,7 +347,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_7);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final IdentifierDto body = (IdentifierDto) response.getBody();
         assertNotNull(body);
@@ -271,7 +368,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_json1_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "application/json";
         final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata1.json"), StandardCharsets.UTF_8), IdentifierDto.class);
 
@@ -280,7 +377,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final IdentifierDto body = (IdentifierDto) response.getBody();
         assertNotNull(body);
@@ -321,7 +418,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_csv_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/csv";
         final InputStreamResource compare = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv")));
         final InputStreamResource mock = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv")));
@@ -333,7 +430,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(mock);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_2_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_2_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final InputStreamResource body = (InputStreamResource) response.getBody();
         assertNotNull(body);
@@ -344,7 +441,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliography_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa1.txt"),
                 StandardCharsets.UTF_8);
@@ -356,7 +453,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -365,9 +462,29 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
 
     @Test
     @WithAnonymousUser
+    public void find_anonymousBibliographyApa0_fails() throws IOException, MalformedException,
+            IdentifierNotFoundException {
+        final String accept = "text/bibliography; style=apa";
+        final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa0.txt"),
+                StandardCharsets.UTF_8);
+
+        /* mock */
+        when(identifierService.exportBibliography(IDENTIFIER_7, BibliographyTypeDto.APA))
+                .thenReturn(compare);
+        when(identifierService.find(IDENTIFIER_7_ID))
+                .thenReturn(IDENTIFIER_7);
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            identifierEndpoint.find(IDENTIFIER_7_ID, accept, null);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_4_USERNAME)
     public void find_bibliographyApa0_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=apa";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa0.txt"),
                 StandardCharsets.UTF_8);
@@ -379,7 +496,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_7);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -390,7 +507,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyApa1_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=apa";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa1.txt"),
                 StandardCharsets.UTF_8);
@@ -402,7 +519,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -410,10 +527,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     }
 
     @Test
-    @WithAnonymousUser
-    public void find_bibliographyApa2_succeeds() throws IOException, MalformedException, DataServiceException,
+    @WithMockUser(username = USER_2_USERNAME)
+    public void find_draftBibliographyApa2_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=apa";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa2.txt"),
                 StandardCharsets.UTF_8);
@@ -425,7 +542,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_5);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept, USER_2_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -436,7 +553,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyApa3_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=apa";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa3.txt"),
                 StandardCharsets.UTF_8);
@@ -448,7 +565,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_6);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_6_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_6_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -459,7 +576,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyApa4_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=apa";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa4.txt"),
                 StandardCharsets.UTF_8);
@@ -471,7 +588,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1_WITH_DOI);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -479,10 +596,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     }
 
     @Test
-    @WithAnonymousUser
+    @WithMockUser(username = USER_4_USERNAME)
     public void find_bibliographyIeee0_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=ieee";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee0.txt"),
                 StandardCharsets.UTF_8);
@@ -494,7 +611,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_7);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -505,7 +622,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyIeee1_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=ieee";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee1.txt"),
                 StandardCharsets.UTF_8);
@@ -517,7 +634,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -525,10 +642,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     }
 
     @Test
-    @WithAnonymousUser
+    @WithMockUser(username = USER_2_USERNAME)
     public void find_bibliographyIeee2_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=ieee";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee2.txt"),
                 StandardCharsets.UTF_8);
@@ -540,7 +657,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_5);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept, USER_2_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -551,7 +668,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyIeee3_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=ieee";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee3.txt"),
                 StandardCharsets.UTF_8);
@@ -563,7 +680,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1_WITH_DOI);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -571,10 +688,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     }
 
     @Test
-    @WithAnonymousUser
+    @WithMockUser(username = USER_4_USERNAME)
     public void find_bibliographyBibtex0_succeeds() throws IOException, MalformedException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=bibtex";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex0.txt"),
                 StandardCharsets.UTF_8);
@@ -586,7 +703,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_7);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_7_ID, accept, USER_4_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -597,7 +714,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyBibtex1_succeeds() throws MalformedException, IOException, DataServiceException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=bibtex";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex1.txt"),
                 StandardCharsets.UTF_8);
@@ -609,7 +726,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -617,10 +734,10 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     }
 
     @Test
-    @WithAnonymousUser
+    @WithMockUser(username = USER_2_USERNAME)
     public void find_bibliographyBibtex2_succeeds() throws MalformedException, DataServiceException, IOException,
             DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=bibtex";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex2.txt"),
                 StandardCharsets.UTF_8);
@@ -632,7 +749,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_5);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_5_ID, accept, USER_2_PRINCIPAL);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -643,7 +760,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_bibliographyBibtex3_succeeds() throws MalformedException, DataServiceException,
             DataServiceConnectionException, IOException, QueryNotFoundException, IdentifierNotFoundException,
-            FormatNotAvailableException {
+            FormatNotAvailableException, NotAllowedException {
         final String accept = "text/bibliography; style=bibtex";
         final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex3.txt"),
                 StandardCharsets.UTF_8);
@@ -655,7 +772,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1_WITH_DOI);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final String body = (String) response.getBody();
         assertNotNull(body);
@@ -665,7 +782,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @Test
     @WithAnonymousUser
     public void find_jsonLd_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException,
-            QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException {
+            QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException {
         final String accept = "application/ld+json";
 
         /* mock */
@@ -673,7 +790,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final LdDatasetDto body = (LdDatasetDto) response.getBody();
         assertNotNull(body);
@@ -689,22 +806,22 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_7);
 
         /* test */
-        assertThrows(FormatNotAvailableException.class, () -> {
-            identifierEndpoint.find(IDENTIFIER_7_ID, accept);
+        assertThrows(NotAllowedException.class, () -> {
+            identifierEndpoint.find(IDENTIFIER_7_ID, accept, null);
         });
     }
 
     @Test
     @WithAnonymousUser
     public void find_move_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException,
-            QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException {
+            QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException {
 
         /* mock */
         when(identifierService.find(IDENTIFIER_1_ID))
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, null);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, null, null);
         assertEquals(HttpStatus.MOVED_PERMANENTLY, response.getStatusCode());
     }
 
@@ -848,7 +965,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @Test
     @WithAnonymousUser
     public void find_json_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException,
-            FormatNotAvailableException, QueryNotFoundException, IdentifierNotFoundException {
+            FormatNotAvailableException, QueryNotFoundException, IdentifierNotFoundException, NotAllowedException {
         final String accept = "application/json";
 
         /* mock */
@@ -856,7 +973,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
                 .thenReturn(IDENTIFIER_1);
 
         /* test */
-        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
         assertEquals(HttpStatus.OK, response.getStatusCode());
         final IdentifierDto body = (IdentifierDto) response.getBody();
         assertNotNull(body);
@@ -875,7 +992,8 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @Test
     @WithAnonymousUser
     public void find_xml_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException,
-            IOException, QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException {
+            IOException, QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException,
+            NotAllowedException {
         final InputStreamResource resource = new InputStreamResource(FileUtils.openInputStream(
                 new File("src/test/resources/xml/datacite-example-dataset-v4.xml")));
 
@@ -892,7 +1010,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
     @WithAnonymousUser
     public void find_httpRedirect_succeeds() throws MalformedException, DataServiceException,
             DataServiceConnectionException, FormatNotAvailableException, QueryNotFoundException,
-            IdentifierNotFoundException {
+            IdentifierNotFoundException, NotAllowedException {
 
         /* test */
         final ResponseEntity<?> response = generic_find(null, null);
@@ -1291,7 +1409,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
 
     protected ResponseEntity<?> generic_find(String accept, InputStreamResource resource)
             throws MalformedException, DataServiceException, DataServiceConnectionException, FormatNotAvailableException,
-            QueryNotFoundException, IdentifierNotFoundException {
+            QueryNotFoundException, IdentifierNotFoundException, NotAllowedException {
 
         /* mock */
         when(identifierService.find(IDENTIFIER_1_ID))
@@ -1304,7 +1422,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest {
         }
 
         /* test */
-        return identifierEndpoint.find(IDENTIFIER_1_ID, accept);
+        return identifierEndpoint.find(IDENTIFIER_1_ID, accept, null);
     }
 
     protected static String inputStreamToString(InputStream inputStream) throws IOException {
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java
index 632affcf91..e22a7a4a05 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java
@@ -6,6 +6,7 @@ import at.tuwien.api.database.DatabaseModifyImageDto;
 import at.tuwien.api.database.DatabaseModifyVisibilityDto;
 import at.tuwien.api.database.DatabaseTransferDto;
 import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto;
+import at.tuwien.api.identifier.IdentifierTypeDto;
 import at.tuwien.config.MetricsConfig;
 import at.tuwien.endpoints.*;
 import at.tuwien.test.AbstractUnitTest;
@@ -278,7 +279,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest {
             /* ignore */
         }
         try {
-            identifierEndpoint.findAll(DATABASE_1_ID, null, null, null, MediaType.APPLICATION_JSON_VALUE);
+            identifierEndpoint.findAll(IdentifierTypeDto.DATABASE, null, DATABASE_1_ID, null, null, null, MediaType.APPLICATION_JSON_VALUE, null);
         } catch (Exception e) {
             /* ignore */
         }
diff --git a/dbrepo-ui/components/ResourceStatus.vue b/dbrepo-ui/components/ResourceStatus.vue
index d766f37660..6db6d25385 100644
--- a/dbrepo-ui/components/ResourceStatus.vue
+++ b/dbrepo-ui/components/ResourceStatus.vue
@@ -5,7 +5,7 @@
       v-if="!inline"
       :size="size"
       :color="color"
-      variant="outlined">
+      :variant="chipVariant">
       {{ status }}
     </v-chip>
     <span
@@ -39,6 +39,9 @@ export default {
       if (!this.resource) {
         return null
       }
+      if (this.hasIdentifier) {
+        return 'pid'
+      }
       if (!this.resource.is_public && !this.resource.is_schema_public) {
         return 'draft'
       } else if(!this.resource.is_public && this.resource.is_schema_public) {
@@ -54,7 +57,19 @@ export default {
       }
       return this.$t(`pages.database.status.${this.mode}`)
     },
+    hasIdentifier () {
+      return this.resource.identifiers?.length > 0
+    },
+    chipVariant () {
+      if (this.hasIdentifier) {
+        return 'tonal'
+      }
+      return 'outlined'
+    },
     color () {
+      if (this.hasIdentifier) {
+        return 'info'
+      }
       switch (this.mode) {
         case 'schema':
         case 'data':
diff --git a/dbrepo-ui/components/identifier/Banner.vue b/dbrepo-ui/components/identifier/Banner.vue
index 7c77a1b28b..63c2a7153a 100644
--- a/dbrepo-ui/components/identifier/Banner.vue
+++ b/dbrepo-ui/components/identifier/Banner.vue
@@ -24,7 +24,7 @@ export default {
       return identifierService.identifierToDisplayName(this.identifier)
     },
     href () {
-      if (!this.identifier || (this.identifier.status && this.identifier.status !== 'published')) {
+      if (!this.identifier) {
         return null
       }
       const identifierService = useIdentifierService()
diff --git a/dbrepo-ui/components/identifier/Persist.vue b/dbrepo-ui/components/identifier/Persist.vue
index b02ff09b76..f37c5c6d7d 100644
--- a/dbrepo-ui/components/identifier/Persist.vue
+++ b/dbrepo-ui/components/identifier/Persist.vue
@@ -12,6 +12,7 @@
       <v-spacer />
       <v-btn
         v-if="canSave"
+        class="mr-2"
         :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-content-save-outline' : null"
         color="secondary"
         variant="flat"
@@ -22,7 +23,7 @@
         @click="createOrSave"/>
       <v-btn
         v-if="canRemove"
-        class="ml-2"
+        class="mr-2"
         :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-delete' : null"
         color="error"
         variant="flat"
@@ -32,7 +33,7 @@
         @click="remove" />
       <v-btn
         v-if="canPublish"
-        class="ml-2"
+        class="mr-2"
         :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-content-save-outline' : null"
         color="primary"
         variant="flat"
@@ -138,14 +139,6 @@
                       :color="canShiftUp(creator, i) ? 'tertiary' : ''"
                       :variant="buttonVariant"
                       @click="shiftDown(i)" />
-                    <v-btn
-                      v-if="canInsertSelf"
-                      class="mr-2"
-                      size="small"
-                      color="secondary"
-                      variant="flat"
-                      :text="$t('pages.identifier.subpages.create.creators.insert.text')"
-                      @click="insertSelf(creator)" />
                     <v-btn
                       v-if="i > 0"
                       size="small"
@@ -1043,12 +1036,6 @@ export default {
           }
       }
     },
-    canInsertSelf () {
-      if (!this.cacheUser) {
-        return false
-      }
-      return this.cacheUser.given_name || this.cacheUser.family_name || this.cacheUser.attributes.affiliation || this.cacheUser.attributes.orcid
-    },
     isCreator () {
       if (!this.cacheUser || !this.identifier) {
         return false
diff --git a/dbrepo-ui/components/identifier/Select.vue b/dbrepo-ui/components/identifier/Select.vue
index 4ef13b2ca7..e557286614 100644
--- a/dbrepo-ui/components/identifier/Select.vue
+++ b/dbrepo-ui/components/identifier/Select.vue
@@ -8,7 +8,7 @@
       :color="color(identifier)"
       :variant="listVariant"
       :href="href(identifier)"
-      :title="formatTimestampUTCLabel(identifier.created)"
+      :title="title(identifier)"
       lines="two">
       <v-list-item-subtitle>
         <Banner
@@ -59,7 +59,7 @@ export default {
     identifier: {
       type: Object,
       default () {
-        return {}
+        return null
       }
     }
   },
@@ -71,11 +71,14 @@ export default {
   },
   computed: {
     cacheUser () {
-      return this.cacheUser.getUser
+      return this.cacheStore.getUser
     },
     displayIdentifiers () {
-      if (!this.identifiers) {
-        return []
+      if (!this.identifiers || this.identifiers.length === 0) {
+        if (!this.identifier) {
+          return []
+        }
+        return [this.identifier]
       }
       if (!this.cacheUser) {
         return this.identifiers.filter(i => i.status === 'published')
@@ -100,6 +103,9 @@ export default {
   },
   methods: {
     href (identifier) {
+      if (!identifier) {
+        return null
+      }
       if (identifier.status === 'published') {
         return `/pid/${identifier.id}`
       }
@@ -114,6 +120,13 @@ export default {
           return `/database/${identifier.database_id}/view/${identifier.view_id}/persist/${identifier.id}`
       }
     },
+    title (identifier) {
+      if (!identifier) {
+        return null
+      }
+      const identifierService = useIdentifierService()
+      return identifierService.identifierPreferEnglishTitle(identifier)
+    },
     isActive (identifier) {
       if (!identifier) {
         return false
diff --git a/dbrepo-ui/components/subset/Builder.vue b/dbrepo-ui/components/subset/Builder.vue
index 4261b098b6..f19c595aad 100644
--- a/dbrepo-ui/components/subset/Builder.vue
+++ b/dbrepo-ui/components/subset/Builder.vue
@@ -1,6 +1,5 @@
 <template>
-  <div
-    v-if="loggedIn">
+  <div>
     <v-toolbar flat>
       <v-btn
         size="small"
@@ -75,7 +74,7 @@
                 required
                 clearable
                 :rules="[
-                  v => !!v || $t('validation.required')
+                  v => v !== null || $t('validation.required')
                 ]"
                 :label="$t('pages.database.resource.data.label')"
                 :hint="$t('pages.database.resource.data.hint')" />
@@ -90,7 +89,7 @@
                 required
                 clearable
                 :rules="[
-                  v => !!v || $t('validation.required')
+                  v => v !== null || $t('validation.required')
                 ]"
                 :label="$t('pages.database.resource.schema.label')"
                 :hint="$t('pages.database.resource.schema.hint', { resource: 'subset', schema: 'query' })" />
@@ -445,7 +444,7 @@ export default {
       if (this.isView) {
         return this.view.name !== null && this.view.is_public !== null && this.view.query !== null
       }
-      return this.sql !== null && !this.sql.includes(';')
+      return this.sql !== null && this.sql !== '' && !this.sql.includes(';')
     },
     inputVariant () {
       const runtimeConfig = useRuntimeConfig()
diff --git a/dbrepo-ui/components/subset/SubsetList.vue b/dbrepo-ui/components/subset/SubsetList.vue
index eb6a27aacd..fa44454af8 100644
--- a/dbrepo-ui/components/subset/SubsetList.vue
+++ b/dbrepo-ui/components/subset/SubsetList.vue
@@ -6,6 +6,7 @@
       rounded="0"
       :text="$t('pages.database.subpages.subsets.empty')" />
     <v-card
+      v-if="subsets.length > 0"
       variant="flat"
       rounded="0">
       <v-list-item
@@ -14,28 +15,20 @@
         <Loading />
       </v-list-item>
       <div
-        v-for="(item, i) in subsets"
+        v-for="(subset, i) in subsets"
         :key="`q-${i}`">
         <v-divider v-if="i !== 0" class="mx-4" />
         <v-list>
           <v-list-item
             lines="two"
-            :title="title(item)"
-            :subtitle="subtitle(item)"
-            :class="clazz(item)"
-            :to="link(item)"
-            :href="link(item)">
+            :title="title(subset)"
+            :subtitle="subtitle(subset)"
+            :class="clazz(subset)"
+            :to="link(subset)"
+            :href="link(subset)">
             <template v-slot:append>
-              <v-tooltip
-                v-if="hasPublishedIdentifier(item)"
-                :text="$t('pages.identifier.pid.title')"
-                left>
-                <template v-slot:activator="{ props }">
-                  <v-icon
-                    color="primary"
-                    v-bind="props">mdi-identifier</v-icon>
-                </template>
-              </v-tooltip>
+              <ResourceStatus
+                :resource="subset" />
             </template>
           </v-list-item>
         </v-list>
diff --git a/dbrepo-ui/components/subset/SubsetToolbar.vue b/dbrepo-ui/components/subset/SubsetToolbar.vue
index 61e26e9967..e602609097 100644
--- a/dbrepo-ui/components/subset/SubsetToolbar.vue
+++ b/dbrepo-ui/components/subset/SubsetToolbar.vue
@@ -112,9 +112,7 @@ export default {
       if (this.pid) {
         const filter = this.identifiers.filter(i => i.id === Number(this.pid))
         if (filter.length > 0) {
-          const identifier = filter[0]
-          console.debug('identifier set according to route pid', identifier)
-          return identifier
+          return filter[0]
         }
       }
       return this.identifiers[0]
diff --git a/dbrepo-ui/components/table/TableList.vue b/dbrepo-ui/components/table/TableList.vue
index 71106f7c65..5f87090b85 100644
--- a/dbrepo-ui/components/table/TableList.vue
+++ b/dbrepo-ui/components/table/TableList.vue
@@ -21,16 +21,6 @@
           <template v-slot:append>
             <ResourceStatus
               :resource="table" />
-            <v-tooltip
-              v-if="hasPublishedIdentifier(table)"
-              :text="$t('pages.identifier.pid.title')"
-              left>
-              <template v-slot:activator="{ props }">
-                <v-icon
-                  color="primary"
-                  v-bind="props">mdi-identifier</v-icon>
-              </template>
-            </v-tooltip>
           </template>
         </v-list-item>
       </v-list>
diff --git a/dbrepo-ui/components/view/ViewList.vue b/dbrepo-ui/components/view/ViewList.vue
index 58038b7a82..afa3067921 100644
--- a/dbrepo-ui/components/view/ViewList.vue
+++ b/dbrepo-ui/components/view/ViewList.vue
@@ -16,16 +16,6 @@
           <template v-slot:append>
             <ResourceStatus
               :resource="view" />
-            <v-tooltip
-              v-if="hasPublishedIdentifier(view)"
-              :text="$t('pages.identifier.pid.title')"
-              left>
-              <template v-slot:activator="{ props }">
-                <v-icon
-                  color="primary"
-                  v-bind="props">mdi-identifier</v-icon>
-              </template>
-            </v-tooltip>
           </template>
         </v-list-item>
       </v-list>
diff --git a/dbrepo-ui/composables/identifier-service.ts b/dbrepo-ui/composables/identifier-service.ts
index 3ae194ff2f..6875a7cb7b 100644
--- a/dbrepo-ui/composables/identifier-service.ts
+++ b/dbrepo-ui/composables/identifier-service.ts
@@ -24,7 +24,7 @@ export const useIdentifierService = (): any => {
   }
 
   async function create(data: IdentifierSaveDto): Promise<IdentifierDto> {
-    const axios= useAxiosInstance()
+    const axios = useAxiosInstance()
     console.debug('create identifier')
     return new Promise<IdentifierDto>((resolve, reject) => {
       axios.post<IdentifierDto>('/api/identifier', data)
@@ -40,7 +40,7 @@ export const useIdentifierService = (): any => {
   }
 
   async function save(data: IdentifierSaveDto): Promise<IdentifierDto> {
-    const axios= useAxiosInstance()
+    const axios = useAxiosInstance()
     console.debug('save identifier', data.id)
     return new Promise<IdentifierDto>((resolve, reject) => {
       axios.put<IdentifierDto>(`/api/identifier/${data.id}`, data)
@@ -241,13 +241,28 @@ export const useIdentifierService = (): any => {
     if (!data || !data.titles || data.titles.length === 0) {
       return null
     }
-    const filtered = data.titles.filter(d => d.language && d.language === 'en')
+    const filtered = data.titles.filter((d) => d.language && d.language === 'en')
     if (filtered.length === 0) {
-      return data.titles[0].title
+      const title = data.titles[0]
+      return title.title
     }
     return filtered[0].title
   }
 
+  function identifierToResourceUrl(identifier: IdentifierDto): string | null {
+    const config = useRuntimeConfig()
+    switch (identifier.type) {
+      case 1:
+        return `${config.public.api.client}/api/database/${identifier.database_id}/subset/${identifier.subset_id}/data`
+      case 2:
+        return `${config.public.api.client}/api/database/${identifier.database_id}/table/${identifier.table_id}/data`
+      case 3:
+        return `${config.public.api.client}/api/database/${identifier.database_id}/view/${identifier.view_id}/data`
+      default:
+        return null
+    }
+  }
+
   function identifierToUrl(data: IdentifierDto): string | null {
     if (!data) {
       return null
@@ -315,244 +330,48 @@ export const useIdentifierService = (): any => {
     return jsonLd
   }
 
-  function identifierToHasPartJsonLd(identifier: IdentifierDto) {
-    return {
-      '@type': 'Dataset',
-      name: identifierPreferEnglishTitle(identifier),
-      description: identifierPreferEnglishDescription(identifier),
-      identifier: identifierToUrl(identifier),
-      citation: identifierToUrl(identifier),
-      temporalCoverage: identifier.publication_year,
-      version: identifier.created
-    }
-  }
-
-  function databaseToServerHead(database: DatabaseDto) {
-    if (!database) {
-      return
+  function identifiersToServerHead(identifiers: IdentifierBriefDto[]): any {
+    if (!identifiers || !identifiers[0]) {
+      return null
     }
-    const config = useRuntimeConfig()
+    const identifier = identifiers[0]
     /* Google Rich Results */
     const json: any = {
       '@context': 'https://schema.org/',
       '@type': 'Dataset',
-      url: `${config.public.api.client}/database/${database.id}/info`,
-      citation: `${config.public.api.client}/database/${database.id}/info`,
+      url: identifierToUrl(identifier),
+      citation: identifierToUrl(identifier),
       hasPart: [],
-      version: database.created
+      identifier: identifiers.map(i => identifierToUrl(i)),
+      creator: identifier.creators.map((c) => creatorToCreatorJsonLd(c)),
+      temporalCoverage: identifier.publication_year
     }
-    /* FAIR Signposting */
-    const meta: any [] = []
-    if (database.identifiers.length > 0) {
-      const identifier = database.identifiers[0]
-      const partIdentifiers: IdentifierDto[] = []
-      if (database.subsets.length > 0) {
-        database.subsets.forEach((s) => {
-          partIdentifiers.push(s)
-        })
-      }
-      if (database.tables.length > 0) {
-        database.tables.forEach((t) => {
-          if (t.identifiers.length > 0) {
-            t.identifiers.forEach(i => partIdentifiers.push(i))
-          }
-        })
-      }
-      if (database.views.length > 0) {
-        database.views.forEach((v) => {
-          if (v.identifiers.length > 0) {
-            v.identifiers.forEach(i => partIdentifiers.push(i))
-          }
-        })
-      }
+    if (identifier.titles.length > 0) {
       json['name'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['identifier'] = database.identifiers.map(i => identifierToUrl(i))
-      json['license'] = identifierToPreferFirstLicenseUri(identifier)
-      json['creator'] = identifier.creators.map(c => creatorToCreatorJsonLd(c))
-      json['citation'] = identifierToUrl(identifier)
-      json['hasPart'] = partIdentifiers.map(i => identifierToHasPartJsonLd(i))
-      json['temporalCoverage'] = identifier.publication_year
-      meta.push({rel: 'cite-as', href: identifierToUrl(identifier)})
-      identifier.creators.forEach((c: CreatorDto) => {
-        if (c.name_identifier) {
-          meta.push({rel: 'author', href: c.name_identifier})
-        }
-      })
-      meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)})
-      meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)})
-      if (identifier.licenses) {
-        identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri}))
-      }
-    }
-    return {
-      script: [
-        {
-          type: 'application/ld+json',
-          innerHTML: json
-        }
-      ],
-      link: meta
     }
-  }
-
-  function subsetToServerHead(subset: QueryDto) {
-    const config = useRuntimeConfig()
-    /* Google Rich Results */
-    const json: any = {
-      '@context': 'https://schema.org/',
-      '@type': 'Dataset',
-      description: subset.query,
-      url: `${config.public.api.client}/database/${subset.database_id}/info`,
-      citation: `${config.public.api.client}/database/${subset.database_id}/info`,
-      hasPart: [],
-      version: subset.created
-    }
-    /* FAIR Signposting */
-    const meta: any[] = []
-    if (subset.identifiers.length > 0) {
-      const identifier = subset.identifiers[0]
-      json['name'] = identifierPreferEnglishTitle(identifier)
+    if (identifier.descriptions.length > 0) {
       json['description'] = identifierPreferEnglishDescription(identifier)
-      json['identifier'] = subset.identifiers.map(i => identifierToUrl(i))
-      json['license'] = identifierToPreferFirstLicenseUri(identifier)
-      json['creator'] = identifier.creators.map(c => creatorToCreatorJsonLd(c))
-      json['citation'] = identifierToUrl(identifier)
-      json['temporalCoverage'] = identifier.publication_year
-      meta.push({rel: 'cite-as', href: identifierToUrl(identifier)})
-      identifier.creators.forEach((c: CreatorDto) => {
-        if (c.name_identifier) {
-          meta.push({rel: 'author', href: c.name_identifier})
-        }
-      })
-      meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)})
-      meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)})
-      if (identifier.licenses) {
-        identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri}))
-      }
-      meta.push({
-        rel: 'item',
-        type: 'application/json',
-        href: `${config.public.api.client}/api/database/${subset.database_id}/subset/${subset.id}/data`
-      })
-      meta.push({
-        rel: 'item',
-        type: 'text/csv',
-        href: `${config.public.api.client}/api/database/${subset.database_id}/subset/${subset.id}/data`
-      })
-    }
-    return {
-      script: [
-        {
-          type: 'application/ld+json',
-          innerHTML: json
-        }
-      ],
-      link: meta
-    }
-  }
-
-  function tableToServerHead(table: TableDto) {
-    const config = useRuntimeConfig()
-    /* Google Rich Results */
-    const json: any = {
-      '@context': 'https://schema.org/',
-      '@type': 'Dataset',
-      description: table.description,
-      url: `${config.public.api.client}/database/${table.database_id}/table/${table.id}/info`,
-      citation: `${config.public.api.client}/database/${table.database_id}/table/${table.id}/info`,
-      hasPart: [],
-      version: table.created
     }
     /* FAIR Signposting */
     const meta: any[] = []
-    if (table.identifiers.length > 0) {
-      const identifier: IdentifierDto = table.identifiers[0]
-      json['name'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['identifier'] = table.identifiers.map((i: IdentifierDto) => identifierToUrl(i))
-      json['license'] = identifierToPreferFirstLicenseUri(identifier)
-      json['creator'] = identifier.creators.map((c: CreatorDto) => creatorToCreatorJsonLd(c))
-      json['citation'] = identifierToUrl(identifier)
-      json['temporalCoverage'] = identifier.publication_year
-      meta.push({rel: 'cite-as', href: identifierToUrl(identifier)})
-      identifier.creators.forEach((c: CreatorDto): void => {
-        if (c.name_identifier) {
-          meta.push({rel: 'author', href: c.name_identifier})
-        }
-      })
-      meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)})
-      meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)})
-      if (identifier.licenses) {
-        identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri}))
+    meta.push({rel: 'cite-as', href: identifierToUrl(identifier)})
+    identifier.creators.forEach((c: CreatorDto) => {
+      if (c.name_identifier) {
+        meta.push({rel: 'author', href: c.name_identifier})
       }
-      meta.push({
-        rel: 'item',
-        type: 'application/json',
-        href: `${config.public.api.client}/api/database/${table.database_id}/table/${table.id}/data`
-      })
-      meta.push({
-        rel: 'item',
-        type: 'text/csv',
-        href: `${config.public.api.client}/api/database/${table.database_id}/table/${table.id}/data`
-      })
-    }
-    return {
-      script: [
-        {
-          type: 'application/ld+json',
-          innerHTML: json
-        }
-      ],
-      link: meta
-    }
-  }
-
-  function viewToServerHead(view: ViewDto) {
-    const config = useRuntimeConfig()
-    /* Google Rich Results */
-    const json: any = {
-      '@context': 'https://schema.org/',
-      '@type': 'Dataset',
-      description: view.query,
-      url: `${config.public.api.client}/database/${view.database_id}/table/${view.id}/info`,
-      citation: `${config.public.api.client}/database/${view.database_id}/table/${view.id}/info`,
-      hasPart: [],
-      version: view.created
-    }
-    /* FAIR Signposting */
-    const meta: any[] = []
-    if (view.identifiers.length > 0) {
-      const identifier = view.identifiers[0]
-      json['name'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['identifier'] = view.identifiers.map(i => identifierToUrl(i))
-      json['license'] = identifierToPreferFirstLicenseUri(identifier)
-      json['creator'] = identifier.creators.map(c => creatorToCreatorJsonLd(c))
-      json['citation'] = identifierToUrl(identifier)
-      json['temporalCoverage'] = identifier.publication_year
-      meta.push({rel: 'cite-as', href: identifierToUrl(identifier)})
-      identifier.creators.forEach((c: CreatorDto) => {
-        if (c.name_identifier) {
-          meta.push({rel: 'author', href: c.name_identifier})
-        }
-      })
-      meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)})
-      meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)})
-      if (identifier.licenses) {
-        identifier.licenses.forEach((l: LicenseDto) => meta.push({rel: 'license', href: l.uri}))
-      }
-      meta.push({
-        rel: 'item',
-        type: 'application/json',
-        href: `${config.public.api.client}/api/database/${view.database_id}/view/${view.id}/data`
-      })
-      meta.push({
-        rel: 'item',
-        type: 'text/csv',
-        href: `${config.public.api.client}/api/database/${view.database_id}/view/${view.id}/data`
-      })
-    }
+    })
+    meta.push({rel: 'describedby', type: 'application/x-bibtex', href: identifierToUrl(identifier)})
+    meta.push({rel: 'describedby', type: 'application/vnd.datacite.datacite+json', href: identifierToUrl(identifier)})
+    meta.push({
+      rel: 'item',
+      type: 'application/json',
+      href: identifierToResourceUrl(identifier)
+    })
+    meta.push({
+      rel: 'item',
+      type: 'text/csv',
+      href: identifierToResourceUrl(identifier)
+    })
     return {
       script: [
         {
@@ -564,56 +383,15 @@ export const useIdentifierService = (): any => {
     }
   }
 
-  function databaseToServerSeoMeta(database: DatabaseDto) {
-    const json: any = {
-      ogTitle: database.name
-    }
-    if (database.identifiers.length > 0) {
-      const identifier = database.identifiers[0]
-      json['ogTitle'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['ogDescription'] = identifierPreferEnglishDescription(identifier)
-    }
-    return json
-  }
-
-  function subsetToServerSeoMeta(subset: QueryDto) {
-    const json: any = {
-      description: subset.query
-    }
-    if (subset.identifiers.length > 0) {
-      const identifier = subset.identifiers[0]
-      json['ogTitle'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['ogDescription'] = identifierPreferEnglishDescription(identifier)
-    }
-    return json
-  }
-
-  function tableToServerSeoMeta(table: TableDto) {
-    const json: any = {
-      ogTitle: table.name,
-      description: table.description
-    }
-    if (table.identifiers.length > 0) {
-      const identifier = table.identifiers[0]
-      json['ogTitle'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['ogDescription'] = identifierPreferEnglishDescription(identifier)
+  function identifiersToServerSeoMeta(identifiers: IdentifierBriefDto[]): any | null {
+    if (!identifiers|| !identifiers[0]) {
+      return null
     }
-    return json
-  }
-
-  function viewToServerSeoMeta(view: ViewDto) {
+    const identifier = identifiers[0]
     const json: any = {
-      ogTitle: view.name,
-      description: view.query
-    }
-    if (view.identifiers.length > 0) {
-      const identifier = view.identifiers[0]
-      json['ogTitle'] = identifierPreferEnglishTitle(identifier)
-      json['description'] = identifierPreferEnglishDescription(identifier)
-      json['ogDescription'] = identifierPreferEnglishDescription(identifier)
+      ogTitle: identifierPreferEnglishTitle(identifier),
+      ogDescription: identifierPreferEnglishDescription(identifier),
+      description: identifierPreferEnglishDescription(identifier)
     }
     return json
   }
@@ -633,13 +411,7 @@ export const useIdentifierService = (): any => {
     identifierToUrl,
     identifierToDisplayName,
     identifierToDisplayAcronym,
-    databaseToServerHead,
-    subsetToServerHead,
-    tableToServerHead,
-    viewToServerHead,
-    databaseToServerSeoMeta,
-    subsetToServerSeoMeta,
-    tableToServerSeoMeta,
-    viewToServerSeoMeta,
+    identifiersToServerHead,
+    identifiersToServerSeoMeta
   }
 }
diff --git a/dbrepo-ui/dto/index.ts b/dbrepo-ui/dto/index.ts
index 9171734aa3..605a7c0db9 100644
--- a/dbrepo-ui/dto/index.ts
+++ b/dbrepo-ui/dto/index.ts
@@ -224,7 +224,11 @@ interface IdentifierFunderSaveDto {
 
 interface IdentifierDto {
   id: number;
-  type: string;
+  database_id: number | null;
+  query_id: number | null;
+  table_id: number | null;
+  view_id: number | null;
+  type: IdentifierTypeDto;
   titles: IdentifierTitleDto[] | [];
   descriptions: IdentifierDescriptionDto[] | [];
   funders: IdentifierFunderDto[] | [];
@@ -236,23 +240,43 @@ interface IdentifierDto {
   licenses: LicenseDto[] | [];
   creators: CreatorDto[] | [];
   created: Date;
-  database_id: number | null;
-  query_id: number | null;
-  table_id: number | null;
-  view_id: number | null;
   query_normalized: string | null;
   related_identifiers: RelatedIdentifierDto[] | [];
   query_hash: string | null;
   result_hash: string | null;
-  /**
-   * @deprecated
-   */
   result_number: number | null;
   publication_day: number | null;
   publication_month: number | null;
-  value: string | null;
   publication_year: number;
-  last_modified: Date;
+}
+
+enum IdentifierTypeDto {
+  database,
+  subset,
+  table,
+  view
+}
+
+enum IdentifierStatusTypeDto {
+  draft,
+  published
+}
+
+interface IdentifierBriefDto {
+  id: number;
+  database_id: number | null;
+  query_id: number | null;
+  table_id: number | null;
+  view_id: number | null;
+  type: IdentifierTypeDto;
+  creators: CreatorBriefDto[] | [];
+  titles: IdentifierTitleDto[] | [];
+  description: IdentifierDescriptionDto[] | [];
+  doi: string | null;
+  publisher: string;
+  publication_year: number;
+  status: IdentifierStatusTypeDto;
+  owned_by: string;
 }
 
 interface IdentifierTitleDto {
@@ -279,19 +303,35 @@ interface IdentifierFunderDto {
   award_title: string;
 }
 
+enum NameTypeDto {
+  Personal,
+  Organizational
+}
+
 interface CreatorDto {
   id: number;
   firstname: string;
   lastname: string;
   affiliation: string;
   creator_name: string;
-  name_type: string;
-  name_identifier: string;
-  name_identifier_scheme: string;
-  name_identifier_scheme_uri: string;
-  affiliation_identifier: string;
-  affiliation_identifier_scheme: string;
-  affiliation_identifier_scheme_uri: string;
+  name_type: NameTypeDto | null;
+  name_identifier: string | null;
+  name_identifier_scheme: string | null;
+  name_identifier_scheme_uri: string | null;
+  affiliation_identifier: string | null;
+  affiliation_identifier_scheme: string | null;
+  affiliation_identifier_scheme_uri: string | null;
+}
+
+interface CreatorBriefDto {
+  id: number;
+  affiliation: string;
+  creator_name: string;
+  name_type: NameTypeDto | null;
+  name_identifier: string | null;
+  name_identifier_scheme: string | null;
+  affiliation_identifier: string | null;
+  affiliation_identifier_scheme: string | null;
 }
 
 interface RelatedIdentifierDto {
@@ -342,7 +382,6 @@ interface ColumnDto {
   database_id: number;
   table_id: number;
   internal_name: string;
-  date_format: ImageDateDto;
   is_primary_key: boolean;
   index_length: number;
   length: number;
diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue
index 9351783574..50aa155d86 100644
--- a/dbrepo-ui/layouts/default.vue
+++ b/dbrepo-ui/layouts/default.vue
@@ -214,9 +214,15 @@ export default {
     access () {
       return this.cacheStore.getAccess
     },
+    roles () {
+      return this.cacheStore.getRoles
+    },
     cacheUser () {
       return this.cacheStore.getUser
     },
+    identifier () {
+      return this.cacheStore.getIdentifier
+    },
     resource () {
       if (!this.$route.params.database_id) {
         return null
@@ -245,6 +251,9 @@ export default {
       return this.$config.public.commit.substr(0, 8)
     },
     error () {
+      if (this.identifier) {
+        return null
+      }
       if (this.databaseError) {
         return this.databaseError
       }
@@ -254,13 +263,13 @@ export default {
       if (!this.cacheUser) {
         return null
       }
-      if (this.table && !this.table.is_public && !this.table.is_schema_public && this.table.owner.id !== this.cacheUser.uid) {
+      if (this.table && !this.table.is_public && !this.table.is_schema_public && !this.access) {
         return makeError(403, null, null)
       }
-      if (this.view && !this.view.is_public && !this.view.is_schema_public && this.view.owner.id !== this.cacheUser.uid) {
+      if (this.view && !this.view.is_public && !this.view.is_schema_public && !this.access) {
         return makeError(403, null, null)
       }
-      if (this.subset && !this.subset.is_public && !this.subset.is_schema_public && this.subset.owner.id !== this.cacheUser.uid) {
+      if (this.subset && !this.subset.is_public && !this.subset.is_schema_public && !this.access) {
         return makeError(403, null, null)
       }
       return null
@@ -288,6 +297,9 @@ export default {
   watch: {
     '$route.params': {
       handler (newObj, oldObj) {
+        if (import.meta.server) {
+          return
+        }
         if (!newObj.database_id) {
           this.databaseError = null
           this.accessError = null
@@ -295,10 +307,20 @@ export default {
           this.cacheStore.setView(null)
           this.cacheStore.setSubset(null)
           this.cacheStore.setAccess(null)
+          this.cacheStore.setIdentifier(null)
           return
         }
-        if (import.meta.server) {
-          return
+        if (this.identifier) {
+          if (newObj.query_id && this.identifier.query_id !== Number(newObj.query_id)) {
+            this.cacheStore.setIdentifier(null)
+          } else if (newObj.table_id && this.identifier.table_id !== Number(newObj.table_id)) {
+            this.cacheStore.setIdentifier(null)
+          } else if (newObj.view_id && this.identifier.view_id !== Number(newObj.view_id)) {
+            this.cacheStore.setIdentifier(null)
+          }
+          if (this.$route.query.pid && this.identifier.id !== Number(this.$route.query.pid)) {
+            this.cacheStore.setIdentifier(null)
+          }
         }
         /* load database and optional access */
         this.cacheStore.setRouteAccess(newObj.database_id, this.cacheUser?.uid)
diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json
index 2e94f572ec..07ac0163ef 100644
--- a/dbrepo-ui/locales/en-US.json
+++ b/dbrepo-ui/locales/en-US.json
@@ -606,10 +606,10 @@
       },
       "status": {
         "title": "Status",
-        "public": "Public",
+        "public": "Visible",
         "data": "Data-only",
         "schema": "Schema-only",
-        "draft": "Draft"
+        "draft": "Hidden"
       },
       "resource": {
         "data": {
diff --git a/dbrepo-ui/pages/database/[database_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/info.vue
index 8340dfe955..025cc9c4c4 100644
--- a/dbrepo-ui/pages/database/[database_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/info.vue
@@ -1,26 +1,27 @@
 <template>
   <div
-    v-if="canViewSchema">
+    v-if="identifier || canViewInfo">
     <DatabaseToolbar />
     <v-window
       v-model="tab">
       <v-window-item value="1">
         <Summary
-          v-if="hasIdentifier"
+          v-if="identifier"
           :identifier="identifier" />
         <v-card
-          v-if="hasIdentifier"
+          v-if="identifier"
           variant="flat"
           rounded="0">
           <v-card-text>
             <Select
-              :identifiers="filteredIdentifiers"
+              :identifiers="identifiers"
               :identifier="identifier" />
           </v-card-text>
         </v-card>
         <v-divider
-          v-if="hasIdentifier" />
+          v-if="identifier" />
         <v-card
+          v-if="canViewInfo"
           :title="$t('pages.database.title')"
           variant="flat"
           rounded="0">
@@ -94,7 +95,7 @@
                 <div>
                   <UserBadge
                     :user="database.owner"
-                    :other-user="user" />
+                    :other-user="cacheUser" />
                 </div>
               </v-list-item>
               <v-list-item
@@ -104,7 +105,7 @@
                 <div>
                   <UserBadge
                     :user="database.contact"
-                    :other-user="user" />
+                    :other-user="cacheUser" />
                 </div>
               </v-list-item>
             </v-list>
@@ -166,7 +167,20 @@
 <script setup>
 import { ref } from 'vue'
 
-const { loggedIn, user, login, logout } = useOidcAuth()
+const config = useRuntimeConfig()
+const { pid } = useRoute().query
+const { database_id } = useRoute().params
+const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&type=database&status=published`)
+
+if (data.value && data.value.length > 0) {
+  const identifierService = useIdentifierService()
+  useServerHead(identifierService.identifiersToServerHead(data.value))
+  useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value))
+}
+const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null)
+
+const cacheStore = useCacheStore()
+cacheStore.setIdentifier(identifier)
 </script>
 <script>
 import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue'
@@ -209,7 +223,7 @@ export default {
       return 0
     },
     description () {
-      if (!this.hasIdentifier) {
+      if (!this.identifier) {
         return ''
       }
       return this.database.identifier.description
@@ -221,7 +235,7 @@ export default {
       return this.$config.public.database.image.height
     },
     publisher () {
-      if (!this.hasIdentifier) {
+      if (!this.identifier) {
         return ''
       }
       return this.database.identifier.publisher
@@ -232,32 +246,14 @@ export default {
     cacheUser () {
       return this.cacheStore.getUser
     },
-    identifiers () {
-      if (!this.database) {
-        return []
-      }
-      return this.database.identifiers
+    access () {
+      return this.cacheStore.getAccess
     },
-    filteredIdentifiers () {
-      if (!this.identifiers) {
+    identifiers () {
+      if (!this.database || !this.database.identifiers) {
         return []
       }
-      if (!this.cacheUser) {
-        return this.identifiers.filter(i => i.status === 'published')
-      }
-      return this.identifiers.filter(i => i.status === 'published' || i.owner.id === this.cacheUser.uid)
-    },
-    identifier () {
-      if (this.pid) {
-        const filter = this.filteredIdentifiers.filter(i => i.id === Number(this.pid))
-        if (filter.length > 0) {
-          return filter[0]
-        }
-      }
-      return this.filteredIdentifiers[0]
-    },
-    access () {
-      return this.cacheStore.getAccess
+      return this.database.identifiers.filter(i => i.query_id === Number(this.$route.params.subset_id))
     },
     pid () {
       return this.$route.query.pid
@@ -300,9 +296,6 @@ export default {
       const databaseService = useDatabaseService()
       return databaseService.databaseToOwner(this.database)
     },
-    hasIdentifier () {
-      return this.identifier
-    },
     accessDescription () {
       if (!this.access) {
         return
@@ -335,19 +328,19 @@ export default {
       }
       return this.database.preview_image
     },
-    canViewSchema () {
-      if (this.error) {
-        return false
-      }
+    canViewInfo () {
       if (!this.database) {
         return false
       }
-      if (this.database.is_schema_public) {
+      if (this.database.is_public || this.database.is_schema_public) {
         return true
       }
+      if (!this.access) {
+        return false
+      }
       const userService = useUserService()
       return userService.hasReadAccess(this.access)
-    }
+    },
   }
 }
 </script>
diff --git a/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue
index 61d34fb5c2..505a765123 100644
--- a/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/persist/[identifier_id]/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canCreateIdentifier || canUpdateIdentifier">
+    v-if="canPersistIdentifier || canUpdateIdentifier">
     <Persist
       type="database"
       :database="database" />
@@ -51,6 +51,9 @@ export default {
     cacheUser () {
       return this.cacheStore.getUser
     },
+    access () {
+      return this.cacheStore.getAccess
+    },
     identifier () {
       if (!this.database) {
         return false
@@ -58,26 +61,30 @@ export default {
       const filter = this.database.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id))
       return filter.length === 1 ? filter[0] : null
     },
-    canCreateIdentifier () {
-      if (!this.roles) {
+    canPersistIdentifier () {
+      if (!this.database || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.database) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('create-identifier') && this.database.owner.id === this.cacheUser.uid
+      const userService = useUserService()
+      return userService.hasReadAccess(this.access) && this.database.owner.id === this.cacheUser.uid
     },
     canUpdateIdentifier () {
-      if (!this.roles) {
+      if (!this.identifier || !this.roles) {
         return false
       }
-      if (!this.identifier) {
+      if (this.roles.includes('modify-identifier-metadata')) {
+        return true
+      }
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('modify-identifier-metadata') && this.identifier.owner.id === this.cacheUser.uid
+      return this.identifier.owner.id === this.cacheUser.uid
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/persist/index.vue
index 52e36cd0d5..4bbf0e8d2a 100644
--- a/dbrepo-ui/pages/database/[database_id]/persist/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/persist/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canPersistDatabase">
+    v-if="canPersistIdentifier">
     <Persist
       type="database"
       :database="database" />
@@ -50,14 +50,14 @@ export default {
       }
       return this.database.owner.id === this.cacheUser.uid
     },
-    canPersistDatabase () {
-      if (!this.database || !this.roles) {
+    canPersistIdentifier () {
+      if (!this.database || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.roles.includes('create-identifier') || !this.cacheUser || !this.access) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
       const userService = useUserService()
diff --git a/dbrepo-ui/pages/database/[database_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/settings.vue
index 26ddf9478c..1556dcc387 100644
--- a/dbrepo-ui/pages/database/[database_id]/settings.vue
+++ b/dbrepo-ui/pages/database/[database_id]/settings.vue
@@ -462,7 +462,7 @@ export default {
         .then((database) => {
           const toast = useToastInstance()
           toast.success(this.$t('success.database.visibility'))
-          this.cacheStore.setDatabase(database)
+          this.cacheStore.reloadDatabase()
         })
         .catch(() => {
           this.loading = false
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 30bebeb2b8..682fc59b98 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
@@ -18,7 +18,7 @@
       </v-toolbar-title>
       <v-spacer />
       <v-btn
-        v-if="canDownload"
+        v-if="canViewSubsetData"
         :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-download' : null"
         variant="flat"
         :loading="downloadLoading"
@@ -97,34 +97,22 @@ export default {
     subset () {
       return this.cacheStore.getSubset
     },
+    access () {
+      return this.cacheStore.getAccess
+    },
     executionUTC () {
       if (!this.subset) {
         return null
       }
       return formatTimestampUTCLabel(this.subset.created)
     },
-    canDownload () {
-      if (!this.result_visibility || !this.subset.id) {
-        return false
-      }
-      return this.subset.id
-    },
-    result_visibility () {
+    canViewSubsetData () {
       if (!this.database || !this.subset) {
         return false
       }
       if (this.database.is_public) {
         return true
       }
-      return this.subset.owner.username === this.username
-    },
-    canViewSubsetData () {
-      if (this.error || !this.subset) {
-        return false
-      }
-      if (this.subset.is_public) {
-        return true
-      }
       if (!this.access) {
         return false
       }
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 eeb0a70c3e..db5d45b461 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
@@ -1,36 +1,29 @@
 <template>
-  <div>
+  <div
+    v-if="identifier || canViewInfo">
     <SubsetToolbar />
     <v-card
       variant="flat"
       rounded="0">
       <Summary
-        v-if="hasIdentifier"
+        v-if="identifier"
         :identifier="identifier" />
       <v-card-text
-        v-if="hasIdentifier">
+        v-if="identifier">
         <Select
           :identifiers="identifiers"
           :identifier="identifier" />
       </v-card-text>
     </v-card>
     <v-divider
-      v-if="subset && identifier" />
+      v-if="canViewInfo && identifier" />
     <v-card
+      v-if="canViewInfo"
       variant="flat"
       rounded="0"
       :title="$t('pages.subset.title')">
       <v-card-text>
         <v-list
-          v-if="!subset"
-          lines="two"
-          dense>
-          <v-skeleton-loader
-            type="list-item-three-line"
-            width="50%" />
-        </v-list>
-        <v-list
-          v-else-if="subset"
           lines="two"
           dense>
           <v-list-item
@@ -88,6 +81,24 @@
   </div>
 </template>
 
+<script setup>
+import { ref } from 'vue'
+
+const config = useRuntimeConfig()
+const { pid } = useRoute().query
+const { database_id, subset_id } = useRoute().params
+const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&qid=${subset_id}&type=subset&status=published`)
+
+if (data.value && data.value.length > 0) {
+  const identifierService = useIdentifierService()
+  useServerHead(identifierService.identifiersToServerHead(data.value))
+  useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value))
+}
+const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null)
+
+const cacheStore = useCacheStore()
+cacheStore.setIdentifier(identifier)
+</script>
 <script>
 import Summary from '@/components/identifier/Summary.vue'
 import SubsetToolbar from '@/components/subset/SubsetToolbar.vue'
@@ -152,25 +163,26 @@ export default {
       return this.cacheStore.getSubset
     },
     identifiers () {
-      if (!this.database || !this.database.subsets || this.database.subsets.length === 0) {
+      if (!this.database || !this.database.subsets) {
         return []
       }
-      return this.database.subsets.filter(s => s.query_id === Number(this.$route.params.subset_id))
-    },
-    hasIdentifier () {
-      return this.identifiers.length > 0
+      return this.database.subsets.filter(i => i.query_id === Number(this.$route.params.subset_id))
     },
-    identifier () {
-      if (this.pid) {
-        const filter = this.identifiers.filter(i => i.id === Number(this.pid))
-        if (filter.length > 0) {
-          return filter[0]
-        }
+    canViewInfo () {
+      if (!this.database) {
+        return false
+      }
+      if (this.database.is_public || this.database.is_schema_public) {
+        return true
+      }
+      if (!this.access) {
+        return false
       }
-      return this.identifiers[0]
+      const userService = useUserService()
+      return userService.hasReadAccess(this.access)
     },
     title () {
-      if (!this.hasIdentifier) {
+      if (!this.identifier) {
         return null
       }
       const enTitle = this.identifier.titles.filter(t => t.language).filter(t => t.language === 'en')
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue
index f50b9788c7..78878a0015 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/[identifier_id]/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canCreateIdentifier || canUpdateIdentifier">
+    v-if="canPersistIdentifier || canUpdateIdentifier">
     <Persist
       type="subset"
       :database="database" />
@@ -59,6 +59,12 @@ export default {
     subset () {
       return this.cacheStore.getSubset
     },
+    access () {
+      return this.cacheStore.getAccess
+    },
+    cacheUser () {
+      return this.cacheStore.getUser
+    },
     identifier () {
       if (!this.subset) {
         return false
@@ -66,26 +72,30 @@ export default {
       const filter = this.subset.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id))
       return filter.length === 1 ? filter[0] : null
     },
-    canCreateIdentifier () {
-      if (!this.roles) {
+    canPersistIdentifier () {
+      if (!this.subset || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.subset) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('create-identifier') && this.subset.owner.id === this.cacheUser.uid
+      const userService = useUserService()
+      return userService.hasReadAccess(this.access) && this.subset.owner.id === this.cacheUser.uid
     },
     canUpdateIdentifier () {
-      if (!this.roles) {
+      if (!this.identifier || !this.roles) {
         return false
       }
-      if (!this.identifier) {
+      if (this.roles.includes('modify-identifier-metadata')) {
+        return true
+      }
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('modify-identifier-metadata') && this.identifier.owner.id === this.cacheUser.uid
+      return this.identifier.owner.id === this.cacheUser.uid
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue
index d8e7510787..88209f5018 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/persist/index.vue
@@ -1,10 +1,10 @@
 <template>
   <div
-    v-if="canPersistSubset">
+    v-if="canPersistIdentifier">
     <Persist
       type="subset"
       :database="database"
-      :query="query" />
+      :query="subset" />
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
 </template>
@@ -20,8 +20,6 @@ export default {
   data () {
     return {
       loading: false,
-      loadingQuery: false,
-      query: null,
       isAuthorizationError: false,
       items: [
         {
@@ -59,44 +57,25 @@ export default {
     subset () {
       return this.cacheStore.getSubset
     },
-    canPersistSubset () {
-      if (!this.subset || !this.roles) {
+    roles () {
+      return this.cacheStore.getRoles
+    },
+    cacheUser () {
+      return this.cacheStore.getUser
+    },
+    canPersistIdentifier () {
+      if (!this.subset || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.roles.includes('create-identifier') || !this.cacheUser || !this.access) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
       const userService = useUserService()
       return userService.hasReadAccess(this.access) && this.subset.owner.id === this.cacheUser.uid
     }
-  },
-  mounted () {
-    this.loadQuery()
-  },
-  methods: {
-    loadQuery () {
-      this.loadingQuery = true
-      return new Promise((resolve, reject) => {
-        const queryService = useQueryService()
-        queryService.findOne(this.$route.params.database_id, this.$route.params.subset_id)
-          .then((query) => {
-            this.query = query
-            resolve(query)
-          })
-          .catch((error) => {
-            if (error.response.status === 405) {
-              this.isAuthorizationError = true
-            }
-            reject(error)
-          })
-          .finally(() => {
-            this.loadingQuery = false
-          })
-      })
-    }
   }
 }
 </script>
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/create.vue b/dbrepo-ui/pages/database/[database_id]/subset/create.vue
index 94fd8e8ec9..c3d07bba15 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/create.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/create.vue
@@ -52,6 +52,9 @@ export default {
       if (this.database.is_public) {
         return true
       }
+      if (!this.access) {
+        return false
+      }
       const userService = useUserService()
       return userService.hasReadAccess(this.access)
     }
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue
index 4811e5c47c..efbcd6accf 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/import.vue
@@ -81,6 +81,9 @@ export default {
     cacheUser () {
       return this.cacheStore.getUser
     },
+    access () {
+      return this.cacheStore.getAccess
+    },
     title () {
       if (!this.table) {
         return this.$t('pages.table.import.title')
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 dad79a6fa8..89e2714115 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,23 +1,23 @@
 <template>
   <div
-    v-if="canViewInfo">
+    v-if="identifier || canViewInfo">
     <TableToolbar
       :selection="selection" />
     <v-card
+      v-if="identifier"
       variant="flat">
       <Summary
-        v-if="hasIdentifier"
         :identifier="identifier" />
-      <v-card-text
-        v-if="hasIdentifier">
+      <v-card-text>
         <Select
           :identifiers="identifiers"
           :identifier="identifier" />
       </v-card-text>
     </v-card>
     <v-divider
-      v-if="identifier" />
+      v-if="canViewInfo" />
     <v-card
+      v-if="canViewInfo"
       variant="flat"
       rounded="0"
       :title="$t('pages.table.title')">
@@ -118,6 +118,24 @@
   </div>
 </template>
 
+<script setup>
+import { ref } from 'vue'
+
+const config = useRuntimeConfig()
+const { pid } = useRoute().query
+const { database_id, table_id } = useRoute().params
+const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&tid=${table_id}&type=table&status=published`)
+
+if (data.value && data.value.length > 0) {
+  const identifierService = useIdentifierService()
+  useServerHead(identifierService.identifiersToServerHead(data.value))
+  useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value))
+}
+const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null)
+
+const cacheStore = useCacheStore()
+cacheStore.setIdentifier(identifier)
+</script>
 <script>
 import TableToolbar from '@/components/table/TableToolbar.vue'
 import Select from '@/components/identifier/Select.vue'
@@ -194,7 +212,7 @@ export default {
       return userService.hasReadAccess(this.access)
     },
     canViewInfo () {
-      if (this.error || !this.table) {
+      if (!this.table) {
         return false
       }
       if (this.table.is_public || this.table.is_schema_public) {
@@ -223,31 +241,10 @@ export default {
       return this.roles.includes('insert-table-data')
     },
     identifiers () {
-      if (!this.table || !this.table.identifiers || this.table.identifiers.length === 0) {
-        return []
-      }
-      return this.table.identifiers
-    },
-    filteredIdentifiers () {
-      if (!this.identifiers) {
+      if (!this.table || !this.table.identifiers) {
         return []
       }
-      if (!this.cacheUser) {
-        return this.identifiers.filter(i => i.status === 'published')
-      }
-      return this.identifiers.filter(i => i.status === 'published' || i.owned_by === this.cacheUser.uid)
-    },
-    identifier () {
-      if (this.pid) {
-        const filter = this.filteredIdentifiers.filter(i => i.id === Number(this.pid))
-        if (filter.length > 0) {
-          return filter[0]
-        }
-      }
-      return this.filteredIdentifiers[0]
-    },
-    hasIdentifier () {
-      return this.identifier
+      return this.table.identifiers.filter(i => i.query_id === Number(this.$route.params.subset_id))
     },
     brokerExtraInfo () {
       return this.$config.public.broker.extra
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue
index 39c0ea1aad..e2d16e8db4 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/[identifier_id]/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canCreateIdentifier || canUpdateIdentifier">
+    v-if="canPersistIdentifier || canUpdateIdentifier">
     <Persist type="table" :database="database" />
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
@@ -57,6 +57,12 @@ export default {
     table () {
       return this.cacheStore.getTable
     },
+    cacheUser () {
+      return this.cacheStore.getUser
+    },
+    access () {
+      return this.cacheStore.getAccess
+    },
     identifier () {
       if (!this.table) {
         return false
@@ -64,26 +70,30 @@ export default {
       const filter = this.table.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id))
       return filter.length === 1 ? filter[0] : null
     },
-    canCreateIdentifier () {
-      if (!this.roles) {
+    canPersistIdentifier () {
+      if (!this.table || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.table) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('create-identifier') && this.table.owner.id === this.cacheUser.uid
+      const userService = useUserService()
+      return userService.hasReadAccess(this.access) && this.table.owner.id === this.cacheUser.uid
     },
     canUpdateIdentifier () {
-      if (!this.roles) {
+      if (!this.identifier || !this.roles) {
         return false
       }
-      if (!this.identifier) {
+      if (this.roles.includes('modify-identifier-metadata')) {
+        return true
+      }
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('modify-identifier-metadata') && this.identifier.owner.id === this.cacheUser.uid
+      return this.identifier.owner.id === this.cacheUser.uid
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue
index 9d75edae1b..6c26187fa2 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/persist/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canPersistTable">
+    v-if="canPersistIdentifier">
     <Persist
       type="table"
       :database="database"
@@ -60,14 +60,20 @@ export default {
     table () {
       return this.cacheStore.getTable
     },
-    canPersistTable () {
-      if (!this.table || !this.roles) {
+    roles () {
+      return this.cacheStore.getRoles
+    },
+    cacheUser () {
+      return this.cacheStore.getUser
+    },
+    canPersistIdentifier () {
+      if (!this.table || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.roles.includes('create-identifier') || !this.cacheUser || !this.access) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
       const userService = useUserService()
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue
index 860d4819bb..4c5b046742 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue
@@ -121,8 +121,6 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
-
 const { loggedIn } = useOidcAuth()
 </script>
 <script>
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue
index d9c98f6111..0f0a8feab6 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue
@@ -22,9 +22,6 @@
                   <v-textarea
                     v-model="modify.description"
                     rows="2"
-                    :rules="[
-                      v => max(v, 180) || ($t('validation.max-length') + 180),
-                    ]"
                     clearable
                     counter="180"
                     persistent-counter
@@ -125,7 +122,7 @@ export default {
   data () {
     return {
       tab: 0,
-      valid: false,
+      valid: true,
       loading: false,
       modify: {
         description: null,
@@ -196,10 +193,10 @@ export default {
       if (!this.table) {
         return false
       }
-      if (this.table.is_public !== this.modify.is_public) {
+      if (this.table.is_public !== this.modify.is_public || this.table.is_schema_public !== this.modify.is_schema_public) {
         return true
       }
-      return this.table.is_schema_public !== this.modify.is_schema_public
+      return this.table.description !== this.modify.description
     },
     canUpdateTable () {
       if (!this.cacheUser || !this.table || !this.access || !this.roles || !this.roles.includes('update-table')) {
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 a9ddd46929..24aed7f2ff 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue
@@ -92,9 +92,6 @@
                       <v-textarea
                         v-model="tableCreate.description"
                         rows="2"
-                        :rules="[
-                          v => (!!v || v.length <= 180) || ($t('validation.max-length') + 180),
-                        ]"
                         clearable
                         counter="180"
                         persistent-counter
diff --git a/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue
index d6bd4414df..804ae03c15 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue
@@ -70,9 +70,6 @@
                     <v-textarea
                       v-model="tableCreate.description"
                       rows="2"
-                      :rules="[
-                        v => (!v || v.length <= 180) || $t('validation.max-length') + 180
-                      ]"
                       clearable
                       counter="180"
                       persistent-counter
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 b492476b97..2b0936cba5 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
@@ -34,12 +34,6 @@
   </div>
 </template>
 
-<script setup>
-import { ref } from 'vue'
-
-const { loggedIn, user, login, logout } = useOidcAuth()
-const cacheUser = ref(loggedIn ? user.value?.cacheUser : null)
-</script>
 <script>
 import TimeDrift from '@/components/TimeDrift.vue'
 import QueryResults from '@/components/subset/Results.vue'
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 75eca0100f..3c0c40e33c 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
@@ -1,25 +1,25 @@
 <template>
   <div
-    v-if="canViewView">
+    v-if="identifier || canViewInfo">
     <ViewToolbar />
     <v-window
       v-model="tab">
-      <v-window-item
-        v-if="view">
+      <v-window-item>
         <v-card variant="flat">
           <Summary
-            v-if="hasIdentifier"
+            v-if="identifier"
             :identifier="identifier" />
           <v-card-text
-            v-if="hasIdentifier">
+            v-if="identifier">
             <Select
               :identifiers="identifiers"
               :identifier="identifier" />
           </v-card-text>
         </v-card>
         <v-divider
-          v-if="hasIdentifier" />
+          v-if="identifier" />
         <v-card
+          v-if="canViewInfo"
           :title="$t('pages.view.title')"
           variant="flat">
           <v-card-text>
@@ -39,7 +39,7 @@
                 <UserBadge
                   v-if="view"
                   :user="view.owner"
-                  :other-user="user" />
+                  :other-user="cacheUser" />
                 <v-skeleton-loader
                   v-else
                   type="subtitle"
@@ -59,6 +59,24 @@
   </div>
 </template>
 
+<script setup>
+import { ref } from 'vue'
+
+const config = useRuntimeConfig()
+const { pid } = useRoute().query
+const { database_id, view_id } = useRoute().params
+const { data } = await useFetch(`${config.public.api.client}/api/identifier?dbid=${database_id}&vid=${view_id}&type=view&status=published`)
+
+if (data.value && data.value.length > 0) {
+  const identifierService = useIdentifierService()
+  useServerHead(identifierService.identifiersToServerHead(data.value))
+  useServerSeoMeta(identifierService.identifiersToServerSeoMeta(data.value))
+}
+const identifier = ref(data.value && data.value.length > 0 ? (pid && data.value.filter(i => i.id === Number(pid)).length > 0 ? data.value.filter(i => i.id === Number(pid))[0] : data.value[0]) : null)
+
+const cacheStore = useCacheStore()
+cacheStore.setIdentifier(identifier)
+</script>
 <script>
 import ViewToolbar from '@/components/view/ViewToolbar.vue'
 import Summary from '@/components/identifier/Summary.vue'
@@ -122,28 +140,10 @@ export default {
       return this.cacheStore.getUser
     },
     identifiers () {
-      if (!this.view) {
-        return []
-      }
-      return this.view.identifiers
-    },
-    filteredIdentifiers () {
-      if (!this.identifiers) {
+      if (!this.view || !this.view.identifiers) {
         return []
       }
-      if (!this.cacheUser) {
-        return this.identifiers.filter(i => i.status === 'published')
-      }
-      return this.identifiers.filter(i => i.status === 'published' || i.owner.id === this.cacheUser.uid)
-    },
-    identifier () {
-      if (this.pid) {
-        const filter = this.filteredIdentifiers.filter(i => i.id === Number(this.pid))
-        if (filter.length > 0) {
-          return filter[0]
-        }
-      }
-      return this.filteredIdentifiers[0]
+      return this.view.identifiers.filter(i => i.query_id === Number(this.$route.params.subset_id))
     },
     views () {
       if (!this.database) {
@@ -154,9 +154,6 @@ export default {
     pid () {
       return this.$route.query.pid
     },
-    hasIdentifier () {
-      return this.identifier
-    },
     creator () {
       if (!this.view) {
         return null
@@ -164,7 +161,7 @@ export default {
       const userService = useUserService()
       return userService.userToFullName(this.view.creator)
     },
-    canViewView () {
+    canViewInfo () {
       if (!this.view) {
         return false
       }
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue
index c220a8aa9b..540bbbdb5e 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/[identifier_id]/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canCreateIdentifier || canUpdateIdentifier">
+    v-if="canPersistIdentifier || canUpdateIdentifier">
     <Persist
       type="view"
       :database="database" />
@@ -59,6 +59,12 @@ export default {
     view () {
       return this.cacheStore.getView
     },
+    access () {
+      return this.cacheStore.getAccess
+    },
+    cacheUser () {
+      return this.cacheStore.getUser
+    },
     identifier () {
       if (!this.view) {
         return false
@@ -66,26 +72,30 @@ export default {
       const filter = this.view.identifiers.filter(i => i.id === Number(this.$route.params.identifier_id))
       return filter.length === 1 ? filter[0] : null
     },
-    canCreateIdentifier () {
-      if (!this.roles) {
+    canPersistIdentifier () {
+      if (!this.view || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.view) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('create-identifier') && this.view.owner.id === this.cacheUser.uid
+      const userService = useUserService()
+      return userService.hasReadAccess(this.access) && this.view.owner.id === this.cacheUser.uid
     },
     canUpdateIdentifier () {
-      if (!this.roles) {
+      if (!this.identifier || !this.roles) {
         return false
       }
-      if (!this.identifier) {
+      if (this.roles.includes('modify-identifier-metadata')) {
+        return true
+      }
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
-      return this.roles.includes('modify-identifier-metadata') && this.identifier.owner.id === this.cacheUser.uid
+      return this.identifier.owner.id === this.cacheUser.uid
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue
index 2a8d010db0..ed8067d213 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/persist/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="canPersistView">
+    v-if="canPersistIdentifier">
     <Persist
       type="view"
       :database="database"
@@ -60,14 +60,17 @@ export default {
     view () {
       return this.cacheStore.getView
     },
-    canPersistView () {
-      if (!this.view || !this.roles) {
+    roles () {
+      return this.cacheStore.getRoles
+    },
+    canPersistIdentifier () {
+      if (!this.view || !this.roles || !this.cacheUser || !this.access) {
         return false
       }
       if (this.roles.includes('create-foreign-identifier')) {
         return true
       }
-      if (!this.roles.includes('create-identifier') || !this.cacheUser || !this.access) {
+      if (!this.roles.includes('create-identifier')) {
         return false
       }
       const userService = useUserService()
diff --git a/dbrepo-ui/pages/database/[database_id]/view/create.vue b/dbrepo-ui/pages/database/[database_id]/view/create.vue
index c3a0e73f49..a834bdb5c9 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/create.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/create.vue
@@ -8,6 +8,7 @@
 
 <script>
 import Builder from '@/components/subset/Builder.vue'
+import { useCacheStore } from '@/stores/cache.js'
 
 export default {
   components: {
@@ -33,7 +34,8 @@ export default {
           to: `/database/${this.$route.params.database_id}/view/create`,
           disabled: true
         }
-      ]
+      ],
+      cacheStore: useCacheStore()
     }
   },
   computed: {
@@ -44,11 +46,11 @@ export default {
       return this.cacheStore.getRoles
     },
     canCreateView () {
-      if (!this.roles) {
+      if (!this.roles || !this.roles.includes('create-database-view')) {
         return false
       }
       const userService = useUserService()
-      return userService.hasReadAccess(this.access) && this.roles.includes('create-database-view')
+      return userService.hasReadAccess(this.access)
     }
   }
 }
diff --git a/dbrepo-ui/stores/cache.js b/dbrepo-ui/stores/cache.js
index 8ba0d2702f..c2e34f48bb 100644
--- a/dbrepo-ui/stores/cache.js
+++ b/dbrepo-ui/stores/cache.js
@@ -10,6 +10,7 @@ export const useCacheStore = defineStore('cache', {
       access: null,
       subset: null,
       locale: null,
+      identifier: null,
       ontologies: [],
       messages: [],
       user: null,
@@ -24,6 +25,7 @@ export const useCacheStore = defineStore('cache', {
     getAccess: (state) => state.access,
     getSubset: (state) => state.subset,
     getLocale: (state) => state.locale,
+    getIdentifier: (state) => state.identifier,
     getOntologies: (state) => state.ontologies,
     getMessages: (state) => state.messages,
     getUser: (state) => state.user,
@@ -49,6 +51,9 @@ export const useCacheStore = defineStore('cache', {
     setLocale(locale) {
       this.locale = locale
     },
+    setIdentifier(identifier) {
+      this.identifier = identifier
+    },
     setOntologies(ontologies) {
       this.ontologies = ontologies
     },
diff --git a/docker-compose.yml b/docker-compose.yml
index ef7e6a8a32..ed0f7e26c0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -486,7 +486,7 @@ services:
       LDAP_ADMIN_PASSWORD: "${IDENTITY_SERVICE_ADMIN_PASSWORD:-admin}"
       LDAP_ROOT: "${IDENTITY_SERVICE_ROOT:-dc=dbrepo,dc=at}"
     healthcheck:
-      test: test -f /opt/bitnami/grafana/tmp/grafana.pid
+      test: curl -fsSL --head http://127.0.0.1:3000
       interval: 10s
       timeout: 5s
       retries: 12
diff --git a/make/build.mk b/make/build.mk
index bc6dfc56a7..800c879c97 100644
--- a/make/build.mk
+++ b/make/build.mk
@@ -14,6 +14,10 @@ build-data-service: ## Build the Data Service.
 build-metadata-service: ## Build the Metadata Service.
 	mvn -f ./dbrepo-metadata-service/pom.xml clean package -DskipTests
 
+.PHONY: build-auth-event-listener
+build-auth-event-listener: ## Build the Auth Service Event Listener.
+	mvn -f ./dbrepo-auth-service/listeners/pom.xml clean package -DskipTests
+
 .PHONY: build-ui
 build-ui: ## Build the UI.
 	bun --cwd ./dbrepo-ui build
diff --git a/make/dev.mk b/make/dev.mk
index 0282dbbce2..d8da31086b 100644
--- a/make/dev.mk
+++ b/make/dev.mk
@@ -1,7 +1,7 @@
 ##@ Development
 
 .PHONY: start-dev
-start-dev: build-images ## Start the development deployment.
+start-dev: build-images build-auth-event-listener ## Start the development deployment.
 	docker container stop dbrepo-gateway-service || true
 	docker container rm dbrepo-gateway-service || true
 	docker compose up -d
-- 
GitLab