diff --git a/docker-compose.yml b/docker-compose.yml index 2ec72e2df94567571d90c39e96d86094ff19589f..07f8df8fa9704a08e043d468bf13efaf917b99c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -262,6 +262,26 @@ services: logging: driver: json-file + fda-user-service: + restart: on-failure + container_name: dbrepo-user-service + hostname: user-service + build: ./fda-user-service + image: fda-user-service + networks: + core: + ports: + - "9098:9098" + env_file: + - .env + depends_on: + fda-metadata-db: + condition: service_healthy + fda-authentication-service: + condition: service_healthy + logging: + driver: json-file + fda-semantics-service: restart: on-failure container_name: dbrepo-semantics-service diff --git a/fda-gateway-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java b/fda-gateway-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java index 937479e0417e4ffd3f8ed19ec3160d7b9c9ce744..bb1450fde72230d2707facca8592f17f5a18c331 100644 --- a/fda-gateway-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java +++ b/fda-gateway-service/rest-service/src/main/java/at/tuwien/config/GatewayConfig.java @@ -16,6 +16,11 @@ public class GatewayConfig { .method("POST", "GET", "PUT", "DELETE") .filters(f -> f.rewritePath("/api/auth/(?<segment>.*)", "/${segment}")) .uri("lb://authentication-service")) + .route("user-service", r -> r.path("/api/user/**") + .and() + .method("POST", "GET", "PUT", "DELETE") + .and() + .uri("lb://user-service")) .route("broker-service", r -> r.path("/api/broker/**") .and() .method("POST", "GET", "PUT", "DELETE") diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java index f394a018034cd8eaa4d9e44d619934fcb480be25..abd946bdb9231d74479b880e4eb822b6ae14dad3 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java @@ -36,6 +36,9 @@ public class User { @Column(name = "last_name") private String lastname; + @Column(name = "realm id") + private String realmId; + @Column(unique = true, nullable = false) private String email; diff --git a/fda-ui/.env.example b/fda-ui/.env.example index 5a83ca98633b20cb62f0b6c9a2c03bae08a58833..366635afbdd0602eb9736a2ac6dfe47fefb7592e 100644 --- a/fda-ui/.env.example +++ b/fda-ui/.env.example @@ -7,4 +7,5 @@ BROKER_USERNAME=fda BROKER_PASSWORD=fda SANDBOX=false SHARED_FILESYSTEM=/tmp -DBREPO_CLIENT_SECRET= +DBREPO_CLIENT_SECRET=MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG +DEFAULT_PID_PUBLISHER= diff --git a/fda-ui/components/dialogs/Persist.vue b/fda-ui/components/dialogs/Persist.vue index a8595e18fd1151270c670c256f2509997c799b63..7ad914ef20d90eda49a8f9069efb5f617d44b191 100644 --- a/fda-ui/components/dialogs/Persist.vue +++ b/fda-ui/components/dialogs/Persist.vue @@ -77,7 +77,7 @@ required /> </v-col> </v-row> - <v-row v-for="(creator,i) in identifier.creators" :key="`c-${i}`" dense> + <v-row v-for="(creator, i) in identifier.creators" :key="`c-${i}`" dense> <v-col cols="3"> <v-text-field v-model="creator.firstname" @@ -108,7 +108,7 @@ name="orcid" label="ORCID" /> </v-col> - <v-col cols="1" class="mt-5"> + <v-col v-if="i > 0" cols="1" class="mt-5"> <v-btn icon x-small @click="deleteCreator(i)"> <v-icon>mdi-close</v-icon> </v-btn> @@ -334,6 +334,8 @@ export default { }, mounted () { this.loadLicenses() + this.addCreator() + this.identifier.publisher = this.$config.defaultPublisher }, methods: { cancel () { @@ -356,6 +358,9 @@ export default { }) }, deleteCreator (index) { + if (index === 0) { + return + } this.identifier.creators.splice(index, 1) }, deleteRelatedIdentifier (index) { diff --git a/fda-ui/config.js b/fda-ui/config.js index a11e09b5c480b74f978ba2a7d86c5d902cfc40a5..b2ea4ef41e7c38bb63c62df110c71fbfd78cb82d 100644 --- a/fda-ui/config.js +++ b/fda-ui/config.js @@ -15,5 +15,6 @@ config.tokenMax = process.env.TOKEN_MAX || 5 config.elasticPassword = process.env.ELASTIC_PASSWORD || 'elastic' config.elasticPassword = process.env.ELASTIC_PASSWORD || 'elastic' config.clientSecret = process.env.DBREPO_CLIENT_SECRET +config.defaultPublisher = process.env.DEFAULT_PID_PUBLISHER module.exports = config diff --git a/fda-ui/nuxt.config.js b/fda-ui/nuxt.config.js index 840e02341c795c2e4969bea69c85c66c65d945f9..9530a9de944e26865ee5bd3eee9d4762e6ca8aaa 100644 --- a/fda-ui/nuxt.config.js +++ b/fda-ui/nuxt.config.js @@ -1,6 +1,6 @@ import path from 'path' import colors from 'vuetify/es5/util/colors' -import { sandbox, title, icon, brokerUsername, brokerPassword, sharedFilesystem, version, logo, mailVerify, tokenMax, elasticPassword, clientSecret, api, search } from './config' +import { sandbox, title, icon, brokerUsername, brokerPassword, sharedFilesystem, version, logo, mailVerify, tokenMax, elasticPassword, clientSecret, api, search, defaultPublisher } from './config' if (sandbox) { console.info('[FDA] Running in sandbox environment') @@ -77,7 +77,8 @@ export default { mailVerify, tokenMax, elasticPassword, - clientSecret + clientSecret, + defaultPublisher }, proxy: { diff --git a/fda-ui/pages/container/_container_id/database/_database_id/info.vue b/fda-ui/pages/container/_container_id/database/_database_id/info.vue index b9224362e62047fb60aa8b81f72f49b67b949b53..0eebace71286ad4c51a7ce7e9173dc11f6fd83e0 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/info.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/info.vue @@ -4,24 +4,38 @@ <v-progress-linear v-if="loading" /> <v-tabs-items v-model="tab"> <v-tab-item> - <v-card v-if="isDataSteward || hasIdentifier || (!hasIdentifier && isCreator && isResearcher)" flat tile> + <v-card v-if="showIdentifierCard" flat tile> <v-card-title>Identifier</v-card-title> <v-card-text v-if="hasIdentifier"> <v-list dense> <v-list-item> <v-list-item-content> - <v-list-item-title v-if="publisher" class="mt-2"> + <v-list-item-title class="mt-2"> Persistent Identifier </v-list-item-title> - <v-list-item-content v-if="publisher"> + <v-list-item-content> <v-skeleton-loader v-if="loading" type="text" class="skeleton-small" /> <a v-if="!loading" :href="pid">{{ pid }}</a> </v-list-item-content> - <v-list-item-title v-if="publisher" class="mt-2"> + <v-list-item-title class="mt-2"> + Database Title + </v-list-item-title> + <v-list-item-content> + <v-skeleton-loader v-if="loading" type="paragraph" width="50%" /> + <span v-if="!loading">{{ identifier.title }}</span> + </v-list-item-content> + <v-list-item-title class="mt-2"> + Database Description + </v-list-item-title> + <v-list-item-content> + <v-skeleton-loader v-if="loading" type="paragraph" width="50%" /> + <span v-if="!loading">{{ identifier.description }}</span> + </v-list-item-content> + <v-list-item-title class="mt-2"> Database Publisher </v-list-item-title> - <v-list-item-content v-if="publisher"> - {{ publisher }} + <v-list-item-content> + {{ database.identifier.publisher }} </v-list-item-content> <v-list-item-title v-if="identifier.creators.length > 0" class="mt-2"> Creators @@ -93,18 +107,17 @@ </v-list-item> </v-list> </v-card-text> - <v-card-text> + <v-card-text v-if="canCreateIdentifier || canDeleteIdentifier"> <v-card-actions> <v-btn - v-if="!hasIdentifier && (isDataSteward || (!hasIdentifier && isCreator && isResearcher))" + v-if="canCreateIdentifier" small color="primary" @click="editDbDialog = true"> Get Database PID </v-btn> - <!-- v-if="isDataSteward && hasIdentifier"--> <v-btn - v-if="false" + v-if="canDeleteIdentifier" small :loading="loadingDelete" color="error" @@ -114,7 +127,7 @@ </v-card-actions> </v-card-text> </v-card> - <v-divider v-if="isDataSteward || hasIdentifier || (!hasIdentifier && isCreator && isResearcher)" /> + <v-divider v-if="showIdentifierCard" /> <v-card flat tile> <v-card-title>Database</v-card-title> <v-card-text> @@ -135,13 +148,6 @@ <v-skeleton-loader v-if="loading" type="text" class="skeleton-small" /> <span v-if="!loading">{{ internal_name }}</span> </v-list-item-content> - <v-list-item-title v-if="description" class="mt-2"> - Database Description - </v-list-item-title> - <v-list-item-content v-if="description"> - <v-skeleton-loader v-if="loading" type="paragraph" width="50%" /> - <span v-if="!loading">{{ description }}</span> - </v-list-item-content> <v-list-item-title class="mt-2"> Database Creator </v-list-item-title> @@ -222,7 +228,7 @@ import DBToolbar from '@/components/DBToolbar' import Persist from '@/components/dialogs/Persist' import OrcidIcon from '@/components/icons/OrcidIcon' import Citation from '@/components/identifier/Citation' -import { formatTimestampUTCLabel, formatUser, isDataSteward, isResearcher } from '@/utils' +import { formatTimestampUTCLabel, formatUser } from '@/utils' export default { components: { @@ -254,18 +260,6 @@ export default { baseUrl () { return location.protocol + '//' + location.host }, - description () { - if (!this.hasIdentifier) { - return '' - } - return this.database.identifier.description - }, - publisher () { - if (!this.hasIdentifier) { - return '' - } - return this.database.identifier.publisher - }, token () { return this.$store.state.token }, @@ -273,10 +267,10 @@ export default { return this.$store.state.user }, identifier () { - if (this.database) { - return this.$store.state.database.identifier + if (!this.database) { + return null } - return null + return this.$store.state?.database.identifier }, access () { return this.$store.state.access @@ -294,27 +288,12 @@ export default { headers: { Authorization: `Bearer ${this.token}`, Accept: 'application/json' } } }, - isResearcher () { - return isResearcher(this.user) - }, - isDataSteward () { - return isDataSteward(this.user) - }, pid () { return `${this.baseUrl}/pid/${this.database.identifier.id}` }, createdUTC () { return formatTimestampUTCLabel(this.database.created) }, - isCreator () { - if (!this.database) { - return false - } - if (!this.database.creator.username || !this.user || !this.user.username) { - return false - } - return this.database.creator.username === this.user.username - }, language () { return this.database.identifier.language }, @@ -327,6 +306,30 @@ export default { container_internal_name () { return this.database.container.internal_name }, + showIdentifierCard () { + if (this.hasIdentifier) { + return true + } + if (!this.user) { + return false + } + return this.canCreateIdentifier || this.canDeleteIdentifier || this.user.roles.includes('modify-identifier-metadata') + }, + canCreateIdentifier () { + if (!this.user) { + return false + } + if (this.hasIdentifier) { + return false + } + return this.user.roles.includes('create-identifier') + }, + canDeleteIdentifier () { + if (!this.user) { + return false + } + return this.user.roles.includes('delete-identifier') + }, contact () { if (this.database.contact === null || this.database.contact === undefined) { return null diff --git a/fda-ui/pages/container/_container_id/database/_database_id/settings.vue b/fda-ui/pages/container/_container_id/database/_database_id/settings.vue index 85631642ea9c0b656309f7024109171412c44176..888ba4f681117fd66f97a8df17748ec5b3998450 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/settings.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/settings.vue @@ -5,8 +5,7 @@ <v-tabs-items v-model="tab"> <v-tab-item> <v-card v-if="isOwner" flat tile> - <v-card-title>Modify database access</v-card-title> - <v-card-subtitle>This is a dangerous operation</v-card-subtitle> + <v-card-title>Access</v-card-title> <v-data-table :headers="headers" :items="database.accesses" @@ -34,15 +33,8 @@ </v-card> <v-divider /> <v-card v-if="canModifyVisibility" flat tile> - <v-card-title>Modify database visibility</v-card-title> - <v-card-subtitle>This is a dangerous operation</v-card-subtitle> + <v-card-title>Visibility</v-card-title> <v-card-text> - <v-alert - v-if="database.is_public !== modifyVisibility.is_public" - border="left" - color="warning"> - <strong>Dangerous operation:</strong> you are about to change the visibility of the database. This affects all (sensitive) data held in the database. - </v-alert> <v-row dense> <v-col sm="6"> <v-select @@ -55,7 +47,29 @@ </v-row> <v-btn small - :disabled="database.is_public === modifyVisibility.is_public" + color="warning" + class="black--text" + @click="updateDatabaseVisibility"> + Modify Visibility + </v-btn> + </v-card-text> + </v-card> + <v-divider /> + <v-card v-if="canModifyOwnership" flat tile> + <v-card-title>Ownership</v-card-title> + <v-card-text> + <v-row dense> + <v-col sm="6"> + <v-select + id="owner" + v-model="modifyOwner.username" + :items="users" + label="Owner" + name="owner" /> + </v-col> + </v-row> + <v-btn + small color="warning" class="black--text" @click="updateDatabaseVisibility"> @@ -89,12 +103,17 @@ export default { dialogDelete: false, confirm: null, username: null, + users: [], loading: false, + loadingUsers: false, editAccessDialog: false, editVisibilityDialog: false, modifyVisibility: { is_public: null }, + modifyOwner: { + username: null + }, visibility: [ { text: 'Public', value: true }, { text: 'Private', value: false } @@ -153,6 +172,12 @@ export default { return false } return this.user.roles.includes('modify-database-visibility') + }, + canModifyOwnership () { + if (!this.isOwner) { + return false + } + return this.user.roles.includes('modify-database-owner') } }, watch: { @@ -164,6 +189,7 @@ export default { } }, mounted () { + this.loadUsers() if (!this.database) { return } @@ -197,6 +223,19 @@ export default { this.username = item.user.username this.editAccessDialog = true }, + async loadUsers () { + this.loadingUsers = true + try { + const res = await this.$axios.get('/api/user', this.config) + this.users = res.data + console.debug('users', this.users) + } catch (error) { + console.error('Failed to load users', error) + const { message } = error.response.data + this.$toast.error(`Failed to load users: ${message}`) + } + this.loadingUsers = false + }, async loadDatabase () { if (!this.$route.params.container_id || !this.$route.params.database_id) { return diff --git a/fda-user-service/.gitignore b/fda-user-service/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d68acdb421ac6a167610221b2792e4dcafa4f935 --- /dev/null +++ b/fda-user-service/.gitignore @@ -0,0 +1,44 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +# Documentation +docs/*.html +docs/css/ +docs/images/ + +# Docker +ready + +## JUnit +.attach_pid* + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/fda-user-service/.mvn/wrapper/MavenWrapperDownloader.java b/fda-user-service/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000000000000000000000000000000000000..a45eb6ba269cd38f8965cef786729790945d9537 --- /dev/null +++ b/fda-user-service/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/fda-user-service/.mvn/wrapper/maven-wrapper.jar b/fda-user-service/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 Binary files /dev/null and b/fda-user-service/.mvn/wrapper/maven-wrapper.jar differ diff --git a/fda-user-service/.mvn/wrapper/maven-wrapper.properties b/fda-user-service/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..642d572ce90e5085986bdd9c9204b9404f028084 --- /dev/null +++ b/fda-user-service/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/fda-user-service/Dockerfile b/fda-user-service/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..dd9fe0250e12a17d5bfd81ce0070530badd5385a --- /dev/null +++ b/fda-user-service/Dockerfile @@ -0,0 +1,42 @@ +###### FIRST STAGE ###### +FROM fda-metadata-db:latest as dependency +MAINTAINER Martin Weise <martin.weise@tuwien.ac.at> + +###### SECOND STAGE ###### +FROM maven:slim as build + +COPY ./pom.xml ./ + +RUN mvn -fn -B dependency:go-offline > /dev/null + +COPY --from=dependency /root/.m2/repository/at/tuwien /root/.m2/repository/at/tuwien + +COPY ./rest-service ./rest-service +COPY ./services ./services +COPY ./report ./report + +# Make sure it compiles +RUN mvn -q clean package -DskipTests > /dev/null + +###### THIRD STAGE ###### +FROM openjdk:11-jre-slim as runtime + +ENV METADATA_DB=fda +ENV METADATA_USERNAME=root +ENV METADATA_PASSWORD=dbrepo +ENV LOG_LEVEL=debug +ENV DBREPO_CLIENT_SECRET=client-secret +ENV CLIENT_ID=dbrepo-client +ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo +ENV JWT_PUBKEY=public-key + +COPY ./service_ready /usr/bin +RUN chmod +x /usr/bin/service_ready + +HEALTHCHECK --interval=10s --timeout=5s --retries=12 CMD service_ready + +COPY --from=build ./rest-service/target/rest-service-*.jar ./user-service.jar + +EXPOSE 9093 + +ENTRYPOINT ["java", "-Dlog4j2.formatMsgNoLookups=true", "-jar", "./user-service.jar"] diff --git a/fda-user-service/mvnw b/fda-user-service/mvnw new file mode 100755 index 0000000000000000000000000000000000000000..a16b5431b4c3cab50323a3f558003fd0abd87dad --- /dev/null +++ b/fda-user-service/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/fda-user-service/mvnw.cmd b/fda-user-service/mvnw.cmd new file mode 100644 index 0000000000000000000000000000000000000000..c8d43372c986d97911cdc21bd87e0cbe3d83bdda --- /dev/null +++ b/fda-user-service/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/fda-user-service/pom.xml b/fda-user-service/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..f06580d159c91afa43a80c0dcb785fcb21b7f434 --- /dev/null +++ b/fda-user-service/pom.xml @@ -0,0 +1,288 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.3.10.RELEASE</version> + </parent> + + <groupId>at.tuwien</groupId> + <artifactId>fda-user-service</artifactId> + <version>1.1.0-alpha</version> + <name>fda-user-service</name> + <description> + The query service provides an interface to insert data into the tables created by the table service. It + also allows for view-only (possibly paginated and versioned) query execution to the raw data and consumes + messages in the message queue from the Broker Service. + </description> + <url>https://dbrepo-docs.ossdip.at</url> + <developers> + <developer> + <name>Martin Weise</name> + <email>martin.weise@tuwien.ac.at</email> + <organization>TU Wien</organization> + </developer> + <developer> + <name>Moritz Staudinger</name> + <email>moritz.staudinger@tuwien.ac.at</email> + <organization>TU Wien</organization> + </developer> + </developers> + + <packaging>pom</packaging> + <modules> + <module>rest-service</module> + <module>services</module> + <module>report</module> + </modules> + + <properties> + <java.version>11</java.version> + <spring-cloud.version>3.0.1</spring-cloud.version> + <mapstruct.version>1.4.2.Final</mapstruct.version> + <docker.version>3.2.7</docker.version> + <testcontainers.version>1.15.2</testcontainers.version> + <swagger.version>2.1.7</swagger.version> + <springfox.version>3.0.0</springfox.version> + <jacoco.version>0.8.7</jacoco.version> + <opencsv.version>5.4</opencsv.version> + <hibernate-c3po.version>5.6.3.Final</hibernate-c3po.version> + <maven-report.version>3.0.0</maven-report.version> + <jwt.version>4.3.0</jwt.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-amqp</artifactId> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + </dependency> + <dependency><!-- https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#validation-starter-no-longer-included-in-web-starters --> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-bootstrap</artifactId> + <version>${spring-cloud.version}</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + <!-- Authentication --> + <dependency> + <groupId>com.auth0</groupId> + <artifactId>java-jwt</artifactId> + <version>${jwt.version}</version> + </dependency> + <!-- Monitoring --> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + <scope>runtime</scope> + </dependency> + <!-- Data Source --> + <dependency> + <groupId>org.mariadb.jdbc</groupId> + <artifactId>mariadb-java-client</artifactId> + <version>${mariadb.version}</version> + </dependency> + <dependency> + <groupId>org.springframework.data</groupId> + <artifactId>spring-data-elasticsearch</artifactId> + </dependency> + <!-- AMPQ --> + <dependency> + <groupId>com.rabbitmq</groupId> + <artifactId>amqp-client</artifactId> + <version>${rabbit-amqp-client.version}</version> + </dependency> + <!-- Docker --> + <dependency> + <groupId>com.github.docker-java</groupId> + <artifactId>docker-java</artifactId> + <version>${docker.version}</version> + <exclusions> + <exclusion> + <groupId>javax.ws.rs</groupId> + <artifactId>jsr311-api</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>com.github.docker-java</groupId> + <artifactId>docker-java-transport-httpclient5</artifactId> + <version>${docker.version}</version> + </dependency> + <!-- IDE --> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <!-- Entity, API, QueryStore --> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>fda-metadata-db-api</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>fda-metadata-db-entites</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> + <!-- Testing --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-vintage-engine</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco.version}</version> + </dependency> + <!-- Mapping --> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${mapstruct.version}</version> + <optional>true</optional> + <!-- IntelliJ --> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>${mapstruct.version}</version> + </dependency> + <!-- Gateway --> + <dependency> + <groupId>org.projectreactor</groupId> + <artifactId>reactor-spring</artifactId> + <version>1.0.1.RELEASE</version> + </dependency> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>2.1.1</version> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>${basedir}/src/main/resources</directory> + <filtering>true</filtering> + <includes> + <include>**/application*.yml</include> + <include>**/templates/*.xml</include> + </includes> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco.version}</version> + <configuration> + <excludes> + <exclude>at/tuwien/utils/**/*</exclude> + <exclude>at/tuwien/mapper/**/*</exclude> + <exclude>at/tuwien/entities/**/*</exclude> + <exclude>at/tuwien/seeder/**/*</exclude> + <exclude>at/tuwien/handlers/**/*</exclude> + <exclude>at/tuwien/exception/**/*</exclude> + <exclude>at/tuwien/config/**/*</exclude> + <exclude>**/RabbitMqServiceImpl.class</exclude> + <exclude>**/ServiceSeeder.class</exclude> + <exclude>**/FdaQueryServiceApplication.class</exclude> + </excludes> + </configuration> + <executions> + <execution> + <id>default-prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>verify</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-site-plugin</artifactId> + <version>3.7.1</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-project-info-reports-plugin</artifactId> + <version>3.0.0</version> +<!-- <configuration>--> +<!-- <outputDirectory>docs</outputDirectory>--> +<!-- </configuration>--> + </plugin> + <plugin> + <groupId>com.soebes.maven.plugins</groupId> + <artifactId>doxygen-maven-plugin</artifactId> + <version>1.1.0</version> + <configuration> + <haveDot>false</haveDot> + <quiet>false</quiet> + <projectName>This is a Test Project (basicReportTest)</projectName> + <outputDirectory>docs</outputDirectory> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/fda-user-service/report/pom.xml b/fda-user-service/report/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..b93720ffeb6fb8f68ae0c891a7a83afd5dfde333 --- /dev/null +++ b/fda-user-service/report/pom.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>fda-user-service</artifactId> + <groupId>at.tuwien</groupId> + <version>1.1.0-alpha</version> + </parent> + + <artifactId>report</artifactId> + <version>1.1.0-alpha</version> + <name>fda-user-service-report</name> + <description> + This module is only intended for the pipeline coverage report. See the detailed report in the + respective modules + </description> + + <dependencies> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>rest-service</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>services</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + + <properties> + <jacoco.version>0.8.7</jacoco.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco.version}</version> + <executions> + <execution> + <id>report-aggregate</id> + <phase>verify</phase> + <goals> + <goal>report-aggregate</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/fda-user-service/rest-service/pom.xml b/fda-user-service/rest-service/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..6a53b116b31e1bb41ae9bda847e770e83bf836fa --- /dev/null +++ b/fda-user-service/rest-service/pom.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>fda-user-service</artifactId> + <groupId>at.tuwien</groupId> + <version>1.1.0-alpha</version> + </parent> + + <artifactId>rest-service</artifactId> + <version>1.1.0-alpha</version> + <name>fda-user-service-rest</name> + + <dependencies> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>services</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.jetbrains</groupId> + <artifactId>annotations</artifactId> + <version>RELEASE</version> + <scope>compile</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal><!-- to make it exuteable with $ java -jar ./app.jar --> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/fda-user-service/rest-service/src/main/java/at/tuwien/FdaUserServiceApplication.java b/fda-user-service/rest-service/src/main/java/at/tuwien/FdaUserServiceApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..4a6c3dff083b1ec4214b2f19a07d3839e6298368 --- /dev/null +++ b/fda-user-service/rest-service/src/main/java/at/tuwien/FdaUserServiceApplication.java @@ -0,0 +1,25 @@ +package at.tuwien; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@EnableJpaAuditing +@SpringBootApplication +@EnableTransactionManagement +@EnableScheduling +@EntityScan(basePackages = "at.tuwien.entities") +@EnableElasticsearchRepositories(basePackages = {"at.tuwien.repository.elastic"}) +@EnableJpaRepositories(basePackages = {"at.tuwien.repository.jpa"}) +public class FdaUserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(FdaUserServiceApplication.class, args); + } + +} \ No newline at end of file diff --git a/fda-user-service/rest-service/src/main/java/at/tuwien/config/SwaggerConfig.java b/fda-user-service/rest-service/src/main/java/at/tuwien/config/SwaggerConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..eb08f946aadc05ad3224133245e771aebf2a2364 --- /dev/null +++ b/fda-user-service/rest-service/src/main/java/at/tuwien/config/SwaggerConfig.java @@ -0,0 +1,46 @@ +package at.tuwien.config; + +import io.swagger.v3.oas.models.ExternalDocumentation; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import org.springdoc.core.GroupedOpenApi; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Value("${app.version:unknown}") + private String version; + + @Bean + public OpenAPI springShopOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Database Repository User Service API") + .contact(new Contact() + .name("Prof. Andreas Rauber") + .email("andreas.rauber@tuwien.ac.at")) + .description("Service that manages the users") + .version(version) + .license(new License() + .name("Apache 2.0") + .url("https://www.apache.org/licenses/LICENSE-2.0"))) + .externalDocs(new ExternalDocumentation() + .description("Wiki Documentation") + .url("https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/wikis")); + } + + @Bean + public GroupedOpenApi publicApi() { + return GroupedOpenApi.builder() + .group("user-service") + .pathsToMatch("/api/**") + .build(); + } + +} + diff --git a/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java b/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..7bb8dc31490b8468f1a772fda01ffcb4b242ea9d --- /dev/null +++ b/fda-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java @@ -0,0 +1,58 @@ +package at.tuwien.endpoint; + +import at.tuwien.api.auth.SignupRequestDto; +import at.tuwien.api.user.UserBriefDto; +import at.tuwien.mapper.UserMapper; +import at.tuwien.service.UserService; +import io.micrometer.core.annotation.Timed; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@Log4j2 +@CrossOrigin(origins = "*") +@RestController +@RequestMapping("/api/user") +public class UserEndpoint { + + private final UserMapper userMapper; + private final UserService userService; + + @Autowired + public UserEndpoint(UserMapper userMapper, UserService userService) { + this.userMapper = userMapper; + this.userService = userService; + } + + @GetMapping + @Transactional(readOnly = true) + @Timed(value = "user.list", description = "Time needed to list all users in the metadata database") + @Operation(summary = "Find all users") + public ResponseEntity<List<UserBriefDto>> findAll() { + log.debug("endpoint find all users"); + final List<UserBriefDto> users = userService.findAll() + .stream() + .map(userMapper::userToUserBriefDto) + .collect(Collectors.toList()); + log.trace("find all users resulted in users {}", users); + return ResponseEntity.ok(users); + } + + @PostMapping + @Transactional + @Timed(value = "user.create", description = "Time needed to create a user in the metadata database") + @Operation(summary = "Create a user") + public ResponseEntity<UserBriefDto> create(SignupRequestDto data) { + log.debug("endpoint create a user, data={}", data); + final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data)); + log.trace("create user resulted in dto {}", dto); + return ResponseEntity.ok(dto); + } + +} diff --git a/fda-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java b/fda-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..b5d0c1658bf704de3856a2321cdf4d2057412c66 --- /dev/null +++ b/fda-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java @@ -0,0 +1,249 @@ +package at.tuwien.handlers; + +import at.tuwien.api.error.ApiErrorDto; +import at.tuwien.exception.*; +import net.sf.jsqlparser.JSQLParserException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class ApiExceptionHandler extends ResponseEntityExceptionHandler { + + @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) + @ExceptionHandler(AmqpException.class) + public ResponseEntity<ApiErrorDto> handle(AmqpException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_ACCEPTABLE) + .message(e.getLocalizedMessage()) + .code("error.query.amqp") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.EXPECTATION_FAILED) + @ExceptionHandler(ColumnParseException.class) + public ResponseEntity<ApiErrorDto> handle(ColumnParseException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.EXPECTATION_FAILED) + .message(e.getLocalizedMessage()) + .code("error.query.columnparse") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ContainerNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(ContainerNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.containernotfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + @ExceptionHandler(DatabaseConnectionException.class) + public ResponseEntity<ApiErrorDto> handle(DatabaseConnectionException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.METHOD_NOT_ALLOWED) + .message(e.getLocalizedMessage()) + .code("error.query.databaseconnection") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(DatabaseNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(DatabaseNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.databasenotfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(FileStorageException.class) + public ResponseEntity<ApiErrorDto> handle(FileStorageException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.BAD_REQUEST) + .message(e.getLocalizedMessage()) + .code("error.query.filestore") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(IdentifierNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(IdentifierNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.identifiernotfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(ImageNotSupportedException.class) + public ResponseEntity<ApiErrorDto> handle(ImageNotSupportedException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.CONFLICT) + .message(e.getLocalizedMessage()) + .code("error.query.imagenotsupported") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + @ExceptionHandler(NotAllowedException.class) + public ResponseEntity<ApiErrorDto> handle(NotAllowedException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.METHOD_NOT_ALLOWED) + .message(e.getLocalizedMessage()) + .code("error.query.permission") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(PaginationException.class) + public ResponseEntity<ApiErrorDto> handle(PaginationException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.BAD_REQUEST) + .message(e.getLocalizedMessage()) + .code("error.query.pagination") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(QueryAlreadyPersistedException.class) + public ResponseEntity<ApiErrorDto> handle(QueryAlreadyPersistedException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.CONFLICT) + .message(e.getLocalizedMessage()) + .code("error.query.alreadypersisted") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(QueryMalformedException.class) + public ResponseEntity<ApiErrorDto> handle(QueryMalformedException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.BAD_REQUEST) + .message(e.getLocalizedMessage()) + .code("error.query.malformed") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(QueryNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(QueryNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.notfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.GATEWAY_TIMEOUT) + @ExceptionHandler(QueryStoreException.class) + public ResponseEntity<ApiErrorDto> handle(QueryStoreException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.GATEWAY_TIMEOUT) + .message(e.getLocalizedMessage()) + .code("error.query.store") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(SortException.class) + public ResponseEntity<ApiErrorDto> handle(SortException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.BAD_REQUEST) + .message(e.getLocalizedMessage()) + .code("error.query.sort") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.LOCKED) + @ExceptionHandler(TableMalformedException.class) + public ResponseEntity<ApiErrorDto> handle(TableMalformedException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.LOCKED) + .message(e.getLocalizedMessage()) + .code("error.query.tablemalformed") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(TableNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(TableNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.tablenotfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(TupleDeleteException.class) + public ResponseEntity<ApiErrorDto> handle(TupleDeleteException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.CONFLICT) + .message(e.getLocalizedMessage()) + .code("error.query.tupledelete") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(UserNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.usernotfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.LOCKED) + @ExceptionHandler(ViewMalformedException.class) + public ResponseEntity<ApiErrorDto> handle(ViewMalformedException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.LOCKED) + .message(e.getLocalizedMessage()) + .code("error.query.viewmalformed") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ViewNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(ViewNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.query.viewnotfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + +} diff --git a/fda-user-service/rest-service/src/main/resources/application-docker.yml b/fda-user-service/rest-service/src/main/resources/application-docker.yml new file mode 100644 index 0000000000000000000000000000000000000000..084cc82986478d9fd376495e9f52bf2c93e2f42c --- /dev/null +++ b/fda-user-service/rest-service/src/main/resources/application-docker.yml @@ -0,0 +1,40 @@ +app.version: '@project.version@' +spring: + main.banner-mode: off + datasource: + url: jdbc:mariadb://metadata-db:3306/fda + driver-class-name: org.mariadb.jdbc.Driver + username: "${METADATA_USERNAME}" + password: "${METADATA_PASSWORD}" + jpa: + show-sql: false + database-platform: org.hibernate.dialect.MariaDBDialect + hibernate: + ddl-auto: validate + use-new-id-generator-mappings: false + open-in-view: false + properties: + hibernate: + default_schema: fda + jdbc: + time_zone: UTC + application: + name: user-service + cloud: + loadbalancer.ribbon.enabled: false +management.endpoints.web.exposure.include: health,info,prometheus +server.port: 9098 +logging: + pattern.console: "%d %highlight(%-5level) %msg%n" + level: + root: warn + at.tuwien.: "${LOG_LEVEL}" + org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug +eureka: + instance.hostname: user-service + client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/ +fda: + ready.path: /ready + jwt: + issuer: "${JWT_ISSUER}" + public_key: "${JWT_PUBKEY}" \ No newline at end of file diff --git a/fda-user-service/rest-service/src/main/resources/application-local.yml b/fda-user-service/rest-service/src/main/resources/application-local.yml new file mode 100644 index 0000000000000000000000000000000000000000..4b86aa03a823e192a3525861c367164eee7a000c --- /dev/null +++ b/fda-user-service/rest-service/src/main/resources/application-local.yml @@ -0,0 +1,40 @@ +app.version: '@project.version@' +spring: + main.banner-mode: off + datasource: + url: jdbc:mariadb://localhost:3306/fda + driver-class-name: org.mariadb.jdbc.Driver + username: root + password: dbrepo + jpa: + show-sql: false + database-platform: org.hibernate.dialect.MariaDBDialect + hibernate: + ddl-auto: validate + use-new-id-generator-mappings: false + open-in-view: false + properties: + hibernate: + default_schema: fda + jdbc: + time_zone: UTC + application: + name: user-service + cloud: + loadbalancer.ribbon.enabled: false +management.endpoints.web.exposure.include: health,info,prometheus +server.port: 9098 +logging: + pattern.console: "%d %highlight(%-5level) %msg%n" + level: + root: warn + at.tuwien.: trace + org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug +eureka: + instance.hostname: user-service + client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/ +fda: + ready.path: /ready + jwt: + issuer: http://localhost:8080/realms/dbrepo + public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB \ No newline at end of file diff --git a/fda-user-service/rest-service/src/main/resources/application.yml b/fda-user-service/rest-service/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..9ba4e1547a5795546ef952e72819193e32d670c3 --- /dev/null +++ b/fda-user-service/rest-service/src/main/resources/application.yml @@ -0,0 +1,40 @@ +app.version: '@project.version@' +spring: + main.banner-mode: off + datasource: + url: "jdbc:mariadb://metadata-db:3306/${METADATA_DB}" + driver-class-name: org.mariadb.jdbc.Driver + username: "${METADATA_USERNAME}" + password: "${METADATA_PASSWORD}" + jpa: + show-sql: false + database-platform: org.hibernate.dialect.MariaDBDialect + hibernate: + ddl-auto: validate + use-new-id-generator-mappings: false + open-in-view: false + properties: + hibernate: + default_schema: "${METADATA_DB}" + jdbc: + time_zone: UTC + application: + name: user-service + cloud: + loadbalancer.ribbon.enabled: false +management.endpoints.web.exposure.include: health,info,prometheus +server.port: 9098 +logging: + pattern.console: "%d %highlight(%-5level) %msg%n" + level: + root: warn + at.tuwien.: "${LOG_LEVEL}" + org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug +eureka: + instance.hostname: user-service + client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/ +fda: + ready.path: /ready + jwt: + issuer: "${JWT_ISSUER}" + public_key: "${JWT_PUBKEY}" diff --git a/fda-user-service/rest-service/src/main/resources/config.properties b/fda-user-service/rest-service/src/main/resources/config.properties new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/fda-user-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml b/fda-user-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml new file mode 100644 index 0000000000000000000000000000000000000000..01f90448caca030263dade2fe9e4d07c20b938f0 --- /dev/null +++ b/fda-user-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE hibernate-configuration PUBLIC + "-//Hibernate/Hibernate Configuration DTD 3.0//EN" + "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> +<hibernate-configuration> + <session-factory> + <property name="current_session_context_class">thread</property> + <property name="transaction.coordinator_class">jdbc</property> + <property name="c3p0.min_size">1</property> + <property name="c3p0.max_size">30</property> + <property name="c3p0.acquire_increment">1</property> + <property name="c3p0.timeout">1800</property> + <property name="show_sql">true</property> + <property name="format_sql">true</property> + <property name="hbm2ddl.auto">update</property> + <mapping class="at.tuwien.querystore.Column" /> + <mapping class="at.tuwien.querystore.Query" /> + <mapping class="at.tuwien.querystore.Table" /> + </session-factory> +</hibernate-configuration> diff --git a/fda-user-service/rest-service/src/test/resources/application.properties b/fda-user-service/rest-service/src/test/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..4eea490a9199d7e0155b1f33cf3500a5e1f2863f --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/application.properties @@ -0,0 +1,32 @@ +# enable local spring profile +spring.profiles.active=local + +# disable discovery +spring.cloud.discovery.enabled=false + +# disable cloud config and config discovery +spring.cloud.config.discovery.enabled=false +spring.cloud.config.enabled=false + +# internal datasource +spring.datasource.url=jdbc:h2:mem:testdb;DATABASE_TO_UPPER=false;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM './src/test/resources/schema.sql' +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=false + +# additional logging +logging.level.root=error +logging.level.at.tuwien.=trace + +# broker service +spring.rabbitmq.host=dbrepo-broker-service +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest + +# search service +fda.consumers=2 +fda.gateway.endpoint: http://localhost:15672 +fda.elastic.endpoint=dbrepo-search-service:9200 \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/schema.sql b/fda-user-service/rest-service/src/test/resources/schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..906d8df808fa8f79c1f7c1c26088c55da6c9ee9b --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/schema.sql @@ -0,0 +1,25 @@ +CREATE SCHEMA IF NOT EXISTS `fda`; +SET SCHEMA `fda`; +DROP TABLE IF EXISTS fda.mdb_concepts; +CREATE TABLE IF NOT EXISTS fda.mdb_concepts +( + uri VARCHAR(500) not null, + name VARCHAR(255), + created timestamp NOT NULL DEFAULT NOW(), + created_by bigint, + PRIMARY KEY (uri) +); +DROP TABLE IF EXISTS fda.mdb_units; +CREATE TABLE IF NOT EXISTS fda.mdb_units +( + uri VARCHAR(500) not null, + name VARCHAR(255), + created timestamp NOT NULL DEFAULT NOW(), + created_by bigint, + PRIMARY KEY (uri) +); +-- Modified for H2 +-- Assume id=1 is invalid +-- Assume id=2 is still valid token +-- CREATE VIEW IF NOT EXISTS fda.mdb_invalid_tokens AS +-- (SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used` FROM fda.`mdb_tokens` WHERE `id` = 1); \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/sensor/1_querystore.sql b/fda-user-service/rest-service/src/test/resources/sensor/1_querystore.sql new file mode 100644 index 0000000000000000000000000000000000000000..2762d130a0044c439b41c0215b0d87924bc8f072 --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/sensor/1_querystore.sql @@ -0,0 +1,75 @@ +CREATE SEQUENCE `qs_queries_seq`; +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(255) 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 +); +DELIMITER $$ +CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255)) +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 FROM `', name, '` INTO @hash;') + 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; +END $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER ; \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/sensor/2_traffic.sql b/fda-user-service/rest-service/src/test/resources/sensor/2_traffic.sql new file mode 100644 index 0000000000000000000000000000000000000000..93d293a6fe9ee11cbd662208a85089ac3f9fab99 --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/sensor/2_traffic.sql @@ -0,0 +1,8 @@ +CREATE SEQUENCE seq_sensor + START 1; + +CREATE TABLE sensor +( + `timestamp` TIMESTAMP NULL, + primary key (`timestamp`) +) with system versioning; \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/traffic/1_querystore.sql b/fda-user-service/rest-service/src/test/resources/traffic/1_querystore.sql new file mode 100644 index 0000000000000000000000000000000000000000..2762d130a0044c439b41c0215b0d87924bc8f072 --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/traffic/1_querystore.sql @@ -0,0 +1,75 @@ +CREATE SEQUENCE `qs_queries_seq`; +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(255) 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 +); +DELIMITER $$ +CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255)) +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 FROM `', name, '` INTO @hash;') + 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; +END $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER ; \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/traffic/2_traffic.sql b/fda-user-service/rest-service/src/test/resources/traffic/2_traffic.sql new file mode 100644 index 0000000000000000000000000000000000000000..3038fe5a7a99e882504fc8382a5bf2ba7763543d --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/traffic/2_traffic.sql @@ -0,0 +1,43 @@ +/* https://www.kaggle.com/laa283/zurich-public-transport/version/2 */ +CREATE SEQUENCE seq_traffic + START 1; + +CREATE TABLE traffic_zurich +( + linie bigint null, + richtung bigint null, + betriebsdatum date null, + fahrzeug bigint null, + kurs bigint null, + seq_von bigint null, + halt_diva_von bigint null, + halt_punkt_diva_von bigint null, + halt_kurz_von1 varchar(255) null, + datum_von date null, + soll_an_von bigint null, + ist_an_von bigint null, + soll_ab_von bigint null, + ist_ab_von bigint null, + seq_nach bigint null, + halt_diva_nach bigint null, + halt_punkt_diva_nach bigint null, + halt_kurz_nach1 varchar(255) null, + datum_nach DATE null, + soll_an_nach bigint null, + ist_an_nach1 bigint null, + soll_ab_nach bigint null, + ist_ab_nach bigint null, + fahrt_id bigint null, + fahrweg_id bigint null, + fw_no bigint null, + fw_typ bigint null, + fw_kurz bigint null, + fw_lang varchar(255) null, + umlauf_von varchar(255) null, + halt_id_von bigint null, + halt_id_nach bigint null, + halt_punkt_id_von bigint null, + halt_punkt_id_nach bigint null, + id bigint default nextval(`seq_traffic`) not null, + primary key (id) +) with system versioning; \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/weather/1_querystore.sql b/fda-user-service/rest-service/src/test/resources/weather/1_querystore.sql new file mode 100644 index 0000000000000000000000000000000000000000..2762d130a0044c439b41c0215b0d87924bc8f072 --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/weather/1_querystore.sql @@ -0,0 +1,75 @@ +CREATE SEQUENCE `qs_queries_seq`; +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(255) 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 +); +DELIMITER $$ +CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255)) +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 FROM `', name, '` INTO @hash;') + 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; +END $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER ; \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/weather/2_weather.sql b/fda-user-service/rest-service/src/test/resources/weather/2_weather.sql new file mode 100644 index 0000000000000000000000000000000000000000..29967f2977ef6b6f12da00b0c218382e5c024322 --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/weather/2_weather.sql @@ -0,0 +1,55 @@ +/* https://www.kaggle.com/jsphyg/weather-dataset-rattle-package */ +CREATE TABLE weather_location +( + location VARCHAR(255) PRIMARY KEY, + lat DOUBLE PRECISION NULL, + lng DOUBLE PRECISION NULL +) WITH SYSTEM VERSIONING; + +CREATE TABLE weather_aus +( + id BIGINT NOT NULL PRIMARY KEY, + `date` DATE NOT NULL, + location VARCHAR(255) NULL, + mintemp DOUBLE PRECISION NULL, + rainfall DOUBLE PRECISION NULL, + FOREIGN KEY (location) REFERENCES weather_location (location), + UNIQUE (`date`), + CHECK (`mintemp` > 0) +) WITH SYSTEM VERSIONING; + +CREATE TABLE sensor +( + `timestamp` TIMESTAMP NOT NULL, + PRIMARY KEY (`timestamp`), + UNIQUE (`timestamp`) +) WITH SYSTEM VERSIONING; + +INSERT INTO weather_location (location, lat, lng) +VALUES ('Albury', -36.0653583, 146.9112214), + ('Melbourne', null, null), + ('Sydney', -33.847927, 150.6517942); + +INSERT INTO weather_aus (id, `date`, location, mintemp, rainfall) +VALUES (1, '2008-12-01', 'Albury', 13.4, 0.6), + (2, '2008-12-02', 'Albury', 7.4, 0), + (3, '2008-12-03', 'Albury', 12.9, 0); + +######################################################################################################################## +## TEST CASE PRE-REQUISITE ## +######################################################################################################################## + +CREATE VIEW junit2 AS +( +SELECT `location`, `lat`, `lng` +FROM `weather_location` +WHERE `location` = 'Albury'); + +CREATE VIEW `hs_weather_aus` AS +SELECT * +FROM (SELECT `id`, ROW_START AS inserted_at, IF(ROW_END > NOW(), NULL, ROW_END) AS deleted_at, COUNT(*) as total + FROM `weather_aus` FOR SYSTEM_TIME ALL + GROUP BY inserted_at, deleted_at + ORDER BY deleted_at DESC + LIMIT 50) AS v +ORDER BY v.inserted_at, v.deleted_at ASC; diff --git a/fda-user-service/rest-service/src/test/resources/weather/location.csv b/fda-user-service/rest-service/src/test/resources/weather/location.csv new file mode 100644 index 0000000000000000000000000000000000000000..b9410c65c9b4169eb1a231dbe5ca04ff20c116cf --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/weather/location.csv @@ -0,0 +1,2 @@ +Albury,-36.0653583,146.9112214 +Sydney,-33.847927,150.6517942 \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/zoo/1_querystore.sql b/fda-user-service/rest-service/src/test/resources/zoo/1_querystore.sql new file mode 100644 index 0000000000000000000000000000000000000000..2762d130a0044c439b41c0215b0d87924bc8f072 --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/zoo/1_querystore.sql @@ -0,0 +1,75 @@ +CREATE SEQUENCE `qs_queries_seq`; +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(255) 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 +); +DELIMITER $$ +CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255)) +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 FROM `', name, '` INTO @hash;') + 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; +END $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER $$ +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); + SELECT COUNT(*) FROM _tmp INTO @count; + 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 $$ +DELIMITER ; \ No newline at end of file diff --git a/fda-user-service/rest-service/src/test/resources/zoo/2_zoo.sql b/fda-user-service/rest-service/src/test/resources/zoo/2_zoo.sql new file mode 100644 index 0000000000000000000000000000000000000000..50f02ee2c53f5afc8a472b1c418225434b45d98e --- /dev/null +++ b/fda-user-service/rest-service/src/test/resources/zoo/2_zoo.sql @@ -0,0 +1,191 @@ +create sequence seq_zoo_id; +create sequence seq_names_id; +create table zoo +( + id bigint not null default nextval(`seq_zoo_id`), + animal_name varchar(255) null, + hair tinyint(1) null, + feathers tinyint(1) null, + eggs tinyint(1) null, + milk tinyint(1) null, + airborne tinyint(1) null, + aquatic tinyint(1) null, + predator tinyint(1) null, + toothed tinyint(1) null, + backbone tinyint(1) null, + breathes tinyint(1) null, + venomous tinyint(1) null, + fins tinyint(1) null, + legs bigint null, + tail tinyint(1) null, + domestic tinyint(1) null, + catsize tinyint(1) null, + class_type bigint null, + primary key (id) +) with system versioning; + +create table names +( + id bigint not null default nextval(`seq_names_id`), + firstname varchar(255), + lastname varchar(255), + primary key (id), + unique key (firstname, lastname) +) with system versioning; + +create table likes +( + name_id bigint not null, + zoo_id bigint not null, + primary key (name_id, zoo_id), + foreign key (name_id) references names (id), + foreign key (zoo_id) references zoo (id) +) with system versioning; + +INSERT INTO zoo (id, animal_name, hair, feathers, eggs, milk, airborne, aquatic, predator, toothed, backbone, breathes, + venomous, fins, legs, tail, domestic, catsize, class_type) +VALUES (1, 'aardvark', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 0, 0, 1, 1), + (2, 'antelope', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (3, 'bass', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (4, 'bear', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 0, 0, 1, 1), + (5, 'boar', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (6, 'buffalo', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (7, 'calf', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 1, 1, 1), + (8, 'carp', 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 4), + (9, 'catfish', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (10, 'cavy', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 0, 1, 0, 1), + (11, 'cheetah', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (12, 'chicken', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 2), + (13, 'chub', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (14, 'clam', 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7), + (15, 'crab', 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 7), + (16, 'crayfish', 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 6, 0, 0, 0, 7), + (17, 'crow', 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (18, 'deer', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (19, 'dogfish', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 4), + (20, 'dolphin', 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1), + (21, 'dove', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 2), + (22, 'duck', 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (23, 'elephant', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (24, 'flamingo', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 1, 2), + (25, 'flea', 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 6, 0, 0, 0, 6), + (26, 'frog', 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 4, 0, 0, 0, 5), + (27, 'frog', 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 0, 5), + (28, 'fruitbat', 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 2, 1, 0, 0, 1), + (29, 'giraffe', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (30, 'girl', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 2, 0, 1, 1, 1), + (31, 'gnat', 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 6, 0, 0, 0, 6), + (32, 'goat', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 1, 1, 1), + (33, 'gorilla', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 2, 0, 0, 1, 1), + (34, 'gull', 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (35, 'haddock', 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (36, 'hamster', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 1, 0, 1), + (37, 'hare', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 0, 1), + (38, 'hawk', 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (39, 'herring', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (40, 'honeybee', 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 6, 0, 1, 0, 6), + (41, 'housefly', 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 6, 0, 0, 0, 6), + (42, 'kiwi', 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (43, 'ladybird', 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 6, 0, 0, 0, 6), + (44, 'lark', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (45, 'leopard', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (46, 'lion', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (47, 'lobster', 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 6, 0, 0, 0, 7), + (48, 'lynx', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (49, 'mink', 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (50, 'mole', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 0, 1), + (51, 'mongoose', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (52, 'moth', 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 6, 0, 0, 0, 6), + (53, 'newt', 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 4, 1, 0, 0, 5), + (54, 'octopus', 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 8, 0, 0, 1, 7), + (55, 'opossum', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 0, 1), + (56, 'oryx', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (57, 'ostrich', 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 1, 2), + (58, 'parakeet', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 2), + (59, 'penguin', 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0, 1, 2), + (60, 'pheasant', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (61, 'pike', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 4), + (62, 'piranha', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (63, 'pitviper', 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 3), + (64, 'platypus', 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (65, 'polecat', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (66, 'pony', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 1, 1, 1), + (67, 'porpoise', 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1), + (68, 'puma', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (69, 'pussycat', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 1, 1, 1), + (70, 'raccoon', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (71, 'reindeer', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 1, 1, 1), + (72, 'rhea', 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 2, 1, 0, 1, 2), + (73, 'scorpion', 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 8, 1, 0, 0, 7), + (74, 'seahorse', 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (75, 'seal', 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1), + (76, 'sealion', 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 2, 1, 0, 1, 1), + (77, 'seasnake', 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 3), + (78, 'seawasp', 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 7), + (79, 'skimmer', 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (80, 'skua', 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (81, 'slowworm', 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 3), + (82, 'slug', 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 7), + (83, 'sole', 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 4), + (84, 'sparrow', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2), + (85, 'squirrel', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 2, 1, 0, 0, 1), + (86, 'starfish', 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 5, 0, 0, 0, 7), + (87, 'stingray', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 4), + (88, 'swan', 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 1, 0, 1, 2), + (89, 'termite', 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 6, 0, 0, 0, 6), + (90, 'toad', 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 4, 0, 0, 0, 5), + (91, 'tortoise', 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 4, 1, 0, 1, 3), + (92, 'tuatara', 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 0, 3), + (93, 'tuna', 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 4), + (94, 'vampire', 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 2, 1, 0, 0, 1), + (95, 'vole', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 4, 1, 0, 0, 1), + (96, 'vulture', 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 2, 1, 0, 1, 2), + (97, 'wallaby', 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 2, 1, 0, 1, 1), + (98, 'wasp', 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 6, 0, 0, 0, 6), + (99, 'wolf', 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 4, 1, 0, 1, 1), + (100, 'worm', 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 7), + (101, 'wren', 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2); + +INSERT INTO names (firstname, lastname) +VALUES ('Moritz', 'Staudinger'), + ('Martin', 'Weise'), + ('Eva', 'Gergely'), + ('Cornelia', 'Michlits'), + ('Kirill', 'Stytsenko'); + +INSERT INTO likes (name_id, zoo_id) +VALUES (1, 5), + (1, 10), + (2, 3), + (2, 80), + (3, 4), + (4, 4), + (5, 100); + +######################################################################################################################## +## TEST CASE PRE-REQUISITE ## +######################################################################################################################## + +CREATE VIEW mock_view AS +( +SELECT `id`, + `animal_name`, + `hair`, + `feathers`, + `eggs`, + `milk`, + `airborne`, + `aquatic`, + `predator`, + `toothed`, + `backbone`, + `breathes`, + `venomous`, + `fins`, + `legs`, + `tail`, + `domestic`, + `catsize`, + `class_type` +FROM `zoo` +WHERE `class_type` = 1); diff --git a/fda-user-service/service_ready b/fda-user-service/service_ready new file mode 100644 index 0000000000000000000000000000000000000000..b2e4f9df6804f249ba8aadd72f742929072badaa --- /dev/null +++ b/fda-user-service/service_ready @@ -0,0 +1,6 @@ +#!/bin/bash +if [ -f /ready ]; then + echo "service is ready and accepting connections" + exit 0 +fi +exit 1 \ No newline at end of file diff --git a/fda-user-service/services/pom.xml b/fda-user-service/services/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..a42439c741b47488ded1aaa259a3c362cecc87eb --- /dev/null +++ b/fda-user-service/services/pom.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>fda-user-service</artifactId> + <groupId>at.tuwien</groupId> + <version>1.1.0-alpha</version> + </parent> + + <artifactId>services</artifactId> + <version>1.1.0-alpha</version> + <name>fda-user-service-services</name> + +</project> diff --git a/fda-user-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-user-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..b2b01c42ee9868215962426b18b3a70450bff7ac --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java @@ -0,0 +1,100 @@ +package at.tuwien.auth; + +import at.tuwien.api.auth.RealmAccessDto; +import at.tuwien.api.user.UserDetailsDto; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Base64; +import java.util.stream.Collectors; + +@Slf4j +public class AuthTokenFilter extends OncePerRequestFilter { + + @Value("${fda.jwt.issuer}") + private String issuer; + + @Value("${fda.jwt.public_key}") + private String publicKey; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + final String jwt = parseJwt(request); + if (jwt != null) { + final UserDetails userDetails = verifyJwt(jwt); + log.debug("authenticated user {}", userDetails); + final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + } + + public UserDetails verifyJwt(String token) throws ServletException { + final KeyFactory kf; + try { + kf = KeyFactory.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + log.error("Failed to find RSA algorithm"); + throw new ServletException("Failed to find RSA algorithm", e); + } + final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)); + final RSAPublicKey pubKey; + try { + pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509); + } catch (InvalidKeySpecException e) { + log.error("Provided public key is invalid"); + throw new ServletException("Provided public key is invalid", e); + } + final Algorithm algorithm = Algorithm.RSA256(pubKey, null); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer(issuer) + .withAudience("spring") + .build(); + final DecodedJWT jwt = verifier.verify(token); + final RealmAccessDto realmAccess = jwt.getClaim("realm_access").as(RealmAccessDto.class); + return UserDetailsDto.builder() + .username(jwt.getClaim("client_id").asString()) + .authorities(Arrays.stream(realmAccess.getRoles()).map(SimpleGrantedAuthority::new).collect(Collectors.toList())) + .build(); + } + + /** + * Parses the token from the HTTP header of the request + * + * @param request The request. + * @return The token. + */ + public String parseJwt(HttpServletRequest request) { + String headerAuth = request.getHeader("Authorization"); + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7, headerAuth.length()); + } + return null; + } +} \ No newline at end of file diff --git a/fda-user-service/services/src/main/java/at/tuwien/config/JacksonConfig.java b/fda-user-service/services/src/main/java/at/tuwien/config/JacksonConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..fba7f99cf2bf1cbb12ac51cd6fd5b80751ff43c1 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/config/JacksonConfig.java @@ -0,0 +1,31 @@ +package at.tuwien.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Date; +import java.util.TimeZone; + +@Slf4j +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() throws JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.findAndRegisterModules(); + objectMapper.registerModule(new Jdk8Module()); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + objectMapper.setTimeZone(TimeZone.getTimeZone("UTC")); + log.debug("current time is {}", objectMapper.writeValueAsString(new Date())); + return objectMapper; + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/config/ReadyConfig.java b/fda-user-service/services/src/main/java/at/tuwien/config/ReadyConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..2250fa50884df3f47b0b063975aea74f06203f80 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/config/ReadyConfig.java @@ -0,0 +1,25 @@ +package at.tuwien.config; + +import com.google.common.io.Files; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +import java.io.File; +import java.io.IOException; + +@Log4j2 +@Configuration +public class ReadyConfig { + + @Value("${fda.ready.path}") + private String readyPath; + + @EventListener(ApplicationReadyEvent.class) + public void init() throws IOException { + Files.touch(new File(readyPath)); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-user-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2af8f294a81b4e9e133ec7a4ef91c412ca4d17 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java @@ -0,0 +1,87 @@ +package at.tuwien.config; + +import at.tuwien.auth.AuthTokenFilter; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import javax.servlet.http.HttpServletResponse; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +@SecurityScheme( + name = "bearerAuth", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + scheme = "bearer" +) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public AuthTokenFilter authTokenFilter() { + return new AuthTokenFilter(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + /* enable CORS and disable CSRF */ + http = http.cors().and().csrf().disable(); + /* set session management to stateless */ + http = http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and(); + /* set unauthorized requests exception handler */ + http = http + .exceptionHandling() + .authenticationEntryPoint( + (request, response, ex) -> { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, + ex.getMessage() + ); + } + ).and(); + /* set permissions on endpoints */ + http.authorizeRequests() + /* our internal endpoints */ + .antMatchers(HttpMethod.GET, "/actuator/prometheus/**").permitAll() + /* our public endpoints */ + .antMatchers(HttpMethod.GET, "/api/user/**").permitAll() + .antMatchers(HttpMethod.POST, "/api/user/**").permitAll() + .antMatchers("/v3/api-docs.yaml", + "/v3/api-docs/**", + "/swagger-ui/**", + "/swagger-ui.html").permitAll() + /* our private endpoints */ + .anyRequest().authenticated(); + /* add JWT token filter */ + http.addFilterBefore(authTokenFilter(), + UsernamePasswordAuthenticationFilter.class + ); + } + + @Bean + public CorsFilter corsFilter() { + final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + final CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/AmqpException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/AmqpException.java new file mode 100644 index 0000000000000000000000000000000000000000..6af0750d6f5089a8442a7159eac2076462df2825 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/AmqpException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_ACCEPTABLE) +public class AmqpException extends Exception { + + public AmqpException(String msg) { + super(msg); + } + + public AmqpException(String msg, Throwable thr) { + super(msg, thr); + } + + public AmqpException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/ColumnParseException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/ColumnParseException.java new file mode 100644 index 0000000000000000000000000000000000000000..c0c1e109de740d33646d7b91bb1a68ebabdc7616 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/ColumnParseException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.EXPECTATION_FAILED) +public class ColumnParseException extends Exception { + + public ColumnParseException(String msg) { + super(msg); + } + + public ColumnParseException(String msg, Throwable thr) { + super(msg, thr); + } + + public ColumnParseException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/ContainerNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/ContainerNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..85d49d4cb34b20b15fdc1441c69cbab0fe0a34f3 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/ContainerNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class ContainerNotFoundException extends Exception { + + public ContainerNotFoundException(String msg) { + super(msg); + } + + public ContainerNotFoundException(String msg, Throwable thr) { + super(msg, thr); + } + + public ContainerNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/DatabaseConnectionException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/DatabaseConnectionException.java new file mode 100644 index 0000000000000000000000000000000000000000..3c1f797647d67b4e1cb8148a4aba11b7d06aefc0 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/DatabaseConnectionException.java @@ -0,0 +1,23 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.io.IOException; + +@ResponseStatus(code = HttpStatus.METHOD_NOT_ALLOWED) +public class DatabaseConnectionException extends Exception { + + public DatabaseConnectionException(String msg) { + super(msg); + } + + public DatabaseConnectionException(String msg, Throwable thr) { + super(msg, thr); + } + + public DatabaseConnectionException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/DatabaseNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/DatabaseNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..b9ca79c783048e8d0298db273abdb9462efeeec8 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/DatabaseNotFoundException.java @@ -0,0 +1,23 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.io.IOException; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class DatabaseNotFoundException extends Exception { + + public DatabaseNotFoundException(String msg) { + super(msg); + } + + public DatabaseNotFoundException(String msg, Throwable thr) { + super(msg, thr); + } + + public DatabaseNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/FileStorageException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/FileStorageException.java new file mode 100644 index 0000000000000000000000000000000000000000..ab068e4245526e77b611d1c8571df867d5fc2cb6 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/FileStorageException.java @@ -0,0 +1,20 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class FileStorageException extends Exception { + + public FileStorageException(String msg) { + super(msg); + } + + public FileStorageException(String msg, Throwable thr) { + super(msg, thr); + } + + public FileStorageException(Throwable thr) { + super(thr); + } +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/IdentifierNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/IdentifierNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..f0bb71f36492511efbe2c8c959dcdb97c679702f --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/IdentifierNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class IdentifierNotFoundException extends Exception { + + public IdentifierNotFoundException(String msg) { + super(msg); + } + + public IdentifierNotFoundException(String msg, Throwable thr) { + super(msg, thr); + } + + public IdentifierNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/ImageNotSupportedException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/ImageNotSupportedException.java new file mode 100644 index 0000000000000000000000000000000000000000..70963128f8410f856029916c606efe152957f12a --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/ImageNotSupportedException.java @@ -0,0 +1,23 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.io.IOException; + +@ResponseStatus(code = HttpStatus.CONFLICT) +public class ImageNotSupportedException extends Exception { + + public ImageNotSupportedException(String msg) { + super(msg); + } + + public ImageNotSupportedException(String msg, Throwable thr) { + super(msg, thr); + } + + public ImageNotSupportedException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/NotAllowedException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/NotAllowedException.java new file mode 100644 index 0000000000000000000000000000000000000000..44c3d430f91d0ff44ce3fb4d1773b53231902d2b --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/NotAllowedException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.METHOD_NOT_ALLOWED) +public class NotAllowedException extends Exception { + + public NotAllowedException(String msg) { + super(msg); + } + + public NotAllowedException(String msg, Throwable thr) { + super(msg, thr); + } + + public NotAllowedException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/PaginationException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/PaginationException.java new file mode 100644 index 0000000000000000000000000000000000000000..9d56aec9c2752c2c37e7b31f227950ad94c95ef3 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/PaginationException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class PaginationException extends Exception { + + public PaginationException(String msg) { + super(msg); + } + + public PaginationException(String msg, Throwable thr) { + super(msg, thr); + } + + public PaginationException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/QueryAlreadyPersistedException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryAlreadyPersistedException.java new file mode 100644 index 0000000000000000000000000000000000000000..48d3bb0ad9ec2fb1f7c6b9727f2a3e700291cbc6 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryAlreadyPersistedException.java @@ -0,0 +1,19 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.CONFLICT) +public class QueryAlreadyPersistedException extends Exception { + + public QueryAlreadyPersistedException(String msg) { + super(msg); + } + + public QueryAlreadyPersistedException(String msg, Throwable thr) { + super(msg, thr); + } + + public QueryAlreadyPersistedException(Throwable thr) { super(thr); + } +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/QueryMalformedException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryMalformedException.java new file mode 100644 index 0000000000000000000000000000000000000000..3d81b6ba4e659b49b59591fa8e9baec31767615a --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryMalformedException.java @@ -0,0 +1,23 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.io.IOException; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class QueryMalformedException extends Exception { + + public QueryMalformedException(String msg) { + super(msg); + } + + public QueryMalformedException(String msg, Throwable thr) { + super(msg, thr); + } + + public QueryMalformedException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/QueryNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..a5e90754898f19f6cce8938d2385f3f9fecd43e4 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class QueryNotFoundException extends Exception { + + public QueryNotFoundException(String msg) { + super(msg); + } + + public QueryNotFoundException(String msg, Throwable thr) { + super(msg, thr); + } + + public QueryNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java new file mode 100644 index 0000000000000000000000000000000000000000..b1f472f2a1179200475b7952dd459af0ee7f7b6a --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java @@ -0,0 +1,19 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.GATEWAY_TIMEOUT) +public class QueryStoreException extends Exception { + + public QueryStoreException(String msg) { + super(msg); + } + + public QueryStoreException(String msg, Throwable thr) { + super(msg, thr); + } + + public QueryStoreException(Throwable thr) { super(thr); + } +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/SortException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/SortException.java new file mode 100644 index 0000000000000000000000000000000000000000..7415590ad637461baac4c9bf1d68b7d411054b19 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/SortException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class SortException extends Exception { + + public SortException(String msg) { + super(msg); + } + + public SortException(String msg, Throwable thr) { + super(msg, thr); + } + + public SortException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java new file mode 100644 index 0000000000000000000000000000000000000000..542c789ad548084b1a98720630246514b431efcb --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.LOCKED) +public class TableMalformedException extends Exception { + + public TableMalformedException(String msg) { + super(msg); + } + + public TableMalformedException(String msg, Throwable thr) { + super(msg, thr); + } + + public TableMalformedException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/TableNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/TableNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..89fa3ed467e76998431c2b366bceb83804824f38 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/TableNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND) +public class TableNotFoundException extends Exception { + + public TableNotFoundException(String msg) { + super(msg); + } + + public TableNotFoundException(String msg, Throwable thr) { + super(msg, thr); + } + + public TableNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/TupleDeleteException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/TupleDeleteException.java new file mode 100644 index 0000000000000000000000000000000000000000..87e5e4a483f9dfe38f9563be9a9d275415b5a0d9 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/TupleDeleteException.java @@ -0,0 +1,19 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.CONFLICT) +public class TupleDeleteException extends Exception { + + public TupleDeleteException(String msg) { + super(msg); + } + + public TupleDeleteException(String msg, Throwable thr) { + super(msg, thr); + } + + public TupleDeleteException(Throwable thr) { super(thr); + } +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/UserNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/UserNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..0abb87f609f0a6706c8e499eabc23e3d46d3304d --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/UserNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "User not found") +public class UserNotFoundException extends Exception { + + public UserNotFoundException(String message) { + super(message); + } + + public UserNotFoundException(String message, Throwable thr) { + super(message, thr); + } + + public UserNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/ViewMalformedException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/ViewMalformedException.java new file mode 100644 index 0000000000000000000000000000000000000000..5dfdaf170eaccc76da2290673448be19511e4fab --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/ViewMalformedException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.LOCKED) +public class ViewMalformedException extends Exception { + + public ViewMalformedException(String msg) { + super(msg); + } + + public ViewMalformedException(String msg, Throwable thr) { + super(msg, thr); + } + + public ViewMalformedException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/exception/ViewNotFoundException.java b/fda-user-service/services/src/main/java/at/tuwien/exception/ViewNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..2f260975ff4746858184a4c8e5d4ee8268425625 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/exception/ViewNotFoundException.java @@ -0,0 +1,21 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "View not found") +public class ViewNotFoundException extends Exception { + + public ViewNotFoundException(String message) { + super(message); + } + + public ViewNotFoundException(String message, Throwable thr) { + super(message, thr); + } + + public ViewNotFoundException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java b/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..cba134ae89d4f6aeae126b925437bb92bff55d21 --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/mapper/UserMapper.java @@ -0,0 +1,31 @@ +package at.tuwien.mapper; + +import at.tuwien.api.auth.SignupRequestDto; +import at.tuwien.api.user.GrantedAuthorityDto; +import at.tuwien.api.user.UserBriefDto; +import at.tuwien.api.user.UserDetailsDto; +import at.tuwien.api.user.UserDto; +import at.tuwien.entities.user.User; +import org.mapstruct.Mapper; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +@Mapper(componentModel = "spring") +public interface UserMapper { + + org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserMapper.class); + + UserDetailsDto userDtoToUserDetailsDto(UserDto data); + + UserDto userToUserDto(User data); + + UserBriefDto userToUserBriefDto(User data); + + User signupRequestDtoToUser(SignupRequestDto data); + + default GrantedAuthority grantedAuthorityDtoToGrantedAuthority(GrantedAuthorityDto data) { + final GrantedAuthority authority = new SimpleGrantedAuthority(data.getAuthority()); + log.trace("mapped granted authority {} to granted authority {}", data, authority); + return authority; + } +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java b/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..e77de8f6a8160d1f2e04a0ce7575b1ee3d833a0a --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java @@ -0,0 +1,14 @@ +package at.tuwien.repository.jpa; + +import at.tuwien.entities.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository<User, String> { + + Optional<User> findByUsername(String username); + +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java b/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java new file mode 100644 index 0000000000000000000000000000000000000000..d9371a4695a522f4a079dc8cb53ee95309eb7a3b --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/service/UserService.java @@ -0,0 +1,39 @@ +package at.tuwien.service; + +import at.tuwien.api.auth.SignupRequestDto; +import at.tuwien.entities.container.Container; +import at.tuwien.entities.user.User; +import at.tuwien.exception.UserNotFoundException; + +import java.security.Principal; +import java.util.List; + +public interface UserService { + + /** + * Finds all users + * + * @return The list of users. + */ + List<User> findAll(); + + /** + * Finds a user by username. + * + * @param username The username. + * @return The user. + * @throws UserNotFoundException The user was not found in the metadata database. + */ + User findByUsername(String username) throws UserNotFoundException; + + User create(SignupRequestDto data); + + /** + * Finds a user by id. + * + * @param id The id. + * @return The user. + * @throws UserNotFoundException The user was not found in the metadata database. + */ + User find(String id) throws UserNotFoundException; +} diff --git a/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java b/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5578d9186b04dce27aa8f54d826a4bc3334d2a9b --- /dev/null +++ b/fda-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java @@ -0,0 +1,66 @@ +package at.tuwien.service.impl; + +import at.tuwien.api.auth.SignupRequestDto; +import at.tuwien.entities.user.User; +import at.tuwien.exception.UserNotFoundException; +import at.tuwien.mapper.UserMapper; +import at.tuwien.repository.jpa.UserRepository; +import at.tuwien.service.UserService; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Log4j2 +@Service +public class UserServiceImpl implements UserService { + + private final UserMapper userMapper; + private final UserRepository userRepository; + + @Autowired + public UserServiceImpl(UserMapper userMapper, UserRepository userRepository) { + this.userMapper = userMapper; + this.userRepository = userRepository; + } + + @Override + public List<User> findAll() { + return userRepository.findAll(); + } + + @Override + public User findByUsername(String username) throws UserNotFoundException { + final Optional<User> optional = userRepository.findByUsername(username); + if (optional.isEmpty()) { + log.error("Failed to retrieve user with username {}", username); + throw new UserNotFoundException("Failed to retrieve user"); + } + return optional.get(); + } + + @Override + public User create(SignupRequestDto data) { + final User user = userMapper.signupRequestDtoToUser(data); + user.setRealmId("82c39861-d877-4667-a0f3-4daa2ee230e0"); + user.setEmailVerified(false); + user.setId(UUID.randomUUID().toString()); + final User entity = userRepository.save(user); + log.info("Created user with id {}", entity.getId()); + return entity; + } + + @Override + public User find(String id) throws UserNotFoundException { + final Optional<User> optional = userRepository.findById(id); + if (optional.isEmpty()) { + log.error("Failed to retrieve user with id {}", id); + throw new UserNotFoundException("Failed to retrieve user"); + } + return optional.get(); + } + +} diff --git a/fda-user-service/services/src/test/resources/application.properties b/fda-user-service/services/src/test/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..69df4a312304990e2f284318cd5428d960f14f0b --- /dev/null +++ b/fda-user-service/services/src/test/resources/application.properties @@ -0,0 +1,14 @@ +# disable discovery +spring.cloud.discovery.enabled = false + +# disable cloud config and config discovery +spring.cloud.config.discovery.enabled = false +spring.cloud.config.enabled = false + +# disable datasource +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=update \ No newline at end of file