From 9e7430958e1c8498b738b85aa9f87125768fc797 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Mon, 7 Oct 2024 18:43:27 +0000 Subject: [PATCH] Hotfix/mapping --- .docker/config/dbrepo.conf | 37 +- .docker/config/enabled_plugins | 2 +- .docker/config/rabbitmq.conf | 6 +- .docker/docker-compose.yml | 11 +- .docs/api/broker-service.md | 11 +- dbrepo-broker-service/README.md | 2 + dbrepo-broker-service/advanced.config | 5 + dbrepo-broker-service/enabled_plugins | 2 +- dbrepo-broker-service/rabbitmq.conf | 6 +- .../dashboards/system.json | 829 +++++++++++------- dbrepo-dashboard-service/grafana.ini | 3 + .../at/tuwien/endpoints/AccessEndpoint.java | 3 +- .../at/tuwien/endpoints/SubsetEndpoint.java | 7 +- .../at/tuwien/endpoints/TableEndpoint.java | 18 +- .../src/main/resources/init/querystore.sql | 2 +- .../java/at/tuwien/config/MariaDbConfig.java | 2 - .../endpoint/AccessEndpointUnitTest.java | 16 +- .../endpoint/SubsetEndpointUnitTest.java | 4 +- .../endpoint/TableEndpointUnitTest.java | 34 +- .../MetadataServiceGatewayUnitTest.java | 6 +- .../tuwien/mvc/PrometheusEndpointMvcTest.java | 4 +- .../service/SchemaServiceIntegrationTest.java | 56 +- .../service/TableServiceIntegrationTest.java | 90 +- .../service/ViewServiceIntegrationTest.java | 8 +- .../src/test/resources/init/querystore.sql | 2 +- .../impl/MetadataServiceGatewayImpl.java | 37 +- .../java/at/tuwien/mapper/DataMapper.java | 62 +- .../java/at/tuwien/mapper/MariaDbMapper.java | 172 +--- .../java/at/tuwien/service/TableService.java | 4 +- .../impl/SubsetServiceMariaDbImpl.java | 8 +- .../service/impl/TableServiceMariaDbImpl.java | 4 +- dbrepo-gateway-service/dbrepo.conf | 15 +- dbrepo-metadata-db/1_setup-schema.sql | 325 ++++--- dbrepo-metadata-service/api/pom.xml | 9 +- .../api/container/image/DataTypeDto.java | 66 ++ .../api/container/image/ImageDateDto.java | 48 - .../tuwien/api/container/image/ImageDto.java | 7 +- .../internal/PrivilegedContainerDto.java | 1 - .../at/tuwien/api/database/ViewColumnDto.java | 4 - .../api/database/query/ImportCsvDto.java | 49 -- .../tuwien/api/database/query/ImportDto.java | 10 - .../table/columns/ColumnCreateDto.java | 3 - .../api/database/table/columns/ColumnDto.java | 4 - .../container/image/ContainerImage.java | 7 +- .../container/image/ContainerImageDate.java | 59 -- .../image/ContainerImageDateKey.java | 14 - .../entities/container/image/DataType.java | 73 ++ .../tuwien/entities/database/ViewColumn.java | 6 - .../database/table/columns/TableColumn.java | 6 - .../java/at/tuwien/mapper/MetadataMapper.java | 10 + .../at/tuwien/endpoints/AccessEndpoint.java | 39 +- .../at/tuwien/endpoints/DatabaseEndpoint.java | 1 + .../at/tuwien/endpoints/TableEndpoint.java | 18 +- .../at/tuwien/endpoints/UserEndpoint.java | 14 +- .../tuwien/validation/EndpointValidator.java | 59 +- .../src/main/resources/application-local.yml | 2 +- .../src/main/resources/datatypes.json | 15 + .../endpoints/AccessEndpointUnitTest.java | 88 +- .../endpoints/ActuatorComponentTest.java | 6 + .../endpoints/ConceptEndpointUnitTest.java | 6 + .../endpoints/ImageEndpointUnitTest.java | 6 + .../endpoints/LicenseEndpointUnitTest.java | 6 + .../endpoints/MessageEndpointUnitTest.java | 6 + .../endpoints/MetadataEndpointUnitTest.java | 6 + .../endpoints/OntologyEndpointUnitTest.java | 6 + .../endpoints/TableEndpointUnitTest.java | 95 -- .../endpoints/UnitEndpointUnitTest.java | 6 + .../endpoints/UserEndpointUnitTest.java | 6 + .../endpoints/ViewEndpointUnitTest.java | 6 + .../gateway/BrokerServiceGatewayUnitTest.java | 32 +- .../AuthenticationServiceIntegrationTest.java | 6 + .../service/BrokerServiceIntegrationTest.java | 17 +- .../DatabaseServicePersistenceTest.java | 4 - .../service/DatabaseServiceUnitTest.java | 13 +- .../tuwien/service/ImageServiceUnitTest.java | 6 + .../service/MetadataServiceUnitTest.java | 6 + .../service/TableServicePersistenceTest.java | 3 - .../tuwien/service/TableServiceUnitTest.java | 10 +- .../tuwien/service/UserServiceUnitTest.java | 6 + .../validator/EndpointValidatorUnitTest.java | 69 +- .../java/at/tuwien/config/MetricsConfig.java | 11 +- .../service/impl/AccessServiceImpl.java | 3 +- .../service/impl/DatabaseServiceImpl.java | 7 +- .../tuwien/service/impl/TableServiceImpl.java | 24 +- .../java/at/tuwien/test/AbstractUnitTest.java | 1 + .../main/java/at/tuwien/test/BaseTest.java | 216 +---- dbrepo-metric-db/prometheus.yml | 6 +- dbrepo-search-service/init/Pipfile | 2 +- .../init/lib/dbrepo-1.4.6rc1-py3-none-any.whl | Bin 30062 -> 0 bytes .../init/lib/dbrepo-1.4.6rc1.tar.gz | Bin 39391 -> 0 bytes dbrepo-search-service/init/test/conftest.py | 49 ++ dbrepo-search-service/init/test/test_app.py | 95 ++ .../test/run_testindicies.py | 91 -- dbrepo-ui/Dockerfile | 11 +- dbrepo-ui/bun.lockb | Bin 380227 -> 375993 bytes dbrepo-ui/components/dialogs/EditAccess.vue | 36 +- dbrepo-ui/components/dialogs/EditTuple.vue | 5 +- dbrepo-ui/components/subset/Builder.vue | 8 +- dbrepo-ui/components/subset/Results.vue | 1 + dbrepo-ui/components/subset/SubsetToolbar.vue | 8 +- dbrepo-ui/components/table/TableImport.vue | 39 - dbrepo-ui/components/table/TableSchema.vue | 121 ++- dbrepo-ui/composables/access-service.ts | 2 +- dbrepo-ui/composables/query-service.ts | 58 +- dbrepo-ui/composables/table-service.ts | 1 - dbrepo-ui/dto/index.ts | 8 - dbrepo-ui/dto/mysql.ts | 19 +- dbrepo-ui/locales/en-US.json | 21 +- dbrepo-ui/nuxt.config.ts | 15 +- dbrepo-ui/package.json | 1 - .../pages/database/[database_id]/settings.vue | 11 +- .../[database_id]/table/[table_id]/data.vue | 1 + .../[database_id]/table/[table_id]/schema.vue | 18 +- .../[database_id]/table/create/dataset.vue | 6 - .../[database_id]/table/create/schema.vue | 4 +- dbrepo-ui/pages/semantic/index.vue | 5 +- .../server/routes/actuator/prometheus.ts | 4 + docker-compose.yml | 15 +- .../charts/dbrepo-mariadb-galera-1.4.6.tgz | Bin 57158 -> 57156 bytes helm/dbrepo/values.schema.json | 17 + helm/dbrepo/values.yaml | 6 +- install.sh | 2 +- lib/python/dbrepo/AmqpClient.py | 18 +- 123 files changed, 1787 insertions(+), 1911 deletions(-) create mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDateDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportCsvDto.java delete mode 100644 dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDate.java delete mode 100644 dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDateKey.java create mode 100644 dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java create mode 100644 dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json delete mode 100644 dbrepo-search-service/init/lib/dbrepo-1.4.6rc1-py3-none-any.whl delete mode 100644 dbrepo-search-service/init/lib/dbrepo-1.4.6rc1.tar.gz create mode 100644 dbrepo-search-service/init/test/conftest.py create mode 100644 dbrepo-search-service/init/test/test_app.py delete mode 100644 dbrepo-search-service/test/run_testindicies.py create mode 100644 dbrepo-ui/server/routes/actuator/prometheus.ts diff --git a/.docker/config/dbrepo.conf b/.docker/config/dbrepo.conf index 8ac239d464..7c98728994 100644 --- a/.docker/config/dbrepo.conf +++ b/.docker/config/dbrepo.conf @@ -1,3 +1,9 @@ +# This is required to proxy Grafana Live WebSocket connections. +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + client_max_body_size 20G; resolver 127.0.0.11 valid=30s; # docker dns @@ -34,17 +40,32 @@ upstream upload { server upload-service:8080; } +upstream dashboard-service { + server dashboard-service:3000; +} + server { listen 80 default_server; server_name _; - location /admin/broker { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://broker; - proxy_read_timeout 90; + location /dashboard { + rewrite ^/dashboard/(.*) /$1 break; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://dashboard-service; + proxy_read_timeout 90; + } + + # Proxy Grafana Live WebSocket connections. + location /dashboard/api/live { + rewrite ^/dashboard/(.*) /$1 break; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_pass http://dashboard-service; } location /api/search { @@ -57,7 +78,7 @@ server { } location /api/broker { - rewrite /api/broker/(.*) /admin/broker/api/$1 break; + rewrite /api/broker/(.*) /api/$1 break; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/.docker/config/enabled_plugins b/.docker/config/enabled_plugins index 95f1c0014d..db0ae88849 100644 --- a/.docker/config/enabled_plugins +++ b/.docker/config/enabled_plugins @@ -1 +1 @@ -[rabbitmq_prometheus,rabbitmq_auth_backend_ldap,rabbitmq_auth_mechanism_ssl,rabbitmq_management]. \ No newline at end of file +[rabbitmq_prometheus,rabbitmq_auth_backend_ldap,rabbitmq_auth_mechanism_ssl,rabbitmq_management,rabbitmq_mqtt]. \ No newline at end of file diff --git a/.docker/config/rabbitmq.conf b/.docker/config/rabbitmq.conf index ff592bb3ec..8503942950 100644 --- a/.docker/config/rabbitmq.conf +++ b/.docker/config/rabbitmq.conf @@ -6,7 +6,6 @@ default_user_tags.administrator = false listeners.tcp.1 = 0.0.0.0:5672 # management prefix (https://www.rabbitmq.com/management.html#path-prefix) -management.path_prefix = /admin/broker management.load_definitions = /app/definitions.json # logging @@ -14,6 +13,11 @@ log.console = true log.console.level = warning auth_ldap.log = true +# MQTT +mqtt.vhost = dbrepo +mqtt.exchange = dbrepo +mqtt.prefetch = 10 + # Obviously your authentication server cannot vouch for itself, so you'll need another backend with at least one user in # it. You should probably use the internal database auth_backends.1.authn = ldap diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index ec7a87d252..87dab50c91 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -7,6 +7,8 @@ volumes: search-db-data: storage-service-data: identity-service-data: + metric-db-data: + dashboard-service-data: services: dbrepo-metadata-db: @@ -191,6 +193,7 @@ services: image: docker.io/bitnami/rabbitmq:3.12-debian-12 ports: - 5672:5672 + - 1883:1883 volumes: - ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf - ./config/advanced.config:/etc/rabbitmq/advanced.config @@ -279,11 +282,6 @@ services: NUXT_PUBLIC_API_CLIENT: "${BASE_URL:-http://localhost}" NUXT_PUBLIC_API_SERVER: "${BASE_URL:-http://localhost}" NUXT_PUBLIC_UPLOAD_CLIENT: "${BASE_URL:-http://localhost}/api/upload/files/" - depends_on: - dbrepo-search-service: - condition: service_started - dbrepo-storage-service: - condition: service_healthy healthcheck: test: wget -qO- localhost:3000 | grep "Database Repository" || exit 1 interval: 10s @@ -386,6 +384,7 @@ services: image: bitnami/prometheus:2.54.1-debian-12-r4 volumes: - ./config/prometheus.yml:/etc/prometheus/prometheus.yml + - metric-db-data:/opt/bitnami/prometheus/data healthcheck: test: promtool check healthy interval: 10s @@ -475,7 +474,7 @@ services: BROKER_HOST: "${BROKER_ENDPOINT:-broker-service}" BROKER_PASSWORD: "${SYSTEM_PASSWORD:-admin}" BROKER_PORT: ${BROKER_PORT:-5672} - BROKER_SERVICE_ENDPOINT: "${BROKER_SERVICE_ENDPOINT:-http://gateway-service/admin/broker}" + BROKER_SERVICE_ENDPOINT: "${BROKER_SERVICE_ENDPOINT:-http://broker-service:15672}" BROKER_USERNAME: "${SYSTEM_USERNAME:-admin}" BROKER_VIRTUALHOST: "${BROKER_VIRTUALHOST:-dbrepo}" CONNECTION_TIMEOUT: ${CONNECTION_TIMEOUT:-60000} diff --git a/.docs/api/broker-service.md b/.docs/api/broker-service.md index f2f684c4a9..41e2dc10b1 100644 --- a/.docs/api/broker-service.md +++ b/.docs/api/broker-service.md @@ -11,7 +11,7 @@ author: Martin Weise * Ports: 5672/tcp, 15672/tcp, 15692/tcp * AMQP: `amqp://<hostname>:5672` * Prometheus: `http://<hostname>:15692/metrics` - * Management: `http://<hostname>/admin/broker` + * Management: `http://<hostname>:15672` ## Overview @@ -19,6 +19,13 @@ It holds exchanges and topics responsible for holding AMQP messages for later co use [RabbitMQ](https://www.rabbitmq.com/) in the implementation. By default, the endpoint listens to the insecure port `5672` for incoming AMQP tuples and insecure port `15672` for the management UI. +## Supported Protocols + +* AMQP (v0.9.1, v1.0), see [RabbitMQ docs](https://www.rabbitmq.com/docs/next/amqp). +* MQTT (v3.1, v3.1.1, v5), see [RabbitMQ docs](https://www.rabbitmq.com/docs/mqtt). + +## Authentication + The default configuration allows any user in the `cn=system,ou=users,dc=dbrepo,dc=at` from the [Identity Service](../identity-service) to access the Broker Service as user with `administrator` role, i.e. the `cn=admin,dc=dbrepo,dc=at` user that is created by default. @@ -28,6 +35,8 @@ The Broker Service allows two ways of authentication for AMQP tuples: 1. LDAP 2. Plain (RabbitMQ's internal authentication) +## Architecture + The queue architecture of the Broker Service is very simple. There is only one durable, topic exchange `dbrepo` and one quorum queue `dbrepo`, connected with a binding of `dbrepo.#` which routes all tuples with routing key prefix `dbrepo.` to this queue. diff --git a/dbrepo-broker-service/README.md b/dbrepo-broker-service/README.md index 95e5afaefd..6cff53bb91 100644 --- a/dbrepo-broker-service/README.md +++ b/dbrepo-broker-service/README.md @@ -1,5 +1,7 @@ # Broker Service +Supports MQTT v3, v4 and v5 (https://www.rabbitmq.com/blog/2023/07/21/mqtt5) + ## Advanced Config https://www.rabbitmq.com/docs/ldap \ No newline at end of file diff --git a/dbrepo-broker-service/advanced.config b/dbrepo-broker-service/advanced.config index 4445ea6019..584bcc5232 100644 --- a/dbrepo-broker-service/advanced.config +++ b/dbrepo-broker-service/advanced.config @@ -1,4 +1,9 @@ [ + { + rabbit, [ + {forced_feature_flags_on_init, [quorum_queue, mqtt_v5]} + ] + }, { rabbitmq_auth_backend_ldap, [ diff --git a/dbrepo-broker-service/enabled_plugins b/dbrepo-broker-service/enabled_plugins index 95f1c0014d..db0ae88849 100644 --- a/dbrepo-broker-service/enabled_plugins +++ b/dbrepo-broker-service/enabled_plugins @@ -1 +1 @@ -[rabbitmq_prometheus,rabbitmq_auth_backend_ldap,rabbitmq_auth_mechanism_ssl,rabbitmq_management]. \ No newline at end of file +[rabbitmq_prometheus,rabbitmq_auth_backend_ldap,rabbitmq_auth_mechanism_ssl,rabbitmq_management,rabbitmq_mqtt]. \ No newline at end of file diff --git a/dbrepo-broker-service/rabbitmq.conf b/dbrepo-broker-service/rabbitmq.conf index ff592bb3ec..8503942950 100644 --- a/dbrepo-broker-service/rabbitmq.conf +++ b/dbrepo-broker-service/rabbitmq.conf @@ -6,7 +6,6 @@ default_user_tags.administrator = false listeners.tcp.1 = 0.0.0.0:5672 # management prefix (https://www.rabbitmq.com/management.html#path-prefix) -management.path_prefix = /admin/broker management.load_definitions = /app/definitions.json # logging @@ -14,6 +13,11 @@ log.console = true log.console.level = warning auth_ldap.log = true +# MQTT +mqtt.vhost = dbrepo +mqtt.exchange = dbrepo +mqtt.prefetch = 10 + # Obviously your authentication server cannot vouch for itself, so you'll need another backend with at least one user in # it. You should probably use the internal database auth_backends.1.authn = ldap diff --git a/dbrepo-dashboard-service/dashboards/system.json b/dbrepo-dashboard-service/dashboards/system.json index 68279f8fe4..4eb40a1a2e 100644 --- a/dbrepo-dashboard-service/dashboards/system.json +++ b/dbrepo-dashboard-service/dashboards/system.json @@ -18,7 +18,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": 2, "links": [ { "asDropdown": false, @@ -55,34 +54,17 @@ }, "fieldConfig": { "defaults": { - "displayName": "QoS", "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "purple", + "color": "blue", "value": null - }, - { - "color": "red", - "value": 0 - }, - { - "color": "orange", - "value": 60 - }, - { - "color": "#EAB839", - "value": 80 - }, - { - "color": "green", - "value": 100 } ] }, - "unit": "percent" + "unit": "short" }, "overrides": [] }, @@ -92,7 +74,7 @@ "x": 0, "y": 1 }, - "id": 9, + "id": 4, "options": { "colorMode": "background", "graphMode": "none", @@ -110,7 +92,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "", + "pluginVersion": "11.2.2", "targets": [ { "datasource": { @@ -118,17 +100,18 @@ "uid": "P18F45E9DC7E75912" }, "disableTextWrap": false, - "editorMode": "code", - "expr": "sum(up)*100/count(up)", + "editorMode": "builder", + "expr": "dbrepo_database_count", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "Services Running", + "legendFormat": "__auto", "range": true, "refId": "A", "useBackend": false } ], + "title": "Databases", "type": "stat" }, { @@ -139,34 +122,17 @@ }, "fieldConfig": { "defaults": { - "displayName": "UI Response Time (Mean)", "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "purple", + "color": "blue", "value": null - }, - { - "color": "green", - "value": 0 - }, - { - "color": "yellow", - "value": 200 - }, - { - "color": "orange", - "value": 400 - }, - { - "color": "red", - "value": 600 } ] }, - "unit": "ms" + "unit": "short" }, "overrides": [] }, @@ -176,7 +142,7 @@ "x": 4, "y": 1 }, - "id": 17, + "id": 5, "options": { "colorMode": "background", "graphMode": "none", @@ -194,22 +160,71 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "", + "pluginVersion": "11.2.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "P18F45E9DC7E75912" }, - "editorMode": "code", - "expr": "avg(page_total_time)", - "format": "table", - "intervalFactor": 3, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "dbrepo_view_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "Views", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "dbrepo_subset_count", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "Subsets", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "dbrepo_table_count", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, "legendFormat": "__auto", "range": true, - "refId": "B", - "step": 15, - "target": "dev.grafana.cb-office.alerting.active_alerts" + "refId": "Tables", + "useBackend": false + } + ], + "title": "Datasources", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "", + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } } ], "type": "stat" @@ -220,9 +235,9 @@ "type": "prometheus", "uid": "P18F45E9DC7E75912" }, + "description": "", "fieldConfig": { "defaults": { - "displayName": "Databases", "mappings": [], "thresholds": { "mode": "absolute", @@ -233,7 +248,7 @@ } ] }, - "unit": "short" + "unit": "decbytes" }, "overrides": [] }, @@ -243,7 +258,7 @@ "x": 8, "y": 1 }, - "id": 4, + "id": 8, "options": { "colorMode": "background", "graphMode": "none", @@ -261,7 +276,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "", + "pluginVersion": "11.2.2", "targets": [ { "datasource": { @@ -270,7 +285,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "dbrepo_database_count", + "expr": "dbrepo_volume_sum", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -280,38 +295,70 @@ "useBackend": false } ], + "title": "Data Volume", "type": "stat" }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 2, + "panels": [], + "title": "Services", + "type": "row" + }, { "datasource": { "default": true, "type": "prometheus", "uid": "P18F45E9DC7E75912" }, + "description": "Quality of Service", "fieldConfig": { "defaults": { - "displayName": "Datasources", "mappings": [], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { - "color": "blue", + "color": "purple", "value": null + }, + { + "color": "red", + "value": 0 + }, + { + "color": "orange", + "value": 60 + }, + { + "color": "#EAB839", + "value": 80 + }, + { + "color": "green", + "value": 100 } ] }, - "unit": "short" + "unit": "percent" }, "overrides": [] }, "gridPos": { - "h": 3, + "h": 4, "w": 4, - "x": 12, - "y": 1 + "x": 0, + "y": 5 }, - "id": 5, + "id": 9, "options": { "colorMode": "background", "graphMode": "none", @@ -329,7 +376,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "", + "pluginVersion": "11.2.2", "targets": [ { "datasource": { @@ -337,33 +384,92 @@ "uid": "P18F45E9DC7E75912" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "dbrepo_view_count", + "editorMode": "code", + "expr": "sum(up)*100/count(up)", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "Services Running", "range": true, - "refId": "Views", + "refId": "A", "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "P18F45E9DC7E75912" + } + ], + "title": "QoS", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "dbrepo_subset_count", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "Subsets", - "useBackend": false + "custom": { + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [ + { + "options": { + "0": { + "index": 0, + "text": "DOWN" + }, + "1": { + "index": 1, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 20, + "x": 4, + "y": 5 + }, + "id": 16, + "options": { + "colWidth": 0.9, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ { "datasource": { "type": "prometheus", @@ -371,42 +477,30 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "dbrepo_table_count", + "expr": "up", "fullMetaSearch": false, - "hide": false, "includeNullMetadata": true, "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{instance}}", "range": true, - "refId": "Tables", + "refId": "A", "useBackend": false } ], - "transformations": [ - { - "id": "calculateField", - "options": { - "alias": "", - "mode": "reduceRow", - "reduce": { - "reducer": "sum" - }, - "replaceFields": true - } - } - ], - "type": "stat" + "title": "Service QoS", + "type": "status-history" }, { "datasource": { - "default": true, "type": "prometheus", "uid": "P18F45E9DC7E75912" }, + "description": "Total used disk space in Storage Service", "fieldConfig": { "defaults": { - "displayName": "Volume", "mappings": [], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -421,12 +515,12 @@ "overrides": [] }, "gridPos": { - "h": 3, + "h": 4, "w": 4, - "x": 16, - "y": 1 + "x": 0, + "y": 9 }, - "id": 8, + "id": 17, "options": { "colorMode": "background", "graphMode": "none", @@ -444,7 +538,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "", + "pluginVersion": "11.2.2", "targets": [ { "datasource": { @@ -452,8 +546,8 @@ "uid": "P18F45E9DC7E75912" }, "disableTextWrap": false, - "editorMode": "builder", - "expr": "dbrepo_volume_sum", + "editorMode": "code", + "expr": "SeaweedFS_volumeServer_total_disk_size", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -463,21 +557,9 @@ "useBackend": false } ], + "title": "S3 Volume", "type": "stat" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 4 - }, - "id": 2, - "panels": [], - "title": "Services", - "type": "row" - }, { "datasource": { "type": "prometheus", @@ -486,7 +568,40 @@ "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -496,50 +611,97 @@ "color": "green", "value": null }, - { - "color": "#EAB839", - "value": 200 - }, - { - "color": "orange", - "value": 400 - }, { "color": "red", - "value": 600 + "value": 80 } ] }, - "unit": "ms" + "unit": "none" }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "auth-service:8080" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "data-service:8080" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metadata-service:8080" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metadata-service:80" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { - "h": 9, - "w": 24, + "h": 7, + "w": 12, "x": 0, - "y": 5 + "y": 13 }, - "id": 13, + "id": 6, "options": { - "displayMode": "gradient", - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" + "tooltip": { + "mode": "multi", + "sort": "none" + } }, - "pluginVersion": "", + "pluginVersion": "11.2.0", "targets": [ { "datasource": { @@ -548,23 +710,19 @@ }, "disableTextWrap": false, "editorMode": "builder", - "exemplar": false, - "expr": "page_total_time{path!=\"empty: empty\"}", - "format": "time_series", + "expr": "process_cpu_usage", "fullMetaSearch": false, + "hide": false, "includeNullMetadata": true, "instant": false, - "intervalFactor": 3, - "legendFormat": "{{path}}", + "legendFormat": "{{instance}}", "range": true, - "refId": "B", - "step": 15, - "target": "dev.grafana.cb-office.alerting.active_alerts", + "refId": "process_cpu_usage", "useBackend": false } ], - "title": "UI Response Time", - "type": "bargauge" + "title": "CPU Usage", + "type": "timeseries" }, { "datasource": { @@ -572,6 +730,7 @@ "type": "prometheus", "uid": "P18F45E9DC7E75912" }, + "description": "Heap and non-heap memory summed", "fieldConfig": { "defaults": { "color": { @@ -584,6 +743,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 25, "gradientMode": "none", @@ -592,9 +752,9 @@ "tooltip": false, "viz": false }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" @@ -616,13 +776,10 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "decbytes" }, "overrides": [ { @@ -643,13 +800,13 @@ { "matcher": { "id": "byName", - "options": "broker-service:15692" + "options": "data-service:8080" }, "properties": [ { "id": "color", "value": { - "fixedColor": "light-blue", + "fixedColor": "blue", "mode": "fixed" } } @@ -670,36 +827,6 @@ } ] }, - { - "matcher": { - "id": "byName", - "options": "analyse-service:80" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "auth-service-metrics:8080" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - }, { "matcher": { "id": "byName", @@ -714,61 +841,16 @@ } } ] - }, - { - "matcher": { - "id": "byName", - "options": "data-service:80" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "search-service:80" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "semi-dark-purple", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "ui:80" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-purple", - "mode": "fixed" - } - } - ] } ] }, "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 14 + "h": 7, + "w": 12, + "x": 12, + "y": 13 }, - "id": 1, + "id": 7, "options": { "legend": { "calcs": [], @@ -777,7 +859,7 @@ "showLegend": true }, "tooltip": { - "mode": "single", + "mode": "multi", "sort": "none" } }, @@ -790,10 +872,10 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "up", + "expr": "sum by(instance) (jvm_memory_used_bytes)", "fullMetaSearch": false, "hide": false, - "includeNullMetadata": true, + "includeNullMetadata": false, "instant": false, "legendFormat": "{{instance}}", "range": true, @@ -801,12 +883,11 @@ "useBackend": false } ], - "title": "Service Instances Running", + "title": "JVM Memory Usage", "type": "timeseries" }, { "datasource": { - "default": true, "type": "prometheus", "uid": "P18F45E9DC7E75912" }, @@ -822,6 +903,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 25, "gradientMode": "none", @@ -832,7 +914,7 @@ }, "insertNulls": false, "lineInterpolation": "smooth", - "lineWidth": 1, + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" @@ -841,7 +923,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "off" @@ -854,25 +936,22 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } + }, + "unit": "reqps" }, "overrides": [ { "matcher": { - "id": "byName", - "options": "auth-service:8080" + "id": "byRegexp", + "options": "/.*search-service.*/" }, "properties": [ { "id": "color", "value": { - "fixedColor": "yellow", + "fixedColor": "orange", "mode": "fixed" } } @@ -880,29 +959,123 @@ }, { "matcher": { - "id": "byName", - "options": "data-service:8080" + "id": "byRegexp", + "options": "/.*analyse-service.*/" }, "properties": [ { "id": "color", "value": { - "fixedColor": "blue", + "fixedColor": "super-light-orange", "mode": "fixed" } } ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 18, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "editorMode": "code", + "expr": "rate(flask_http_request_duration_seconds_count{status=~\"200|201|202\",path!=\"/health\"}[$__rate_interval])", + "instant": false, + "legendFormat": "{{method}} {{instance}} {{path}} ({{status}})", + "range": true, + "refId": "A" + } + ], + "title": "Successful API Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] }, + "unit": "reqps" + }, + "overrides": [ { "matcher": { - "id": "byName", - "options": "metadata-service:8080" + "id": "byRegexp", + "options": "/.*search-service.*/" }, "properties": [ { "id": "color", "value": { - "fixedColor": "purple", + "fixedColor": "orange", "mode": "fixed" } } @@ -910,14 +1083,14 @@ }, { "matcher": { - "id": "byName", - "options": "metadata-service:80" + "id": "byRegexp", + "options": "/.*analyse-service.*/" }, "properties": [ { "id": "color", "value": { - "fixedColor": "blue", + "fixedColor": "super-light-orange", "mode": "fixed" } } @@ -926,12 +1099,12 @@ ] }, "gridPos": { - "h": 6, - "w": 24, - "x": 0, + "h": 7, + "w": 12, + "x": 12, "y": 20 }, - "id": 6, + "id": 19, "options": { "legend": { "calcs": [], @@ -944,27 +1117,21 @@ "sort": "none" } }, - "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "P18F45E9DC7E75912" }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "process_cpu_usage", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": true, + "editorMode": "code", + "expr": "rate(flask_http_request_duration_seconds_count{status!~\"200|201|202\"}[$__rate_interval])", "instant": false, - "legendFormat": "{{instance}}", + "legendFormat": "{{method}} {{instance}} ({{status}})", "range": true, - "refId": "Java", - "useBackend": false + "refId": "A" } ], - "title": "CPU Usage", + "title": "Failed API Requests", "type": "timeseries" }, { @@ -973,7 +1140,6 @@ "type": "prometheus", "uid": "P18F45E9DC7E75912" }, - "description": "Heap and non-heap memory summed", "fieldConfig": { "defaults": { "color": { @@ -986,8 +1152,9 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 25, + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, @@ -1005,7 +1172,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "off" @@ -1020,20 +1187,19 @@ "value": null } ] - }, - "unit": "decbytes" + } }, "overrides": [ { "matcher": { "id": "byName", - "options": "auth-service:8080" + "options": "DELETE" }, "properties": [ { "id": "color", "value": { - "fixedColor": "yellow", + "fixedColor": "red", "mode": "fixed" } } @@ -1042,7 +1208,7 @@ { "matcher": { "id": "byName", - "options": "data-service:8080" + "options": "GET" }, "properties": [ { @@ -1057,7 +1223,7 @@ { "matcher": { "id": "byName", - "options": "metadata-service:8080" + "options": "HEAD" }, "properties": [ { @@ -1072,13 +1238,42 @@ { "matcher": { "id": "byName", - "options": "metadata-service:80" + "options": "PATCH" }, "properties": [ { "id": "color", "value": { - "fixedColor": "blue", + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "OPTIONS" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "POST" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", "mode": "fixed" } } @@ -1087,12 +1282,12 @@ ] }, "gridPos": { - "h": 6, - "w": 24, + "h": 7, + "w": 12, "x": 0, - "y": 26 + "y": 27 }, - "id": 7, + "id": 20, "options": { "legend": { "calcs": [], @@ -1105,7 +1300,6 @@ "sort": "none" } }, - "pluginVersion": "11.2.0", "targets": [ { "datasource": { @@ -1114,18 +1308,17 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "sum by(instance) (jvm_memory_used_bytes)", + "expr": "tusd_requests_total", "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, + "includeNullMetadata": true, "instant": false, - "legendFormat": "{{instance}}", + "legendFormat": "{{method}}", "range": true, "refId": "A", "useBackend": false } ], - "title": "JVM Memory Usage", + "title": "Storage Service Requests", "type": "timeseries" } ], diff --git a/dbrepo-dashboard-service/grafana.ini b/dbrepo-dashboard-service/grafana.ini index cc2f5d41a6..1f8d9c1ef3 100644 --- a/dbrepo-dashboard-service/grafana.ini +++ b/dbrepo-dashboard-service/grafana.ini @@ -1,4 +1,7 @@ [server] +protocol = http +domain = localhost +root_url = http://%(domain)s/dashboard/ http_port = 3000 [security] diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java index 133bee769c..4059a37a92 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java @@ -18,6 +18,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -88,7 +89,7 @@ public class AccessEndpoint { } try { accessService.create(database, user, data.getType()); - return ResponseEntity.accepted() + return ResponseEntity.status(HttpStatus.CREATED) .build(); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java index 87471c74c2..1637878a67 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java @@ -250,12 +250,7 @@ public class SubsetEndpoint { endpointValidator.validateDataParams(page, size); endpointValidator.validateForbiddenStatements(data.getStatement()); /* parameters */ - final UUID userId; - if (principal == null) { - userId = metadataServiceGateway.getSystemUserId(); - } else { - userId = UserUtil.getId(principal); - } + final UUID userId = principal != null ? UserUtil.getId(principal) : null; if (page == null) { page = 0L; log.debug("page not set: default to {}", page); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 4af577bed5..548558cafd 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -4,8 +4,7 @@ import at.tuwien.ExportResourceDto; import at.tuwien.api.database.DatabaseAccessDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.query.ImportCsvDto; -import at.tuwien.api.database.query.QueryDto; +import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.internal.PrivilegedTableDto; @@ -13,6 +12,7 @@ import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; +import at.tuwien.service.SchemaService; import at.tuwien.service.TableService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; @@ -33,7 +33,6 @@ import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -51,13 +50,15 @@ import java.util.List; public class TableEndpoint { private final TableService tableService; + private final SchemaService schemaService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public TableEndpoint(TableService tableService, EndpointValidator endpointValidator, + public TableEndpoint(TableService tableService, SchemaService schemaService, EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.tableService = tableService; + this.schemaService = schemaService; this.endpointValidator = endpointValidator; this.metadataServiceGateway = metadataServiceGateway; } @@ -107,8 +108,9 @@ public class TableEndpoint { /* create */ final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); try { + final TableDto table = tableService.createTable(database, data); return ResponseEntity.status(HttpStatus.CREATED) - .body(tableService.createTable(database, data)); + .body(schemaService.inspectTable(database, table.getInternalName())); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -614,7 +616,7 @@ public class TableEndpoint { }) public ResponseEntity<Void> importDataset(@NotBlank @PathVariable("databaseId") Long databaseId, @NotBlank @PathVariable("tableId") Long tableId, - @Valid @RequestBody ImportCsvDto data, + @Valid @RequestBody ImportDto data, @NotNull Principal principal) throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException, QueryMalformedException, StorageNotFoundException, SidecarImportException, NotAllowedException, @@ -623,10 +625,6 @@ public class TableEndpoint { final PrivilegedTableDto table = metadataServiceGateway.getTableById(databaseId, tableId); final DatabaseAccessDto access = metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), UserUtil.getId(principal)); - if (data.getNullElement() == null) { - data.setNullElement(""); - log.debug("null element not present, default to empty string"); - } if (data.getLineTermination() == null) { data.setLineTermination("\\r\\n"); log.debug("line termination not present, default to {}", data.getLineTermination()); diff --git a/dbrepo-data-service/rest-service/src/main/resources/init/querystore.sql b/dbrepo-data-service/rest-service/src/main/resources/init/querystore.sql index c1df44d1b0..3e7471df3e 100644 --- a/dbrepo-data-service/rest-service/src/main/resources/init/querystore.sql +++ b/dbrepo-data-service/rest-service/src/main/resources/init/querystore.sql @@ -1,5 +1,5 @@ CREATE SEQUENCE `qs_queries_seq` NOCACHE; -CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(36) not null, `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint ) WITH SYSTEM VERSIONING; +CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(36), `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint ) WITH SYSTEM VERSIONING; CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255), OUT count BIGINT) BEGIN DECLARE _sql TEXT; SELECT CONCAT('SELECT SHA2(GROUP_CONCAT(CONCAT_WS(\'\',', GROUP_CONCAT(CONCAT('`', column_name, '`') ORDER BY column_name), ') SEPARATOR \',\'), 256) AS hash, COUNT(*) AS count FROM `', name, '` INTO @hash, @count;') FROM `information_schema`.`columns` WHERE `table_schema` = DATABASE() AND `table_name` = name INTO _sql; PREPARE stmt FROM _sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET hash = @hash; SET count = @count; END; CREATE PROCEDURE store_query(IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _username varchar(255) DEFAULT REGEXP_REPLACE(current_user(), '@.*', ''); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); DROP TABLE IF EXISTS `_tmp`; IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END; CREATE DEFINER = 'root' PROCEDURE _store_query(IN _username VARCHAR(255), IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); DROP TABLE IF EXISTS `_tmp`; IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END; \ No newline at end of file diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java index b0c332a63b..691d96006b 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java @@ -6,13 +6,11 @@ import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.querystore.Query; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import java.io.IOException; import java.sql.*; import java.time.Instant; import java.util.*; diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java index 3beb5626b3..a2cbae3ea8 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java @@ -15,12 +15,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.sql.SQLException; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @Log4j2 @@ -54,7 +56,9 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { .thenReturn(USER_4_PRIVILEGED_DTO); /* test */ - accessEndpoint.create(DATABASE_1_ID, USER_4_ID, UPDATE_DATABASE_ACCESS_READ_DTO); + final ResponseEntity<Void> response = accessEndpoint.create(DATABASE_1_ID, USER_4_ID, UPDATE_DATABASE_ACCESS_READ_DTO); + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertNull(response.getBody()); } @Test @@ -150,7 +154,9 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { .thenReturn(USER_1_DTO); /* test */ - accessEndpoint.update(DATABASE_1_ID, USER_1_ID, UPDATE_DATABASE_ACCESS_READ_DTO); + final ResponseEntity<Void> response = accessEndpoint.update(DATABASE_1_ID, USER_1_ID, UPDATE_DATABASE_ACCESS_READ_DTO); + assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); + assertNull(response.getBody()); } @Test @@ -250,7 +256,9 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { .delete(any(PrivilegedDatabaseDto.class), any(UserDto.class)); /* test */ - accessEndpoint.revoke(DATABASE_1_ID, USER_1_ID); + final ResponseEntity<Void> response = accessEndpoint.revoke(DATABASE_1_ID, USER_1_ID); + assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); + assertNull(response.getBody()); } @Test diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java index 358a04008f..9ab7082d0e 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java @@ -368,9 +368,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(metadataServiceGateway.getSystemUserId()) - .thenReturn(USER_LOCAL_ADMIN_ID); - when(subsetService.execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(USER_LOCAL_ADMIN_ID), eq(0L), eq(10L), eq(null), eq(null))) + when(subsetService.execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(null), eq(0L), eq(10L), eq(null), eq(null))) .thenReturn(QUERY_5_RESULT_DTO); /* test */ diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java index f03f4c3f18..cc4b957226 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java @@ -2,13 +2,14 @@ package at.tuwien.endpoint; import at.tuwien.ExportResourceDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.query.ImportCsvDto; +import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.endpoints.TableEndpoint; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; +import at.tuwien.service.SchemaService; import at.tuwien.service.TableService; import at.tuwien.test.AbstractUnitTest; import jakarta.servlet.http.HttpServletRequest; @@ -54,6 +55,9 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @MockBean private TableService tableService; + @MockBean + private SchemaService schemaService; + @MockBean private MetadataServiceGateway metadataServiceGateway; @@ -89,6 +93,8 @@ public class TableEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_PRIVILEGED_DTO); when(tableService.createTable(DATABASE_1_PRIVILEGED_DTO, TABLE_4_CREATE_INTERNAL_DTO)) .thenReturn(TABLE_4_DTO); + when(schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, TABLE_4_INTERNALNAME)) + .thenReturn(TABLE_4_DTO); /* test */ final ResponseEntity<TableDto> response = tableEndpoint.create(DATABASE_1_ID, TABLE_4_CREATE_INTERNAL_DTO); @@ -1261,7 +1267,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_succeeds() throws DatabaseUnavailableException, TableNotFoundException, SidecarImportException, NotAllowedException, QueryMalformedException, RemoteUnavailableException, StorageNotFoundException, SQLException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination(null) .location("deadbeef") @@ -1287,7 +1293,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_4_USERNAME) public void importDataset_noRole_fails() { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1303,7 +1309,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_3_USERNAME, authorities = {"insert-table-data"}) public void importDataset_tableNotFound_fails() throws TableNotFoundException, RemoteUnavailableException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1325,7 +1331,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_unavailable_fails() throws RemoteUnavailableException, SidecarImportException, SQLException, QueryMalformedException, StorageNotFoundException, TableNotFoundException, MetadataServiceException, NotAllowedException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1351,7 +1357,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_writeOwnAccess_fails() throws RemoteUnavailableException, SidecarImportException, SQLException, QueryMalformedException, StorageNotFoundException, TableNotFoundException, MetadataServiceException, NotAllowedException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1376,7 +1382,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_3_USERNAME, authorities = {"insert-table-data"}) public void importDataset_readAccess_fails() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1399,7 +1405,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_writeOwnAccess_succeeds() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, DatabaseUnavailableException, SidecarImportException, QueryMalformedException, StorageNotFoundException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1419,7 +1425,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_3_USERNAME, authorities = {"insert-table-data"}) public void importDataset_writeOwnAccessForeign_fails() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1442,7 +1448,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_writeAllAccessForeign_succeeds() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, DatabaseUnavailableException, SidecarImportException, QueryMalformedException, StorageNotFoundException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1463,7 +1469,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_privateForeign_succeeds() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, DatabaseUnavailableException, SidecarImportException, QueryMalformedException, StorageNotFoundException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1484,7 +1490,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { public void importDataset_private_succeeds() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, DatabaseUnavailableException, SidecarImportException, QueryMalformedException, StorageNotFoundException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1504,7 +1510,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_2_USERNAME, authorities = {"insert-table-data"}) public void importDataset_privateForeign_fails() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") @@ -1526,7 +1532,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_2_USERNAME, authorities = {"insert-table-data"}) public void importDataset_privateReadAccess_fails() throws TableNotFoundException, RemoteUnavailableException, NotAllowedException, MetadataServiceException { - final ImportCsvDto request = ImportCsvDto.builder() + final ImportDto request = ImportDto.builder() .skipLines(1L) .lineTermination("\\n") .location("deadbeef") diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java index c224af4cb2..44b01b1e9e 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java @@ -175,8 +175,12 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { MetadataServiceException { /* mock */ + final HttpHeaders headers = new HttpHeaders(); + headers.set("X-Username", CONTAINER_1_PRIVILEGED_USERNAME); + headers.set("X-Password", CONTAINER_1_PRIVILEGED_PASSWORD); when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto[].class))) .thenReturn(ResponseEntity.status(HttpStatus.OK) + .headers(headers) .body(new PrivilegedDatabaseDto[]{DATABASE_1_PRIVILEGED_DTO})); /* test */ @@ -221,7 +225,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { .build()); /* test */ - assertThrows(MetadataServiceException.class, () -> { + assertThrows(DatabaseNotFoundException.class, () -> { metadataServiceGateway.getDatabaseByInternalName(DATABASE_1_INTERNALNAME); }); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java index b331a03fa6..f4bd429a90 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java @@ -1,7 +1,7 @@ package at.tuwien.mvc; import at.tuwien.api.database.query.ExecuteStatementDto; -import at.tuwien.api.database.query.ImportCsvDto; +import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryPersistDto; import at.tuwien.api.database.table.TupleDeleteDto; import at.tuwien.api.database.table.TupleDto; @@ -201,7 +201,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* ignore */ } try { - tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, ImportCsvDto.builder().build(), USER_1_PRINCIPAL); + tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, ImportDto.builder().build(), USER_1_PRINCIPAL); } catch (Exception e) { /* ignore */ } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java index d69ea86e68..85e2b80711 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java @@ -1,6 +1,5 @@ package at.tuwien.service; -import at.tuwien.api.container.image.ImageDateDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.table.TableBriefDto; @@ -16,7 +15,8 @@ import at.tuwien.api.database.table.constraints.unique.UniqueDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; -import at.tuwien.exception.*; +import at.tuwien.exception.TableNotFoundException; +import at.tuwien.exception.ViewNotFoundException; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; @@ -74,11 +74,11 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns = response.getColumns(); assertNotNull(columns); assertEquals(5, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null, null); - assertColumn(columns.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null, null); - assertColumn(columns.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null, null); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null); + assertColumn(columns.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null); final ConstraintsDto constraints = response.getConstraints(); assertNotNull(constraints); final Set<PrimaryKeyDto> primaryKey = constraints.getPrimaryKey(); @@ -127,11 +127,11 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns = response.getColumns(); assertNotNull(columns); assertEquals(3, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_2_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns.get(1), null, null, DATABASE_2_ID, "mode", "mode", ColumnTypeDto.ENUM, 3L, null, false, null, null); + assertColumn(columns.get(0), null, null, DATABASE_2_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_2_ID, "mode", "mode", ColumnTypeDto.ENUM, 3L, null, false, null); assertEquals(2, columns.get(1).getEnums().size()); assertEquals(List.of("ABC", "DEF"), columns.get(1).getEnums()); - assertColumn(columns.get(2), null, null, DATABASE_2_ID, "seq", "seq", ColumnTypeDto.SET, 5L, null, true, null, null); + assertColumn(columns.get(2), null, null, DATABASE_2_ID, "seq", "seq", ColumnTypeDto.SET, 5L, null, true, null); assertEquals(3, columns.get(2).getSets().size()); assertEquals(List.of("1", "2", "3"), columns.get(2).getSets()); /* ignore rest (constraints) */ @@ -167,11 +167,11 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns = response.getColumns(); assertNotNull(columns); assertEquals(5, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "date", "date", ColumnTypeDto.DATE, null, null, false, IMAGE_DATE_1_ID, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "location", "location", ColumnTypeDto.VARCHAR, 255L, null, true, null, "Closest city"); - assertColumn(columns.get(3), null, null, DATABASE_1_ID, "mintemp", "mintemp", ColumnTypeDto.DOUBLE, 22L, null, true, null, null); - assertColumn(columns.get(4), null, null, DATABASE_1_ID, "rainfall", "rainfall", ColumnTypeDto.DOUBLE, 22L, null, true, null, null); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "date", "date", ColumnTypeDto.DATE, null, null, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "location", "location", ColumnTypeDto.VARCHAR, 255L, null, true, "Closest city"); + assertColumn(columns.get(3), null, null, DATABASE_1_ID, "mintemp", "mintemp", ColumnTypeDto.DOUBLE, 22L, null, true, null); + assertColumn(columns.get(4), null, null, DATABASE_1_ID, "rainfall", "rainfall", ColumnTypeDto.DOUBLE, 22L, null, true, null); final ConstraintsDto constraints = response.getConstraints(); final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); assertEquals(1, primaryKey.size()); @@ -359,9 +359,9 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { assertEquals(ColumnTypeDto.BOOL, pk0.getColumn().getColumnType()); final List<ColumnDto> columns = response.getColumns(); assertEquals(3, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null, null); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null); } @Test @@ -398,9 +398,9 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { assertEquals(DATABASE_1_ID, column3.getDatabaseId()); } - protected static void assertViewColumn(ViewColumnDto column, Long id, Long databaseId, String name, String internalName, - ColumnTypeDto type, Long size, Long d, Boolean nullAllowed, - ImageDateDto dateFormat, String description) { + protected static void assertViewColumn(ViewColumnDto column, Long id, Long databaseId, String name, + String internalName, ColumnTypeDto type, Long size, Long d, + Boolean nullAllowed, String description) { log.trace("assert column: {}", internalName); assertNotNull(column); assertEquals(id, column.getId()); @@ -412,17 +412,11 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { assertEquals(d, column.getD()); assertEquals(nullAllowed, column.getIsNullAllowed()); assertEquals(description, column.getDescription()); - if (dateFormat != null) { - assertNotNull(column.getDateFormat()); - assertEquals(dateFormat.getId(), column.getDateFormat().getId()); - } else { - assertNull(column.getDateFormat()); - } } protected static void assertColumn(ColumnDto column, Long id, Long tableId, Long databaseId, String name, String internalName, ColumnTypeDto type, Long size, Long d, Boolean nullAllowed, - Long dfid, String description) { + String description) { log.trace("assert column: {}", internalName); assertNotNull(column); assertEquals(id, column.getId()); @@ -437,12 +431,6 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { assertEquals(d, column.getD()); assertEquals(nullAllowed, column.getIsNullAllowed()); assertEquals(description, column.getDescription()); - if (dfid != null) { - assertNotNull(column.getDateFormat()); - assertEquals(dfid, column.getDateFormat().getId()); - } else { - assertNull(column.getDateFormat()); - } } } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java index 4ebaba4931..f21fc0e539 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java @@ -1,7 +1,7 @@ package at.tuwien.service; import at.tuwien.ExportResourceDto; -import at.tuwien.api.database.query.ImportCsvDto; +import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.columns.ColumnCreateDto; @@ -19,7 +19,6 @@ import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; import at.tuwien.config.QueryConfig; -import at.tuwien.config.S3Config; import at.tuwien.exception.*; import at.tuwien.gateway.DataDatabaseSidecarGateway; import at.tuwien.gateway.MetadataServiceGateway; @@ -45,15 +44,11 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; -import java.nio.charset.Charset; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermissions; import java.sql.SQLException; import java.time.Instant; import java.util.*; import static at.tuwien.service.SchemaServiceIntegrationTest.assertColumn; -import static at.tuwien.service.SchemaServiceIntegrationTest.assertViewColumn; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.doNothing; @@ -383,9 +378,9 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns0 = table0.getColumns(); assertNotNull(columns0); Assertions.assertEquals(3, columns0.size()); - assertColumn(columns0.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns0.get(1), null, null, DATABASE_1_ID, "weather_id", "weather_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns0.get(2), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); + assertColumn(columns0.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns0.get(1), null, null, DATABASE_1_ID, "weather_id", "weather_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns0.get(2), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); final ConstraintsDto constraints0 = table0.getConstraints(); assertNotNull(constraints0); assertEquals(1, constraints0.getPrimaryKey().size()); @@ -430,8 +425,8 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns1 = table1.getColumns(); assertNotNull(columns1); Assertions.assertEquals(2, columns1.size()); - assertColumn(columns1.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns1.get(1), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); + assertColumn(columns1.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns1.get(1), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); final ConstraintsDto constraints1 = table1.getConstraints(); assertNotNull(constraints1); assertEquals(2, constraints1.getPrimaryKey().size()); @@ -458,9 +453,9 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns2 = table2.getColumns(); assertNotNull(columns2); Assertions.assertEquals(3, columns2.size()); - assertColumn(columns2.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null, null); - assertColumn(columns2.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null, null); - assertColumn(columns2.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null, null); + assertColumn(columns2.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns2.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns2.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null); final ConstraintsDto constraints2 = table2.getConstraints(); assertNotNull(constraints2); final Set<PrimaryKeyDto> primaryKey2 = constraints2.getPrimaryKey(); @@ -479,11 +474,11 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { final List<ColumnDto> columns3 = table3.getColumns(); assertNotNull(columns3); Assertions.assertEquals(5, columns3.size()); - assertColumn(columns3.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null, null); - assertColumn(columns3.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null, null); - assertColumn(columns3.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null, null); - assertColumn(columns3.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null, null); - assertColumn(columns3.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null, null); + assertColumn(columns3.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns3.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns3.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null); + assertColumn(columns3.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns3.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null); final ConstraintsDto constraints3 = table3.getConstraints(); assertNotNull(constraints3); final Set<PrimaryKeyDto> primaryKey3 = constraints3.getPrimaryKey(); @@ -509,8 +504,8 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { assertEquals(TABLE_4_INTERNALNAME, response.getInternalName()); final List<ColumnDto> columns = response.getColumns(); assertEquals(TABLE_4_COLUMNS.size(), columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "timestamp", "timestamp", ColumnTypeDto.TIMESTAMP, null, null, false, queryConfig.getDefaultTimestampFormatId(), null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "value", "value", ColumnTypeDto.DECIMAL, 10L, 10L, true, null, null); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "timestamp", "timestamp", ColumnTypeDto.TIMESTAMP, null, null, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "value", "value", ColumnTypeDto.DECIMAL, 10L, 10L, true, null); final ConstraintsDto constraints = response.getConstraints(); assertNotNull(constraints); final Set<PrimaryKeyDto> primaryKey = constraints.getPrimaryKey(); @@ -619,9 +614,9 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { assertEquals("composite_primary_key", response.getInternalName()); final List<ColumnDto> columns = response.getColumns(); assertEquals(3, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "name", "name", ColumnTypeDto.VARCHAR, 255L, null, false, null, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "lat", "lat", ColumnTypeDto.DECIMAL, 10L, 10L, false, null, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "lng", "lng", ColumnTypeDto.DECIMAL, 10L, 10L, false, null, null); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "name", "name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "lat", "lat", ColumnTypeDto.DECIMAL, 10L, 10L, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "lng", "lng", ColumnTypeDto.DECIMAL, 10L, 10L, false, null); final ConstraintsDto constraints = response.getConstraints(); assertNotNull(constraints); final Set<String> checks = constraints.getChecks(); @@ -768,53 +763,6 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { }); } - @Test - public void importDataset_withSeparatorAndQuoteAndNullElement_succeeds() throws SidecarImportException, - SQLException, QueryMalformedException, RemoteUnavailableException, StorageNotFoundException, IOException { - final ImportCsvDto request = ImportCsvDto.builder() - .location("weather_aus.csv") - .separator(';') - .quote('"') - .nullElement("NA") - .build(); - - /* mock */ - final File source = new File("src/test/resources/csv/weather_aus.csv"); - final File target = new File("/tmp/weather_aus.csv") /* must be /tmp */; - log.trace("copy dataset from {} to {}", source.toPath().toAbsolutePath(), target.toPath().toAbsolutePath()); - FileUtils.copyFile(source, target); - doNothing() - .when(dataDatabaseSidecarGateway) - .importFile(anyString(), anyInt(), eq("weather_aus.csv")); - - /* test */ - tableService.importDataset(TABLE_1_PRIVILEGED_DTO, request); - } - - @Test - public void importDataset_malformedData_fails() throws RemoteUnavailableException, StorageNotFoundException, - IOException, SidecarImportException { - final ImportCsvDto request = ImportCsvDto.builder() - .location("weather_aus.csv") - .separator(';') - .quote('"') - .build(); - - /* mock */ - final File source = new File("src/test/resources/csv/weather_aus.csv"); - final File target = new File("/tmp/weather_aus.csv"); - log.trace("copy dataset from {} to {}", source.toPath().toAbsolutePath(), target.toPath().toAbsolutePath()); - FileUtils.copyFile(source, target); - doNothing() - .when(dataDatabaseSidecarGateway) - .importFile(anyString(), anyInt(), eq("weather_aus.csv")); - - /* test */ - assertThrows(QueryMalformedException.class, () -> { - tableService.importDataset(TABLE_1_PRIVILEGED_DTO, request); - }); - } - @Test public void exportDataset_succeeds() throws SQLException, QueryMalformedException, RemoteUnavailableException, StorageNotFoundException, StorageUnavailableException, SidecarExportException { diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java index 20a769f8ce..5f20464eb9 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java @@ -3,7 +3,6 @@ package at.tuwien.service; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.query.QueryResultDto; -import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; import at.tuwien.exception.*; @@ -24,7 +23,6 @@ import java.time.Instant; import java.util.List; import java.util.Map; -import static at.tuwien.service.SchemaServiceIntegrationTest.assertViewColumn; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -68,11 +66,11 @@ public class ViewServiceIntegrationTest extends AbstractUnitTest { final List<ViewColumnDto> columns = response.getColumns(); assertEquals(VIEW_1_COLUMNS.size(), columns.size()); ViewColumnDto ref = VIEW_1_COLUMNS_DTO.get(0); - SchemaServiceIntegrationTest.assertViewColumn(columns.get(0), null, ref.getDatabaseId(), ref.getName(), ref.getInternalName(), ref.getColumnType(), ref.getSize(), ref.getD(), ref.getIsNullAllowed(), ref.getDateFormat(), ref.getDescription()); + SchemaServiceIntegrationTest.assertViewColumn(columns.get(0), null, ref.getDatabaseId(), ref.getName(), ref.getInternalName(), ref.getColumnType(), ref.getSize(), ref.getD(), ref.getIsNullAllowed(), ref.getDescription()); ref = VIEW_1_COLUMNS_DTO.get(1); - SchemaServiceIntegrationTest.assertViewColumn(columns.get(1), null, ref.getDatabaseId(), ref.getName(), ref.getInternalName(), ref.getColumnType(), ref.getSize(), ref.getD(), ref.getIsNullAllowed(), ref.getDateFormat(), ref.getDescription()); + SchemaServiceIntegrationTest.assertViewColumn(columns.get(1), null, ref.getDatabaseId(), ref.getName(), ref.getInternalName(), ref.getColumnType(), ref.getSize(), ref.getD(), ref.getIsNullAllowed(), ref.getDescription()); ref = VIEW_1_COLUMNS_DTO.get(2); - SchemaServiceIntegrationTest.assertViewColumn(columns.get(2), null, ref.getDatabaseId(), ref.getName(), ref.getInternalName(), ref.getColumnType(), ref.getSize(), ref.getD(), ref.getIsNullAllowed(), ref.getDateFormat(), ref.getDescription()); + SchemaServiceIntegrationTest.assertViewColumn(columns.get(2), null, ref.getDatabaseId(), ref.getName(), ref.getInternalName(), ref.getColumnType(), ref.getSize(), ref.getD(), ref.getIsNullAllowed(), ref.getDescription()); } diff --git a/dbrepo-data-service/rest-service/src/test/resources/init/querystore.sql b/dbrepo-data-service/rest-service/src/test/resources/init/querystore.sql index c1df44d1b0..3e7471df3e 100644 --- a/dbrepo-data-service/rest-service/src/test/resources/init/querystore.sql +++ b/dbrepo-data-service/rest-service/src/test/resources/init/querystore.sql @@ -1,5 +1,5 @@ CREATE SEQUENCE `qs_queries_seq` NOCACHE; -CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(36) not null, `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint ) WITH SYSTEM VERSIONING; +CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(36), `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint ) WITH SYSTEM VERSIONING; CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255), OUT count BIGINT) BEGIN DECLARE _sql TEXT; SELECT CONCAT('SELECT SHA2(GROUP_CONCAT(CONCAT_WS(\'\',', GROUP_CONCAT(CONCAT('`', column_name, '`') ORDER BY column_name), ') SEPARATOR \',\'), 256) AS hash, COUNT(*) AS count FROM `', name, '` INTO @hash, @count;') FROM `information_schema`.`columns` WHERE `table_schema` = DATABASE() AND `table_name` = name INTO _sql; PREPARE stmt FROM _sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET hash = @hash; SET count = @count; END; CREATE PROCEDURE store_query(IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _username varchar(255) DEFAULT REGEXP_REPLACE(current_user(), '@.*', ''); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); DROP TABLE IF EXISTS `_tmp`; IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END; CREATE DEFINER = 'root' PROCEDURE _store_query(IN _username VARCHAR(255), IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); DROP TABLE IF EXISTS `_tmp`; IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END; \ No newline at end of file diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java index 640ef7172a..11a90afde7 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java @@ -66,8 +66,11 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { log.error("Failed to find container with id {}: service responded unsuccessful: {}", containerId, response.getStatusCode()); throw new MetadataServiceException("Failed to find container: service responded unsuccessful: " + response.getStatusCode()); } - if (!response.getHeaders().keySet().containsAll(List.of("X-Username", "X-Password"))) { + final List<String> expectedHeaders = List.of("X-Username", "X-Password"); + if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { log.error("Failed to find all privileged container headers"); + log.debug("expected headers: {}", expectedHeaders); + log.debug("found headers: {}", response.getHeaders().keySet()); throw new MetadataServiceException("Failed to find all privileged container headers"); } if (response.getBody() == null) { @@ -98,8 +101,11 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { log.error("Failed to find database with id {}: service responded unsuccessful: {}", id, response.getStatusCode()); throw new MetadataServiceException("Failed to find database: service responded unsuccessful: " + response.getStatusCode()); } - if (!response.getHeaders().keySet().containsAll(List.of("X-Username", "X-Password"))) { + final List<String> expectedHeaders = List.of("X-Username", "X-Password"); + if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { log.error("Failed to find all privileged database headers"); + log.debug("expected headers: {}", expectedHeaders); + log.debug("found headers: {}", response.getHeaders().keySet()); throw new MetadataServiceException("Failed to find all privileged database headers"); } if (response.getBody() == null) { @@ -123,14 +129,22 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { log.error("Failed to find database with internal name {}: {}", internalName, e.getMessage()); throw new RemoteUnavailableException("Failed to find database: " + e.getMessage(), e); } - if (!response.getStatusCode().equals(HttpStatus.OK) || response.getBody() == null) { + if (!response.getStatusCode().equals(HttpStatus.OK)) { log.error("Failed to find database with internal name {}: service responded unsuccessful: {}", internalName, response.getStatusCode()); throw new MetadataServiceException("Failed to find database: service responded unsuccessful: " + response.getStatusCode()); } - if (response.getBody().length != 1) { + /* body first, then headers next */ + if (response.getBody() == null || response.getBody().length != 1) { log.error("Failed to find database with internal name {}: body is empty", internalName); throw new DatabaseNotFoundException("Failed to find database: body is empty"); } + final List<String> expectedHeaders = List.of("X-Username", "X-Password"); + if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { + log.error("Failed to find all privileged database headers"); + log.debug("expected headers: {}", expectedHeaders); + log.debug("found headers: {}", response.getHeaders().keySet()); + throw new MetadataServiceException("Failed to find all privileged database headers"); + } return response.getBody()[0]; } @@ -151,8 +165,11 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { log.error("Failed to find table with id {}: service responded unsuccessful: {}", id, response.getStatusCode()); throw new MetadataServiceException("Failed to find table: service responded unsuccessful: " + response.getStatusCode()); } - if (!response.getHeaders().keySet().containsAll(List.of("X-Type", "X-Host", "X-Port", "X-Username", "X-Password", "X-Database", "X-Sidecar-Host", "X-Sidecar-Port"))) { + final List<String> expectedHeaders = List.of("X-Type", "X-Host", "X-Port", "X-Username", "X-Password", "X-Database", "X-Sidecar-Host", "X-Sidecar-Port"); + if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { log.error("Failed to find all privileged table headers"); + log.debug("expected headers: {}", expectedHeaders); + log.debug("found headers: {}", response.getHeaders().keySet()); throw new MetadataServiceException("Failed to find all privileged table headers"); } if (response.getBody() == null) { @@ -189,8 +206,11 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { log.error("Failed to find view with id {}: service responded unsuccessful: {}", id, response.getStatusCode()); throw new MetadataServiceException("Failed to find view: service responded unsuccessful: " + response.getStatusCode()); } - if (!response.getHeaders().keySet().containsAll(List.of("X-Type", "X-Host", "X-Port", "X-Username", "X-Password", "X-Database"))) { + final List<String> expectedHeaders = List.of("X-Type", "X-Host", "X-Port", "X-Username", "X-Password", "X-Database"); + if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { log.error("Failed to find all privileged view headers"); + log.debug("expected headers: {}", expectedHeaders); + log.debug("found headers: {}", response.getHeaders().keySet()); throw new MetadataServiceException("Failed to find all privileged view headers"); } if (response.getBody() == null) { @@ -276,8 +296,11 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { log.error("Failed to find user with id {}: service responded unsuccessful: {}", userId, response.getStatusCode()); throw new MetadataServiceException("Failed to find user: service responded unsuccessful: " + response.getStatusCode()); } - if (!response.getHeaders().keySet().containsAll(List.of("X-Username", "X-Password"))) { + final List<String> expectedHeaders = List.of("X-Username", "X-Password"); + if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { log.error("Failed to find all privileged user headers"); + log.debug("expected headers: {}", expectedHeaders); + log.debug("found headers: {}", response.getHeaders().keySet()); throw new MetadataServiceException("Failed to find all privileged user headers"); } if (response.getBody() == null) { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java index b69d06b0aa..01854e0cb7 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java @@ -1,6 +1,5 @@ package at.tuwien.mapper; -import at.tuwien.api.container.image.ImageDateDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; @@ -45,7 +44,6 @@ import java.io.StringReader; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.sql.*; -import java.sql.Date; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -53,7 +51,6 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; @Mapper(componentModel = "spring") public interface DataMapper { @@ -177,19 +174,6 @@ public interface DataMapper { } else if (resultSet.getString(6) != null) { column.setSize(resultSet.getLong(6)); } - if (column.getColumnType().equals(ColumnTypeDto.TIMESTAMP) || column.getColumnType().equals(ColumnTypeDto.DATETIME)) { - column.setDateFormat(ImageDateDto.builder() - .id(queryConfig.getDefaultTimestampFormatId()) - .build()); - } else if (column.getColumnType().equals(ColumnTypeDto.DATE)) { - column.setDateFormat(ImageDateDto.builder() - .id(queryConfig.getDefaultDateFormatId()) - .build()); - } else if (column.getColumnType().equals(ColumnTypeDto.TIME)) { - column.setDateFormat(ImageDateDto.builder() - .id(queryConfig.getDefaultTimeFormatId()) - .build()); - } /* constraints */ if (resultSet.getString(9) != null && resultSet.getString(9).equals("PRI")) { table.getConstraints().getPrimaryKey().add(PrimaryKeyDto.builder() @@ -221,19 +205,6 @@ public interface DataMapper { } else if (resultSet.getString(6) != null) { column.setSize(resultSet.getLong(6)); } - if (column.getColumnType().equals(ColumnTypeDto.TIMESTAMP) || column.getColumnType().equals(ColumnTypeDto.DATETIME)) { - column.setDateFormat(ImageDateDto.builder() - .id(queryConfig.getDefaultTimestampFormatId()) - .build()); - } else if (column.getColumnType().equals(ColumnTypeDto.DATE)) { - column.setDateFormat(ImageDateDto.builder() - .id(queryConfig.getDefaultDateFormatId()) - .build()); - } else if (column.getColumnType().equals(ColumnTypeDto.TIME)) { - column.setDateFormat(ImageDateDto.builder() - .id(queryConfig.getDefaultTimeFormatId()) - .build()); - } view.getColumns() .add(column); log.trace("parsed view {}.{} column: {}", view.getDatabase().getInternalName(), view.getInternalName(), column.getInternalName()); @@ -562,10 +533,6 @@ public interface DataMapper { } switch (column.getColumnType()) { case DATE -> { - if (column.getDateFormat() == null) { - log.error("Missing date format for column {}", column.getId()); - throw new IllegalArgumentException("Missing date format"); - } final DateTimeFormatter formatter = new DateTimeFormatterBuilder() .parseCaseInsensitive() /* case insensitive to parse JAN and FEB */ .appendPattern("yyyy-MM-dd") @@ -575,10 +542,6 @@ public interface DataMapper { .toInstant(); } case TIMESTAMP, DATETIME -> { - if (column.getDateFormat() == null) { - log.error("Missing date format for column {}", column.getId()); - throw new IllegalArgumentException("Missing date format"); - } return Timestamp.valueOf(data.toString()) .toInstant(); } @@ -678,7 +641,7 @@ public interface DataMapper { ps.setNull(idx, Types.DATE); break; } - ps.setDate(idx, Date.valueOf(String.valueOf(value))); + ps.setString(idx, String.valueOf(value)); break; case BIGINT: if (value == null) { @@ -743,28 +706,7 @@ public interface DataMapper { } ps.setBoolean(idx, Boolean.parseBoolean(String.valueOf(value))); break; - case TIMESTAMP: - if (value == null) { - ps.setNull(idx, Types.TIMESTAMP); - break; - } - ps.setTimestamp(idx, Timestamp.valueOf(String.valueOf(value))); - break; - case DATETIME: - if (value == null) { - ps.setNull(idx, Types.TIMESTAMP); - break; - } - ps.setTimestamp(idx, Timestamp.valueOf(String.valueOf(value))); - break; - case TIME: - if (value == null) { - ps.setNull(idx, Types.TIME); - break; - } - ps.setTime(idx, Time.valueOf(String.valueOf(value))); - break; - case YEAR: + case TIME, DATETIME, TIMESTAMP, YEAR: if (value == null) { ps.setNull(idx, Types.TIME); break; diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java index d870215771..0e0a94f427 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java @@ -1,22 +1,27 @@ package at.tuwien.mapper; -import at.tuwien.api.database.query.ImportCsvDto; -import at.tuwien.api.database.table.*; -import at.tuwien.api.database.table.columns.*; +import at.tuwien.api.database.query.ImportDto; +import at.tuwien.api.database.table.TupleDeleteDto; +import at.tuwien.api.database.table.TupleDto; +import at.tuwien.api.database.table.TupleUpdateDto; +import at.tuwien.api.database.table.columns.ColumnCreateDto; +import at.tuwien.api.database.table.columns.ColumnDto; +import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.exception.*; +import at.tuwien.exception.QueryMalformedException; +import at.tuwien.exception.TableMalformedException; import at.tuwien.utils.MariaDbUtil; import org.mapstruct.Mapper; import org.mapstruct.Named; -import java.io.*; -import java.math.BigInteger; -import java.sql.*; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.sql.Date; +import java.sql.*; import java.text.Normalizer; -import java.time.*; +import java.time.Instant; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -548,7 +553,7 @@ public interface MariaDbMapper { return statement.toString(); } - default String datasetToRawInsertQuery(String databaseName, PrivilegedTableDto table, ImportCsvDto data) { + default String datasetToRawInsertQuery(String databaseName, PrivilegedTableDto table, ImportDto data) { final StringBuilder statement = new StringBuilder("LOAD DATA INFILE '") .append(data.getLocation()) .append("' REPLACE INTO TABLE `") @@ -563,11 +568,17 @@ public interface MariaDbMapper { .append(data.getQuote()) .append("'"); } - statement.append(" LINES TERMINATED BY '") - .append(data.getLineTermination()) - .append("'") - .append(data.getSkipLines() != null ? (" IGNORE " + data.getSkipLines() + " LINES") : "") - .append(" ("); + if (data.getLineTermination() != null) { + statement.append(" LINES TERMINATED BY '") + .append(data.getLineTermination()) + .append("'"); + } + if (data.getSkipLines() != null) { + statement.append(" IGNORE") + .append(data.getSkipLines()) + .append(" LINES"); + } + statement.append(" ("); final StringBuilder set = new StringBuilder(); int[] idx = new int[]{0}; table.getColumns() @@ -580,20 +591,10 @@ public interface MariaDbMapper { /* format as variable */ statement.append("@") .append(column.getInternalName()); - if (column.getDateFormat() != null) { - /* reformat dates */ - columnToDateSet(data, column, set); - } else if (column.getColumnType().equals(ColumnTypeDto.BOOL)) { - /* reformat booleans */ - columnToBoolSet(data, column, set); - } else { - /* reformat others */ - columnToTextSet(data, column, set); - } idx[0]++; }); statement.append(")") - .append(set.length() != 0 ? (" SET " + set) : "") + .append(!set.isEmpty() ? (" SET " + set) : "") .append(";"); log.trace("mapped insert statement: {}", statement); return statement.toString(); @@ -710,125 +711,6 @@ public interface MariaDbMapper { return statement.toString(); } - default void columnToDateSet(ImportCsvDto data, ColumnDto column, StringBuilder set) { - log.trace("import column has date format, need to format it: {}", column.getDateFormat().getUnixFormat()); - set.append(!set.isEmpty() ? ", " : "") - .append("`") - .append(column.getInternalName()) - .append("` = STR_TO_DATE("); - if (data.getNullElement() != null) { - set.append("IF(STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getNullElement()) - .append("'), @") - .append(column.getInternalName()) - .append(", NULL), '") - .append(column.getDateFormat() - .getDatabaseFormat() - .replace('\'', '\\')) - .append("')"); - return; - } - set.append("@") - .append(column.getInternalName()) - .append(", '") - .append(column.getDateFormat() - .getDatabaseFormat() - .replace('\'', '\\')) - .append("')"); - } - - default void columnToBoolSet(ImportCsvDto data, ColumnDto column, StringBuilder set) { - set.append(!set.isEmpty() ? ", " : "") - .append("`") - .append(column.getInternalName()) - .append("` = "); - if (data.getNullElement() != null) { - set.append("IF(!STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getNullElement()) - .append("'),NULL,"); - columnToBoolSet2(data, column, set); - set.append(")"); - return; - } - columnToBoolSet2(data, column, set); - } - - default void columnToBoolSet2(ImportCsvDto data, ColumnDto column, StringBuilder set) { - if (data.getTrueElement() != null) { - set.append("IF(!STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getTrueElement()) - .append("'),TRUE,"); - if (data.getFalseElement() != null) { - log.trace("import has false element present (both true and false)"); - /* can map both true/false */ - set.append("IF(!STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getFalseElement()) - .append("'),FALSE,@") - .append(column.getInternalName()) - .append("))"); - } else { - /* can only map true */ - set.append("@") - .append(column.getInternalName()) - .append(")"); - } - return; - } - if (data.getFalseElement() != null) { - set.append("IF(!STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getFalseElement()) - .append("'),FALSE,"); - if (data.getTrueElement() != null) { - log.trace("import has true element present (both true and false)"); - /* can map both true/false */ - set.append("IF(!STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getTrueElement()) - .append("'),TRUE,@") - .append(column.getInternalName()) - .append("))"); - } else { - /* can only map true */ - set.append("@") - .append(column.getInternalName()) - .append(")"); - } - return; - } - set.append("@") - .append(column.getInternalName()); - } - - default void columnToTextSet(ImportCsvDto data, ColumnDto column, StringBuilder set) { - set.append(!set.isEmpty() ? ", " : "") - .append("`") - .append(column.getInternalName()) - .append("` = "); - if (data.getNullElement() != null) { - set.append("IF(STRCMP(@") - .append(column.getInternalName()) - .append(",'") - .append(data.getNullElement()) - .append("'), @") - .append(column.getInternalName()) - .append(", NULL)"); - return; - } - set.append("@") - .append(column.getInternalName()); - } - default void prepareStatementWithColumnTypeObject(PreparedStatement statement, ColumnTypeDto columnType, int idx, String columnName, Object value) throws SQLException { switch (columnType) { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java index 765a3b7e2e..33ef0026a8 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java @@ -2,7 +2,7 @@ package at.tuwien.service; import at.tuwien.ExportResourceDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.query.ImportCsvDto; +import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.internal.PrivilegedTableDto; @@ -104,7 +104,7 @@ public interface TableService { Long getCount(PrivilegedTableDto table, Instant timestamp) throws SQLException, QueryMalformedException; - void importDataset(PrivilegedTableDto table, ImportCsvDto data) throws SidecarImportException, + void importDataset(PrivilegedTableDto table, ImportDto data) throws SidecarImportException, StorageNotFoundException, SQLException, QueryMalformedException, RemoteUnavailableException; void deleteTuple(PrivilegedTableDto table, TupleDeleteDto data) throws SQLException, diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java index e691bee25e..8bfdc0089a 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java @@ -17,8 +17,8 @@ import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MariaDbMapper; import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.SubsetService; import at.tuwien.service.StorageService; +import at.tuwien.service.SubsetService; import com.mchange.v2.c3p0.ComboPooledDataSource; import io.micrometer.core.instrument.Counter; import lombok.extern.log4j.Log4j2; @@ -287,7 +287,11 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs /* insert query into query store */ final long start = System.currentTimeMillis(); final CallableStatement callableStatement = connection.prepareCall(mariaDbMapper.queryStoreStoreQueryRawQuery()); - callableStatement.setString(1, String.valueOf(userId)); + if (userId != null) { + callableStatement.setString(1, String.valueOf(userId)); + } else { + callableStatement.setNull(1, Types.VARCHAR); + } callableStatement.setString(2, query); callableStatement.setTimestamp(3, Timestamp.from(timestamp)); callableStatement.registerOutParameter(4, Types.BIGINT); diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java index cdcde4f2c2..3ab02bbb01 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java @@ -2,7 +2,7 @@ package at.tuwien.service.impl; import at.tuwien.ExportResourceDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.query.ImportCsvDto; +import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.columns.ColumnDto; @@ -273,7 +273,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } @Override - public void importDataset(PrivilegedTableDto table, ImportCsvDto data) throws StorageNotFoundException, + public void importDataset(PrivilegedTableDto table, ImportDto data) throws StorageNotFoundException, SQLException, QueryMalformedException, RemoteUnavailableException, SidecarImportException { /* import .csv from blob storage to sidecar */ dataDatabaseSidecarGateway.importFile(table.getDatabase().getContainer().getSidecarHost(), table.getDatabase().getContainer().getSidecarPort(), data.getLocation()); diff --git a/dbrepo-gateway-service/dbrepo.conf b/dbrepo-gateway-service/dbrepo.conf index 49e5ce6496..e61d202266 100644 --- a/dbrepo-gateway-service/dbrepo.conf +++ b/dbrepo-gateway-service/dbrepo.conf @@ -48,16 +48,7 @@ server { listen 80 default_server; server_name _; - location /admin/broker { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://broker; - proxy_read_timeout 90; - } - - location /dashboard { + location /dashboard/ { rewrite ^/dashboard/(.*) /$1 break; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -68,7 +59,7 @@ server { } # Proxy Grafana Live WebSocket connections. - location /dashboard/api/live { + location /dashboard/api/live/ { rewrite ^/dashboard/(.*) /$1 break; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -87,7 +78,7 @@ server { } location /api/broker { - rewrite /api/broker/(.*) /admin/broker/api/$1 break; + rewrite /api/broker/(.*) /api/$1 break; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/dbrepo-metadata-db/1_setup-schema.sql b/dbrepo-metadata-db/1_setup-schema.sql index 9c21d44c0e..db90e94c1f 100644 --- a/dbrepo-metadata-db/1_setup-schema.sql +++ b/dbrepo-metadata-db/1_setup-schema.sql @@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS `mdb_users` CREATE TABLE IF NOT EXISTS `mdb_images` ( - id bigint NOT NULL AUTO_INCREMENT, + id SERIAL, registry character varying(255) NOT NULL DEFAULT 'docker.io', name character varying(255) NOT NULL, version character varying(255) NOT NULL, @@ -35,23 +35,9 @@ CREATE TABLE IF NOT EXISTS `mdb_images` UNIQUE (is_default) ) WITH SYSTEM VERSIONING; -CREATE TABLE IF NOT EXISTS `mdb_images_date` -( - id bigint NOT NULL AUTO_INCREMENT, - iid bigint NOT NULL, - database_format character varying(255) NOT NULL, - unix_format character varying(255) NOT NULL, - example character varying(255) NOT NULL, - has_time boolean NOT NULL, - created_at timestamp NOT NULL DEFAULT NOW(), - PRIMARY KEY (id), - FOREIGN KEY (iid) REFERENCES mdb_images (id), - UNIQUE (database_format, unix_format, example) -) WITH SYSTEM VERSIONING; - CREATE TABLE IF NOT EXISTS `mdb_containers` ( - id bigint NOT NULL AUTO_INCREMENT, + id SERIAL, internal_name character varying(255) NOT NULL, name character varying(255) NOT NULL, host character varying(255) NOT NULL, @@ -67,13 +53,12 @@ CREATE TABLE IF NOT EXISTS `mdb_containers` privileged_username character varying(255) NOT NULL, privileged_password character varying(255) NOT NULL, quota integer NOT NULL DEFAULT 50, - PRIMARY KEY (id), - FOREIGN KEY (image_id) REFERENCES mdb_images (id) + PRIMARY KEY (id) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS `mdb_data` ( - ID bigint NOT NULL AUTO_INCREMENT, + ID SERIAL, PROVENANCE text, FileEncoding text, FileType character varying(100), @@ -93,8 +78,8 @@ CREATE TABLE IF NOT EXISTS `mdb_licenses` CREATE TABLE IF NOT EXISTS `mdb_databases` ( - id bigint NOT NULL AUTO_INCREMENT, - cid bigint NOT NULL, + id SERIAL, + cid BIGINT UNSIGNED NOT NULL, name character varying(255) NOT NULL, internal_name character varying(255) NOT NULL, exchange_name character varying(255) NOT NULL, @@ -108,7 +93,7 @@ CREATE TABLE IF NOT EXISTS `mdb_databases` created timestamp NOT NULL DEFAULT NOW(), last_modified timestamp, PRIMARY KEY (id), - FOREIGN KEY (cid) REFERENCES mdb_containers (id) /* currently we only support one-to-one */, + FOREIGN KEY (cid) REFERENCES mdb_containers (id), FOREIGN KEY (created_by) REFERENCES mdb_users (id), FOREIGN KEY (owned_by) REFERENCES mdb_users (id), FOREIGN KEY (contact_person) REFERENCES mdb_users (id) @@ -123,8 +108,8 @@ CREATE TABLE IF NOT EXISTS `mdb_databases_subjects` CREATE TABLE IF NOT EXISTS `mdb_tables` ( - ID bigint NOT NULL AUTO_INCREMENT, - tDBID bigint NOT NULL, + ID SERIAL, + tDBID BIGINT UNSIGNED NOT NULL, tName VARCHAR(64) NOT NULL, internal_name VARCHAR(64) NOT NULL, queue_name VARCHAR(255) NOT NULL, @@ -155,26 +140,25 @@ CREATE TABLE IF NOT EXISTS `mdb_tables` CREATE TABLE IF NOT EXISTS `mdb_columns` ( - ID BIGINT NOT NULL AUTO_INCREMENT, - tID BIGINT NOT NULL, - dfID BIGINT, + ID SERIAL, + tID BIGINT UNSIGNED NOT NULL, cName VARCHAR(64), - internal_name VARCHAR(64) NOT NULL, + internal_name VARCHAR(64) NOT NULL, Datatype ENUM ('CHAR','VARCHAR','BINARY','VARBINARY','TINYBLOB','TINYTEXT','TEXT','BLOB','MEDIUMTEXT','MEDIUMBLOB','LONGTEXT','LONGBLOB','ENUM','SET','BIT','TINYINT','BOOL','SMALLINT','MEDIUMINT','INT','BIGINT','FLOAT','DOUBLE','DECIMAL','DATE','DATETIME','TIMESTAMP','TIME','YEAR'), - length BIGINT NULL, - ordinal_position INTEGER NOT NULL, - index_length BIGINT NULL, + length BIGINT UNSIGNED NULL, + ordinal_position INTEGER NOT NULL, + index_length BIGINT UNSIGNED NULL, description VARCHAR(2048), - size BIGINT, - d BIGINT, - auto_generated BOOLEAN DEFAULT false, - is_null_allowed BOOLEAN NOT NULL DEFAULT true, - val_min NUMERIC NULL, - val_max NUMERIC NULL, - mean NUMERIC NULL, - median NUMERIC NULL, - std_dev Numeric NULL, - created timestamp NOT NULL DEFAULT NOW(), + size BIGINT UNSIGNED, + d BIGINT UNSIGNED, + auto_generated BOOLEAN DEFAULT false, + is_null_allowed BOOLEAN NOT NULL DEFAULT true, + val_min NUMERIC NULL, + val_max NUMERIC NULL, + mean NUMERIC NULL, + median NUMERIC NULL, + std_dev Numeric NULL, + created timestamp NOT NULL DEFAULT NOW(), last_modified timestamp, FOREIGN KEY (tID) REFERENCES mdb_tables (ID) ON DELETE CASCADE, PRIMARY KEY (ID), @@ -183,8 +167,8 @@ CREATE TABLE IF NOT EXISTS `mdb_columns` CREATE TABLE IF NOT EXISTS `mdb_columns_enums` ( - id bigint NOT NULL AUTO_INCREMENT, - column_id bigint NOT NULL, + id SERIAL, + column_id BIGINT UNSIGNED NOT NULL, value CHARACTER VARYING(255) NOT NULL, FOREIGN KEY (column_id) REFERENCES mdb_columns (ID) ON DELETE CASCADE, PRIMARY KEY (id) @@ -192,8 +176,8 @@ CREATE TABLE IF NOT EXISTS `mdb_columns_enums` CREATE TABLE IF NOT EXISTS `mdb_columns_sets` ( - id bigint NOT NULL AUTO_INCREMENT, - column_id bigint NOT NULL, + id SERIAL, + column_id BIGINT UNSIGNED NOT NULL, value CHARACTER VARYING(255) NOT NULL, FOREIGN KEY (column_id) REFERENCES mdb_columns (ID) ON DELETE CASCADE, PRIMARY KEY (id) @@ -201,8 +185,8 @@ CREATE TABLE IF NOT EXISTS `mdb_columns_sets` CREATE TABLE IF NOT EXISTS `mdb_columns_nom` ( - cID bigint, - tID bigint, + cID BIGINT UNSIGNED, + tID BIGINT UNSIGNED, maxlength INTEGER, last_modified timestamp, created timestamp NOT NULL DEFAULT NOW(), @@ -212,8 +196,8 @@ CREATE TABLE IF NOT EXISTS `mdb_columns_nom` CREATE TABLE IF NOT EXISTS `mdb_columns_cat` ( - cID bigint, - tID bigint, + cID BIGINT UNSIGNED, + tID BIGINT UNSIGNED, num_cat INTEGER, -- cat_array TEXT[], last_modified timestamp, @@ -224,13 +208,13 @@ CREATE TABLE IF NOT EXISTS `mdb_columns_cat` CREATE TABLE IF NOT EXISTS `mdb_constraints_foreign_key` ( - fkid BIGINT NOT NULL AUTO_INCREMENT, - tid BIGINT NOT NULL, - rtid BIGINT NOT NULL, - name VARCHAR(255) NOT NULL, - on_update VARCHAR(50) NULL, - on_delete VARCHAR(50) NULL, - position INT NULL, + fkid BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + tid BIGINT UNSIGNED NOT NULL, + rtid BIGINT UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + on_update VARCHAR(50) NULL, + on_delete VARCHAR(50) NULL, + position INT NULL, PRIMARY KEY (fkid), FOREIGN KEY (tid) REFERENCES mdb_tables (id) ON DELETE CASCADE, FOREIGN KEY (rtid) REFERENCES mdb_tables (id) @@ -238,9 +222,9 @@ CREATE TABLE IF NOT EXISTS `mdb_constraints_foreign_key` CREATE TABLE IF NOT EXISTS `mdb_constraints_primary_key` ( - pkid BIGINT NOT NULL AUTO_INCREMENT, - tID BIGINT NOT NULL, - cid BIGINT NOT NULL, + pkid SERIAL, + tID BIGINT UNSIGNED NOT NULL, + cid BIGINT UNSIGNED NOT NULL, PRIMARY KEY (pkid), FOREIGN KEY (tID) REFERENCES mdb_tables (id) ON DELETE CASCADE, FOREIGN KEY (cid) REFERENCES mdb_columns (id) ON DELETE CASCADE @@ -248,10 +232,10 @@ CREATE TABLE IF NOT EXISTS `mdb_constraints_primary_key` CREATE TABLE IF NOT EXISTS `mdb_constraints_foreign_key_reference` ( - id BIGINT NOT NULL AUTO_INCREMENT, - fkid BIGINT NOT NULL, - cid BIGINT NOT NULL, - rcid BIGINT NOT NULL, + id SERIAL, + fkid BIGINT UNSIGNED NOT NULL, + cid BIGINT UNSIGNED NOT NULL, + rcid BIGINT UNSIGNED NOT NULL, PRIMARY KEY (id), UNIQUE (fkid, cid, rcid), FOREIGN KEY (fkid) REFERENCES mdb_constraints_foreign_key (fkid) ON UPDATE CASCADE, @@ -261,19 +245,19 @@ CREATE TABLE IF NOT EXISTS `mdb_constraints_foreign_key_reference` CREATE TABLE IF NOT EXISTS `mdb_constraints_unique` ( - uid BIGINT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - tid BIGINT NOT NULL, - position INT NULL, + uid BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + tid BIGINT UNSIGNED NOT NULL, + position INT NULL, PRIMARY KEY (uid), FOREIGN KEY (tid) REFERENCES mdb_tables (id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS `mdb_constraints_unique_columns` ( - id BIGINT NOT NULL AUTO_INCREMENT, - uid BIGINT NOT NULL, - cid BIGINT NOT NULL, + id SERIAL, + uid BIGINT UNSIGNED NOT NULL, + cid BIGINT UNSIGNED NOT NULL, PRIMARY KEY (id), FOREIGN KEY (uid) REFERENCES mdb_constraints_unique (uid), FOREIGN KEY (cid) REFERENCES mdb_columns (id) ON DELETE CASCADE @@ -281,9 +265,9 @@ CREATE TABLE IF NOT EXISTS `mdb_constraints_unique_columns` CREATE TABLE IF NOT EXISTS `mdb_constraints_checks` ( - id BIGINT NOT NULL AUTO_INCREMENT, - tid BIGINT NOT NULL, - checks VARCHAR(255) NOT NULL, + id SERIAL, + tid BIGINT UNSIGNED NOT NULL, + checks VARCHAR(255) NOT NULL, PRIMARY KEY (id), FOREIGN KEY (tid) REFERENCES mdb_tables (id) ON DELETE CASCADE ) WITH SYSTEM VERSIONING; @@ -291,7 +275,7 @@ CREATE TABLE IF NOT EXISTS `mdb_constraints_checks` CREATE TABLE IF NOT EXISTS `mdb_concepts` ( - id bigint NOT NULL AUTO_INCREMENT, + id SERIAL, uri text not null, name VARCHAR(255) null, description TEXT null, @@ -302,7 +286,7 @@ CREATE TABLE IF NOT EXISTS `mdb_concepts` CREATE TABLE IF NOT EXISTS `mdb_units` ( - id bigint NOT NULL AUTO_INCREMENT, + id SERIAL, uri text not null, name VARCHAR(255) null, description TEXT null, @@ -313,26 +297,26 @@ CREATE TABLE IF NOT EXISTS `mdb_units` CREATE TABLE IF NOT EXISTS `mdb_columns_concepts` ( - id bigint NOT NULL, - cID bigint NOT NULL, - created timestamp NOT NULL DEFAULT NOW(), + id BIGINT UNSIGNED NOT NULL, + cID BIGINT UNSIGNED NOT NULL, + created timestamp NOT NULL DEFAULT NOW(), PRIMARY KEY (id, cid), FOREIGN KEY (cID) REFERENCES mdb_columns (ID) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS `mdb_columns_units` ( - id bigint NOT NULL, - cID bigint NOT NULL, - created timestamp NOT NULL DEFAULT NOW(), + id BIGINT UNSIGNED NOT NULL, + cID BIGINT UNSIGNED NOT NULL, + created timestamp NOT NULL DEFAULT NOW(), PRIMARY KEY (id, cID), FOREIGN KEY (cID) REFERENCES mdb_columns (ID) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS `mdb_view` ( - id bigint NOT NULL AUTO_INCREMENT, - vdbid bigint NOT NULL, + id SERIAL, + vdbid BIGINT UNSIGNED NOT NULL, vName VARCHAR(64) NOT NULL, internal_name VARCHAR(64) NOT NULL, Query TEXT NOT NULL, @@ -349,7 +333,7 @@ CREATE TABLE IF NOT EXISTS `mdb_view` CREATE TABLE IF NOT EXISTS `mdb_banner_messages` ( - id bigint NOT NULL AUTO_INCREMENT, + id SERIAL, type ENUM ('ERROR', 'WARNING', 'INFO') NOT NULL default 'INFO', message TEXT NOT NULL, link TEXT NULL, @@ -361,7 +345,7 @@ CREATE TABLE IF NOT EXISTS `mdb_banner_messages` CREATE TABLE IF NOT EXISTS `mdb_ontologies` ( - id bigint NOT NULL AUTO_INCREMENT, + id SERIAL, prefix VARCHAR(8) NOT NULL, uri TEXT NOT NULL, uri_pattern TEXT, @@ -376,28 +360,28 @@ CREATE TABLE IF NOT EXISTS `mdb_ontologies` CREATE TABLE IF NOT EXISTS `mdb_view_columns` ( - id BIGINT NOT NULL AUTO_INCREMENT, - view_id BIGINT NOT NULL, - dfID BIGINT, + id SERIAL, + view_id BIGINT UNSIGNED NOT NULL, + dfID BIGINT UNSIGNED, name VARCHAR(64), - internal_name VARCHAR(64) NOT NULL, + internal_name VARCHAR(64) NOT NULL, column_type ENUM ('CHAR','VARCHAR','BINARY','VARBINARY','TINYBLOB','TINYTEXT','TEXT','BLOB','MEDIUMTEXT','MEDIUMBLOB','LONGTEXT','LONGBLOB','ENUM','SET','BIT','TINYINT','BOOL','SMALLINT','MEDIUMINT','INT','BIGINT','FLOAT','DOUBLE','DECIMAL','DATE','DATETIME','TIMESTAMP','TIME','YEAR'), - ordinal_position INTEGER NOT NULL, - size BIGINT, - d BIGINT, - auto_generated BOOLEAN DEFAULT false, - is_null_allowed BOOLEAN NOT NULL DEFAULT true, + ordinal_position INTEGER NOT NULL, + size BIGINT UNSIGNED, + d BIGINT UNSIGNED, + auto_generated BOOLEAN DEFAULT false, + is_null_allowed BOOLEAN NOT NULL DEFAULT true, PRIMARY KEY (id), FOREIGN KEY (view_id) REFERENCES mdb_view (id) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS `mdb_identifiers` ( - id BIGINT NOT NULL AUTO_INCREMENT, - dbid BIGINT NOT NULL, - qid BIGINT, - vid BIGINT, - tid BIGINT, + id SERIAL, + dbid BIGINT UNSIGNED NOT NULL, + qid BIGINT UNSIGNED, + vid BIGINT UNSIGNED, + tid BIGINT UNSIGNED, publisher VARCHAR(255) NOT NULL, language VARCHAR(2), publication_year INTEGER NOT NULL, @@ -422,8 +406,8 @@ CREATE TABLE IF NOT EXISTS `mdb_identifiers` CREATE TABLE IF NOT EXISTS `mdb_identifier_licenses` ( - pid bigint NOT NULL, - license_id VARCHAR(255) NOT NULL, + pid BIGINT UNSIGNED NOT NULL, + license_id VARCHAR(255) NOT NULL, PRIMARY KEY (pid, license_id), FOREIGN KEY (pid) REFERENCES mdb_identifiers (id), FOREIGN KEY (license_id) REFERENCES mdb_licenses (identifier) @@ -431,9 +415,9 @@ CREATE TABLE IF NOT EXISTS `mdb_identifier_licenses` CREATE TABLE IF NOT EXISTS `mdb_identifier_titles` ( - id bigint NOT NULL AUTO_INCREMENT, - pid bigint NOT NULL, - title text NOT NULL, + id SERIAL, + pid BIGINT UNSIGNED NOT NULL, + title text NOT NULL, title_type ENUM ('ALTERNATIVE_TITLE', 'SUBTITLE', 'TRANSLATED_TITLE', 'OTHER'), language VARCHAR(2), PRIMARY KEY (id), @@ -442,9 +426,9 @@ CREATE TABLE IF NOT EXISTS `mdb_identifier_titles` CREATE TABLE IF NOT EXISTS `mdb_identifier_funders` ( - id bigint NOT NULL AUTO_INCREMENT, - pid bigint NOT NULL, - funder_name VARCHAR(255) NOT NULL, + id SERIAL, + pid BIGINT UNSIGNED NOT NULL, + funder_name VARCHAR(255) NOT NULL, funder_identifier TEXT, funder_identifier_type ENUM ('CROSSREF_FUNDER_ID', 'GRID', 'ISNI', 'ROR', 'OTHER'), scheme_uri text, @@ -457,9 +441,9 @@ CREATE TABLE IF NOT EXISTS `mdb_identifier_funders` CREATE TABLE IF NOT EXISTS `mdb_identifier_descriptions` ( - id bigint NOT NULL AUTO_INCREMENT, - pid bigint NOT NULL, - description text NOT NULL, + id SERIAL, + pid BIGINT UNSIGNED NOT NULL, + description text NOT NULL, description_type ENUM ('ABSTRACT', 'METHODS', 'SERIES_INFORMATION', 'TABLE_OF_CONTENTS', 'TECHNICAL_INFO', 'OTHER'), language VARCHAR(2), PRIMARY KEY (id), @@ -468,11 +452,11 @@ CREATE TABLE IF NOT EXISTS `mdb_identifier_descriptions` CREATE TABLE IF NOT EXISTS `mdb_related_identifiers` ( - id bigint NOT NULL AUTO_INCREMENT, - pid bigint NOT NULL, - value varchar(255) NOT NULL, - type varchar(255) NOT NULL, - relation varchar(255) NOT NULL, + id SERIAL, + pid BIGINT UNSIGNED NOT NULL, + value varchar(255) NOT NULL, + type varchar(255) NOT NULL, + relation varchar(255) NOT NULL, PRIMARY KEY (id), /* must be a single id from persistent identifier concept */ FOREIGN KEY (pid) REFERENCES mdb_identifiers (id), UNIQUE (pid, value) @@ -480,11 +464,11 @@ CREATE TABLE IF NOT EXISTS `mdb_related_identifiers` CREATE TABLE IF NOT EXISTS `mdb_identifier_creators` ( - id bigint NOT NULL AUTO_INCREMENT, - pid bigint NOT NULL, + id SERIAL, + pid BIGINT UNSIGNED NOT NULL, given_names text, family_name text, - creator_name VARCHAR(255) NOT NULL, + creator_name VARCHAR(255) NOT NULL, name_type ENUM ('PERSONAL', 'ORGANIZATIONAL') default 'PERSONAL', name_identifier text, name_identifier_scheme ENUM ('ROR', 'GRID', 'ISNI', 'ORCID'), @@ -500,7 +484,7 @@ CREATE TABLE IF NOT EXISTS `mdb_identifier_creators` CREATE TABLE IF NOT EXISTS `mdb_update` ( uUserID character varying(255) NOT NULL, - uDBID bigint NOT NULL, + uDBID BIGINT UNSIGNED NOT NULL, created timestamp NOT NULL DEFAULT NOW(), PRIMARY KEY (uUserID, uDBID), FOREIGN KEY (uDBID) REFERENCES mdb_databases (id) @@ -509,7 +493,7 @@ CREATE TABLE IF NOT EXISTS `mdb_update` CREATE TABLE IF NOT EXISTS `mdb_access` ( aUserID character varying(255) NOT NULL, - aDBID bigint REFERENCES mdb_databases (id), + aDBID BIGINT UNSIGNED REFERENCES mdb_databases (id), attime TIMESTAMP, download BOOLEAN, created timestamp NOT NULL DEFAULT NOW(), @@ -519,14 +503,40 @@ CREATE TABLE IF NOT EXISTS `mdb_access` CREATE TABLE IF NOT EXISTS `mdb_have_access` ( user_id character varying(36) NOT NULL, - database_id bigint REFERENCES mdb_databases (id), + database_id BIGINT UNSIGNED REFERENCES mdb_databases (id), access_type ENUM ('READ', 'WRITE_OWN', 'WRITE_ALL') NOT NULL, created timestamp NOT NULL DEFAULT NOW(), PRIMARY KEY (user_id, database_id), FOREIGN KEY (user_id) REFERENCES mdb_users (id) ) WITH SYSTEM VERSIONING; +CREATE TABLE IF NOT EXISTS `mdb_image_types` +( + id SERIAL, + image_id BIGINT UNSIGNED NOT NULL, + display_name varchar(255) NOT NULL, + value varchar(255) NOT NULL, + size_min INT UNSIGNED, + size_max INT UNSIGNED, + size_default INT UNSIGNED, + size_required BOOLEAN COMMENT 'When setting NULL, the service assumes the data type has no size', + size_step INT UNSIGNED, + d_min INT UNSIGNED, + d_max INT UNSIGNED, + d_default INT UNSIGNED, + d_required BOOLEAN COMMENT 'When setting NULL, the service assumes the data type has no d', + d_step INT UNSIGNED, + hint TEXT, + documentation TEXT NOT NULL, + is_quoted BOOLEAN NOT NULL, + is_buildable BOOLEAN NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (image_id) REFERENCES `mdb_images` (`id`), + UNIQUE (value) +) WITH SYSTEM VERSIONING; + COMMIT; + BEGIN; INSERT INTO `mdb_licenses` (identifier, uri, description) @@ -539,14 +549,71 @@ INSERT INTO `mdb_images` (name, registry, version, default_port, dialect, driver VALUES ('mariadb', 'docker.io', '11.1.3', 3306, 'org.hibernate.dialect.MariaDBDialect', 'org.mariadb.jdbc.Driver', 'mariadb'); -INSERT INTO `mdb_images_date` (iid, database_format, unix_format, example, has_time) -VALUES (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 %H:%i:%S', 'yyyy-MM-dd HH:mm:ss', '2022-01-30 13:44:25', true), - (1, '%Y-%c-%d', 'yyyy-MM-dd', '2022-01-30', false), - (1, '%H:%i:%S', 'HH:mm:ss', '13:44:25', true), - (1, '%d.%c.%Y', 'dd.MM.yyyy', '30.01.2022', false); - -INSERT INTO `mdb_ontologies` (prefix, uri, uri_pattern, sparql_endpoint, rdf_path) +INSERT INTO `mdb_image_types` (image_id, display_name, value, size_min, size_max, size_default, size_required, + size_step, d_min, d_max, d_default, d_required, d_step, hint, documentation, is_quoted, + is_buildable) +VALUES (1, 'BIGINT(size)', 'bigint', 0, null, null, false, 1, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/bigint/', false, true), + (1, 'BINARY(size)', 'binary', 0, 255, 255, true, 1, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/binary/', false, true), + (1, 'BIT(size)', 'bit', 0, 64, null, false, 1, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/bit/', false, true), + (1, 'BLOB(size)', 'blob', 0, 65535, null, false, 1, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/blob/', false, false), + (1, 'BOOL', 'bool', null, null, null, null, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/bool/', false, true), + (1, 'CHAR(size)', 'char', 0, 255, 255, false, 1, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/char/', false, true), + (1, 'DATE', 'date', null, null, null, null, null, null, null, null, null, null, + 'min. 1000-01-01, max. 9999-12-31', 'https://mariadb.com/kb/en/date/', true, true), + (1, 'DATETIME(fsp)', 'datetime', 0, 6, null, null, 1, null, null, null, null, null, + 'fsp=microsecond precision, min. 1000-01-01 00:00:00.0, max. 9999-12-31 23:59:59.9', + 'https://mariadb.com/kb/en/datetime/', true, true), + (1, 'DECIMAL(size, d)', 'decimal', 0, 65, null, false, 1, 0, 38, null, false, null, null, + 'https://mariadb.com/kb/en/decimal/', false, true), + (1, 'DOUBLE(size, d)', 'double', null, null, null, false, null, null, null, null, false, null, null, + 'https://mariadb.com/kb/en/double/', false, true), + (1, 'ENUM(v1,v2,...)', 'enum', null, null, null, null, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/enum/', true, true), + (1, 'FLOAT(size)', 'float', null, null, null, false, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/float/', false, true), + (1, 'INT(size)', 'int', null, null, null, false, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/int/', false, true), + (1, 'LONGBLOB', 'longblob', null, null, null, null, null, null, null, null, null, null, 'max. 3.999 GiB', + 'https://mariadb.com/kb/en/longblob/', false, true), + (1, 'LONGTEXT', 'longtext', null, null, null, null, null, null, null, null, null, null, 'max. 3.999 GiB', + 'https://mariadb.com/kb/en/longtext/', true, true), + (1, 'MEDIUMBLOB', 'mediumblob', null, null, null, null, null, null, null, null, null, null, 'max. 15.999 MiB', + 'https://mariadb.com/kb/en/mediumblob/', false, true), + (1, 'MEDIUMINT', 'mediumint', null, null, null, null, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/mediumint/', false, true), + (1, 'MEDIUMTEXT', 'mediumtext', null, null, null, null, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/mediumtext/', true, true), + (1, 'SET(v1,v2,...)', 'set', null, null, null, null, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/set/', true, true), + (1, 'SMALLINT(size)', 'smallint', 0, null, null, false, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/smallint/', false, true), + (1, 'TEXT(size)', 'text', 0, null, null, false, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/text/', true, true), + (1, 'TIME(fsp)', 'time', 0, 6, 0, false, null, null, null, null, null, null, + 'fsp=microsecond precision, min. 0, max. 6', 'https://mariadb.com/kb/en/time/', true, true), + (1, 'TIMESTAMP(fsp)', 'timestamp', 0, 6, 0, false, null, null, null, null, null, null, + 'fsp=microsecond precision, min. 0, max. 6', 'https://mariadb.com/kb/en/timestamp/', true, true), + (1, 'TINYBLOB', 'tinyblob', null, null, null, null, null, null, null, null, null, null, + 'fsp=microsecond precision, min. 0, max. 6', 'https://mariadb.com/kb/en/timestamp/', false, true), + (1, 'TINYINT(size)', 'tinyint', 0, null, null, false, null, null, null, null, null, null, + 'size in Bytes', 'https://mariadb.com/kb/en/tinyint/', false, true), + (1, 'TINYTEXT', 'tinytext', null, null, null, null, null, null, null, null, null, null, + 'max. 255 characters', 'https://mariadb.com/kb/en/tinytext/', true, true), + (1, 'YEAR', 'year', 2, 4, null, false, 2, null, null, null, null, null, 'min. 1901, max. 2155', + 'https://mariadb.com/kb/en/year/', false, true), + (1, 'VARBINARY(size)', 'varbinary', 0, null, null, true, null, null, null, null, null, null, + null, 'https://mariadb.com/kb/en/varbinary/', false, true), + (1, 'VARCHAR(size)', 'varchar', 0, 65532, 255, true, null, null, null, null, null, null, + null, 'https://mariadb.com/kb/en/varchar/', false, true); + +INSERT +INTO `mdb_ontologies` (prefix, uri, uri_pattern, sparql_endpoint, rdf_path) VALUES ('om', 'http://www.ontology-of-units-of-measure.org/resource/om-2/', 'http://www.ontology-of-units-of-measure.org/resource/om-2/.*', null, 'rdf/om-2.0.rdf'), ('wd', 'http://www.wikidata.org/', 'http://www.wikidata.org/entity/.*', 'https://query.wikidata.org/sparql', diff --git a/dbrepo-metadata-service/api/pom.xml b/dbrepo-metadata-service/api/pom.xml index 4722e9c3c9..328fc8ee57 100644 --- a/dbrepo-metadata-service/api/pom.xml +++ b/dbrepo-metadata-service/api/pom.xml @@ -13,7 +13,14 @@ <name>dbrepo-metadata-service-api</name> <version>1.4.6</version> - <dependencies/> + <dependencies> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>dbrepo-metadata-service-entities</artifactId> + <version>1.4.6</version> + <scope>compile</scope> + </dependency> + </dependencies> <build> <plugins> diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java new file mode 100644 index 0000000000..cd31aa2255 --- /dev/null +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java @@ -0,0 +1,66 @@ +package at.tuwien.api.container.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Jacksonized +@ToString +public class DataTypeDto { + + @NotBlank + @JsonProperty("display_name") + @Schema(example = "BIGINT") + private String displayName; + + @NotBlank + @Schema(example = "bigint") + private String value; + + @JsonProperty("size_min") + private Integer sizeMin; + + @JsonProperty("size_max") + private Integer sizeMax; + + @JsonProperty("size_default") + private Integer sizeDefault; + + @JsonProperty("size_required") + private Boolean sizeRequired; + + @JsonProperty("d_min") + private Integer dMin; + + @JsonProperty("d_max") + private Integer dMax; + + @JsonProperty("d_default") + private Integer dDefault; + + @JsonProperty("d_required") + private Boolean dRequired; + + @NotNull + @Schema(example = "https://mariadb.com/kb/en/bigint/") + private String documentation; + + @NotNull + @Schema(description = "frontend needs to quote this data type") + @JsonProperty("is_quoted") + private Boolean quoted; + + @NotNull + @JsonProperty("is_buildable") + @Schema(description = "frontend can build this data type") + private Boolean buildable; + +} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDateDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDateDto.java deleted file mode 100644 index 6fc25ad3cb..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDateDto.java +++ /dev/null @@ -1,48 +0,0 @@ -package at.tuwien.api.container.image; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.extern.jackson.Jacksonized; - -import java.time.Instant; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class ImageDateDto { - - @NotNull - private Long id; - - @NotBlank - @JsonProperty("database_format") - @Schema(example = "%d.%c.%Y") - private String databaseFormat; - - @NotBlank - @JsonProperty("unix_format") - @Schema(example = "dd.MM.YYYY") - private String unixFormat; - - @NotNull - @JsonProperty("has_time") - @Schema(example = "false") - private Boolean hasTime; - - - @NotNull - @Schema(example = "2021-03-12T15:26:21Z") - @JsonProperty("created_at") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC") - private Instant createdAt; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java index c0cf7f3bce..743f1f2b0a 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java @@ -38,9 +38,6 @@ public class ImageDto { @Schema(example = "org.mariadb.jdbc.Driver") private String driverClass; - @JsonProperty("date_formats") - private List<ImageDateDto> dateFormats; - @NotBlank @Schema(example = "org.hibernate.dialect.MariaDBDialect") private String dialect; @@ -60,4 +57,8 @@ public class ImageDto { @Schema(example = "3306") private Integer defaultPort; + @NotNull + @JsonProperty("data_types") + private List<DataTypeDto> dataTypes; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java index 8de17a48f3..8bda16bf41 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java @@ -1,6 +1,5 @@ package at.tuwien.api.container.internal; -import at.tuwien.api.container.image.ImageDateDto; import at.tuwien.api.container.image.ImageDto; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewColumnDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewColumnDto.java index 337a61a637..75b0a3d684 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewColumnDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewColumnDto.java @@ -1,6 +1,5 @@ package at.tuwien.api.database; -import at.tuwien.api.container.image.ImageDateDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.columns.concepts.ConceptDto; import at.tuwien.api.database.table.columns.concepts.UnitDto; @@ -47,9 +46,6 @@ public class ViewColumnDto { @Schema private String alias; - @JsonProperty("date_format") - private ImageDateDto dateFormat; - @NotNull @JsonProperty("auto_generated") @Schema(example = "false") diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportCsvDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportCsvDto.java deleted file mode 100644 index eac536143e..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportCsvDto.java +++ /dev/null @@ -1,49 +0,0 @@ -package at.tuwien.api.database.query; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.extern.jackson.Jacksonized; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class ImportCsvDto { - - @NotBlank - @Schema(example = "file.csv") - private String location; - - @Min(value = 0L) - @JsonProperty("skip_lines") - private Long skipLines; - - @JsonProperty("false_element") - private String falseElement; - - @JsonProperty("true_element") - private String trueElement; - - @JsonProperty("null_element") - @Schema(example = "NA") - private String nullElement; - - @NotNull - @Schema(example = ",") - private Character separator; - - @Schema(example = "\"") - private Character quote; - - @JsonProperty("line_termination") - @Schema(example = "\\r\\n") - private String lineTermination; -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportDto.java index b865f7892c..043e3bc3ee 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/ImportDto.java @@ -27,16 +27,6 @@ public class ImportDto { @JsonProperty("skip_lines") private Long skipLines; - @JsonProperty("false_element") - private String falseElement; - - @JsonProperty("true_element") - private String trueElement; - - @JsonProperty("null_element") - @Schema(example = "NA") - private String nullElement; - @NotNull @Schema(example = ",") private Character separator; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java index 37aa493020..2d0696abc0 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java @@ -52,9 +52,6 @@ public class ColumnCreateDto { @JsonProperty("unit_uri") private String unitUri; - @Schema(description = "date format id") - private Long dfid; - @Schema(description = "enum values, only considered when type = ENUM") private List<String> enums; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnDto.java index a506dbca82..4e95fefc3e 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/columns/ColumnDto.java @@ -1,6 +1,5 @@ package at.tuwien.api.database.table.columns; -import at.tuwien.api.container.image.ImageDateDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.columns.concepts.ConceptDto; @@ -59,9 +58,6 @@ public class ColumnDto { @Schema private String alias; - @JsonProperty("date_format") - private ImageDateDto dateFormat; - @NotNull @JsonProperty("auto_generated") @Schema(example = "false") diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java index 8250b6ae43..080a843aad 100644 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java @@ -57,9 +57,6 @@ public class ContainerImage { @Column(nullable = false, unique = true, columnDefinition = "BOOLEAN DEFAULT FALSE") private Boolean isDefault = false; - @OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL, CascadeType.PERSIST}, mappedBy = "image") - private List<ContainerImageDate> dateFormats; - @ToString.Exclude @OneToMany(fetch = FetchType.LAZY, mappedBy = "image") private List<Container> containers; @@ -73,4 +70,8 @@ public class ContainerImage { @Column(columnDefinition = "TIMESTAMP") private Instant lastModified; + @ToString.Exclude + @OneToMany(fetch = FetchType.LAZY, mappedBy = "image") + private List<DataType> dataTypes; + } diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDate.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDate.java deleted file mode 100644 index 5b370ecc06..0000000000 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDate.java +++ /dev/null @@ -1,59 +0,0 @@ -package at.tuwien.entities.container.image; - -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.*; -import org.hibernate.annotations.GenericGenerator; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import jakarta.persistence.*; - -import java.time.Instant; - -@Data -@Entity -@Builder -@ToString -@AllArgsConstructor -@NoArgsConstructor -@EntityListeners(AuditingEntityListener.class) -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@Table(name = "mdb_images_date", uniqueConstraints = @UniqueConstraint(columnNames = {"database_format"})) -public class ContainerImageDate { - - @Id - @EqualsAndHashCode.Include - @GeneratedValue(generator = "dates-sequence") - @GenericGenerator(name = "dates-sequence", strategy = "increment") - @Column(updatable = false, nullable = false) - private Long id; - - @EqualsAndHashCode.Include - @Column(name = "iid") - private Long iid; - - @ToString.Exclude - @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE}) - @JoinColumns({ - @JoinColumn(name = "iid", insertable = false, updatable = false) - }) - private ContainerImage image; - - @Column(name = "example", nullable = false) - private String example; - - @Column(name = "has_time", nullable = false) - private Boolean hasTime; - - @Column(name = "database_format", nullable = false) - private String databaseFormat; - - @Column(name = "unix_format", nullable = false) - private String unixFormat; - - @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "TIMESTAMP") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC") - private Instant createdAt; - -} diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDateKey.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDateKey.java deleted file mode 100644 index c5ae9598f7..0000000000 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImageDateKey.java +++ /dev/null @@ -1,14 +0,0 @@ -package at.tuwien.entities.container.image; - -import lombok.EqualsAndHashCode; - -import java.io.Serializable; - -@EqualsAndHashCode -public class ContainerImageDateKey implements Serializable { - - private Long id; - - private Long iid; - -} diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java new file mode 100644 index 0000000000..8333470ad3 --- /dev/null +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java @@ -0,0 +1,73 @@ +package at.tuwien.entities.container.image; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.util.List; + +@Data +@Entity +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Table(name = "mdb_image_types") +public class DataType { + + @Id + @EqualsAndHashCode.Include + @GeneratedValue(generator = "image-type-sequence") + @GenericGenerator(name = "image-type-sequence", strategy = "increment") + @Column(updatable = false, nullable = false) + public Long id; + + @Column(name = "display_name", nullable = false) + private String displayName; + + @Column(name = "value", nullable = false, unique = true) + private String value; + + @Column(name = "size_min", nullable = false) + private Integer sizeMin; + + @Column(name = "size_max") + private Integer sizeMax; + + @Column(name = "size_default") + private Integer sizeDefault; + + @Column(name = "size_required", nullable = false) + private Boolean sizeRequired; + + @Column(name = "d_min") + private Integer dMin; + + @Column(name = "d_max") + private Integer dMax; + + @Column(name = "d_default") + private Integer dDefault; + + @Column(name = "d_required", nullable = false) + private Boolean dRequired; + + @Column(nullable = false) + private String documentation; + + @Column(name = "is_quoted", nullable = false) + private Boolean quoted; + + @Column(name = "is_buildable", nullable = false) + private Boolean buildable; + + @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL, CascadeType.PERSIST}) + @JoinColumns({ + @JoinColumn(name = "image_id", referencedColumnName = "id") + }) + private ContainerImage image; + +} diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/ViewColumn.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/ViewColumn.java index ff18c0137d..b9eff2c694 100644 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/ViewColumn.java +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/ViewColumn.java @@ -1,6 +1,5 @@ package at.tuwien.entities.database; -import at.tuwien.entities.container.image.ContainerImageDate; import at.tuwien.entities.database.table.columns.TableColumnType; import lombok.*; import org.hibernate.annotations.GenericGenerator; @@ -25,11 +24,6 @@ public class ViewColumn implements Comparable<ViewColumn> { @Column(updatable = false, nullable = false) private Long id; - @ToString.Exclude - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "dfid", referencedColumnName = "id") - private ContainerImageDate dateFormat; - @ToString.Exclude @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE}) @JoinColumns({ diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java index dd59e21003..9ddf89c453 100644 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumn.java @@ -1,6 +1,5 @@ package at.tuwien.entities.database.table.columns; -import at.tuwien.entities.container.image.ContainerImageDate; import at.tuwien.entities.database.table.Table; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.*; @@ -38,11 +37,6 @@ public class TableColumn implements Comparable<TableColumn> { @Column(updatable = false, nullable = false) private Long id; - @ToString.Exclude - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "dfid", referencedColumnName = "id") - private ContainerImageDate dateFormat; - @ToString.Exclude @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE}) @JoinColumns({ diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java index f6c96f133d..0555e1c62c 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -4,6 +4,7 @@ import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerCreateDto; import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.container.image.DataTypeDto; import at.tuwien.api.container.image.ImageBriefDto; import at.tuwien.api.container.image.ImageCreateDto; import at.tuwien.api.container.image.ImageDto; @@ -55,6 +56,7 @@ import at.tuwien.api.user.external.ExternalResultType; import at.tuwien.api.user.external.affiliation.ExternalAffiliationDto; import at.tuwien.entities.container.Container; import at.tuwien.entities.container.image.ContainerImage; +import at.tuwien.entities.container.image.DataType; import at.tuwien.entities.database.*; import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.columns.TableColumn; @@ -86,6 +88,14 @@ public interface MetadataMapper { org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); + @Mappings({ + @Mapping(target = "dMin", source = "DMin"), + @Mapping(target = "dMax", source = "DMax"), + @Mapping(target = "dDefault", source = "DDefault"), + @Mapping(target = "dRequired", source = "DRequired") + }) + DataTypeDto dataTypeToDataTypeDto(DataType data); + BannerMessageDto bannerMessageToBannerMessageDto(BannerMessage data); BannerMessageBriefDto bannerMessageToBannerMessageBriefDto(BannerMessage data); diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java index 9cdcfdedf9..f50f916ac4 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java @@ -101,18 +101,19 @@ public class AccessEndpoint { data.getType()); final Database database = databaseService.findById(databaseId); final User user = userService.findByUsername(principal.getName()); - if (database.getOwner().equals(user)) { - log.error("Failed to give access to user with id {}: not owner", userId); - throw new NotAllowedException("Failed to give access to user with id " + userId + ": not owner"); + if (!database.getOwner().equals(user)) { + log.error("Failed to create access: not owner"); + throw new NotAllowedException("Failed to create access: not owner"); } + final User otherUser = userService.findById(userId); try { - accessService.find(database, user); - log.error("Failed to give access to user with id {}: already has access", userId); - throw new NotAllowedException("Failed to give access to user with id " + userId + ": already has access"); + accessService.find(database, otherUser); + log.error("Failed to create access to user with id {}: already has access", userId); + throw new NotAllowedException("Failed to create access to user with id " + userId + ": already has access"); } catch (AccessNotFoundException e) { /* ignore */ } - accessService.create(database, user, data.getType()); + accessService.create(database, otherUser, data.getType()); return ResponseEntity.accepted() .build(); } @@ -163,12 +164,13 @@ public class AccessEndpoint { data.getType()); final Database database = databaseService.findById(databaseId); final User user = userService.findByUsername(principal.getName()); - if (database.getOwner().equals(user)) { - log.error("Failed to give access to user with id {}: not owner", userId); - throw new NotAllowedException("Failed to give access to user with id " + userId + ": not owner"); + if (!database.getOwner().equals(user)) { + log.error("Failed to update access: not owner"); + throw new NotAllowedException("Failed to update access: not owner"); } - accessService.find(database, user); - accessService.update(database, user, data.getType()); + final User otherUser = userService.findById(userId); + accessService.find(database, otherUser); + accessService.update(database, otherUser, data.getType()); return ResponseEntity.accepted() .build(); } @@ -211,8 +213,8 @@ public class AccessEndpoint { log.trace("principal is allowed to check foreign user access"); } final Database database = databaseService.findById(databaseId); - final User user = userService.findById(userId); - final DatabaseAccess access = accessService.find(database, user); + final User otherUser = userService.findById(userId); + final DatabaseAccess access = accessService.find(database, otherUser); final DatabaseAccessDto dto = databaseMapper.databaseAccessToDatabaseAccessDto(access); log.trace("check access resulted in dto {}", dto); return ResponseEntity.ok(dto); @@ -263,11 +265,12 @@ public class AccessEndpoint { final Database database = databaseService.findById(databaseId); final User user = userService.findByUsername(principal.getName()); if (!database.getOwner().equals(user)) { - log.error("Failed to revoke access to user with id {}: not owner", user.getId()); - throw new NotAllowedException("Failed to revoke access to user with id " + user.getId() + ": not owner"); + log.error("Failed to revoke access: not owner"); + throw new NotAllowedException("Failed to revoke access: not owner"); } - accessService.find(database, user); - accessService.delete(database, user); + final User otherUser = userService.findById(userId); + accessService.find(database, otherUser); + accessService.delete(database, otherUser); return ResponseEntity.accepted() .build(); } diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java index e89a38b6f6..d5c316fed9 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java @@ -3,6 +3,7 @@ package at.tuwien.endpoints; import at.tuwien.api.database.*; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.entities.container.Container; +import at.tuwien.entities.container.image.DataType; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; import at.tuwien.entities.user.User; diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 4fb8240b1d..738e30d4e4 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -3,9 +3,7 @@ package at.tuwien.endpoints; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.api.semantics.EntityDto; @@ -16,7 +14,10 @@ import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.*; +import at.tuwien.service.DatabaseService; +import at.tuwien.service.EntityService; +import at.tuwien.service.TableService; +import at.tuwien.service.UserService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; import io.micrometer.observation.annotation.Observed; @@ -39,7 +40,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.security.Principal; -import java.util.*; +import java.util.List; import java.util.stream.Collectors; @Log4j2 @@ -349,15 +350,6 @@ public class TableEndpoint { final Database database = databaseService.findById(databaseId); endpointValidator.validateOnlyAccess(database, principal, true); endpointValidator.validateColumnCreateConstraints(data); - final List<ColumnCreateDto> failedDateColumns = data.getColumns() - .stream() - .filter(column -> List.of(ColumnTypeDto.DATE, ColumnTypeDto.DATETIME, ColumnTypeDto.TIME, ColumnTypeDto.TIMESTAMP).contains(column.getType())) - .filter(column -> Objects.isNull(column.getDfid())) - .toList(); - if (!failedDateColumns.isEmpty()) { - log.error("Failed to create table: date column(s) {} do not contain date format id", failedDateColumns.stream().map(ColumnCreateDto::getName).toList()); - throw new MalformedException("Failed to create table: date column(s) " + failedDateColumns.stream().map(ColumnCreateDto::getName).toList() + " do not contain date format id"); - } final Table table = tableService.createTable(database, data, principal); final TableDto dto = metadataMapper.customTableToTableDto(table); log.info("Created table with id {}", dto.getId()); diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java index b81a8142f7..34082d18c1 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/UserEndpoint.java @@ -5,7 +5,10 @@ import at.tuwien.api.auth.RefreshTokenRequestDto; import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.api.keycloak.TokenDto; -import at.tuwien.api.user.*; +import at.tuwien.api.user.UserBriefDto; +import at.tuwien.api.user.UserDto; +import at.tuwien.api.user.UserPasswordDto; +import at.tuwien.api.user.UserUpdateDto; import at.tuwien.entities.database.Database; import at.tuwien.entities.user.User; import at.tuwien.exception.*; @@ -26,6 +29,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -284,7 +288,13 @@ public class UserEndpoint { } } final UserDto dto = userMapper.userToUserDto(user); - return ResponseEntity.ok() + final HttpHeaders headers = new HttpHeaders(); + if (UserUtil.isSystem(principal)) { + headers.set("X-Username", user.getUsername()); + headers.set("X-Password", user.getMariadbPassword()); + } + return ResponseEntity.status(HttpStatus.OK) + .headers(headers) .body(dto); } diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java index 7f05bf84a5..a3fb6833ce 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java @@ -68,9 +68,9 @@ public class EndpointValidator { if (data == null) { throw new MalformedException("Validation failed: table data is null"); } - final List<ColumnTypeDto> needSize = List.of(ColumnTypeDto.CHAR, ColumnTypeDto.VARCHAR, ColumnTypeDto.BINARY, ColumnTypeDto.VARBINARY, ColumnTypeDto.BIT, ColumnTypeDto.TINYINT, ColumnTypeDto.SMALLINT, ColumnTypeDto.MEDIUMINT, ColumnTypeDto.INT); - final List<ColumnTypeDto> needSizeAndD = List.of(ColumnTypeDto.DOUBLE, ColumnTypeDto.DECIMAL); - final List<ColumnTypeDto> needDateFormat = List.of(ColumnTypeDto.DATETIME, ColumnTypeDto.TIMESTAMP, ColumnTypeDto.TIME); + final List<ColumnTypeDto> needSize = List.of(ColumnTypeDto.VARCHAR, ColumnTypeDto.BINARY, ColumnTypeDto.VARBINARY); + final List<ColumnTypeDto> canHaveSize = List.of(ColumnTypeDto.CHAR, ColumnTypeDto.VARCHAR, ColumnTypeDto.BINARY, ColumnTypeDto.VARBINARY, ColumnTypeDto.BIT, ColumnTypeDto.TINYINT, ColumnTypeDto.SMALLINT, ColumnTypeDto.MEDIUMINT, ColumnTypeDto.INT); + final List<ColumnTypeDto> canHaveSizeAndD = List.of(ColumnTypeDto.DOUBLE, ColumnTypeDto.DECIMAL); /* check size */ final Optional<ColumnCreateDto> optional0 = data.getColumns() .stream() @@ -78,36 +78,37 @@ public class EndpointValidator { .filter(c -> needSize.contains(c.getType())) .findFirst(); if (optional0.isPresent()) { - log.error("Validation failed: column {} needs size parameter", optional0.get().getName()); - throw new MalformedException("Validation failed: column " + optional0.get().getName() + " needs size parameter"); + log.error("Validation failed: column {} need size parameter", optional0.get().getName()); + throw new MalformedException("Validation failed: column " + optional0.get().getName() + " need size parameter"); } - /* check size and d */ - final Optional<ColumnCreateDto> optional1 = data.getColumns() + final Optional<ColumnCreateDto> optional0a = data.getColumns() .stream() - .filter(c -> needSizeAndD.contains(c.getType())) - .filter(c -> Objects.isNull(c.getSize()) || Objects.isNull(c.getD())) + .filter(c -> !Objects.isNull(c.getSize())) + .filter(c -> canHaveSize.contains(c.getType())) + .filter(c -> c.getSize() < 0) .findFirst(); - if (optional1.isPresent()) { - log.error("Validation failed: column {} needs size and d parameter", optional1.get().getName()); - throw new MalformedException("Validation failed: column " + optional1.get().getName() + " needs size and d parameter"); + if (optional0a.isPresent()) { + log.error("Validation failed: column {} needs positive size parameter", optional0a.get().getName()); + throw new MalformedException("Validation failed: column " + optional0a.get().getName() + " needs positive size parameter"); } - final Optional<ColumnCreateDto> optional1a = data.getColumns() + final Optional<ColumnCreateDto> optional0b = data.getColumns() .stream() - .filter(c -> needSizeAndD.contains(c.getType())) - .filter(c -> c.getSize() > 65 || c.getD() > 38) + .filter(c -> !Objects.isNull(c.getSize())) + .filter(c -> !canHaveSize.contains(c.getType())) .findFirst(); - if (optional1a.isPresent()) { - log.error("Validation failed: column {} needs size (max 65) and d (max 30)", optional1a.get().getName()); - throw new MalformedException("Validation failed: column " + optional1a.get().getName() + " needs size (max 65) and d (max 30)"); + if (optional0b.isPresent()) { + log.error("Validation failed: column {} cannot have size parameter", optional0b.get().getName()); + throw new MalformedException("Validation failed: column " + optional0b.get().getName() + " cannot have size parameter"); } - final Optional<ColumnCreateDto> optional1b = data.getColumns() + /* check size and d */ + final Optional<ColumnCreateDto> optional1 = data.getColumns() .stream() - .filter(c -> needSizeAndD.contains(c.getType())) - .filter(c -> c.getSize() < c.getD()) + .filter(c -> Objects.isNull(c.getSize()) ^ Objects.isNull(c.getD())) + .filter(c -> canHaveSizeAndD.contains(c.getType())) .findFirst(); - if (optional1b.isPresent()) { - log.error("Validation failed: column {} needs size >= d", optional1b.get().getName()); - throw new MalformedException("Validation failed: column " + optional1b.get().getName() + " needs size >= d"); + if (optional1.isPresent()) { + log.error("Validation failed: column {} either needs both size and d parameter or none (use default)", optional1.get().getName()); + throw new MalformedException("Validation failed: column " + optional1.get().getName() + " either needs both size and d parameter or none (use default)"); } /* check enum */ final Optional<ColumnCreateDto> optional2 = data.getColumns() @@ -129,16 +130,6 @@ public class EndpointValidator { log.error("Validation failed: column {} needs at least 1 allowed set value", optional3.get().getName()); throw new MalformedException("Validation failed: column " + optional3.get().getName() + " needs at least 1 allowed set value"); } - /* check date */ - final Optional<ColumnCreateDto> optional4 = data.getColumns() - .stream() - .filter(c -> needDateFormat.contains(c.getType())) - .filter(c -> Objects.isNull(c.getDfid())) - .findFirst(); - if (optional4.isPresent()) { - log.error("Validation failed: column {} needs a format", optional4.get().getName()); - throw new MalformedException("Validation failed: column " + optional4.get().getName() + " needs a format"); - } } public boolean validateOnlyMineOrWriteAccessOrHasRole(User owner, Principal principal, DatabaseAccess access, String role) { diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml b/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml index e2c9df6a59..793a2b6021 100644 --- a/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml +++ b/dbrepo-metadata-service/rest-service/src/main/resources/application-local.yml @@ -66,7 +66,7 @@ dbrepo: searchService: http://localhost analyseService: http://localhost dataService: http://localhost:9093 - brokerService: http://localhost/admin/broker + brokerService: http://localhost:15672 authService: http://localhost/api/auth storageService: http://localhost/api/storage rorService: https://api.ror.org diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json b/dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json new file mode 100644 index 0000000000..3779d12cbe --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json @@ -0,0 +1,15 @@ +[ + { + "name": "", + "size": { + "min": 0, + "required": true + }, + "d": { + "required": false + }, + "documentation": "https://mariadb.com/kb/en/bigint/", + "quoted": false, + "buildable": true + } +] \ No newline at end of file diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java index 69d817afb7..7c6061ed1e 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/AccessEndpointUnitTest.java @@ -1,17 +1,18 @@ package at.tuwien.endpoints; -import at.tuwien.mapper.MetadataMapper; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.database.AccessTypeDto; import at.tuwien.api.database.DatabaseAccessDto; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; import at.tuwien.entities.user.User; import at.tuwien.exception.*; +import at.tuwien.mapper.MetadataMapper; import at.tuwien.repository.DatabaseRepository; import at.tuwien.repository.UserRepository; import at.tuwien.service.AccessService; +import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -51,13 +52,18 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { @Autowired private MetadataMapper metadataMapper; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void create_anonymous_fails() { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - generic_create(null, USER_2_ID, null, null); + generic_create(null, null, null, null); }); } @@ -67,7 +73,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - generic_create(USER_2_PRINCIPAL, USER_4_ID, USER_4_USERNAME, USER_4); + generic_create(USER_2_PRINCIPAL, USER_2, USER_4_ID, USER_4); }); } @@ -82,7 +88,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_USER_1_READ_ACCESS); /* test */ - generic_create(USER_2_PRINCIPAL, USER_2_ID, USER_2_USERNAME, USER_2); + generic_create(USER_1_PRINCIPAL, USER_1, USER_2_ID, USER_2); } @Test @@ -129,7 +135,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - generic_update(null, USER_4_USERNAME, USER_4, null, null); + generic_update(null, null, null, null, null); }); } @@ -138,8 +144,8 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { public void update_hasRoleNoAccess_fails() { /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_update(null, USER_4_USERNAME, USER_4, USER_1_PRINCIPAL, USER_1); + assertThrows(AccessNotFoundException.class, () -> { + generic_update(USER_1_PRINCIPAL, USER_1, USER_4_ID, USER_4, null); }); } @@ -149,7 +155,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - generic_update(null, USER_4_USERNAME, USER_4, USER_4_PRINCIPAL, USER_4); + generic_update(USER_4_PRINCIPAL, USER_4, USER_1_ID, USER_1, null); }); } @@ -165,7 +171,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { .update(eq(DATABASE_1), eq(USER_2), any(AccessTypeDto.class)); /* test */ - generic_update(DATABASE_1_USER_2_WRITE_OWN_ACCESS, USER_2_USERNAME, USER_2, USER_2_PRINCIPAL, USER_2); + generic_update(USER_1_PRINCIPAL, USER_1, USER_2_ID, USER_2, DATABASE_1_USER_2_WRITE_OWN_ACCESS); } @Test @@ -174,7 +180,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - generic_revoke(USER_1_PRINCIPAL, USER_1); + generic_revoke(null, null, USER_1_ID, USER_1); }); } @@ -184,7 +190,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { - generic_revoke(USER_4_PRINCIPAL, USER_4); + generic_revoke(USER_4_PRINCIPAL, USER_4, USER_1_ID, USER_1); }); } @@ -200,14 +206,14 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { .delete(DATABASE_1, USER_2); /* test */ - generic_revoke(USER_1_PRINCIPAL, USER_1); + generic_revoke(USER_1_PRINCIPAL, USER_1, USER_2_ID, USER_2); } /* ################################################################################################### */ /* ## GENERIC TEST CASES ## */ /* ################################################################################################### */ - protected void generic_create(Principal principal, UUID userId, String username, User user) + protected void generic_create(Principal principal, User principalUser, UUID userId, User user) throws NotAllowedException, DataServiceException, DataServiceConnectionException, UserNotFoundException, DatabaseNotFoundException, AccessNotFoundException, SearchServiceException, SearchServiceConnectionException { @@ -218,11 +224,18 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { doThrow(AccessNotFoundException.class) .when(accessService) .find(DATABASE_1, user); + if (principalUser != null) { + when(userRepository.findByUsername(principal.getName())) + .thenReturn(Optional.of(principalUser)); + } else { + when(userRepository.findByUsername(anyString())) + .thenReturn(Optional.empty()); + } if (user != null) { - when(userRepository.findByUsername(username)) + when(userRepository.findById(userId)) .thenReturn(Optional.of(user)); } else { - when(userRepository.findByUsername(anyString())) + when(userRepository.findById(any(UUID.class))) .thenReturn(Optional.empty()); } @@ -268,61 +281,62 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { } } - protected void generic_update(DatabaseAccess access, String otherUsername, User otherUser, Principal principal, - User user) throws NotAllowedException, DataServiceException, DataServiceConnectionException, - AccessNotFoundException, UserNotFoundException, DatabaseNotFoundException, SearchServiceException, - SearchServiceConnectionException { + protected void generic_update(Principal principal, User principalUser, UUID userId, User user, + DatabaseAccess access) throws NotAllowedException, DataServiceException, + DataServiceConnectionException, AccessNotFoundException, UserNotFoundException, DatabaseNotFoundException, + SearchServiceException, SearchServiceConnectionException { /* mock */ when(databaseRepository.findById(DATABASE_1_ID)) .thenReturn(Optional.of(DATABASE_1)); if (access != null) { - log.trace("mock access {} for user with id {} for database with id {}", access.getType(), USER_1_ID, DATABASE_1_ID); - when(accessService.find(DATABASE_1, USER_1)) + log.trace("mock access {} for user with id {} for database with id {}", access.getType(), userId, DATABASE_1_ID); + when(accessService.find(DATABASE_1, user)) .thenReturn(access); } else { - log.trace("mock no access for user with id {} for database with id {}", USER_1_ID, DATABASE_1_ID); + log.trace("mock no access for user with id {} for database with id {}", userId, DATABASE_1_ID); doThrow(AccessNotFoundException.class) .when(accessService) - .find(DATABASE_1, USER_1); + .find(DATABASE_1, user); } - if (otherUsername != null) { - when(userRepository.findByUsername(otherUsername)) - .thenReturn(Optional.of(otherUser)); + if (userId != null) { + when(userRepository.findById(userId)) + .thenReturn(Optional.of(user)); } else { - when(userRepository.findByUsername(anyString())) + when(userRepository.findById(any(UUID.class))) .thenReturn(Optional.empty()); } if (principal != null) { when(userRepository.findByUsername(principal.getName())) - .thenReturn(Optional.of(user)); + .thenReturn(Optional.of(principalUser)); } else { when(userRepository.findByUsername(anyString())) .thenReturn(Optional.empty()); } /* test */ - final ResponseEntity<?> response = accessEndpoint.update(DATABASE_1_ID, USER_1_ID, UPDATE_DATABASE_ACCESS_READ_DTO, principal); + final ResponseEntity<?> response = accessEndpoint.update(DATABASE_1_ID, userId, UPDATE_DATABASE_ACCESS_READ_DTO, principal); assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); assertNull(response.getBody()); } - protected void generic_revoke(Principal principal, User user) throws DataServiceConnectionException, - NotAllowedException, DataServiceException, UserNotFoundException, DatabaseNotFoundException, - AccessNotFoundException, SearchServiceException, SearchServiceConnectionException { + protected void generic_revoke(Principal principal, User principalUser, UUID userId, User user) + throws DataServiceConnectionException, NotAllowedException, DataServiceException, UserNotFoundException, + DatabaseNotFoundException, AccessNotFoundException, SearchServiceException, + SearchServiceConnectionException { /* mock */ - when(accessService.find(any(Database.class), eq(user))) - .thenReturn(DATABASE_1_USER_1_READ_ACCESS); when(databaseRepository.findById(DATABASE_1_ID)) .thenReturn(Optional.of(DATABASE_1)); if (principal != null) { when(userRepository.findByUsername(principal.getName())) - .thenReturn(Optional.of(user)); + .thenReturn(Optional.of(principalUser)); } + when(userRepository.findById(userId)) + .thenReturn(Optional.of(user)); /* test */ - final ResponseEntity<?> response = accessEndpoint.revoke(DATABASE_1_ID, USER_1_ID, principal); + final ResponseEntity<?> response = accessEndpoint.revoke(DATABASE_1_ID, userId, principal); assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); assertNull(response.getBody()); } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ActuatorComponentTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ActuatorComponentTest.java index 238dec0db1..78b7f086c3 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ActuatorComponentTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ActuatorComponentTest.java @@ -2,6 +2,7 @@ package at.tuwien.endpoints; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +25,11 @@ public class ActuatorComponentTest extends AbstractUnitTest { @Autowired private MockMvc mockMvc; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test public void actuatorInfo_succeeds() throws Exception { this.mockMvc.perform(get("/actuator/info")) diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ConceptEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ConceptEndpointUnitTest.java index 6698be6995..d48317f119 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ConceptEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ConceptEndpointUnitTest.java @@ -4,6 +4,7 @@ import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.database.table.columns.concepts.ConceptDto; import at.tuwien.service.ConceptService; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +32,11 @@ public class ConceptEndpointUnitTest extends AbstractUnitTest { @Autowired private ConceptEndpoint conceptEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void findAllConcepts_anonymous_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ImageEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ImageEndpointUnitTest.java index 3d1c37d363..c5c3c24cfd 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ImageEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ImageEndpointUnitTest.java @@ -9,6 +9,7 @@ import at.tuwien.entities.container.image.ContainerImage; import at.tuwien.exception.*; import at.tuwien.service.impl.ImageServiceImpl; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +38,11 @@ public class ImageEndpointUnitTest extends AbstractUnitTest { @Autowired private ImageEndpoint imageEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void findAll_anonymous_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/LicenseEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/LicenseEndpointUnitTest.java index 5be4624021..f45dd85fb4 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/LicenseEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/LicenseEndpointUnitTest.java @@ -4,6 +4,7 @@ import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.database.LicenseDto; import at.tuwien.repository.LicenseRepository; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -30,6 +31,11 @@ public class LicenseEndpointUnitTest extends AbstractUnitTest { @Autowired private LicenseEndpoint licenseEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test public void list_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MessageEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MessageEndpointUnitTest.java index cea67bc510..59166b7200 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MessageEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MessageEndpointUnitTest.java @@ -8,6 +8,7 @@ import at.tuwien.api.maintenance.BannerMessageUpdateDto; import at.tuwien.entities.maintenance.BannerMessage; import at.tuwien.service.BannerMessageService; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +36,11 @@ public class MessageEndpointUnitTest extends AbstractUnitTest { @Autowired private MessageEndpoint messageEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void list_anonymous_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java index d024978449..7ed9945342 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/MetadataEndpointUnitTest.java @@ -6,6 +6,7 @@ import at.tuwien.oaipmh.OaiRecordParameters; import at.tuwien.repository.IdentifierRepository; import at.tuwien.utils.XmlUtils; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +34,11 @@ public class MetadataEndpointUnitTest extends AbstractUnitTest { @Autowired private MetadataEndpoint metadataEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void identify_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/OntologyEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/OntologyEndpointUnitTest.java index fc20a0b9e3..b7af494253 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/OntologyEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/OntologyEndpointUnitTest.java @@ -12,6 +12,7 @@ import lombok.extern.log4j.Log4j2; import org.apache.jena.sys.JenaSystem; import org.hibernate.HibernateException; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -51,6 +52,11 @@ public class OntologyEndpointUnitTest extends AbstractUnitTest { JenaSystem.init(); } + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void findAll_anonymous_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java index 154ebda86c..69f9a8dadd 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/TableEndpointUnitTest.java @@ -167,101 +167,6 @@ public class TableEndpointUnitTest extends AbstractUnitTest { }); } - @Test - @WithMockUser(username = USER_3_USERNAME, authorities = {"create-table"}) - public void create_publicDecimalColumnSizeMissing_fails() { - final TableCreateDto request = TableCreateDto.builder() - .name("Some Table") - .description("Some Description") - .columns(List.of(ColumnCreateDto.builder() - .name("ID") - .type(ColumnTypeDto.DECIMAL) - .build())) - .constraints(null) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - generic_create(DATABASE_3_ID, DATABASE_3, request, USER_1_PRINCIPAL, USER_1, DATABASE_3_USER_1_WRITE_OWN_ACCESS); - }); - } - - @Test - @WithMockUser(username = USER_3_USERNAME, authorities = {"create-table"}) - public void create_publicDateFormatMissing_fails() { - final TableCreateDto request = TableCreateDto.builder() - .name("Some Table") - .description("Some Description") - .columns(List.of(ColumnCreateDto.builder() - .name("timestamp") - .type(ColumnTypeDto.DATE) - .build())) - .constraints(null) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - generic_create(DATABASE_3_ID, DATABASE_3, request, USER_1_PRINCIPAL, USER_1, DATABASE_3_USER_1_WRITE_OWN_ACCESS); - }); - } - - @Test - @WithMockUser(username = USER_3_USERNAME, authorities = {"create-table"}) - public void create_publicDateTimeFormatMissing_fails() { - final TableCreateDto request = TableCreateDto.builder() - .name("Some Table") - .description("Some Description") - .columns(List.of(ColumnCreateDto.builder() - .name("timestamp") - .type(ColumnTypeDto.DATETIME) - .build())) - .constraints(null) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - generic_create(DATABASE_3_ID, DATABASE_3, request, USER_1_PRINCIPAL, USER_1, DATABASE_3_USER_1_WRITE_OWN_ACCESS); - }); - } - - @Test - @WithMockUser(username = USER_3_USERNAME, authorities = {"create-table"}) - public void create_publicTimeFormatMissing_fails() { - final TableCreateDto request = TableCreateDto.builder() - .name("Some Table") - .description("Some Description") - .columns(List.of(ColumnCreateDto.builder() - .name("timestamp") - .type(ColumnTypeDto.TIME) - .build())) - .constraints(null) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - generic_create(DATABASE_3_ID, DATABASE_3, request, USER_1_PRINCIPAL, USER_1, DATABASE_3_USER_1_WRITE_OWN_ACCESS); - }); - } - - @Test - @WithMockUser(username = USER_3_USERNAME, authorities = {"create-table"}) - public void create_publicTimestampFormatMissing_fails() { - final TableCreateDto request = TableCreateDto.builder() - .name("Some Table") - .description("Some Description") - .columns(List.of(ColumnCreateDto.builder() - .name("timestamp") - .type(ColumnTypeDto.TIMESTAMP) - .build())) - .constraints(null) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - generic_create(DATABASE_3_ID, DATABASE_3, request, USER_1_PRINCIPAL, USER_1, DATABASE_3_USER_1_WRITE_OWN_ACCESS); - }); - } - @Test @WithMockUser(username = USER_3_USERNAME, authorities = {"create-table"}) public void create_publicDecimalColumnSizeTooSmall_fails() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UnitEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UnitEndpointUnitTest.java index b5fc19681c..7d74e615be 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UnitEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UnitEndpointUnitTest.java @@ -4,6 +4,7 @@ import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.database.table.columns.concepts.UnitDto; import at.tuwien.service.UnitService; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +32,11 @@ public class UnitEndpointUnitTest extends AbstractUnitTest { @Autowired private UnitEndpoint unitEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void findAllUnits_anonymous_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java index ad4cef5bbe..da86dd9344 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/UserEndpointUnitTest.java @@ -8,6 +8,7 @@ import at.tuwien.exception.*; import at.tuwien.service.AuthenticationService; import at.tuwien.service.UserService; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +44,11 @@ public class UserEndpointUnitTest extends AbstractUnitTest { @Autowired private UserEndpoint userEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void findAll_anonymous_succeeds() throws UserNotFoundException { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java index 74e418f42b..b4291ce902 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/ViewEndpointUnitTest.java @@ -14,6 +14,7 @@ import at.tuwien.service.DatabaseService; import at.tuwien.service.UserService; import at.tuwien.service.ViewService; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -52,6 +53,11 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @Autowired private ViewEndpoint viewEndpoint; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test @WithAnonymousUser public void findAll_publicAnonymous_succeeds() throws ViewNotFoundException, UserNotFoundException, diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/BrokerServiceGatewayUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/BrokerServiceGatewayUnitTest.java index a812bd5de4..fc2a9c2c22 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/BrokerServiceGatewayUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/BrokerServiceGatewayUnitTest.java @@ -1,5 +1,6 @@ package at.tuwien.gateway; +import at.tuwien.api.amqp.GrantExchangePermissionsDto; import at.tuwien.test.AbstractUnitTest; import at.tuwien.exception.*; import lombok.extern.log4j.Log4j2; @@ -33,6 +34,12 @@ public class BrokerServiceGatewayUnitTest extends AbstractUnitTest { @Autowired private BrokerServiceGateway brokerServiceGateway; + private final GrantExchangePermissionsDto WRITE_ALL_PERMISSIONS = GrantExchangePermissionsDto.builder() + .exchange("dbrepo") + .read("^(dbrepo\\.1\\..*)$") /* WRITE_ALL */ + .write("^(dbrepo\\.1\\..*)$") + .build(); + @Test public void grantTopicPermission_exchangeNoRightsBefore_succeeds() throws BrokerServiceException, BrokerServiceConnectionException { @@ -178,43 +185,40 @@ public class BrokerServiceGatewayUnitTest extends AbstractUnitTest { @Test public void grantExchangePermission_succeeds() throws BrokerServiceException, BrokerServiceConnectionException { - final ResponseEntity<Void> mock = ResponseEntity.status(HttpStatus.CREATED) - .build(); /* mock */ when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(mock); + .thenReturn(ResponseEntity.status(HttpStatus.CREATED) + .build()); /* test */ - brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, USER_1_RABBITMQ_GRANT_TOPIC_DTO); + brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, WRITE_ALL_PERMISSIONS); } @Test public void grantExchangePermission_exists_succeeds() throws BrokerServiceException, BrokerServiceConnectionException { - final ResponseEntity<Void> mock = ResponseEntity.status(HttpStatus.NO_CONTENT) - .build(); /* mock */ when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(mock); + .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) + .build()); /* test */ - brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, USER_1_RABBITMQ_GRANT_TOPIC_DTO); + brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, WRITE_ALL_PERMISSIONS); } @Test public void grantExchangePermission_unexpected2_fails() { - final ResponseEntity<Void> mock = ResponseEntity.status(HttpStatus.BAD_GATEWAY) - .build(); /* mock */ when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) - .thenReturn(mock); + .thenReturn(ResponseEntity.status(HttpStatus.BAD_GATEWAY) + .build()); /* test */ assertThrows(BrokerServiceException.class, () -> { - brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, USER_1_RABBITMQ_GRANT_TOPIC_DTO); + brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, WRITE_ALL_PERMISSIONS); }); } @@ -228,7 +232,7 @@ public class BrokerServiceGatewayUnitTest extends AbstractUnitTest { /* test */ assertThrows(BrokerServiceConnectionException.class, () -> { - brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, USER_1_RABBITMQ_GRANT_TOPIC_DTO); + brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, WRITE_ALL_PERMISSIONS); }); } @@ -242,7 +246,7 @@ public class BrokerServiceGatewayUnitTest extends AbstractUnitTest { /* test */ assertThrows(BrokerServiceException.class, () -> { - brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, USER_1_RABBITMQ_GRANT_TOPIC_DTO); + brokerServiceGateway.grantExchangePermission(USER_1_USERNAME, WRITE_ALL_PERMISSIONS); }); } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java index 4125529155..fa1cd5d4be 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java @@ -6,6 +6,7 @@ import at.tuwien.exception.*; import at.tuwien.gateway.KeycloakGateway; import dasniko.testcontainers.keycloak.KeycloakContainer; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +32,11 @@ public class AuthenticationServiceIntegrationTest extends AbstractUnitTest { @Autowired private KeycloakGateway keycloakGateway; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Container private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:24.0") .withImagePullPolicy(PullPolicy.alwaysPull()) diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/BrokerServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/BrokerServiceIntegrationTest.java index c9a2ad62b6..d04409c87b 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/BrokerServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/BrokerServiceIntegrationTest.java @@ -1,13 +1,15 @@ package at.tuwien.service; -import at.tuwien.config.RabbitConfig; -import at.tuwien.exception.*; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.amqp.GrantExchangePermissionsDto; +import at.tuwien.api.amqp.GrantVirtualHostPermissionsDto; import at.tuwien.api.amqp.TopicPermissionDto; import at.tuwien.api.amqp.VirtualHostPermissionDto; +import at.tuwien.config.RabbitConfig; import at.tuwien.entities.database.DatabaseAccess; import at.tuwien.entities.user.User; +import at.tuwien.exception.BrokerServiceConnectionException; +import at.tuwien.exception.BrokerServiceException; +import at.tuwien.test.AbstractUnitTest; import at.tuwien.utils.AmqpUtils; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; @@ -27,7 +29,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.List; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; @Log4j2 @Testcontainers @@ -167,10 +169,15 @@ public class BrokerServiceIntegrationTest extends AbstractUnitTest { protected VirtualHostPermissionDto setVirtualHostPermissions_generic() throws BrokerServiceException, BrokerServiceConnectionException { + final GrantVirtualHostPermissionsDto permissions = GrantVirtualHostPermissionsDto.builder() + .configure("") + .read("") + .write("") + .build(); final AmqpUtils amqpUtils = new AmqpUtils(rabbitContainer.getHttpUrl()); /* mock */ - amqpUtils.setVirtualHostPermissions(REALM_DBREPO_NAME, USER_1_USERNAME, USER_1_RABBITMQ_GRANT_DTO); + amqpUtils.setVirtualHostPermissions(REALM_DBREPO_NAME, USER_1_USERNAME, permissions); /* test */ brokerService.setVirtualHostPermissions(USER_1); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServicePersistenceTest.java index b4bd67f88d..51b6df4d27 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServicePersistenceTest.java @@ -82,8 +82,6 @@ public class DatabaseServicePersistenceTest extends AbstractUnitTest { assertEquals(IMAGE_1_DRIVER, response.getContainer().getImage().getDriverClass()); assertEquals(IMAGE_1_REGISTRY, response.getContainer().getImage().getRegistry()); assertEquals(IMAGE_1_PORT, response.getContainer().getImage().getDefaultPort()); - assertNotNull(response.getContainer().getImage().getDateFormats()); - assertEquals(4, response.getContainer().getImage().getDateFormats().size()); /* creator */ assertNotNull(response.getCreator()); assertEquals(USER_1_ID, response.getCreator().getId()); @@ -124,8 +122,6 @@ public class DatabaseServicePersistenceTest extends AbstractUnitTest { assertEquals(IMAGE_1_DRIVER, response.getContainer().getImage().getDriverClass()); assertEquals(IMAGE_1_REGISTRY, response.getContainer().getImage().getRegistry()); assertEquals(IMAGE_1_PORT, response.getContainer().getImage().getDefaultPort()); - assertNotNull(response.getContainer().getImage().getDateFormats()); - assertEquals(4, response.getContainer().getImage().getDateFormats().size()); /* creator */ assertNotNull(response.getCreator()); assertEquals(USER_1_ID, response.getCreator().getId()); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java index 68d6e1a93d..1e7633b851 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java @@ -1,6 +1,5 @@ package at.tuwien.service; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.database.DatabaseModifyVisibilityDto; import at.tuwien.api.database.internal.CreateDatabaseDto; import at.tuwien.entities.database.Database; @@ -8,8 +7,8 @@ import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.gateway.DataServiceGateway; import at.tuwien.gateway.SearchServiceGateway; -import at.tuwien.repository.ContainerRepository; import at.tuwien.repository.DatabaseRepository; +import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,10 +22,10 @@ import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; @Log4j2 @SpringBootTest @@ -212,7 +211,11 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { SearchServiceConnectionException { /* test */ - generic_modifyOwner(DATABASE_1, USER_1); + final Database response = generic_modifyOwner(DATABASE_1, USER_2); + assertEquals(USER_2, response.getOwner()); + assertEquals(USER_2_ID, response.getOwnedBy()); + assertEquals(USER_2, response.getContact()); + assertEquals(USER_2_ID, response.getContactPerson()); } @Test diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceUnitTest.java index 524c5715b4..725d956f57 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceUnitTest.java @@ -10,6 +10,7 @@ import at.tuwien.exception.ImageAlreadyExistsException; import at.tuwien.repository.ImageRepository; import at.tuwien.service.impl.ImageServiceImpl; import jakarta.validation.ConstraintViolationException; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +35,11 @@ public class ImageServiceUnitTest extends AbstractUnitTest { @Autowired private ImageService imageService; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test public void getAll_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceUnitTest.java index 24ed0f686e..fa10d09a87 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/MetadataServiceUnitTest.java @@ -16,6 +16,7 @@ import at.tuwien.gateway.RorGateway; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -59,6 +60,11 @@ public class MetadataServiceUnitTest extends AbstractUnitTest { @Autowired private ObjectMapper objectMapper; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test public void identify_succeeds() { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java index e2d7d33896..7aa22159c3 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java @@ -92,7 +92,6 @@ public class TableServicePersistenceTest extends AbstractUnitTest { .name("date") .nullAllowed(true) .type(ColumnTypeDto.DATE) - .dfid(3L) .build())) .constraints(ConstraintsCreateDto.builder() .checks(Set.of()) @@ -124,8 +123,6 @@ public class TableServicePersistenceTest extends AbstractUnitTest { assertEquals("date", date.getName()); assertEquals("date", date.getInternalName()); assertEquals(TableColumnType.DATE, date.getColumnType()); - assertNotNull(date.getDateFormat()); - assertEquals(3L, date.getDateFormat().getId()); assertTrue(date.getIsNullAllowed()); assertNotNull(response.getConstraints()); final List<Unique> uniques = response.getConstraints().getUniques(); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java index 59419f9bad..94636bb3c5 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java @@ -5,16 +5,16 @@ import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.constraints.ConstraintsCreateDto; import at.tuwien.api.database.table.constraints.foreign.ForeignKeyCreateDto; -import at.tuwien.entities.database.table.columns.TableColumnType; -import at.tuwien.entities.database.table.constraints.Constraints; -import at.tuwien.test.AbstractUnitTest; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.columns.TableColumn; +import at.tuwien.entities.database.table.columns.TableColumnType; +import at.tuwien.entities.database.table.constraints.Constraints; import at.tuwien.exception.*; import at.tuwien.gateway.DataServiceGateway; import at.tuwien.gateway.SearchServiceGateway; import at.tuwien.repository.DatabaseRepository; +import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -49,9 +49,6 @@ public class TableServiceUnitTest extends AbstractUnitTest { @MockBean private DataServiceGateway dataServiceGateway; - @MockBean - private OntologyService ontologyService; - @Autowired private TableService tableService; @@ -202,7 +199,6 @@ public class TableServiceUnitTest extends AbstractUnitTest { .name("date") .nullAllowed(true) .type(ColumnTypeDto.DATE) - .dfid(9999L) .build())) .constraints(ConstraintsCreateDto.builder() .checks(Set.of()) diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java index 5becb9225a..a9fe4694cc 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/UserServiceUnitTest.java @@ -5,6 +5,7 @@ import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.gateway.KeycloakGateway; import at.tuwien.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -32,6 +33,11 @@ public class UserServiceUnitTest extends AbstractUnitTest { @Autowired private UserService userService; + @BeforeEach + public void beforeEach() { + genesis(); + } + @Test public void findByUsername_succeeds() throws UserNotFoundException { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java index f6cf4e887d..8528d29f07 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/validator/EndpointValidatorUnitTest.java @@ -53,30 +53,9 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { public static Stream<Arguments> needSize_parameters() { return Stream.of( - Arguments.arguments(ColumnTypeDto.CHAR), Arguments.arguments(ColumnTypeDto.VARCHAR), Arguments.arguments(ColumnTypeDto.BINARY), - Arguments.arguments(ColumnTypeDto.VARBINARY), - Arguments.arguments(ColumnTypeDto.BIT), - Arguments.arguments(ColumnTypeDto.TINYINT), - Arguments.arguments(ColumnTypeDto.SMALLINT), - Arguments.arguments(ColumnTypeDto.MEDIUMINT), - Arguments.arguments(ColumnTypeDto.INT) - ); - } - - public static Stream<Arguments> needSizeAndD_parameters() { - return Stream.of( - Arguments.arguments(ColumnTypeDto.DOUBLE), - Arguments.arguments(ColumnTypeDto.DECIMAL) - ); - } - - public static Stream<Arguments> needDateFormat_parameters() { - return Stream.of( - Arguments.arguments(ColumnTypeDto.DATETIME), - Arguments.arguments(ColumnTypeDto.TIMESTAMP), - Arguments.arguments(ColumnTypeDto.TIME) + Arguments.arguments(ColumnTypeDto.VARBINARY) ); } @@ -308,23 +287,6 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { }); } - @ParameterizedTest - @MethodSource("needSizeAndD_parameters") - public void validateColumnCreateConstraints_needSizeAndD_fails(ColumnTypeDto type) { - final TableCreateDto request = TableCreateDto.builder() - .columns(List.of(ColumnCreateDto.builder() - .type(type) - .size(10L) - .d(null) // <<<<<<< - .build())) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - endpointValidator.validateColumnCreateConstraints(request); - }); - } - @Test public void validateColumnCreateConstraints_needEnum_fails() { final TableCreateDto request = TableCreateDto.builder() @@ -355,35 +317,6 @@ public class EndpointValidatorUnitTest extends AbstractUnitTest { }); } - @ParameterizedTest - @MethodSource("needDateFormat_parameters") - public void validateColumnCreateConstraints_needDateFormat_fails(ColumnTypeDto type) { - final TableCreateDto request = TableCreateDto.builder() - .columns(List.of(ColumnCreateDto.builder() - .type(type) - .dfid(null) // <<<<<<< - .build())) - .build(); - - /* test */ - assertThrows(MalformedException.class, () -> { - endpointValidator.validateColumnCreateConstraints(request); - }); - } - - @Test - public void validateColumnCreateConstraints_dateFormatEmpty_succeeds() throws MalformedException { - final TableCreateDto request = TableCreateDto.builder() - .columns(List.of(ColumnCreateDto.builder() - .type(ColumnTypeDto.DATE) - .dfid(null) // <<<<<<< - .build())) - .build(); - - /* test */ - endpointValidator.validateColumnCreateConstraints(request); - } - @Test public void validateOnlyOwnerOrWriteAll_onlyReadAccess_fails() throws DatabaseNotFoundException, TableNotFoundException, AccessNotFoundException { diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/MetricsConfig.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/MetricsConfig.java index e366666244..1b7578b4bc 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/MetricsConfig.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/config/MetricsConfig.java @@ -1,12 +1,10 @@ package at.tuwien.config; -import at.tuwien.entities.database.Database; import at.tuwien.entities.database.table.Table; import at.tuwien.repository.DatabaseRepository; import at.tuwien.repository.IdentifierRepository; import at.tuwien.repository.TableRepository; import at.tuwien.repository.ViewRepository; -import at.tuwien.service.DatabaseService; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.Metrics; import io.micrometer.observation.ObservationRegistry; @@ -16,8 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.List; - @Log4j2 @Configuration public class MetricsConfig { @@ -70,7 +66,12 @@ public class MetricsConfig { @Bean public Gauge volumeSumGauge() { - return Gauge.builder("dbrepo.volume.sum", () -> tableRepository.findAll().stream().map(Table::getDataLength).mapToLong(d -> d).sum()) + return Gauge.builder("dbrepo.volume.sum", () -> { + if (tableRepository.findAll().isEmpty()) { + return 0; + } + return tableRepository.findAll().stream().map(Table::getDataLength).mapToLong(d -> d).sum(); + }) .description("The total volume of available research data") .strongReference(true) .register(Metrics.globalRegistry); diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AccessServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AccessServiceImpl.java index aaa50251c3..b6af901811 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AccessServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/AccessServiceImpl.java @@ -73,6 +73,7 @@ public class AccessServiceImpl implements AccessService { .database(database) .huserid(user.getId()) .type(metadataMapper.accessTypeDtoToAccessType(type)) + .user(user) .build(); database.getAccesses() .add(access); @@ -95,8 +96,8 @@ public class AccessServiceImpl implements AccessService { .hdbid(database.getId()) .database(database) .huserid(user.getId()) - .user(user) .type(metadataMapper.accessTypeDtoToAccessType(access)) + .user(user) .build(); final int idx = database.getAccesses().indexOf(entity); if (idx == -1) { diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java index 0a962f7d6e..330d4518de 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java @@ -167,7 +167,10 @@ public class DatabaseServiceImpl implements DatabaseService { public Database modifyOwner(Database database, User user) throws DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException { /* update in metadata database */ + database.setOwner(user); database.setOwnedBy(user.getId()); + database.setContact(user); + database.setContactPerson(user.getId()); database = databaseRepository.save(database); /* save in search service */ searchServiceGateway.update(database); @@ -281,7 +284,7 @@ public class DatabaseServiceImpl implements DatabaseService { /* correct the unique constraint columns */ for (Table table : database.getTables()) { for (Unique uniqueConstraint : table.getConstraints().getUniques()) { - uniqueConstraint.setColumns(uniqueConstraint.getColumns() + uniqueConstraint.setColumns(new LinkedList<>(uniqueConstraint.getColumns() .stream() .map(column -> { final Optional<TableColumn> optional = table.getColumns() @@ -294,7 +297,7 @@ public class DatabaseServiceImpl implements DatabaseService { } return optional.get(); }) - .toList()); + .toList())); } } /* update in metadata database */ diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index f88ba5c28f..0013f01563 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -4,15 +4,12 @@ import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.TableStatisticDto; import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnStatisticDto; -import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto; import at.tuwien.config.RabbitConfig; -import at.tuwien.entities.container.image.ContainerImageDate; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.database.table.columns.TableColumnConcept; -import at.tuwien.entities.database.table.columns.TableColumnType; import at.tuwien.entities.database.table.columns.TableColumnUnit; import at.tuwien.entities.user.User; import at.tuwien.exception.*; @@ -27,7 +24,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.security.Principal; -import java.util.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; @Log4j2 @Service @@ -146,20 +146,6 @@ public class TableServiceImpl implements TableService { } column.setConcept(concept); } - if (List.of(TableColumnType.TIME, TableColumnType.TIMESTAMP, TableColumnType.DATE, TableColumnType.DATETIME).contains(column.getColumnType())) { - final Optional<ContainerImageDate> optional = database.getContainer() - .getImage() - .getDateFormats() - .stream() - .filter(df -> df.getId().equals(c.getDfid())) - .findFirst(); - if (optional.isEmpty()) { - log.error("Failed to find date format with id {} in metadata database", c.getDfid()); - throw new IllegalArgumentException("Failed to find date format in metadata database"); - } - column.setDateFormat(optional.get()); - log.debug("column is of temporal type: added date format with id {}", column.getDateFormat().getId()); - } table.getColumns() .add(column); } @@ -172,6 +158,8 @@ public class TableServiceImpl implements TableService { for (int i = 0; i < data.getConstraints().getUniques().size(); i++) { if (data.getConstraints().getUniques().get(i).size() != table.getConstraints().getUniques().get(i).getColumns().size()) { log.error("Failed to create table: some unique constraint(s) reference non-existing table columns: {}", data.getConstraints().getUniques().get(i)); + log.debug("payload uniques: {}", data.getConstraints().getUniques()); + log.debug("mapped table uniques: {}", table.getConstraints().getUniques().stream().map(u -> List.of(u.getColumns().stream().map(TableColumn::getInternalName).toList())).toList()); throw new MalformedException("Failed to create table: some unique constraint(s) reference non-existing table columns"); } } diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java index 996dbb9a7d..924db22930 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java @@ -22,6 +22,7 @@ public abstract class AbstractUnitTest extends BaseTest { /* USER_4 */ USER_5.setAccesses(new LinkedList<>()); /* DATABASE 1 */ + DATABASE_1.setOwner(USER_1); DATABASE_1.setSubsets(new LinkedList<>()); DATABASE_1.setAccesses(new LinkedList<>(List.of(DATABASE_1_USER_1_READ_ACCESS, DATABASE_1_USER_2_WRITE_OWN_ACCESS, DATABASE_1_USER_3_WRITE_ALL_ACCESS))); DATABASE_1_PRIVILEGED_DTO.setAccesses(new LinkedList<>(List.of(DATABASE_1_USER_1_READ_ACCESS_DTO, DATABASE_1_USER_2_WRITE_OWN_ACCESS_DTO, DATABASE_1_USER_3_WRITE_ALL_ACCESS_DTO))); diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java index 6ade19c1ec..f372879434 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java @@ -1,12 +1,18 @@ package at.tuwien.test; import at.tuwien.ExportResourceDto; -import at.tuwien.api.amqp.*; +import at.tuwien.api.amqp.CreateVirtualHostDto; +import at.tuwien.api.amqp.ExchangeDto; +import at.tuwien.api.amqp.GrantVirtualHostPermissionsDto; +import at.tuwien.api.amqp.QueueDto; import at.tuwien.api.auth.LoginRequestDto; import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerDto; -import at.tuwien.api.container.image.*; +import at.tuwien.api.container.image.ImageBriefDto; +import at.tuwien.api.container.image.ImageChangeDto; +import at.tuwien.api.container.image.ImageCreateDto; +import at.tuwien.api.container.image.ImageDto; import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.*; import at.tuwien.api.database.internal.CreateDatabaseDto; @@ -20,7 +26,10 @@ import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.TableStatisticDto; import at.tuwien.api.database.table.columns.*; -import at.tuwien.api.database.table.columns.concepts.*; +import at.tuwien.api.database.table.columns.concepts.ConceptDto; +import at.tuwien.api.database.table.columns.concepts.ConceptSaveDto; +import at.tuwien.api.database.table.columns.concepts.UnitDto; +import at.tuwien.api.database.table.columns.concepts.UnitSaveDto; import at.tuwien.api.database.table.constraints.ConstraintsCreateDto; import at.tuwien.api.database.table.constraints.ConstraintsDto; import at.tuwien.api.database.table.constraints.foreign.*; @@ -51,11 +60,9 @@ import at.tuwien.api.orcid.person.name.OrcidValueDto; import at.tuwien.api.semantics.OntologyCreateDto; import at.tuwien.api.semantics.OntologyModifyDto; import at.tuwien.api.user.*; -import at.tuwien.api.user.UserDetailsDto; import at.tuwien.api.user.internal.UpdateUserPasswordDto; import at.tuwien.entities.container.Container; import at.tuwien.entities.container.image.ContainerImage; -import at.tuwien.entities.container.image.ContainerImageDate; import at.tuwien.entities.database.*; import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.columns.TableColumn; @@ -422,17 +429,6 @@ public abstract class BaseTest { public final static Instant USER_1_LAST_MODIFIED = USER_1_CREATED; public final static UUID USER_1_REALM_ID = REALM_DBREPO_ID; - public final static CreateUserDto USER_1_RABBITMQ_CREATE_DTO = CreateUserDto.builder() - .password("") - .tags("") - .build(); - - public final static GrantVirtualHostPermissionsDto USER_1_RABBITMQ_GRANT_DTO = GrantVirtualHostPermissionsDto.builder() - .configure("") - .read("") - .write("") - .build(); - public final static UpdateUserPasswordDto USER_1_UPDATE_PASSWORD_DTO = UpdateUserPasswordDto.builder() .username(USER_1_USERNAME) .password(USER_1_PASSWORD) @@ -908,29 +904,6 @@ public abstract class BaseTest { public final static Integer IMAGE_1_PORT = 3306; public final static Boolean IMAGE_1_IS_DEFAULT = true; - 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_UNIX_FORMAT = "yyyy-MM-dd"; - public final static String IMAGE_DATE_1_DATABASE_FORMAT = "%Y-%c-%d"; - public final static String IMAGE_DATE_1_EXAMPLE = "2022-01-30"; - public final static Boolean IMAGE_DATE_1_HAS_TIME = false; - - public final static ContainerImageDate IMAGE_DATE_1 = ContainerImageDate.builder() - .id(IMAGE_DATE_1_ID) - .iid(IMAGE_DATE_1_IMAGE_ID) - .unixFormat(IMAGE_DATE_1_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_1_DATABASE_FORMAT) - .example(IMAGE_DATE_1_EXAMPLE) - .hasTime(IMAGE_DATE_1_HAS_TIME) - .build(); - - public final static ImageDateDto IMAGE_DATE_1_DTO = ImageDateDto.builder() - .id(IMAGE_DATE_1_ID) - .unixFormat(IMAGE_DATE_1_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_1_DATABASE_FORMAT) - .hasTime(IMAGE_DATE_1_HAS_TIME) - .build(); - public final static ImageCreateDto IMAGE_1_CREATE_DTO = ImageCreateDto.builder() .registry(IMAGE_1_REGISTRY) .name(IMAGE_1_NAME) @@ -949,75 +922,6 @@ public abstract class BaseTest { .defaultPort(IMAGE_1_PORT) .build(); - 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_UNIX_FORMAT = "dd.MM.yy"; - public final static String IMAGE_DATE_2_DATABASE_FORMAT = "%d.%c.%y"; - public final static String IMAGE_DATE_2_EXAMPLE = "30.01.2022"; - public final static Boolean IMAGE_DATE_2_HAS_TIME = false; - - public final static ContainerImageDate IMAGE_DATE_2 = ContainerImageDate.builder() - .id(IMAGE_DATE_2_ID) - .iid(IMAGE_DATE_2_IMAGE_ID) - .unixFormat(IMAGE_DATE_2_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_2_DATABASE_FORMAT) - .example(IMAGE_DATE_2_EXAMPLE) - .hasTime(IMAGE_DATE_2_HAS_TIME) - .build(); - - public final static ImageDateDto IMAGE_DATE_2_DTO = ImageDateDto.builder() - .id(IMAGE_DATE_2_ID) - .unixFormat(IMAGE_DATE_2_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_2_DATABASE_FORMAT) - .hasTime(IMAGE_DATE_2_HAS_TIME) - .build(); - - 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_UNIX_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"; - public final static String IMAGE_DATE_3_DATABASE_FORMAT = "%Y-%c-%dT%H:%i:%S.%f"; - public final static String IMAGE_DATE_3_EXAMPLE = "2022-01-30T13:44:25.499"; - public final static Boolean IMAGE_DATE_3_HAS_TIME = true; - - public final static ContainerImageDate IMAGE_DATE_3 = ContainerImageDate.builder() - .id(IMAGE_DATE_3_ID) - .iid(IMAGE_DATE_3_IMAGE_ID) - .unixFormat(IMAGE_DATE_3_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_3_DATABASE_FORMAT) - .example(IMAGE_DATE_3_EXAMPLE) - .hasTime(IMAGE_DATE_3_HAS_TIME) - .build(); - - public final static ImageDateDto IMAGE_DATE_3_DTO = ImageDateDto.builder() - .id(IMAGE_DATE_3_ID) - .unixFormat(IMAGE_DATE_3_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_3_DATABASE_FORMAT) - .hasTime(IMAGE_DATE_3_HAS_TIME) - .build(); - - 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_UNIX_FORMAT = "HH:mm:ss"; - public final static String IMAGE_DATE_4_DATABASE_FORMAT = "%H:%i:%S"; - public final static String IMAGE_DATE_4_EXAMPLE = "14:44:25"; - public final static Boolean IMAGE_DATE_4_HAS_TIME = true; - - public final static ContainerImageDate IMAGE_DATE_4 = ContainerImageDate.builder() - .id(IMAGE_DATE_4_ID) - .iid(IMAGE_DATE_4_IMAGE_ID) - .unixFormat(IMAGE_DATE_4_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_4_DATABASE_FORMAT) - .example(IMAGE_DATE_4_EXAMPLE) - .hasTime(IMAGE_DATE_4_HAS_TIME) - .build(); - - public final static ImageDateDto IMAGE_DATE_4_DTO = ImageDateDto.builder() - .id(IMAGE_DATE_4_ID) - .unixFormat(IMAGE_DATE_4_UNIX_FORMAT) - .databaseFormat(IMAGE_DATE_4_DATABASE_FORMAT) - .hasTime(IMAGE_DATE_4_HAS_TIME) - .build(); - public final static ContainerImage IMAGE_1 = ContainerImage.builder() .id(IMAGE_1_ID) .name(IMAGE_1_NAME) @@ -1028,7 +932,6 @@ public abstract class BaseTest { .driverClass(IMAGE_1_DRIVER) .defaultPort(IMAGE_1_PORT) .isDefault(IMAGE_1_IS_DEFAULT) - .dateFormats(new LinkedList<>(List.of(IMAGE_DATE_1, IMAGE_DATE_2, IMAGE_DATE_3, IMAGE_DATE_4))) .build(); public final static ImageDto IMAGE_1_DTO = ImageDto.builder() @@ -1041,7 +944,6 @@ public abstract class BaseTest { .driverClass(IMAGE_1_DRIVER) .defaultPort(IMAGE_1_PORT) .isDefault(IMAGE_1_IS_DEFAULT) - .dateFormats(List.of(IMAGE_DATE_1_DTO, IMAGE_DATE_2_DTO, IMAGE_DATE_3_DTO)) .build(); public final static ImageBriefDto IMAGE_1_BRIEF_DTO = ImageBriefDto.builder() @@ -1253,12 +1155,6 @@ public abstract class BaseTest { public final static UserDto DATABASE_1_CREATOR_DTO = USER_1_DTO; public final static UserDto DATABASE_1_OWNER_DTO = USER_1_DTO; - public final static GrantExchangePermissionsDto USER_1_RABBITMQ_GRANT_TOPIC_DTO = GrantExchangePermissionsDto.builder() - .exchange("dbrepo") - .read("^(dbrepo\\." + DATABASE_1_INTERNALNAME + "\\..)$") - .write("^(dbrepo\\." + DATABASE_1_INTERNALNAME + "\\..)$") - .build(); - public final static DatabaseCreateDto DATABASE_1_CREATE = DatabaseCreateDto.builder() .name(DATABASE_1_NAME) .isPublic(DATABASE_1_PUBLIC) @@ -1498,25 +1394,21 @@ public abstract class BaseTest { .name("col25") .type(ColumnTypeDto.DATE) .nullAllowed(true) - .dfid(IMAGE_DATE_1_ID) .build(), ColumnCreateDto.builder() .name("col26") .type(ColumnTypeDto.DATETIME) .nullAllowed(true) - .dfid(IMAGE_DATE_3_ID) .build(), ColumnCreateDto.builder() .name("col27") .type(ColumnTypeDto.TIMESTAMP) .nullAllowed(true) - .dfid(IMAGE_DATE_3_ID) .build(), ColumnCreateDto.builder() .name("col28") .type(ColumnTypeDto.TIME) .nullAllowed(true) - .dfid(IMAGE_DATE_4_ID) .build(), ColumnCreateDto.builder() .name("col29") @@ -1632,7 +1524,6 @@ public abstract class BaseTest { .internalName("date") .ordinalPosition(1) .columnType(ColumnTypeDto.DATE) - .dateFormat(IMAGE_DATE_1_DTO) .isNullAllowed(true) .autoGenerated(false) .enums(null) @@ -2214,7 +2105,6 @@ public abstract class BaseTest { public final static List<ColumnCreateDto> TABLE_4_COLUMNS_CREATE_DTO = List.of(ColumnCreateDto.builder() .name("Timestamp") .type(ColumnTypeDto.TIMESTAMP) - .dfid(IMAGE_DATE_4_ID) .nullAllowed(false) .build(), ColumnCreateDto.builder() @@ -2253,7 +2143,6 @@ public abstract class BaseTest { .name("Timestamp") .internalName("timestamp") .columnType(ColumnTypeDto.TIMESTAMP) - .dateFormat(IMAGE_DATE_3_DTO) .isNullAllowed(false) .autoGenerated(false) .build(), @@ -2264,7 +2153,6 @@ public abstract class BaseTest { .name("Value") .internalName("value") .columnType(ColumnTypeDto.DECIMAL) - .dateFormat(null) .isNullAllowed(true) .autoGenerated(false) .build()); @@ -2893,7 +2781,6 @@ public abstract class BaseTest { .name("Date") .internalName("date") .columnType(TableColumnType.DATE) - .dateFormat(IMAGE_DATE_1) .isNullAllowed(true) .autoGenerated(false) .build(), @@ -2946,14 +2833,12 @@ public abstract class BaseTest { .name("Date") .type(ColumnTypeDto.DATE) .nullAllowed(true) - .dfid(IMAGE_DATE_1_ID) .build(), ColumnCreateDto.builder() .name("Location") .type(ColumnTypeDto.VARCHAR) .size(255L) .nullAllowed(true) - .dfid(IMAGE_DATE_1_ID) .build(), ColumnCreateDto.builder() .name("MinTemp") @@ -2961,7 +2846,6 @@ public abstract class BaseTest { .size(10L) .d(0L) .nullAllowed(true) - .dfid(IMAGE_DATE_1_ID) .build(), ColumnCreateDto.builder() .name("Rainfall") @@ -2969,7 +2853,6 @@ public abstract class BaseTest { .size(10L) .d(0L) .nullAllowed(true) - .dfid(IMAGE_DATE_1_ID) .conceptUri(CONCEPT_1_URI) .unitUri(UNIT_1_URI) .build()); @@ -3206,7 +3089,6 @@ public abstract class BaseTest { .name("id") .internalName("id") .isNullAllowed(false) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3219,7 +3101,6 @@ public abstract class BaseTest { .name("linie") .internalName("linie") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3232,7 +3113,6 @@ public abstract class BaseTest { .name("richtung") .internalName("richtung") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3257,7 +3137,6 @@ public abstract class BaseTest { .name("fahrzeug") .internalName("fahrzeug") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3270,7 +3149,6 @@ public abstract class BaseTest { .name("kurs") .internalName("kurs") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3283,7 +3161,6 @@ public abstract class BaseTest { .name("seq_von") .internalName("seq_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3296,7 +3173,6 @@ public abstract class BaseTest { .name("halt_diva_von") .internalName("halt_diva_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3309,7 +3185,6 @@ public abstract class BaseTest { .name("halt_punkt_diva_von") .internalName("halt_punkt_diva_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3322,7 +3197,6 @@ public abstract class BaseTest { .name("halt_kurz_von1") .internalName("halt_kurz_von1") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3347,7 +3221,6 @@ public abstract class BaseTest { .name("soll_an_von") .internalName("soll_an_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3360,7 +3233,6 @@ public abstract class BaseTest { .name("ist_an_von") .internalName("ist_an_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3373,7 +3245,6 @@ public abstract class BaseTest { .name("soll_ab_von") .internalName("soll_ab_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3386,7 +3257,6 @@ public abstract class BaseTest { .name("ist_ab_von") .internalName("ist_ab_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3399,7 +3269,6 @@ public abstract class BaseTest { .name("seq_nach") .internalName("seq_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3412,7 +3281,6 @@ public abstract class BaseTest { .name("halt_diva_nach") .internalName("halt_diva_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3425,7 +3293,6 @@ public abstract class BaseTest { .name("halt_punkt_diva_nach") .internalName("halt_punkt_diva_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3438,7 +3305,6 @@ public abstract class BaseTest { .name("halt_kurz_nach1") .internalName("halt_kurz_nach1") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3463,7 +3329,6 @@ public abstract class BaseTest { .name("soll_an_nach") .internalName("soll_an_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3476,7 +3341,6 @@ public abstract class BaseTest { .name("ist_an_nach1") .internalName("ist_an_nach1") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3489,7 +3353,6 @@ public abstract class BaseTest { .name("soll_ab_nach") .internalName("soll_ab_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3502,7 +3365,6 @@ public abstract class BaseTest { .name("ist_ab_nach") .internalName("ist_ab_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3515,7 +3377,6 @@ public abstract class BaseTest { .name("fahrt_id") .internalName("fahrt_id") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3528,7 +3389,6 @@ public abstract class BaseTest { .name("fahrweg_id") .internalName("fahrweg_id") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3541,7 +3401,6 @@ public abstract class BaseTest { .name("fw_no") .internalName("fw_no") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3554,7 +3413,6 @@ public abstract class BaseTest { .name("fw_typ") .internalName("fw_typ") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3567,7 +3425,6 @@ public abstract class BaseTest { .name("fw_kurz") .internalName("fw_kurz") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3580,7 +3437,6 @@ public abstract class BaseTest { .name("fw_lang") .internalName("fw_lang") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3593,7 +3449,6 @@ public abstract class BaseTest { .name("umlauf_von") .internalName("umlauf_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3606,7 +3461,6 @@ public abstract class BaseTest { .name("halt_id_von") .internalName("halt_id_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3619,7 +3473,6 @@ public abstract class BaseTest { .name("halt_id_nach") .internalName("halt_id_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3632,7 +3485,6 @@ public abstract class BaseTest { .name("halt_punkt_id_von") .internalName("halt_punkt_id_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3645,7 +3497,6 @@ public abstract class BaseTest { .name("halt_punkt_id_nach") .internalName("halt_punkt_id_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build()); @@ -3660,7 +3511,6 @@ public abstract class BaseTest { .name("id") .internalName("id") .isNullAllowed(false) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3674,7 +3524,6 @@ public abstract class BaseTest { .name("linie") .internalName("linie") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3688,7 +3537,6 @@ public abstract class BaseTest { .name("richtung") .internalName("richtung") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3702,7 +3550,6 @@ public abstract class BaseTest { .name("betriebsdatum") .internalName("betriebsdatum") .isNullAllowed(true) - .dateFormat(IMAGE_DATE_2_DTO) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3716,7 +3563,6 @@ public abstract class BaseTest { .name("fahrzeug") .internalName("fahrzeug") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3730,7 +3576,6 @@ public abstract class BaseTest { .name("kurs") .internalName("kurs") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3744,7 +3589,6 @@ public abstract class BaseTest { .name("seq_von") .internalName("seq_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3758,7 +3602,6 @@ public abstract class BaseTest { .name("halt_diva_von") .internalName("halt_diva_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3772,7 +3615,6 @@ public abstract class BaseTest { .name("halt_punkt_diva_von") .internalName("halt_punkt_diva_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3786,7 +3628,6 @@ public abstract class BaseTest { .name("halt_kurz_von1") .internalName("halt_kurz_von1") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3800,7 +3641,6 @@ public abstract class BaseTest { .name("datum_von") .internalName("datum_von") .isNullAllowed(true) - .dateFormat(IMAGE_DATE_2_DTO) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3814,7 +3654,6 @@ public abstract class BaseTest { .name("soll_an_von") .internalName("soll_an_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3828,7 +3667,6 @@ public abstract class BaseTest { .name("ist_an_von") .internalName("ist_an_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3842,7 +3680,6 @@ public abstract class BaseTest { .name("soll_ab_von") .internalName("soll_ab_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3856,7 +3693,6 @@ public abstract class BaseTest { .name("ist_ab_von") .internalName("ist_ab_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3870,7 +3706,6 @@ public abstract class BaseTest { .name("seq_nach") .internalName("seq_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3884,7 +3719,6 @@ public abstract class BaseTest { .name("halt_diva_nach") .internalName("halt_diva_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3898,7 +3732,6 @@ public abstract class BaseTest { .name("halt_punkt_diva_nach") .internalName("halt_punkt_diva_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3912,7 +3745,6 @@ public abstract class BaseTest { .name("halt_kurz_nach1") .internalName("halt_kurz_nach1") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3926,7 +3758,6 @@ public abstract class BaseTest { .name("datum_nach") .internalName("datum_nach") .isNullAllowed(true) - .dateFormat(IMAGE_DATE_2_DTO) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3940,7 +3771,6 @@ public abstract class BaseTest { .name("soll_an_nach") .internalName("soll_an_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3954,7 +3784,6 @@ public abstract class BaseTest { .name("ist_an_nach1") .internalName("ist_an_nach1") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3968,7 +3797,6 @@ public abstract class BaseTest { .name("soll_ab_nach") .internalName("soll_ab_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3982,7 +3810,6 @@ public abstract class BaseTest { .name("ist_ab_nach") .internalName("ist_ab_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -3996,7 +3823,6 @@ public abstract class BaseTest { .name("fahrt_id") .internalName("fahrt_id") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4010,7 +3836,6 @@ public abstract class BaseTest { .name("fahrweg_id") .internalName("fahrweg_id") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4024,7 +3849,6 @@ public abstract class BaseTest { .name("fw_no") .internalName("fw_no") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4038,7 +3862,6 @@ public abstract class BaseTest { .name("fw_typ") .internalName("fw_typ") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4052,7 +3875,6 @@ public abstract class BaseTest { .name("fw_kurz") .internalName("fw_kurz") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4066,7 +3888,6 @@ public abstract class BaseTest { .name("fw_lang") .internalName("fw_lang") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4080,7 +3901,6 @@ public abstract class BaseTest { .name("umlauf_von") .internalName("umlauf_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4094,7 +3914,6 @@ public abstract class BaseTest { .name("halt_id_von") .internalName("halt_id_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4108,7 +3927,6 @@ public abstract class BaseTest { .name("halt_id_nach") .internalName("halt_id_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4122,7 +3940,6 @@ public abstract class BaseTest { .name("halt_punkt_id_von") .internalName("halt_punkt_id_von") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build(), @@ -4136,7 +3953,6 @@ public abstract class BaseTest { .name("halt_punkt_id_nach") .internalName("halt_punkt_id_nach") .isNullAllowed(true) - .dateFormat(null) .enums(new LinkedList<>()) .sets(new LinkedList<>()) .build()); @@ -4817,7 +4633,6 @@ public abstract class BaseTest { .name("reminder") .internalName("reminder") .columnType(TableColumnType.TIME) - .dateFormat(IMAGE_DATE_4) .isNullAllowed(false) .autoGenerated(false) .build(), @@ -4891,7 +4706,6 @@ public abstract class BaseTest { .name("reminder") .internalName("reminder") .columnType(ColumnTypeDto.TIME) - .dateFormat(IMAGE_DATE_4_DTO) .isNullAllowed(false) .autoGenerated(false) .build(), @@ -5050,7 +4864,7 @@ public abstract class BaseTest { .size(22L) .isNullAllowed(true) .autoGenerated(false) - .build() ); + .build()); public final static View VIEW_1 = View.builder() .id(VIEW_1_ID) @@ -5195,7 +5009,6 @@ public abstract class BaseTest { .internalName("date") .ordinalPosition(1) .columnType(ColumnTypeDto.DATE) - .dateFormat(IMAGE_DATE_1_DTO) .isNullAllowed(true) .autoGenerated(false) .build(), @@ -5254,7 +5067,6 @@ public abstract class BaseTest { .name("Date") .internalName("date") .columnType(TableColumnType.DATE) - .dateFormat(IMAGE_DATE_1) .isNullAllowed(true) .autoGenerated(false) .view(VIEW_2) @@ -5388,7 +5200,6 @@ public abstract class BaseTest { .internalName("date") .ordinalPosition(3) .columnType(ColumnTypeDto.DATE) - .dateFormat(IMAGE_DATE_1_DTO) .isNullAllowed(true) .autoGenerated(false) .build() @@ -5463,7 +5274,6 @@ public abstract class BaseTest { .name("Date") .internalName("date") .columnType(TableColumnType.DATE) - .dateFormat(IMAGE_DATE_1) .isNullAllowed(true) .autoGenerated(false) .view(VIEW_3) diff --git a/dbrepo-metric-db/prometheus.yml b/dbrepo-metric-db/prometheus.yml index 447396d167..82986b2490 100644 --- a/dbrepo-metric-db/prometheus.yml +++ b/dbrepo-metric-db/prometheus.yml @@ -9,11 +9,11 @@ alerting: - targets: [] scrape_configs: - - job_name: 'spring boot scrape' + - job_name: 'actuator scrape' metrics_path: '/actuator/prometheus' static_configs: - - targets: ['data-service:8080', 'metadata-service:8080'] + - targets: ['data-service:8080', 'metadata-service:8080', 'ui:3000'] - job_name: 'metrics scrape' metrics_path: '/metrics' static_configs: - - targets: ['auth-service:8080', 'analyse-service:8080', 'search-service:8080', 'data-db-sidecar:8080', 'broker-service:15692', 'ui:3000', 'dashboard-service:3000', 'storage-service:9090', 'upload-service:8080'] + - targets: ['auth-service:8080', 'analyse-service:8080', 'search-service:8080', 'data-db-sidecar:8080', 'broker-service:15692', 'storage-service:9090', 'upload-service:8080', 'dashboard-service:3000'] diff --git a/dbrepo-search-service/init/Pipfile b/dbrepo-search-service/init/Pipfile index 517796af74..9647c2ca4e 100644 --- a/dbrepo-search-service/init/Pipfile +++ b/dbrepo-search-service/init/Pipfile @@ -9,7 +9,7 @@ opensearch-py = "~=2.2" python-dotenv = "~=1.0" testcontainers-opensearch = "*" pytest = "*" -dbrepo = {path = "./lib/dbrepo-1.4.4.tar.gz"} +dbrepo = {path = "./lib/dbrepo-1.4.6.tar.gz"} [dev-packages] coverage = "*" diff --git a/dbrepo-search-service/init/lib/dbrepo-1.4.6rc1-py3-none-any.whl b/dbrepo-search-service/init/lib/dbrepo-1.4.6rc1-py3-none-any.whl deleted file mode 100644 index 83944ce88d8aec5a3b767aa09caf9a8700323104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30062 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�wbidPiqipp2q_1Bb4bC47TXf^9 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 E09ImukpKVy diff --git a/dbrepo-search-service/init/lib/dbrepo-1.4.6rc1.tar.gz b/dbrepo-search-service/init/lib/dbrepo-1.4.6rc1.tar.gz deleted file mode 100644 index a0c8432134f3c21359cd7fb8ee1a341812a6c034..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39391 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 diff --git a/dbrepo-search-service/init/test/conftest.py b/dbrepo-search-service/init/test/conftest.py new file mode 100644 index 0000000000..2a21f68970 --- /dev/null +++ b/dbrepo-search-service/init/test/conftest.py @@ -0,0 +1,49 @@ +import logging + +import pytest +from app import app +from flask import current_app + +from testcontainers.opensearch import OpenSearchContainer + + +@pytest.fixture(scope="session", autouse=True) +def session(request): + """ + Create one OpenSearch container per test run only (admin:admin) + :param request: / + :return: The OpenSearch container + """ + logging.debug("[fixture] creating opensearch container") + container = OpenSearchContainer() + logging.debug("[fixture] starting opensearch container") + container.start() + + with app.app_context(): + current_app.config['OPENSEARCH_HOST'] = container.get_container_host_ip() + current_app.config['OPENSEARCH_PORT'] = container.get_exposed_port(9200) + + # destructor + def stop_opensearch(): + container.stop() + + request.addfinalizer(stop_opensearch) + return container + +# @pytest.fixture(scope="function", autouse=True) +# def cleanup(request, session): +# """ +# Clean up after each test by removing the buckets and re-adding them (=so they are empty again) +# :param request: / +# :param session: / +# :return: +# """ +# logging.info("[fixture] truncate buckets") +# for bucket in ["dbrepo-upload", "dbrepo-download"]: +# objects = [] +# for obj in session.get_client().list_objects(bucket): +# objects.append(DeleteObject(obj.object_name)) +# logging.info(f'request to remove objects {objects}') +# errors = session.get_client().remove_objects(bucket, objects) +# for error in errors: +# raise ConnectionError(f'Failed to delete object with key {error.object_name} of bucket {bucket}') diff --git a/dbrepo-search-service/init/test/test_app.py b/dbrepo-search-service/init/test/test_app.py new file mode 100644 index 0000000000..a8e6d9755b --- /dev/null +++ b/dbrepo-search-service/init/test/test_app.py @@ -0,0 +1,95 @@ +import datetime +import unittest + +from app import app + +from clients.opensearch_client import OpenSearchClient + + +class OpenSearchClientTest(unittest.TestCase): + + def test_index_exists_succeeds(self): + with app.app_context(): + client = RestClient(endpoint=self.metadata_service_endpoint) + # mock + client.update_database(database_id=1, data=req) + + # test + req.tables = [Table(id=1, + name="Test Table", + internal_name="test_table", + queue_name="dbrepo", + routing_key="dbrepo.test_tuw1.test_table", + is_public=True, + database_id=req.id, + constraints=Constraints(uniques=[], foreign_keys=[], checks=[], + primary_key=[PrimaryKey(id=1, + table=TableMinimal(id=1, database_id=1), + column=ColumnMinimal(id=1, table_id=1, + database_id=1))]), + is_versioned=True, + created_by="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", + creator=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", + username="foo", + attributes=UserAttributes(theme="dark")), + owner=User(id="c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", + username="foo", + attributes=UserAttributes(theme="dark")), + created=datetime.datetime(2024, 4, 25, 17, 44, tzinfo=datetime.timezone.utc), + columns=[Column(id=1, + name="ID", + internal_name="id", + database_id=req.id, + table_id=1, + auto_generated=True, + column_type=ColumnType.BIGINT, + is_public=True, + is_null_allowed=False)])] + database = client.update_database(database_id=1, data=req) + self.assertEqual(1, database.id) + self.assertEqual("Test", database.name) + self.assertEqual("test_tuw1", database.internal_name) + self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.creator.id) + self.assertEqual("foo", database.creator.username) + self.assertEqual("dark", database.creator.attributes.theme) + self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.owner.id) + self.assertEqual("foo", database.owner.username) + self.assertEqual("dark", database.owner.attributes.theme) + self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.contact.id) + self.assertEqual("foo", database.contact.username) + self.assertEqual("dark", database.contact.attributes.theme) + self.assertEqual(datetime.datetime(2024, 3, 25, 16, tzinfo=datetime.timezone.utc), database.created) + self.assertEqual("dbrepo", database.exchange_name) + self.assertEqual(True, database.is_public) + self.assertEqual(1, database.container.id) + # ... + self.assertEqual(1, database.container.image.id) + # ... + self.assertEqual(1, len(database.tables)) + self.assertEqual(1, database.tables[0].id) + self.assertEqual("Test Table", database.tables[0].name) + self.assertEqual("test_table", database.tables[0].internal_name) + self.assertEqual("dbrepo", database.tables[0].queue_name) + self.assertEqual("dbrepo.test_tuw1.test_table", database.tables[0].routing_key) + self.assertEqual(True, database.tables[0].is_public) + self.assertEqual(1, database.tables[0].database_id) + self.assertEqual(True, database.tables[0].is_versioned) + self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.tables[0].created_by) + self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.tables[0].creator.id) + self.assertEqual("foo", database.tables[0].creator.username) + self.assertEqual("dark", database.tables[0].creator.attributes.theme) + self.assertEqual("c6b71ef5-2d2f-48b2-9d79-b8f23a3a0502", database.tables[0].owner.id) + self.assertEqual("foo", database.tables[0].owner.username) + self.assertEqual("dark", database.tables[0].owner.attributes.theme) + self.assertEqual(datetime.datetime(2024, 4, 25, 17, 44, tzinfo=datetime.timezone.utc), + database.tables[0].created) + self.assertEqual(1, len(database.tables[0].columns)) + self.assertEqual(1, database.tables[0].columns[0].id) + self.assertEqual("ID", database.tables[0].columns[0].name) + self.assertEqual("id", database.tables[0].columns[0].internal_name) + self.assertEqual(ColumnType.BIGINT, database.tables[0].columns[0].column_type) + self.assertEqual(1, database.tables[0].columns[0].database_id) + self.assertEqual(1, database.tables[0].columns[0].table_id) + self.assertEqual(True, database.tables[0].columns[0].auto_generated) + self.assertEqual(True, database.tables[0].columns[0].is_public) + self.assertEqual(False, database.tables[0].columns[0].is_null_allowed) diff --git a/dbrepo-search-service/test/run_testindicies.py b/dbrepo-search-service/test/run_testindicies.py deleted file mode 100644 index b547573dd6..0000000000 --- a/dbrepo-search-service/test/run_testindicies.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -This script spins up docker containers running an opensearch db with predefined entries. -This is useful e.g. if you want to run tests on the functionality of the opensearch_client. - -note: The port of the test container should be 9200, but it's somehow kinda random, -and using environmet variables also doesn't really work, -so the correct port number is just saved in the .testpickle -""" - -from testcontainers.opensearch import OpenSearchContainer -import pprint -import time -import os -import pickle - - -doc1 = { - "author": "aaa", - "name": "Hi! My name is", - "description":"here's some description text", - "created": "2023-07-27", - "docID":1, - "public":True, - "details": { - "nestedObject1": "something", - "nestedObject2": "something else", - "evenMoreNested": { - "bla":"blib", - "blob":"blub" - } - } -} - -doc2 = { - "author": "max", - "name": "Bla Bla", - "public": False, - "description": "here's another description text, about a fictional entry with some random measurement data", - "created": "2023-07-27", - "docID":2, - "details": { - "nestedObject1": "something", - "nestedObject2": "something else" - } -} - -doc3 = { - "author": "mweise", - "name": "databaseName", - "public": True, - "description": "here is a really old entry", - "created":"2022-07-27", - "docID":3, - "details": { - "nestedObject1": "something", - "nestedObject2": "something else" - } -} -placeholderDoc = { - "blib":"blub", - "public": False -} - -with OpenSearchContainer(port_to_expose=9200) as opensearch: - client = opensearch.get_client() - creation_result = client.index(index="database", body=doc1) - creation_result = client.index(index="database", body=doc2) - creation_result = client.index(index="database", body=doc3) - creation_result = client.index(index="user", body=placeholderDoc) - creation_result = client.index(index="table", body=placeholderDoc) - creation_result = client.index(index="column", body=placeholderDoc) - creation_result = client.index(index="identifier", body=placeholderDoc) - refresh_result = client.indices.refresh(index="database") - search_result = client.search(index="database", body={"query": {"match_all": {}}}) - pp = pprint.PrettyPrinter(indent=1) - config = opensearch.get_config() - os.environ["TEST_OPENSEARCH_HOST"] = config["host"] - os.putenv("TEST_OPENSEARCH_HOST", config["host"]) - os.environ["TEST_OPENSEARCH_PORT"] = config["port"] - os.environ["TEST_OPENSEARCH_USERNAME"] = config["user"] - os.environ["TEST_OPENSEARCH_PASSWORD"] = config["password"] - - pickle_info = {} - pickle_info["port"] = config["port"] - pickle_info["host"] = config["host"] - with open(".testpickle", "ab") as outfile: - pickle.dump(pickle_info, outfile) - print(f"serving on port: {config['port']}") - while True: - time.sleep(1) - diff --git a/dbrepo-ui/Dockerfile b/dbrepo-ui/Dockerfile index 130ce0082d..07f7f5d903 100644 --- a/dbrepo-ui/Dockerfile +++ b/dbrepo-ui/Dockerfile @@ -1,10 +1,10 @@ -FROM node:18.20.4-alpine3.20 AS build +FROM oven/bun:1.1.20-alpine AS build WORKDIR /app COPY ./package.json ./package.json -RUN npm install +RUN bun install ENV NODE_ENV="production" @@ -22,9 +22,9 @@ COPY ./stores ./stores COPY ./utils ./utils COPY ./nuxt.config.ts ./nuxt.config.ts -RUN npm run build +RUN bun run build -FROM node:18.20.4-alpine3.20 AS runtime +FROM oven/bun:1.1.20-alpine AS runtime ARG APP_VERSION="latest" ARG COMMIT="" @@ -39,7 +39,8 @@ RUN chmod -R 755 /app/.output ENV NUXT_PUBLIC_VERSION="${APP_VERSION:-}" ENV NUXT_PUBLIC_COMMIT="${COMMIT:-}" +ENV NODE_OPTIONS="--max_old_space_size=4096" EXPOSE 3000 -ENTRYPOINT [ "node", ".output/server/index.mjs" ] +ENTRYPOINT [ "bun", "run", ".output/server/index.mjs" ] diff --git a/dbrepo-ui/bun.lockb b/dbrepo-ui/bun.lockb index 08343a9606e23719aa606a7bb102499777d9e001..d4a0ef58602cbb7a069d13bc894340f4b0d72465 100755 GIT binary patch delta 53432 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ೌ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 J?Srft+yFf%YGD8X delta 56016 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 A(EtDd diff --git a/dbrepo-ui/components/dialogs/EditAccess.vue b/dbrepo-ui/components/dialogs/EditAccess.vue index 8132adddf5..039b1c40e8 100644 --- a/dbrepo-ui/components/dialogs/EditAccess.vue +++ b/dbrepo-ui/components/dialogs/EditAccess.vue @@ -12,14 +12,13 @@ <v-col> <v-autocomplete v-if="!isModification" - v-model="modify.userId" + v-model="localUserId" :items="eligibleUsers" :disabled="loadingUsers" :loading="loadingUsers" :rules="[v => !!v || $t('validation.required')]" required :variant="inputVariant" - hide-no-data hide-selected hide-details item-value="id" @@ -56,7 +55,7 @@ :disabled="!valid || loading || accessType === modify.type" :color="buttonColor" type="submit" - :text="$t('pages.database.subpages.access.submit.text')" + :text="$t('navigation.modify')" :loading="loading" @click="updateAccess" /> </v-card-actions> @@ -90,6 +89,7 @@ export default { loadingUsers: false, users: [], error: false, + localUserId: null, types: [ { title: this.$t('pages.database.subpages.access.read'), value: 'read' }, { title: this.$t('pages.database.subpages.access.write-own'), value: 'write_own' }, @@ -169,36 +169,48 @@ export default { }, revokeAccess () { const accessService = useAccessService() - accessService.remove(this.$route.params.database_id, this.userId) + accessService.remove(this.$route.params.database_id, this.localUserId) .then(() => { const toast = useToastInstance() - toast.success(this.$t('notifications.access.revoked')) + toast.success(this.$t('success.access.revoked')) this.$emit('close-dialog', { success: true }) }) + .catch(({code, message}) => { + const toast = useToastInstance() + toast.error(message) + }) .finally(() => { this.loading = false }) }, modifyAccess () { const accessService = useAccessService() - accessService.modify(this.$route.params.database_id, this.userId, this.modify) + accessService.modify(this.$route.params.database_id, this.localUserId, this.modify) .then(() => { const toast = useToastInstance() - toast.success(this.$t('notifications.access.modified')) + toast.success(this.$t('success.access.modified')) this.$emit('close-dialog', { success: true }) }) + .catch(({code, message}) => { + const toast = useToastInstance() + toast.error(message) + }) .finally(() => { this.loading = false }) }, giveAccess () { const accessService = useAccessService() - accessService.create(this.$route.params.database_id, this.userId, this.modify) + accessService.create(this.$route.params.database_id, this.localUserId, this.modify) .then(() => { const toast = useToastInstance() - toast.success(this.$t('notifications.access.created')) + toast.success(this.$t('success.access.created')) this.$emit('close-dialog', { success: true }) }) + .catch(({code, message}) => { + const toast = useToastInstance() + toast.error(message) + }) .finally(() => { this.loading = false }) @@ -210,6 +222,10 @@ export default { .then((users) => { this.users = users.filter(u => u.username !== this.database.creator.username) }) + .catch(({code}) => { + const toast = useToastInstance() + toast.error(this.$t(code)) + }) .finally(() => { this.loadingUsers = false }) @@ -217,6 +233,8 @@ export default { init () { if (!this.userId) { this.loadUsers() + } else { + this.localUserId = this.userId } if (!this.accessType) { this.modify.type = null diff --git a/dbrepo-ui/components/dialogs/EditTuple.vue b/dbrepo-ui/components/dialogs/EditTuple.vue index 589c82b9b7..da8000dae2 100644 --- a/dbrepo-ui/components/dialogs/EditTuple.vue +++ b/dbrepo-ui/components/dialogs/EditTuple.vue @@ -179,6 +179,7 @@ export default { } }, mounted() { + this.$refs.form.validate() this.oldTuple = Object.assign({}, this.tuple) }, computed: { @@ -212,7 +213,7 @@ export default { hint += ' ' + this.$t('pages.table.subpages.data.primary-key.hint') } if (['double', 'decimal'].includes(column_type)) { - hint += ' ' + this.$t('pages.table.subpages.data.format.hint') + ` ${'d'.repeat(size)}.${'f'.repeat(d)}` + hint += ' ' + this.$t('pages.table.subpages.data.format.hint') + ' ddd.f' } if (['date', 'datetime', 'timestamp', 'time'].includes(column_type) && date_format) { hint += ' ' + this.$t('pages.table.subpages.data.format.hint') + ' ' + date_format.unix_format @@ -258,7 +259,7 @@ export default { rules.push(v => v !== null || this.$t('validation.required')) if (column.column_type === 'decimal' || column.column_type === 'double') { rules.push(v => !(!v || v.split('.')[0].length > column.size) || `${this.$t('pages.table.subpages.data.float.max')} ${column.size} ${this.$t('pages.table.subpages.data.float.before')}`) - rules.push(v => !(!v || v.split('.')[1].length > column.d) || `${this.$t('pages.table.subpages.data.float.max')} ${column.d} ${this.$t('pages.table.subpages.data.float.after')}`) + rules.push(v => !(!v || (column.d && v.split('.')[1].length > column.d)) || `${this.$t('pages.table.subpages.data.float.max')} ${column.d} ${this.$t('pages.table.subpages.data.float.after')}`) } return rules }, diff --git a/dbrepo-ui/components/subset/Builder.vue b/dbrepo-ui/components/subset/Builder.vue index 7c30fd8f5a..9ae9916203 100644 --- a/dbrepo-ui/components/subset/Builder.vue +++ b/dbrepo-ui/components/subset/Builder.vue @@ -370,6 +370,12 @@ export default { database () { return this.cacheStore.getDatabase }, + columnTypes () { + if (!this.database) { + return [] + } + return this.database.container.image.data_types + }, user () { return this.userStore.getUser }, @@ -533,7 +539,7 @@ export default { return } const queryService = useQueryService() - const { error, reason, column, raw, formatted } = queryService.build(this.table.internal_name, this.select, this.clauses) + const { error, reason, column, raw, formatted } = queryService.build(this.table.internal_name, this.select, this.columnTypes, this.clauses) if (error) { const toast = useToastInstance() toast.error(this.$t('error.query.' + reason) + ' ' + column) diff --git a/dbrepo-ui/components/subset/Results.vue b/dbrepo-ui/components/subset/Results.vue index 95becef12c..4ba414309c 100644 --- a/dbrepo-ui/components/subset/Results.vue +++ b/dbrepo-ui/components/subset/Results.vue @@ -8,6 +8,7 @@ :items="result.rows" :items-length="total" :footer-props="footerProps" + :items-per-page-options="footerProps.itemsPerPageOptions" @update:options="updateOptions" /> </div> </template> diff --git a/dbrepo-ui/components/subset/SubsetToolbar.vue b/dbrepo-ui/components/subset/SubsetToolbar.vue index 5c5081a2f8..db9452feaa 100644 --- a/dbrepo-ui/components/subset/SubsetToolbar.vue +++ b/dbrepo-ui/components/subset/SubsetToolbar.vue @@ -166,11 +166,17 @@ export default { } return this.subset.creator.username === this.username }, + hasReadAccess () { + if (!this.access) { + return false + } + return this.access.type === 'read' || this.access.type === 'write_all' || this.access.type === 'write_own' + }, canGetPid () { if (!this.user || !this.subset || !this.database) { return false } - return this.database.owner.id === this.user.id || (this.subset.creator.id === this.user.id && UserUtils.hasReadAccess(this.access)) + return this.database.owner.id === this.user.id || (this.subset.creator.id === this.user.id && this.hasReadAccess) }, title () { if (!this.identifier) { diff --git a/dbrepo-ui/components/table/TableImport.vue b/dbrepo-ui/components/table/TableImport.vue index e55db4130e..f1658a58bb 100644 --- a/dbrepo-ui/components/table/TableImport.vue +++ b/dbrepo-ui/components/table/TableImport.vue @@ -83,39 +83,6 @@ </v-select> </v-col> </v-row> - <v-row dense> - <v-col md="8"> - <v-text-field - v-model="tableImport.null_element" - clearable - persistent-hint - :variant="inputVariant" - :hint="$t('pages.table.subpages.import.null.hint')" - :label="$t('pages.table.subpages.import.null.label')"/> - </v-col> - </v-row> - <v-row dense> - <v-col md="8"> - <v-text-field - v-model="tableImport.true_element" - clearable - persistent-hint - :variant="inputVariant" - :hint="$t('pages.table.subpages.import.true.hint')" - :label="$t('pages.table.subpages.import.true.label')"/> - </v-col> - </v-row> - <v-row dense> - <v-col md="8"> - <v-text-field - v-model="tableImport.false_element" - clearable - persistent-hint - :variant="inputVariant" - :hint="$t('pages.table.subpages.import.false.hint')" - :label="$t('pages.table.subpages.import.false.label')"/> - </v-col> - </v-row> </v-container> </v-form> </v-stepper-window> @@ -356,9 +323,6 @@ export default { this.cacheStore.setUploadProgress(null) this.setQueryParamSafely('location') this.setQueryParamSafely('quote') - this.setQueryParamSafely('false_element') - this.setQueryParamSafely('true_element') - this.setQueryParamSafely('null_element') this.setQueryParamSafely('separator') this.setQueryParamSafely('line_termination') this.setQueryParamSafely('skip_lines') @@ -541,9 +505,6 @@ export default { separator: this.tableImport.separator, skip_lines: this.tableImport.skip_lines, quote: this.tableImport.quote, - null_element: this.tableImport.null_element, - true_element: this.tableImport.true_element, - false_element: this.tableImport.false_element }) this.loading = false }) diff --git a/dbrepo-ui/components/table/TableSchema.vue b/dbrepo-ui/components/table/TableSchema.vue index da30905fa3..5e105577d7 100644 --- a/dbrepo-ui/components/table/TableSchema.vue +++ b/dbrepo-ui/components/table/TableSchema.vue @@ -14,7 +14,10 @@ <v-text-field v-model="c.name" required - :rules="[v => !!v || $t('validation.required')]" + :rules="[ + v => !!v || $t('validation.required'), + v => this.columns.filter(column => column.name === v).length === 1 || $t('validation.column.exists') + ]" persistent-hint :variant="inputVariant" :label="$t('pages.table.subpages.schema.name.label')" @@ -25,7 +28,7 @@ <v-select v-model="c.type" :items="columnTypes" - item-title="text" + item-title="display_name" item-value="value" required :rules="[v => !!v || $t('validation.required')]" @@ -68,43 +71,43 @@ @focusout="formatValues(c)" /> </v-col> <v-col - v-if="defaultSize(c) !== false" + v-if="columnType(c) && columnType(c).size_required !== null" cols="1"> <v-text-field v-model.number="c.size" type="number" - required + :min="columnType(c).size_min !== null ? columnType(c).size_min : null" + :max="columnType(c).size_max !== null ? columnType(c).size_max : null" + :step="columnType(c).size_step" + :hint="sizeHint(c)" + :clearable="!columnType(c).size_required" + persistent-hint :variant="inputVariant" - :rules="[v => (v !== null && v !== '') || $t('validation.required')]" + :rules="[ + v => !(columnType(c).size_required && (v === null || v === '')) || $t('validation.required') + ]" :error-messages="sizeErrorMessages(c)" :label="$t('pages.table.subpages.schema.size.label')" /> </v-col> <v-col - v-if="defaultD(c) !== false" + v-if="columnType(c) && columnType(c).d_required !== null" cols="1"> <v-text-field v-model.number="c.d" type="number" - required + :min="columnType(c).d_min !== null ? columnType(c).d_min : null" + :max="columnType(c).d_max !== null ? columnType(c).d_max : null" + :step="columnType(c).d_step" + :hint="dHint(c)" + :clearable="!columnType(c).d_required" + persistent-hint :variant="inputVariant" - :rules="[v => (v !== null && v !== '') || $t('validation.required')]" + :rules="[ + v => !(columnType(c).d_required && (v === null || v === '')) || $t('validation.required') + ]" :error-messages="dErrorMessages(c)" :label="$t('pages.table.subpages.schema.d.label')" /> </v-col> - <v-col - cols="2" - v-if="hasDate(c)"> - <v-select - v-model="c.dfid" - required - :variant="inputVariant" - :disabled="disabled" - :rules="[v => !!v || $t('validation.required')]" - :items="filterDateFormats(c)" - item-title="unix_format" - item-value="id" - :label="$t('pages.table.subpages.schema.fsp.label')" /> - </v-col> <v-col v-if="shift(c)" :cols="shift(c)" /> @@ -219,7 +222,6 @@ export default { return { valid: false, tableColumns: [], - columnTypes: useQueryService().mySql8DataTypes(), cacheStore: useCacheStore() } }, @@ -227,6 +229,12 @@ export default { database () { return this.cacheStore.getDatabase }, + columnTypes () { + if (!this.database) { + return [] + } + return this.database.container.image.data_types + }, dateFormats () { if (!this.database || !('container' in this.database) || !('image' in this.database.container) || !('date_formats' in this.database.container.image)) { return [] @@ -258,16 +266,10 @@ export default { return false } let shift = 0 - if (this.hasDate(column) === false && this.columns.filter(c => this.hasDate(c) !== false).length > 0) { - shift++ - } - if (this.defaultSize(column) === false && this.columns.filter(c => this.defaultSize(c) !== false).length > 0) { - shift++ - } - if (this.defaultD(column) === false && this.columns.filter(c => this.defaultD(c) !== false).length > 0) { + if (!this.hasEnumOrSet(column) && (this.columnType(column).size_required === null || this.columnType(column).size_required === undefined) && this.columns.filter(c => (this.columnType(c).size_required !== null || this.columnType(c).size_required !== undefined)).length > 0) { shift++ } - if (this.hasEnumOrSet(column) === false && this.columns.filter(c => this.hasEnumOrSet(c) !== false).length > 0) { + if (!this.hasEnumOrSet(column) && (this.columnType(column).d_required === null || this.columnType(column).d_required === undefined) && this.columns.filter(c => (this.columnType(c).d_required !== null || this.columnType(c).d_required !== undefined)).length > 0) { shift++ } return shift @@ -298,7 +300,6 @@ export default { type, null_allowed, primary_key, - dfid: null, sets: [], sets_values: null, enums: [], @@ -321,31 +322,57 @@ export default { column.enums = column.enums_values.split(',').map(v => v.trim()) } }, - defaultSize (column) { + columnType (column) { const filter = this.columnTypes.filter(t => t.value === column.type) if (!filter || filter.length === 0) { return false } - if (filter[0].defaultSize === undefined || filter[0].defaultSize === null) { - return false + return filter[0] + }, + sizeHint (column) { + let hint = '' + if (this.columnType(column).size_min !== null) { + hint += `min. ${this.columnType(column).size_min}` + } + if (this.columnType(column).size_max) { + if (hint.length > 0) { + hint += ', ' + } + hint += `max. ${this.columnType(column).size_max}` } - return filter[0].defaultSize + if (!this.columnType(column).size_required) { + hint += ' (optional)' + } + return hint }, - defaultD (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false + dHint (column) { + let hint = '' + if (this.columnType(column).d_min !== null) { + hint += `min. ${this.columnType(column).d_min}` } - if (filter[0].defaultD === undefined || filter[0].defaultD === null) { - return false + if (this.columnType(column).d_max) { + if (hint.length > 0) { + hint += ', ' + } + hint += `max. ${this.columnType(column).d_max}` + } + if (!this.columnType(column).d_required) { + hint += ' (optional)' } - return filter[0].defaultD + return hint }, setDefaultSizeAndD (column) { - column.size = this.defaultSize(column) - column.d = this.defaultD(column) - column.dfid = null - console.debug('for column type', column.type, 'set default size', column.size, '& d', column.d, '& dfid', column.dfid) + if (this.columnType(column).size_default !== null) { + column.size = this.columnType(column).size_default + } else { + column.size = null + } + if (this.columnType(column).d_default !== null) { + column.d = this.columnType(column).d_default + } else { + column.d = null + } + console.debug('for column type', column.type, 'set default size', column.size, '& d', column.d) }, hasDate (column) { return column.type === 'date' || column.type === 'datetime' || column.type === 'timestamp' || column.type === 'time' diff --git a/dbrepo-ui/composables/access-service.ts b/dbrepo-ui/composables/access-service.ts index c08e5d0b9f..056efec117 100644 --- a/dbrepo-ui/composables/access-service.ts +++ b/dbrepo-ui/composables/access-service.ts @@ -21,7 +21,7 @@ export const useAccessService = (): any => { const axios = useAxiosInstance() console.debug('create access for user with id', userId, 'of database with id', databaseId) return new Promise<DatabaseAccessDto>((resolve, reject) => { - axios.post<DatabaseAccessDto>(`/api/database/${databaseId}/access`, payload) + axios.post<DatabaseAccessDto>(`/api/database/${databaseId}/access/${userId}`, payload) .then((response) => { console.info('Created access for user with id', userId, 'of database with id', databaseId) resolve(response.data) diff --git a/dbrepo-ui/composables/query-service.ts b/dbrepo-ui/composables/query-service.ts index f5d805b958..b3c21c6053 100644 --- a/dbrepo-ui/composables/query-service.ts +++ b/dbrepo-ui/composables/query-service.ts @@ -126,7 +126,7 @@ export const useQueryService = (): any => { }) } - function build(table: TableDto, columns: ColumnDto[], clauses: any[]): QueryBuildResultDto { + function build(table: TableDto, columns: ColumnDto[], types: DataTypeDto[], clauses: any[]): QueryBuildResultDto { var sql = 'SELECT' for (let i = 0; i < columns.length; i++) { sql += `${i > 0 ? ',' : ''} \`${columns[i].internal_name}\`` @@ -140,8 +140,8 @@ export const useQueryService = (): any => { sql += ` ${clause.type.toUpperCase()} ` continue } - const fCol = columns.filter(c => c.internal_name === clause.params[0]) - if (fCol.length === 0) { + const filteredColumn = columns.filter(c => c.internal_name === clause.params[0]) + if (filteredColumn.length === 0) { return { error: true, reason: 'column.exists', @@ -151,26 +151,26 @@ export const useQueryService = (): any => { } } sql += ` \`${clause.params[0]}\` ${clause.params[1]} ` - const fCon = mySql8DataTypes().filter(t => t.value === fCol[0].column_type) - if (fCol.length === 0) { + const filteredType = types.filter(t => t.value === filteredColumn[0].column_type) + if (filteredType.length === 0) { return { error: true, - reason: 'type.exists', - column: fCol[0].column_type, + reason: 'exists', + column: filteredColumn[0].column_type, raw: null, formatted: null } } - if (!fCon[0].isBuildable) { + if (!filteredType[0].is_buildable) { return { error: true, - reason: 'type.build', - column: fCol[0].column_type, + reason: 'build', + column: filteredColumn[0].column_type, raw: null, formatted: null } } - if (fCon[0].quoted) { + if (filteredType[0].is_quoted) { sql += `'${clause.params[2]}'` } else { sql += `${clause.params[2]}` @@ -196,39 +196,5 @@ export const useQueryService = (): any => { return {timestamp, page, size} } - function mySql8DataTypes(): MySql8DataType[] { - return [ - {value: 'bigint', text: 'BIGINT(size)', defaultSize: 255, defaultD: null, quoted: false, isBuildable: true}, - {value: 'binary', text: 'BINARY(size)', defaultSize: 1, defaultD: null, quoted: false, isBuildable: false}, - {value: 'bit', text: 'BIT(size)', defaultSize: 1, defaultD: null, quoted: false, isBuildable: true}, - {value: 'blob', text: 'BLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false}, - {value: 'bool', text: 'BOOL', defaultSize: null, defaultD: null, quoted: false, isBuildable: true}, - {value: 'char', text: 'CHAR(size)', defaultSize: 1, defaultD: null, quoted: true, isBuildable: true}, - {value: 'date', text: 'DATE', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'datetime', text: 'DATETIME(fsp)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'decimal', text: 'DECIMAL(size, d)', defaultSize: 40, defaultD: 10, quoted: false, isBuildable: true}, - {value: 'double', text: 'DOUBLE(size, d)', defaultSize: 40, defaultD: 10, quoted: false, isBuildable: true}, - {value: 'enum', text: 'ENUM(val1,val2,...)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'float', text: 'FLOAT(p)', defaultSize: 24, defaultD: null, quoted: false, isBuildable: true}, - {value: 'int', text: 'INT(size)', defaultSize: 255, defaultD: null, quoted: false, isBuildable: true}, - {value: 'longblob', text: 'LONGBLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false}, - {value: 'longtext', text: 'LONGTEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'mediumblob', text: 'MEDIUMBLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false}, - {value: 'mediumint', text: 'MEDIUMINT(size)', defaultSize: 40, defaultD: null, quoted: false, isBuildable: true}, - {value: 'mediumtext', text: 'MEDIUMTEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'set', text: 'SET(val1,val2,...)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'smallint', text: 'SMALLINT(size)', defaultSize: 10, defaultD: null, quoted: false, isBuildable: true}, - {value: 'text', text: 'TEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'time', text: 'TIME(fsp)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'timestamp', text: 'TIMESTAMP(fsp)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'tinyblob', text: 'TINYBLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false}, - {value: 'tinyint', text: 'TINYINT(size)', defaultSize: 10, defaultD: null, quoted: false, isBuildable: true}, - {value: 'tinytext', text: 'TINYTEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'year', text: 'YEAR', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'varbinary', text: 'VARBINARY(size)', defaultSize: 1, defaultD: null, quoted: false, isBuildable: false}, - {value: 'varchar', text: 'VARCHAR(size)', defaultSize: 255, defaultD: null, quoted: true, isBuildable: true} - ] - } - - return {findAll, findOne, update, exportCsv, execute, reExecuteData, reExecuteCount, build, mySql8DataTypes} + return {findAll, findOne, update, exportCsv, execute, reExecuteData, reExecuteCount, build} } diff --git a/dbrepo-ui/composables/table-service.ts b/dbrepo-ui/composables/table-service.ts index ffd7ebcd60..3d87e68d4f 100644 --- a/dbrepo-ui/composables/table-service.ts +++ b/dbrepo-ui/composables/table-service.ts @@ -210,7 +210,6 @@ export const useTableService = (): any => { type: c.type, size: c.size ? c.size : null, d: c.d ? c.d : null, - dfid: c.dfid ? c.dfid : null, enums: c.enums_values ? c.enums_values.split(',') : [], sets: c.sets_values ? c.sets_values.split(',') : [], index_length: c.index_length, diff --git a/dbrepo-ui/dto/index.ts b/dbrepo-ui/dto/index.ts index df0babcfe1..543bd0a391 100644 --- a/dbrepo-ui/dto/index.ts +++ b/dbrepo-ui/dto/index.ts @@ -532,9 +532,6 @@ interface ImportCsv { separator: string; quote: string; skip_lines: number; - false_element: string; - true_element: string; - null_element: string; line_termination: string; } @@ -562,7 +559,6 @@ interface ColumnCreateDto { type: string; size: number | null; d: number | null; - dfid: number | null; enums: string[]; sets: string[]; index_length: number; @@ -574,7 +570,6 @@ interface InternalColumnDto { type: string; size: number; d: number; - dfid: number; enums: string[]; sets: string[]; primary_key: boolean; @@ -658,9 +653,6 @@ interface ImportDto { separator: string; quote: string; skip_lines: number; - false_element: string; - true_element: string; - null_element: string; line_termination: string; } diff --git a/dbrepo-ui/dto/mysql.ts b/dbrepo-ui/dto/mysql.ts index b100da017c..c366e43f64 100644 --- a/dbrepo-ui/dto/mysql.ts +++ b/dbrepo-ui/dto/mysql.ts @@ -1,8 +1,15 @@ -interface MySql8DataType { +interface DataTypeDto { + display_name: string; value: string; - text: string; - defaultSize: number | null; - defaultD: number | null; - quoted: boolean; - isBuildable: boolean; + size_min: number | null; + size_max: number | null; + size_default: number | null; + size_required: number | null; + d_min: number | null; + d_max: number | null; + d_default: number | null; + d_required: number | null; + documentation: string; + is_quoted: boolean; + is_buildable: boolean; } diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json index 6abf715bc0..db2c41bfd2 100644 --- a/dbrepo-ui/locales/en-US.json +++ b/dbrepo-ui/locales/en-US.json @@ -32,7 +32,8 @@ "no": "No", "mine": "(mine)", "loading": "Loading", - "view": "View" + "view": "View", + "modify": "Modify" }, "pages": { "identifier": { @@ -611,10 +612,10 @@ "access": { "title": "Database Access", "subtitle": "Overview on users with their access to the database", - "read": "You can read all contents", - "write-own": "You can write own tables and read all contents", - "write-all": "You can write own tables and read all contents", - "revoke": "Revoke", + "read": "Read all contents", + "write-own": "Read all contents & write own tables", + "write-all": "Read all contents & write all tables", + "revoke": "No access", "action": "Action", "username": { "label": "Username", @@ -623,9 +624,6 @@ "type": { "label": "Access Type", "hint": "Required" - }, - "submit": { - "text": "Modify" } }, "create": { @@ -1144,8 +1142,8 @@ "query": { "missing": "Failed to find query in data service", "invalid": "Query is invalid", - "type.exists": "Failed to build query: no such column type", - "type.build": "Failed to build query: currently no query build support for column type", + "exists": "Failed to build query: no such column type", + "build": "Failed to build query: currently no query build support for column type", "column.exists": "Failed to build query: data columns are missing column with name" }, "store": { @@ -1442,6 +1440,9 @@ "pattern": "Invalid URI", "exists": "URI exists" }, + "column": { + "exists": "Column with this name exists" + }, "user": { "pattern": "Only lowercase letters, min. 3 length", "exists": "This username is already taken" diff --git a/dbrepo-ui/nuxt.config.ts b/dbrepo-ui/nuxt.config.ts index f33c990a71..80b866f1c1 100644 --- a/dbrepo-ui/nuxt.config.ts +++ b/dbrepo-ui/nuxt.config.ts @@ -102,13 +102,13 @@ export default defineNuxtConfig({ endpoint: 'https://doi.org' }, links: { - rabbitmq: { - text: 'RabbitMQ Admin', - href: '/admin/broker/' - }, keycloak: { - text: 'Keycloak Admin', - href: '/api/auth/' + text: 'Auth Service', + href: 'http://localhost/api/auth/' + }, + grafana: { + text: 'Dashboard Service', + href: 'http://localhost:3000/dashboards' } } } @@ -123,8 +123,7 @@ export default defineNuxtConfig({ modules: [ '@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt', - '@nuxtjs/i18n', - '@artmizu/nuxt-prometheus' + '@nuxtjs/i18n' ], pinia: { diff --git a/dbrepo-ui/package.json b/dbrepo-ui/package.json index a1ed44b93a..2bbe6696bc 100644 --- a/dbrepo-ui/package.json +++ b/dbrepo-ui/package.json @@ -11,7 +11,6 @@ "prod": "bun run .output/server/index.mjs" }, "dependencies": { - "@artmizu/nuxt-prometheus": "^2.4.0", "@fontsource/open-sans": "^5.0.24", "@mdi/font": "^7.4.47", "@nuxtjs/robots": "^3.0.0", diff --git a/dbrepo-ui/pages/database/[database_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/settings.vue index 09c3d8263b..1d2d9dca0c 100644 --- a/dbrepo-ui/pages/database/[database_id]/settings.vue +++ b/dbrepo-ui/pages/database/[database_id]/settings.vue @@ -96,8 +96,9 @@ v-if="item && item.user && item.user.username !== user.username" size="x-small" variant="flat" + color="warning" :disabled="!canModifyAccess" - :text="$t('pages.database.subpages.access.submit.text')" + :text="$t('navigation.modify')" @click="modifyAccess(item)" /> </template> </v-data-table> @@ -107,7 +108,7 @@ variant="flat" :disabled="!canCreateAccess" color="warning" - :text="$t('pages.database.subpages.access.submit.text')" + :text="$t('navigation.create')" @click="giveAccess" /> </v-card-text> </v-card> @@ -422,7 +423,7 @@ export default { this.$refs.form.validate() }, closeDialog () { - this.reloadDatabase() + this.cacheStore.reloadDatabase() this.editAccessDialog = false }, updateDatabaseVisibility () { @@ -510,11 +511,11 @@ export default { updateDatabaseOwner () { this.loading = true const databaseService = useDatabaseService() - databaseService.updateOwner(this.$route.params.database_id, this.modifyOwner.id) + databaseService.updateOwner(this.$route.params.database_id, { id: this.modifyOwner.id }) .then(() => { const toast = useToastInstance() toast.success(this.$t('success.database.transfer')) - location.reload() + this.$router.push(`/database/${this.$route.params.database_id}/info`) }) .catch(() => { this.loading = false diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue index bcab9b60be..72e7501d16 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue @@ -75,6 +75,7 @@ :loading="loadingData || loadingCount" :options.sync="options" :footer-props="footerProps" + :items-per-page-options="footerProps.itemsPerPageOptions" @update:options="loadData"> <template v-for="(blobColumn, idx) in blobColumns" diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue index 3a821a730b..7e8aed6717 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue @@ -217,12 +217,20 @@ export default { }, methods: { extra (column) { - if (['date', 'datetime', 'timestamp', 'time'].includes(column.column_type)) { - return `fsp=${column.date_format.unix_format}` - } else if (column.column_type === 'float') { - return `p=${column.size}` + if (column.column_type === 'float') { + return `precision=${column.size}` } else if (['decimal', 'double'].includes(column.column_type)) { - return `size=${column.size} d=${column.d}` + let extra = '' + if (column.size !== null) { + extra += `size=${column.size}` + } + if (column.d !== null) { + if (extra.length > 0) { + extra += ', ' + } + extra += `d=${column.d}` + } + return extra } else if (column.column_type === 'enum') { return `(${column.enums.join(', ')})` } else if (column.column_type === 'set') { diff --git a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue index c3b5a38c7a..e1d71643ea 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/create/dataset.vue @@ -241,9 +241,6 @@ export default { tableImport: { location: null, quote: '"', - false_element: null, - true_element: null, - null_element: '', separator: ',', line_termination: null, skip_lines: 1 @@ -364,9 +361,6 @@ export default { this.tableImport.separator = separator this.tableImport.skip_lines = skip_lines this.tableImport.quote = quote - this.tableImport.null_element = null_element - this.tableImport.true_element = true_element - this.tableImport.false_element = false_element if (filename) { this.step = 4 } diff --git a/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue index 6642e89bf4..79493ec39f 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/create/schema.vue @@ -269,13 +269,13 @@ export default { this.cacheStore.reloadDatabase() this.table = table }) - .catch(({code}) => { + .catch(({code, message}) => { this.loading = false const toast = useToastInstance() if (typeof code !== 'string') { return } - toast.error(this.$t(code)) + toast.error(message) }) .finally(() => { this.loading = false diff --git a/dbrepo-ui/pages/semantic/index.vue b/dbrepo-ui/pages/semantic/index.vue index f6b6721b17..c5f12d48f4 100644 --- a/dbrepo-ui/pages/semantic/index.vue +++ b/dbrepo-ui/pages/semantic/index.vue @@ -27,7 +27,8 @@ :items="rows" :options.sync="options" :server-items-length="total" - :footer-props="footerProps"> + :footer-props="footerProps" + :items-per-page-options="footerProps.itemsPerPageOptions"> <template v-slot:item.uri="{ item }"> <a :href="item.uri" target="_blank" v-text="item.uri" /> </template> @@ -83,7 +84,7 @@ export default { }, total: -1, footerProps: { - 'items-per-page-options': [10, 20, 30, 40, 50] + itemsPerPageOptions: [10, 25, 50, 100] }, tab: 0, tabs: [ diff --git a/dbrepo-ui/server/routes/actuator/prometheus.ts b/dbrepo-ui/server/routes/actuator/prometheus.ts new file mode 100644 index 0000000000..3647103148 --- /dev/null +++ b/dbrepo-ui/server/routes/actuator/prometheus.ts @@ -0,0 +1,4 @@ +export default defineEventHandler((event) => { + event.node.res.setHeader('Content-Type', 'text/plain'); + return 'service_started 1' +}) diff --git a/docker-compose.yml b/docker-compose.yml index 5effbfdb0b..9a13b7ca63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ volumes: search-db-data: storage-service-data: identity-service-data: + metric-db-data: + dashboard-service-data: services: dbrepo-metadata-db: @@ -207,16 +209,19 @@ services: restart: "no" container_name: dbrepo-broker-service hostname: broker-service - image: docker.io/bitnami/rabbitmq:3.12-debian-12 + image: docker.io/bitnami/rabbitmq:3.13.7-debian-12-r4 ports: - 15672:15672 - 5672:5672 + - 1883:1883 volumes: - ./dbrepo-broker-service/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf - ./dbrepo-broker-service/advanced.config:/etc/rabbitmq/advanced.config - ./dbrepo-broker-service/enabled_plugins:/etc/rabbitmq/enabled_plugins - ./dbrepo-broker-service/definitions.json:/app/definitions.json - broker-service-data:/bitnami/rabbitmq/mnesia + environment: + RABBITMQ_FEATURE_FLAGS: mqtt_v5 depends_on: dbrepo-identity-service: condition: service_healthy @@ -432,6 +437,7 @@ services: image: bitnami/prometheus:2.54.1-debian-12-r4 volumes: - ./dbrepo-metric-db/prometheus.yml:/etc/prometheus/prometheus.yml + - metric-db-data:/opt/bitnami/prometheus/data ports: - 9090:9090 healthcheck: @@ -450,10 +456,9 @@ services: build: context: ./dbrepo-dashboard-service network: host + volumes: + - dashboard-service-data:/opt/bitnami/grafana/data environment: - GF_SERVER_DOMAIN: "dashboard-service" - GF_SERVER_ROOT_URL: "${BASE_URL:-http://localhost}/dashboard/" - GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION: "true" LDAP_ADMIN_USERNAME: "${IDENTITY_SERVICE_ADMIN_USERNAME:-admin}" LDAP_ADMIN_PASSWORD: "${IDENTITY_SERVICE_ADMIN_PASSWORD:-admin}" LDAP_ROOT: "${IDENTITY_SERVICE_ROOT:-dc=dbrepo,dc=at}" @@ -534,7 +539,7 @@ services: BROKER_HOST: "${BROKER_ENDPOINT:-broker-service}" BROKER_PASSWORD: "${SYSTEM_PASSWORD:-admin}" BROKER_PORT: ${BROKER_PORT:-5672} - BROKER_SERVICE_ENDPOINT: "${BROKER_SERVICE_ENDPOINT:-http://gateway-service/admin/broker}" + BROKER_SERVICE_ENDPOINT: "${BROKER_SERVICE_ENDPOINT:-http://broker-service:15672}" BROKER_USERNAME: "${SYSTEM_USERNAME:-admin}" BROKER_VIRTUALHOST: "${BROKER_VIRTUALHOST:-dbrepo}" CONNECTION_TIMEOUT: ${CONNECTION_TIMEOUT:-60000} diff --git a/helm/dbrepo/charts/dbrepo-mariadb-galera-1.4.6.tgz b/helm/dbrepo/charts/dbrepo-mariadb-galera-1.4.6.tgz index f5a16c907c311685e859a0f971aeae9e603300bc..4af22ff6e998199f69a8e1ff43fd96c3f55aa8ec 100644 GIT binary patch delta 46458 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­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 CXNlkd delta 46459 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 Ma-K=n3?Zxx0QgS8<p2Nx diff --git a/helm/dbrepo/values.schema.json b/helm/dbrepo/values.schema.json index 072efb6fe1..a8e7bbbf74 100644 --- a/helm/dbrepo/values.schema.json +++ b/helm/dbrepo/values.schema.json @@ -386,6 +386,23 @@ }, "service": { "properties": { + "extraPorts": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "targetPort": { + "type": "integer" + } + }, + "type": "object" + }, + "type": "array" + }, "managerPortEnabled": { "type": "boolean" }, diff --git a/helm/dbrepo/values.yaml b/helm/dbrepo/values.yaml index d4133c8465..d889cc85b7 100644 --- a/helm/dbrepo/values.yaml +++ b/helm/dbrepo/values.yaml @@ -262,7 +262,7 @@ brokerservice: enabled: true existingSecret: broker-service-secret ## @param brokerservice.extraPlugins The list of plugins to be activated. - extraPlugins: rabbitmq_prometheus rabbitmq_auth_backend_ldap rabbitmq_auth_mechanism_ssl + extraPlugins: rabbitmq_prometheus rabbitmq_auth_backend_ldap rabbitmq_auth_mechanism_ssl rabbitmq_mqtt persistence: ## @param brokerservice.persistence.enabled If set to true, a PVC will be created. enabled: false @@ -270,6 +270,10 @@ brokerservice: service: type: ClusterIP managerPortEnabled: true + extraPorts: + - name: mqtt + port: 1883 + targetPort: 1883 # loadBalancerIP: ## @param brokerservice.replicaCount The number of replicas. replicaCount: 1 diff --git a/install.sh b/install.sh index a59965816b..bce01f0ec3 100644 --- a/install.sh +++ b/install.sh @@ -58,7 +58,7 @@ fi # environment echo "[🚀] Gathering environment for version ${VERSION} ..." -curl -sSL -o ./dist.tar.gz "https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/${VERSION}/dist.tar.gz" +curl -ksSL -o ./dist.tar.gz "https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/${VERSION}/dist.tar.gz" tar xzfv ./dist.tar.gz if [[ $DOWNLOAD_ONLY -eq 1 ]]; then diff --git a/lib/python/dbrepo/AmqpClient.py b/lib/python/dbrepo/AmqpClient.py index 27f7fc4f0f..cd0b114099 100644 --- a/lib/python/dbrepo/AmqpClient.py +++ b/lib/python/dbrepo/AmqpClient.py @@ -4,6 +4,8 @@ import sys import json import logging +from dbrepo.api.exceptions import AuthenticationError + logging.basicConfig(format='%(asctime)s %(name)-12s %(levelname)-6s %(message)s', level=logging.INFO, stream=sys.stdout) @@ -14,9 +16,9 @@ class AmqpClient: via environment variables, e.g. set endpoint with DBREPO_ENDPOINT. You can override the constructor parameters \ with the environment variables. - :param broker_host: The AMQP API host. Optional. Default: "broker-service" - :param broker_port: The AMQP API port. Optional. Default: 5672 - :param broker_virtual_host: The AMQP API virtual host. Optional. Default: "/" + :param broker_host: The AMQP API host. Optional. Default: "localhost". + :param broker_port: The AMQP API port. Optional. Default: 5672, + :param broker_virtual_host: The AMQP API virtual host. Optional. Default: "dbrepo". :param username: The AMQP API username. Optional. :param password: The AMQP API password. Optional. """ @@ -27,9 +29,9 @@ class AmqpClient: password: str = None def __init__(self, - broker_host: str = 'broker-service', + broker_host: str = 'localhost', broker_port: int = 5672, - broker_virtual_host: str = '/', + broker_virtual_host: str = 'dbrepo', username: str = None, password: str = None) -> None: self.broker_host = os.environ.get('AMQP_API_HOST', broker_host) @@ -41,14 +43,16 @@ class AmqpClient: self.username = os.environ.get('AMQP_API_USERNAME', username) self.password = os.environ.get('AMQP_API_PASSWORD', password) - def publish(self, exchange: str, routing_key: str, data=dict) -> None: + def publish(self, routing_key: str, data=dict, exchange: str = 'dbrepo') -> None: """ Publishes data to a given exchange with the given routing key with a blocking connection. - :param exchange: The exchange name. :param routing_key: The routing key. :param data: The data. + :param exchange: The exchange name. Default: "dbrepo". """ + if self.username is None or self.password is None: + raise AuthenticationError(f"Failed to perform request: authentication required") parameters = pika.ConnectionParameters(host=self.broker_host, port=self.broker_port, virtual_host=self.broker_virtual_host, credentials=pika.credentials.PlainCredentials(self.username, -- GitLab