commit 26acf2085ca892ec46c5c83efbea9cc165083d8c Author: Torma Kristóf Date: Wed Aug 11 14:40:05 2021 +0200 first commit diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..968063b --- /dev/null +++ b/.drone.yml @@ -0,0 +1,38 @@ +kind: pipeline +type: docker +name: default + +steps: +- name: code-analysis + image: aosapps/drone-sonar-plugin + settings: + sonar_host: + from_secret: SONAR_HOST + sonar_token: + from_secret: SONAR_CODE + +- name: build_application + image: openjdk:11-jdk + commands: + - ./gradlew build -x test + +- name: kaniko + image: banzaicloud/drone-kaniko + settings: + registry: registry.kmlabz.com + repo: birbnetes/${DRONE_REPO_NAME} + username: + from_secret: DOCKER_USERNAME + password: + from_secret: DOCKER_PASSWORD + tags: + - latest + - ${DRONE_BUILD_NUMBER} + +- name: ms-teams + image: kuperiu/drone-teams + settings: + webhook: + from_secret: TEAMS_WEBHOOK + when: + status: [ failure ] \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9affc3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.gradle +/.idea +/out +/build +*.iml +*.ipr +*.iws +*.log \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6473b30 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM openjdk:11-jre + +ENV APPLICATION_USER ktor +RUN useradd $APPLICATION_USER + +RUN mkdir /app +RUN chown -R $APPLICATION_USER /app + +USER $APPLICATION_USER + +COPY ./build/libs/sample-service.jar /app/sample-service.jar +WORKDIR /app + +CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:InitialRAMFraction=2", "-XX:MinRAMFraction=2", "-XX:MaxRAMFraction=2", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "sample-service.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f6dfbcf --- /dev/null +++ b/build.gradle @@ -0,0 +1,75 @@ +buildscript { + repositories { + jcenter() + maven { url "https://plugins.gradle.org/m2/" } + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.github.jengelman.gradle.plugins:shadow:5.2.0" + classpath "org.koin:koin-gradle-plugin:$koin_version" + } +} + +tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +apply plugin: 'kotlin' +apply plugin: "com.github.johnrengelman.shadow" +apply plugin: 'application' +apply plugin: 'koin' + +group 'com.kmalbz' +version '0.0.1' +mainClassName = "io.ktor.server.netty.EngineMain" + +sourceSets { + main.kotlin.srcDirs = main.java.srcDirs = ['src'] + test.kotlin.srcDirs = test.java.srcDirs = ['test'] + main.resources.srcDirs = ['resources'] + test.resources.srcDirs = ['testresources'] +} + +repositories { + mavenLocal() + jcenter() + maven { url 'https://kotlin.bintray.com/ktor' } +} + +dependencies { + compile 'org.postgresql:postgresql:42.2.2' + compile 'org.jetbrains.exposed:exposed-core:0.23.1' + compile 'org.jetbrains.exposed:exposed-dao:0.23.1' + compile 'org.jetbrains.exposed:exposed-jdbc:0.23.1' + compile 'org.jetbrains.exposed:exposed-java-time:0.23.1' + compile 'com.rabbitmq:amqp-client:2.7.1' + compile 'com.zaxxer:HikariCP:2.7.8' + compile 'com.viartemev:the-white-rabbit:0.0.5' + implementation "org.koin:koin-ktor:$koin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "io.ktor:ktor-server-netty:$ktor_version" + implementation "ch.qos.logback:logback-classic:$logback_version" + implementation "io.ktor:ktor-server-core:$ktor_version" + implementation "io.ktor:ktor-gson:$ktor_version" + implementation "io.ktor:ktor-server-host-common:$ktor_version" + implementation "io.ktor:ktor-client-core:$ktor_version" + implementation "io.ktor:ktor-client-core-jvm:$ktor_version" + implementation "io.ktor:ktor-client-apache:$ktor_version" + implementation "io.ktor:ktor-auth:$ktor_version" +} + +kotlin.experimental.coroutines = 'enable' + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "11" + } +} + +shadowJar { + baseName = 'sample-service' + classifier = null + version = null +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6f6b135 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3' + +services: + + output-service-postgres: + image: "postgres:12" + restart: "always" + volumes: + - "ktor-data:/var/lib/postgresql/data" + ports: + - "127.0.0.1:54321:5432" + environment: + POSTGRES_USER: "output-service-rdb" + POSTGRES_PASSWORD: "output-service-rdb" + POSTGRES_DB: "output-service-rdb" + + output-service-rdb: + image: "registry.kmlabz.com/birbnetes/output-service-rdb" + restart: "always" + ports: + - "127.0.0.1:8080:8080" + environment: + DB_USER: "output-service-rdb" + DB_PASSWORD: "output-service-rdb" + POSTGRES_DB: "output-service-rdb" + DB_URL: "jdbc:postgresql://output-service-postgres:5432/output-service-rdb" + MQ_HOST: rabbitmq + MQ_USERNAME: rabbitmq + MQ_PASSWORD: rabbitmq + depends_on: + - rabbitmq + - output-service-postgres + + rabbitmq: + image: "rabbitmq:3-management" + hostname: "rabbitmq" + environment: + RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG" + RABBITMQ_DEFAULT_USER: "rabbitmq" + RABBITMQ_DEFAULT_PASS: "rabbitmq" + RABBITMQ_DEFAULT_VHOST: "/" + ports: + - "127.0.0.1:15672:15672" + - "127.0.0.1:5672:5672" + +volumes: + ktor-data: \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..a1c1bc9 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +ktor_version=1.4.2 +kotlin.code.style=official +kotlin_version=1.4.10 +logback_version=1.2.1 +koin_version=2.2.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..28861d2 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6b47e69 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Apr 04 18:02:20 CEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +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 +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/http-client.env.json b/http-client.env.json new file mode 100644 index 0000000..bc4f348 --- /dev/null +++ b/http-client.env.json @@ -0,0 +1,8 @@ +{ + "localhost": { + "host": "http://127.0.0.1:8080", + "param_dateAfter": "dateAfter", + "param_dateBefore": "dateBefore", + "param_tagID": "tagID" + } +} diff --git a/k8s/configmap.yml b/k8s/configmap.yml new file mode 100644 index 0000000..b769882 --- /dev/null +++ b/k8s/configmap.yml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: output-service-rdb-config + labels: + app: output-service-rdb + namespace: birbnetes +data: + PORT: "8080" + DB_URL: "jdbc:postgresql://output-postgres:5432/output-service" + DB_USER: output-service + DB_PASSWORD: output-service-supersecret + MQ_HOST: birb-rabbitmq + MQ_USERNAME: user + MQ_PASSWORD: F3ACadDRsT diff --git a/k8s/deployment.yml b/k8s/deployment.yml new file mode 100644 index 0000000..f27c6de --- /dev/null +++ b/k8s/deployment.yml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: output-service-rdb + namespace: birbnetes + labels: + app: output-service-rdb +spec: + replicas: 1 + selector: + matchLabels: + app: output-service-rdb + strategy: + type: Recreate + template: + metadata: + labels: + app: output-service-rdb + spec: + containers: + - image: registry.kmlabz.com/birbnetes/output-service-rdb + imagePullPolicy: Always + name: output-service-rdb + envFrom: + - configMapRef: + name: output-service-rdb-config + ports: + - containerPort: 8080 + imagePullSecrets: + - name: regcred diff --git a/k8s/service.yml b/k8s/service.yml new file mode 100644 index 0000000..c77c4bb --- /dev/null +++ b/k8s/service.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: output-service-rdb + namespace: birbnetes + labels: + app: output-service-rdb +spec: + ports: + - name: output-service-rdb + port: 80 + targetPort: 8080 + protocol: TCP + selector: + app: output-service-rdb + type: ClusterIP \ No newline at end of file diff --git a/resources/application.conf b/resources/application.conf new file mode 100644 index 0000000..3b2b44c --- /dev/null +++ b/resources/application.conf @@ -0,0 +1,21 @@ +ktor { + deployment { + port = 8080 + port = ${?PORT} + } + application { + modules = [ com.kmalbz.ApplicationKt.module ] + } + db { + jdbc = ${DB_URL} + user = ${DB_USER} + password = ${DB_PASSWORD} + } + mq{ + host = ${MQ_HOST} + username = ${MQ_USERNAME} + password = ${MQ_PASSWORD} + exchange = "output" + exchange = ${?MQ_EXCHANGE} + } +} \ No newline at end of file diff --git a/resources/logback.xml b/resources/logback.xml new file mode 100644 index 0000000..bdbb64e --- /dev/null +++ b/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4f05450 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "sample-service" diff --git a/src/Application.kt b/src/Application.kt new file mode 100644 index 0000000..6c1a1c1 --- /dev/null +++ b/src/Application.kt @@ -0,0 +1,75 @@ +package com.kmalbz + +import com.kmalbz.api.route.OutputServiceRDBServer +import com.kmalbz.consumer.DatabaseConsumer +import io.ktor.application.* +import io.ktor.response.* +import io.ktor.routing.* +import io.ktor.http.* +import io.ktor.gson.* +import io.ktor.features.* +import org.apache.http.HttpException +import com.kmalbz.database.DatabaseFactory +import com.kmalbz.database.dao.SampleObjects +import io.ktor.util.KtorExperimentalAPI +import com.rabbitmq.client.* +import com.typesafe.config.ConfigFactory +import io.ktor.config.HoconApplicationConfig +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.koin.ktor.ext.Koin + +fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) + +@KtorExperimentalAPI +@Suppress("unused") // Referenced in application.conf +fun Application.module() { + install(ContentNegotiation) { + gson { + } + } + + install(Koin) { + printLogger() + modules(com.kmalbz.di.injectionModule) + } + + DatabaseFactory.init() + transaction{ + SchemaUtils.create(SampleObjects) + } + + + val appConfig = HoconApplicationConfig(ConfigFactory.load()) + val factory = ConnectionFactory() + factory.host = appConfig.property("ktor.mq.host").getString() + factory.username = appConfig.property("ktor.mq.username").getString() + factory.password = appConfig.property("ktor.mq.password").getString() + + val connection = factory.newConnection() + val channel = connection.createChannel() + + val rabbitExchangeName = appConfig.property("ktor.mq.exchange").getString() + + channel.exchangeDeclare(rabbitExchangeName, BuiltinExchangeType.FANOUT) + val queueName = channel.queueDeclare().queue + channel.queueBind(queueName, rabbitExchangeName, "") + + channel.basicConsume(queueName, true, DatabaseConsumer()) + + routing { + install(StatusPages) { + exception { + call.respond(HttpStatusCode.BadRequest) + } + + exception { + call.respond(HttpStatusCode.NotAcceptable) + } + } + + OutputServiceRDBServer().apply { + registerOutput() + } + } +} diff --git a/src/api/model/ApiObject.kt b/src/api/model/ApiObject.kt new file mode 100644 index 0000000..94c9b74 --- /dev/null +++ b/src/api/model/ApiObject.kt @@ -0,0 +1,11 @@ +package com.kmalbz.api.model + +import com.google.gson.annotations.SerializedName +import java.time.LocalDate + +data class ApiObject( + @SerializedName("id") val id: Int, + @SerializedName("tag") val tag: String, + @SerializedName("device_id") val device_id: Int, + @SerializedName("device_date") val device_date: LocalDate +) \ No newline at end of file diff --git a/src/api/route/OutputServiceRDBServer.kt b/src/api/route/OutputServiceRDBServer.kt new file mode 100644 index 0000000..b14a85a --- /dev/null +++ b/src/api/route/OutputServiceRDBServer.kt @@ -0,0 +1,71 @@ +package com.kmalbz.api.route + +import com.kmalbz.database.service.ISampleObjectService +import io.ktor.application.call +import io.ktor.http.HttpStatusCode +import io.ktor.response.respond +import io.ktor.routing.Routing +import io.ktor.routing.get +import org.koin.ktor.ext.inject +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +/** + * Output Service - RDB + * + * This is the output interface of the Birbnetes system. + */ +class OutputServiceRDBServer { + /** + * output + */ + fun Routing.registerOutput() { + val resultObjectService by inject() + get("/output"){ + call.respond(resultObjectService.getAllSampleObjects()) + } + + get("/output/filter/negative") { + val resultList = resultObjectService.getSampleObjecLessthanId(0.5) ?: call.respond(HttpStatusCode.NotFound) + + call.respond(resultList) + } + + get("/output/filter/positive") { + val resultList = resultObjectService.getSampleObjecGreaterthanId(0.5) ?: call.respond(HttpStatusCode.NotFound) + + call.respond(resultList) + } + + get("/output/filter/undecided") { + val resultList = resultObjectService.getSampleObjecEqualsId(0.5) ?: call.respond(HttpStatusCode.NotFound) + + call.respond(resultList) + } + + get("/output/after/{dateAfter}") { + val dateAfter = call.parameters["dateAfter"] ?: error(HttpStatusCode.NotAcceptable) + val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE + val localDateAfter : LocalDate = LocalDate.parse(dateAfter,dateTimeFormatter) + val resultList = resultObjectService.getSampleObjectafterDate(localDateAfter) ?: call.respond(HttpStatusCode.NotFound) + + call.respond(resultList) + } + + get("/output/before/{dateBefore}") { + val dateAfter = call.parameters["dateBefore"] ?: error(HttpStatusCode.NotAcceptable) + val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE + val localDateBefore : LocalDate = LocalDate.parse(dateAfter,dateTimeFormatter) + val resultList = resultObjectService.getSampleObjectbeforeDate(localDateBefore) ?: call.respond(HttpStatusCode.NotFound) + + call.respond(resultList) + } + + get("/output/{tagID}") { + val tagID = call.parameters["tagID"] ?: error(HttpStatusCode.NotAcceptable) + val resultObject = resultObjectService.getSampleObjectbyTag(tagID) ?: call.respond(HttpStatusCode.NotFound) + + call.respond(resultObject) + } + } +} diff --git a/src/consumer/DatabaseConsumer.kt b/src/consumer/DatabaseConsumer.kt new file mode 100644 index 0000000..4eaa7b8 --- /dev/null +++ b/src/consumer/DatabaseConsumer.kt @@ -0,0 +1,35 @@ +package com.kmalbz.consumer + +import com.google.gson.Gson +import com.kmalbz.api.model.ApiObject +import com.kmalbz.database.service.SampleObjectService +import com.rabbitmq.client.AMQP.BasicProperties +import com.rabbitmq.client.Consumer +import com.rabbitmq.client.Envelope +import com.rabbitmq.client.ShutdownSignalException + +class DatabaseConsumer : Consumer { + private val sampleObjectService = SampleObjectService() + + private val gson = Gson() + override fun handleConsumeOk(consumerTag : String?) { + } + override fun handleCancelOk(p0 : String?) { + throw UnsupportedOperationException() + } + override fun handleRecoverOk(p0 : String?) { + throw UnsupportedOperationException() + } + override fun handleCancel(p0 : String?) { + throw UnsupportedOperationException() + } + override fun handleDelivery(consumerTag : String?, envelope : Envelope?, basicProperties : BasicProperties?, body : ByteArray?) { + val rawJson = body!!.toString(Charsets.UTF_8) + val apiObject = gson.fromJson(rawJson, ApiObject::class.java) + + sampleObjectService.addOne(apiObject) + } + override fun handleShutdownSignal(p0 : String?, p1 : ShutdownSignalException?) { + println("got shutdown signal") + } +} \ No newline at end of file diff --git a/src/database/DatabaseFactory.kt b/src/database/DatabaseFactory.kt new file mode 100644 index 0000000..c2467e5 --- /dev/null +++ b/src/database/DatabaseFactory.kt @@ -0,0 +1,46 @@ +package com.kmalbz.database + +import com.typesafe.config.ConfigFactory +import com.zaxxer.hikari.* +import io.ktor.config.HoconApplicationConfig +import io.ktor.util.KtorExperimentalAPI +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction + +object DatabaseFactory { + + @KtorExperimentalAPI + private val appConfig = HoconApplicationConfig(ConfigFactory.load()) + @KtorExperimentalAPI + private val dbUrl = appConfig.property("ktor.db.jdbc").getString() + @KtorExperimentalAPI + private val dbUser = appConfig.property("ktor.db.user").getString() + @KtorExperimentalAPI + private val dbPassword = appConfig.property("ktor.db.password").getString() + + @KtorExperimentalAPI + fun init() { + Database.connect(hikari()) + } + + @KtorExperimentalAPI + private fun hikari(): HikariDataSource { + val config = HikariConfig() + config.driverClassName = "org.postgresql.Driver" + config.jdbcUrl = dbUrl + config.username = dbUser + config.password = dbPassword + config.maximumPoolSize = 3 + config.isAutoCommit = false + config.transactionIsolation = "TRANSACTION_REPEATABLE_READ" + config.validate() + return HikariDataSource(config) + } + + suspend fun dbQuery(block: () -> T): T = + withContext(Dispatchers.IO) { + transaction { block() } + } +} \ No newline at end of file diff --git a/src/database/dao/SampleObjects.kt b/src/database/dao/SampleObjects.kt new file mode 100644 index 0000000..62f3cfb --- /dev/null +++ b/src/database/dao/SampleObjects.kt @@ -0,0 +1,14 @@ +package com.kmalbz.database.dao + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.`java-time`.date +import java.time.LocalDate + +object SampleObjects : IntIdTable() { + val tag: Column = varchar("tag", 32) + val timestamp: Column = date("timestamp").default(LocalDate.now()) + val device_id: Column = integer("device_id") + val device_date: Column = date("device_date") + override val primaryKey = PrimaryKey(id, name = "PK_ResultObject_Id") +} \ No newline at end of file diff --git a/src/database/model/SampleObject.kt b/src/database/model/SampleObject.kt new file mode 100644 index 0000000..5e5cfe5 --- /dev/null +++ b/src/database/model/SampleObject.kt @@ -0,0 +1,14 @@ +package com.kmalbz.database.model + +import com.kmalbz.database.dao.SampleObjects +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class SampleObject(id: EntityID): IntEntity(id) { + companion object : IntEntityClass(SampleObjects) + var tag by SampleObjects.tag + var timestamp by SampleObjects.timestamp + var device_id by SampleObjects.device_id + var device_date by SampleObjects.device_date +} \ No newline at end of file diff --git a/src/database/service/ISampleObjectService.kt b/src/database/service/ISampleObjectService.kt new file mode 100644 index 0000000..abf61f8 --- /dev/null +++ b/src/database/service/ISampleObjectService.kt @@ -0,0 +1,17 @@ +package com.kmalbz.database.service + +import com.kmalbz.api.model.ApiObject +import java.time.LocalDate +import java.util.* + +interface ISampleObjectService{ + fun addOne(apiObject: ApiObject) + suspend fun getAllSampleObjects(): List + suspend fun getSampleObjectbyTag(tag: String): ApiObject? + suspend fun getSampleObjectbyDate(date: LocalDate): List? + suspend fun getSampleObjectbeforeDate(date: LocalDate): List? + suspend fun getSampleObjectafterDate(date: LocalDate): List? + suspend fun getSampleObjecGreaterthanId(id: Int): List? + suspend fun getSampleObjecLessthanId(id: Int): List? + suspend fun getSampleObjecEqualsId(id: Int): List? +} \ No newline at end of file diff --git a/src/database/service/SampleObjectService.kt b/src/database/service/SampleObjectService.kt new file mode 100644 index 0000000..69d22c1 --- /dev/null +++ b/src/database/service/SampleObjectService.kt @@ -0,0 +1,81 @@ +package com.kmalbz.database.service + +import com.kmalbz.database.DatabaseFactory.dbQuery +import com.kmalbz.database.model.SampleObject +import com.kmalbz.database.dao.SampleObjects +import com.kmalbz.api.model.ApiObject +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDate +import java.util.* + + +class SampleObjectService : ISampleObjectService { + + override fun addOne(apiObject: ApiObject) { + transaction { + SampleObject.new { + tag = apiObject.tag + device_date = apiObject.device_date + device_id = apiObject.device_id + } + } + } + + override suspend fun getAllSampleObjects(): List = dbQuery { + SampleObjects.selectAll().map { toResultObject(it) } + } + + override suspend fun getSampleObjectbyTag(tag: String): ApiObject? = dbQuery { + SampleObjects.select { + (SampleObjects.tag eq tag) + }.mapNotNull { toResultObject(it) } + .singleOrNull() + } + + override suspend fun getSampleObjectbyDate(date: LocalDate): List? = dbQuery { + SampleObjects.select { + (SampleObjects.date eq date) + }.mapNotNull { toResultObject(it) } + } + + override suspend fun getSampleObjectbeforeDate(date: LocalDate): List? = dbQuery { + SampleObjects.select { + (SampleObjects.date less date) + }.mapNotNull { toResultObject(it) } + } + + override suspend fun getSampleObjectafterDate(date: LocalDate): List? = dbQuery { + SampleObjects.select { + (SampleObjects.date greater date) + }.mapNotNull { toResultObject(it) } + } + + override suspend fun getSampleObjecGreaterthanId(probability: Double): List? = dbQuery { + SampleObjects.select { + (SampleObjects.probability greater probability) + }.mapNotNull { toResultObject(it) } + } + + override suspend fun getSampleObjecLessthanId(probability: Double): List? = dbQuery { + SampleObjects.select { + (SampleObjects.probability less probability) + }.mapNotNull { toResultObject(it) } + } + + override suspend fun getSampleObjecEqualsId(probability: Double): List? = dbQuery { + SampleObjects.select { + (SampleObjects.probability eq probability) + }.mapNotNull { toResultObject(it) } + } + + private fun toResultObject(row: ResultRow): ApiObject = + ApiObject( + tag = row[SampleObjects.tag], + device_date = row[SampleObjects.device_date], + device_id = row[SampleObjects.device_id], + id = row[SampleObjects.id.] + ) +} \ No newline at end of file diff --git a/src/di/InjectionModule.kt b/src/di/InjectionModule.kt new file mode 100644 index 0000000..26d98d0 --- /dev/null +++ b/src/di/InjectionModule.kt @@ -0,0 +1,10 @@ +package com.kmalbz.di + +import com.kmalbz.database.service.ISampleObjectService +import com.kmalbz.database.service.SampleObjectService +import org.koin.dsl.module +import org.koin.experimental.builder.singleBy + +val injectionModule = module(createdAtStart = true) { + singleBy() +}