From a650ae0792fe6f1a5934f23693e76046cd73ff0d Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Tue, 28 Jun 2022 17:52:52 +0200
Subject: [PATCH] Deposit files works for invenio

---
 .invenio/.gitignore                           |   4 +-
 .invenio/api_authentication/configuration.py  |   2 +-
 .invenio/api_container/configuration.py       |   2 +-
 .invenio/api_database/configuration.py        |   2 +-
 .invenio/api_document/configuration.py        |   2 +-
 .invenio/api_identifier/configuration.py      |   2 +-
 .../api_query/api/table_data_endpoint_api.py  |   4 +-
 .invenio/api_query/configuration.py           |   2 +-
 .invenio/api_table/configuration.py           |   2 +-
 .invenio/{analyze.ipynb => deposit.ipynb}     |  10 +-
 .invenio/dev.py                               |  89 ++---
 .invenio/feature_extract.ipynb                | 367 ++++++++++++++++++
 .invenio/requirements.txt                     |   3 +-
 .../at/tuwien/endpoints/DocumentEndpoint.java |  28 +-
 .../at/tuwien/endpoints/FileEndpoint.java     |  19 +-
 .../src/main/resources/application-docker.yml |   1 +
 .../src/main/resources/application.yml        |   4 +-
 .../src/test/java/at/tuwien/BaseUnitTest.java |  47 ++-
 .../endpoint/DocumentEndpointUnitTest.java    |  39 ++
 .../DocumentServiceIntegrationTest.java       |   8 +-
 .../service/FileServiceIntegrationTest.java   |  40 +-
 .../src/test/resources/images/mock.png        | Bin 0 -> 11666 bytes
 .../java/at/tuwien/auth/AuthTokenFilter.java  |   2 +-
 .../java/at/tuwien/config/GatewayConfig.java  |  12 +-
 .../at/tuwien/config/WebSecurityConfig.java   |   1 -
 .../exception/CommitFileUploadException.java  |  21 +
 .../tuwien/exception/FileUploadException.java |  21 +
 .../at/tuwien/gateway/DocumentGateway.java    |  20 +-
 .../AuthenticationServiceGatewayImpl.java     |  10 +-
 .../impl/InvenioDocumentGatewayImpl.java      | 128 ++++--
 .../java/at/tuwien/mapper/DocumentMapper.java |   6 +
 .../at/tuwien/service/DocumentService.java    |  10 +-
 .../java/at/tuwien/service/FileService.java   |  10 +-
 .../service/impl/InvenioDraftServiceImpl.java |  22 +-
 .../service/impl/InvenioFileServiceImpl.java  |  15 +-
 .../gatewayservice/config/GatewayConfig.java  |   5 +
 .../FdaIdentifierServiceApplication.java      |   1 -
 .../src/main/resources/application.yml        |   1 +
 .../api/document/file/FileAnnounceDto.java    |  37 ++
 .../at/tuwien/api/document/file/FileDto.java  |  72 ++++
 .../api/document/file/FileEntryDto.java       |  43 ++
 .../tuwien/api/document/file/FileKeyDto.java  |  23 ++
 .../record/{DraftDto.java => RecordDto.java}  |   2 +-
 fda-ui/server-middleware/index.js             |   8 +-
 44 files changed, 975 insertions(+), 172 deletions(-)
 rename .invenio/{analyze.ipynb => deposit.ipynb} (82%)
 mode change 100644 => 100755 .invenio/dev.py
 create mode 100644 .invenio/feature_extract.ipynb
 create mode 100644 fda-document-service/rest-service/src/test/resources/images/mock.png
 create mode 100644 fda-document-service/services/src/main/java/at/tuwien/exception/CommitFileUploadException.java
 create mode 100644 fda-document-service/services/src/main/java/at/tuwien/exception/FileUploadException.java
 create mode 100644 fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileAnnounceDto.java
 create mode 100644 fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileDto.java
 create mode 100644 fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileEntryDto.java
 create mode 100644 fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileKeyDto.java
 rename fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/{DraftDto.java => RecordDto.java} (98%)

diff --git a/.invenio/.gitignore b/.invenio/.gitignore
index ed8ebf583f..150c6f7559 100644
--- a/.invenio/.gitignore
+++ b/.invenio/.gitignore
@@ -1 +1,3 @@
-__pycache__
\ No newline at end of file
+__pycache__
+
+features.csv
\ No newline at end of file
diff --git a/.invenio/api_authentication/configuration.py b/.invenio/api_authentication/configuration.py
index a35f4efa83..2638b5e36f 100644
--- a/.invenio/api_authentication/configuration.py
+++ b/.invenio/api_authentication/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9097"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/api_container/configuration.py b/.invenio/api_container/configuration.py
index b93a7ea5d3..23d21b8381 100644
--- a/.invenio/api_container/configuration.py
+++ b/.invenio/api_container/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9091"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/api_database/configuration.py b/.invenio/api_database/configuration.py
index 758efb3300..5ee4406b93 100644
--- a/.invenio/api_database/configuration.py
+++ b/.invenio/api_database/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9092"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/api_document/configuration.py b/.invenio/api_document/configuration.py
index bb7e50accf..3acecd4c60 100644
--- a/.invenio/api_document/configuration.py
+++ b/.invenio/api_document/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9099"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/api_identifier/configuration.py b/.invenio/api_identifier/configuration.py
index 7845c1cc68..28b5cf8755 100644
--- a/.invenio/api_identifier/configuration.py
+++ b/.invenio/api_identifier/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9096"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/api_query/api/table_data_endpoint_api.py b/.invenio/api_query/api/table_data_endpoint_api.py
index 12101dabc2..34b6434bf0 100644
--- a/.invenio/api_query/api/table_data_endpoint_api.py
+++ b/.invenio/api_query/api/table_data_endpoint_api.py
@@ -259,7 +259,7 @@ class TableDataEndpointApi(object):
         auth_settings = []  # noqa: E501
 
         return self.api_client.call_api(
-            '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'HEAD',
+            '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'GET',
             path_params,
             query_params,
             header_params,
@@ -380,7 +380,7 @@ class TableDataEndpointApi(object):
         auth_settings = []  # noqa: E501
 
         return self.api_client.call_api(
-            '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'GET',
+            '/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'HEAD',
             path_params,
             query_params,
             header_params,
diff --git a/.invenio/api_query/configuration.py b/.invenio/api_query/configuration.py
index 1b566374df..2991fff8bf 100644
--- a/.invenio/api_query/configuration.py
+++ b/.invenio/api_query/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9093"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/api_table/configuration.py b/.invenio/api_table/configuration.py
index f24b34599f..557adf4db5 100644
--- a/.invenio/api_table/configuration.py
+++ b/.invenio/api_table/configuration.py
@@ -46,7 +46,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
     def __init__(self):
         """Constructor"""
         # Default Base url
-        self.host = "http://localhost:9094"
+        self.host = "http://localhost:9095"
         # Temp file folder for downloading files
         self.temp_folder_path = None
 
diff --git a/.invenio/analyze.ipynb b/.invenio/deposit.ipynb
similarity index 82%
rename from .invenio/analyze.ipynb
rename to .invenio/deposit.ipynb
index 542a3613a6..6fe7d9c770 100644
--- a/.invenio/analyze.ipynb
+++ b/.invenio/deposit.ipynb
@@ -2,15 +2,11 @@
  "cells": [
   {
    "cell_type": "markdown",
+   "source": [],
    "metadata": {
-    "collapsed": true,
-    "pycharm": {
-     "name": "#%% md\n"
-    }
+    "collapsed": false
    },
-   "source": [
-    "# Test"
-   ]
+   "outputs": []
   }
  ],
  "metadata": {
diff --git a/.invenio/dev.py b/.invenio/dev.py
old mode 100644
new mode 100755
index aa0cb1af32..81a94da41a
--- a/.invenio/dev.py
+++ b/.invenio/dev.py
@@ -1,67 +1,34 @@
 #!/usr/bin/env python3
-import json
-
+import re
+import csv
 import requests
 
-from api_authentication.api.authentication_endpoint_api import AuthenticationEndpointApi
-from api_authentication.api.user_endpoint_api import UserEndpointApi
-from api_container.api.container_endpoint_api import ContainerEndpointApi
-from api_database.api.container_database_endpoint_api import ContainerDatabaseEndpointApi
+doi = '10.5281/zenodo.5649276'
+headers = {
+    'Authorize': 'Bearer djCvqkoOW69keHajybZiwE8bBjyir2QSZOLKpAtc4S1Wp17KXgcHmMoWJwft' 
+}
 
-authentication = AuthenticationEndpointApi()
-user = UserEndpointApi()
-container = ContainerEndpointApi()
-database = ContainerDatabaseEndpointApi()
+# Resolve DOI
+response = requests.get('https://doi.org/' + doi)
+id = re.findall('/([a-z0-9-]+)$', response.url)[0]
+host = re.findall('^https?:\/\/([a-z0-9]+\.[a-z]+)', response.url)[0]
+print("Resolved DOI to", host, "and record id", id)
 
-# # Create account
-# response = user.register({
-#     'username': 'mweise',
-#     'password': 'fda',
-#     'email': 'martin.weise@tuwien.ac.at'
-# })
-# print('Created account with username %s' % response.username)
-#
-# # Create authentication
-# response = authentication.authenticate_user1({
-#     'username': 'mweise',
-#     'password': 'fda'
-# })
-# container.api_client.default_headers = {
-#     'Authorization': 'Bearer ' + response.token
-# }
-# database.api_client.default_headers = {
-#     'Authorization': 'Bearer ' + response.token
-# }
-#
-# # Create container
-# response = container.create1({
-#     'name': 'MIR ' + str(uuid.uuid1()),
-#     'repository': 'mariadb',
-#     'tag': '10.5'
-# })
-# cid = response.id
-# print('Created container with id %d' % cid)
-#
-# # Start container
-# response = container.modify({
-#     'action': 'START'
-# }, cid)
-# time.sleep(5)
-# print('Started container with id %d' % cid)
-#
-# # Create database
-# response = database.create({
-#     'name': 'MIR ' + str(uuid.uuid1()),
-#     'description': 'Music Information Retrieval',
-#     'is_public': True
-# }, cid)
-# dbid = response.id
-# print('Created database with id %d' % dbid)
+# Find files
+url = 'https://' + host + '/api/records/' + id
+response = requests.get(url, headers=headers)
+record = response.json()
 
-# Analyse Table
-response = requests.post('http://localhost:5000/api/analyse/determinedt', json={
-    'filepath': '/tmp/test.csv',
-})
-data = json.loads(response.content)
-print('Determined data types')
-print(response)
+# Write some .csv
+i = 0
+with open('./features.csv', 'w') as f:
+    writer = csv.writer(f)
+    writer.writerow(['key', 'size', 'link'])
+    for file in record['files']:
+        requests.get(file['links']['self'])
+        print("... feature extract from", file['links']['self'])
+        writer.writerow([file['key'], file['size'], file['links']['self']])
+        i += 1
+        if i > 10:
+            break
+print("Generated a feature .csv")
diff --git a/.invenio/feature_extract.ipynb b/.invenio/feature_extract.ipynb
new file mode 100644
index 0000000000..9de40c112c
--- /dev/null
+++ b/.invenio/feature_extract.ipynb
@@ -0,0 +1,367 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "collapsed": true,
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
+   "source": [
+    "# Feature Extraction & Deposit\n",
+    "\n",
+    "In this notebook we define an example of creating a database from a .csv and perform feature extraction of audio files. The APIs are auto-generated from the Swagger Endpoint documentations using [`generate.sh`](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-docs/-/blob/master/swagger/generate.sh). Steps we perform:\n",
+    "\n",
+    "  1. Download a music file from a public repository\n",
+    "  2. Perform feature extraction\n",
+    "  3. Create an account at DBRepo\n",
+    "  4. Create an authentication token\n",
+    "  5. Create a mariadb container\n",
+    "  6. Start the mariadb container\n",
+    "  7. Create a database within the mariadb container\n",
+    "  8. Import the feature .csv (manually)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "outputs": [],
+   "source": [
+    "import os.path\n",
+    "import uuid\n",
+    "import time\n",
+    "import re\n",
+    "import csv\n",
+    "import requests as rq\n",
+    "from api_authentication.api.authentication_endpoint_api import AuthenticationEndpointApi\n",
+    "from api_authentication.api.user_endpoint_api import UserEndpointApi\n",
+    "from api_container.api.container_endpoint_api import ContainerEndpointApi\n",
+    "from api_database.api.container_database_endpoint_api import ContainerDatabaseEndpointApi\n",
+    "from api_table.api.table_endpoint_api import TableEndpointApi\n",
+    "\n",
+    "authentication = AuthenticationEndpointApi()\n",
+    "user = UserEndpointApi()\n",
+    "container = ContainerEndpointApi()\n",
+    "database = ContainerDatabaseEndpointApi()\n",
+    "table = TableEndpointApi()\n",
+    "\n",
+    "doi = \"10.5281/zenodo.5649276\"\n",
+    "email = \"some@example.com\""
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 1. Download wav\n",
+    "\n",
+    "Resolve the DOI to URI"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Resolved DOI to zenodo.org and record id 5649276\n"
+     ]
+    }
+   ],
+   "source": [
+    "response = rq.get(\"https://doi.org/\" + doi)\n",
+    "id = re.findall(\"/([a-z0-9-]+)$\", response.url)[0]\n",
+    "host = re.findall(\"^https?:\\/\\/([a-z0-9]+\\.[a-z]+)\", response.url)[0]\n",
+    "print(\"Resolved DOI to\", host, \"and record id\", id)"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 2. Perform feature extraction"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0044_20200518133554_1_m4a_1.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0044_20200518133554_2_m4a_1.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200611134530_1_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200611134530_2_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200612072315_1_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200612072315_2_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200613082517_1_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200613082517_2_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200614080017_1_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200614080017_2_m4a_0.wav\n",
+      "... feature extract from https://zenodo.org/api/files/22d69a63-2aff-47ae-b818-be78a23e9889/colive.0066_20200615070238_1_m4a_0.wav\n",
+      "Generated a feature .csv in your home directory\n"
+     ]
+    }
+   ],
+   "source": [
+    "response = rq.get(\"https://\" + host + \"/api/records/\" + id)\n",
+    "record = response.json()\n",
+    "\n",
+    "i = 0\n",
+    "with open(os.path.expanduser(\"~/features.csv\"), \"w\") as f:\n",
+    "    writer = csv.writer(f)\n",
+    "    writer.writerow([\"key\", \"size\", \"link\"])\n",
+    "    for file in record[\"files\"]:\n",
+    "        rq.get(file[\"links\"][\"self\"])\n",
+    "        print(\"... feature extract from\", file[\"links\"][\"self\"])\n",
+    "        writer.writerow([file[\"key\"], file[\"size\"], file[\"links\"][\"self\"]])\n",
+    "        i += 1\n",
+    "        if i > 10:\n",
+    "            break\n",
+    "print(\"Generated a feature .csv in your home directory\")"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 3. Create an account at DBRepo"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{'authorities': [{'authority': 'ROLE_RESEARCHER'}],\n",
+      " 'containers': None,\n",
+      " 'databases': None,\n",
+      " 'email': 'martinweiseat@gmail.com',\n",
+      " 'firstname': None,\n",
+      " 'id': 2,\n",
+      " 'identifiers': None,\n",
+      " 'lastname': None,\n",
+      " 'titles_after': None,\n",
+      " 'titles_before': None,\n",
+      " 'username': 'user'}\n"
+     ]
+    }
+   ],
+   "source": [
+    "response = user.register({\n",
+    "    \"username\": \"user\",\n",
+    "    \"password\": \"user\",\n",
+    "    \"email\": email\n",
+    "})\n",
+    "print(response)"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 4. Create an authentication token"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "outputs": [],
+   "source": [
+    "response = authentication.authenticate_user1({\n",
+    "    \"username\": \"user\",\n",
+    "    \"password\": \"user\"\n",
+    "})\n",
+    "container.api_client.default_headers = {\"Authorization\": \"Bearer \" + response.token}\n",
+    "database.api_client.default_headers = {\"Authorization\": \"Bearer \" + response.token}\n",
+    "table.api_client.default_headers = {\"Authorization\": \"Bearer \" + response.token}"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 5. Create a mariadb container"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{'hash': 'f5a649a71aae3748e62228721c44627ffc866f665d677bb890c37b9111590ffa',\n",
+      " 'id': 2,\n",
+      " 'internal_name': 'fda-userdb-mir-1010b964-f6fa-11ec-9f77-64bc58900b78',\n",
+      " 'is_public': None,\n",
+      " 'name': 'MIR 1010b964-f6fa-11ec-9f77-64bc58900b78'}\n"
+     ]
+    }
+   ],
+   "source": [
+    "response = container.create1({\n",
+    "    \"name\": \"MIR \" + str(uuid.uuid1()),\n",
+    "    \"repository\": \"mariadb\",\n",
+    "    \"tag\": \"10.5\"\n",
+    "})\n",
+    "container_id = response.id\n",
+    "print(response)"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 6. Start the mariadb container"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{'hash': 'f5a649a71aae3748e62228721c44627ffc866f665d677bb890c37b9111590ffa',\n",
+      " 'id': 2,\n",
+      " 'internal_name': 'fda-userdb-mir-1010b964-f6fa-11ec-9f77-64bc58900b78',\n",
+      " 'is_public': None,\n",
+      " 'name': 'MIR 1010b964-f6fa-11ec-9f77-64bc58900b78'}\n"
+     ]
+    }
+   ],
+   "source": [
+    "response = container.modify({\n",
+    "    \"action\": \"START\"\n",
+    "}, container_id)\n",
+    "time.sleep(5)\n",
+    "print(response)"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 7. Create a database within the mariadb container"
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "outputs": [],
+   "source": [
+    "response = database.create({\n",
+    "    \"name\": \"MIR \" + str(uuid.uuid1()),\n",
+    "    \"description\": \"Music Information Retrieval\",\n",
+    "    \"is_public\": True\n",
+    "}, container_id)\n",
+    "database_id = response.id"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## 8. Import the feature .csv\n",
+    "\n",
+    "Now open [http://localhost:3000/](http://localhost:3000/) and import the .csv file by clicking the database. After successful creation of the table, come back here."
+   ],
+   "metadata": {
+    "collapsed": false
+   }
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/.invenio/requirements.txt b/.invenio/requirements.txt
index 47f78a7d88..cead3141b6 100644
--- a/.invenio/requirements.txt
+++ b/.invenio/requirements.txt
@@ -1,2 +1 @@
-invenio-client==0.1.0
-six==1.16.0
\ No newline at end of file
+requests==2.28.0
\ No newline at end of file
diff --git a/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/DocumentEndpoint.java b/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/DocumentEndpoint.java
index 2436b9748b..395e77b5a3 100644
--- a/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/DocumentEndpoint.java
+++ b/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/DocumentEndpoint.java
@@ -1,7 +1,7 @@
 package at.tuwien.endpoints;
 
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
 import at.tuwien.exception.DraftRecordCreateException;
 import at.tuwien.service.DocumentService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -20,9 +20,8 @@ import java.security.Principal;
 
 
 @Log4j2
-@RestController
 @CrossOrigin(origins = "*")
-@ControllerAdvice
+@RestController
 @RequestMapping("/api/document")
 public class DocumentEndpoint {
 
@@ -37,9 +36,9 @@ public class DocumentEndpoint {
     @PreAuthorize("hasRole('ROLE_RESEARCHER')")
     @Transactional(readOnly = true)
     @Operation(summary = "Create a draft", security = @SecurityRequirement(name = "bearerAuth"))
-    public ResponseEntity<DraftDto> create(@NotNull @Valid @RequestBody CreateDraftDto data,
+    public ResponseEntity<RecordDto> create(@NotNull @Valid @RequestBody CreateDraftDto data,
                                            @NotNull Principal principal) throws DraftRecordCreateException {
-        final DraftDto document = documentService.create(data, principal);
+        final RecordDto document = documentService.create(data, principal);
         return ResponseEntity.status(HttpStatus.CREATED)
                 .body(document);
     }
@@ -48,22 +47,33 @@ public class DocumentEndpoint {
     @PreAuthorize("hasRole('ROLE_RESEARCHER')")
     @Transactional(readOnly = true)
     @Operation(summary = "Find a draft", security = @SecurityRequirement(name = "bearerAuth"))
-    public ResponseEntity<DraftDto> find(@NotNull @PathVariable("id") String documentId,
+    public ResponseEntity<RecordDto> find(@NotNull @PathVariable("id") String documentId,
                                          @NotNull Principal principal) throws DraftRecordCreateException {
-        final DraftDto document = documentService.findById(documentId, principal);
+        final RecordDto document = documentService.findById(documentId, principal);
         log.info("Found draft record with id {}", documentId);
         log.debug("found draft record {}", document);
         return ResponseEntity.status(HttpStatus.OK)
                 .body(document);
     }
 
+    @PutMapping("/{id}/publish")
+    @PreAuthorize("hasRole('ROLE_RESEARCHER')")
+    @Transactional(readOnly = true)
+    @Operation(summary = "Publish a draft", security = @SecurityRequirement(name = "bearerAuth"))
+    public ResponseEntity<RecordDto> publish(@NotNull @PathVariable("id") String documentId,
+                                         @NotNull Principal principal) throws DraftRecordCreateException {
+        final RecordDto document = documentService.publish(documentId, principal);
+        return ResponseEntity.status(HttpStatus.ACCEPTED)
+                .body(document);
+    }
+
     @PostMapping("/{id}")
     @PreAuthorize("hasRole('ROLE_RESEARCHER')")
     @Transactional(readOnly = true)
     @Operation(summary = "Reserve draft DOI", security = @SecurityRequirement(name = "bearerAuth"))
-    public ResponseEntity<DraftDto> reserve(@NotNull @PathVariable("id") String documentId,
+    public ResponseEntity<RecordDto> reserve(@NotNull @PathVariable("id") String documentId,
                                             @NotNull Principal principal) throws DraftRecordCreateException {
-        final DraftDto document = documentService.reserveDoi(documentId, principal);
+        final RecordDto document = documentService.reserveDoi(documentId, principal);
         return ResponseEntity.status(HttpStatus.CREATED)
                 .body(document);
     }
diff --git a/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/FileEndpoint.java b/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/FileEndpoint.java
index 5457e3c67a..402d37ed3b 100644
--- a/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/FileEndpoint.java
+++ b/fda-document-service/rest-service/src/main/java/at/tuwien/endpoints/FileEndpoint.java
@@ -1,6 +1,8 @@
 package at.tuwien.endpoints;
 
-import at.tuwien.api.document.file.FileStartDto;
+import at.tuwien.api.document.file.FileDto;
+import at.tuwien.exception.FileUploadException;
+import at.tuwien.exception.CommitFileUploadException;
 import at.tuwien.exception.DraftRecordCreateException;
 import at.tuwien.service.FileService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -12,6 +14,7 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.constraints.NotNull;
 import java.security.Principal;
@@ -20,7 +23,6 @@ import java.security.Principal;
 @Log4j2
 @RestController
 @CrossOrigin(origins = "*")
-@ControllerAdvice
 @RequestMapping("/api/document/{id}/file")
 public class FileEndpoint {
 
@@ -34,11 +36,14 @@ public class FileEndpoint {
     @PostMapping
     @PreAuthorize("hasRole('ROLE_RESEARCHER')")
     @Transactional(readOnly = true)
-    @Operation(summary = "Start draft files", security = @SecurityRequirement(name = "bearerAuth"))
-    public ResponseEntity<FileStartDto> start(@NotNull @PathVariable("id") String documentId,
-                                           @NotNull Principal principal) throws DraftRecordCreateException {
-        final FileStartDto document = fileService.start(documentId, principal);
-        return ResponseEntity.status(HttpStatus.CREATED)
+    @Operation(summary = "Upload file", security = @SecurityRequirement(name = "bearerAuth"))
+    public ResponseEntity<FileDto> uploadFile(@NotNull @PathVariable("id") String documentId,
+                                              @NotNull @RequestParam("file") MultipartFile file,
+                                              @NotNull Principal principal)
+            throws DraftRecordCreateException, CommitFileUploadException, FileUploadException,
+            org.apache.tomcat.util.http.fileupload.FileUploadException {
+        final FileDto document = fileService.uploadFile(documentId, file, principal);
+        return ResponseEntity.status(HttpStatus.ACCEPTED)
                 .body(document);
     }
 
diff --git a/fda-document-service/rest-service/src/main/resources/application-docker.yml b/fda-document-service/rest-service/src/main/resources/application-docker.yml
index 46f56dc08b..6cc0944799 100644
--- a/fda-document-service/rest-service/src/main/resources/application-docker.yml
+++ b/fda-document-service/rest-service/src/main/resources/application-docker.yml
@@ -30,5 +30,6 @@ eureka:
 fda:
   mount.path: /tmp
   ready.path: /ready
+  gateway.endpoint: http://fda-gateway-service:9095
   document.endpoint: https://test.researchdata.tuwien.ac.at
 dev.token: "${TOKEN}"
\ No newline at end of file
diff --git a/fda-document-service/rest-service/src/main/resources/application.yml b/fda-document-service/rest-service/src/main/resources/application.yml
index 47db505983..bf035c9f11 100644
--- a/fda-document-service/rest-service/src/main/resources/application.yml
+++ b/fda-document-service/rest-service/src/main/resources/application.yml
@@ -7,7 +7,7 @@ spring:
     username: postgres
     password: postgres
   jpa:
-    show-sql: false
+    show-sql: true
     database-platform: org.hibernate.dialect.PostgreSQLDialect
     hibernate:
       ddl-auto: validate
@@ -16,7 +16,6 @@ spring:
     name: fda-document-service
   cloud:
     loadbalancer.ribbon.enabled: false
-springdoc.swagger-ui.enabled: true
 server.port: 9099
 logging:
   pattern.console: "%d %highlight(%-5level) %msg%n"
@@ -31,5 +30,6 @@ eureka:
 fda:
   mount.path: /tmp
   ready.path: ./ready
+  gateway.endpoint: http://localhost:9095
   document.endpoint: https://test.researchdata.tuwien.ac.at
 dev.token: "${TOKEN}"
\ No newline at end of file
diff --git a/fda-document-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-document-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index 7f4f0cc7eb..c772a6be5d 100644
--- a/fda-document-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-document-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -2,6 +2,8 @@ package at.tuwien;
 
 import at.tuwien.api.document.metadata.*;
 import at.tuwien.api.document.record.*;
+import at.tuwien.api.user.UserDetailsDto;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.test.context.TestPropertySource;
 
 import java.time.Instant;
@@ -11,30 +13,58 @@ import java.util.List;
 @TestPropertySource(locations = "classpath:application.properties")
 public abstract class BaseUnitTest {
 
+    public final static Long USER_1_ID = 1L;
     public final static String USER_1_USERNAME = "junit";
 
+    public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
+            .id(USER_1_ID)
+            .username(USER_1_USERNAME)
+            .build();
+
     public final static AccessTypeDto DOCUMENT_1_RECORD_TYPE = AccessTypeDto.PUBLIC;
     public final static FileTypeDto DOCUMENT_1_FILE_TYPE = FileTypeDto.PUBLIC;
 
+    public final static AccessTypeDto DOCUMENT_2_RECORD_TYPE = AccessTypeDto.PUBLIC;
+    public final static FileTypeDto DOCUMENT_2_FILE_TYPE = FileTypeDto.RESTRICTED;
+
     public final static AccessOptionsDto DOCUMENT_1_ACCESS_OPTIONS = AccessOptionsDto.builder()
             .record(DOCUMENT_1_RECORD_TYPE)
             .files(DOCUMENT_1_FILE_TYPE)
             .build();
 
-    public final static Boolean DOCUMENT_1_FILES_ENABLED = true;
+    public final static AccessOptionsDto DOCUMENT_2_ACCESS_OPTIONS = AccessOptionsDto.builder()
+            .record(DOCUMENT_2_RECORD_TYPE)
+            .files(DOCUMENT_2_FILE_TYPE)
+            .build();
+
+    public final static Boolean DOCUMENT_1_FILES_ENABLED = false;
+
+    public final static Boolean DOCUMENT_2_FILES_ENABLED = true;
 
     public final static FilesOptionsDto DOCUMENT_1_FILES_OPTIONS = FilesOptionsDto.builder()
             .enabled(DOCUMENT_1_FILES_ENABLED)
             .build();
 
-    public final static String DOCUMENT_1_TITLE = "Test Draft";
+    public final static FilesOptionsDto DOCUMENT_2_FILES_OPTIONS = FilesOptionsDto.builder()
+            .enabled(DOCUMENT_2_FILES_ENABLED)
+            .build();
+
+    public final static String DOCUMENT_1_TITLE = "Public Test-Record";
     public final static String DOCUMENT_1_RESOURCE_TYPE_TYPE = "other";
     public final static Date DOCUMENT_1_PUBLICATION_DATE = Date.from(Instant.now());
 
+    public final static String DOCUMENT_2_TITLE = "Restricted Test-Record";
+    public final static String DOCUMENT_2_RESOURCE_TYPE_TYPE = "other";
+    public final static Date DOCUMENT_2_PUBLICATION_DATE = Date.from(Instant.now());
+
     public final static ResourceTypeDto DOCUMENT_1_RESOURCE_TYPE = ResourceTypeDto.builder()
             .id(DOCUMENT_1_RESOURCE_TYPE_TYPE)
             .build();
 
+    public final static ResourceTypeDto DOCUMENT_2_RESOURCE_TYPE = ResourceTypeDto.builder()
+            .id(DOCUMENT_2_RESOURCE_TYPE_TYPE)
+            .build();
+
     public final static String IDENTIFIER_1_IDENTIFIER = "0000-0003-4216-302X";
     public final static IdentifierTypeDto IDENTIFIER_1_TYPE = IdentifierTypeDto.ORCID;
 
@@ -72,10 +102,23 @@ public abstract class BaseUnitTest {
             .creators(List.of(CREATOR_1))
             .build();
 
+    public final static MetadataDto DOCUMENT_2_METADATA = MetadataDto.builder()
+            .title(DOCUMENT_2_TITLE)
+            .resourceType(DOCUMENT_2_RESOURCE_TYPE)
+            .publicationDate(DOCUMENT_2_PUBLICATION_DATE)
+            .creators(List.of(CREATOR_1))
+            .build();
+
     public final static CreateDraftDto DOCUMENT_1_CREATE_DRAFT = CreateDraftDto.builder()
             .access(DOCUMENT_1_ACCESS_OPTIONS)
             .files(DOCUMENT_1_FILES_OPTIONS)
             .metadata(DOCUMENT_1_METADATA)
             .build();
 
+    public final static CreateDraftDto DOCUMENT_2_CREATE_DRAFT = CreateDraftDto.builder()
+            .access(DOCUMENT_2_ACCESS_OPTIONS)
+            .files(DOCUMENT_2_FILES_OPTIONS)
+            .metadata(DOCUMENT_2_METADATA)
+            .build();
+
 }
diff --git a/fda-document-service/rest-service/src/test/java/at/tuwien/endpoint/DocumentEndpointUnitTest.java b/fda-document-service/rest-service/src/test/java/at/tuwien/endpoint/DocumentEndpointUnitTest.java
index 0965a30b1e..534038dbad 100644
--- a/fda-document-service/rest-service/src/test/java/at/tuwien/endpoint/DocumentEndpointUnitTest.java
+++ b/fda-document-service/rest-service/src/test/java/at/tuwien/endpoint/DocumentEndpointUnitTest.java
@@ -1,13 +1,52 @@
 package at.tuwien.endpoint;
 
 import at.tuwien.BaseUnitTest;
+import at.tuwien.api.document.record.CreateDraftDto;
+import at.tuwien.api.document.record.RecordDto;
+import at.tuwien.endpoints.DocumentEndpoint;
+import at.tuwien.exception.DraftRecordCreateException;
+import at.tuwien.gateway.AuthenticationServiceGateway;
+import org.apache.http.auth.BasicUserPrincipal;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
+import java.security.Principal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
 @ExtendWith(SpringExtension.class)
 @SpringBootTest
 public class DocumentEndpointUnitTest extends BaseUnitTest {
 
+    @Autowired
+    private DocumentEndpoint documentEndpoint;
+
+    @MockBean
+    private AuthenticationServiceGateway authenticationServiceGateway;
+
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {"ROLE_RESEARCHER"})
+    public void create_succeed() throws DraftRecordCreateException {
+        final CreateDraftDto request = DOCUMENT_1_CREATE_DRAFT;
+        final Principal principal = new BasicUserPrincipal(USER_1_USERNAME);
+
+        /* mock */
+        when(authenticationServiceGateway.validate(anyString()))
+                .thenReturn(USER_1_DETAILS);
+
+        /* test */
+        final ResponseEntity<RecordDto> response = documentEndpoint.create(request, principal);
+        assertEquals(HttpStatus.CREATED, response.getStatusCode());
+    }
 
 }
diff --git a/fda-document-service/rest-service/src/test/java/at/tuwien/service/DocumentServiceIntegrationTest.java b/fda-document-service/rest-service/src/test/java/at/tuwien/service/DocumentServiceIntegrationTest.java
index 055fd45ca2..cdef81d0dc 100644
--- a/fda-document-service/rest-service/src/test/java/at/tuwien/service/DocumentServiceIntegrationTest.java
+++ b/fda-document-service/rest-service/src/test/java/at/tuwien/service/DocumentServiceIntegrationTest.java
@@ -2,7 +2,7 @@ package at.tuwien.service;
 
 import at.tuwien.BaseUnitTest;
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
 import at.tuwien.exception.DraftRecordCreateException;
 import lombok.extern.log4j.Log4j2;
 import org.apache.http.auth.BasicUserPrincipal;
@@ -35,7 +35,7 @@ public class DocumentServiceIntegrationTest extends BaseUnitTest {
         /* mock */
 
         /* test */
-        final DraftDto response = documentService.create(request, principal);
+        final RecordDto response = documentService.create(request, principal);
         assertEquals(DOCUMENT_1_TITLE, response.getMetadata().getTitle());
     }
 
@@ -47,8 +47,8 @@ public class DocumentServiceIntegrationTest extends BaseUnitTest {
         /* mock */
 
         /* test */
-        final DraftDto document = documentService.create(request, principal);
-        final DraftDto response = documentService.reserveDoi(document.getId(), principal);
+        final RecordDto document = documentService.create(request, principal);
+        final RecordDto response = documentService.reserveDoi(document.getId(), principal);
         assertNotNull(response.getPids().getDoi());
     }
 
diff --git a/fda-document-service/rest-service/src/test/java/at/tuwien/service/FileServiceIntegrationTest.java b/fda-document-service/rest-service/src/test/java/at/tuwien/service/FileServiceIntegrationTest.java
index 9172889617..b694b019aa 100644
--- a/fda-document-service/rest-service/src/test/java/at/tuwien/service/FileServiceIntegrationTest.java
+++ b/fda-document-service/rest-service/src/test/java/at/tuwien/service/FileServiceIntegrationTest.java
@@ -1,21 +1,31 @@
 package at.tuwien.service;
 
 import at.tuwien.BaseUnitTest;
-import at.tuwien.api.document.file.FileStartDto;
+import at.tuwien.api.document.file.FileDto;
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
+import at.tuwien.exception.FileUploadException;
+import at.tuwien.exception.CommitFileUploadException;
 import at.tuwien.exception.DraftRecordCreateException;
 import lombok.extern.log4j.Log4j2;
+import org.apache.commons.io.FileUtils;
 import org.apache.http.auth.BasicUserPrincipal;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.File;
+import java.io.IOException;
 import java.security.Principal;
 
+import static org.junit.jupiter.api.Assertions.*;
+
+
 @Log4j2
 @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
 @ExtendWith(SpringExtension.class)
@@ -29,15 +39,35 @@ public class FileServiceIntegrationTest extends BaseUnitTest {
     private FileService fileService;
 
     @Test
-    public void start_succeeds() throws DraftRecordCreateException {
+    public void upload_succeeds()
+            throws DraftRecordCreateException, IOException, CommitFileUploadException, FileUploadException {
+        final CreateDraftDto request = DOCUMENT_2_CREATE_DRAFT;
+        final Principal principal = new BasicUserPrincipal(USER_1_USERNAME);
+        final File mockFile = new File("src/test/resources/images/mock.png");
+
+        /* mock */
+        final MultipartFile file = new MockMultipartFile(mockFile.getName(), FileUtils.openInputStream(mockFile)
+                .readAllBytes());
+        final RecordDto document = documentService.create(request, principal);
+        assertFalse(document.getIsPublished());
+
+        /* test */
+        final FileDto response = fileService.uploadFile(document.getId(), file, principal);
+        assertEquals(file.getName(), response.getKey());
+    }
+    @Test
+    public void publish_succeeds()
+            throws DraftRecordCreateException {
         final CreateDraftDto request = DOCUMENT_1_CREATE_DRAFT;
         final Principal principal = new BasicUserPrincipal(USER_1_USERNAME);
 
         /* mock */
-        final DraftDto document = documentService.create(request, principal);
+        final RecordDto document = documentService.create(request, principal);
 
         /* test */
-        final FileStartDto response = fileService.start(document.getId(), principal);
+        final RecordDto response = documentService.publish(document.getId(), principal);
+        assertTrue(response.getIsPublished());
+        assertNotNull(response.getPids().getDoi());
     }
 
 }
diff --git a/fda-document-service/rest-service/src/test/resources/images/mock.png b/fda-document-service/rest-service/src/test/resources/images/mock.png
new file mode 100644
index 0000000000000000000000000000000000000000..4094aa6f22d95544339040f01f4f3d19f587d1d3
GIT binary patch
literal 11666
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
E0DWw>p8x;=

literal 0
HcmV?d00001

diff --git a/fda-document-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-document-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index e33f0dba4d..4d819a1786 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -55,4 +55,4 @@ public class AuthTokenFilter extends OncePerRequestFilter {
         }
         return null;
     }
-}
\ No newline at end of file
+}
diff --git a/fda-document-service/services/src/main/java/at/tuwien/config/GatewayConfig.java b/fda-document-service/services/src/main/java/at/tuwien/config/GatewayConfig.java
index 006541ad8c..776e4aa5d8 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/config/GatewayConfig.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/config/GatewayConfig.java
@@ -9,11 +9,21 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
 @Configuration
 public class GatewayConfig {
 
+    @Value("${fda.gateway.endpoint}")
+    private String gatewayEndpoint;
+
     @Value("${fda.document.endpoint}")
     private String documentEndpoint;
 
     @Bean
-    public RestTemplate restTemplate() {
+    public RestTemplate gatewayRestTemplate() {
+        final RestTemplate restTemplate =  new RestTemplate();
+        restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(gatewayEndpoint));
+        return restTemplate;
+    }
+
+    @Bean
+    public RestTemplate documentRestTemplate() {
         final RestTemplate restTemplate =  new RestTemplate();
         restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(documentEndpoint));
         return restTemplate;
diff --git a/fda-document-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-document-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index 70dac68fcd..472089fc21 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -65,7 +65,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         /* set permissions on endpoints */
         http.authorizeRequests()
                 /* our public endpoints */
-                .antMatchers(HttpMethod.GET, "/api/document/**").permitAll()
                 .antMatchers("/v3/api-docs.yaml",
                         "/v3/api-docs/**",
                         "/swagger-ui/**",
diff --git a/fda-document-service/services/src/main/java/at/tuwien/exception/CommitFileUploadException.java b/fda-document-service/services/src/main/java/at/tuwien/exception/CommitFileUploadException.java
new file mode 100644
index 0000000000..430f7bba54
--- /dev/null
+++ b/fda-document-service/services/src/main/java/at/tuwien/exception/CommitFileUploadException.java
@@ -0,0 +1,21 @@
+package at.tuwien.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(code = HttpStatus.CONFLICT)
+public class CommitFileUploadException extends Exception {
+
+    public CommitFileUploadException(String msg) {
+        super(msg);
+    }
+
+    public CommitFileUploadException(String msg, Throwable thr) {
+        super(msg, thr);
+    }
+
+    public CommitFileUploadException(Throwable thr) {
+        super(thr);
+    }
+
+}
diff --git a/fda-document-service/services/src/main/java/at/tuwien/exception/FileUploadException.java b/fda-document-service/services/src/main/java/at/tuwien/exception/FileUploadException.java
new file mode 100644
index 0000000000..590060826f
--- /dev/null
+++ b/fda-document-service/services/src/main/java/at/tuwien/exception/FileUploadException.java
@@ -0,0 +1,21 @@
+package at.tuwien.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(code = HttpStatus.FAILED_DEPENDENCY)
+public class FileUploadException extends Exception {
+
+    public FileUploadException(String msg) {
+        super(msg);
+    }
+
+    public FileUploadException(String msg, Throwable thr) {
+        super(msg, thr);
+    }
+
+    public FileUploadException(Throwable thr) {
+        super(thr);
+    }
+
+}
diff --git a/fda-document-service/services/src/main/java/at/tuwien/gateway/DocumentGateway.java b/fda-document-service/services/src/main/java/at/tuwien/gateway/DocumentGateway.java
index fe6a27d6d5..36d86d6d02 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/gateway/DocumentGateway.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/gateway/DocumentGateway.java
@@ -1,18 +1,26 @@
 package at.tuwien.gateway;
 
-import at.tuwien.api.document.file.FileStartDto;
+import at.tuwien.api.document.file.FileDto;
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
+import at.tuwien.exception.FileUploadException;
+import at.tuwien.exception.CommitFileUploadException;
 import at.tuwien.exception.DraftRecordCreateException;
+import org.springframework.web.multipart.MultipartFile;
 
 public interface DocumentGateway {
-    DraftDto createDraft(CreateDraftDto data, String token) throws DraftRecordCreateException;
+    RecordDto createDraft(CreateDraftDto data, String token) throws DraftRecordCreateException;
 
-    DraftDto reserveDraftDoi(String id, String token) throws DraftRecordCreateException;
+    RecordDto reserveDraftDoi(String id, String token) throws DraftRecordCreateException;
 
-    DraftDto findDraft(String id, String token) throws DraftRecordCreateException;
+    RecordDto findDraft(String id, String token) throws DraftRecordCreateException;
 
-    FileStartDto startUpload(String id, String token) throws DraftRecordCreateException;
+    RecordDto publishDraft(String id, String token) throws DraftRecordCreateException;
+
+    FileDto uploadFile(String id, MultipartFile file, String token)
+            throws DraftRecordCreateException, FileUploadException,
+            org.apache.tomcat.util.http.fileupload.FileUploadException,
+            CommitFileUploadException;
 
     void delete(String id, String token) throws DraftRecordCreateException;
 }
diff --git a/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
index 56d691a6c8..5492233420 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
@@ -18,20 +18,20 @@ import org.springframework.web.client.RestTemplate;
 public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway {
 
     private final UserMapper userMapper;
-    private final RestTemplate restTemplate;
+    private final RestTemplate gatewayRestTemplate;
 
     @Autowired
-    public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate restTemplate) {
+    public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate gatewayRestTemplate) {
         this.userMapper = userMapper;
-        this.restTemplate = restTemplate;
+        this.gatewayRestTemplate = gatewayRestTemplate;
     }
 
     @Override
     public UserDetails validate(String token) {
         final HttpHeaders headers = new HttpHeaders();
         headers.set("Authorization", "Bearer " + token);
-        final ResponseEntity<UserDto> response = restTemplate.exchange("/api/auth", HttpMethod.PUT,
-                new HttpEntity<>("", headers), UserDto.class);
+        final ResponseEntity<UserDto> response = gatewayRestTemplate.exchange("/api/auth", HttpMethod.PUT,
+                new HttpEntity<>(null, headers), UserDto.class);
         return userMapper.userDtoToUserDetailsDto(response.getBody());
     }
 
diff --git a/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/InvenioDocumentGatewayImpl.java b/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/InvenioDocumentGatewayImpl.java
index 318594ee12..bca9d5b863 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/InvenioDocumentGatewayImpl.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/gateway/impl/InvenioDocumentGatewayImpl.java
@@ -1,36 +1,49 @@
 package at.tuwien.gateway.impl;
 
-import at.tuwien.api.document.file.FileStartDto;
+import at.tuwien.api.document.file.FileAnnounceDto;
+import at.tuwien.api.document.file.FileDto;
+import at.tuwien.api.document.file.FileKeyDto;
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
+import at.tuwien.exception.FileUploadException;
+import at.tuwien.exception.CommitFileUploadException;
 import at.tuwien.exception.DraftRecordCreateException;
 import at.tuwien.gateway.DocumentGateway;
+import at.tuwien.mapper.DocumentMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.*;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
 
 @Slf4j
 @Component
 public class InvenioDocumentGatewayImpl implements DocumentGateway {
 
-    private RestTemplate restTemplate;
+    private final static String OCTET_STREAM = "application/octet-stream";
+
+    private final DocumentMapper documentMapper;
+    private final RestTemplate documentRestTemplate;
 
     @Autowired
-    public InvenioDocumentGatewayImpl(RestTemplate restTemplate) {
-        this.restTemplate = restTemplate;
+    public InvenioDocumentGatewayImpl(DocumentMapper documentMapper, RestTemplate documentRestTemplate) {
+        this.documentMapper = documentMapper;
+        this.documentRestTemplate = documentRestTemplate;
     }
 
     @Override
-    public DraftDto createDraft(CreateDraftDto data, String token) throws DraftRecordCreateException {
+    public RecordDto createDraft(CreateDraftDto data, String token) throws DraftRecordCreateException {
         log.trace("sending {}", data);
         final String url = "/api/records";
-        final ResponseEntity<DraftDto> response;
+        final ResponseEntity<RecordDto> response;
         try {
-            response = restTemplate.exchange(url, HttpMethod.POST,
-                    new HttpEntity<>(data, headers(token)), DraftDto.class);
+            response = documentRestTemplate.exchange(url, HttpMethod.POST,
+                    new HttpEntity<>(data, headers(token)), RecordDto.class);
         } catch (HttpClientErrorException.BadRequest e) {
             log.error("Failed to create draft record");
             throw new DraftRecordCreateException("Failed to create draft record", e);
@@ -39,12 +52,12 @@ public class InvenioDocumentGatewayImpl implements DocumentGateway {
     }
 
     @Override
-    public DraftDto reserveDraftDoi(String id, String token) throws DraftRecordCreateException {
+    public RecordDto reserveDraftDoi(String id, String token) throws DraftRecordCreateException {
         final String url = "/api/records/" + id + "/draft/pids/doi";
-        final ResponseEntity<DraftDto> response;
+        final ResponseEntity<RecordDto> response;
         try {
-            response = restTemplate.exchange(url, HttpMethod.POST,
-                    new HttpEntity<>(null, headers(token)), DraftDto.class);
+            response = documentRestTemplate.exchange(url, HttpMethod.POST,
+                    new HttpEntity<>(null, headers(token)), RecordDto.class);
         } catch (HttpClientErrorException.BadRequest e) {
             log.error("Failed to reserve draft doi");
             throw new DraftRecordCreateException("Failed to reserve draft doi", e);
@@ -53,12 +66,12 @@ public class InvenioDocumentGatewayImpl implements DocumentGateway {
     }
 
     @Override
-    public DraftDto findDraft(String id, String token) throws DraftRecordCreateException {
+    public RecordDto findDraft(String id, String token) throws DraftRecordCreateException {
         final String url = "/api/records/" + id + "/draft";
-        final ResponseEntity<DraftDto> response;
+        final ResponseEntity<RecordDto> response;
         try {
-            response = restTemplate.exchange(url, HttpMethod.GET,
-                    new HttpEntity<>(null, headers(token)), DraftDto.class);
+            response = documentRestTemplate.exchange(url, HttpMethod.GET,
+                    new HttpEntity<>(null, headers(token)), RecordDto.class);
         } catch (HttpClientErrorException.BadRequest e) {
             log.error("Failed to find draft record");
             throw new DraftRecordCreateException("Failed to create find record", e);
@@ -67,24 +80,76 @@ public class InvenioDocumentGatewayImpl implements DocumentGateway {
     }
 
     @Override
-    public FileStartDto startUpload(String id, String token) throws DraftRecordCreateException {
-        final String url = "/api/records/" + id + "/draft/files";
-        final ResponseEntity<FileStartDto> response;
+    public RecordDto publishDraft(String id, String token) throws DraftRecordCreateException {
+        final String url = "/api/records/" + id + "/draft/actions/publish";
+        final ResponseEntity<RecordDto> response;
         try {
-            response = restTemplate.exchange(url, HttpMethod.POST,
-                    new HttpEntity<>(null, headers(token)), FileStartDto.class);
+            response = documentRestTemplate.exchange(url, HttpMethod.POST,
+                    new HttpEntity<>(null, headers(token)), RecordDto.class);
         } catch (HttpClientErrorException.BadRequest e) {
-            log.error("Failed to start draft files");
-            throw new DraftRecordCreateException("Failed to start draft files", e);
+            log.error("Failed to publish draft record");
+            throw new DraftRecordCreateException("Failed to publish find record", e);
         }
         return response.getBody();
     }
 
+    @Override
+    public FileDto uploadFile(String id, MultipartFile file, String token)
+            throws FileUploadException {
+        /* announce */
+        final String url1 = "/api/records/" + id + "/draft/files";
+        final List<FileKeyDto> files = List.of(documentMapper.stringToFileKeyDto(file.getName()));
+        final ResponseEntity<FileAnnounceDto> response1;
+        try {
+            response1 = documentRestTemplate.exchange(url1, HttpMethod.POST,
+                    new HttpEntity<>(files, headers(token)), FileAnnounceDto.class);
+        } catch (HttpClientErrorException.BadRequest e) {
+            log.error("Failed to announce draft file");
+            throw new FileUploadException("Failed to announce draft file", e);
+        }
+        if (response1.getStatusCode() != HttpStatus.CREATED) {
+            log.error("Failed to announce file upload");
+            throw new FileUploadException("Failed to announce file upload");
+        }
+        /* upload */
+        final String url2 = "/api/records/" + id + "/draft/files/" + file.getName() + "/content";
+        final ResponseEntity<Void> response2;
+        try {
+            response2 = documentRestTemplate.exchange(url2, HttpMethod.PUT,
+                    new HttpEntity<>(file.getBytes(), headers(token, OCTET_STREAM)), Void.class);
+        } catch (HttpClientErrorException.BadRequest e) {
+            log.error("Failed to upload draft file");
+            throw new FileUploadException("Failed to upload draft file", e);
+        } catch (IOException e) {
+            log.error("Failed to get draft file bytes");
+            throw new FileUploadException("Failed to get draft file bytes", e);
+        }
+        if (response2.getStatusCode() != HttpStatus.OK) {
+            log.error("Failed to upload file");
+            throw new FileUploadException("Failed to upload file");
+        }
+        /* commit */
+        final String url3 = "/api/records/" + id + "/draft/files/" + file.getName() + "/commit";
+        final ResponseEntity<FileDto> response3;
+        try {
+            response3 = documentRestTemplate.exchange(url3, HttpMethod.POST,
+                    new HttpEntity<>(null, headers(token)), FileDto.class);
+        } catch (HttpClientErrorException.BadRequest e) {
+            log.error("Failed to commit draft file");
+            throw new FileUploadException("Failed to commit draft file", e);
+        }
+        if (response3.getStatusCode() != HttpStatus.OK) {
+            log.error("Failed to commit file");
+            throw new FileUploadException("Failed to commit file");
+        }
+        return response3.getBody();
+    }
+
     @Override
     public void delete(String id, String token) throws DraftRecordCreateException {
         final String url = "/api/records" + id + "/draft";
         try {
-            restTemplate.exchange(url, HttpMethod.DELETE,
+            documentRestTemplate.exchange(url, HttpMethod.DELETE,
                     new HttpEntity<>(null, headers(token)), Void.class);
         } catch (HttpClientErrorException.BadRequest e) {
             log.error("Failed to delete draft record");
@@ -99,9 +164,20 @@ public class InvenioDocumentGatewayImpl implements DocumentGateway {
      * @return The headers.
      */
     private HttpHeaders headers(String token) {
+        return headers(token, "application/json");
+    }
+
+    /**
+     * Prepares the headers for all requests to authorize with the bearer token and content type.
+     *
+     * @param token       The token.
+     * @param contentType The content type.
+     * @return The headers.
+     */
+    private HttpHeaders headers(String token, String contentType) {
         final HttpHeaders headers = new HttpHeaders();
         headers.add("Authorization", "Bearer " + token);
-        headers.add("Content-Type", "application/json");
+        headers.add("Content-Type", contentType);
         return headers;
     }
 }
diff --git a/fda-document-service/services/src/main/java/at/tuwien/mapper/DocumentMapper.java b/fda-document-service/services/src/main/java/at/tuwien/mapper/DocumentMapper.java
index 0132073b62..2457da7cec 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/mapper/DocumentMapper.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/mapper/DocumentMapper.java
@@ -1,10 +1,16 @@
 package at.tuwien.mapper;
 
+import at.tuwien.api.document.file.FileKeyDto;
 import org.mapstruct.*;
 
 
 @Mapper(componentModel = "spring")
 public interface DocumentMapper {
 
+    default FileKeyDto stringToFileKeyDto(String data) {
+        return FileKeyDto.builder()
+                .key(data)
+                .build();
+    }
 
 }
diff --git a/fda-document-service/services/src/main/java/at/tuwien/service/DocumentService.java b/fda-document-service/services/src/main/java/at/tuwien/service/DocumentService.java
index 5d102b335c..182feb2d5b 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/service/DocumentService.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/service/DocumentService.java
@@ -1,18 +1,20 @@
 package at.tuwien.service;
 
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
 import at.tuwien.exception.DraftRecordCreateException;
 
 import java.security.Principal;
 
 public interface DocumentService {
 
-    DraftDto findById(String id, Principal principal) throws DraftRecordCreateException;
+    RecordDto findById(String id, Principal principal) throws DraftRecordCreateException;
 
-    DraftDto create(CreateDraftDto data, Principal principal) throws DraftRecordCreateException;
+    RecordDto create(CreateDraftDto data, Principal principal) throws DraftRecordCreateException;
 
-    DraftDto reserveDoi(String id, Principal principal) throws DraftRecordCreateException;
+    RecordDto publish(String id, Principal principal) throws DraftRecordCreateException;
+
+    RecordDto reserveDoi(String id, Principal principal) throws DraftRecordCreateException;
 
     void delete(String id, Principal principal) throws DraftRecordCreateException;
 }
diff --git a/fda-document-service/services/src/main/java/at/tuwien/service/FileService.java b/fda-document-service/services/src/main/java/at/tuwien/service/FileService.java
index 5113be20f9..67fb8932b5 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/service/FileService.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/service/FileService.java
@@ -1,11 +1,17 @@
 package at.tuwien.service;
 
 
-import at.tuwien.api.document.file.FileStartDto;
+import at.tuwien.api.document.file.FileDto;
+import at.tuwien.exception.FileUploadException;
+import at.tuwien.exception.CommitFileUploadException;
 import at.tuwien.exception.DraftRecordCreateException;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.security.Principal;
 
 public interface FileService {
-    FileStartDto start(String id, Principal principal) throws DraftRecordCreateException;
+
+    FileDto uploadFile(String id, MultipartFile file, Principal principal)
+            throws DraftRecordCreateException, CommitFileUploadException, FileUploadException,
+            org.apache.tomcat.util.http.fileupload.FileUploadException;
 }
diff --git a/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioDraftServiceImpl.java b/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioDraftServiceImpl.java
index 456fd6b540..06c038521a 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioDraftServiceImpl.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioDraftServiceImpl.java
@@ -1,7 +1,7 @@
 package at.tuwien.service.impl;
 
 import at.tuwien.api.document.record.CreateDraftDto;
-import at.tuwien.api.document.record.DraftDto;
+import at.tuwien.api.document.record.RecordDto;
 import at.tuwien.config.InvenioConfig;
 import at.tuwien.exception.DraftRecordCreateException;
 import at.tuwien.gateway.DocumentGateway;
@@ -26,27 +26,37 @@ public class InvenioDraftServiceImpl implements DocumentService {
     }
 
     @Override
-    public DraftDto findById(String id, Principal principal) throws DraftRecordCreateException {
+    public RecordDto findById(String id, Principal principal) throws DraftRecordCreateException {
         /* get token */
         /* remote */
         return documentGateway.findDraft(id, invenioConfig.getDebugToken());
     }
 
     @Override
-    public DraftDto create(CreateDraftDto data, Principal principal) throws DraftRecordCreateException {
+    public RecordDto create(CreateDraftDto data, Principal principal) throws DraftRecordCreateException {
         /* get token */
         /* remote */
-        final DraftDto document = documentGateway.createDraft(data, invenioConfig.getDebugToken());
+        final RecordDto document = documentGateway.createDraft(data, invenioConfig.getDebugToken());
         log.info("Created draft record with id {}", document.getId());
         log.debug("created draft record {}", document);
         return document;
     }
 
     @Override
-    public DraftDto reserveDoi(String id, Principal principal) throws DraftRecordCreateException {
+    public RecordDto publish(String id, Principal principal) throws DraftRecordCreateException {
         /* get token */
         /* remote */
-        final DraftDto document = documentGateway.reserveDraftDoi(id, invenioConfig.getDebugToken());
+        final RecordDto document = documentGateway.publishDraft(id, invenioConfig.getDebugToken());
+        log.info("Published draft record with id {}", document.getId());
+        log.debug("published draft record {}", document);
+        return document;
+    }
+
+    @Override
+    public RecordDto reserveDoi(String id, Principal principal) throws DraftRecordCreateException {
+        /* get token */
+        /* remote */
+        final RecordDto document = documentGateway.reserveDraftDoi(id, invenioConfig.getDebugToken());
         log.info("Reserved DOI {} for draft record with id {}", document.getPids().getDoi(), document.getId());
         log.debug("reserved PID {} for draft record with id {}", document.getPids(), document);
         return document;
diff --git a/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioFileServiceImpl.java b/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioFileServiceImpl.java
index 459454f346..4110d07846 100644
--- a/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioFileServiceImpl.java
+++ b/fda-document-service/services/src/main/java/at/tuwien/service/impl/InvenioFileServiceImpl.java
@@ -1,13 +1,16 @@
 package at.tuwien.service.impl;
 
-import at.tuwien.api.document.file.FileStartDto;
+import at.tuwien.api.document.file.FileDto;
 import at.tuwien.config.InvenioConfig;
+import at.tuwien.exception.FileUploadException;
+import at.tuwien.exception.CommitFileUploadException;
 import at.tuwien.exception.DraftRecordCreateException;
 import at.tuwien.gateway.DocumentGateway;
 import at.tuwien.service.FileService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.security.Principal;
 
@@ -25,12 +28,14 @@ public class InvenioFileServiceImpl implements FileService {
     }
 
     @Override
-    public FileStartDto start(String id, Principal principal) throws DraftRecordCreateException {
+    public FileDto uploadFile(String id, MultipartFile file, Principal principal)
+            throws DraftRecordCreateException, CommitFileUploadException, FileUploadException,
+            org.apache.tomcat.util.http.fileupload.FileUploadException {
         /* get token */
         /* remote */
-        final FileStartDto document = documentGateway.startUpload(id, invenioConfig.getDebugToken());
-        log.info("Started draft files with id {}", id);
-        log.debug("started draft files {}", document);
+        final FileDto document = documentGateway.uploadFile(id, file, invenioConfig.getDebugToken());
+        log.info("Deposited draft file content for record with id {}", id);
+        log.debug("Deposited draft file content for record {}", document);
         return document;
     }
 
diff --git a/fda-gateway-service/gateway/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java b/fda-gateway-service/gateway/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java
index 040bbb0e96..27a3863e80 100644
--- a/fda-gateway-service/gateway/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java
+++ b/fda-gateway-service/gateway/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java
@@ -56,6 +56,11 @@ public class GatewayConfig {
                         .method("POST", "GET", "PUT", "DELETE")
                         .and()
                         .uri("lb://fda-units-service"))
+                .route("fda-document-service", r -> r.path("/api/document/**")
+                        .and()
+                        .method("POST", "GET", "PUT", "DELETE")
+                        .and()
+                        .uri("lb://fda-document-service"))
                 .build();
 
     }
diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/FdaIdentifierServiceApplication.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/FdaIdentifierServiceApplication.java
index 185a41efc0..56712b0f03 100644
--- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/FdaIdentifierServiceApplication.java
+++ b/fda-identifier-service/rest-service/src/main/java/at/tuwien/FdaIdentifierServiceApplication.java
@@ -8,7 +8,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
-
 @SpringBootApplication
 @EnableJpaAuditing
 @EnableTransactionManagement
diff --git a/fda-identifier-service/rest-service/src/main/resources/application.yml b/fda-identifier-service/rest-service/src/main/resources/application.yml
index 11335fa111..e71ce69b3f 100644
--- a/fda-identifier-service/rest-service/src/main/resources/application.yml
+++ b/fda-identifier-service/rest-service/src/main/resources/application.yml
@@ -22,6 +22,7 @@ logging:
   level:
     root: warn
     at.tuwien.: debug
+    at.tuwien.gateway.: trace
     org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug
 eureka:
   instance.hostname: fda-identifier-service
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileAnnounceDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileAnnounceDto.java
new file mode 100644
index 0000000000..c2941ccdec
--- /dev/null
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileAnnounceDto.java
@@ -0,0 +1,37 @@
+
+package at.tuwien.api.document.file;
+
+import at.tuwien.api.document.links.LinksDto;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.Parameter;
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Getter
+@Setter
+@ToString
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FileAnnounceDto {
+
+    @JsonProperty("default_preview")
+    @Parameter(name = "file name")
+    private String defaultPreview;
+
+    @NotNull
+    @Parameter(name = "file enabled")
+    private Boolean enabled;
+
+    @NotNull
+    @Parameter(name = "file entries")
+    private List<FileEntryDto> entries;
+
+    @Parameter(name = "file links")
+    private LinksDto links;
+
+}
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileDto.java
new file mode 100644
index 0000000000..63f820e372
--- /dev/null
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileDto.java
@@ -0,0 +1,72 @@
+package at.tuwien.api.document.file;
+
+import at.tuwien.api.document.links.LinksDto;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.Parameter;
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+
+
+@Getter
+@Setter
+@ToString
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FileDto {
+
+    @NotBlank
+    @JsonProperty("bucket_id")
+    @Parameter(name = "bucket id", description = "Bucket id.")
+    private String bucketId;
+
+    @NotBlank
+    @Parameter(name = "file checksum", description = "File checksum.", example = "md5:ef8fcf1f046bb24f1db1f1a376ddbfbb")
+    private String checksum;
+
+    @NotNull
+    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", timezone = "UTC+2")
+    @Parameter(name = "file creation timestamp")
+    private Instant created;
+
+    @NotNull
+    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", timezone = "UTC+2")
+    @Parameter(name = "file updated timestamp")
+    private Instant updated;
+
+    @NotBlank
+    @JsonProperty("file_id")
+    @Parameter(name = "file id")
+    private String fileId;
+
+    @NotBlank
+    @Parameter(name = "file key", example = "mock.png")
+    private String key;
+
+    @NotNull
+    @Parameter(name = "file links")
+    private LinksDto links;
+
+    @Parameter(name = "file mimetype")
+    private String mimetype;
+
+    @Parameter(name = "file size")
+    private Long size;
+
+    @Parameter(name = "file status")
+    private String status;
+
+    @JsonProperty("storage_class")
+    @Parameter(name = "file storage class", example = "S")
+    private String storageClass;
+
+    @JsonProperty("version_id")
+    @Parameter(name = "file version id")
+    private String versionId;
+}
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileEntryDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileEntryDto.java
new file mode 100644
index 0000000000..d4c7909d5a
--- /dev/null
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileEntryDto.java
@@ -0,0 +1,43 @@
+
+package at.tuwien.api.document.file;
+
+import at.tuwien.api.document.links.LinksDto;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.Parameter;
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+
+@Getter
+@Setter
+@ToString
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FileEntryDto {
+
+    @NotBlank
+    @Parameter(name = "file name", description = "Name of the file.")
+    private String key;
+
+    @NotNull
+    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", timezone = "UTC+2")
+    @Parameter(name = "file updated")
+    private Instant updated;
+
+    @NotNull
+    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", timezone = "UTC+2")
+    @Parameter(name = "file created")
+    private Instant created;
+
+    @Parameter(name = "file status")
+    private String status;
+
+    @Parameter(name = "file links")
+    private LinksDto links;
+
+}
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileKeyDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileKeyDto.java
new file mode 100644
index 0000000000..79c7f3d7b7
--- /dev/null
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/file/FileKeyDto.java
@@ -0,0 +1,23 @@
+
+package at.tuwien.api.document.file;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.Parameter;
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+
+@Getter
+@Setter
+@ToString
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FileKeyDto {
+
+    @NotBlank
+    @Parameter(name = "file name", description = "Name of the file.", example = "mock.png")
+    private String key;
+
+}
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/DraftDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/RecordDto.java
similarity index 98%
rename from fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/DraftDto.java
rename to fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/RecordDto.java
index d754f22fce..0f41c523bb 100644
--- a/fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/DraftDto.java
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/document/record/RecordDto.java
@@ -18,7 +18,7 @@ import java.time.Instant;
 @AllArgsConstructor
 @NoArgsConstructor
 @JsonInclude(JsonInclude.Include.NON_NULL)
-public class DraftDto {
+public class RecordDto {
 
     @NotNull(message = "access is required")
     @Parameter(name = "access")
diff --git a/fda-ui/server-middleware/index.js b/fda-ui/server-middleware/index.js
index b74dab0892..819f649f24 100644
--- a/fda-ui/server-middleware/index.js
+++ b/fda-ui/server-middleware/index.js
@@ -25,6 +25,7 @@ app.post('/table_from_csv', upload.single('file'), async (req, res) => {
 
   // send path to analyse service
   let analysis
+  let json
   try {
     const analyseUrl = `${process.env.API}/api/analyse/determinedt`
     analysis = await fetch(analyseUrl, {
@@ -35,9 +36,8 @@ app.post('/table_from_csv', upload.single('file'), async (req, res) => {
       console.error('data type determination failed', error)
       throw error
     })
-    const json = await analysis.json()
-    analysis = JSON.parse(json)
-    if (!analysis.columns) {
+    json = await analysis.json()
+    if (!json.columns) {
       return res.json({ success: false, message: 'Columns array missing' })
     }
   } catch (error) {
@@ -47,7 +47,7 @@ app.post('/table_from_csv', upload.single('file'), async (req, res) => {
 
   // map messytables / CoMi's `determine_dt` column types to ours
   // e.g. "Integer" -> "NUMBER"
-  let entries = Object.entries(analysis.columns)
+  let entries = Object.entries(json.columns)
   entries = entries.map(([k, v]) => {
     if (colTypeMap[v]) {
       v = colTypeMap[v]
-- 
GitLab