From cbe1a94546c0d55211165b8d533a5b5d09486cd9 Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Sun, 28 Jan 2024 21:51:38 +0000
Subject: [PATCH] Resolve "Add teaser image per database for the frontend (e.g.
 university logo)"

---
 .docs/.swagger/api-metadata.yaml              | 994 +++++++++---------
 .../dbrepo-realm.json                         |  58 +-
 dbrepo-metadata-db/setup-schema.sql           |   1 +
 dbrepo-metadata-service/Dockerfile            |   1 +
 .../at/tuwien/api/database/DatabaseDto.java   |   3 +
 .../api/database/DatabaseModifyImageDto.java  |  17 +
 .../at/tuwien/entities/database/Database.java |   6 +
 .../main/java/at/tuwien/mapper/S3Mapper.java  |  18 -
 .../at/tuwien/endpoints/DatabaseEndpoint.java |  76 +-
 .../tuwien/endpoints/MaintenanceEndpoint.java |   7 +-
 .../src/main/resources/application-local.yml  |   2 +
 .../src/main/resources/application.yml        |   2 +
 .../at/tuwien/annotations/MockListeners.java  |   3 +-
 .../test/java/at/tuwien/config/S3Config.java  |  26 +-
 .../endpoints/DatabaseEndpointUnitTest.java   |  47 +
 .../tuwien/mvc/PrometheusEndpointMvcTest.java |   9 +-
 .../StorageServiceIntegrationTest.java        |  79 ++
 .../src/test/resources/application.properties |   6 +-
 .../main/java/at/tuwien/config/S3Config.java  |   3 +
 .../at/tuwien/listener/StorageListener.java   |  15 +
 .../listener/impl/StorageListenerImpl.java    |  34 +
 .../at/tuwien/service/DatabaseService.java    |  12 +
 .../at/tuwien/service/StorageService.java     |  65 ++
 .../service/impl/MariaDbServiceImpl.java      |  16 +-
 .../tuwien/service/impl/QueryServiceImpl.java |  38 +-
 .../service/impl/SeaweedServiceImpl.java      | 123 +++
 .../main/java/at/tuwien/test/BaseTest.java    |   2 +-
 dbrepo-ui/api/database.service.js             |  14 +
 .../pages/database/_database_id/info.vue      |  12 +
 .../pages/database/_database_id/settings.vue  | 100 ++
 .../_database_id/table/_table_id/info.vue     |   4 +-
 docker-compose.yml                            |   1 +
 32 files changed, 1224 insertions(+), 570 deletions(-)
 create mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseModifyImageDto.java
 create mode 100644 dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/StorageServiceIntegrationTest.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/StorageListener.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/impl/StorageListenerImpl.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/service/StorageService.java
 create mode 100644 dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/SeaweedServiceImpl.java

diff --git a/.docs/.swagger/api-metadata.yaml b/.docs/.swagger/api-metadata.yaml
index e65ce0020b..df5304c003 100644
--- a/.docs/.swagger/api-metadata.yaml
+++ b/.docs/.swagger/api-metadata.yaml
@@ -38,14 +38,8 @@ paths:
           type: integer
           format: int64
       responses:
-        "409":
-          description: Query store failed to query table history
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Find table history is not permitted
+        "404":
+          description: "Table, database or user could not be found"
           content:
             application/json:
               schema:
@@ -64,8 +58,14 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/TableHistoryDto'
-        "404":
-          description: "Table, database or user could not be found"
+        "403":
+          description: Find table history is not permitted
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
+        "409":
+          description: Query store failed to query table history
           content:
             application/json:
               schema:
@@ -92,14 +92,8 @@ paths:
           type: integer
           format: int64
       responses:
-        "409":
-          description: Query store failed to query table history
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Find table history is not permitted
+        "404":
+          description: "Table, database or user could not be found"
           content:
             application/json:
               schema:
@@ -118,8 +112,14 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/TableHistoryDto'
-        "404":
-          description: "Table, database or user could not be found"
+        "403":
+          description: Find table history is not permitted
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
+        "409":
+          description: Query store failed to query table history
           content:
             application/json:
               schema:
@@ -190,24 +190,24 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "422":
-          description: Could not import csv via sidecar
+        "202":
+          description: Get table data successfully
           content:
-            application/json:
+            '*/*':
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
+                $ref: '#/components/schemas/QueryResultDto'
         "403":
           description: Access to the database is forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Get table data successfully
+        "422":
+          description: Could not import csv via sidecar
           content:
-            '*/*':
+            application/json:
               schema:
-                $ref: '#/components/schemas/QueryResultDto'
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -237,6 +237,12 @@ paths:
               $ref: '#/components/schemas/TableCsvDto'
         required: true
       responses:
+        "202":
+          description: Inserted data successfully
+          content:
+            '*/*':
+              schema:
+                type: object
         "404":
           description: Table or database could not be found
           content:
@@ -249,12 +255,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Inserted data successfully
-          content:
-            '*/*':
-              schema:
-                type: object
         "403":
           description: Access to the database is forbidden
           content:
@@ -290,22 +290,22 @@ paths:
               $ref: '#/components/schemas/TableCsvDeleteDto'
         required: true
       responses:
+        "202":
+          description: Deleted table data successfully
         "404":
           description: Table or database could not be found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Deleted table data successfully
-        "403":
-          description: Access to the database is forbidden
+        "400":
+          description: Table data or query is malformed
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "400":
-          description: Table data or query is malformed
+        "403":
+          description: Access to the database is forbidden
           content:
             application/json:
               schema:
@@ -375,24 +375,24 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "422":
-          description: Could not import csv via sidecar
+        "202":
+          description: Get table data successfully
           content:
-            application/json:
+            '*/*':
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
+                $ref: '#/components/schemas/QueryResultDto'
         "403":
           description: Access to the database is forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Get table data successfully
+        "422":
+          description: Could not import csv via sidecar
           content:
-            '*/*':
+            application/json:
               schema:
-                $ref: '#/components/schemas/QueryResultDto'
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -410,12 +410,6 @@ paths:
           type: string
           format: uuid
       responses:
-        "403":
-          description: Find user is not permitted
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "200":
           description: Found user
           content:
@@ -428,6 +422,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "403":
+          description: Find user is not permitted
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -450,20 +450,26 @@ paths:
               $ref: '#/components/schemas/UserUpdateDto'
         required: true
       responses:
-        "400":
-          description: Modify user query is malformed
+        "403":
+          description: Modify user is not permitted
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "202":
+          description: Modified user information
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UserDto'
         "405":
           description: Foreign user modification
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Modify user is not permitted
+        "400":
+          description: Modify user query is malformed
           content:
             application/json:
               schema:
@@ -474,12 +480,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Modified user information
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/UserDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -503,8 +503,8 @@ paths:
               $ref: '#/components/schemas/UserThemeSetDto'
         required: true
       responses:
-        "405":
-          description: Foreign user modification
+        "404":
+          description: User or user attribute was not found
           content:
             application/json:
               schema:
@@ -515,18 +515,18 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: User or user attribute was not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "202":
           description: Modified user theme
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/UserDto'
+        "405":
+          description: Foreign user modification
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -550,14 +550,14 @@ paths:
               $ref: '#/components/schemas/UserPasswordDto'
         required: true
       responses:
-        "405":
-          description: Foreign user modification
+        "404":
+          description: User was not found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Modify is not allowed
+        "503":
+          description: Authentication service does not respond
           content:
             application/json:
               schema:
@@ -568,14 +568,14 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/UserDto'
-        "503":
-          description: Authentication service does not respond
+        "405":
+          description: Foreign user modification
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: User was not found
+        "403":
+          description: Modify is not allowed
           content:
             application/json:
               schema:
@@ -656,16 +656,16 @@ paths:
           type: integer
           format: int64
       responses:
-        "202":
-          description: Deleted ontology successfully
-          content:
-            application/json: {}
         "404":
           description: Could not find ontology
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "202":
+          description: Deleted ontology successfully
+          content:
+            application/json: {}
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -763,18 +763,18 @@ paths:
           type: integer
           format: int64
       responses:
-        "200":
-          description: Found image
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ImageDto'
         "404":
           description: Image could not be found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Found image
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ImageDto'
     put:
       tags:
       - image-endpoint
@@ -853,24 +853,24 @@ paths:
               $ref: '#/components/schemas/DatabaseModifyVisibilityDto'
         required: true
       responses:
-        "202":
-          description: Visibility modified successfully
+        "404":
+          description: Database could not be found
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/DatabaseDto'
+                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Visibility modification is not permitted
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: Database could not be found
+        "202":
+          description: Visibility modified successfully
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
+                $ref: '#/components/schemas/DatabaseDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -894,12 +894,6 @@ paths:
               $ref: '#/components/schemas/DatabaseTransferDto'
         required: true
       responses:
-        "404":
-          description: Database or user could not be found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Transfer of ownership is not permitted
           content:
@@ -912,6 +906,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/DatabaseDto'
+        "404":
+          description: Database or user could not be found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -958,12 +958,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Access to the database is forbidden
+        "202":
+          description: Updated column semantics successfully
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
+                $ref: '#/components/schemas/ColumnDto'
         "400":
           description: Update semantic concept query is malformed or update unit of
             measurement query is malformed
@@ -971,21 +971,21 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Updated column semantics successfully
+        "403":
+          description: Access to the database is forbidden
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ColumnDto'
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
-  /api/database/{id}/access/{userId}:
+  /api/database/{id}/image:
     put:
       tags:
-      - access-endpoint
-      summary: Modify access to some database
-      operationId: update_4
+      - database-endpoint
+      summary: Modify database image
+      operationId: modifyImage
       parameters:
       - name: id
         in: path
@@ -993,48 +993,46 @@ paths:
         schema:
           type: integer
           format: int64
-      - name: userId
-        in: path
-        required: true
-        schema:
-          type: string
-          format: uuid
       requestBody:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/DatabaseModifyAccessDto'
+              $ref: '#/components/schemas/DatabaseModifyImageDto'
         required: true
       responses:
-        "403":
-          description: Modify access not permitted when no access is granted in the
-            first place
+        "410":
+          description: File was not found in the Storage Service
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: Database or user not found
+        "202":
+          description: Modify of image was successful
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/DatabaseDto'
+        "403":
+          description: Modify of image is not permitted
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "400":
-          description: Modify access query or database connection is malformed
+        "404":
+          description: Database or user could not be found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Modify access succeeded
       security:
       - bearerAuth: []
       - basicAuth: []
-    post:
+  /api/database/{id}/access/{userId}:
+    put:
       tags:
       - access-endpoint
-      summary: Give access to some database
-      operationId: create_6
+      summary: Modify access to some database
+      operationId: update_4
       parameters:
       - name: id
         in: path
@@ -1052,19 +1050,60 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/DatabaseGiveAccessDto'
+              $ref: '#/components/schemas/DatabaseModifyAccessDto'
         required: true
       responses:
-        "404":
+        "202":
+          description: Modify access succeeded
+        "404":
           description: Database or user not found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Granting access succeeded
-        "405":
-          description: Granting access not permitted
+        "403":
+          description: Modify access not permitted when no access is granted in the
+            first place
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
+        "400":
+          description: Modify access query or database connection is malformed
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
+      security:
+      - bearerAuth: []
+      - basicAuth: []
+    post:
+      tags:
+      - access-endpoint
+      summary: Give access to some database
+      operationId: create_6
+      parameters:
+      - name: id
+        in: path
+        required: true
+        schema:
+          type: integer
+          format: int64
+      - name: userId
+        in: path
+        required: true
+        schema:
+          type: string
+          format: uuid
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DatabaseGiveAccessDto'
+        required: true
+      responses:
+        "404":
+          description: Database or user not found
           content:
             application/json:
               schema:
@@ -1081,6 +1120,14 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "202":
+          description: Granting access succeeded
+        "405":
+          description: Granting access not permitted
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1103,22 +1150,22 @@ paths:
           type: string
           format: uuid
       responses:
-        "400":
-          description: Modify access query or database connection is malformed
+        "202":
+          description: Revoked access successfully
+        "403":
+          description: Revoke of access not permitted as no access was found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "User, database with access was not found"
+        "400":
+          description: Modify access query or database connection is malformed
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Revoked access successfully
-        "403":
-          description: Revoke of access not permitted as no access was found
+        "404":
+          description: "User, database with access was not found"
           content:
             application/json:
               schema:
@@ -1152,36 +1199,36 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "405":
-          description: Find query is not permitted
+        "504":
+          description: Query store failed to select query
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: Connection to the database failed
+        "404":
+          description: "Database, query or user could not be found"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, query or user could not be found"
+        "200":
+          description: List queries
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "504":
-          description: Query store failed to select query
+                $ref: '#/components/schemas/QueryDto'
+        "503":
+          description: Connection to the database failed
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: List queries
+        "405":
+          description: Find query is not permitted
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/QueryDto'
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1216,12 +1263,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/QueryDto'
-        "405":
-          description: Persist query is not permitted
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Not allowed to persist query
           content:
@@ -1246,6 +1287,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "405":
+          description: Persist query is not permitted
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1276,16 +1323,12 @@ paths:
               $ref: '#/components/schemas/SignupRequestDto'
         required: true
       responses:
-        "417":
-          description: User with e-mail already exists
+        "201":
+          description: Created user
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "400":
-          description: Parameters are not well-formed (likely email)
-          content:
-            application/json: {}
+                $ref: '#/components/schemas/UserBriefDto'
         "409":
           description: User with username already exists
           content:
@@ -1298,12 +1341,16 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "201":
-          description: Created user
+        "400":
+          description: Parameters are not well-formed (likely email)
+          content:
+            application/json: {}
+        "417":
+          description: User with e-mail already exists
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/UserBriefDto'
+                $ref: '#/components/schemas/ApiErrorDto'
   /api/semantic/ontology:
     get:
       tags:
@@ -1400,12 +1447,6 @@ paths:
               $ref: '#/components/schemas/ImageCreateDto'
         required: true
       responses:
-        "201":
-          description: Created image
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ImageDto'
         "400":
           description: Image specification is invalid
           content:
@@ -1418,6 +1459,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "201":
+          description: Created image
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ImageDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1483,26 +1530,20 @@ paths:
               $ref: '#/components/schemas/IdentifierSaveDto'
         required: true
       responses:
-        "400":
-          description: Identifier form contains invalid request data
+        "503":
+          description: DataCite system did not respond
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "406":
-          description: Creating identifier not allowed
+        "403":
+          description: Insufficient access rights or authorities
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "201":
-          description: Created identifier
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/IdentifierDto'
-        "502":
-          description: Query information could not be retrieved
+        "404":
+          description: "Failed to find database, table or view"
           content:
             application/json:
               schema:
@@ -1513,32 +1554,38 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Insufficient access rights or authorities
+        "400":
+          description: Identifier form contains invalid request data
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "405":
-          description: Creating identifier not permitted
+        "409":
+          description: Identifier for this resource already exists
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: DataCite system did not respond
+        "201":
+          description: Created identifier
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/IdentifierDto'
+        "405":
+          description: Creating identifier not permitted
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Failed to find database, table or view"
+        "502":
+          description: Query information could not be retrieved
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "409":
-          description: Identifier for this resource already exists
+        "406":
+          description: Creating identifier not allowed
           content:
             application/json:
               schema:
@@ -1598,12 +1645,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: Connection to the database failed
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "404":
           description: "Container, user or database could not be found"
           content:
@@ -1622,6 +1663,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "503":
+          description: Connection to the database failed
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1665,12 +1712,6 @@ paths:
           type: integer
           format: int64
       responses:
-        "404":
-          description: Database or user could not be found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "200":
           description: Find views successfully
           content:
@@ -1679,6 +1720,12 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/ViewBriefDto'
+        "404":
+          description: Database or user could not be found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1701,14 +1748,20 @@ paths:
               $ref: '#/components/schemas/ViewCreateDto'
         required: true
       responses:
-        "400":
-          description: Create view query is malformed
+        "423":
+          description: Create view resulted in an invalid query statement
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: Database or user could not be found
+        "201":
+          description: Create view successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ViewBriefDto'
+        "401":
+          description: Credentials missing
           content:
             application/json:
               schema:
@@ -1719,32 +1772,26 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "401":
-          description: Credentials missing
+        "503":
+          description: Connection to the database failed
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "201":
-          description: Create view successfully
+        "400":
+          description: Create view query is malformed
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ViewBriefDto'
+                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Credentials missing
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: Connection to the database failed
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "423":
-          description: Create view resulted in an invalid query statement
+        "404":
+          description: Database or user could not be found
           content:
             application/json:
               schema:
@@ -1772,6 +1819,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "404":
+          description: Database could not be found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
         "200":
           description: List tables
           content:
@@ -1780,12 +1833,6 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/TableBriefDto'
-        "404":
-          description: Database could not be found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1808,8 +1855,8 @@ paths:
               $ref: '#/components/schemas/TableCreateDto'
         required: true
       responses:
-        "400":
-          description: Create table query is malformed
+        "403":
+          description: Create table not permitted
           content:
             application/json:
               schema:
@@ -1820,20 +1867,20 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "201":
-          description: Created a new table
+        "404":
+          description: "Database, container or user could not be found"
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/TableBriefDto'
-        "403":
-          description: Create table not permitted
+                $ref: '#/components/schemas/ApiErrorDto'
+        "201":
+          description: Created a new table
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, container or user could not be found"
+                $ref: '#/components/schemas/TableBriefDto'
+        "400":
+          description: Create table query is malformed
           content:
             application/json:
               schema:
@@ -1867,12 +1914,6 @@ paths:
               $ref: '#/components/schemas/ImportDto'
         required: true
       responses:
-        "404":
-          description: Table or database could not be found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "202":
           description: Import table data successfully
         "400":
@@ -1881,14 +1922,14 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "409":
-          description: Import failed in sidecar
+        "404":
+          description: Table or database could not be found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "422":
-          description: Could not import csv via sidecar
+        "409":
+          description: Import failed in sidecar
           content:
             application/json:
               schema:
@@ -1899,6 +1940,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "422":
+          description: Could not import csv via sidecar
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -1921,21 +1968,20 @@ paths:
         schema:
           type: boolean
       responses:
-        "405":
-          description: Find all queries is not permitted
+        "501":
+          description: Image is not supported
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "501":
-          description: Image is not supported
+        "404":
+          description: "Database, container or user could not be found"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "423":
-          description: Selection of time-versioned query resulted in an invalid query
-            statement
+        "504":
+          description: Query store failed to select query
           content:
             application/json:
               schema:
@@ -1954,14 +2000,15 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/QueryBriefDto'
-        "504":
-          description: Query store failed to select query
+        "405":
+          description: Find all queries is not permitted
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, container or user could not be found"
+        "423":
+          description: Selection of time-versioned query resulted in an invalid query
+            statement
           content:
             application/json:
               schema:
@@ -2013,12 +2060,6 @@ paths:
               $ref: '#/components/schemas/ExecuteStatementDto'
         required: true
       responses:
-        "417":
-          description: Could not parse columns
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Execute query not permitted
           content:
@@ -2031,8 +2072,8 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, query or user could not be found"
+        "417":
+          description: Could not parse columns
           content:
             application/json:
               schema:
@@ -2043,6 +2084,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "404":
+          description: "Database, query or user could not be found"
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
         "202":
           description: Executed query
           content:
@@ -2086,6 +2133,12 @@ paths:
               $ref: '#/components/schemas/ContainerCreateRequestDto'
         required: true
       responses:
+        "201":
+          description: Created a new container
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ContainerBriefDto'
         "409":
           description: Container name already exists
           content:
@@ -2098,12 +2151,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "201":
-          description: Created a new container
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ContainerBriefDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2146,22 +2193,14 @@ paths:
         schema:
           type: string
       responses:
-        "200":
-          description: Found entities
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/EntityDto'
         "400":
           description: Filter params are invalid
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "422":
-          description: Ontology does not have rdf or sparql endpoint
+        "417":
+          description: Generated query or uri is malformed
           content:
             application/json:
               schema:
@@ -2172,12 +2211,20 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "417":
-          description: Generated query or uri is malformed
+        "422":
+          description: Ontology does not have rdf or sparql endpoint
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Found entities
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/EntityDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2262,6 +2309,14 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Suggested table column semantics successfully
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/TableColumnEntityDto'
         "404":
           description: Could not find the table column
           content:
@@ -2274,14 +2329,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: Suggested table column semantics successfully
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/TableColumnEntityDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2319,41 +2366,29 @@ paths:
         schema:
           type: string
       responses:
-        "409":
-          description: Exported resource was not found
+        "400":
+          description: "Identifier could not be exported, the requested style is not\
+            \ known"
           content:
-            text/csv:
+            text/bibliography:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: Found identifier successfully
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/IdentifierDto'
-            text/csv: {}
-            text/xml: {}
-            text/bibliography: {}
-            text/bibliography; style=apa: {}
-            text/bibliography; style=ieee: {}
-            text/bibliography; style=bibtex: {}
-        "422":
-          description: Failed to retrieve from database sidecar
+        "503":
+          description: Identifier could not exported from database as it is not reachable
           content:
             text/csv:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: Identifier could not be found
+        "409":
+          description: Exported resource was not found
           content:
             text/csv:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "400":
-          description: "Identifier could not be exported, the requested style is not\
-            \ known"
+        "422":
+          description: Failed to retrieve from database sidecar
           content:
-            text/bibliography:
+            text/csv:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
         "410":
@@ -2362,12 +2397,24 @@ paths:
             text/csv:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: Identifier could not exported from database as it is not reachable
+        "404":
+          description: Identifier could not be found
           content:
             text/csv:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Found identifier successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/IdentifierDto'
+            text/csv: {}
+            text/xml: {}
+            text/bibliography: {}
+            text/bibliography; style=apa: {}
+            text/bibliography; style=ieee: {}
+            text/bibliography; style=bibtex: {}
   /api/oai:
     get:
       tags:
@@ -2441,18 +2488,18 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: Database found successfully
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DatabaseDto'
         "503":
           description: Connection to the broker service could not be established
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Database found successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/DatabaseDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2482,20 +2529,20 @@ paths:
           type: string
           format: date-time
       responses:
-        "201":
-          description: Created identifier
+        "422":
+          description: Sidecar operation could not be completed
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/IdentifierDto'
-        "403":
-          description: Operation is not allowed
+                $ref: '#/components/schemas/ApiErrorDto'
+        "201":
+          description: Created identifier
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: Database connection could not be established
+                $ref: '#/components/schemas/IdentifierDto'
+        "409":
+          description: Failed to export file from sidecar
           content:
             application/json:
               schema:
@@ -2506,26 +2553,26 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Table, database or user was not found"
+        "410":
+          description: Blob storage operation could not be completed
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "410":
-          description: Blob storage operation could not be completed
+                $ref: '#/components/schemas/ApiErrorDto'
+        "503":
+          description: Database connection could not be established
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "409":
-          description: Failed to export file from sidecar
+        "404":
+          description: "Table, database or user was not found"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "422":
-          description: Sidecar operation could not be completed
+        "403":
+          description: Operation is not allowed
           content:
             application/json:
               schema:
@@ -2547,6 +2594,12 @@ paths:
           type: integer
           format: int64
       responses:
+        "403":
+          description: No access to this database
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
         "404":
           description: Database not found
           content:
@@ -2559,12 +2612,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/DatabaseAccessDto'
-        "403":
-          description: No access to this database
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2594,18 +2641,18 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: Find view successfully
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ViewDto'
         "404":
           description: "Database, view or user could not be found"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Find view successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ViewDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2628,22 +2675,14 @@ paths:
           type: integer
           format: int64
       responses:
-        "200":
-          description: Delete view successfully
         "405":
           description: Delete view is not permitted
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "503":
-          description: Connection to the database failed
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Deletion not allowed
+        "404":
+          description: "Database, view or user could not be found"
           content:
             application/json:
               schema:
@@ -2660,8 +2699,16 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, view or user could not be found"
+        "200":
+          description: Delete view successfully
+        "503":
+          description: Connection to the database failed
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
+        "403":
+          description: Deletion not allowed
           content:
             application/json:
               schema:
@@ -2701,26 +2748,26 @@ paths:
           type: integer
           format: int64
       responses:
-        "200":
-          description: Find data successfully
+        "400":
+          description: Pagination not in valid range or find data query is malformed
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/QueryResultDto'
-        "403":
-          description: View data not allowed
+                $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Find data successfully
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "400":
-          description: Pagination not in valid range or find data query is malformed
+                $ref: '#/components/schemas/QueryResultDto'
+        "404":
+          description: "Database, view, container or user could not be found"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, view, container or user could not be found"
+        "403":
+          description: View data not allowed
           content:
             application/json:
               schema:
@@ -2748,33 +2795,33 @@ paths:
           type: integer
           format: int64
       responses:
-        "200":
-          description: Count data successfully
+        "400":
+          description: Pagination not in valid range or find data query is malformed
           content:
             application/json:
               schema:
-                type: integer
-                format: int64
-        "403":
-          description: Count data not allowed
+                $ref: '#/components/schemas/ApiErrorDto'
+        "404":
+          description: "Database, view, container or user could not be found"
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "400":
-          description: Pagination not in valid range or find data query is malformed
+        "200":
+          description: Count data successfully
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
+                type: integer
+                format: int64
         "409":
           description: Could not count query data
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Database, view, container or user could not be found"
+        "403":
+          description: Count data not allowed
           content:
             application/json:
               schema:
@@ -2802,26 +2849,26 @@ paths:
           type: integer
           format: int64
       responses:
-        "503":
-          description: Could not communicate with the broker service
+        "200":
+          description: Find table successfully
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Table, database or container could not be found"
+                $ref: '#/components/schemas/TableDto'
+        "503":
+          description: Could not communicate with the broker service
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: Find table successfully
+        "403":
+          description: Access to the database is forbidden
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/TableDto'
-        "403":
-          description: Access to the database is forbidden
+                $ref: '#/components/schemas/ApiErrorDto'
+        "404":
+          description: "Table, database or container could not be found"
           content:
             application/json:
               schema:
@@ -2856,14 +2903,14 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: "Table, database or container could not be found"
+        "403":
+          description: Access to the database is forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "403":
-          description: Access to the database is forbidden
+        "404":
+          description: "Table, database or container could not be found"
           content:
             application/json:
               schema:
@@ -2909,25 +2956,25 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "422":
-          description: Could not import csv via sidecar
+        "202":
+          description: Get table data count successfully
           content:
-            application/json:
+            '*/*':
               schema:
-                $ref: '#/components/schemas/ApiErrorDto'
+                type: integer
+                format: int64
         "403":
           description: Access to the database is forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Get table data count successfully
+        "422":
+          description: Could not import csv via sidecar
           content:
-            '*/*':
+            application/json:
               schema:
-                type: integer
-                format: int64
+                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -2956,18 +3003,6 @@ paths:
         schema:
           type: string
       responses:
-        "409":
-          description: Export of query failed
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
-        "200":
-          description: Executed query
-          content:
-            '*/*':
-              schema:
-                type: object
         "403":
           description: Execute query not permitted
           content:
@@ -2986,14 +3021,26 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "200":
+          description: Executed query
+          content:
+            '*/*':
+              schema:
+                type: object
+        "404":
+          description: Database or query could not be found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
         "422":
           description: Sidecar failed to export
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "404":
-          description: Database or query could not be found
+        "409":
+          description: Export of query failed
           content:
             application/json:
               schema:
@@ -3046,12 +3093,6 @@ paths:
         schema:
           type: string
       responses:
-        "417":
-          description: Could not parse columns
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Execute query not permitted
           content:
@@ -3064,6 +3105,18 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "417":
+          description: Could not parse columns
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
+        "404":
+          description: Database or query could not be found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
         "409":
           description: Could not store query in query store
           content:
@@ -3076,12 +3129,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/QueryResultDto'
-        "404":
-          description: Database or query could not be found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -3105,12 +3152,6 @@ paths:
           type: integer
           format: int64
       responses:
-        "417":
-          description: Could not parse columns
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "403":
           description: Execute query not permitted
           content:
@@ -3123,18 +3164,24 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
-        "202":
-          description: Executed query
+        "417":
+          description: Could not parse columns
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/QueryResultDto'
+                $ref: '#/components/schemas/ApiErrorDto'
         "404":
           description: Database or query could not be found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "202":
+          description: Executed query
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/QueryResultDto'
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -3167,18 +3214,18 @@ paths:
           type: integer
           format: int64
       responses:
-        "404":
-          description: Container image could not be found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiErrorDto'
         "200":
           description: Found container
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ContainerDto'
+        "404":
+          description: Container image could not be found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiErrorDto'
     delete:
       tags:
       - container-endpoint
@@ -3192,18 +3239,18 @@ paths:
           type: integer
           format: int64
       responses:
-        "202":
-          description: Deleted container successfully
-          content:
-            '*/*':
-              schema:
-                type: object
         "404":
           description: Container not found
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ApiErrorDto'
+        "202":
+          description: Deleted container successfully
+          content:
+            '*/*':
+              schema:
+                type: object
       security:
       - bearerAuth: []
       - basicAuth: []
@@ -4080,6 +4127,9 @@ components:
           $ref: '#/components/schemas/UserDto'
         owner:
           $ref: '#/components/schemas/UserDto'
+        image:
+          type: string
+          format: byte
         created:
           type: string
           format: date-time
@@ -5200,6 +5250,11 @@ components:
           type: string
         unit_uri:
           type: string
+    DatabaseModifyImageDto:
+      type: object
+      properties:
+        key:
+          type: string
     DatabaseModifyAccessDto:
       required:
       - type
@@ -6732,10 +6787,10 @@ components:
           type: string
         parametersString:
           type: string
-        untilDate:
+        fromDate:
           type: string
           format: date-time
-        fromDate:
+        untilDate:
           type: string
           format: date-time
     BannerMessageDto:
@@ -6927,12 +6982,12 @@ components:
           type: string
         identifier:
           $ref: '#/components/schemas/Identifier'
-        apaName:
-          type: string
         bibtexName:
           type: string
         ieeeName:
           type: string
+        apaName:
+          type: string
     Database:
       type: object
       properties:
@@ -6989,6 +7044,9 @@ components:
             $ref: '#/components/schemas/DatabaseAccess'
         isPublic:
           type: boolean
+        image:
+          type: string
+          format: byte
         created:
           type: string
           format: date-time
diff --git a/dbrepo-authentication-service/dbrepo-realm.json b/dbrepo-authentication-service/dbrepo-realm.json
index c861fbd662..b2730612a7 100644
--- a/dbrepo-authentication-service/dbrepo-realm.json
+++ b/dbrepo-authentication-service/dbrepo-realm.json
@@ -99,7 +99,7 @@
       "description" : "${default-database-handling}",
       "composite" : true,
       "composites" : {
-        "realm" : [ "modify-database-owner", "update-database-access", "create-database", "list-databases", "create-database-access", "find-database", "modify-database-visibility", "import-database-data", "delete-database-access", "check-database-access" ]
+        "realm" : [ "modify-database-image", "modify-database-owner", "update-database-access", "create-database", "list-databases", "create-database-access", "find-database", "modify-database-visibility", "import-database-data", "delete-database-access", "check-database-access" ]
       },
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
@@ -220,6 +220,14 @@
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
+    }, {
+      "id" : "1f0a9b13-c2b8-474c-bc08-59dbd71835a6",
+      "name" : "modify-database-image",
+      "description" : "${modify-database-image}",
+      "composite" : false,
+      "clientRole" : false,
+      "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
+      "attributes" : { }
     }, {
       "id" : "a7ad038c-5c06-42fc-951c-15ac09d4df66",
       "name" : "modify-database-owner",
@@ -2049,7 +2057,7 @@
       "subType" : "authenticated",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper" ]
       }
     }, {
       "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979",
@@ -2058,7 +2066,7 @@
       "subType" : "anonymous",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper" ]
+        "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper" ]
       }
     } ],
     "org.keycloak.keys.KeyProvider" : [ {
@@ -2110,7 +2118,7 @@
   "internationalizationEnabled" : false,
   "supportedLocales" : [ ],
   "authenticationFlows" : [ {
-    "id" : "b8378805-a082-46a0-9e28-a1e5d4db7e41",
+    "id" : "05f92ecb-5a34-416a-a9a4-b4aeab2704c4",
     "alias" : "Account verification options",
     "description" : "Method with which to verity the existing account",
     "providerId" : "basic-flow",
@@ -2132,7 +2140,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "2652bbd9-bd49-465c-8595-690099333bf7",
+    "id" : "e85f1d42-30c8-4878-ab0c-3cb9baaa308f",
     "alias" : "Authentication Options",
     "description" : "Authentication options.",
     "providerId" : "basic-flow",
@@ -2161,7 +2169,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "967c3248-c2e9-45a9-b770-b02e965b958a",
+    "id" : "754e6269-c096-41d6-88df-44bd2652ec82",
     "alias" : "Browser - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2183,7 +2191,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "f78ad348-c3e1-476e-a916-fce0c383376a",
+    "id" : "5b2a16dd-7192-4558-931a-a67dfa7b14e1",
     "alias" : "Direct Grant - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2205,7 +2213,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "788cf02b-5744-4ea6-940a-96bc762da4bd",
+    "id" : "c12d7c33-256e-486f-8fb8-c8594eafd64e",
     "alias" : "First broker login - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2227,7 +2235,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "273e61b7-9cc3-464e-a7b8-27c71aca4014",
+    "id" : "711adf58-692f-4f22-ae20-0ba01d8d667c",
     "alias" : "Handle Existing Account",
     "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
     "providerId" : "basic-flow",
@@ -2249,7 +2257,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "00f41bfc-8513-466d-8c6a-366b7f2f36ca",
+    "id" : "dd53182d-ca4a-4096-b1fc-60237af977c4",
     "alias" : "Reset - Conditional OTP",
     "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
     "providerId" : "basic-flow",
@@ -2271,7 +2279,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "980ebf01-fe0a-4cfa-880e-dd86ce8e190e",
+    "id" : "23c368c2-dce4-4ca8-8096-b6c726fa0e32",
     "alias" : "User creation or linking",
     "description" : "Flow for the existing/non-existing user alternatives",
     "providerId" : "basic-flow",
@@ -2294,7 +2302,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "5e6a7a10-4be8-4038-8fc5-0588b452328d",
+    "id" : "37ff6b93-bdfe-4245-9247-009061fdfc7b",
     "alias" : "Verify Existing Account by Re-authentication",
     "description" : "Reauthentication of existing account",
     "providerId" : "basic-flow",
@@ -2316,7 +2324,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "024e07f8-f975-41ef-b755-d2b089b5567c",
+    "id" : "c1f58e18-5d41-40b1-aa73-4a4e4a970430",
     "alias" : "browser",
     "description" : "browser based authentication",
     "providerId" : "basic-flow",
@@ -2352,7 +2360,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "324da9be-755e-4556-a1d3-58569b9df47c",
+    "id" : "9229472e-78c8-4e83-aa20-7a2e22c28f59",
     "alias" : "clients",
     "description" : "Base authentication for clients",
     "providerId" : "client-flow",
@@ -2388,7 +2396,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "bced47d4-5d04-4bb9-8605-94041185c0f3",
+    "id" : "d841dca1-b9ca-47bc-8f9a-dcd5896678dd",
     "alias" : "direct grant",
     "description" : "OpenID Connect Resource Owner Grant",
     "providerId" : "basic-flow",
@@ -2417,7 +2425,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "6b301d9d-68c0-44c3-9a57-92669d08b2f3",
+    "id" : "42e0301c-d81c-4127-9e17-064811566f9a",
     "alias" : "docker auth",
     "description" : "Used by Docker clients to authenticate against the IDP",
     "providerId" : "basic-flow",
@@ -2432,7 +2440,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "9c9ddfeb-37a2-4186-a58f-cf90dca8e191",
+    "id" : "4809629a-0e3c-4894-8cd7-60d99abeb2e8",
     "alias" : "first broker login",
     "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
     "providerId" : "basic-flow",
@@ -2455,7 +2463,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "a9ef5094-93bf-49fc-9d0f-dcfc551cac5a",
+    "id" : "7ce37ac0-9aba-412d-98fb-78745e6df1ff",
     "alias" : "forms",
     "description" : "Username, password, otp and other auth forms.",
     "providerId" : "basic-flow",
@@ -2477,7 +2485,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "fae6e2e4-a071-458b-ac03-41dda3456f5a",
+    "id" : "9fa4ee30-9ab4-40c3-bb9f-b56b8738d1c0",
     "alias" : "http challenge",
     "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
     "providerId" : "basic-flow",
@@ -2499,7 +2507,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "ae5bcac5-8867-42e1-887f-fc67418b0c4b",
+    "id" : "bba37884-4bd0-4597-9f26-e8b8c7d60dc6",
     "alias" : "registration",
     "description" : "registration flow",
     "providerId" : "basic-flow",
@@ -2515,7 +2523,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "72524b5d-1cfc-41b0-b29b-6f6890d2dc7f",
+    "id" : "9e3b3ba5-e37e-4f6d-a7a7-fd37558f6e2d",
     "alias" : "registration form",
     "description" : "registration form",
     "providerId" : "form-flow",
@@ -2551,7 +2559,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "834c96b8-790d-4869-8c66-d42cd35e4873",
+    "id" : "e38d574a-2171-408b-9f9d-1ebe60791110",
     "alias" : "reset credentials",
     "description" : "Reset credentials for a user if they forgot their password or something",
     "providerId" : "basic-flow",
@@ -2587,7 +2595,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "7f131501-e3ff-48f2-98e6-e34e4c5d6f9e",
+    "id" : "5560dfff-822c-43fb-a910-db38b4470268",
     "alias" : "saml ecp",
     "description" : "SAML ECP Profile Authentication Flow",
     "providerId" : "basic-flow",
@@ -2603,13 +2611,13 @@
     } ]
   } ],
   "authenticatorConfig" : [ {
-    "id" : "638341f1-94ba-4042-a3ee-41a0f41718f6",
+    "id" : "201f18f6-b170-4fcc-bcc2-2ca05b1558aa",
     "alias" : "create unique user config",
     "config" : {
       "require.password.update.after.registration" : "false"
     }
   }, {
-    "id" : "3c355b8c-8a51-4346-88f2-1ff81856b55c",
+    "id" : "f6e84d09-4994-452a-be1a-fe896289ae9d",
     "alias" : "review profile config",
     "config" : {
       "update.profile.on.first.login" : "missing"
diff --git a/dbrepo-metadata-db/setup-schema.sql b/dbrepo-metadata-db/setup-schema.sql
index 2981117119..f3de67233f 100644
--- a/dbrepo-metadata-db/setup-schema.sql
+++ b/dbrepo-metadata-db/setup-schema.sql
@@ -95,6 +95,7 @@ CREATE TABLE IF NOT EXISTS `mdb_databases`
     description    text,
     engine         character varying(20),
     is_public      boolean                NOT NULL DEFAULT TRUE,
+    image          longblob,
     created_by     character varying(36),
     owned_by       character varying(36),
     contact_person character varying(36),
diff --git a/dbrepo-metadata-service/Dockerfile b/dbrepo-metadata-service/Dockerfile
index 14f3dbe95f..4c9ae73e54 100644
--- a/dbrepo-metadata-service/Dockerfile
+++ b/dbrepo-metadata-service/Dockerfile
@@ -75,6 +75,7 @@ ENV DATACITE_PASSWORD=""
 ENV S3_STORAGE_ENDPOINT="http://storage-service:9000"
 ENV S3_ACCESS_KEY_ID="seaweedfsadmin"
 ENV S3_SECRET_ACCESS_KEY="seaweedfsadmin"
+ENV DELETE_STALE_FILES_RATE=60
 ENV MIRROR_RATE=60
 ENV OBTAIN_METADATA_RATE=60
 ENV DELETE_STALE_QUERIES_RATE=60
diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java
index e6db0c9736..9ecf26e386 100644
--- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java
+++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java
@@ -97,6 +97,9 @@ public class DatabaseDto {
     @Field(name = "owner", type = FieldType.Object)
     private UserDto owner;
 
+    @ToString.Exclude
+    private byte[] image;
+
     @NotNull
     @Schema(example = "2021-03-12T15:26:21Z")
     @Field(name = "created", type = FieldType.Date)
diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseModifyImageDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseModifyImageDto.java
new file mode 100644
index 0000000000..627714f6cb
--- /dev/null
+++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseModifyImageDto.java
@@ -0,0 +1,17 @@
+package at.tuwien.api.database;
+
+import lombok.*;
+import lombok.extern.jackson.Jacksonized;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Jacksonized
+@ToString
+public class DatabaseModifyImageDto {
+
+    private String key;
+
+}
diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java
index 8a1f869a0e..19b7eae6c9 100644
--- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java
+++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java
@@ -17,6 +17,7 @@ import org.springframework.data.annotation.LastModifiedDate;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
 import java.io.Serializable;
+import java.sql.Blob;
 import java.time.Instant;
 import java.util.List;
 import java.util.UUID;
@@ -131,6 +132,11 @@ public class Database implements Serializable {
     @Column(nullable = false)
     private Boolean isPublic;
 
+    @Lob
+    @Basic(fetch = FetchType.LAZY)
+    @Column(columnDefinition = "LONGBLOB")
+    private byte[] image;
+
     @CreatedDate
     @Column(nullable = false, updatable = false, columnDefinition = "TIMESTAMP default NOW()")
     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC")
diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/S3Mapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/S3Mapper.java
index 87e68ef389..6e89e98494 100644
--- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/S3Mapper.java
+++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/S3Mapper.java
@@ -1,28 +1,10 @@
 package at.tuwien.mapper;
 
-import io.minio.GetObjectArgs;
 import org.mapstruct.Mapper;
-import org.springframework.http.MediaType;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-
-import java.util.Collections;
 
 @Mapper(componentModel = "spring")
 public interface S3Mapper {
 
     org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(S3Mapper.class);
 
-    default GetObjectArgs s3ArgsToObjectArgs(String bucketName, String key) {
-        return s3ArgsToObjectArgs(bucketName, key, null);
-    }
-
-    default GetObjectArgs s3ArgsToObjectArgs(String bucketName, String key, Long length) {
-        final GetObjectArgs.Builder builder = GetObjectArgs.builder()
-                .bucket(bucketName)
-                .object(key);
-        if (length != null) {
-            builder.length(length);
-        }
-        return builder.build();
-    }
 }
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
index 5f6aad756d..435e31feeb 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
@@ -4,6 +4,7 @@ import at.tuwien.api.amqp.ExchangeDto;
 import at.tuwien.api.database.*;
 import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.config.RabbitConfig;
+import at.tuwien.config.S3Config;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.DatabaseAccess;
 import at.tuwien.entities.user.User;
@@ -13,6 +14,10 @@ import at.tuwien.service.*;
 import at.tuwien.utils.PrincipalUtil;
 import at.tuwien.utils.UserUtil;
 import io.micrometer.observation.annotation.Observed;
+import io.minio.GetObjectArgs;
+import io.minio.GetObjectResponse;
+import io.minio.MinioClient;
+import io.minio.errors.*;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.ArraySchema;
 import io.swagger.v3.oas.annotations.media.Content;
@@ -31,6 +36,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -45,16 +53,19 @@ public class DatabaseEndpoint {
     private final RabbitConfig rabbitConfig;
     private final AccessService accessService;
     private final DatabaseMapper databaseMapper;
+    private final StorageService storageService;
     private final DatabaseService databaseService;
     private final QueryStoreService queryStoreService;
     private final MessageQueueService messageQueueService;
 
     @Autowired
     public DatabaseEndpoint(DatabaseMapper databaseMapper, UserService userService, RabbitConfig rabbitConfig,
-                            DatabaseService databaseService, QueryStoreService queryStoreService,
-                            AccessService accessService, MessageQueueService messageQueueService) {
+                            StorageService storageService, DatabaseService databaseService,
+                            QueryStoreService queryStoreService, AccessService accessService,
+                            MessageQueueService messageQueueService) {
         this.userService = userService;
         this.rabbitConfig = rabbitConfig;
+        this.storageService = storageService;
         this.accessService = accessService;
         this.databaseMapper = databaseMapper;
         this.databaseService = databaseService;
@@ -218,11 +229,11 @@ public class DatabaseEndpoint {
                                                   @Valid @RequestBody DatabaseModifyVisibilityDto data,
                                                   @NotNull Principal principal) throws DatabaseNotFoundException,
             NotAllowedException {
-        log.debug("endpoint update database, id={}, data={}, {}", id, data, PrincipalUtil.formatForDebug(principal));
+        log.debug("endpoint modify database visibility, id={}, data={}, {}", id, data, PrincipalUtil.formatForDebug(principal));
         final Database database = databaseService.findById(id);
         if (!database.getOwnedBy().equals(UserUtil.getId(principal))) {
-            log.error("Failed to create database: not owner");
-            throw new NotAllowedException(("Failed to create database: not owner"));
+            log.error("Failed to modify database visibility: not owner");
+            throw new NotAllowedException("Failed to modify database visibility: not owner");
         }
         final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(databaseService.visibility(id, data));
         log.trace("update database resulted in database {}", dto);
@@ -256,12 +267,12 @@ public class DatabaseEndpoint {
                                                 @Valid @RequestBody DatabaseTransferDto transferDto,
                                                 @NotNull Principal principal) throws DatabaseNotFoundException,
             UserNotFoundException, NotAllowedException {
-        log.debug("endpoint update database, id={}, transferDto={}, {}", id, transferDto, PrincipalUtil.formatForDebug(principal));
+        log.debug("endpoint transfer database, id={}, transferDto={}, {}", id, transferDto, PrincipalUtil.formatForDebug(principal));
         final Database database = databaseService.findById(id);
         final User user = userService.findByUsername(principal.getName());
         if (!database.getOwnedBy().equals(user.getId())) {
-            log.error("Failed to create database: not owner");
-            throw new NotAllowedException(("Failed to create database: not owner"));
+            log.error("Failed to transfer database: not owner");
+            throw new NotAllowedException("Failed to transfer database: not owner");
         }
         final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(databaseService.transfer(id, transferDto));
         log.trace("update database resulted in database {}", dto);
@@ -269,6 +280,55 @@ public class DatabaseEndpoint {
                 .body(dto);
     }
 
+    @PutMapping("/{id}/image")
+    @Transactional
+    @PreAuthorize("hasAuthority('modify-database-image')")
+    @Observed(name = "dbr_database_image")
+    @Operation(summary = "Modify database image", security = {@SecurityRequirement(name = "bearerAuth"), @SecurityRequirement(name = "basicAuth")})
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "202",
+                    description = "Modify of image was successful",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = DatabaseDto.class))}),
+            @ApiResponse(responseCode = "404",
+                    description = "Database or user could not be found",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "403",
+                    description = "Modify of image is not permitted",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "410",
+                    description = "File was not found in the Storage Service",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+    })
+    public ResponseEntity<DatabaseDto> modifyImage(@NotNull @PathVariable Long id,
+                                                   @Valid @RequestBody DatabaseModifyImageDto imageDto,
+                                                   @NotNull Principal principal) throws DatabaseNotFoundException,
+            UserNotFoundException, NotAllowedException, FileStorageException {
+        log.debug("endpoint modify database image, id={}, imageDto={}, {}", id, imageDto, PrincipalUtil.formatForDebug(principal));
+        final Database database = databaseService.findById(id);
+        final User user = userService.findByUsername(principal.getName());
+        if (!database.getOwnedBy().equals(user.getId())) {
+            log.error("Failed to update database image: not owner");
+            throw new NotAllowedException("Failed to update database image: not owner");
+        }
+        final DatabaseDto dto;
+        byte[] image = null;
+        if (imageDto.getKey() != null) {
+            image = storageService.getBytes(imageDto.getKey());
+        }
+        dto = databaseMapper.databaseToDatabaseDto(databaseService.modifyImage(id, image));
+        log.trace("update database resulted in database {}", dto);
+        return ResponseEntity.accepted()
+                .body(dto);
+    }
+
     @GetMapping("/{id}")
     @Transactional(readOnly = true)
     @Observed(name = "dbr_database_find")
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MaintenanceEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MaintenanceEndpoint.java
index e0702cd8a5..dd003742bd 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MaintenanceEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/MaintenanceEndpoint.java
@@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotNull;
 import lombok.extern.log4j.Log4j2;
@@ -106,7 +107,7 @@ public class MaintenanceEndpoint {
 
     @PostMapping("/message")
     @Observed(name = "dbr_maintenance_create")
-    @Operation(summary = "Create maintenance message")
+    @Operation(summary = "Create maintenance message", security = {@SecurityRequirement(name = "bearerAuth"), @SecurityRequirement(name = "basicAuth")})
     @PreAuthorize("hasAuthority('create-maintenance-message')")
     @ApiResponses(value = {
             @ApiResponse(responseCode = "201",
@@ -125,7 +126,7 @@ public class MaintenanceEndpoint {
 
     @PutMapping("/message/{id}")
     @Observed(name = "dbr_maintenance_update")
-    @Operation(summary = "Update maintenance message")
+    @Operation(summary = "Update maintenance message", security = {@SecurityRequirement(name = "bearerAuth"), @SecurityRequirement(name = "basicAuth")})
     @PreAuthorize("hasAuthority('update-maintenance-message')")
     @ApiResponses(value = {
             @ApiResponse(responseCode = "202",
@@ -151,7 +152,7 @@ public class MaintenanceEndpoint {
 
     @DeleteMapping("/message/{id}")
     @Observed(name = "dbr_maintenance_delete")
-    @Operation(summary = "Delete maintenance message")
+    @Operation(summary = "Delete maintenance message", security = {@SecurityRequirement(name = "bearerAuth"), @SecurityRequirement(name = "basicAuth")})
     @PreAuthorize("hasAuthority('delete-maintenance-message')")
     @ApiResponses(value = {
             @ApiResponse(responseCode = "202",
diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml b/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml
index ed462f3bc4..1ae0d498b2 100644
--- a/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml
+++ b/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml
@@ -59,6 +59,8 @@ fda:
     secretAccessKey: seaweedfsadmin
     importBucket: dbrepo-upload
     exportBucket: dbrepo-download
+    deleteStaleFilesRate: 60
+    staleSeconds: 60
   jwt:
     issuer: http://localhost/api/auth/realms/dbrepo
     public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/application.yml b/dbrepo-metadata-service/rest-service/src/main/resources/application.yml
index 26bcbeb843..994ce611d0 100644
--- a/dbrepo-metadata-service/rest-service/src/main/resources/application.yml
+++ b/dbrepo-metadata-service/rest-service/src/main/resources/application.yml
@@ -72,6 +72,8 @@ fda:
     secretAccessKey: "${S3_SECRET_ACCESS_KEY}"
     importBucket: "${S3_IMPORT_BUCKET}"
     exportBucket: "${S3_EXPORT_BUCKET}"
+    deleteStaleFilesRate: "${DELETE_STALE_FILES_RATE}"
+    staleSeconds: 3600
   jwt:
     issuer: "${JWT_ISSUER}"
     public_key: "${JWT_PUBKEY}"
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/annotations/MockListeners.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/annotations/MockListeners.java
index e5b7427ad0..14fb3972ef 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/annotations/MockListeners.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/annotations/MockListeners.java
@@ -2,6 +2,7 @@ package at.tuwien.annotations;
 
 import at.tuwien.listener.DatabaseListener;
 import at.tuwien.listener.MirrorListener;
+import at.tuwien.listener.StorageListener;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.MockBeans;
 
@@ -12,6 +13,6 @@ import java.lang.annotation.Target;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
-@MockBeans({@MockBean(DatabaseListener.class), @MockBean(MirrorListener.class)})
+@MockBeans({@MockBean(DatabaseListener.class), @MockBean(MirrorListener.class), @MockBean(StorageListener.class)})
 public @interface MockListeners {
 }
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/S3Config.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/S3Config.java
index 98ed8d5a21..7ecd9496e4 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/S3Config.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/S3Config.java
@@ -1,20 +1,17 @@
 package at.tuwien.config;
 
-import io.minio.BucketExistsArgs;
-import io.minio.MakeBucketArgs;
-import io.minio.MinioClient;
-import io.minio.UploadObjectArgs;
+import io.minio.*;
+import io.minio.errors.*;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FileUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 
 @Slf4j
 @Getter
@@ -36,6 +33,9 @@ public class S3Config {
     @Value("${fda.s3.exportBucket}")
     private String s3ExportBucket;
 
+    @Value("${fda.s3.staleSeconds}")
+    private Integer staleSeconds;
+
     @Bean
     public MinioClient minioClient() {
         return MinioClient.builder()
@@ -74,6 +74,18 @@ public class S3Config {
         }
     }
 
+    public boolean objectExists(String bucket, String key) {
+        try {
+            final StatObjectResponse response = minioClient().statObject(StatObjectArgs.builder()
+                    .object(key)
+                    .bucket(bucket)
+                    .build());
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     public void uploadFile(String bucket, String filepath, String filename) throws IOException {
         final File file = new File(filepath);
         if (!file.exists()) {
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java
index 2b6b480b59..1fc625b8dc 100644
--- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java
@@ -16,7 +16,12 @@ import at.tuwien.service.ContainerService;
 import at.tuwien.service.MessageQueueService;
 import at.tuwien.service.QueryStoreService;
 import at.tuwien.service.impl.MariaDbServiceImpl;
+import io.minio.GetObjectArgs;
+import io.minio.GetObjectResponse;
+import io.minio.MinioClient;
+import io.minio.errors.*;
 import lombok.extern.log4j.Log4j2;
+import okhttp3.Headers;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +33,10 @@ import org.springframework.security.test.context.support.WithAnonymousUser;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.util.List;
 import java.util.Optional;
@@ -71,6 +80,9 @@ public class DatabaseEndpointUnitTest extends BaseUnitTest {
     @MockBean
     private UserRepository userRepository;
 
+    @MockBean
+    private MinioClient minioClient;
+
     @Autowired
     private DatabaseEndpoint databaseEndpoint;
 
@@ -275,6 +287,41 @@ public class DatabaseEndpointUnitTest extends BaseUnitTest {
         });
     }
 
+    @Test
+    @WithMockUser(username = USER_4_USERNAME)
+    public void modifyImage_noRole_fails() {
+        final DatabaseModifyImageDto request = DatabaseModifyImageDto.builder()
+                .key("s3key_here")
+                .build();
+
+        /* test */
+        assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> {
+            databaseEndpoint.modifyImage(DATABASE_3_ID, request, USER_4_PRINCIPAL);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-database-image"})
+    public void modifyImage_hasRole_succeeds() throws UserNotFoundException, DatabaseNotFoundException,
+            NotAllowedException, IOException, FileStorageException, ServerException, InsufficientDataException,
+            ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
+            XmlParserException, InternalException {
+        final DatabaseModifyImageDto request = DatabaseModifyImageDto.builder()
+                .key("s3key_here")
+                .build();
+
+        /* mock */
+        when(databaseService.findById(DATABASE_1_ID))
+                .thenReturn(DATABASE_1);
+        when(minioClient.getObject(any(GetObjectArgs.class)))
+                .thenReturn(new GetObjectResponse(Headers.of(), "dbrepo-upload", "default", "object", InputStream.nullInputStream()));
+        when(userRepository.findByUsername(USER_1_USERNAME))
+                .thenReturn(Optional.of(USER_1));
+
+        /* test */
+        databaseEndpoint.modifyImage(DATABASE_1_ID, request, USER_1_PRINCIPAL);
+    }
+
     @Test
     @WithMockUser(username = USER_4_USERNAME)
     public void transfer_noRole_fails() {
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 680754fd3f..94a97d3643 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
@@ -197,7 +197,7 @@ public class PrometheusEndpointMvcTest extends BaseUnitTest {
     }
 
     @Test
-    @WithMockUser(username = USER_1_USERNAME, authorities = {"create-database", "modify-database-visibility", "modify-database-owner", "delete-database"})
+    @WithMockUser(username = USER_1_USERNAME, authorities = {"create-database", "modify-database-visibility", "modify-database-owner", "delete-database", "modify-database-image"})
     public void prometheusDatabaseEndpoint_succeeds() {
 
         /* mock */
@@ -231,9 +231,14 @@ public class PrometheusEndpointMvcTest extends BaseUnitTest {
         } catch (Exception e) {
             /* ignore */
         }
+        try {
+            databaseEndpoint.modifyImage(DATABASE_1_ID, DatabaseModifyImageDto.builder().build(), USER_1_PRINCIPAL);
+        } catch (Exception e) {
+            /* ignore */
+        }
 
         /* test */
-        for (String metric : List.of("dbr_database_findall", "dbr_database_count", "dbr_database_create", "dbr_database_visibility", "dbr_database_transfer", "dbr_database_find")) {
+        for (String metric : List.of("dbr_database_findall", "dbr_database_count", "dbr_database_create", "dbr_database_visibility", "dbr_database_transfer", "dbr_database_find", "dbr_database_image")) {
             assertThat(registry)
                     .hasObservationWithNameEqualTo(metric);
         }
diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/StorageServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/StorageServiceIntegrationTest.java
new file mode 100644
index 0000000000..b058a69867
--- /dev/null
+++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/StorageServiceIntegrationTest.java
@@ -0,0 +1,79 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.annotations.MockAmqp;
+import at.tuwien.annotations.MockListeners;
+import at.tuwien.annotations.MockOpensearch;
+import at.tuwien.config.S3Config;
+import at.tuwien.exception.*;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.testcontainers.containers.MinIOContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Log4j2
+@Testcontainers
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+@MockAmqp
+@MockListeners
+@MockOpensearch
+public class StorageServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private S3Config s3Config;
+
+    @Autowired
+    private StorageService storageService;
+
+    @Container
+    private static MinIOContainer minIOContainer = new MinIOContainer("minio/minio")
+            .withUserName("seaweedfsadmin")
+            .withPassword("seaweedfsadmin");
+
+    @DynamicPropertySource
+    static void openSearchProperties(DynamicPropertyRegistry registry) {
+        registry.add("fda.s3.endpoint", () -> minIOContainer.getS3URL());
+    }
+
+    @BeforeEach
+    public void beforeEach() throws IOException {
+        s3Config.makeBuckets(s3Config.getS3ImportBucket());
+        s3Config.uploadFile(s3Config.getS3ImportBucket(), "./src/test/resources/csv/testdata.csv", "s3_filekey");
+    }
+
+    @Test
+    public void deleteStaleFiles_succeeds() throws FileStorageException, InterruptedException {
+
+        /* test */
+        Thread.sleep(5000);
+        storageService.deleteStaleFiles(s3Config.getS3ImportBucket());
+        assertFalse(s3Config.objectExists(s3Config.getS3ImportBucket(), "s3_filekey"));
+    }
+
+    @Test
+    public void deleteStaleFiles_fails() throws FileStorageException {
+
+        /* test */
+        storageService.deleteStaleFiles(s3Config.getS3ImportBucket());
+        assertTrue(s3Config.objectExists(s3Config.getS3ImportBucket(), "s3_filekey"));
+    }
+
+}
diff --git a/dbrepo-metadata-service/rest-service/src/test/resources/application.properties b/dbrepo-metadata-service/rest-service/src/test/resources/application.properties
index 70e4aa0fa0..81fe783d17 100644
--- a/dbrepo-metadata-service/rest-service/src/test/resources/application.properties
+++ b/dbrepo-metadata-service/rest-service/src/test/resources/application.properties
@@ -16,8 +16,7 @@ spring.jpa.hibernate.ddl-auto=create
 
 # logging
 logging.level.root=error
-logging.level.at.tuwien.=debug
-logging.level.at.tuwien.validation.=trace
+logging.level.at.tuwien.=trace
 
 # rabbitmq
 spring.rabbitmq.host=localhost
@@ -34,5 +33,8 @@ fda.datacite.password: test-password
 # keycloak
 fda.keycloak.endpoint: http://localhost:8080/
 
+# s3
+fda.s3.staleSeconds=1
+
 # consumers
 fda.consumers=2
\ No newline at end of file
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/S3Config.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/S3Config.java
index 6e99fc0ee2..3bbf37d2cf 100644
--- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/S3Config.java
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/S3Config.java
@@ -27,6 +27,9 @@ public class S3Config {
     @Value("${fda.s3.exportBucket}")
     private String s3ExportBucket;
 
+    @Value("${fda.s3.staleSeconds}")
+    private Integer staleSeconds;
+
     @Bean
     public MinioClient minioClient() {
         return MinioClient.builder()
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/StorageListener.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/StorageListener.java
new file mode 100644
index 0000000000..88a5260387
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/StorageListener.java
@@ -0,0 +1,15 @@
+package at.tuwien.listener;
+
+import at.tuwien.exception.FileStorageException;
+import org.springframework.scheduling.annotation.Scheduled;
+
+public interface StorageListener {
+
+    /**
+     * Deletes old files from the buckets used by the system in regular intervals.
+     *
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    @Scheduled
+    void deleteStaleFiles() throws FileStorageException;
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/impl/StorageListenerImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/impl/StorageListenerImpl.java
new file mode 100644
index 0000000000..73c4c9913a
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/listener/impl/StorageListenerImpl.java
@@ -0,0 +1,34 @@
+package at.tuwien.listener.impl;
+
+import at.tuwien.config.S3Config;
+import at.tuwien.exception.FileStorageException;
+import at.tuwien.listener.StorageListener;
+import at.tuwien.service.StorageService;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Log4j2
+@Component
+public class StorageListenerImpl implements StorageListener {
+
+    final S3Config s3Config;
+    final StorageService storageService;
+
+    @Autowired
+    public StorageListenerImpl(S3Config s3Config, StorageService storageService) {
+        this.s3Config = s3Config;
+        this.storageService = storageService;
+    }
+
+    @Override
+    @Scheduled(fixedRateString = "${fda.s3.deleteStaleFilesRate}", timeUnit = TimeUnit.SECONDS)
+    public void deleteStaleFiles() throws FileStorageException {
+        storageService.deleteStaleFiles(s3Config.getS3ExportBucket());
+        storageService.deleteStaleFiles(s3Config.getS3ImportBucket());
+    }
+
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/DatabaseService.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/DatabaseService.java
index e013d98274..59c0743a2e 100644
--- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/DatabaseService.java
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/DatabaseService.java
@@ -1,12 +1,14 @@
 package at.tuwien.service;
 
 import at.tuwien.api.database.DatabaseCreateDto;
+import at.tuwien.api.database.DatabaseModifyImageDto;
 import at.tuwien.api.database.DatabaseModifyVisibilityDto;
 import at.tuwien.api.database.DatabaseTransferDto;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.user.User;
 import at.tuwien.exception.*;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.security.Principal;
 import java.util.List;
@@ -101,6 +103,16 @@ public interface DatabaseService {
     Database transfer(Long databaseId, DatabaseTransferDto transferDto) throws DatabaseNotFoundException,
             UserNotFoundException;
 
+    /**
+     * Modify image of database with given id.
+     *
+     * @param databaseId The database id.
+     * @param image      The image.
+     * @return The database, if successful.
+     * @throws DatabaseNotFoundException The database was not found in the metadata database.
+     */
+    Database modifyImage(Long databaseId, byte[] image) throws DatabaseNotFoundException;
+
     /**
      * Obtain metadata from database with given id to read table and view information (schema) and write it to the metadata database for management by DBRepo.
      *
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/StorageService.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/StorageService.java
new file mode 100644
index 0000000000..52a32bd563
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/StorageService.java
@@ -0,0 +1,65 @@
+package at.tuwien.service;
+
+import at.tuwien.ExportResource;
+import at.tuwien.exception.FileStorageException;
+
+import java.io.InputStream;
+
+public interface StorageService {
+
+    /**
+     * Loads an object of a bucket from the Storage Service into an input stream.
+     *
+     * @param bucket The bucket name.
+     * @param key    The object key.
+     * @return The input stream, if successful.
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    InputStream getObject(String bucket, String key) throws FileStorageException;
+
+    /**
+     * Loads an object of the default upload bucket from the Storage Service into a byte array.
+     *
+     * @param key The object key.
+     * @return The byte array.
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    byte[] getBytes(String key) throws FileStorageException;
+
+    /**
+     * Loads an object of a bucket from the Storage Service into a byte array.
+     *
+     * @param bucket The bucket name.
+     * @param key    The object key.
+     * @return The byte array.
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    byte[] getBytes(String bucket, String key) throws FileStorageException;
+
+    /**
+     * Loads an object of the default export bucket from the Storage Service into an export resource.
+     *
+     * @param key The object key.
+     * @return The export resource, if successful.
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    ExportResource getResource(String key) throws FileStorageException;
+
+    /**
+     * Loads an object of a bucket from the Storage Service into an export resource.
+     *
+     * @param bucket The bucket name.
+     * @param key    The object key.
+     * @return The export resource, if successful.
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    ExportResource getResource(String bucket, String key) throws FileStorageException;
+
+    /**
+     * Deletes files older than an hour from the bucket.
+     *
+     * @param bucketName The bucket name.
+     * @throws FileStorageException The object failed to be loaded from the Storage Service.
+     */
+    void deleteStaleFiles(String bucketName) throws FileStorageException;
+}
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java
index 626cbc38d3..1b675b0fac 100644
--- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java
@@ -1,6 +1,7 @@
 package at.tuwien.service.impl;
 
 import at.tuwien.api.database.DatabaseCreateDto;
+import at.tuwien.api.database.DatabaseModifyImageDto;
 import at.tuwien.api.database.DatabaseModifyVisibilityDto;
 import at.tuwien.api.database.DatabaseTransferDto;
 import at.tuwien.config.QueryConfig;
@@ -36,7 +37,6 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.*;
-import java.util.function.UnaryOperator;
 
 @Log4j2
 @Service
@@ -214,6 +214,20 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe
         return entity;
     }
 
+    @Override
+    @Transactional
+    public Database modifyImage(Long databaseId, byte[] image) throws DatabaseNotFoundException {
+        /* check */
+        final Database database = findById(databaseId);
+        /* update in metadata database */
+        database.setImage(image);
+        final Database entity = databaseRepository.save(database);
+        /* save in open search database */
+        databaseIdxRepository.save(databaseMapper.databaseToDatabaseDto(entity));
+        log.info("Updated database owner of database with id {} in metadata database & search database", entity.getId());
+        return entity;
+    }
+
     @Override
     @Transactional
     public Database obtainMetadata(Long databaseId) throws DatabaseNotFoundException, QueryMalformedException,
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
index 94b66158f2..775df9e82d 100644
--- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
@@ -7,7 +7,6 @@ import at.tuwien.api.database.query.ImportDto;
 import at.tuwien.api.database.query.QueryResultDto;
 import at.tuwien.api.database.table.TableCsvDeleteDto;
 import at.tuwien.api.database.table.TableCsvDto;
-import at.tuwien.config.S3Config;
 import at.tuwien.entities.container.Container;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.View;
@@ -18,26 +17,15 @@ import at.tuwien.gateway.DataDbSidecarGateway;
 import at.tuwien.mapper.QueryMapper;
 import at.tuwien.mapper.ViewMapper;
 import at.tuwien.querystore.Query;
-import at.tuwien.service.DatabaseService;
-import at.tuwien.service.QueryService;
-import at.tuwien.service.StoreService;
-import at.tuwien.service.TableService;
+import at.tuwien.service.*;
 import com.mchange.v2.c3p0.ComboPooledDataSource;
-import io.minio.GetObjectArgs;
-import io.minio.MinioClient;
-import io.minio.errors.*;
 import lombok.extern.log4j.Log4j2;
 import net.sf.jsqlparser.JSQLParserException;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.io.InputStreamResource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
@@ -51,24 +39,22 @@ import java.util.List;
 @Service
 public class QueryServiceImpl extends HibernateConnector implements QueryService {
 
-    private final S3Config s3Config;
     private final ViewMapper viewMapper;
-    private final MinioClient minioClient;
     private final QueryMapper queryMapper;
     private final StoreService storeService;
     private final TableService tableService;
+    private final StorageService storageService;
     private final DatabaseService databaseService;
     private final DataDbSidecarGateway dataDbSidecarGateway;
 
     @Autowired
-    public QueryServiceImpl(S3Config s3Config, ViewMapper viewMapper, MinioClient minioClient, QueryMapper queryMapper,
-                            TableService tableService, DatabaseService databaseService, StoreService storeService,
+    public QueryServiceImpl(ViewMapper viewMapper, QueryMapper queryMapper, TableService tableService,
+                            StorageService storageService, DatabaseService databaseService, StoreService storeService,
                             DataDbSidecarGateway dataDbSidecarGateway) {
-        this.s3Config = s3Config;
         this.viewMapper = viewMapper;
-        this.minioClient = minioClient;
         this.queryMapper = queryMapper;
         this.tableService = tableService;
+        this.storageService = storageService;
         this.storeService = storeService;
         this.databaseService = databaseService;
         this.dataDbSidecarGateway = dataDbSidecarGateway;
@@ -268,19 +254,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
         /* upload from sidecar into blob storage */
         dataDbSidecarGateway.exportFile(container.getSidecarHost(), container.getSidecarPort(), filename);
         /* export file from blob storage */
-        try {
-            final InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket(s3Config.getS3ExportBucket()).object(filename).build());
-            log.debug("found object with key {} in bucket {}", filename, s3Config.getS3ExportBucket());
-            return ExportResource.builder()
-                    .resource(new InputStreamResource(stream))
-                    .filename(filename)
-                    .build();
-        } catch (ServerException | InsufficientDataException | ErrorResponseException | IOException |
-                 NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | XmlParserException |
-                 InternalException e) {
-            log.error("Failed to find object {} in bucket {}: {}", filename, s3Config.getS3ExportBucket(), e.getMessage());
-            throw new FileStorageException("Failed to find object " + filename + " in bucket " + s3Config.getS3ExportBucket() + ": " + e.getMessage());
-        }
+        return storageService.getResource(filename);
     }
 
     @Override
diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/SeaweedServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/SeaweedServiceImpl.java
new file mode 100644
index 0000000000..41b9de1d44
--- /dev/null
+++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/SeaweedServiceImpl.java
@@ -0,0 +1,123 @@
+package at.tuwien.service.impl;
+
+import at.tuwien.ExportResource;
+import at.tuwien.config.S3Config;
+import at.tuwien.exception.FileStorageException;
+import at.tuwien.service.StorageService;
+import io.minio.*;
+import io.minio.errors.*;
+import io.minio.messages.DeleteError;
+import io.minio.messages.DeleteObject;
+import io.minio.messages.Item;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.time.ZonedDateTime;
+import java.util.LinkedList;
+import java.util.List;
+
+@Log4j2
+@Service
+public class SeaweedServiceImpl implements StorageService {
+
+    private final S3Config s3Config;
+    private final MinioClient minioClient;
+
+    @Autowired
+    public SeaweedServiceImpl(S3Config s3Config, MinioClient minioClient) {
+        this.s3Config = s3Config;
+        this.minioClient = minioClient;
+    }
+
+    @Override
+    public InputStream getObject(String bucket, String key) throws FileStorageException {
+        try {
+            return minioClient.getObject(GetObjectArgs.builder()
+                    .bucket(bucket)
+                    .object(key)
+                    .build());
+        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
+                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
+                 XmlParserException e) {
+            log.error("Failed to find object {} in bucket {}: {}", key, bucket, e.getMessage());
+            throw new FileStorageException("Failed to find object " + key + " in bucket " + bucket + ": " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public byte[] getBytes(String key) throws FileStorageException {
+        return getBytes(s3Config.getS3ImportBucket(), key);
+    }
+
+    @Override
+    public byte[] getBytes(String bucket, String key) throws FileStorageException {
+        try {
+            return getObject(bucket, key)
+                    .readAllBytes();
+        } catch (IOException e) {
+            log.error("Failed to read bytes from input stream: {}", e.getMessage());
+            throw new FileStorageException("Failed to read bytes from input stream: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public ExportResource getResource(String key) throws FileStorageException {
+        return getResource(s3Config.getS3ExportBucket(), key);
+    }
+
+    @Override
+    public ExportResource getResource(String bucket, String key) throws FileStorageException {
+        final InputStream stream = getObject(bucket, key);
+        return ExportResource.builder()
+                .resource(new InputStreamResource(stream))
+                .filename(key)
+                .build();
+    }
+
+    @Override
+    public void deleteStaleFiles(String bucketName) throws FileStorageException {
+        final List<Item> objects = new LinkedList<>();
+        for (Result<Item> result : minioClient.listObjects(ListObjectsArgs.builder()
+                .bucket(bucketName)
+                .build())) {
+            try {
+                final Item item = result.get();
+                final long diff = item.lastModified().toEpochSecond() - ZonedDateTime.now().minusSeconds(s3Config.getStaleSeconds()).toEpochSecond();
+                if (diff <= 0) {
+                    log.trace("file {} of bucket {} is due {} second(s)", item.objectName(), bucketName, diff * -1);
+                    objects.add(item);
+                } else {
+                    log.trace("file {} of bucket {} is not yet due for {} second(s)", item.objectName(), bucketName, diff);
+                }
+            } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
+                     InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
+                     XmlParserException e) {
+                log.error("Failed to retrieve file infos from bucket {}: {}", bucketName, e.getMessage());
+                throw new FileStorageException("Failed to retrieve file infos from bucket " + bucketName + ": " + e.getMessage(), e);
+            }
+        }
+        log.debug("deleting files {}", objects.stream().map(Item::objectName).toList());
+        final Iterable<Result<DeleteError>> response = minioClient.removeObjects(RemoveObjectsArgs.builder()
+                .bucket(bucketName)
+                .objects(objects.stream().map(o -> new DeleteObject(o.objectName())).toList())
+                .build());
+        for (Result<DeleteError> result : response) {
+            try {
+                result.get();
+            } catch (ServerException | InsufficientDataException | ErrorResponseException | IOException |
+                     NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | XmlParserException |
+                     InternalException e) {
+                log.error("Failed to delete file from bucket {}: {}", bucketName, e.getMessage());
+                throw new FileStorageException("Failed to delete file from bucket " + bucketName + ": " + e.getMessage(), e);
+            }
+        }
+        log.info("Deleted {} files", objects.size());
+    }
+
+}
diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java
index 3342e7939d..67a2711a98 100644
--- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java
+++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java
@@ -143,7 +143,7 @@ public abstract class BaseTest {
 
     public final static String[] DEFAULT_DATABASE_HANDLING = new String[]{"default-database-handling",
             "update-database-access", "modify-database-visibility", "create-database", "modify-database-owner",
-            "delete-database-access", "check-database-access", "list-databases",
+            "delete-database-access", "check-database-access", "list-databases", "modify-database-image",
             "create-database-access", "find-database", "import-database-data"};
 
     public final static String[] ESCALATED_DATABASE_HANDLING = new String[]{"escalated-database-handling",
diff --git a/dbrepo-ui/api/database.service.js b/dbrepo-ui/api/database.service.js
index 9c4fb14f22..c112b6982f 100644
--- a/dbrepo-ui/api/database.service.js
+++ b/dbrepo-ui/api/database.service.js
@@ -99,6 +99,20 @@ class DatabaseService {
     })
   }
 
+  modifyImage (databaseId, payload) {
+    return new Promise((resolve, reject) => {
+      api.put(`/api/database/${databaseId}/image`, payload, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const database = response.data
+          console.debug('response database', database)
+          resolve(database)
+        }).catch((error) => {
+          displayError(error, 'Failed to modify database visibility')
+          reject(error)
+        })
+    })
+  }
+
   modifyOwner (databaseId, username) {
     return new Promise((resolve, reject) => {
       api.put(`/api/database/${databaseId}/transfer`, { username }, { headers: { Accept: 'application/json' } })
diff --git a/dbrepo-ui/pages/database/_database_id/info.vue b/dbrepo-ui/pages/database/_database_id/info.vue
index bfacf06b0a..d2a619b876 100644
--- a/dbrepo-ui/pages/database/_database_id/info.vue
+++ b/dbrepo-ui/pages/database/_database_id/info.vue
@@ -16,6 +16,12 @@
             <v-list dense>
               <v-list-item>
                 <v-list-item-content>
+                  <v-list-item-title v-if="databaseImage" class="mt-2">
+                    Database Image
+                  </v-list-item-title>
+                  <v-list-item-content v-if="databaseImage">
+                    <v-img :src="databaseImage" alt="database image" max-width="200" max-height="200" />
+                  </v-list-item-content>
                   <v-list-item-title class="mt-2">
                     Database Name
                   </v-list-item-title>
@@ -237,6 +243,12 @@ export default {
       let sum = 0
       this.database.tables.forEach((t) => { sum += t.data_length })
       return sizeToHumanLabel(sum)
+    },
+    databaseImage () {
+      if (!this.database || !this.database.image) {
+        return null
+      }
+      return `data:image/webp;base64,${this.database.image}`
     }
   },
   methods: {
diff --git a/dbrepo-ui/pages/database/_database_id/settings.vue b/dbrepo-ui/pages/database/_database_id/settings.vue
index 8734d197c9..77c5cccb82 100644
--- a/dbrepo-ui/pages/database/_database_id/settings.vue
+++ b/dbrepo-ui/pages/database/_database_id/settings.vue
@@ -4,6 +4,48 @@
     <v-progress-linear v-if="loading" />
     <v-tabs-items v-model="tab">
       <v-tab-item>
+        <v-card v-if="canModifyImage" flat tile>
+          <v-card-title>Image</v-card-title>
+          <v-card-text>
+            <v-row dense>
+              <v-col>
+                The image will be displayed in a box with maximum dimensions 200x200 pixels.
+              </v-col>
+            </v-row>
+            <v-row dense>
+              <v-col sm="6">
+                <v-file-input
+                  v-model="fileModel"
+                  accept="image/*"
+                  hint="max. 1MB file size"
+                  persistent-hint
+                  clearable
+                  :loading="loadingUpload"
+                  :show-size="1000"
+                  counter
+                  label="Teaser Image"
+                  @change="uploadFile" />
+              </v-col>
+            </v-row>
+            <v-btn
+              small
+              class="black--text mt-4"
+              :loading="loadingImage"
+              @click="updateDatabaseImage">
+              Modify Image
+            </v-btn>
+            <v-btn
+              v-if="database.image"
+              small
+              color="warning"
+              class="black--text mt-4"
+              :loading="loadingDeleteImage"
+              @click="removeDatabaseImage">
+              Remove Image
+            </v-btn>
+          </v-card-text>
+        </v-card>
+        <v-divider />
         <v-card v-if="isOwner" flat tile>
           <v-card-title>Access</v-card-title>
           <v-data-table
@@ -100,6 +142,7 @@ import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue'
 import EditAccess from '@/components/dialogs/EditAccess.vue'
 import DatabaseService from '@/api/database.service'
 import UserService from '@/api/user.service'
+import UploadService from '@/api/upload.service'
 
 export default {
   components: {
@@ -114,6 +157,10 @@ export default {
       accessType: null,
       users: [],
       loading: false,
+      loadingUpload: false,
+      loadingImage: false,
+      loadingDeleteImage: false,
+      fileModel: null,
       loadingUsers: false,
       editAccessDialog: false,
       editVisibilityDialog: false,
@@ -123,6 +170,9 @@ export default {
       modifyOwner: {
         id: null
       },
+      modifyImage: {
+        key: null
+      },
       visibility: [
         { text: 'Public', value: true },
         { text: 'Private', value: false }
@@ -214,6 +264,12 @@ export default {
         return false
       }
       return this.roles.includes('create-database-access')
+    },
+    canModifyImage () {
+      if (!this.isOwner) {
+        return false
+      }
+      return this.roles.includes('modify-database-image')
     }
   },
   watch: {
@@ -254,6 +310,50 @@ export default {
           this.loading = false
         })
     },
+    uploadFile () {
+      this.loadingUpload = true
+      UploadService.upload(this.fileModel)
+        .then((metadata) => {
+          console.debug('uploaded image', metadata)
+          this.modifyImage.key = metadata.s3key
+          this.loadingUpload = false
+        })
+        .finally(() => {
+          this.loadingUpload = false
+        })
+    },
+    updateDatabaseImage () {
+      this.loadingImage = true
+      DatabaseService.modifyImage(this.$route.params.database_id, this.modifyImage)
+        .then(() => {
+          this.$toast.success('Updated image successfully')
+          this.$store.dispatch('reloadDatabase')
+          this.loadingImage = false
+        })
+        .catch(() => {
+          this.$toast.error('Failed to modify image')
+          this.loadingImage = false
+        })
+        .finally(() => {
+          this.loadingImage = false
+        })
+    },
+    removeDatabaseImage () {
+      this.loadingDeleteImage = true
+      DatabaseService.modifyImage(this.$route.params.database_id, { key: null })
+        .then(() => {
+          this.$toast.success('Removed image successfully')
+          this.$store.dispatch('reloadDatabase')
+          this.loadingDeleteImage = false
+        })
+        .catch(() => {
+          this.$toast.error('Failed to delete image')
+          this.loadingDeleteImage = false
+        })
+        .finally(() => {
+          this.loadingDeleteImage = false
+        })
+    },
     updateDatabaseOwner () {
       this.loading = true
       DatabaseService.modifyOwner(this.$route.params.database_id, this.modifyOwner.username)
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 a074108ef3..bff92a1fb5 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
@@ -18,11 +18,11 @@
                 Table ID
               </v-list-item-title>
               <v-list-item-content v-if="table && table.id" v-text="table.id" />
-              <v-list-item-title>
+              <v-list-item-title v-if="table && table.data_length">
                 Table Size
               </v-list-item-title>
               <v-list-item-content v-if="table && table.data_length" v-text="sizeToHumanLabel(table.data_length)" />
-              <v-list-item-title>
+              <v-list-item-title v-if="table && table.num_rows">
                 Table Rows
               </v-list-item-title>
               <v-list-item-content v-if="table && table.num_rows" v-text="table.num_rows" />
diff --git a/docker-compose.yml b/docker-compose.yml
index 9eeafdeeb6..1fb934e775 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -160,6 +160,7 @@ services:
       S3_SECRET_ACCESS_KEY: "${STORAGE_PASSWORD:-seaweedfsadmin}"
       S3_IMPORT_BUCKET: "${STORAGE_IMPORT_BUCKET:-dbrepo-upload}"
       S3_EXPORT_BUCKET: "${STORAGE_EXPORT_BUCKET:-dbrepo-download}"
+      DELETE_STALE_FILES_RATE: "${DELETE_STALE_FILES_RATE:-60}"
       MIRROR_RATE: ${METADATA_SERVICE_MIRROR_RATE:-60}
       OBTAIN_METADATA_RATE: ${METADATA_SERVICE_OBTAIN_METADATA_RATE:-60}
       DELETE_STALE_QUERIES_RATE: ${METADATA_SERVICE_DELETE_STALE_QUERIES_RATE:-60}
-- 
GitLab