From 35d0696a0c78465a12efa785af0cf1a48343ad78 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Wed, 24 Aug 2022 09:18:11 +0200 Subject: [PATCH] Added Broker API again for Python, fixed some bugs and moved the download .csv button down to time-versioned area --- .env.win.example | 2 +- .gitignore | 1 - .jupyter/.gitignore | 8 +- .jupyter/api_broker/BrokerServiceClient.py | 26 ++ .jupyter/api_broker/__init__.py | 0 .jupyter/dev.py | 33 +- .jupyter/load_test.py | 21 ++ .jupyter/requirements.txt | 3 +- .jupyter/resources/ugz_ogd_air_h1_2021.csv | 1 - .jupyter/setup.py | 0 .jupyter/tuple_publish.ipynb | 329 ++++++++++-------- docker-compose.prod.yml | 6 +- docker-compose.yml | 14 - .../java/at/tuwien/auth/AuthTokenFilter.java | 1 - .../service/impl/UserDetailsServiceImpl.java | 2 +- fda-container-service/Dockerfile | 1 + fda-container-service/pom.xml | 8 +- .../src/main/resources/application-docker.yml | 2 +- .../src/main/resources/application-local.yml | 37 ++ .../src/main/resources/application.yml | 2 +- .../java/at/tuwien/config/DockerConfig.java | 6 + .../java/at/tuwien/config/ReadyConfig.java | 10 - .../main/java/at/tuwien/seeder/Seeder.java | 6 - .../at/tuwien/seeder/impl/AbstractSeeder.java | 216 ------------ .../seeder/impl/ContainerSeederImpl.java | 42 --- .../tuwien/seeder/impl/ImageSeederImpl.java | 53 --- .../at/tuwien/seeder/impl/SeederImpl.java | 31 -- .../java/at/tuwien/service/ImageService.java | 9 + .../service/impl/ContainerServiceImpl.java | 26 +- .../tuwien/service/impl/ImageServiceImpl.java | 11 +- .../java/at/tuwien/mapper/DatabaseMapper.java | 5 - .../api/database/query/QueryBriefDto.java | 77 ++++ fda-metadata-db/setup-schema.sql | 20 ++ .../at/tuwien/endpoint/StoreEndpoint.java | 20 +- .../at/tuwien/endpoint/TableDataEndpoint.java | 3 +- .../tuwien/endpoint/TableHistoryEndpoint.java | 3 +- .../tuwien/handlers/ApiExceptionHandler.java | 4 +- .../src/main/resources/application-docker.yml | 2 +- .../endpoint/StoreEndpointUnitTest.java | 3 +- .../exception/TableMalformedException.java | 2 +- .../java/at/tuwien/mapper/QueryMapper.java | 13 +- .../java/at/tuwien/service/UserService.java | 9 + .../tuwien/service/impl/QueryServiceImpl.java | 21 +- .../tuwien/service/impl/UserServiceImpl.java | 6 + fda-ui/.env.example | 1 + fda-ui/Dockerfile | 2 + fda-ui/components/TableList.vue | 45 ++- fda-ui/components/TableSchema.vue | 9 +- fda-ui/components/dialogs/CreateDB.vue | 22 +- fda-ui/layouts/default.vue | 102 +++--- fda-ui/nuxt.config.js | 16 +- .../_database_id/table/_table_id/import.vue | 5 +- .../_database_id/table/_table_id/index.vue | 24 +- .../database/_database_id/table/import.vue | 8 +- fda-ui/pages/privacy.vue | 53 --- fda-ui/server-middleware/index.js | 9 +- fda-ui/yarn.lock | 23 ++ 57 files changed, 632 insertions(+), 782 deletions(-) create mode 100644 .jupyter/api_broker/BrokerServiceClient.py create mode 100644 .jupyter/api_broker/__init__.py mode change 100644 => 100755 .jupyter/dev.py mode change 100644 => 100755 .jupyter/load_test.py create mode 100644 .jupyter/setup.py create mode 100644 fda-container-service/rest-service/src/main/resources/application-local.yml delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/seeder/Seeder.java delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/seeder/impl/AbstractSeeder.java delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ContainerSeederImpl.java delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ImageSeederImpl.java delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/seeder/impl/SeederImpl.java create mode 100644 fda-metadata-db/api/src/main/java/at/tuwien/api/database/query/QueryBriefDto.java delete mode 100644 fda-ui/pages/privacy.vue diff --git a/.env.win.example b/.env.win.example index f023263227..c7e7597ae8 100644 --- a/.env.win.example +++ b/.env.win.example @@ -19,4 +19,4 @@ SMTP_PORT= SMTP_USERNAME= SMTP_PASSWORD= EUREKA_SERVER=http://discovery-service:9090/eureka/ -SHARED_FILESYSTEM=%localappdata%\Temp +SHARED_FILESYSTEM=C:\tmp diff --git a/.gitignore b/.gitignore index 2518b0c438..34c4c63aa3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ target/ .jupyter/.idea/ .jupyter/.ipynb_checkpoints .jupyter/api_authentication -.jupyter/api_broker .jupyter/api_container .jupyter/api_database .jupyter/api_identifier diff --git a/.jupyter/.gitignore b/.jupyter/.gitignore index 4da7d714ec..83270ebc6e 100644 --- a/.jupyter/.gitignore +++ b/.jupyter/.gitignore @@ -1,6 +1,2 @@ -__pycache__ -venv/ - -publish.py - -./swagger-codegen-cli.jar \ No newline at end of file +# Python +__pycache__ \ No newline at end of file diff --git a/.jupyter/api_broker/BrokerServiceClient.py b/.jupyter/api_broker/BrokerServiceClient.py new file mode 100644 index 0000000000..e7bfd582a8 --- /dev/null +++ b/.jupyter/api_broker/BrokerServiceClient.py @@ -0,0 +1,26 @@ +import json +from pika import BlockingConnection, ConnectionParameters +from pika.credentials import PlainCredentials + + +class BrokerServiceClient: + + def __init__(self, exchange, routing_key, host, username, password): + self.exchange = exchange + self.routing_key = routing_key + self.host = host + self.username = username + self.password = password + self.connection = BlockingConnection(ConnectionParameters(self.host, + credentials=PlainCredentials( + username=self.username, + password=self.password))) + + def send(self, message): + channel = self.connection.channel() + payload = json.dumps(message) + print("... sending tuple %s" % payload) + channel.basic_publish(exchange=self.exchange, routing_key=self.routing_key, + body=bytes(payload, encoding='utf8')) + print("... sent tuple") + channel.close() diff --git a/.jupyter/api_broker/__init__.py b/.jupyter/api_broker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.jupyter/dev.py b/.jupyter/dev.py old mode 100644 new mode 100755 index 63f03ca33a..0f75b2eb77 --- a/.jupyter/dev.py +++ b/.jupyter/dev.py @@ -1,34 +1,7 @@ #!/bin/env python3 -import time - from api_broker.BrokerServiceClient import BrokerServiceClient -broker = BrokerServiceClient(exchange="airquality2", routing_key="airquality2", host="localhost", username="user", - password="user") -payload = {'date': '2022-07-21', 'location': 'Kuala Lumpur', 'parameter': 'T', 'interval': 'h1', 'unit': 'deg-celsius', - 'value': 33.0, 'status': 'tentative'} -response = broker.send(payload) -print(payload) - -# faulty date -payload = {'date': '2022-07', 'location': 'Kuala Lumpur', 'parameter': 'T', 'interval': 'h1', 'unit': 'deg-celsius', - 'value': 33.0, 'status': 'tentative'} -response = broker.send(payload) -print(payload) - -# faulty number -payload = {'date': '2022-07-01', 'location': 'Kuala Lumpur', 'parameter': 'T', 'interval': 'h1', 'unit': 'deg-celsius', - 'value': 'hello', 'status': 'tentative'} -response = broker.send(payload) -print(payload) - -# faulty string -payload = {'date': '2022-07-01', 'location': 'Kuala Lumpur', 'parameter': 'T', 'interval': 'h1', 'unit': 'deg-celsius', - 'value': 33.0, 'status': 88} +broker = BrokerServiceClient(exchange="airquality_6f28efee-2377-11ed-9491-8c8caada74c3", routing_key="airquality_6f510ee8-2377-11ed-9491-8c8caada74c3", host="localhost", username="test1", + password="test1") +payload = {"date": "2021-01-01", "location": "Stampfenbachstrasse", "parameter": "CO", "interval": "h1", "unit": "mg/m3", "value": 0.44, "status": "tentative"} response = broker.send(payload) -print(payload) - -while True: - response = broker.send(payload) - print(payload) - time.sleep(1) diff --git a/.jupyter/load_test.py b/.jupyter/load_test.py old mode 100644 new mode 100755 index 3b3afa3705..1336060476 --- a/.jupyter/load_test.py +++ b/.jupyter/load_test.py @@ -8,6 +8,7 @@ import requests as rq from postgres import Postgres import api_query.rest +from api_broker.BrokerServiceClient import BrokerServiceClient 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 @@ -99,6 +100,12 @@ def create_database(container_id, is_public=True): return response +def find_database(container_id, database_id): + response = database.find_by_id(container_id, database_id) + print("found database with id %d" % response.id) + return response + + def update_database(container_id, database_id, is_public=True): response = database.update({ "description": "This dataset includes daily values from 1983 to the current day, divided into annual files. This includes the maximum hourly average and the number of times the hourly average limit value for ozone was exceeded and the daily averages for sulfur dioxide (SO2), carbon monoxide (CO), nitrogen oxide (NOx), nitrogen monoxide (NO), nitrogen dioxide (NO2), particulate matter (PM10 and PM2.5). ) and particle number (PN), provided that they are of sufficient quality. The values of the completed day for the current year are updated every 30 minutes after midnight (UTC+1).", @@ -295,6 +302,14 @@ def download_identifier_metadata(container_id, database_id, identifier_id): return response +def send_tuple(exchange, routing_key, username, password, payload): + broker = BrokerServiceClient(exchange=exchange, routing_key=routing_key, host="localhost", username=username, + password=password) + response = broker.send(payload) + print("sent tuple to exchange with routing key %s" % routing_key) + return response + + if __name__ == '__main__': # # create 1 user and 3 containers (public, private, public) @@ -349,6 +364,7 @@ if __name__ == '__main__': cid = create_container().id start_container(cid) dbid = create_database(cid).id + dbexchange = find_database(cid, dbid).exchange update_database(cid, dbid) create_table(cid, dbid, columns=[]) create_table(cid, dbid, columns=[{ @@ -365,6 +381,10 @@ if __name__ == '__main__': "primary_key": True, "null_allowed": False, }]).id + ttopic = find_table(cid, dbid, tid).topic + send_tuple(dbexchange, ttopic, "test1", "test1", {"primary": 1}) + send_tuple(dbexchange, ttopic, "test1", "test1", {"primary": 2}) + send_tuple(dbexchange, ttopic, "test1", "test1", {"primary": 3}) create_table(cid, dbid, columns=[{ "name": "primary", "type": "date", @@ -400,3 +420,4 @@ if __name__ == '__main__': auth_user("test3") update_user(uid) update_theme(uid) + print("FINISHED") diff --git a/.jupyter/requirements.txt b/.jupyter/requirements.txt index 1ea469ff4a..4dadefc848 100644 --- a/.jupyter/requirements.txt +++ b/.jupyter/requirements.txt @@ -1,3 +1,4 @@ requests>=2.28.0 pandas>=1.4.3 -postgres>=4.0 \ No newline at end of file +postgres>=4.0 +pika>=1.3.0 \ No newline at end of file diff --git a/.jupyter/resources/ugz_ogd_air_h1_2021.csv b/.jupyter/resources/ugz_ogd_air_h1_2021.csv index ec8080c0a8..18fb5aef99 100644 --- a/.jupyter/resources/ugz_ogd_air_h1_2021.csv +++ b/.jupyter/resources/ugz_ogd_air_h1_2021.csv @@ -1,4 +1,3 @@ -Date,Location,Parameter,Interval,Unit,Value,Status 2021-01-01,Stampfenbachstrasse,CO,h1,mg/m3,0.44,tentative 2021-01-01,Stampfenbachstrasse,SO2,h1,µg/m3,4.88,tentative 2021-01-01,Stampfenbachstrasse,NOx,h1,ppb,29.46,tentative diff --git a/.jupyter/setup.py b/.jupyter/setup.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.jupyter/tuple_publish.ipynb b/.jupyter/tuple_publish.ipynb index 1c34c2d3f8..bc9935849e 100644 --- a/.jupyter/tuple_publish.ipynb +++ b/.jupyter/tuple_publish.ipynb @@ -55,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 3, "metadata": { "pycharm": { "name": "#%%\n" @@ -98,10 +98,10 @@ " 'authorities': [{'authority': 'ROLE_RESEARCHER'}],\n", " 'containers': None,\n", " 'databases': None,\n", - " 'email': 'martinweiseat@gmail.com',\n", + " 'email': 'martin.weise@tuwien.ac.at',\n", " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'identifiers': None,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", @@ -122,7 +122,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" @@ -133,21 +133,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'created': datetime.datetime(2022, 8, 17, 10, 59, 9, 964000, tzinfo=tzutc()),\n", + "{'created': datetime.datetime(2022, 8, 24, 6, 44, 27, 20000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", " 'titles_after': None,\n", " 'titles_before': None,\n", " 'username': 'test'},\n", - " 'hash': '5188e3aa1d16411f98b0cde473c39e79f54c205da3d0f5cae601e797efd053cb',\n", - " 'id': 2,\n", - " 'internal_name': 'fda-userdb-airquality-9c04db38-1e1b-11ed-b193-8c8caada74c3',\n", + " 'hash': '56eb904f34a73adb34a5dc93ce4895de05d2d7891ccff38b7a7ed45bc654aa64',\n", + " 'id': 8,\n", + " 'internal_name': 'fda-userdb-airquality-328b5a3a-2378-11ed-b35c-8c8caada74c3',\n", " 'is_public': None,\n", - " 'name': 'Airquality 9c04db38-1e1b-11ed-b193-8c8caada74c3'}\n" + " 'name': 'Airquality 328b5a3a-2378-11ed-b35c-8c8caada74c3'}\n" ] } ], @@ -163,7 +164,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" @@ -174,21 +175,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'created': datetime.datetime(2022, 8, 17, 10, 59, 9, 964000, tzinfo=tzutc()),\n", + "{'created': datetime.datetime(2022, 8, 24, 6, 44, 27, 20000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", " 'titles_after': None,\n", " 'titles_before': None,\n", " 'username': 'test'},\n", - " 'hash': '5188e3aa1d16411f98b0cde473c39e79f54c205da3d0f5cae601e797efd053cb',\n", - " 'id': 2,\n", - " 'internal_name': 'fda-userdb-airquality-9c04db38-1e1b-11ed-b193-8c8caada74c3',\n", + " 'hash': '56eb904f34a73adb34a5dc93ce4895de05d2d7891ccff38b7a7ed45bc654aa64',\n", + " 'id': 8,\n", + " 'internal_name': 'fda-userdb-airquality-328b5a3a-2378-11ed-b35c-8c8caada74c3',\n", " 'is_public': None,\n", - " 'name': 'Airquality 9c04db38-1e1b-11ed-b193-8c8caada74c3'}\n" + " 'name': 'Airquality 328b5a3a-2378-11ed-b35c-8c8caada74c3'}\n" ] } ], @@ -202,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" @@ -213,25 +215,27 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'container': {'created': datetime.datetime(2022, 8, 17, 10, 59, 9, 964000, tzinfo=tzutc()),\n", + "{'container': {'created': datetime.datetime(2022, 8, 24, 6, 44, 27, 20000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", " 'titles_after': None,\n", " 'titles_before': None,\n", " 'username': 'test'},\n", - " 'hash': '5188e3aa1d16411f98b0cde473c39e79f54c205da3d0f5cae601e797efd053cb',\n", - " 'id': 2,\n", - " 'internal_name': 'fda-userdb-airquality-9c04db38-1e1b-11ed-b193-8c8caada74c3',\n", + " 'hash': '56eb904f34a73adb34a5dc93ce4895de05d2d7891ccff38b7a7ed45bc654aa64',\n", + " 'id': 8,\n", + " 'internal_name': 'fda-userdb-airquality-328b5a3a-2378-11ed-b35c-8c8caada74c3',\n", " 'is_public': None,\n", - " 'name': 'Airquality 9c04db38-1e1b-11ed-b193-8c8caada74c3'},\n", - " 'created': datetime.datetime(2022, 8, 17, 10, 59, 15, 497000, tzinfo=tzutc()),\n", + " 'name': 'Airquality 328b5a3a-2378-11ed-b35c-8c8caada74c3'},\n", + " 'created': datetime.datetime(2022, 8, 24, 6, 44, 32, 558000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", @@ -240,9 +244,9 @@ " 'username': 'test'},\n", " 'description': 'Airquality',\n", " 'engine': 'mariadb:10.5',\n", - " 'id': 2,\n", + " 'id': 8,\n", " 'is_public': False,\n", - " 'name': 'Airquality a24d3738-1e1b-11ed-b193-8c8caada74c3'}\n" + " 'name': 'Airquality 35dc8a38-2378-11ed-b35c-8c8caada74c3'}\n" ] } ], @@ -258,28 +262,29 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 7, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'contact': None,\n", - " 'container': {'created': datetime.datetime(2022, 8, 17, 10, 59, 9, 964000, tzinfo=tzutc()),\n", + " 'container': {'created': datetime.datetime(2022, 8, 24, 6, 44, 27, 20000, tzinfo=tzutc()),\n", " 'databases': None,\n", - " 'hash': '5188e3aa1d16411f98b0cde473c39e79f54c205da3d0f5cae601e797efd053cb',\n", - " 'id': 2,\n", + " 'hash': '56eb904f34a73adb34a5dc93ce4895de05d2d7891ccff38b7a7ed45bc654aa64',\n", + " 'id': 8,\n", " 'image': {'id': 1, 'repository': 'mariadb', 'tag': '10.5'},\n", - " 'internal_name': 'fda-userdb-airquality-9c04db38-1e1b-11ed-b193-8c8caada74c3',\n", + " 'internal_name': 'fda-userdb-airquality-328b5a3a-2378-11ed-b35c-8c8caada74c3',\n", " 'ip_address': None,\n", " 'is_public': None,\n", - " 'name': 'Airquality 9c04db38-1e1b-11ed-b193-8c8caada74c3',\n", - " 'port': 11584,\n", + " 'name': 'Airquality 328b5a3a-2378-11ed-b35c-8c8caada74c3',\n", + " 'port': 12709,\n", " 'state': None},\n", - " 'created': datetime.datetime(2022, 8, 17, 10, 59, 15, 497000, tzinfo=tzutc()),\n", + " 'created': datetime.datetime(2022, 8, 24, 6, 44, 32, 558000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", @@ -288,57 +293,63 @@ " 'username': 'test'},\n", " 'deleted': None,\n", " 'description': 'Airquality',\n", - " 'exchange': 'airquality_a24d3738-1e1b-11ed-b193-8c8caada74c3',\n", - " 'id': 2,\n", + " 'exchange': 'airquality_35dc8a38-2378-11ed-b35c-8c8caada74c3',\n", + " 'id': 8,\n", " 'image': {'compiled': None,\n", - " 'date_formats': [{'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 614000, tzinfo=tzutc()),\n", + " 'date_formats': [{'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", " 'database_format': '%Y-%c-%d',\n", " 'example': '2022-01-30',\n", " 'has_time': False,\n", " 'id': 1,\n", " 'unix_format': 'yyyy-MM-dd'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 622000, tzinfo=tzutc()),\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", " 'database_format': '%d.%c.%Y',\n", " 'example': '30.01.2022',\n", " 'has_time': False,\n", " 'id': 2,\n", " 'unix_format': 'yyyy-MM-dd'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 625000, tzinfo=tzutc()),\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", " 'database_format': '%d.%c.%y',\n", " 'example': '30.01.22',\n", " 'has_time': False,\n", " 'id': 3,\n", " 'unix_format': 'yyyy-MM-dd'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 628000, tzinfo=tzutc()),\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", " 'database_format': '%c/%d/%Y',\n", " 'example': '01/30/2022',\n", " 'has_time': False,\n", " 'id': 4,\n", " 'unix_format': 'yyyy-MM-dd'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 631000, tzinfo=tzutc()),\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", " 'database_format': '%c/%d/%y',\n", " 'example': '01/30/22',\n", " 'has_time': False,\n", " 'id': 5,\n", " 'unix_format': 'yyyy-MM-dd'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 634000, tzinfo=tzutc()),\n", - " 'database_format': '%Y-%c-%d %H:%i:%S.%f',\n", - " 'example': '2022-01-30 13:44:25.0',\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", + " 'database_format': \"%Y-%c-%d'T'%H:%i:%S.%f\",\n", + " 'example': '2022-01-30T13:44:25.499',\n", " 'has_time': True,\n", " 'id': 6,\n", - " 'unix_format': 'yyyy-MM-dd HH:mm:ss.SSSSSS'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 636000, tzinfo=tzutc()),\n", - " 'database_format': '%Y-%c-%d %H:%i:%S',\n", - " 'example': '2022-01-30 13:44:25',\n", + " 'unix_format': \"yyyy-MM-dd'T'HH:mm:ss.SSSSSS\"},\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", + " 'database_format': '%Y-%c-%d %H:%i:%S.%f',\n", + " 'example': '2022-01-30 13:44:25.499',\n", " 'has_time': True,\n", " 'id': 7,\n", - " 'unix_format': 'yyyy-MM-dd HH:mm:ss'},\n", - " {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 640000, tzinfo=tzutc()),\n", - " 'database_format': '%d.%c.%Y %H:%i:%S',\n", - " 'example': '30.01.2022 13:44:25',\n", + " 'unix_format': 'yyyy-MM-dd HH:mm:ss.SSSSSS'},\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", + " 'database_format': \"%Y-%c-%d'T'%H:%i:%S\",\n", + " 'example': '2022-01-30T13:44:25',\n", " 'has_time': True,\n", " 'id': 8,\n", - " 'unix_format': 'dd.MM.yyyy HH:mm:ss'}],\n", + " 'unix_format': \"yyyy-MM-dd'T'HH:mm:ss\"},\n", + " {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", + " 'database_format': '%Y-%c-%d %H:%i:%S',\n", + " 'example': '2022-01-30 13:44:25',\n", + " 'has_time': True,\n", + " 'id': 9,\n", + " 'unix_format': 'yyyy-MM-dd HH:mm:ss'}],\n", " 'default_port': 3306,\n", " 'dialect': 'org.hibernate.dialect.MariaDBDialect',\n", " 'driver_class': 'org.mariadb.jdbc.Driver',\n", @@ -364,11 +375,11 @@ " 'repository': 'mariadb',\n", " 'size': None,\n", " 'tag': '10.5'},\n", - " 'internal_name': 'airquality_a24d3738-1e1b-11ed-b193-8c8caada74c3',\n", + " 'internal_name': 'airquality_35dc8a38-2378-11ed-b35c-8c8caada74c3',\n", " 'is_public': False,\n", " 'language': None,\n", " 'license': None,\n", - " 'name': 'Airquality a24d3738-1e1b-11ed-b193-8c8caada74c3',\n", + " 'name': 'Airquality 35dc8a38-2378-11ed-b35c-8c8caada74c3',\n", " 'publication_year': 2022,\n", " 'publisher': None,\n", " 'subjects': [],\n", @@ -390,7 +401,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 8, "metadata": { "pycharm": { "name": "#%%\n" @@ -401,25 +412,27 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'container': {'created': datetime.datetime(2022, 8, 17, 10, 59, 9, 964000, tzinfo=tzutc()),\n", + "{'container': {'created': datetime.datetime(2022, 8, 24, 6, 44, 27, 20000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", " 'titles_after': None,\n", " 'titles_before': None,\n", " 'username': 'test'},\n", - " 'hash': '5188e3aa1d16411f98b0cde473c39e79f54c205da3d0f5cae601e797efd053cb',\n", - " 'id': 2,\n", - " 'internal_name': 'fda-userdb-airquality-9c04db38-1e1b-11ed-b193-8c8caada74c3',\n", + " 'hash': '56eb904f34a73adb34a5dc93ce4895de05d2d7891ccff38b7a7ed45bc654aa64',\n", + " 'id': 8,\n", + " 'internal_name': 'fda-userdb-airquality-328b5a3a-2378-11ed-b35c-8c8caada74c3',\n", " 'is_public': None,\n", - " 'name': 'Airquality 9c04db38-1e1b-11ed-b193-8c8caada74c3'},\n", - " 'created': datetime.datetime(2022, 8, 17, 10, 59, 15, 497000, tzinfo=tzutc()),\n", + " 'name': 'Airquality 328b5a3a-2378-11ed-b35c-8c8caada74c3'},\n", + " 'created': datetime.datetime(2022, 8, 24, 6, 44, 32, 558000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", @@ -437,9 +450,9 @@ " 'values of the completed day for the current year are updated '\n", " 'every 30 minutes after midnight (UTC+1).',\n", " 'engine': 'mariadb:10.5',\n", - " 'id': 2,\n", + " 'id': 8,\n", " 'is_public': False,\n", - " 'name': 'Airquality a24d3738-1e1b-11ed-b193-8c8caada74c3'}\n" + " 'name': 'Airquality 35dc8a38-2378-11ed-b35c-8c8caada74c3'}\n" ] } ], @@ -460,7 +473,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 9, "metadata": { "pycharm": { "name": "#%%\n" @@ -472,17 +485,18 @@ "output_type": "stream", "text": [ "{'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", " 'titles_after': None,\n", " 'titles_before': None,\n", " 'username': 'test'},\n", - " 'id': 2,\n", - " 'internal_name': 'airquality_a28ac288-1e1b-11ed-b193-8c8caada74c3',\n", - " 'name': 'Airquality a28ac288-1e1b-11ed-b193-8c8caada74c3'}\n" + " 'id': 11,\n", + " 'internal_name': 'airquality_360185cc-2378-11ed-b35c-8c8caada74c3',\n", + " 'name': 'Airquality 360185cc-2378-11ed-b35c-8c8caada74c3'}\n" ] } ], @@ -541,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 10, "metadata": { "pycharm": { "name": "#%%\n" @@ -561,7 +575,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 9,\n", + " 'id': 53,\n", " 'internal_name': 'id',\n", " 'is_null_allowed': False,\n", " 'is_primary_key': True,\n", @@ -572,7 +586,7 @@ " 'check_expression': None,\n", " 'column_concept': None,\n", " 'column_type': 'date',\n", - " 'date_format': {'created_at': datetime.datetime(2022, 8, 17, 10, 21, 43, 614000, tzinfo=tzutc()),\n", + " 'date_format': {'created_at': datetime.datetime(2022, 8, 24, 6, 15, 21, 255000, tzinfo=tzutc()),\n", " 'database_format': '%Y-%c-%d',\n", " 'example': '2022-01-30',\n", " 'has_time': False,\n", @@ -582,7 +596,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 10,\n", + " 'id': 54,\n", " 'internal_name': 'date',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", @@ -598,7 +612,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 11,\n", + " 'id': 55,\n", " 'internal_name': 'location',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", @@ -614,7 +628,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 12,\n", + " 'id': 56,\n", " 'internal_name': 'parameter',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", @@ -630,7 +644,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 13,\n", + " 'id': 57,\n", " 'internal_name': 'interval',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", @@ -646,7 +660,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 14,\n", + " 'id': 58,\n", " 'internal_name': 'unit',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", @@ -662,7 +676,7 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 15,\n", + " 'id': 59,\n", " 'internal_name': 'value',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", @@ -678,17 +692,18 @@ " 'decimal_digits_before': None,\n", " 'enum_values': [],\n", " 'foreign_key': None,\n", - " 'id': 16,\n", + " 'id': 60,\n", " 'internal_name': 'status',\n", " 'is_null_allowed': True,\n", " 'is_primary_key': False,\n", " 'name': 'Status',\n", " 'references': None,\n", " 'unique': False}],\n", - " 'created': datetime.datetime(2022, 8, 17, 10, 59, 15, 923000, tzinfo=tzutc()),\n", + " 'created': datetime.datetime(2022, 8, 24, 6, 44, 32, 816000, tzinfo=tzutc()),\n", " 'creator': {'affiliation': None,\n", + " 'email_verified': False,\n", " 'firstname': 'Martin',\n", - " 'id': 3,\n", + " 'id': 5,\n", " 'lastname': 'Weise',\n", " 'orcid': None,\n", " 'theme_dark': False,\n", @@ -696,10 +711,10 @@ " 'titles_before': None,\n", " 'username': 'test'},\n", " 'description': 'Airquality in Zürich, Switzerland',\n", - " 'id': 2,\n", - " 'internal_name': 'airquality_a28ac288-1e1b-11ed-b193-8c8caada74c3',\n", - " 'name': 'Airquality a28ac288-1e1b-11ed-b193-8c8caada74c3',\n", - " 'topic': 'airquality_a28ac288-1e1b-11ed-b193-8c8caada74c3'}\n" + " 'id': 11,\n", + " 'internal_name': 'airquality_360185cc-2378-11ed-b35c-8c8caada74c3',\n", + " 'name': 'Airquality 360185cc-2378-11ed-b35c-8c8caada74c3',\n", + " 'topic': 'airquality_360185cc-2378-11ed-b35c-8c8caada74c3'}\n" ] } ], @@ -711,7 +726,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 11, "metadata": { "pycharm": { "name": "#%%\n" @@ -722,66 +737,65 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'date': 'Date', 'location': 'Location', 'parameter': 'Parameter', 'interval': 'Interval', 'unit': 'Unit', 'value': 'Value', 'status': 'Status'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'CO', 'interval': 'h1', 'unit': 'mg/m3', 'value': '0.44', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'SO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '4.88', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '29.46', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '9.85', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '41.24', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '8.51', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'PM10', 'interval': 'h1', 'unit': 'µg/m3', 'value': '88.34', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'PM2.5', 'interval': 'h1', 'unit': 'µg/m3', 'value': '75.72', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '41.66', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '21.64', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '46.49', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '6.32', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'PM10', 'interval': 'h1', 'unit': 'µg/m3', 'value': '98.65', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'PM2.5', 'interval': 'h1', 'unit': 'µg/m3', 'value': '84.78', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '9.11', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '0.77', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '16.24', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '33.11', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '36.55', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '17.2', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '43.51', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '4.38', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'PM10', 'interval': 'h1', 'unit': 'µg/m3', 'value': '74.05', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'PM2.5', 'interval': 'h1', 'unit': 'µg/m3', 'value': '74.11', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'CO', 'interval': 'h1', 'unit': 'mg/m3', 'value': '0.4', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'SO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '5.02', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '35.47', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '16.28', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '42.88', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '5.34', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'PM10', 'interval': 'h1', 'unit': 'µg/m3', 'value': '140.15', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Stampfenbachstrasse', 'parameter': 'PM2.5', 'interval': 'h1', 'unit': 'µg/m3', 'value': '118.87', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '39.43', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '20.8', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '43.51', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '6.93', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'PM10', 'interval': 'h1', 'unit': 'µg/m3', 'value': '118.95', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Schimmelstrasse', 'parameter': 'PM2.5', 'interval': 'h1', 'unit': 'µg/m3', 'value': '102.54', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '5.31', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '0.52', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '9.36', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Heubeeribüel', 'parameter': 'O3', 'interval': 'h1', 'unit': 'µg/m3', 'value': '51.36', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'NOx', 'interval': 'h1', 'unit': 'ppb', 'value': '45.83', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'NO', 'interval': 'h1', 'unit': 'µg/m3', 'value': '28.15', 'status': 'tentative'}\n", - "{'date': '2021-01-01', 'location': 'Rosengartenstrasse', 'parameter': 'NO2', 'interval': 'h1', 'unit': 'µg/m3', 'value': '44.48', 'status': 'tentative'}\n" + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"CO\", \"interval\": \"h1\", \"unit\": \"mg/m3\", \"value\": 0.44, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"SO2\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 4.88, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"NOx\", \"interval\": \"h1\", \"unit\": \"ppb\", \"value\": 29.46, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"NO\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 9.85, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"NO2\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 41.24, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"O3\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 8.51, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"PM10\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 88.34, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"PM2.5\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 75.72, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Schimmelstrasse\", \"parameter\": \"NOx\", \"interval\": \"h1\", \"unit\": \"ppb\", \"value\": 41.66, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Schimmelstrasse\", \"parameter\": \"NO\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 21.64, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Schimmelstrasse\", \"parameter\": \"NO2\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 46.49, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Schimmelstrasse\", \"parameter\": \"O3\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 6.32, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Schimmelstrasse\", \"parameter\": \"PM10\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 98.65, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Schimmelstrasse\", \"parameter\": \"PM2.5\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 84.78, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Heubeerib\\u00fcel\", \"parameter\": \"NOx\", \"interval\": \"h1\", \"unit\": \"ppb\", \"value\": 9.11, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Heubeerib\\u00fcel\", \"parameter\": \"NO\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 0.77, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Heubeerib\\u00fcel\", \"parameter\": \"NO2\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 16.24, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Heubeerib\\u00fcel\", \"parameter\": \"O3\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 33.11, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Rosengartenstrasse\", \"parameter\": \"NOx\", \"interval\": \"h1\", \"unit\": \"ppb\", \"value\": 36.55, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Rosengartenstrasse\", \"parameter\": \"NO\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 17.2, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Rosengartenstrasse\", \"parameter\": \"NO2\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 43.51, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Rosengartenstrasse\", \"parameter\": \"O3\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 4.38, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Rosengartenstrasse\", \"parameter\": \"PM10\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 74.05, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Rosengartenstrasse\", \"parameter\": \"PM2.5\", \"interval\": \"h1\", \"unit\": \"\\u00b5g/m3\", \"value\": 74.11, \"status\": \"tentative\"}\n", + "... sent tuple\n", + "... sending tuple {\"date\": \"2021-01-01\", \"location\": \"Stampfenbachstrasse\", \"parameter\": \"CO\", \"interval\": \"h1\", \"unit\": \"mg/m3\", \"value\": 0.4, \"status\": \"tentative\"}\n", + "... sent tuple\n" ] }, { - "ename": "AMQPConnectionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mAMQPConnectionError\u001B[0m Traceback (most recent call last)", - "Input \u001B[0;32mIn [19]\u001B[0m, in \u001B[0;36m<cell line: 3>\u001B[0;34m()\u001B[0m\n\u001B[1;32m 5\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m row \u001B[38;5;129;01min\u001B[39;00m csv_reader:\n\u001B[1;32m 6\u001B[0m payload \u001B[38;5;241m=\u001B[39m {\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mdate\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m0\u001B[39m], \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mlocation\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m1\u001B[39m], \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mparameter\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m2\u001B[39m], \u001B[38;5;124m'\u001B[39m\u001B[38;5;124minterval\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m3\u001B[39m], \u001B[38;5;124m'\u001B[39m\u001B[38;5;124munit\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m4\u001B[39m],\n\u001B[1;32m 7\u001B[0m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mvalue\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m5\u001B[39m], \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mstatus\u001B[39m\u001B[38;5;124m'\u001B[39m: row[\u001B[38;5;241m6\u001B[39m]}\n\u001B[0;32m----> 8\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[43mbroker\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mpayload\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 9\u001B[0m time\u001B[38;5;241m.\u001B[39msleep(\u001B[38;5;241m1\u001B[39m)\n\u001B[1;32m 10\u001B[0m \u001B[38;5;28mprint\u001B[39m(payload)\n", - "File \u001B[0;32m~/Projects/fda-services/.jupyter/api_broker/BrokerServiceClient.py:17\u001B[0m, in \u001B[0;36mBrokerServiceClient.send\u001B[0;34m(self, data)\u001B[0m\n\u001B[1;32m 15\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21msend\u001B[39m(\u001B[38;5;28mself\u001B[39m, data):\n\u001B[1;32m 16\u001B[0m creds \u001B[38;5;241m=\u001B[39m pika\u001B[38;5;241m.\u001B[39mcredentials\u001B[38;5;241m.\u001B[39mPlainCredentials(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39musername, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mpassword)\n\u001B[0;32m---> 17\u001B[0m connection \u001B[38;5;241m=\u001B[39m \u001B[43mpika\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mBlockingConnection\u001B[49m\u001B[43m(\u001B[49m\u001B[43mpika\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mConnectionParameters\u001B[49m\u001B[43m(\u001B[49m\u001B[43mhost\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mhost\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mcredentials\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mcreds\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 18\u001B[0m channel \u001B[38;5;241m=\u001B[39m connection\u001B[38;5;241m.\u001B[39mchannel()\n\u001B[1;32m 19\u001B[0m dump \u001B[38;5;241m=\u001B[39m json\u001B[38;5;241m.\u001B[39mdumps(data)\n", - "File \u001B[0;32m~/Projects/fda-services/.jupyter/venv/lib/python3.9/site-packages/pika/adapters/blocking_connection.py:360\u001B[0m, in \u001B[0;36mBlockingConnection.__init__\u001B[0;34m(self, parameters, _impl_class)\u001B[0m\n\u001B[1;32m 358\u001B[0m \u001B[38;5;66;03m# Perform connection workflow\u001B[39;00m\n\u001B[1;32m 359\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_impl \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;66;03m# so that attribute is created in case below raises\u001B[39;00m\n\u001B[0;32m--> 360\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_impl \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_create_connection\u001B[49m\u001B[43m(\u001B[49m\u001B[43mparameters\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43m_impl_class\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 361\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_impl\u001B[38;5;241m.\u001B[39madd_on_close_callback(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_closed_result\u001B[38;5;241m.\u001B[39mset_value_once)\n", - "File \u001B[0;32m~/Projects/fda-services/.jupyter/venv/lib/python3.9/site-packages/pika/adapters/blocking_connection.py:451\u001B[0m, in \u001B[0;36mBlockingConnection._create_connection\u001B[0;34m(self, configs, impl_class)\u001B[0m\n\u001B[1;32m 449\u001B[0m error \u001B[38;5;241m=\u001B[39m on_cw_done_result\u001B[38;5;241m.\u001B[39mvalue\u001B[38;5;241m.\u001B[39mresult\n\u001B[1;32m 450\u001B[0m LOGGER\u001B[38;5;241m.\u001B[39merror(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mConnection workflow failed: \u001B[39m\u001B[38;5;132;01m%r\u001B[39;00m\u001B[38;5;124m'\u001B[39m, error)\n\u001B[0;32m--> 451\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_reap_last_connection_workflow_error(error)\n\u001B[1;32m 452\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m 453\u001B[0m LOGGER\u001B[38;5;241m.\u001B[39minfo(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mConnection workflow succeeded: \u001B[39m\u001B[38;5;132;01m%r\u001B[39;00m\u001B[38;5;124m'\u001B[39m,\n\u001B[1;32m 454\u001B[0m on_cw_done_result\u001B[38;5;241m.\u001B[39mvalue\u001B[38;5;241m.\u001B[39mresult)\n", - "\u001B[0;31mAMQPConnectionError\u001B[0m: " + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "KeyboardInterrupt\n", + "\n" ] } ], @@ -792,10 +806,9 @@ " csv_reader = csv.reader(f, delimiter=',', quotechar='\"')\n", " for row in csv_reader:\n", " payload = {'date': row[0], 'location': row[1], 'parameter': row[2], 'interval': row[3], 'unit': row[4],\n", - " 'value': row[5], 'status': row[6]}\n", + " 'value': float(row[5]), 'status': row[6]}\n", " response = broker.send(payload)\n", - " time.sleep(1)\n", - " print(payload)" + " time.sleep(1)" ] }, { @@ -808,6 +821,18 @@ }, "outputs": [], "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } } ], "metadata": { diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4c624cc17e..ed89268c49 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -117,6 +117,7 @@ services: METADATA_PASSWORD: ${METADATA_PASSWORD} BROKER_USERNAME: ${BROKER_USERNAME} BROKER_PASSWORD: ${BROKER_PASSWORD} + SHARED_FILESYSTEM: ${SHARED_FILESYSTEM} volumes: - /var/run/docker.sock:/var/run/docker.sock depends_on: @@ -176,7 +177,7 @@ services: BROKER_USERNAME: ${BROKER_USERNAME} BROKER_PASSWORD: ${BROKER_PASSWORD} volumes: - - /tmp:/tmp + - ${SHARED_FILESYSTEM}:/tmp depends_on: table-service: condition: service_healthy @@ -266,7 +267,7 @@ services: ports: - "5010:5010" volumes: - - /tmp:/tmp + - ${SHARED_FILESYSTEM}:/tmp - /var/run/docker.sock:/var/run/docker.sock depends_on: discovery-service: @@ -343,5 +344,6 @@ services: environment: BROKER_USERNAME: ${BROKER_USERNAME} BROKER_PASSWORD: ${BROKER_PASSWORD} + SHARED_FILESYSTEM: ${SHARED_FILESYSTEM} logging: driver: json-file diff --git a/docker-compose.yml b/docker-compose.yml index 8374730a1b..5489f2d4f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -79,8 +79,6 @@ services: userdb: public: environment: - GATEWAY_ENDPOINT: http://fda-gateway-service:9095 - SEARCH_ENDPOINT: fda-search-service:9200 SPRING_PROFILES_ACTIVE: docker ports: - "9092:9092" @@ -105,7 +103,6 @@ services: networks: public: environment: - GATEWAY_ENDPOINT: http://fda-gateway-service:9095 SPRING_PROFILES_ACTIVE: docker ports: - "9091:9091" @@ -126,13 +123,6 @@ services: networks: public: environment: - GATEWAY_ENDPOINT: http://fda-gateway-service:9095 - WEBSITE: http://localhost:3000 - MAIL_FROM: Database Repository <noreply@dbrepo.ossdip.at> - MAIL_REPLY_TO: Martin Weise <martin.weise@tuwien.ac.at> - JWT_ISSUER: fda-dbrepo - JWT_SECRET: fda-secret - JWT_EXPIRATION: 86400000 SPRING_PROFILES_ACTIVE: docker ports: - "9097:9097" @@ -157,7 +147,6 @@ services: userdb: environment: SPRING_PROFILES_ACTIVE: docker - GATEWAY_ENDPOINT: http://fda-gateway-service:9095 ports: - "9093:9093" volumes: @@ -208,7 +197,6 @@ services: networks: public: environment: - GATEWAY_ENDPOINT: http://fda-gateway-service:9095 SPRING_PROFILES_ACTIVE: docker ports: - "9096:9096" @@ -231,7 +219,6 @@ services: userdb: command: sh -c "/wait && flask run" # docker-compose should not test the implementation environment: - EUREKA_SERVER: http://fda-discovery-service:9090/eureka/ hostname: analyse-service ports: - "5000:5000" @@ -273,7 +260,6 @@ services: image: fda-broker-service environment: SPRING_PROFILES_ACTIVE: docker - GATEWAY_ENDPOINT: http://fda-gateway-service:9095 networks: public: ports: diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java index 235a120647..6551bd41ef 100644 --- a/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java +++ b/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java @@ -32,7 +32,6 @@ public class AuthTokenFilter extends OncePerRequestFilter { final String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { final String username = jwtUtils.getUserNameFromJwtToken(jwt); - final UserDetails userDetails = userDetailsService.loadUserByUsername(username); final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/UserDetailsServiceImpl.java b/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/UserDetailsServiceImpl.java index d8f1ecdb09..1fd36453a3 100644 --- a/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/UserDetailsServiceImpl.java +++ b/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/UserDetailsServiceImpl.java @@ -35,7 +35,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { try { user = userService.findByUsername(username); } catch (UserNotFoundException e) { - log.error("Failed to find user"); + log.error("Failed to find user with username {}", username); throw new UsernameNotFoundException("Failed to find user", e); } log.trace("loaded user {}", user); diff --git a/fda-container-service/Dockerfile b/fda-container-service/Dockerfile index 91bb62cdd4..f0433e1740 100644 --- a/fda-container-service/Dockerfile +++ b/fda-container-service/Dockerfile @@ -26,6 +26,7 @@ ENV METADATA_USERNAME=postgres ENV METADATA_PASSWORD=postgres ENV BROKER_USERNAME=fda ENV BROKER_PASSWORD=fda +ENV SHARED_FILESYSTEM=/tmp COPY ./service_ready /usr/bin RUN chmod +x /usr/bin/service_ready diff --git a/fda-container-service/pom.xml b/fda-container-service/pom.xml index f39df32995..17dc8db0fb 100644 --- a/fda-container-service/pom.xml +++ b/fda-container-service/pom.xml @@ -27,7 +27,7 @@ <mapstruct.version>1.4.2.Final</mapstruct.version> <docker.version>3.2.7</docker.version> <testcontainers.version>1.15.2</testcontainers.version> - <swagger.version>2.1.7</swagger.version> + <swagger.version>2.2.2</swagger.version> <jacoco.version>0.8.7</jacoco.version> <sqlserver.version>9.2.1.jre11</sqlserver.version> </properties> @@ -59,6 +59,12 @@ <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> + <!-- Swagger --> + <dependency> + <groupId>io.swagger.core.v3</groupId> + <artifactId>swagger-core</artifactId> + <version>2.2.2</version> + </dependency> <!-- Data Source --> <dependency> <groupId>org.springframework.boot</groupId> diff --git a/fda-container-service/rest-service/src/main/resources/application-docker.yml b/fda-container-service/rest-service/src/main/resources/application-docker.yml index 8cadde6a05..510c4099b1 100644 --- a/fda-container-service/rest-service/src/main/resources/application-docker.yml +++ b/fda-container-service/rest-service/src/main/resources/application-docker.yml @@ -34,4 +34,4 @@ eureka: fda: mount.path: /tmp ready.path: /ready - gateway.endpoint: "${GATEWAY_ENDPOINT}" \ No newline at end of file + gateway.endpoint: http://gateway-service:9095 \ No newline at end of file diff --git a/fda-container-service/rest-service/src/main/resources/application-local.yml b/fda-container-service/rest-service/src/main/resources/application-local.yml new file mode 100644 index 0000000000..38b31bcd56 --- /dev/null +++ b/fda-container-service/rest-service/src/main/resources/application-local.yml @@ -0,0 +1,37 @@ +app.version: '@project.version@' +spring: + main.banner-mode: off + datasource: + url: jdbc:postgresql://localhost:5432/fda + driver-class-name: org.postgresql.Driver + username: postgres + password: postgres + jpa: + show-sql: false + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: validate + open-in-view: false + properties: + hibernate: + jdbc: + time_zone: UTC + application: + name: container-service + cloud: + loadbalancer.ribbon.enabled: false +springdoc.swagger-ui.enabled: true +server.port: 9091 +logging: + pattern.console: "%d %highlight(%-5level) %msg%n" + level: + root: warn + at.tuwien.: debug + org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug +eureka: + instance.hostname: container-service + client.serviceUrl.defaultZone: http://localhost:9090/eureka/ +fda: + mount.path: /tmp + ready.path: ./ready + gateway.endpoint: http://localhost:9095 \ No newline at end of file diff --git a/fda-container-service/rest-service/src/main/resources/application.yml b/fda-container-service/rest-service/src/main/resources/application.yml index d38936b08a..afb2e5e103 100644 --- a/fda-container-service/rest-service/src/main/resources/application.yml +++ b/fda-container-service/rest-service/src/main/resources/application.yml @@ -32,6 +32,6 @@ eureka: instance.hostname: container-service client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/ fda: - mount.path: /tmp + mount.path: "${SHARED_FILESYSTEM}" ready.path: /ready gateway.endpoint: http://gateway-service:9095 \ No newline at end of file diff --git a/fda-container-service/services/src/main/java/at/tuwien/config/DockerConfig.java b/fda-container-service/services/src/main/java/at/tuwien/config/DockerConfig.java index e9ed1d6dff..4915aba793 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/config/DockerConfig.java +++ b/fda-container-service/services/src/main/java/at/tuwien/config/DockerConfig.java @@ -10,12 +10,18 @@ import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; import com.github.dockerjava.transport.DockerHttpClient; import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.security.SecurityScheme; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +@Getter @Configuration public class DockerConfig { + @Value("${fda.mount.path}") + private String mountPath; + @Bean public HostConfig hostConfig() { return HostConfig.newHostConfig() diff --git a/fda-container-service/services/src/main/java/at/tuwien/config/ReadyConfig.java b/fda-container-service/services/src/main/java/at/tuwien/config/ReadyConfig.java index 6463b1eb05..2250fa5088 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/config/ReadyConfig.java +++ b/fda-container-service/services/src/main/java/at/tuwien/config/ReadyConfig.java @@ -1,9 +1,7 @@ package at.tuwien.config; -import at.tuwien.seeder.Seeder; import com.google.common.io.Files; import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Configuration; @@ -19,16 +17,8 @@ public class ReadyConfig { @Value("${fda.ready.path}") private String readyPath; - private final Seeder seederImpl; - - @Autowired - public ReadyConfig(Seeder seederImpl) { - this.seederImpl = seederImpl; - } - @EventListener(ApplicationReadyEvent.class) public void init() throws IOException { - seederImpl.seed(); Files.touch(new File(readyPath)); } diff --git a/fda-container-service/services/src/main/java/at/tuwien/seeder/Seeder.java b/fda-container-service/services/src/main/java/at/tuwien/seeder/Seeder.java deleted file mode 100644 index 544ccc3054..0000000000 --- a/fda-container-service/services/src/main/java/at/tuwien/seeder/Seeder.java +++ /dev/null @@ -1,6 +0,0 @@ -package at.tuwien.seeder; - -public interface Seeder { - - void seed(); -} diff --git a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/AbstractSeeder.java b/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/AbstractSeeder.java deleted file mode 100644 index 290f148356..0000000000 --- a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/AbstractSeeder.java +++ /dev/null @@ -1,216 +0,0 @@ -package at.tuwien.seeder.impl; - -import at.tuwien.api.container.ContainerCreateRequestDto; -import at.tuwien.entities.container.image.ContainerImage; -import at.tuwien.entities.container.image.ContainerImageDate; -import at.tuwien.entities.container.image.ContainerImageEnvironmentItem; -import at.tuwien.entities.container.image.ContainerImageEnvironmentItemType; -import org.apache.http.auth.BasicUserPrincipal; - -import java.security.Principal; -import java.util.List; - -public abstract class AbstractSeeder { - - public final static Long USER_1_ID = 1L; - public final static String USER_1_USERNAME = "system"; - - public final static Principal PRINCIPAL_1 = new BasicUserPrincipal(USER_1_USERNAME); - - public final static Long IMAGE_1_ID = 1L; - public final static String IMAGE_1_REPOSITORY = "mariadb"; - public final static String IMAGE_1_TAG = "10.5"; - public final static String IMAGE_1_DIALECT = "org.hibernate.dialect.MariaDBDialect"; - public final static String IMAGE_1_DRIVER = "org.mariadb.jdbc.Driver"; - public final static String IMAGE_1_JDBC = "mariadb"; - public final static String IMAGE_1_LOGO = "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"; - public final static Integer IMAGE_1_PORT = 3306; - - public final static List<ContainerImageEnvironmentItem> IMAGE_1_ENVIRONMENT = List.of( - ContainerImageEnvironmentItem.builder() - .iid(IMAGE_1_ID) - .key("ROOT") - .value("root") - .type(ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME) - .build(), - ContainerImageEnvironmentItem.builder() - .iid(IMAGE_1_ID) - .key("MARIADB_ROOT_PASSWORD") - .value("mariadb") - .type(ContainerImageEnvironmentItemType.PRIVILEGED_PASSWORD) - .build(), - ContainerImageEnvironmentItem.builder() - .iid(IMAGE_1_ID) - .key("MARIADB_USER") - .value("mariadb") - .type(ContainerImageEnvironmentItemType.USERNAME) - .build(), - ContainerImageEnvironmentItem.builder() - .iid(IMAGE_1_ID) - .key("MARIADB_PASSWORD") - .value("mariadb") - .type(ContainerImageEnvironmentItemType.PASSWORD) - .build()); - - public final static Long IMAGE_DATE_1_ID = 1L; - public final static Long IMAGE_DATE_1_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_1_DATABASE_FORMAT = "%Y-%c-%d"; - public final static String IMAGE_DATE_1_UNIX_FORMAT = "yyyy-MM-dd"; - public final static String IMAGE_DATE_1_EXAMPLE = "2022-01-30"; - public final static Boolean IMAGE_DATE_1_HAS_TIME = false; - - public final static Long IMAGE_DATE_2_ID = 2L; - public final static Long IMAGE_DATE_2_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_2_DATABASE_FORMAT = "%d.%c.%Y"; - public final static String IMAGE_DATE_2_UNIX_FORMAT = "yyyy-MM-dd"; - public final static String IMAGE_DATE_2_EXAMPLE = "30.01.2022"; - public final static Boolean IMAGE_DATE_2_HAS_TIME = false; - - public final static Long IMAGE_DATE_3_ID = 3L; - public final static Long IMAGE_DATE_3_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_3_DATABASE_FORMAT = "%d.%c.%y"; - public final static String IMAGE_DATE_3_UNIX_FORMAT = "yyyy-MM-dd"; - public final static String IMAGE_DATE_3_EXAMPLE = "30.01.22"; - public final static Boolean IMAGE_DATE_3_HAS_TIME = false; - - public final static Long IMAGE_DATE_4_ID = 4L; - public final static Long IMAGE_DATE_4_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_4_DATABASE_FORMAT = "%c/%d/%Y"; - public final static String IMAGE_DATE_4_UNIX_FORMAT = "yyyy-MM-dd"; - public final static String IMAGE_DATE_4_EXAMPLE = "01/30/2022"; - public final static Boolean IMAGE_DATE_4_HAS_TIME = false; - - public final static Long IMAGE_DATE_5_ID = 5L; - public final static Long IMAGE_DATE_5_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_5_DATABASE_FORMAT = "%c/%d/%y"; - public final static String IMAGE_DATE_5_UNIX_FORMAT = "yyyy-MM-dd"; - public final static String IMAGE_DATE_5_EXAMPLE = "01/30/22"; - public final static Boolean IMAGE_DATE_5_HAS_TIME = false; - - public final static Long IMAGE_DATE_6_ID = 6L; - public final static Long IMAGE_DATE_6_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_6_DATABASE_FORMAT = "%Y-%c-%d %H:%i:%S.%f"; - public final static String IMAGE_DATE_6_UNIX_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSSSS"; - public final static String IMAGE_DATE_6_EXAMPLE = "2022-01-30 13:44:25.0"; - public final static Boolean IMAGE_DATE_6_HAS_TIME = true; - - public final static Long IMAGE_DATE_7_ID = 7L; - public final static Long IMAGE_DATE_7_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_7_DATABASE_FORMAT = "%Y-%c-%d %H:%i:%S"; - public final static String IMAGE_DATE_7_UNIX_FORMAT = "yyyy-MM-dd HH:mm:ss"; - public final static String IMAGE_DATE_7_EXAMPLE = "2022-01-30 13:44:25"; - public final static Boolean IMAGE_DATE_7_HAS_TIME = true; - - public final static Long IMAGE_DATE_8_ID = 8L; - public final static Long IMAGE_DATE_8_IMAGE_ID = IMAGE_1_ID; - public final static String IMAGE_DATE_8_DATABASE_FORMAT = "%d.%c.%Y %H:%i:%S"; - public final static String IMAGE_DATE_8_UNIX_FORMAT = "dd.MM.yyyy HH:mm:ss"; - public final static String IMAGE_DATE_8_EXAMPLE = "30.01.2022 13:44:25"; - public final static Boolean IMAGE_DATE_8_HAS_TIME = true; - - public final static ContainerImageDate IMAGE_DATE_1 = ContainerImageDate.builder() - .id(IMAGE_DATE_1_ID) - .iid(IMAGE_DATE_1_IMAGE_ID) - .databaseFormat(IMAGE_DATE_1_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_1_UNIX_FORMAT) - .example(IMAGE_DATE_1_EXAMPLE) - .hasTime(IMAGE_DATE_1_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_2 = ContainerImageDate.builder() - .id(IMAGE_DATE_2_ID) - .iid(IMAGE_DATE_2_IMAGE_ID) - .databaseFormat(IMAGE_DATE_2_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_2_UNIX_FORMAT) - .example(IMAGE_DATE_2_EXAMPLE) - .hasTime(IMAGE_DATE_2_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_3 = ContainerImageDate.builder() - .id(IMAGE_DATE_3_ID) - .iid(IMAGE_DATE_3_IMAGE_ID) - .databaseFormat(IMAGE_DATE_3_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_3_UNIX_FORMAT) - .example(IMAGE_DATE_3_EXAMPLE) - .hasTime(IMAGE_DATE_3_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_4 = ContainerImageDate.builder() - .id(IMAGE_DATE_4_ID) - .iid(IMAGE_DATE_4_IMAGE_ID) - .databaseFormat(IMAGE_DATE_4_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_4_UNIX_FORMAT) - .example(IMAGE_DATE_4_EXAMPLE) - .hasTime(IMAGE_DATE_4_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_5 = ContainerImageDate.builder() - .id(IMAGE_DATE_5_ID) - .iid(IMAGE_DATE_5_IMAGE_ID) - .databaseFormat(IMAGE_DATE_5_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_5_UNIX_FORMAT) - .example(IMAGE_DATE_5_EXAMPLE) - .hasTime(IMAGE_DATE_5_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_6 = ContainerImageDate.builder() - .id(IMAGE_DATE_6_ID) - .iid(IMAGE_DATE_6_IMAGE_ID) - .databaseFormat(IMAGE_DATE_6_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_6_UNIX_FORMAT) - .example(IMAGE_DATE_6_EXAMPLE) - .hasTime(IMAGE_DATE_6_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_7 = ContainerImageDate.builder() - .id(IMAGE_DATE_7_ID) - .iid(IMAGE_DATE_7_IMAGE_ID) - .databaseFormat(IMAGE_DATE_7_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_7_UNIX_FORMAT) - .example(IMAGE_DATE_7_EXAMPLE) - .hasTime(IMAGE_DATE_7_HAS_TIME) - .build(); - - public final static ContainerImageDate IMAGE_DATE_8 = ContainerImageDate.builder() - .id(IMAGE_DATE_8_ID) - .iid(IMAGE_DATE_8_IMAGE_ID) - .databaseFormat(IMAGE_DATE_8_DATABASE_FORMAT) - .unixFormat(IMAGE_DATE_8_UNIX_FORMAT) - .example(IMAGE_DATE_8_EXAMPLE) - .hasTime(IMAGE_DATE_8_HAS_TIME) - .build(); - - public final static ContainerImage IMAGE_1 = ContainerImage.builder() - .dialect(IMAGE_1_DIALECT) - .driverClass(IMAGE_1_DRIVER) - .jdbcMethod(IMAGE_1_JDBC) - .logo(IMAGE_1_LOGO) - .repository(IMAGE_1_REPOSITORY) - .tag(IMAGE_1_TAG) - .environment(IMAGE_1_ENVIRONMENT) - .defaultPort(IMAGE_1_PORT) - .build(); - - public final static Long CONTAINER_1_ID = 1L; - public final static String CONTAINER_1_NAME = "Traffic"; - public final static String CONTAINER_1_REPOSITORY = "mariadb"; - public final static String CONTAINER_1_TAG = "10.5"; - - public final static Long CONTAINER_2_ID = 2L; - public final static String CONTAINER_2_NAME = "Sensor"; - public final static String CONTAINER_2_REPOSITORY = "mariadb"; - public final static String CONTAINER_2_TAG = "10.5"; - - public final static ContainerCreateRequestDto CONTAINER_1_CREATE_DTO = ContainerCreateRequestDto.builder() - .name(CONTAINER_1_NAME) - .repository(CONTAINER_1_REPOSITORY) - .tag(CONTAINER_1_TAG) - .build(); - - public final static ContainerCreateRequestDto CONTAINER_2_CREATE_DTO = ContainerCreateRequestDto.builder() - .name(CONTAINER_2_NAME) - .repository(CONTAINER_2_REPOSITORY) - .tag(CONTAINER_2_TAG) - .build(); - -} diff --git a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ContainerSeederImpl.java b/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ContainerSeederImpl.java deleted file mode 100644 index 4fa4283d20..0000000000 --- a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ContainerSeederImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package at.tuwien.seeder.impl; - -import at.tuwien.entities.container.Container; -import at.tuwien.repository.jpa.ContainerRepository; -import at.tuwien.seeder.Seeder; -import at.tuwien.service.ContainerService; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class ContainerSeederImpl extends AbstractSeeder implements Seeder { - - private final ContainerRepository containerRepository; - private final ContainerService containerService; - - @Autowired - public ContainerSeederImpl(ContainerRepository containerRepository, ContainerService containerService) { - this.containerRepository = containerRepository; - this.containerService = containerService; - } - - @SneakyThrows - @Override - public void seed() { - if (containerRepository.existsById(CONTAINER_1_ID)) { - log.warn("Already seeded. Skip."); - return; - } - final Container container1 = containerService.create(CONTAINER_1_CREATE_DTO, PRINCIPAL_1); - log.info("Created container id {}", container1.getId()); - final Container container1start = containerService.start(CONTAINER_1_ID); - log.info("Started container id {}", container1start.getId()); - final Container container2 = containerService.create(CONTAINER_2_CREATE_DTO, PRINCIPAL_1); - log.info("Created container id {}", container2.getId()); - final Container container2start = containerService.start(CONTAINER_2_ID); - log.info("Started container id {}", container2start.getId()); - } - -} diff --git a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ImageSeederImpl.java b/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ImageSeederImpl.java deleted file mode 100644 index f9c878cf61..0000000000 --- a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/ImageSeederImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -package at.tuwien.seeder.impl; - -import at.tuwien.entities.container.image.ContainerImage; -import at.tuwien.entities.container.image.ContainerImageDate; -import at.tuwien.repository.jpa.ImageDateRepository; -import at.tuwien.repository.jpa.ImageRepository; -import at.tuwien.seeder.Seeder; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -public class ImageSeederImpl extends AbstractSeeder implements Seeder { - - private final ImageRepository imageRepository; - private final ImageDateRepository imageDateRepository; - - @Autowired - public ImageSeederImpl(ImageRepository imageRepository, ImageDateRepository imageDateRepository) { - this.imageRepository = imageRepository; - this.imageDateRepository = imageDateRepository; - } - - @Override - @Transactional - public void seed() { - if (imageRepository.existsById(IMAGE_1_ID)) { - log.warn("Already seeded. Skip."); - return; - } - final ContainerImage imageMariaDb = imageRepository.save(IMAGE_1); - log.info("Seeded image id {}", imageMariaDb.getId()); - final ContainerImageDate date1 = imageDateRepository.save(IMAGE_DATE_1); - log.info("Seeded image date id {}", date1.getId()); - final ContainerImageDate date2 = imageDateRepository.save(IMAGE_DATE_2); - log.info("Seeded image date id {}", date2.getId()); - final ContainerImageDate date3 = imageDateRepository.save(IMAGE_DATE_3); - log.info("Seeded image date id {}", date3.getId()); - final ContainerImageDate date4 = imageDateRepository.save(IMAGE_DATE_4); - log.info("Seeded image date id {}", date4.getId()); - final ContainerImageDate date5 = imageDateRepository.save(IMAGE_DATE_5); - log.info("Seeded image date id {}", date5.getId()); - final ContainerImageDate date6 = imageDateRepository.save(IMAGE_DATE_6); - log.info("Seeded image date id {}", date6.getId()); - final ContainerImageDate date7 = imageDateRepository.save(IMAGE_DATE_7); - log.info("Seeded image date id {}", date7.getId()); - final ContainerImageDate date8 = imageDateRepository.save(IMAGE_DATE_8); - log.info("Seeded image date id {}", date8.getId()); - } - -} diff --git a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/SeederImpl.java b/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/SeederImpl.java deleted file mode 100644 index 6d61535417..0000000000 --- a/fda-container-service/services/src/main/java/at/tuwien/seeder/impl/SeederImpl.java +++ /dev/null @@ -1,31 +0,0 @@ -package at.tuwien.seeder.impl; - -import at.tuwien.seeder.Seeder; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Service; - -import java.util.Arrays; - -@Slf4j -@Service -public class SeederImpl implements Seeder { - - private final Seeder imageSeederImpl; - private final Environment environment; - private final Seeder containerSeederImpl; - - public SeederImpl(Seeder imageSeederImpl, Environment environment, Seeder containerSeederImpl) { - this.imageSeederImpl = imageSeederImpl; - this.environment = environment; - this.containerSeederImpl = containerSeederImpl; - } - - @Override - public void seed() { - imageSeederImpl.seed(); - if (Arrays.asList(environment.getActiveProfiles()).contains("seeder")) { - containerSeederImpl.seed(); - } - } -} diff --git a/fda-container-service/services/src/main/java/at/tuwien/service/ImageService.java b/fda-container-service/services/src/main/java/at/tuwien/service/ImageService.java index c68e667c51..9bcee5e55b 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/service/ImageService.java +++ b/fda-container-service/services/src/main/java/at/tuwien/service/ImageService.java @@ -59,4 +59,13 @@ public interface ImageService { * @throws PersistenceException The database returned an error. */ void delete(Long id) throws ImageNotFoundException, PersistenceException; + + /** + * Pulls a container image by given repository and tag. + * + * @param repository The repository. + * @param tag The tag. + * @throws ImageNotFoundException The image was not found. + */ + void pull(String repository, String tag) throws ImageNotFoundException; } diff --git a/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java b/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java index 99c335de1c..618de10640 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java +++ b/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java @@ -1,6 +1,7 @@ package at.tuwien.service.impl; import at.tuwien.api.container.ContainerCreateRequestDto; +import at.tuwien.config.DockerConfig; import at.tuwien.entities.container.Container; import at.tuwien.entities.container.image.ContainerImage; import at.tuwien.entities.user.User; @@ -38,6 +39,7 @@ public class ContainerServiceImpl implements ContainerService { private final ImageMapper imageMapper; private final UserService userService; private final DockerClient dockerClient; + private final DockerConfig dockerConfig; private final ContainerMapper containerMapper; private final ImageRepository imageRepository; private final ContainerRepository containerRepository; @@ -45,7 +47,7 @@ public class ContainerServiceImpl implements ContainerService { @Autowired public ContainerServiceImpl(DockerClient dockerClient, ContainerRepository containerRepository, ImageRepository imageRepository, HostConfig hostConfig, ContainerMapper containerMapper, - ImageMapper imageMapper, UserService userService) { + ImageMapper imageMapper, UserService userService, DockerConfig dockerConfig) { this.hostConfig = hostConfig; this.dockerClient = dockerClient; this.imageRepository = imageRepository; @@ -53,6 +55,7 @@ public class ContainerServiceImpl implements ContainerService { this.containerMapper = containerMapper; this.imageMapper = imageMapper; this.userService = userService; + this.dockerConfig = dockerConfig; } @Override @@ -65,27 +68,30 @@ public class ContainerServiceImpl implements ContainerService { log.error("failed to get image with name {}:{}", createDto.getRepository(), createDto.getTag()); throw new ImageNotFoundException("image was not found in metadata database."); } - final Integer availableTcpPort = SocketUtils.findAvailableTcpPort(10000); - final HostConfig hostConfig = this.hostConfig - .withNetworkMode("userdb") - .withBinds(Bind.parse("/tmp:/tmp")) - .withPortBindings(PortBinding.parse(availableTcpPort + ":" + image.get().getDefaultPort())); /* save to metadata database */ + final Integer availableTcpPort = SocketUtils.findAvailableTcpPort(10000); Container container = new Container(); container.setImage(image.get()); container.setPort(availableTcpPort); container.setName(createDto.getName()); container.setInternalName(containerMapper.containerToInternalContainerName(container)); - final User user = userService.findByUsername(principal.getName()); - container.setCreator(user); - log.trace("will create host config {} and container {}", hostConfig, container); /* create the volume */ final CreateVolumeResponse response = dockerClient.createVolumeCmd() .withName(container.getInternalName()) .exec(); log.info("Created volume {}", response.getName()); log.debug("created volume {} with mapping /var/lib/mysql", response.getName()); -// final Volume volume = new Volume("/var/lib/mysql"); + /* create host mapping */ + log.info("Configured container binds"); + log.debug("configured container binds as {} and {}", dockerConfig.getMountPath() + ":/tmp", response.getName() + ":/var/lib/mysql"); + final HostConfig hostConfig = this.hostConfig + .withNetworkMode("userdb") + .withBinds(Bind.parse(dockerConfig.getMountPath() + ":/tmp"), Bind.parse(response.getName() + ":/var/lib/mysql")) + .withPortBindings(PortBinding.parse(availableTcpPort + ":" + image.get().getDefaultPort())); + log.debug("host config {}", hostConfig); + final User user = userService.findByUsername(principal.getName()); + container.setCreator(user); + log.trace("will create host config {} and container {}", hostConfig, container); /* create the container */ final CreateContainerResponse response1; try { diff --git a/fda-container-service/services/src/main/java/at/tuwien/service/impl/ImageServiceImpl.java b/fda-container-service/services/src/main/java/at/tuwien/service/impl/ImageServiceImpl.java index 3b4c7678c8..b538e34bd8 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/service/impl/ImageServiceImpl.java +++ b/fda-container-service/services/src/main/java/at/tuwien/service/impl/ImageServiceImpl.java @@ -150,13 +150,7 @@ public class ImageServiceImpl implements ImageService { return imageMapper.inspectImageResponseToContainerImage(response); } - /** - * Pulls a container image by given repository and tag. - * - * @param repository The repository. - * @param tag The tag. - * @throws ImageNotFoundException The image was not found. - */ + @Override public void pull(String repository, String tag) throws ImageNotFoundException { final ResultCallback.Adapter<PullResponseItem> response; try { @@ -165,7 +159,8 @@ public class ImageServiceImpl implements ImageService { .start(); final Instant now = Instant.now(); response.awaitCompletion(); - log.debug("pulled image in {} seconds", Duration.between(now, Instant.now()).getSeconds()); + log.info("Pulled image {}:{}", repository, tag); + log.debug("pulled image {}:{} in {} seconds", repository, tag, Duration.between(now, Instant.now()).getSeconds()); } catch (NotFoundException | InterruptedException | InternalServerErrorException e) { log.warn("image {}:{} not found in library", repository, tag); throw new ImageNotFoundException("image not found in library", e); diff --git a/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java b/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java index be6bc90490..a2efcc3704 100644 --- a/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java +++ b/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java @@ -28,11 +28,6 @@ public interface DatabaseMapper { org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DatabaseMapper.class); - @Mappings({ - @Mapping(target = "name", source = "internalName") - }) - CreateVirtualHostDto databaseToCreateVirtualHostDto(Database data); - @Named("internalMapping") default String nameToInternalName(String data) { if (data == null || data.length() == 0) { diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/query/QueryBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/query/QueryBriefDto.java new file mode 100644 index 0000000000..421e23ff38 --- /dev/null +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/query/QueryBriefDto.java @@ -0,0 +1,77 @@ +package at.tuwien.api.database.query; + +import at.tuwien.api.user.UserDto; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +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 +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode +@ToString +public class QueryBriefDto { + + @NotNull(message = "id is required") + @Parameter(name = "query id", example = "1") + private Long id; + + @NotNull(message = "container id is required") + @Parameter(name = "container id", example = "1") + private Long cid; + + @NotNull(message = "database id is required") + @Parameter(name = "database id", example = "1") + private Long dbid; + + @JsonIgnore + @NotNull(message = "created by is required") + @Parameter(name = "creator id", example = "1") + private Long createdBy; + + @NotNull(message = "creator is required") + @Parameter(name = "creator") + private UserDto creator; + + @Parameter(name = "execution time", example = "2022-01-01 08:00:00.000") + private Instant execution; + + @NotBlank(message = "statement is required") + @Parameter(name = "query raw", example = "select * from table") + private String query; + + @JsonProperty("query_normalized") + @Parameter(name = "query normalized", example = "select id, name from table") + private String queryNormalized; + + @NotBlank(message = "query hash is required") + @JsonProperty("query_hash") + @Parameter(name = "query hash sha256", example = "17e682f060b5f8e47ea04c5c4855908b0a5ad612022260fe50e11ecb0cc0ab76") + private String queryHash; + + @JsonProperty("result_hash") + @Parameter(name = "result hash sha256", example = "17e682f060b5f8e47ea04c5c4855908b0a5ad612022260fe50e11ecb0cc0ab76") + private String resultHash; + + @JsonProperty("result_number") + @Parameter(name = "result number of records", example = "1") + private Long resultNumber; + + @NotNull(message = "created timestamp is required") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC") + private Instant created; + + @JsonProperty("last_modified") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC") + private Instant lastModified; + +} diff --git a/fda-metadata-db/setup-schema.sql b/fda-metadata-db/setup-schema.sql index f5dd40b136..ab0739b379 100644 --- a/fda-metadata-db/setup-schema.sql +++ b/fda-metadata-db/setup-schema.sql @@ -580,4 +580,24 @@ VALUES ('MIT', 'https://opensource.org/licenses/MIT'), ('CC0-1.0', 'https://creativecommons.org/publicdomain/zero/1.0/legalcode'), ('CC-BY-4.0', 'https://creativecommons.org/licenses/by/4.0/legalcode'); +INSERT INTO mdb_images (repository, tag, default_port, dialect, driver_class, jdbc_method) +VALUES ('mariadb', '10.5', 3306, 'org.hibernate.dialect.MariaDBDialect', 'org.mariadb.jdbc.Driver', 'mariadb'); + +INSERT INTO mdb_images_environment_item (key, value, etype, iid) +VALUES ('ROOT', 'root', 'PRIVILEGED_USERNAME', 1), + ('MARIADB_ROOT_PASSWORD', 'mariadb', 'PRIVILEGED_PASSWORD', 1), + ('MARIADB_USER', 'mariadb', 'USERNAME', 1), + ('MARIADB_PASSWORD', 'mariadb', 'PASSWORD', 1); + +INSERT INTO mdb_images_date (iid, database_format, unix_format, example, has_time) +VALUES (1, '%Y-%c-%d', 'yyyy-MM-dd', '2022-01-30', false), + (1, '%d.%c.%Y', 'yyyy-MM-dd', '30.01.2022', false), + (1, '%d.%c.%y', 'yyyy-MM-dd', '30.01.22', false), + (1, '%c/%d/%Y', 'yyyy-MM-dd', '01/30/2022', false), + (1, '%c/%d/%y', 'yyyy-MM-dd', '01/30/22', false), + (1, '%Y-%c-%d''T''%H:%i:%S.%f', 'yyyy-MM-dd''T''HH:mm:ss.SSSSSS', '2022-01-30T13:44:25.499', true), + (1, '%Y-%c-%d %H:%i:%S.%f', 'yyyy-MM-dd HH:mm:ss.SSSSSS', '2022-01-30 13:44:25.499', true), + (1, '%Y-%c-%d''T''%H:%i:%S', 'yyyy-MM-dd''T''HH:mm:ss', '2022-01-30T13:44:25', true), + (1, '%Y-%c-%d %H:%i:%S', 'yyyy-MM-dd HH:mm:ss', '2022-01-30 13:44:25', true); + COMMIT; diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java index ebaa599925..229f2328a8 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java @@ -1,5 +1,6 @@ package at.tuwien.endpoint; +import at.tuwien.api.database.query.QueryBriefDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.entities.user.User; import at.tuwien.mapper.UserMapper; @@ -18,6 +19,8 @@ import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; import java.security.Principal; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; @Log4j2 @RestController @@ -43,9 +46,9 @@ public class StoreEndpoint extends AbstractEndpoint { @GetMapping @Transactional(readOnly = true) @Operation(summary = "Find queries", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<List<QueryDto>> findAll(@NotNull @PathVariable("id") Long containerId, - @NotNull @PathVariable("databaseId") Long databaseId, - Principal principal) + public ResponseEntity<List<QueryBriefDto>> findAll(@NotNull @PathVariable("id") Long containerId, + @NotNull @PathVariable("databaseId") Long databaseId, + Principal principal) throws QueryStoreException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, NotAllowedException, DatabaseConnectionException, TableMalformedException { @@ -54,7 +57,16 @@ public class StoreEndpoint extends AbstractEndpoint { throw new NotAllowedException("Missing view all queries permission"); } final List<Query> queries = storeService.findAll(containerId, databaseId); - return ResponseEntity.ok(queryMapper.queryListToQueryDtoList(queries)); + final List<User> users = userService.findAll(); + final List<QueryBriefDto> out = queries.stream() + .map(q -> { + final QueryBriefDto dto = queryMapper.queryToQueryBriefDto(q); + final Optional<User> optional = users.stream().filter(u -> u.getId().equals(q.getCreatedBy())).findFirst(); + optional.ifPresent(user -> dto.setCreator(userMapper.userToUserDto(user))); + return dto; + }) + .collect(Collectors.toList()); + return ResponseEntity.ok(out); } @GetMapping("/{queryId}") diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java index 197da01129..ed7ab9fa26 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java @@ -109,7 +109,8 @@ public class TableDataEndpoint extends AbstractEndpoint { log.error("Missing data insert permission"); throw new NotAllowedException("Missing data insert permission"); } - log.info("Insert data from location {} into database id {}", data, databaseId); + log.info("Insert data into database with id {}", databaseId); + log.debug("insert data from location {} into database with id {}", data, databaseId); queryService.insert(containerId, databaseId, tableId, data); return ResponseEntity.accepted() .build(); diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java index 7c23a49c1b..f7b4033dfc 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java @@ -4,6 +4,7 @@ import at.tuwien.api.database.table.TableHistoryDto; import at.tuwien.exception.*; import at.tuwien.service.*; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -31,7 +32,7 @@ public class TableHistoryEndpoint extends AbstractEndpoint { @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}) @Transactional(readOnly = true) - @Operation(summary = "Find all history") + @Operation(summary = "Find all history", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<List<TableHistoryDto>> getAll(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("tableId") Long tableId, diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java b/fda-query-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java index 690a4510b8..067474b9c9 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java @@ -114,11 +114,11 @@ public class ApiExceptionHandler extends ResponseEntityExceptionHandler { return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); } - @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseStatus(HttpStatus.LOCKED) @ExceptionHandler(TableMalformedException.class) public ResponseEntity<ApiErrorDto> handle(TableMalformedException e, WebRequest request) { final ApiErrorDto response = ApiErrorDto.builder() - .status(HttpStatus.BAD_REQUEST) + .status(HttpStatus.LOCKED) .message(e.getLocalizedMessage()) .code("error.table.malformed") .build(); diff --git a/fda-query-service/rest-service/src/main/resources/application-docker.yml b/fda-query-service/rest-service/src/main/resources/application-docker.yml index 77c04460c1..031d6cc3bf 100644 --- a/fda-query-service/rest-service/src/main/resources/application-docker.yml +++ b/fda-query-service/rest-service/src/main/resources/application-docker.yml @@ -30,7 +30,7 @@ logging: level: root: warn at.tuwien.: debug - at.tuwien.mapper.: debug + at.tuwien.mapper.: trace at.tuwien.service.: debug at.tuwien.config.: debug at.tuwien.auth.UserPermissionEvaluator: trace diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java index 6631d0b6c1..3a95fba931 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java @@ -1,6 +1,7 @@ package at.tuwien.endpoint; import at.tuwien.BaseUnitTest; +import at.tuwien.api.database.query.QueryBriefDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.config.ReadyConfig; import at.tuwien.exception.*; @@ -50,7 +51,7 @@ public class StoreEndpointUnitTest extends BaseUnitTest { .thenReturn(List.of(QUERY_1)); /* test */ - final ResponseEntity<List<QueryDto>> response = storeEndpoint.findAll(CONTAINER_1_ID, DATABASE_1_ID, principal); + final ResponseEntity<List<QueryBriefDto>> response = storeEndpoint.findAll(CONTAINER_1_ID, DATABASE_1_ID, principal); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); assertEquals(1, response.getBody().size()); diff --git a/fda-query-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java b/fda-query-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java index fb232e7155..542c789ad5 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java +++ b/fda-query-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java @@ -3,7 +3,7 @@ package at.tuwien.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; -@ResponseStatus(code = HttpStatus.BAD_REQUEST) +@ResponseStatus(code = HttpStatus.LOCKED) public class TableMalformedException extends Exception { public TableMalformedException(String msg) { diff --git a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java index 6d2f3df272..af76e96b7a 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java +++ b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java @@ -1,6 +1,5 @@ package at.tuwien.mapper; -import at.tuwien.InsertTableRawQuery; import at.tuwien.api.database.query.*; import at.tuwien.api.database.table.TableCsvDeleteDto; import at.tuwien.api.database.table.TableCsvDto; @@ -21,7 +20,6 @@ import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.Named; import org.mariadb.jdbc.MariaDbBlob; -import org.mariadb.jdbc.internal.ColumnType; import org.springframework.transaction.annotation.Transactional; import java.math.BigInteger; @@ -53,12 +51,9 @@ public interface QueryMapper { ExecuteStatementDto saveStatementDtoToExecuteStatementDto(SaveStatementDto data); - @Mappings({ - @Mapping(target = "creator", ignore = true) - }) QueryDto queryToQueryDto(Query data); - List<QueryDto> queryListToQueryDtoList(List<Query> data); + QueryBriefDto queryToQueryBriefDto(Query data); @Named("internalMapping") default String nameToInternalName(String data) { @@ -286,7 +281,8 @@ public interface QueryMapper { .append(column.getInternalName()) .append(", NULL), '") .append(column.getDateFormat() - .getDatabaseFormat()) + .getDatabaseFormat() + .replace('\'', '\\')) .append("')"); return; } @@ -294,7 +290,8 @@ public interface QueryMapper { .append(column.getInternalName()) .append(", '") .append(column.getDateFormat() - .getDatabaseFormat()) + .getDatabaseFormat() + .replace('\'', '\\')) .append("')"); } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/UserService.java b/fda-query-service/services/src/main/java/at/tuwien/service/UserService.java index 5bbe7d4e98..3672477166 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/UserService.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/UserService.java @@ -3,8 +3,17 @@ package at.tuwien.service; import at.tuwien.entities.user.User; import at.tuwien.exception.UserNotFoundException; +import java.util.List; + public interface UserService { + /** + * Finds all users + * + * @return The list of users. + */ + List<User> findAll(); + /** * Finds a user by username. * diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java index 04c51ace0c..16601ad35d 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java @@ -327,15 +327,32 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService final Connection connection = dataSource.getConnection(); queryMapper.generateTemporaryTableSQL(connection, table) .executeUpdate(); + } catch (SQLException e) { + log.error("Failed to create temporary table: {}", e.getMessage()); + log.debug("failed to create temporary table {}", table); + dataSource.close(); + throw new TableMalformedException("Failed to create temporary table", e); + } + try { + final Connection connection = dataSource.getConnection(); queryMapper.pathToRawInsertQuery(connection, table, data) .executeUpdate(); queryMapper.generateInsertFromTemporaryTableSQL(connection, table) .executeUpdate(); + } catch (SQLException e) { + log.error("Failed to insert temporary table: {}", e.getMessage()); + log.debug("failed to insert temporary table {}", table); + dataSource.close(); + throw new TableMalformedException("Failed to insert temporary table", e); + } + try { + final Connection connection = dataSource.getConnection(); queryMapper.dropTemporaryTableSQL(connection, table) .executeUpdate(); } catch (SQLException e) { - log.error("Failed to create/insert/drop temporary table"); - throw new TableMalformedException("Failed to create/insert/drop temporary table"); + log.error("Failed to drop temporary table: {}", e.getMessage()); + log.debug("failed to drop temporary table {}", table); + throw new TableMalformedException("Failed to drop temporary table", e); } finally { dataSource.close(); } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java index d0c7fdb2d0..da0c472016 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java @@ -8,6 +8,7 @@ import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Optional; @Log4j2 @@ -21,6 +22,11 @@ public class UserServiceImpl implements UserService { this.userRepository = userRepository; } + @Override + public List<User> findAll() { + return userRepository.findAll(); + } + @Override public User findByUsername(String username) throws UserNotFoundException { final Optional<User> optional = userRepository.findByUsername(username); diff --git a/fda-ui/.env.example b/fda-ui/.env.example index cd48b1313b..6d67c5eca9 100644 --- a/fda-ui/.env.example +++ b/fda-ui/.env.example @@ -5,3 +5,4 @@ API="http://localhost:9095" BROKER_USERNAME=fda BROKER_PASSWORD=fda SANDBOX=false +SHARED_FILESYSTEM=/tmp diff --git a/fda-ui/Dockerfile b/fda-ui/Dockerfile index e94b6866a5..d9b406ac01 100644 --- a/fda-ui/Dockerfile +++ b/fda-ui/Dockerfile @@ -34,5 +34,7 @@ ENV API=http://gateway-service:9095 ENV BROKER_USERNAME=fda ENV BROKER_PASSWORD=fda ENV SANDBOX=false +ENV SHARED_FILESYSTEM=/tmp +ENV VERSION=1.1.1 ENTRYPOINT ["yarn", "start"] diff --git a/fda-ui/components/TableList.vue b/fda-ui/components/TableList.vue index 68b2442e5d..0fb5ddc2ff 100644 --- a/fda-ui/components/TableList.vue +++ b/fda-ui/components/TableList.vue @@ -61,16 +61,10 @@ AMQP Consumer(s) </v-list-item-title> <v-list-item-content class="amqp-consumer"> - {{ tableDetails.consumers.length }} <v-badge - v-if="!tableDetails.consumersUp" - class="ml-1" - color="error" - content="down" /> <v-badge - v-if="tableDetails.consumersUp" class="ml-1" - color="success" - content="up" /> + :color="consumersState" + :content="`${consumersUp}/${consumersTotal} up`" /> </v-list-item-content> </v-list-item-content> </v-list-item> @@ -188,11 +182,13 @@ export default { return { loading: false, loadingDetails: false, + loadingConsumers: false, error: false, tables: [], panel: null, column: null, unitDialog: false, + consumers: [], database: { exchange: null, tables: [] @@ -203,9 +199,7 @@ export default { description: null, topic: null, columns: [], - created: null, - consumers: [], - consumersUp: false + created: null }, dialogDelete: false, headers: [ @@ -252,6 +246,21 @@ export default { }, createdUTC () { return formatTimestampUTCLabel(this.tableDetails.created) + }, + consumersState () { + if (this.consumersTotal === 0) { + return 'error' + } + if (this.consumersTotal - this.consumersUp > 0) { + return 'warning' + } + return 'success' + }, + consumersTotal () { + return this.consumers.length + }, + consumersUp () { + return this.consumers.filter(c => c.active).length } }, mounted () { @@ -286,6 +295,10 @@ export default { return column.column_type }, async details (tableId) { + if (tableId === this.tableDetails.id) { + /* prevent weird glitch of opening and collapsing simultaneously */ + return + } try { this.loadingDetails = true const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${tableId}`, this.config) @@ -334,18 +347,16 @@ export default { }, async consumerDetails (topic) { try { - this.loading = true + this.loadingConsumers = true const res = await this.$axios.get('/api/broker/consumers/%2F', this.brokerConfig) const consumers = res.data.filter(c => c.queue.name === topic) console.debug('consumers', consumers) - const state = res.data.filter(c => c.queue.name === topic && c.active) - this.tableDetails.consumers = consumers - this.tableDetails.consumersUp = consumers.length === state.length - this.loading = false + this.consumers = consumers } catch (err) { + console.error('Could not find consumers') this.$toast.error('Could not find consumers.') } - this.dialogDelete = false + this.loadingConsumers = false }, showDeleteTableDialog (id) { this.deleteTableId = id diff --git a/fda-ui/components/TableSchema.vue b/fda-ui/components/TableSchema.vue index 93a96e92cb..f010738d85 100644 --- a/fda-ui/components/TableSchema.vue +++ b/fda-ui/components/TableSchema.vue @@ -89,7 +89,7 @@ <v-btn v-if="back" class="mt-10 mr-2 mb-1" @click="stepBack()"> Back </v-btn> - <v-btn color="primary" :loading="finished" :disabled="!valid" class="mt-10 mb-1" @click="submit()"> + <v-btn color="primary" :loading="finished && !error" :disabled="!valid" class="mt-10 mb-1" @click="submit()"> Continue </v-btn> </div> @@ -111,6 +111,12 @@ export default { default () { return false } + }, + error: { + type: Boolean, + default () { + return false + } } }, data () { @@ -118,7 +124,6 @@ export default { loading: false, dateFormats: [], valid: true, - error: false, finished: false, tableColumns: [], container: { diff --git a/fda-ui/components/dialogs/CreateDB.vue b/fda-ui/components/dialogs/CreateDB.vue index d97ee9e875..686d0cede1 100644 --- a/fda-ui/components/dialogs/CreateDB.vue +++ b/fda-ui/components/dialogs/CreateDB.vue @@ -38,11 +38,13 @@ :rules="[v => !!v || $t('Required')]" return-object required /> - <v-checkbox + <v-switch id="public" v-model="createDatabase.is_public" - name="public" - label="Public" /> + color="primary" + :label="publicLabel" + name="public" /> + <p>{{ summary }}</p> </v-card-text> <v-card-actions> <v-spacer /> @@ -76,7 +78,10 @@ export default { valid: false, loading: false, error: false, - engine: null, + engine: { + repository: null, + tag: null + }, engines: [], progress: 0, createContainer: { @@ -107,6 +112,15 @@ export default { return { headers: { Authorization: `Bearer ${this.token}` } } + }, + publicLabel () { + return this.createDatabase.is_public ? 'Public' : 'Private' + }, + summary () { + return 'Your database will be ' + + (this.createDatabase.is_public ? 'publicly visible to the world' : 'visible only to you') + + ' and run ' + + (this.engine.repository === 'mariadb' ? 'MariaDB Engine (' + this.engine.tag + ')' : 'other') } }, mounted () { diff --git a/fda-ui/layouts/default.vue b/fda-ui/layouts/default.vue index 91a78486ff..52f6400b95 100644 --- a/fda-ui/layouts/default.vue +++ b/fda-ui/layouts/default.vue @@ -1,32 +1,51 @@ <template> <v-app> <v-navigation-drawer v-model="drawer" fixed app> - <v-img - contain - class="tu-logo" - src="/tu_logo_512.png" /> - <v-img - contain - class="univie-logo" - src="/univie_logo_512.png" /> - <v-list> + <v-list-item> + <v-list-item-content> + <v-list-item-subtitle> + {{ version }} + </v-list-item-subtitle> + <v-list-item-title class="text-h6"> + Database Repository + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-list nav> <v-list-item - v-for="(item, i) in filteredItems" - :key="i" - :to="item.to" + to="/" router> <v-list-item-action> - <v-icon>{{ item.icon }}</v-icon> + <v-icon>mdi-information-outline</v-icon> </v-list-item-action> <v-list-item-content> - <v-list-item-title v-text="item.title" /> + <v-list-item-title>Information</v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-list-item + to="/container" + router> + <v-list-item-action> + <v-icon>mdi-database</v-icon> + </v-list-item-action> + <v-list-item-content> + <v-list-item-title>Databases</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> + <div> + <v-img + contain + class="tu-logo" + src="/tu_logo_512.png" /> + <v-img + contain + class="univie-logo" + src="/univie_logo_512.png" /> + </div> </v-navigation-drawer> <v-app-bar fixed app> <v-app-bar-nav-icon @click.stop="drawer = !drawer" /> - <v-toolbar-title v-text="title" /> <v-spacer /> <v-btn v-if="!token" @@ -93,15 +112,6 @@ </template> <script> -import { - mdiDatabase, - mdiTable, - mdiFileDelimited, - mdiDatabaseSearch, - mdiHome, - mdiCog -} from '@mdi/js' - export default { name: 'DefaultLayout', data () { @@ -110,43 +120,7 @@ export default { user: { theme_dark: null }, - loadingUser: true, - items: [ - { - icon: mdiHome, - title: 'Home', - to: '/' - }, - { - icon: mdiDatabase, - title: 'Databases', - to: '/container' - }, - { - icon: mdiCog, - title: 'Privacy', - to: '/privacy' - }, - { - icon: mdiTable, - title: 'Tables', - to: '/tables', - needsContainer: true - }, - { - icon: mdiFileDelimited, - title: 'Import CSV', - to: '/import_csv', - needsContainer: true - }, - { - icon: mdiDatabaseSearch, - title: 'SQL Query', - to: '/queries', - needsContainer: true - } - ], - title: 'FAIR Data Austria — Database Repository' + loadingUser: true } }, computed: { @@ -171,6 +145,9 @@ export default { db () { return this.$store.state.db }, + version () { + return this.$config.version + }, config () { if (this.token === null) { return {} @@ -255,4 +232,7 @@ export default { .univie-logo { margin: 1em 1em .5em; } +.sl { + padding-left: 36px; +} </style> diff --git a/fda-ui/nuxt.config.js b/fda-ui/nuxt.config.js index a03852d240..512644de56 100644 --- a/fda-ui/nuxt.config.js +++ b/fda-ui/nuxt.config.js @@ -4,12 +4,6 @@ import colors from 'vuetify/es5/util/colors' // pick env vars from .env file or get them passed through docker-compose require('dotenv').config() -if (!process.env.API) { - throw new Error(`Environment variable API not defined. - -Have you passed env vars from docker-compose or defined them in your .env file?`) -} - if (process.env.SANDBOX) { console.info('[FDA] Running in sandbox environment') } @@ -87,13 +81,15 @@ export default { }, publicRuntimeConfig: { - brokerUsername: process.env.BROKER_USERNAME, - brokerPassword: process.env.BROKER_PASSWORD, - sandbox: process.env.SANDBOX + brokerUsername: process.env.BROKER_USERNAME || 'fda', + brokerPassword: process.env.BROKER_PASSWORD || 'fda', + sandbox: process.env.SANDBOX || false, + shared_filesystem: process.env.SHARED_FILESYSTEM || '/tmp', + version: process.env.VERSION || 'latest' }, proxy: { - '/api': process.env.API + '/api': process.env.API || 'http://localhost:9095' }, serverMiddleware: [ diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue index ccc79ac9df..e9426fa1bb 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue @@ -154,6 +154,9 @@ export default { }, token () { return this.$store.state.token + }, + shared_filesystem () { + return this.$config.shared_filesystem } }, mounted () { @@ -190,7 +193,7 @@ export default { }) if (res.data.success) { this.fileLocation = res.data.file.filename - this.tableImport.location = `/tmp/${this.fileLocation}` + this.tableImport.location = `${this.shared_filesystem()}/${this.fileLocation}` console.debug('upload csv', res.data) } else { console.error('Could not upload CSV data', res.data) diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue index 88752b42f4..d00ccf387e 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue @@ -27,18 +27,6 @@ <v-btn v-if="token" class="mb-1" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/import`"> <v-icon left>mdi-cloud-upload</v-icon> Import csv </v-btn> - <v-btn class="ml-2 mb-1" :loading="downloadLoading" @click.stop="download"> - <v-icon left>mdi-download</v-icon> Download csv - </v-btn> - <v-btn - v-if="false" - color="primary" - class="mb-1" - :disabled="!token" - :href="`/api/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/data/export`" - target="_blank"> - <v-icon left>mdi-download</v-icon> Download - </v-btn> </v-toolbar-title> </v-toolbar> <v-toolbar :color="versionColor" flat> @@ -48,6 +36,9 @@ </v-toolbar-title> <v-spacer /> <v-toolbar-title> + <v-btn class="mr-2" :loading="downloadLoading" @click.stop="download"> + <v-icon left>mdi-download</v-icon> Download csv + </v-btn> <v-btn @click="pick()"> <v-icon left>mdi-update</v-icon> Pick </v-btn> @@ -196,9 +187,16 @@ export default { }, methods: { async download () { + if (!this.token) { + return + } this.downloadLoading = true try { - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/export`, { + let exportUrl = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/export` + if (this.version) { + exportUrl += `?timestamp=${this.versionISO}` + } + const res = await this.$axios.get(exportUrl, { headers: { Authorization: `Bearer ${this.token}` }, responseType: 'text' }) diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue index 7c8e38a4b5..2669a69d18 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue @@ -171,7 +171,7 @@ </v-stepper-step> <v-stepper-content step="4"> - <TableSchema :back="true" :columns="tableCreate.columns" @close="schemaClose" /> + <TableSchema :back="true" :error="error" :columns="tableCreate.columns" @close="schemaClose" /> </v-stepper-content> <v-stepper-step @@ -206,6 +206,7 @@ export default { validStep2: false, validStep3: false, validStep4: false, + error: false, separators: [ { key: ',', value: ',' }, { key: ';', value: ';' }, @@ -276,6 +277,9 @@ export default { .replace(/\s+/g, '-') .replace(/[^\w-]+/g, '') .replace(/--+/g, '_')) + }, + shared_filesystem () { + return this.$config.shared_filesystem } }, mounted () { @@ -391,6 +395,7 @@ export default { console.debug('created table', createResult.data) } catch (err) { this.loading = false + this.error = true if (err.response.status === 409) { this.$toast.error('Table name already exists.') } else { @@ -406,6 +411,7 @@ export default { console.debug('inserted table', insertResult.data) } catch (err) { this.loading = false + this.error = true console.error('insert table failed', err) this.$toast.error('Could not insert csv into table.') return diff --git a/fda-ui/pages/privacy.vue b/fda-ui/pages/privacy.vue deleted file mode 100644 index 9b14579d0b..0000000000 --- a/fda-ui/pages/privacy.vue +++ /dev/null @@ -1,53 +0,0 @@ -<template> - <div> - <v-card flat> - <v-card-title> - General Data Protection Regulation Statement - </v-card-title> - <v-card-subtitle> - Data processing name: FAIR Data Austria Database Repository - </v-card-subtitle> - <v-card-text> - <h3>Legal basis</h3> - <p> - You have voluntarily provided us with personal data which we process with your consent for the following - purposes: - </p> - <ul> - <li>Provide virtual database environment</li> - <li>View database records</li> - <li>Routine statistics</li> - </ul> - <p class="mt-2"> - You can withdraw this consent at any time. Withdrawal will result in your data no longer being processed for - the above purposes from that time on. To withdraw your consent, please contact: - <a href="mailto:privacy@ossdip.at">privacy@ossdip.at</a> - </p> - <h3>Storage duration</h3> - <p> - We store your personal data for [please enter the period and the criteria for erasure. If available, the legal - regulations can be quoted - see leaflet in German “Storage and retention periods”. - Your data will not be disclosed to third parties. - </p> - <h3>Contact</h3> - <p> - You can reach us at: <a href="mailto:privacy@ossdip.at">privacy@ossdip.at</a> - </p> - <h3>Help</h3> - <p> - Basically, you have the right to access, rectification, erasure, restriction, data portability and to object. - Contact us about this. - If you believe that the processing of your data contravenes data-protection law or your legal claim to - protection of your data has been violated in some other way, you can complain to the supervisory authorities. - The Data Protection Authority is responsible for this in Austria. - </p> - </v-card-text> - </v-card> - </div> -</template> - -<script> -export default { - components: {} -} -</script> diff --git a/fda-ui/server-middleware/index.js b/fda-ui/server-middleware/index.js index a22bed855d..dfde4c0eb1 100644 --- a/fda-ui/server-middleware/index.js +++ b/fda-ui/server-middleware/index.js @@ -22,8 +22,7 @@ const colTypeMap = { app.post('/table_from_csv', upload.single('file'), async (req, res) => { const { file } = req - const { path } = file - + const { filename } = file // send path to analyse service let analysis let json @@ -31,10 +30,10 @@ app.post('/table_from_csv', upload.single('file'), async (req, res) => { const analyseUrl = `${process.env.API}/api/analyse/determinedt` analysis = await fetch(analyseUrl, { method: 'post', - body: JSON.stringify({ filepath: path }), + body: JSON.stringify({ filepath: `/tmp/${filename}` }), headers: { 'Content-Type': 'application/json' } }).catch((error) => { - console.error('data type determination failed', error) + console.error('data type determination failed:', error) throw error }) json = await analysis.json() @@ -42,7 +41,7 @@ app.post('/table_from_csv', upload.single('file'), async (req, res) => { return res.json({ success: false, message: 'Columns array missing' }) } } catch (error) { - console.error('failed to analyze', error) + console.error('failed to analyze:', error) return res.json({ success: false, error }) } diff --git a/fda-ui/yarn.lock b/fda-ui/yarn.lock index 9dc3884b81..dfe7752b33 100644 --- a/fda-ui/yarn.lock +++ b/fda-ui/yarn.lock @@ -2830,6 +2830,13 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +basic-auth@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + big.js@^3.1.3: version "3.2.0" resolved "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz" @@ -4450,6 +4457,11 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + des.js@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz" @@ -8115,6 +8127,17 @@ moment@^2.29.1: resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== +morgan@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" + integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== + dependencies: + basic-auth "~2.0.1" + debug "2.6.9" + depd "~2.0.0" + on-finished "~2.3.0" + on-headers "~1.0.2" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz" -- GitLab