From 0eef3c9b39d33d31f2df23e0a10a5abcbd363231 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sat, 2 May 2020 19:09:37 +0200 Subject: [PATCH 01/86] kotlin multiplatform --- .vscode/launch.json | 24 ++++++++++++++++++++++++ aitrainer_app | 1 + 2 files changed, 25 insertions(+) create mode 100644 .vscode/launch.json create mode 160000 aitrainer_app diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2e77c5d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "android", + "request": "launch", + "name": "Android launch", + "appSrcRoot": "${workspaceRoot}/app/src/main", + "apkFile": "${workspaceRoot}/app/build/outputs/apk/debug/app-debug.apk", + "adbPort": 5037 + }, + { + "type": "android", + "request": "attach", + "name": "Android attach", + "appSrcRoot": "${workspaceRoot}/app/src/main", + "adbPort": 5037, + "processId": "${command:PickAndroidProcess}" + } + ] +} \ No newline at end of file diff --git a/aitrainer_app b/aitrainer_app new file mode 160000 index 0000000..ce06c27 --- /dev/null +++ b/aitrainer_app @@ -0,0 +1 @@ +Subproject commit ce06c27c7389053536e3aa87765a53ded9aa12e9 From 6c4abfe8bfca246ccbb0322213c9776bf86dba3c Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 3 May 2020 12:14:02 +0000 Subject: [PATCH 02/86] Deleted .vscode/launch.json --- .vscode/launch.json | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 2e77c5d..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "android", - "request": "launch", - "name": "Android launch", - "appSrcRoot": "${workspaceRoot}/app/src/main", - "apkFile": "${workspaceRoot}/app/build/outputs/apk/debug/app-debug.apk", - "adbPort": 5037 - }, - { - "type": "android", - "request": "attach", - "name": "Android attach", - "appSrcRoot": "${workspaceRoot}/app/src/main", - "adbPort": 5037, - "processId": "${command:PickAndroidProcess}" - } - ] -} \ No newline at end of file From f6e273643c908795d8d2afd11c514024da42a3f0 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 3 May 2020 12:14:55 +0000 Subject: [PATCH 03/86] add readme --- readme.MD | 1 + 1 file changed, 1 insertion(+) create mode 100644 readme.MD diff --git a/readme.MD b/readme.MD new file mode 100644 index 0000000..daaa3c5 --- /dev/null +++ b/readme.MD @@ -0,0 +1 @@ +aitrainer server API v0.1 \ No newline at end of file From 12376fb9bfdb6a4b7b29571f620f784abfbfeda8 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 3 May 2020 14:26:49 +0200 Subject: [PATCH 04/86] readme extended, unnecessery directory deleted --- aitrainer_app | 1 - readme.MD | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) delete mode 160000 aitrainer_app diff --git a/aitrainer_app b/aitrainer_app deleted file mode 160000 index ce06c27..0000000 --- a/aitrainer_app +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ce06c27c7389053536e3aa87765a53ded9aa12e9 diff --git a/readme.MD b/readme.MD index daaa3c5..7da294b 100644 --- a/readme.MD +++ b/readme.MD @@ -1 +1,4 @@ -aitrainer server API v0.1 \ No newline at end of file +aitrainer server API v0.1.1 + +connects the MYSQL Database +provide a RESTful API to the mobile app \ No newline at end of file From ce499986b26629be9744541a95be51b178a1581d Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 3 May 2020 21:02:57 +0200 Subject: [PATCH 05/86] api/customer --- .gitignore | 32 +++ build.gradle.kts | 40 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 ++++++++++++++++++ gradlew.bat | 103 ++++++++++ settings.gradle | 1 + .../com/aitrainer/api/ApiApplication.kt | 12 ++ .../api/controller/CustomerController.kt | 44 +++++ .../kotlin/com/aitrainer/api/enums/SexEnum.kt | 6 + .../com/aitrainer/api/model/Customer.kt | 23 +++ .../api/repository/CustomerRepository.kt | 8 + src/main/resources/application.properties | 12 ++ .../com/aitrainer/api/ApiApplicationTests.kt | 13 ++ 14 files changed, 482 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle.kts create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/kotlin/com/aitrainer/api/ApiApplication.kt create mode 100644 src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt create mode 100644 src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt create mode 100644 src/main/kotlin/com/aitrainer/api/model/Customer.kt create mode 100644 src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt create mode 100644 src/main/resources/application.properties create mode 100644 src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c01878 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..64770e4 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,40 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("org.springframework.boot") version "2.2.6.RELEASE" + id("io.spring.dependency-management") version "1.0.9.RELEASE" + kotlin("jvm") version "1.3.71" + kotlin("plugin.spring") version "1.3.71" + kotlin("plugin.jpa") version "1.3.71" +} + +group = "com.aitrainer" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + runtimeOnly("mysql:mysql-connector-java") + testImplementation("org.springframework.boot:spring-boot-starter-test") { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } +} + +tasks.withType { + useJUnitPlatform() +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = "1.8" + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f3d88b1c2faf2fc91d853cd5d4242b5547257070 GIT binary patch literal 58695 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a4b4429 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..62bd9b9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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/settings.gradle b/settings.gradle new file mode 100644 index 0000000..2f19c73 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':src' \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt new file mode 100644 index 0000000..d96019c --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt @@ -0,0 +1,12 @@ +package com.aitrainer.api + +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication + +@SpringBootApplication +class ApiApplication { +} + +fun main(args: Array) { + SpringApplication.run(ApiApplication::class.java, *args) +} diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt new file mode 100644 index 0000000..8a1ea03 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt @@ -0,0 +1,44 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.model.Customer +import com.aitrainer.api.repository.CustomerRepository +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.util.* +import javax.validation.Valid + +@RestController +@RequestMapping("/api") +class CustomerController ( private val customerRepository: CustomerRepository ) { + + @GetMapping("/customers") + fun getAllCustomers(): List = + customerRepository.findAll() + + @PostMapping("/customers") + fun createNewArticle(@Valid @RequestBody customer: Customer): Customer = + customerRepository.save(customer) + + @GetMapping("/customers/{id}") + fun getCustomerById(@PathVariable(value = "id") customerId: Long): ResponseEntity { + return customerRepository.findById(customerId).map { customer -> + ResponseEntity.ok(customer) + }.orElse(ResponseEntity.notFound().build()) + } + + @PutMapping("/customers/{id}") + fun updateCustomerById(@PathVariable(value = "id") customerId: Long, + @Valid @RequestBody newCustomer: Customer): ResponseEntity { + + return customerRepository.findById(customerId).map { existingCustomer -> + val updatedCustomer: Customer = existingCustomer + .copy(name = newCustomer.name, + firstname = newCustomer.firstname, + sex = newCustomer.sex, + age = newCustomer.age) + ResponseEntity.ok().body(customerRepository.save(updatedCustomer)) + }.orElse(ResponseEntity.notFound().build()) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt b/src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt new file mode 100644 index 0000000..444e79b --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt @@ -0,0 +1,6 @@ +package com.aitrainer.api.enums + +enum class SexEnum (val sex: String) { + MAN ("m"), + WOMAN("w") +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/model/Customer.kt b/src/main/kotlin/com/aitrainer/api/model/Customer.kt new file mode 100644 index 0000000..7b40f65 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/Customer.kt @@ -0,0 +1,23 @@ +package com.aitrainer.api.model + +import com.aitrainer.api.enums.SexEnum + +import javax.persistence.Entity +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType +import javax.persistence.Id +import javax.validation.constraints.NotBlank + +@Entity +data class Customer ( + @get: NotBlank + val name: String = "", + + val firstname: String, + val email: String, + val age: Int, + val sex: String, + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val customer_id: Long? = null +) diff --git a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt new file mode 100644 index 0000000..cfb8431 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt @@ -0,0 +1,8 @@ +package com.aitrainer.api.repository + +import com.aitrainer.api.model.Customer +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface CustomerRepository : JpaRepository \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..40fb793 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,12 @@ +## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) +#spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false +spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.username = aitrainer +spring.datasource.password = andio2009 + + +## Hibernate Properties + + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt b/src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt new file mode 100644 index 0000000..e8a12cd --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt @@ -0,0 +1,13 @@ +package com.aitrainer.api + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class ApiApplicationTests { + + @Test + fun contextLoads() { + } + +} From 66be44a93f2ffdc669cf76fff3baf774895265f5 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 3 May 2020 21:04:03 +0000 Subject: [PATCH 06/86] Update readme according to the API milestone --- readme.MD | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/readme.MD b/readme.MD index 7da294b..83947a2 100644 --- a/readme.MD +++ b/readme.MD @@ -1,4 +1,8 @@ aitrainer server API v0.1.1 connects the MYSQL Database -provide a RESTful API to the mobile app \ No newline at end of file +provide a RESTful API to the mobile app + +finished API: + +customers From 3dfef8add80f1eac1b7e94e4997920e7f0c983cd Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 5 May 2020 14:42:50 +0200 Subject: [PATCH 07/86] build with junit --- build.gradle.kts | 1 + src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt diff --git a/build.gradle.kts b/build.gradle.kts index 64770e4..54d08af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } + testCompile("org.junit.jupiter:junit-jupiter-api") } tasks.withType { diff --git a/src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt b/src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt deleted file mode 100644 index 444e79b..0000000 --- a/src/main/kotlin/com/aitrainer/api/enums/SexEnum.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.aitrainer.api.enums - -enum class SexEnum (val sex: String) { - MAN ("m"), - WOMAN("w") -} \ No newline at end of file From fa4efd699d624d5ad53d59113ca9e6d4947f5b8d Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 5 May 2020 17:49:22 +0200 Subject: [PATCH 08/86] unit test customer table --- build.gradle.kts | 6 ++-- readme.MD | 2 +- .../api/controller/CustomerController.kt | 2 +- .../com/aitrainer/api/model/Customer.kt | 14 ++++----- .../api/{ => test}/ApiApplicationTests.kt | 2 +- .../com/aitrainer/api/test/CustomerTests.kt | 29 +++++++++++++++++++ 6 files changed, 42 insertions(+), 13 deletions(-) rename src/test/kotlin/com/aitrainer/api/{ => test}/ApiApplicationTests.kt (85%) create mode 100644 src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt diff --git a/build.gradle.kts b/build.gradle.kts index 54d08af..d565847 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "com.aitrainer" -version = "0.0.1-SNAPSHOT" +version = "0.0.2" java.sourceCompatibility = JavaVersion.VERSION_1_8 repositories { @@ -26,7 +26,9 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } - testCompile("org.junit.jupiter:junit-jupiter-api") + testCompile("junit:junit:4.13") + testCompile("org.jetbrains.kotlin:kotlin-test-junit5:1.3.72") + } tasks.withType { diff --git a/readme.MD b/readme.MD index 7da294b..ff43bda 100644 --- a/readme.MD +++ b/readme.MD @@ -1,4 +1,4 @@ -aitrainer server API v0.1.1 +aitrainer server API v0.0.2 connects the MYSQL Database provide a RESTful API to the mobile app \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt index 8a1ea03..aa5cb16 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt @@ -17,7 +17,7 @@ class CustomerController ( private val customerRepository: CustomerRepository ) customerRepository.findAll() @PostMapping("/customers") - fun createNewArticle(@Valid @RequestBody customer: Customer): Customer = + fun createNewCustomer(@Valid @RequestBody customer: Customer): Customer = customerRepository.save(customer) @GetMapping("/customers/{id}") diff --git a/src/main/kotlin/com/aitrainer/api/model/Customer.kt b/src/main/kotlin/com/aitrainer/api/model/Customer.kt index 7b40f65..e468125 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Customer.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Customer.kt @@ -1,7 +1,5 @@ package com.aitrainer.api.model -import com.aitrainer.api.enums.SexEnum - import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType @@ -11,13 +9,13 @@ import javax.validation.constraints.NotBlank @Entity data class Customer ( @get: NotBlank - val name: String = "", - val firstname: String, - val email: String, - val age: Int, - val sex: String, + val name: String = "", + val firstname: String = "", + val email: String = "", + val age: Int = 0, + val sex: String = "m", @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val customer_id: Long? = null + val customer_id: Long = 0 ) diff --git a/src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt b/src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt similarity index 85% rename from src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt rename to src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt index e8a12cd..2c4a873 100644 --- a/src/test/kotlin/com/aitrainer/api/ApiApplicationTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt @@ -1,4 +1,4 @@ -package com.aitrainer.api +package com.aitrainer.api.test import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt new file mode 100644 index 0000000..cf13247 --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -0,0 +1,29 @@ +package com.aitrainer.api.test + +import org.springframework.boot.test.context.SpringBootTest +import com.aitrainer.api.repository.CustomerRepository +import com.aitrainer.api.model.Customer +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.fail +import org.springframework.beans.factory.annotation.Autowired; + +@SpringBootTest +class CustomerTests { + + @Autowired + lateinit private var customerRepository: CustomerRepository + private val customerId: Long = 4 + + @Test + fun testInsert() { + val newCustomer = Customer("Bossanyi", "Tibor", "", 48, "m"); + val savedCustomer: Customer = customerRepository.save(newCustomer) + assertEquals(savedCustomer?.age, 48) + + val customer: Customer? = customerRepository.findById( savedCustomer?.customer_id ).orElse(null); + assertEquals( customer?.firstname, "Tibor") + //assertEquals( customer?.name, "Boss") + } + +} \ No newline at end of file From 054e896f70c879e993ee10fe2fa13d272d9e6062 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Tue, 5 May 2020 17:22:10 +0000 Subject: [PATCH 09/86] add CI gitlab-ci.yml --- .gitlab-ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..bc9b927 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +image: java:8-jdk + +stages: + - build + - test + - deploy + +before_script: +# - echo `pwd` # debug +# - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug + - export GRADLE_USER_HOME=`pwd`/.gradle + +cache: + paths: + - .gradle/wrapper + - .gradle/caches + +build: + stage: build + script: + - ./gradlew assemble + artifacts: + paths: + - build/libs/*.jar + expire_in: 1 week + only: + - master + +test: + stage: test + script: + - ./gradlew check + +deploy: + stage: deploy + script: + - ./deploy + +after_script: + - echo "End CI" \ No newline at end of file From 8bf529fe4218569e493e59ce2a9331d5e30df2be Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 09:32:24 +0200 Subject: [PATCH 10/86] build runner options --- config.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ gradlew | 0 2 files changed, 52 insertions(+) create mode 100644 config.toml mode change 100644 => 100755 gradlew diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..03b57f3 --- /dev/null +++ b/config.toml @@ -0,0 +1,52 @@ +concurrent = 4 +log_level = "warning" + +[session_server] + listen_address = "0.0.0.0:8093" # listen on all available interfaces on port 8093 + advertise_address = "andio.club:8093" + session_timeout = 1800 + +[[runners]] + name = "aitraner-server-docker" + url = "https://andio.biz" + token = "R_-WrxvRXkP6HuU95dEs" + limit = 0 + executor = "shell" + builds_dir = "/home/bosi/build" + shell = "bash" + environment = ["ENV=value", "LC_ALL=en_US.UTF-8"] + clone_url = "http://localhost" + +[runners.docker] + host = "" + hostname = "" + tls_cert_path = "" + image = "docker-runner" + memory = "128m" + memory_swap = "256m" + memory_reservation = "64m" + oom_kill_disable = false + cpuset_cpus = "0,1" + cpus = "2" + dns = ["8.8.8.8"] + dns_search = [""] + privileged = false + userns_mode = "host" + cap_add = ["NET_ADMIN"] + cap_drop = ["DAC_OVERRIDE"] + devices = ["/dev/net/tun"] + disable_cache = false + wait_for_services_timeout = 30 + cache_dir = "" + volumes = ["/data", "/home/project/cache"] + extra_hosts = ["127.0.0.1"] + shm_size = 300000 + volumes_from = ["storage_container:ro"] + links = ["mysql_container:mysql"] + allowed_images = ["ruby:*", "python:*", "php:*"] + allowed_services = ["mysql"] + [[runners.docker.services]] + name = "mysql" + alias = "db" + [runners.docker.sysctls] + "net.ipv4.ip_forward" = "1" \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From e4c5e8bd183b9b934c62a65e0d3e5d5bdb932c63 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 15:32:32 +0200 Subject: [PATCH 11/86] CustomerTest update, Gradle 6.4 --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../com/aitrainer/api/test/ApiApplicationTests.kt | 13 ------------- .../kotlin/com/aitrainer/api/test/CustomerTests.kt | 6 +++--- 3 files changed, 5 insertions(+), 18 deletions(-) delete mode 100644 src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b4429..c6b040f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-bin.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt b/src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt deleted file mode 100644 index 2c4a873..0000000 --- a/src/test/kotlin/com/aitrainer/api/test/ApiApplicationTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.aitrainer.api.test - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class ApiApplicationTests { - - @Test - fun contextLoads() { - } - -} diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index cf13247..54df8ae 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -19,10 +19,10 @@ class CustomerTests { fun testInsert() { val newCustomer = Customer("Bossanyi", "Tibor", "", 48, "m"); val savedCustomer: Customer = customerRepository.save(newCustomer) - assertEquals(savedCustomer?.age, 48) + assertEquals(savedCustomer.age, 48) - val customer: Customer? = customerRepository.findById( savedCustomer?.customer_id ).orElse(null); - assertEquals( customer?.firstname, "Tibor") + val customer: Customer = customerRepository.findById( savedCustomer.customer_id ).orElse(null); + assertEquals( customer.firstname, "Tibor") //assertEquals( customer?.name, "Boss") } From c47147ce5c0f96e33705cafcb2aad79308a8e0c4 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 19:00:25 +0200 Subject: [PATCH 12/86] CustomerTest finish --- .../com/aitrainer/api/model/Customer.kt | 10 +++--- .../com/aitrainer/api/test/CustomerTests.kt | 33 ++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/aitrainer/api/model/Customer.kt b/src/main/kotlin/com/aitrainer/api/model/Customer.kt index e468125..984ed26 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Customer.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Customer.kt @@ -10,11 +10,11 @@ import javax.validation.constraints.NotBlank data class Customer ( @get: NotBlank - val name: String = "", - val firstname: String = "", - val email: String = "", - val age: Int = 0, - val sex: String = "m", + var name: String = "", + var firstname: String = "", + var email: String = "", + var age: Int = 0, + var sex: String = "m", @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val customer_id: Long = 0 diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index 54df8ae..6d07ee3 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -5,6 +5,8 @@ import com.aitrainer.api.repository.CustomerRepository import com.aitrainer.api.model.Customer import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull import kotlin.test.fail import org.springframework.beans.factory.annotation.Autowired; @@ -14,6 +16,14 @@ class CustomerTests { @Autowired lateinit private var customerRepository: CustomerRepository private val customerId: Long = 4 + private var inserted_id: Long? = null + + @Test + fun testGet() { + val id: Long = 7; + val customer: Customer = customerRepository.findById( id ).orElse(null); + assertEquals( customer.name, "Átlag 18 éves fiú") + } @Test fun testInsert() { @@ -21,9 +31,30 @@ class CustomerTests { val savedCustomer: Customer = customerRepository.save(newCustomer) assertEquals(savedCustomer.age, 48) + this.inserted_id = savedCustomer.customer_id; + val customer: Customer = customerRepository.findById( savedCustomer.customer_id ).orElse(null); assertEquals( customer.firstname, "Tibor") - //assertEquals( customer?.name, "Boss") + + this.testUpdate(savedCustomer.customer_id); } + fun testUpdate( customerId: Long ) { + var id: Long? = customerId; + assertNotNull(id) + + var updatedCustomer: Customer = customerRepository.findById( id ).orElse(null); + + assertNotNull(updatedCustomer) + updatedCustomer.firstname ="Tiborka" + + val customer: Customer = customerRepository.save( updatedCustomer ); + assertEquals( customer.firstname, "Tiborka") + + customerRepository.delete(updatedCustomer); + + val customerDeleted: Customer? = customerRepository.findById( id ); + assertNull(customer); + } + } \ No newline at end of file From 760139027ad18ae52346e650afed1ff24dd1ec92 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 19:15:04 +0200 Subject: [PATCH 13/86] CustomerTest delete --- src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index 6d07ee3..4924dc9 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.fail import org.springframework.beans.factory.annotation.Autowired; @@ -52,9 +53,6 @@ class CustomerTests { assertEquals( customer.firstname, "Tiborka") customerRepository.delete(updatedCustomer); - - val customerDeleted: Customer? = customerRepository.findById( id ); - assertNull(customer); } } \ No newline at end of file From bf923fc959606bc4c5da32b188c813e69b6fc453 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 19:41:57 +0200 Subject: [PATCH 14/86] ci debug --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bc9b927..17c6a84 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,8 +6,8 @@ stages: - deploy before_script: -# - echo `pwd` # debug -# - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug + - echo `pwd` # debug + - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug - export GRADLE_USER_HOME=`pwd`/.gradle cache: From 64e776ed78c6b4e4ef8c26527bfa3fe7672b2e22 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 21:43:24 +0200 Subject: [PATCH 15/86] spring.profiles.active dev, deploy --- .gitlab-ci.yml | 2 +- gradlew.bat | 1 + src/main/resources/application-deploy.properties | 13 +++++++++++++ src/main/resources/application-dev.properties | 13 +++++++++++++ src/main/resources/application.properties | 1 + 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/application-deploy.properties create mode 100644 src/main/resources/application-dev.properties diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 17c6a84..70c616c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ build: test: stage: test script: - - ./gradlew check + - ./gradlew check --args='--spring.profiles.active=deploy' deploy: stage: deploy diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9..85ded3c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -85,6 +85,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle +echo JAVA_OPTS: %JAVA_OPTS% "%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 diff --git a/src/main/resources/application-deploy.properties b/src/main/resources/application-deploy.properties new file mode 100644 index 0000000..0f0152b --- /dev/null +++ b/src/main/resources/application-deploy.properties @@ -0,0 +1,13 @@ +spring.profiles.active=deploy +## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) +#spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false +spring.datasource.url = jdbc:mysql://172.19.0.2:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.username = aitrainer +spring.datasource.password = andio2009 + + +## Hibernate Properties + + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..2097769 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,13 @@ +spring.profiles.active=dev +## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) +#spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false +spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.username = aitrainer +spring.datasource.password = andio2009 + + +## Hibernate Properties + + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 40fb793..7de7b16 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,4 @@ +spring.profiles.active=dev,deploy ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true From e574bb61fd238edff9344f1d9ecfcab8bdf05767 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 6 May 2020 22:08:31 +0200 Subject: [PATCH 16/86] gradlew cli agrs --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 70c616c..97b71fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ build: test: stage: test script: - - ./gradlew check --args='--spring.profiles.active=deploy' + - ./gradlew check -Pargs='spring.profiles.active=deploy' deploy: stage: deploy From 1ee521aef69ff30b38be848a40e35bae368ec5fc Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Thu, 7 May 2020 09:32:00 +0200 Subject: [PATCH 17/86] interactive web terminal web ide --- .gitlab/.gitlab-webide.yml | 6 ++++++ config.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .gitlab/.gitlab-webide.yml diff --git a/.gitlab/.gitlab-webide.yml b/.gitlab/.gitlab-webide.yml new file mode 100644 index 0000000..cf25012 --- /dev/null +++ b/.gitlab/.gitlab-webide.yml @@ -0,0 +1,6 @@ +terminal: + script: sleep 60 + variables: + RAILS_ENV: "test" + NODE_ENV: "test" + \ No newline at end of file diff --git a/config.toml b/config.toml index 03b57f3..5ebdc82 100644 --- a/config.toml +++ b/config.toml @@ -8,7 +8,7 @@ log_level = "warning" [[runners]] name = "aitraner-server-docker" - url = "https://andio.biz" + url = "https://andio.club" token = "R_-WrxvRXkP6HuU95dEs" limit = 0 executor = "shell" From af848822ebabd3acb64a0fff7a1a5bef29c8097e Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Thu, 7 May 2020 11:24:15 +0200 Subject: [PATCH 18/86] mysql ip for deploy --- src/main/resources/application-deploy.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-deploy.properties b/src/main/resources/application-deploy.properties index 0f0152b..25cac9f 100644 --- a/src/main/resources/application-deploy.properties +++ b/src/main/resources/application-deploy.properties @@ -1,7 +1,7 @@ spring.profiles.active=deploy ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false -spring.datasource.url = jdbc:mysql://172.19.0.2:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.url = jdbc:mysql://172.18.0.1:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true spring.datasource.username = aitrainer spring.datasource.password = andio2009 From 1deb4d8f214560609db20bee3d3b0cc8d4a66eb0 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Thu, 7 May 2020 13:30:20 +0000 Subject: [PATCH 19/86] .gitlab-ci.yml add mysql, java_home --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97b71fa..c70764c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ image: java:8-jdk +image: mysql:mysql-server stages: - build @@ -8,6 +9,7 @@ stages: before_script: - echo `pwd` # debug - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug + - echo "$JAVA_HOME" - export GRADLE_USER_HOME=`pwd`/.gradle cache: @@ -37,4 +39,4 @@ deploy: - ./deploy after_script: - - echo "End CI" \ No newline at end of file + - echo "End CI" From aff3b6935e87bb774698a058ac923c7a525088f8 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Thu, 7 May 2020 21:52:31 +0200 Subject: [PATCH 20/86] mysql for ci --- .gitlab-ci.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c70764c..c17de8c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,13 @@ image: java:8-jdk -image: mysql:mysql-server + +services: + - mysql:latest + + variables: + # Configure mysql environment variables (https://hub.docker.com/_/mysql/) + MYSQL_DATABASE: "aitrainer" + MYSQL_ROOT_PASSWORD: "andio2009" + stages: - build @@ -30,6 +38,11 @@ build: test: stage: test + Host: mysql + User: aitrainer + Password: andio2009 + Database: aitrainer + script: - ./gradlew check -Pargs='spring.profiles.active=deploy' From 6e7556015534be2d74774cb89ba9eea664c5dd92 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Thu, 7 May 2020 19:57:03 +0000 Subject: [PATCH 21/86] .gitlab-ci.yml fix --- .gitlab-ci.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c17de8c..c74c064 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,10 +3,10 @@ image: java:8-jdk services: - mysql:latest - variables: - # Configure mysql environment variables (https://hub.docker.com/_/mysql/) - MYSQL_DATABASE: "aitrainer" - MYSQL_ROOT_PASSWORD: "andio2009" +variables: + # Configure mysql environment variables (https://hub.docker.com/_/mysql/) + MYSQL_DATABASE: "aitrainer" + MYSQL_ROOT_PASSWORD: "andio2009" stages: @@ -38,11 +38,6 @@ build: test: stage: test - Host: mysql - User: aitrainer - Password: andio2009 - Database: aitrainer - script: - ./gradlew check -Pargs='spring.profiles.active=deploy' From b9678f4c87e853421582cb485769a6cf01b642b6 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Thu, 7 May 2020 20:06:17 +0000 Subject: [PATCH 22/86] fix in .gitlab-ci.yml --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c74c064..c41d35b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,6 +28,7 @@ cache: build: stage: build script: + - docker run java:latest - ./gradlew assemble artifacts: paths: From a7584abeaf872affd8c156e206cd24930d508e35 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Thu, 7 May 2020 20:17:30 +0000 Subject: [PATCH 23/86] fix .gitlab-ci.yml --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c41d35b..2f07a54 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,6 +17,7 @@ stages: before_script: - echo `pwd` # debug - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug + - export JAVA_HOME="/usr/local/java-8-openjdk-amd64/" - echo "$JAVA_HOME" - export GRADLE_USER_HOME=`pwd`/.gradle From bc15721ea496893c2fe5e16867a6619fb4e2faad Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Thu, 7 May 2020 20:18:38 +0000 Subject: [PATCH 24/86] fix .gitlab-ci.yml --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2f07a54..1779871 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,6 @@ cache: build: stage: build script: - - docker run java:latest - ./gradlew assemble artifacts: paths: From 46ebf0cfa15922997c79ca7dae2abc756d504684 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Thu, 7 May 2020 20:20:52 +0000 Subject: [PATCH 25/86] fix .gitlab-ci.yml java home --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1779871..65386f2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ stages: before_script: - echo `pwd` # debug - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug - - export JAVA_HOME="/usr/local/java-8-openjdk-amd64/" + - export JAVA_HOME="/usr/local/java-8-openjdk-amd64" - echo "$JAVA_HOME" - export GRADLE_USER_HOME=`pwd`/.gradle From cc3581f8a0bf2ead0217af0e996d62e89d4c27f5 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 07:17:35 +0000 Subject: [PATCH 26/86] fix .gitlab-ci.yml --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 65386f2..4a31552 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ image: java:8-jdk +image: docker:latest services: - mysql:latest @@ -17,8 +18,6 @@ stages: before_script: - echo `pwd` # debug - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug - - export JAVA_HOME="/usr/local/java-8-openjdk-amd64" - - echo "$JAVA_HOME" - export GRADLE_USER_HOME=`pwd`/.gradle cache: @@ -29,6 +28,7 @@ cache: build: stage: build script: + - docker run java:8-jdk - ./gradlew assemble artifacts: paths: From db97d427f657cfe255f0d6f464ad492018790699 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 07:39:13 +0000 Subject: [PATCH 27/86] fix .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a31552..036fa1d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,7 +28,7 @@ cache: build: stage: build script: - - docker run java:8-jdk + # set JAVA_HOME - ./gradlew assemble artifacts: paths: From 8688f08b81dfa48811536439bc028d35044fe2a6 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 08:02:37 +0000 Subject: [PATCH 28/86] .gitlab-ci.yml openjdk --- .gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 036fa1d..787a06d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,4 @@ -image: java:8-jdk -image: docker:latest +image: openjdk:latest services: - mysql:latest @@ -28,7 +27,6 @@ cache: build: stage: build script: - # set JAVA_HOME - ./gradlew assemble artifacts: paths: From 8d7fa13c0157850441a6f4f272e9112293d062a4 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 08:06:15 +0000 Subject: [PATCH 29/86] .gitlab-ci.yml services --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 787a06d..899d814 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,8 @@ image: openjdk:latest services: - - mysql:latest + - mysql/mysql-server:latest + - openjdk:latest variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) From 14bd4664ee6cd6d5a1dffe404f3a8872982b28a1 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 08:17:37 +0000 Subject: [PATCH 30/86] .gitlab-ci.yml docker --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 899d814..720e8e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,8 @@ image: openjdk:latest services: + - docker:dint - mysql/mysql-server:latest - - openjdk:latest variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) @@ -28,6 +28,7 @@ cache: build: stage: build script: + - docker run openjdk:latest - ./gradlew assemble artifacts: paths: From 55f6778389ed5fffa7dd60b4bd65bc0f81e1ec9b Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 09:44:19 +0000 Subject: [PATCH 31/86] gitlab-ci --- .gitlab-ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 720e8e2..a562ee6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,6 @@ -image: openjdk:latest +image: + - openjdk:latest + - docker:dint services: - docker:dint @@ -8,6 +10,7 @@ variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) MYSQL_DATABASE: "aitrainer" MYSQL_ROOT_PASSWORD: "andio2009" + MYSQL_DATABASE: "aitrainer" stages: From 41b3407fd355e47566864452f7df30492c4f29f4 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 09:46:06 +0000 Subject: [PATCH 32/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a562ee6..ecf7f6d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,4 @@ -image: - - openjdk:latest - - docker:dint +image: openjdk:latest docker:dint services: - docker:dint From 8c7e94aa5a2b18b79540a32b7e5c333deeffa7bf Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 09:48:25 +0000 Subject: [PATCH 33/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ecf7f6d..7b1c912 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,6 +28,9 @@ cache: build: stage: build + image: openjdk:latest docker:dint + services: + - docker:dint script: - docker run openjdk:latest - ./gradlew assemble From da68faefa01d49b757a315b48896bbc7fa0fbc31 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 10:40:52 +0000 Subject: [PATCH 34/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b1c912..99cb440 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,6 @@ -image: openjdk:latest docker:dint +image: openjdk:latest services: - - docker:dint - mysql/mysql-server:latest variables: @@ -28,11 +27,7 @@ cache: build: stage: build - image: openjdk:latest docker:dint - services: - - docker:dint script: - - docker run openjdk:latest - ./gradlew assemble artifacts: paths: From b069ea4d66782d18e398a095676ac8646c31e19b Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 13:49:32 +0000 Subject: [PATCH 35/86] .gitlab-ci.yml mysql test --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 99cb440..f6c9867 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,7 @@ before_script: - echo `pwd` # debug - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug - export GRADLE_USER_HOME=`pwd`/.gradle + - mysql --version cache: paths: @@ -39,6 +40,7 @@ build: test: stage: test script: + - echo "SELECT 'OK';" | mysql --user=root --password="${MYSQL_ROOT_PASSWORD}" --host=mysql "${MYSQL_DATABASE}" - ./gradlew check -Pargs='spring.profiles.active=deploy' deploy: From 9079c2b509df42527e475322b553d7b6384538fc Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Fri, 8 May 2020 18:50:34 +0000 Subject: [PATCH 36/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f6c9867..1c404e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ image: openjdk:latest services: - - mysql/mysql-server:latest + - mysql:latest variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) From 98c2de8e5e8c8759f52b67671bfc3c52fad193e2 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Fri, 8 May 2020 23:29:52 +0200 Subject: [PATCH 37/86] mysql service for ci --- .gitlab-ci.yml | 4 +--- .gitlab-runner-register | 1 + src/main/resources/application-deploy.properties | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 .gitlab-runner-register diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1c404e2..bb6fedc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,8 +19,7 @@ before_script: - echo `pwd` # debug - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug - export GRADLE_USER_HOME=`pwd`/.gradle - - mysql --version - + cache: paths: - .gradle/wrapper @@ -40,7 +39,6 @@ build: test: stage: test script: - - echo "SELECT 'OK';" | mysql --user=root --password="${MYSQL_ROOT_PASSWORD}" --host=mysql "${MYSQL_DATABASE}" - ./gradlew check -Pargs='spring.profiles.active=deploy' deploy: diff --git a/.gitlab-runner-register b/.gitlab-runner-register new file mode 100644 index 0000000..644d1dc --- /dev/null +++ b/.gitlab-runner-register @@ -0,0 +1 @@ +docker run --rm -t -i -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \ No newline at end of file diff --git a/src/main/resources/application-deploy.properties b/src/main/resources/application-deploy.properties index 25cac9f..6ad5e57 100644 --- a/src/main/resources/application-deploy.properties +++ b/src/main/resources/application-deploy.properties @@ -1,7 +1,7 @@ spring.profiles.active=deploy ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false -spring.datasource.url = jdbc:mysql://172.18.0.1:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.url = jdbc:mysql://mysql:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true spring.datasource.username = aitrainer spring.datasource.password = andio2009 From 44297f4dc80eeb1596d73db20a031dab0b47486b Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Fri, 8 May 2020 23:58:20 +0200 Subject: [PATCH 38/86] mysql parameter ci --- src/main/resources/application-deploy.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-deploy.properties b/src/main/resources/application-deploy.properties index 6ad5e57..52f5ee2 100644 --- a/src/main/resources/application-deploy.properties +++ b/src/main/resources/application-deploy.properties @@ -1,7 +1,7 @@ spring.profiles.active=deploy ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false -spring.datasource.url = jdbc:mysql://mysql:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.url = jdbc:mysql://mysql:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true spring.datasource.username = aitrainer spring.datasource.password = andio2009 From 677f04fd49466109002dd7368f7a4297bad818e8 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 06:24:04 +0000 Subject: [PATCH 39/86] gitlab-ci.yml state prepare for mysql --- .gitlab-ci.yml | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bb6fedc..66bef04 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,19 +1,27 @@ image: openjdk:latest -services: - - mysql:latest - -variables: - # Configure mysql environment variables (https://hub.docker.com/_/mysql/) - MYSQL_DATABASE: "aitrainer" - MYSQL_ROOT_PASSWORD: "andio2009" - MYSQL_DATABASE: "aitrainer" - - +#services: + # - mysql:latest stages: + - prepare - build - test - - deploy + - deploy + +variables: + # Configure mysql environment variables (https://hub.docker.com/_/mysql/) + MYSQL_DATABASE: "aitrainer" + MYSQL_ROOT_PASSWORD: "andio2009" + MYSQL_DATABASE: "aitrainer" + +connect: + stage: prepare + image: mysql:latest + script: + - service mysql start + - echo "SELECT 'OK';" | mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -h mysql "SHOW DATABASES;" + + before_script: - echo `pwd` # debug From 128cb3537a3502336aa563fdf73bde2c3ca87cbe Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 06:45:17 +0000 Subject: [PATCH 40/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 66bef04..662291f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: openjdk:latest +image: openjdk:latest mysql:latest #services: # - mysql:latest From f92cd5f8a25cd63350e1860fb02040aed6f5ee9f Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 06:48:01 +0000 Subject: [PATCH 41/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 662291f..74b5c56 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ -image: openjdk:latest mysql:latest +image: openjdk:latest -#services: - # - mysql:latest +services: + - mysql:latest stages: - prepare - build From 9448231aaef3acd8f8eea339cda0ff2946d1fc56 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 06:49:58 +0000 Subject: [PATCH 42/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 74b5c56..cb8c886 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,8 @@ -image: openjdk:latest +image: openjdk:latest mysql:latest services: - mysql:latest + stages: - prepare - build From 698dc601168093d5a8516eab7476cddea5983a1b Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 08:02:35 +0000 Subject: [PATCH 43/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb8c886..3b9d285 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,7 @@ connect: stage: prepare image: mysql:latest script: + - apt-get update && apt-get --assume-yes install mysql-client - service mysql start - echo "SELECT 'OK';" | mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -h mysql "SHOW DATABASES;" From fe550f013b20a762ddf0d284a307da03ab44527f Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 08:11:42 +0000 Subject: [PATCH 44/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3b9d285..9c48119 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,11 +1,9 @@ -image: openjdk:latest mysql:latest - services: - mysql:latest stages: - - prepare - build + - prepare - test - deploy @@ -15,15 +13,6 @@ variables: MYSQL_ROOT_PASSWORD: "andio2009" MYSQL_DATABASE: "aitrainer" -connect: - stage: prepare - image: mysql:latest - script: - - apt-get update && apt-get --assume-yes install mysql-client - - service mysql start - - echo "SELECT 'OK';" | mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -h mysql "SHOW DATABASES;" - - before_script: - echo `pwd` # debug @@ -37,6 +26,7 @@ cache: build: stage: build + image: openjdk:latest script: - ./gradlew assemble artifacts: @@ -46,8 +36,18 @@ build: only: - master +connect: + stage: prepare + image: mysql:latest + script: + - apt-get update && apt-get --assume-yes install mysql-client + - echo "SELECT 'OK';" | mysql -uroot -p "$MYSQL_ROOT_PASSWORD" -h mysql "SHOW DATABASES;" + + + test: stage: test + image: openjdk:latest script: - ./gradlew check -Pargs='spring.profiles.active=deploy' From 2392f56d59f30020d443259f4ccdd00e3733ddf3 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 08:16:06 +0000 Subject: [PATCH 45/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9c48119..1ab9652 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,9 +41,6 @@ connect: image: mysql:latest script: - apt-get update && apt-get --assume-yes install mysql-client - - echo "SELECT 'OK';" | mysql -uroot -p "$MYSQL_ROOT_PASSWORD" -h mysql "SHOW DATABASES;" - - test: stage: test From 192905be71e048140dcf378f52305d5ab9b757d5 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Sun, 10 May 2020 08:28:34 +0000 Subject: [PATCH 46/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ab9652..d45b4a6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,8 @@ variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) MYSQL_DATABASE: "aitrainer" MYSQL_ROOT_PASSWORD: "andio2009" - MYSQL_DATABASE: "aitrainer" + MYSQL_USER: "aitrainer" + MYSQL_PASSWORD: "andio2009" before_script: From 9fd319e8be3be1d3759ed3811f180986d096ead1 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 10:58:18 +0200 Subject: [PATCH 47/86] mysql access for ci --- .gitlab-ci.yml | 1 - gradlew.bat | 1 - src/main/resources/application-deploy.properties | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d45b4a6..3983d35 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,6 @@ services: - mysql:latest stages: - - build - prepare - test - deploy diff --git a/gradlew.bat b/gradlew.bat index 85ded3c..62bd9b9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -85,7 +85,6 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -echo JAVA_OPTS: %JAVA_OPTS% "%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 diff --git a/src/main/resources/application-deploy.properties b/src/main/resources/application-deploy.properties index 52f5ee2..31f76ef 100644 --- a/src/main/resources/application-deploy.properties +++ b/src/main/resources/application-deploy.properties @@ -2,7 +2,7 @@ spring.profiles.active=deploy ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false spring.datasource.url = jdbc:mysql://mysql:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true -spring.datasource.username = aitrainer +spring.datasource.username = root spring.datasource.password = andio2009 From 49afafd00801ac91355ad29f7da82d39a9c87eb0 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:00:42 +0200 Subject: [PATCH 48/86] Test extension with configuration --- .gitlab-ci.yml | 22 ++--- docker.development.yml | 87 ++++++++++++++++++ .../com/aitrainer/api/test/AitrainerDBTest.kt | 90 +++++++++++++++++++ .../api/test/ApplicationConfiguration.kt | 12 +++ 4 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 docker.development.yml create mode 100644 src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3983d35..539e5f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,17 +24,17 @@ cache: - .gradle/wrapper - .gradle/caches -build: - stage: build - image: openjdk:latest - script: - - ./gradlew assemble - artifacts: - paths: - - build/libs/*.jar - expire_in: 1 week - only: - - master +#build: +# stage: build +# image: openjdk:latest +# script: +# - ./gradlew assemble +# artifacts: +# paths: +# - build/libs/*.jar +# expire_in: 1 week +# only: +# - master connect: stage: prepare diff --git a/docker.development.yml b/docker.development.yml new file mode 100644 index 0000000..77d6753 --- /dev/null +++ b/docker.development.yml @@ -0,0 +1,87 @@ +version: '3.8' +services: + jira: + image: 'atlassian/jira-software:latest' + container_name: 'jira' + restart: 'always' + volumes: + - jiraVolume:/var/atlassian/application-data/jira + - db_data:/var/lib/mysql + ports: + - 8082:80 + gitlab: + image: 'gitlab/gitlab-ce:latest' + container_name: 'gitlab' + restart: always + hostname: 'localhost' + environment: + GITLAB_OMNIBUS_CONFIG: | + external_url 'https://andio.club:443' + gitlab_rails['smtp_enable'] = true + gitlab_rails['smtp_address'] = "email-smtp.eu-west-1.amazonaws.com" + gitlab_rails['smtp_port'] = 587 + gitlab_rails['smtp_user_name'] = "AKIAIWHHQDMPADT7ETHQ" + gitlab_rails['smtp_password'] = "AjCB8NA+61i/URp09gik0HHtbEuy48e4JXhuPaqGacFs" + gitlab_rails['smtp_domain'] = "andio.club" + gitlab_rails['smtp_authentication'] = "login" + gitlab_rails['smtp_enable_starttls_auto'] = true + gitlab_rails['smtp_openssl_verify_mode'] = 'peer' + # Add any other gitlab.rb configuration here, each on its own line + gitlab_rails['gitlab_shell_ssh_port'] = 6622 + ports: + - '80:80' + - '443:443' + - '6622:22' + - '587:587' + volumes: + - '/srv/gitlab/config:/etc/gitlab' + - '/srv/gitlab/logs:/var/log/gitlab' + - '/srv/gitlab/data:/var/opt/gitlab' + mysql: + image: mysql:latest + volumes: + - db_data:/var/lib/mysql + restart: always + ports: + - 33061:33061 + environment: + MYSQL_ROOT_PASSWORD: andio2009 + MYSQL_DATABASE: aitrainer + MYSQL_USER: aitrainer + MYSQL_PASSWORD: andio2009 + networks: + - bosi_default + phpmyadmin: + depends_on: + - mysql + image: phpmyadmin/phpmyadmin + restart: always + ports: + - '8081:80' + environment: + PMA_HOST: mysql + MYSQL_ROOT_PASSWORD: andio2009 + networks: + - bosi_default + php: + image: php:7.2-fpm + volumes: + - php:/var/www/html + - ./php/php.ini:/usr/local/etc/php/php.ini + depends_on: + - mysql + gitlab-runner: + image: gitlab/gitlab-runner:latest + container_name: gitlab-runner + restart: always + networks: + - bosi_default + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /srv/gitlab-runner/config:/etc/gitlab-runner +networks: + bosi_default: +volumes: + db_data: + php: + jiraVolume: \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt new file mode 100644 index 0000000..253d387 --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt @@ -0,0 +1,90 @@ +package com.aitrainer.api.test + +import org.springframework.beans.factory.annotation.Autowired + +import java.sql.* +import kotlin.test.assertFails + +import java.util.* +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + + +class AitrainerDBTest(configuration: ApplicationConfiguration) { + + + @Autowired + private lateinit var username: String; + private lateinit var password: String; + private lateinit var datasourceUrl: String; + private lateinit var conn: Connection + private val conf: ApplicationConfiguration = configuration + + fun initDB() { + this.username = this.conf.username.orEmpty() + this.password = this.conf.password.orEmpty() + this.datasourceUrl = this.conf.url.orEmpty() + assertTrue ("username should not be empty", { this.username.isNotEmpty() }) + assertTrue ("password should not be empty", { this.password.isNotEmpty() }) + assertTrue ("url should not be empty", { this.datasourceUrl.isNotEmpty() }) + + this.getConnection() + + assertNotNull({this.conn}, "MySQL connection should not be null") + } + + + private fun executeDBQueries() { + var stmt: Statement? = null + var resultset: ResultSet? = null + try { + stmt = conn.createStatement() + val sql: String = "CREATE DATABASE aitrainer_test;" + if ( stmt.execute( sql ) ) { + + } + //while (resultset!!.next()) { + // println(resultset.getString("Database")) + //} + } catch (ex: SQLException) { + throw Exception(ex) + } finally { + // release resources + if (resultset != null) { + try { + resultset.close() + } catch (sqlEx: SQLException) { + } + } + if (stmt != null) { + try { + stmt.close() + } catch (sqlEx: SQLException) { + throw Exception(sqlEx) + } + } + conn.close() + } + } + /** + * This method makes a connection to MySQL Server + * In this example, MySQL Server is running in the local host (so 127.0.0.1) + * at the standard port 3306 + */ + private fun getConnection() { + val connectionProps = Properties() + connectionProps["user"] = this.username + connectionProps["password"] = this.password + //try { + Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance() + this.conn = DriverManager.getConnection(this.datasourceUrl, connectionProps) + //} catch (ex: SQLException) { + // handle any errors + // ex.printStackTrace() + //} catch (ex: Exception) { + // handle any errors + // ex.printStackTrace() + //} + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt b/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt new file mode 100644 index 0000000..2d4438e --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt @@ -0,0 +1,12 @@ +package com.aitrainer.api.test + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component + +@Component +@ConfigurationProperties(prefix = "spring.datasource") +class ApplicationConfiguration { + open var username: String? = null + open var password: String? = null + open var url: String? = null +} \ No newline at end of file From 4ccc7e5795dc814154c7ade35c75f922fe36c7cb Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:08:04 +0200 Subject: [PATCH 49/86] show tables in ci --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 539e5f4..3e012ff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,6 +41,7 @@ connect: image: mysql:latest script: - apt-get update && apt-get --assume-yes install mysql-client + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "show databases; use $MYSQL_DATABASE_NAME; show tables;" test: stage: test From f0536fb47048d06b8b1aaeb1e650fe0e2245e56a Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:13:20 +0200 Subject: [PATCH 50/86] eliminate gradle warning --- .gitlab-ci.yml | 2 +- build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3e012ff..af63ef1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,7 @@ connect: image: mysql:latest script: - apt-get update && apt-get --assume-yes install mysql-client - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "show databases; use $MYSQL_DATABASE_NAME; show tables;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "show databases; use $MYSQL_DATABASE; show tables;" test: stage: test diff --git a/build.gradle.kts b/build.gradle.kts index d565847..bc11d04 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,8 +26,8 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } - testCompile("junit:junit:4.13") - testCompile("org.jetbrains.kotlin:kotlin-test-junit5:1.3.72") + testImplementation("junit:junit:4.13") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.3.72") } From ec58b934970b003847a66bf3d277c0c6430f0729 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:16:02 +0200 Subject: [PATCH 51/86] show tables for ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af63ef1..9e52e2e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,7 @@ connect: image: mysql:latest script: - apt-get update && apt-get --assume-yes install mysql-client - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "show databases; use $MYSQL_DATABASE; show tables;" + - mysql --user="MYSQL_USER" --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" test: stage: test From 92760f86d66d43ee47469f4731212e8830a47a59 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:22:37 +0200 Subject: [PATCH 52/86] mysql user update for ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e52e2e..4af8b91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,7 @@ connect: image: mysql:latest script: - apt-get update && apt-get --assume-yes install mysql-client - - mysql --user="MYSQL_USER" --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" + - mysql --user="$MYSQL_USER" --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" test: stage: test From 4e552ee645b455c40f50d7ffd4034255c211e7b2 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:32:49 +0200 Subject: [PATCH 53/86] install.sql for ci --- .gitlab-ci.yml | 3 +- src/data/db/install.sql | 114 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/data/db/install.sql diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4af8b91..d63a2bd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,8 @@ connect: image: mysql:latest script: - apt-get update && apt-get --assume-yes install mysql-client - - mysql --user="$MYSQL_USER" --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/scripts/create-db.sql" test: stage: test diff --git a/src/data/db/install.sql b/src/data/db/install.sql new file mode 100644 index 0000000..9d754e7 --- /dev/null +++ b/src/data/db/install.sql @@ -0,0 +1,114 @@ +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Szerver verzió: 8.0.20 - MySQL Community Server - GPL +-- Szerver OS: Win64 +-- HeidiSQL Verzió: 11.0.0.5919 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; + +-- Struktúra mentése tábla aitrainer. customer +CREATE TABLE IF NOT EXISTS `customer` ( + `customer_id` int NOT NULL AUTO_INCREMENT, + `name` char(100) NOT NULL, + `firstname` char(100) NOT NULL, + `email` char(100) DEFAULT NULL, + `sex` enum('m','w') DEFAULT 'm', + `age` tinyint DEFAULT NULL, + PRIMARY KEY (`customer_id`) +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.customer: ~15 rows (hozzávetőleg) +/*!40000 ALTER TABLE `customer` DISABLE KEYS */; +INSERT INTO `customer` (`customer_id`, `name`, `firstname`, `email`, `sex`, `age`) VALUES + (1, 'Átlag 13 éves fiú', '', NULL, 'm', 13), + (2, 'Átlag 14 éves fiú', '', NULL, 'm', 14), + (3, 'Átlag 15 éves fiú', '', NULL, 'm', 15), + (4, 'Átlag 15 éves fiú', '', NULL, 'm', 15), + (5, 'Átlag 16 éves fiú', '', NULL, 'm', 16), + (6, 'Átlag 17 éves fiú', '', NULL, 'm', 17), + (7, 'Átlag 18 éves fiú', '', NULL, 'm', 18), + (8, 'Átlag 13 éves lány', '', NULL, 'w', 13), + (9, 'Átlag 14 éves lány', '', NULL, 'w', 14), + (10, 'Átlag 15 éves lány', '', NULL, 'w', 15), + (11, 'Átlag 16 éves lány', '', NULL, 'w', 16), + (12, 'Átlag 17 éves lány', '', NULL, 'w', 17), + (13, 'Átlag 18 éves lány', '', NULL, 'w', 18), + (14, 'Bossanyi', 'Tibor', '', 'm', 48), + (15, 'Bossanyi', 'Tibor', '', 'm', 48); +/*!40000 ALTER TABLE `customer` ENABLE KEYS */; + +-- Struktúra mentése tábla aitrainer. exercises +CREATE TABLE IF NOT EXISTS `exercises` ( + `exercise_id` int NOT NULL AUTO_INCREMENT, + `exercise_type_id` int NOT NULL, + `user_id` int NOT NULL, + `datetime_exercise` datetime NOT NULL, + `quantity` int DEFAULT NULL, + `rest_time` int DEFAULT NULL COMMENT 'in sec', + PRIMARY KEY (`exercise_id`), + KEY `exercise_type_id` (`exercise_type_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.exercises: ~1 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercises` DISABLE KEYS */; +INSERT INTO `exercises` (`exercise_id`, `exercise_type_id`, `user_id`, `datetime_exercise`, `quantity`, `rest_time`) VALUES + (1, 1, 1, '2020-05-01 00:00:00', 12, NULL); +/*!40000 ALTER TABLE `exercises` ENABLE KEYS */; + +-- Struktúra mentése tábla aitrainer. exercise_ages +CREATE TABLE IF NOT EXISTS `exercise_ages` ( + `exercise_age_id` int NOT NULL AUTO_INCREMENT, + `exercise_type_id` int NOT NULL, + `name` char(100) NOT NULL, + `sex` enum('m','w') DEFAULT 'm', + `age` tinyint DEFAULT NULL, + `min_exercises` int DEFAULT NULL, + `avg_exercises` int DEFAULT NULL, + `max_exercises` int DEFAULT NULL, + PRIMARY KEY (`exercise_age_id`), + UNIQUE KEY `exercise_type_id_2` (`exercise_type_id`,`sex`,`age`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.exercise_ages: ~6 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercise_ages` DISABLE KEYS */; +INSERT INTO `exercise_ages` (`exercise_age_id`, `exercise_type_id`, `name`, `sex`, `age`, `min_exercises`, `avg_exercises`, `max_exercises`) VALUES + (1, 1, '', 'm', 13, 12, NULL, NULL), + (2, 1, '', 'm', 14, 14, NULL, NULL), + (3, 1, '', 'm', 15, 16, NULL, NULL), + (4, 1, '', 'm', 16, 18, NULL, NULL), + (7, 1, '', 'm', 17, 18, NULL, NULL), + (8, 1, '', 'm', 18, 18, NULL, NULL); +/*!40000 ALTER TABLE `exercise_ages` ENABLE KEYS */; + +-- Struktúra mentése tábla aitrainer. exercise_types +CREATE TABLE IF NOT EXISTS `exercise_types` ( + `exercise_id` int NOT NULL AUTO_INCREMENT, + `name` char(100) NOT NULL, + `description` varchar(1000) DEFAULT NULL, + `video` mediumblob, + PRIMARY KEY (`exercise_id`) +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.exercise_types: ~11 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercise_types` DISABLE KEYS */; +INSERT INTO `exercise_types` (`exercise_id`, `name`, `description`, `video`) VALUES + (1, 'Melső fekvőtámasz 1 perc', 'Ezt igazolja a 2016 márciusában beállított guinness rekord is,\r\namelyben KJ Joseph 1 perc alatt 82 szabályos karhajlítás-nyújtást\r\nvégzett.', NULL), + (2, 'Húzódzkodás', 'Ennek a gyakorlatnak a 24 órás csúcstartója Joonas Mäkipelto 5050\r\ngyakorlattal.', NULL), + (3, 'Melső fekvőtámasz 30mp', 'A gyakorlatot 30 másodperc alatt olyan sokszor kell végrehajtani, ahányszor a\r\nfelvételiző képes rá. Azonban törekedni kell a szabályos végrehajtásra, ugyanis csak azokat\r\nszámolják. A nők esetében 20, a férfiak esetében 35 gyakorlatot kell végrehajtani a maximális\r\npont megszerzéséért. A gyakorlat akkor sikeres, ha a női legalább 1, a férfi felvételiző\r\nlegalább 11 gyakorlatot képes végrehajtani.', NULL), + (4, 'Melső fekvőtámasz 2perc', 'Magyar Honvédség: A gyakorlat végrehajtására 2 perc áll rendelkezésre. Ennek során csak a szabályosan a\r\nfentiekben leírt módon végrehajtott gyakorlat értékelhető. Férfiaknál 70 karhajlítás nyújtást\r\nkell végrehajtani a maximális pontért.', NULL), + (5, 'Hajlított karú függés', 'A gyakorlat addig tart, amíg a végrehajtó szemmagassága a kiinduló helyzettől\r\nsüllyedve a keresztvas alá nem kerül. Az értékeléshez stopperórát alkalmaznak, és az\r\neredmény másodperc pontossággal kerül megállapításra. Nők esetében 45, férfiak\r\ntekintetében 73 másodperctől jár a maximális pontszám. A gyakorlat sikeres végrehajtásához\r\nlegalább 8, 10 másodpercig kell megtartaniuk a kiinduló helyzetet a női és a férfi\r\nfelvételizőknek.\r\n\r\nA NKE-RTK-án lévő hallgatók egyéni rekordjai Iván Viktor 90s, Kiss Regina 74s', NULL), + (6, 'Fekvenyomás', '2015-ben a fekvenyomó világbajnokságot Smulter Fredrik finn súlyemelő 401 Kg-al nyerte.\r\nA súlyzó tömege nők esetében 25 kg, a férfiak esetében 60 kg a rúddal együtt. Az\r\nértékelésénél a szabályosan végrehajtott gyakorlatokat értékelik csak.\r\nA legtöbb pontért 25 gyakorlatot kell végezni mind a nőknek, mind a férfiaknak. A minimum:\r\negy gyakorlat mindkét nem esetében.', NULL), + (7, '4x10m-es ingafutás', 'A legjobb pontszámért 9,4 s illetve 8,8s alatt kell teljesíteni a nőknek, férfiaknak. A\r\ngyakorlat sikertelen 11,8 s illetve 11,2 s-on túl.', NULL), + (8, 'Helyből távolugrás', 'Byron Jones 2015-ben a 3,73 méteres ugrásával érte el a világcsúcsot.\r\nA nőknek 220 cm-re, a férfiak 250 cm-re kell ugraniuk a maximális pontért. A\r\nminimális távolság 172cm illetve 198 cm.', NULL), + (9, 'Felülés hanyattfekvésből', 'Az NKE-RTK hallgatói közül Papp Zsófia 66 db-ot, Gál Valentin 80\r\ndb-ot csinált 1 perc leforgása alatt.\r\nElső ütemre megtörténik a felülés, ami akkor szabályos, ha valamelyik könyök érinti a\r\ntérdet. Második ütemre vissza kell térni a kiinduló helyzetbe. A maximális pont eléréséhez 1\r\nperc alatt 45 illetve 55 ismétlést kell végezni a nőknek illetve a férfiaknak. A minimumhoz 7\r\nés 25 ismétlés szükséges.', NULL), + (10, 'Felülés hajlított térddel', 'Magyar Honvédség: A kiinduló helyzet hajlított ülés, ennek során a sarkak a talajon, a térd 90 fokban meghajlítva\r\nvan.\r\nA gyakorlat végrehajtására két perc áll rendelkezésre. Ennek során csak a szabályosan,\r\na fentiekben leírt módon végrehajtott gyakorlat értékelhető. Férfiaknál, nőknél egyaránt 90\r\ngyakorlatot kell végrehajtani a maximális pontért. Amennyiben a megadott időkeret alatt nem\r\nsikerül legalább 25 szabályos gyakorlatot végrehajtani, úgy az sikertelennek minősül.', NULL), + (11, 'Síkfutás 2000m', 'A maximálisan megszerezhető pontot az a felvételiző gyűjtheti be, aki a távot nők\r\nesetében 10:00 perc alatt, férfiak esetében 7:35 perc alatt teljesíti. A gyakorlat sikeres\r\nteljesítésére nők esetében maximum 16:00 perc, férfiak esetében 13:30 perc áll rendelkezésre.', NULL); +/*!40000 ALTER TABLE `exercise_types` ENABLE KEYS */; + +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; From 7f81a5f2185af28a72bdab4ab6dfd428d7b34d0e Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:33:53 +0200 Subject: [PATCH 54/86] install.sql script for ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d63a2bd..5e25755 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,7 +42,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/scripts/create-db.sql" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" test: stage: test From 0c5016bcef29213d4d255e9b615c7dd6b102ee59 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:38:41 +0200 Subject: [PATCH 55/86] data/dib dir --- {src/data => data}/db/install.sql | 0 settings.gradle | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) rename {src/data => data}/db/install.sql (100%) diff --git a/src/data/db/install.sql b/data/db/install.sql similarity index 100% rename from src/data/db/install.sql rename to data/db/install.sql diff --git a/settings.gradle b/settings.gradle index 2f19c73..c500055 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -include ':src' \ No newline at end of file +include ':src' +include ':data' \ No newline at end of file From 3a78c5b2f9bc199cc5850b11da5e768e77bd5886 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:40:55 +0200 Subject: [PATCH 56/86] use database --- data/db/install.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/db/install.sql b/data/db/install.sql index 9d754e7..a43a979 100644 --- a/data/db/install.sql +++ b/data/db/install.sql @@ -10,6 +10,8 @@ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +use aitrainer; + -- Struktúra mentése tábla aitrainer. customer CREATE TABLE IF NOT EXISTS `customer` ( `customer_id` int NOT NULL AUTO_INCREMENT, From 7239d4de14e7a82931ec62e2dd82a352eeb39001 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:52:41 +0200 Subject: [PATCH 57/86] deploy dir for ci --- .gitlab-ci.yml | 23 ++++++++++++----------- deploy/readme.md | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 deploy/readme.md diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e25755..72f55e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ services: - mysql:latest stages: + - build - prepare - test - deploy @@ -24,17 +25,17 @@ cache: - .gradle/wrapper - .gradle/caches -#build: -# stage: build -# image: openjdk:latest -# script: -# - ./gradlew assemble -# artifacts: -# paths: -# - build/libs/*.jar -# expire_in: 1 week -# only: -# - master +build: + stage: build + image: openjdk:latest + script: + - ./gradlew assemble + artifacts: + paths: + - build/libs/*.jar + expire_in: 1 week + only: + - master connect: stage: prepare diff --git a/deploy/readme.md b/deploy/readme.md new file mode 100644 index 0000000..d56a3a1 --- /dev/null +++ b/deploy/readme.md @@ -0,0 +1 @@ +jar file to deploy \ No newline at end of file From 7055f2927fe3283b715437587f2661155546fa5a Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 10 May 2020 11:57:37 +0200 Subject: [PATCH 58/86] install.sql not necessary --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 72f55e0..2266189 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" + #- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" test: stage: test From 99598420da116ef34d0e83be449769e6fe8146ae Mon Sep 17 00:00:00 2001 From: ZalanBoss <48163754+ZalanBoss@users.noreply.github.com> Date: Sun, 10 May 2020 13:39:14 +0200 Subject: [PATCH 59/86] Exercise Type Classes --- .../api/controller/ExerciseTypeController.kt | 50 +++++++++++++++++++ .../com/aitrainer/api/model/ExerciseType.kt | 20 ++++++++ .../api/repository/ExerciseTypeRepository.kt | 12 +++++ 3 files changed, 82 insertions(+) create mode 100644 src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt create mode 100644 src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt create mode 100644 src/main/kotlin/com/aitrainer/api/repository/ExerciseTypeRepository.kt diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt new file mode 100644 index 0000000..b186bfe --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt @@ -0,0 +1,50 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.model.ExerciseType +import com.aitrainer.api.repository.ExerciseTypeRepository +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.util.* +import javax.validation.Valid + +@RestController +@RequestMapping("/api") +class ExerciseTypeController ( private val exerciseTypeRepository: ExerciseTypeRepository ) { + + @GetMapping("/exercise_type") + fun getAllExerciseTypes(): List = + exerciseTypeRepository.findAll() + + @PostMapping("/exercise_type") + fun createNewExerciseType(@Valid @RequestBody exerciseType: ExerciseType): ExerciseType = + exerciseTypeRepository.save(exerciseType) + + @GetMapping("/exercise_type/{id}") + fun getExerciseTypeById(@PathVariable(value = "id") exerciseTypeId: Long): ResponseEntity { + return exerciseTypeRepository.findById(exerciseTypeId).map { exerciseType -> + ResponseEntity.ok(exerciseType) + }.orElse(ResponseEntity.notFound().build()) + } +/* + @GetMapping("/exercise_type/name/{name}") + fun getExerciseTypeByName(@PathVariable(value = "name") name: String): ResponseEntity { + return exerciseTypeRepository.findByName(name).map { exerciseType -> + ResponseEntity.ok(exerciseType) + }.orElse(ResponseEntity.notFound().build()) + }*/ + + @PutMapping("/exercise_type/{id}") + fun updateExerciseTypesById(@PathVariable(value = "id") exerciseTypeId: Long, + @Valid @RequestBody newExerciseType: ExerciseType): ResponseEntity { + + return exerciseTypeRepository.findById(exerciseTypeId).map { existingExerciseType -> + val updatedExerciseType: ExerciseType = existingExerciseType + .copy(name = newExerciseType.name, + description = newExerciseType.description, + video = newExerciseType.video) + ResponseEntity.ok().body(exerciseTypeRepository.save(updatedExerciseType)) + }.orElse(ResponseEntity.notFound().build()) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt b/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt new file mode 100644 index 0000000..2d181e3 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt @@ -0,0 +1,20 @@ +package com.aitrainer.api.model + +import javax.persistence.Entity +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType +import javax.persistence.Id +import javax.validation.constraints.NotBlank + +@Entity +data class ExerciseType ( + @get: NotBlank + + val name: String = "", + val description: String = "", + val video: Long = 0, + + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val type_id: Long = 0 +) diff --git a/src/main/kotlin/com/aitrainer/api/repository/ExerciseTypeRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/ExerciseTypeRepository.kt new file mode 100644 index 0000000..c5cdc1e --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/repository/ExerciseTypeRepository.kt @@ -0,0 +1,12 @@ +package com.aitrainer.api.repository + +import com.aitrainer.api.model.ExerciseType +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ExerciseTypeRepository : JpaRepository{ + + fun findByName(name: String): ExerciseType + +} \ No newline at end of file From 9a8402d63440293905a64fcb2cad24180a9cd972 Mon Sep 17 00:00:00 2001 From: ZalanBoss <48163754+ZalanBoss@users.noreply.github.com> Date: Sun, 10 May 2020 13:46:26 +0200 Subject: [PATCH 60/86] exercise type readme changes --- readme.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.MD b/readme.MD index bce0cc5..3a4e2c2 100644 --- a/readme.MD +++ b/readme.MD @@ -6,3 +6,4 @@ provide a RESTful API to the mobile app finished API: customers +exercise type impl.ed From 7c23ec7fa404a66de0a4bdc2ee71eaec251e3f46 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Mon, 11 May 2020 16:42:52 +0200 Subject: [PATCH 61/86] Exercise Data class, repo, controller --- .gitlab-ci.yml | 14 ++++---- .gitlab/.gitlab-webide.yml | 6 ---- config.toml => ci-cd/config.toml | 0 .../docker.development.yml | 11 +++++++ ci-cd/production-docker-compose.yml | 31 +++++++++++++++++ {deploy => ci-cd}/readme.md | 0 gradlew.bat | 1 - .../api/controller/ExerciesController.kt | 33 +++++++++++++++++++ .../com/aitrainer/api/model/Exercises.kt | 20 +++++++++++ .../api/repository/ExercisesRepository.kt | 8 +++++ .../resources/application-prod.properties | 13 ++++++++ ...properties => application-test.properties} | 2 +- src/main/resources/application.properties | 2 +- 13 files changed, 125 insertions(+), 16 deletions(-) delete mode 100644 .gitlab/.gitlab-webide.yml rename config.toml => ci-cd/config.toml (100%) rename docker.development.yml => ci-cd/docker.development.yml (89%) create mode 100644 ci-cd/production-docker-compose.yml rename {deploy => ci-cd}/readme.md (100%) create mode 100644 src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt create mode 100644 src/main/kotlin/com/aitrainer/api/model/Exercises.kt create mode 100644 src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt create mode 100644 src/main/resources/application-prod.properties rename src/main/resources/{application-deploy.properties => application-test.properties} (95%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2266189..8a874bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ stages: - build - prepare - test - - deploy +# - deploy variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) @@ -43,18 +43,18 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - #- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" + #- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: stage: test image: openjdk:latest script: - - ./gradlew check -Pargs='spring.profiles.active=deploy' + - ./gradlew check -Pargs='spring.profiles.active=test' -deploy: - stage: deploy - script: - - ./deploy +#deploy: +# stage: deploy +# script: +# - ./deploy after_script: - echo "End CI" diff --git a/.gitlab/.gitlab-webide.yml b/.gitlab/.gitlab-webide.yml deleted file mode 100644 index cf25012..0000000 --- a/.gitlab/.gitlab-webide.yml +++ /dev/null @@ -1,6 +0,0 @@ -terminal: - script: sleep 60 - variables: - RAILS_ENV: "test" - NODE_ENV: "test" - \ No newline at end of file diff --git a/config.toml b/ci-cd/config.toml similarity index 100% rename from config.toml rename to ci-cd/config.toml diff --git a/docker.development.yml b/ci-cd/docker.development.yml similarity index 89% rename from docker.development.yml rename to ci-cd/docker.development.yml index 77d6753..55c2c83 100644 --- a/docker.development.yml +++ b/ci-cd/docker.development.yml @@ -4,6 +4,12 @@ services: image: 'atlassian/jira-software:latest' container_name: 'jira' restart: 'always' + environment: + ATL_TOMCAT_PORT: 8082 + ATL_TOMCAT_SCHEME: "https" + ATL_TOMCAT_SECURE: "true" + ATL_DB_DRIVER: "com.mysql.jdbc.Driver" + ATL_DB_TYPE: "mysql" volumes: - jiraVolume:/var/atlassian/application-data/jira - db_data:/var/lib/mysql @@ -79,6 +85,11 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - /srv/gitlab-runner/config:/etc/gitlab-runner +secrets: + mysql_root_pwd: + file: /.sec/mysql_root_pwd + mysql_user_pwd: + file: /.sec/mysql_user_pwd networks: bosi_default: volumes: diff --git a/ci-cd/production-docker-compose.yml b/ci-cd/production-docker-compose.yml new file mode 100644 index 0000000..2cc74b7 --- /dev/null +++ b/ci-cd/production-docker-compose.yml @@ -0,0 +1,31 @@ +version: '3.8' + +services: + mysql: + image: mysql:latest + container_name: mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: andio2009 + MYSQL_DATABASE: aitrainer + MYSQL_USER: aitrainer + MYSQL_PASSWORD: andio2009 + volumes: + - ./docker/db:/docker-entrypoint-initdb.d + ports: + - "33061:33061" + command: mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=localhost < "/home/bosi/aitrainer/data/install.sql" + + java: + image: openjdk:latest + + + tomcat: + image: tomcat:latest + container_name: tomcat + volumes: + - ./docker/aitrainer_server.jar:/home/bosi/aitrainer/deploy/aitrainer_server.jar + ports: + - "8080:8080" + depends_on: + - java \ No newline at end of file diff --git a/deploy/readme.md b/ci-cd/readme.md similarity index 100% rename from deploy/readme.md rename to ci-cd/readme.md diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9..8294cac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -83,7 +83,6 @@ set CMD_LINE_ARGS=%* @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% diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt new file mode 100644 index 0000000..39a5744 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt @@ -0,0 +1,33 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.model.Customer +import com.aitrainer.api.model.Exercises +import com.aitrainer.api.repository.ExercisesRepository +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import javax.validation.Valid + +class ExerciesController(private val exercisesRepository: ExercisesRepository) { + + @PostMapping("/exercises") + fun createNewCustomer(@Valid @RequestBody exercises: Exercises): Exercises = + exercisesRepository.save(exercises) + + @PutMapping("/exercises/{id}") + fun updateExerciseById(@PathVariable(value = "id") exerciseId: Long, + @Valid @RequestBody newExercises: Exercises): ResponseEntity { + return exercisesRepository.findById(exerciseId).map { existingExercises -> + val updatedExercises: Exercises = existingExercises.copy( + exercise_type_id = newExercises.exercise_type_id, + customer_id = newExercises.customer_id, + dateTimeExercise = newExercises.dateTimeExercise, + quantity = newExercises.quantity, + restTime = newExercises.restTime + ) + ResponseEntity.ok().body(exercisesRepository.save(updatedExercises)) + }.orElse(ResponseEntity.notFound().build()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt new file mode 100644 index 0000000..58b1333 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt @@ -0,0 +1,20 @@ +package com.aitrainer.api.model + +import org.springframework.lang.NonNull +import java.sql.Date +import javax.persistence.Entity +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType +import javax.persistence.Id + +@Entity +data class Exercises ( + @get: NonNull var exercise_type_id: Long = 0, + @get: NonNull var customer_id: Long = 0, + @get: NonNull var dateTimeExercise: Date? = null, + @get: NonNull var quantity: Int = 0, + var restTime: Int = 0, // in seconds + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val exercise_id: Long = 0 +) \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt new file mode 100644 index 0000000..1f13b8d --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt @@ -0,0 +1,8 @@ +package com.aitrainer.api.repository + +import com.aitrainer.api.model.Exercises +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ExercisesRepository : JpaRepository \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..a7b7f9e --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,13 @@ +spring.profiles.active=prod +## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) +#spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false +spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true +spring.datasource.username = aitrainer +spring.datasource.password = andio2009 + + +## Hibernate Properties + + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect \ No newline at end of file diff --git a/src/main/resources/application-deploy.properties b/src/main/resources/application-test.properties similarity index 95% rename from src/main/resources/application-deploy.properties rename to src/main/resources/application-test.properties index 31f76ef..def8592 100644 --- a/src/main/resources/application-deploy.properties +++ b/src/main/resources/application-test.properties @@ -1,4 +1,4 @@ -spring.profiles.active=deploy +spring.profiles.active=test ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false spring.datasource.url = jdbc:mysql://mysql:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7de7b16..1f3f585 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ -spring.profiles.active=dev,deploy +spring.profiles.active=dev,test,prod ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) #spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true From a6627a31e762aa71d2369efa46ee69702cb6d7fc Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Mon, 11 May 2020 21:26:44 +0200 Subject: [PATCH 62/86] Exercise repo, getAllByCustomerId, unit test (+code inspection) --- .gitlab-ci.yml | 3 +- .../com/aitrainer/api/ApiApplication.kt | 3 +- .../api/controller/CustomerController.kt | 2 - .../api/controller/ExerciesController.kt | 18 +++---- .../api/controller/ExerciseTypeController.kt | 2 - .../com/aitrainer/api/model/Exercises.kt | 15 +++--- .../api/repository/ExercisesRepository.kt | 4 +- .../com/aitrainer/api/test/AitrainerDBTest.kt | 47 ++----------------- .../api/test/ApplicationConfiguration.kt | 6 +-- .../com/aitrainer/api/test/CustomerTests.kt | 29 +++++------- 10 files changed, 42 insertions(+), 87 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a874bc..22c1e42 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -49,7 +49,8 @@ test: stage: test image: openjdk:latest script: - - ./gradlew check -Pargs='spring.profiles.active=test' + - export spring_profiles_active=test + - ./gradlew check #deploy: # stage: deploy diff --git a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt index d96019c..da8c717 100644 --- a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt +++ b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt @@ -4,8 +4,7 @@ import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication @SpringBootApplication -class ApiApplication { -} +class ApiApplication fun main(args: Array) { SpringApplication.run(ApiApplication::class.java, *args) diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt index aa5cb16..6b385f6 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt @@ -2,10 +2,8 @@ package com.aitrainer.api.controller import com.aitrainer.api.model.Customer import com.aitrainer.api.repository.CustomerRepository -import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import java.util.* import javax.validation.Valid @RestController diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt index 39a5744..95f72fc 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt @@ -1,19 +1,19 @@ package com.aitrainer.api.controller -import com.aitrainer.api.model.Customer import com.aitrainer.api.model.Exercises import com.aitrainer.api.repository.ExercisesRepository import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.PutMapping -import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.* import javax.validation.Valid class ExerciesController(private val exercisesRepository: ExercisesRepository) { + @GetMapping("/exercises/customer/{id}") + fun getAllExersicesByCustomerId(customerId: Long): List = + exercisesRepository.getAllByCustomerId(customerId) + @PostMapping("/exercises") - fun createNewCustomer(@Valid @RequestBody exercises: Exercises): Exercises = + fun createNewExercise(@Valid @RequestBody exercises: Exercises): Exercises = exercisesRepository.save(exercises) @PutMapping("/exercises/{id}") @@ -21,9 +21,9 @@ class ExerciesController(private val exercisesRepository: ExercisesRepository) { @Valid @RequestBody newExercises: Exercises): ResponseEntity { return exercisesRepository.findById(exerciseId).map { existingExercises -> val updatedExercises: Exercises = existingExercises.copy( - exercise_type_id = newExercises.exercise_type_id, - customer_id = newExercises.customer_id, - dateTimeExercise = newExercises.dateTimeExercise, + exerciseTypeId = newExercises.exerciseTypeId, + customerId = newExercises.customerId, + datetimeExercise = newExercises.datetimeExercise, quantity = newExercises.quantity, restTime = newExercises.restTime ) diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt index b186bfe..26f8739 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt @@ -2,10 +2,8 @@ package com.aitrainer.api.controller import com.aitrainer.api.model.ExerciseType import com.aitrainer.api.repository.ExerciseTypeRepository -import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import java.util.* import javax.validation.Valid @RestController diff --git a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt index 58b1333..6f706c9 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt @@ -6,15 +6,16 @@ import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id +import javax.validation.constraints.Null @Entity data class Exercises ( - @get: NonNull var exercise_type_id: Long = 0, - @get: NonNull var customer_id: Long = 0, - @get: NonNull var dateTimeExercise: Date? = null, - @get: NonNull var quantity: Int = 0, - var restTime: Int = 0, // in seconds + @get: NonNull var exerciseTypeId: Long = 0, + @get: NonNull var customerId: Long = 0, + @get: NonNull var datetimeExercise: Date? = null, + @get: NonNull var quantity: Int = 0, + @get: Null var restTime: Integer, // in seconds - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val exercise_id: Long = 0 + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val exerciseId: Long = 0 ) \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt index 1f13b8d..d077144 100644 --- a/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt +++ b/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt @@ -5,4 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface ExercisesRepository : JpaRepository \ No newline at end of file +interface ExercisesRepository : JpaRepository { + fun getAllByCustomerId( customerId: Long ):List +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt index 253d387..e7bdfe5 100644 --- a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt @@ -3,8 +3,6 @@ package com.aitrainer.api.test import org.springframework.beans.factory.annotation.Autowired import java.sql.* -import kotlin.test.assertFails - import java.util.* import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -14,9 +12,9 @@ class AitrainerDBTest(configuration: ApplicationConfiguration) { @Autowired - private lateinit var username: String; - private lateinit var password: String; - private lateinit var datasourceUrl: String; + private lateinit var username: String + private lateinit var password: String + private lateinit var datasourceUrl: String private lateinit var conn: Connection private val conf: ApplicationConfiguration = configuration @@ -34,38 +32,7 @@ class AitrainerDBTest(configuration: ApplicationConfiguration) { } - private fun executeDBQueries() { - var stmt: Statement? = null - var resultset: ResultSet? = null - try { - stmt = conn.createStatement() - val sql: String = "CREATE DATABASE aitrainer_test;" - if ( stmt.execute( sql ) ) { - } - //while (resultset!!.next()) { - // println(resultset.getString("Database")) - //} - } catch (ex: SQLException) { - throw Exception(ex) - } finally { - // release resources - if (resultset != null) { - try { - resultset.close() - } catch (sqlEx: SQLException) { - } - } - if (stmt != null) { - try { - stmt.close() - } catch (sqlEx: SQLException) { - throw Exception(sqlEx) - } - } - conn.close() - } - } /** * This method makes a connection to MySQL Server * In this example, MySQL Server is running in the local host (so 127.0.0.1) @@ -75,16 +42,8 @@ class AitrainerDBTest(configuration: ApplicationConfiguration) { val connectionProps = Properties() connectionProps["user"] = this.username connectionProps["password"] = this.password - //try { Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance() this.conn = DriverManager.getConnection(this.datasourceUrl, connectionProps) - //} catch (ex: SQLException) { - // handle any errors - // ex.printStackTrace() - //} catch (ex: Exception) { - // handle any errors - // ex.printStackTrace() - //} } } \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt b/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt index 2d4438e..8d378d5 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component @Component @ConfigurationProperties(prefix = "spring.datasource") class ApplicationConfiguration { - open var username: String? = null - open var password: String? = null - open var url: String? = null + var username: String? = null + var password: String? = null + var url: String? = null } \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index 4924dc9..b76f1e6 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -5,54 +5,51 @@ import com.aitrainer.api.repository.CustomerRepository import com.aitrainer.api.model.Customer import org.junit.jupiter.api.Test import kotlin.test.assertEquals -import kotlin.test.assertNotEquals import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.fail -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Autowired @SpringBootTest class CustomerTests { @Autowired - lateinit private var customerRepository: CustomerRepository + private lateinit var customerRepository: CustomerRepository private val customerId: Long = 4 - private var inserted_id: Long? = null + private var insertedId: Long? = null @Test fun testGet() { - val id: Long = 7; - val customer: Customer = customerRepository.findById( id ).orElse(null); + val id: Long = 7 + val customer: Customer = customerRepository.findById( id ).orElse(null) assertEquals( customer.name, "Átlag 18 éves fiú") } @Test fun testInsert() { - val newCustomer = Customer("Bossanyi", "Tibor", "", 48, "m"); + val newCustomer = Customer("Bossanyi", "Tibor", "", 48, "m") val savedCustomer: Customer = customerRepository.save(newCustomer) assertEquals(savedCustomer.age, 48) - this.inserted_id = savedCustomer.customer_id; + this.insertedId = savedCustomer.customer_id - val customer: Customer = customerRepository.findById( savedCustomer.customer_id ).orElse(null); + val customer: Customer = customerRepository.findById( savedCustomer.customer_id ).orElse(null) assertEquals( customer.firstname, "Tibor") - this.testUpdate(savedCustomer.customer_id); + this.testUpdate(savedCustomer.customer_id) } fun testUpdate( customerId: Long ) { - var id: Long? = customerId; + val id: Long? = customerId assertNotNull(id) - var updatedCustomer: Customer = customerRepository.findById( id ).orElse(null); + val updatedCustomer: Customer = customerRepository.findById( id ).orElse(null) assertNotNull(updatedCustomer) updatedCustomer.firstname ="Tiborka" - val customer: Customer = customerRepository.save( updatedCustomer ); + val customer: Customer = customerRepository.save( updatedCustomer ) assertEquals( customer.firstname, "Tiborka") - customerRepository.delete(updatedCustomer); + customerRepository.delete(updatedCustomer) } } \ No newline at end of file From 85ce06d4c75cad2749ff7f02a2cf819c9b6e5a47 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Mon, 11 May 2020 21:42:01 +0200 Subject: [PATCH 63/86] java Integer to kotlin.int API 0.0.3 --- build.gradle.kts | 2 +- .../com/aitrainer/api/model/Exercises.kt | 2 +- .../aitrainer/api/service/ExerciseService.kt | 10 +++++++ .../com/aitrainer/api/test/ExerciseTest.kt | 28 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index bc11d04..51b59ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "com.aitrainer" -version = "0.0.2" +version = "0.0.3" java.sourceCompatibility = JavaVersion.VERSION_1_8 repositories { diff --git a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt index 6f706c9..bb2736c 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt @@ -14,7 +14,7 @@ data class Exercises ( @get: NonNull var customerId: Long = 0, @get: NonNull var datetimeExercise: Date? = null, @get: NonNull var quantity: Int = 0, - @get: Null var restTime: Integer, // in seconds + @get: Null var restTime: Int?, // in seconds @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val exerciseId: Long = 0 diff --git a/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt b/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt new file mode 100644 index 0000000..0b951ef --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt @@ -0,0 +1,10 @@ +package com.aitrainer.api.service + +import com.aitrainer.api.model.Exercises +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param + +interface ExerciseService { + @Query("FROM Exercises WHERE customer_id = :customerId") + fun findAllByCustomerId( @Param("customerId") customerId: Long): List +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt new file mode 100644 index 0000000..497423f --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt @@ -0,0 +1,28 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.model.Exercises +import com.aitrainer.api.repository.ExercisesRepository +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import kotlin.test.assertEquals + +@SpringBootTest +class ExerciseTest { + + @Autowired + private lateinit var exerciseRepository: ExercisesRepository + + @Test + fun testGet() { + var id: Long = 1 + + val exercises: List = exerciseRepository.getAllByCustomerId( id ) + assertEquals( exercises[0].quantity, 12) + + id = 100000 + val exercises2: List = exerciseRepository.getAllByCustomerId( id ) + + assertEquals( exercises2.size, 0) + } +} \ No newline at end of file From 28e5873aa8ed03cda95f5e25da0356402db405f7 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 14:07:56 +0200 Subject: [PATCH 64/86] db fix --- data/db/install.sql | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/db/install.sql b/data/db/install.sql index a43a979..75fa298 100644 --- a/data/db/install.sql +++ b/data/db/install.sql @@ -47,7 +47,7 @@ INSERT INTO `customer` (`customer_id`, `name`, `firstname`, `email`, `sex`, `age CREATE TABLE IF NOT EXISTS `exercises` ( `exercise_id` int NOT NULL AUTO_INCREMENT, `exercise_type_id` int NOT NULL, - `user_id` int NOT NULL, + `customer_id` int NOT NULL, `datetime_exercise` datetime NOT NULL, `quantity` int DEFAULT NULL, `rest_time` int DEFAULT NULL COMMENT 'in sec', @@ -57,7 +57,7 @@ CREATE TABLE IF NOT EXISTS `exercises` ( -- Tábla adatainak mentése aitrainer.exercises: ~1 rows (hozzávetőleg) /*!40000 ALTER TABLE `exercises` DISABLE KEYS */; -INSERT INTO `exercises` (`exercise_id`, `exercise_type_id`, `user_id`, `datetime_exercise`, `quantity`, `rest_time`) VALUES +INSERT INTO `exercises` (`exercise_id`, `exercise_type_id`, `customer_id`, `datetime_exercise`, `quantity`, `rest_time`) VALUES (1, 1, 1, '2020-05-01 00:00:00', 12, NULL); /*!40000 ALTER TABLE `exercises` ENABLE KEYS */; @@ -87,17 +87,17 @@ INSERT INTO `exercise_ages` (`exercise_age_id`, `exercise_type_id`, `name`, `sex /*!40000 ALTER TABLE `exercise_ages` ENABLE KEYS */; -- Struktúra mentése tábla aitrainer. exercise_types -CREATE TABLE IF NOT EXISTS `exercise_types` ( - `exercise_id` int NOT NULL AUTO_INCREMENT, +CREATE TABLE IF NOT EXISTS `exercise_type` ( + `exercise_type_id` int NOT NULL AUTO_INCREMENT, `name` char(100) NOT NULL, `description` varchar(1000) DEFAULT NULL, `video` mediumblob, - PRIMARY KEY (`exercise_id`) + PRIMARY KEY (`exercise_type_id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; -- Tábla adatainak mentése aitrainer.exercise_types: ~11 rows (hozzávetőleg) -/*!40000 ALTER TABLE `exercise_types` DISABLE KEYS */; -INSERT INTO `exercise_types` (`exercise_id`, `name`, `description`, `video`) VALUES +/*!40000 ALTER TABLE `exercise_type` DISABLE KEYS */; +INSERT INTO `exercise_type` (`exercise_type_id`, `name`, `description`, `video`) VALUES (1, 'Melső fekvőtámasz 1 perc', 'Ezt igazolja a 2016 márciusában beállított guinness rekord is,\r\namelyben KJ Joseph 1 perc alatt 82 szabályos karhajlítás-nyújtást\r\nvégzett.', NULL), (2, 'Húzódzkodás', 'Ennek a gyakorlatnak a 24 órás csúcstartója Joonas Mäkipelto 5050\r\ngyakorlattal.', NULL), (3, 'Melső fekvőtámasz 30mp', 'A gyakorlatot 30 másodperc alatt olyan sokszor kell végrehajtani, ahányszor a\r\nfelvételiző képes rá. Azonban törekedni kell a szabályos végrehajtásra, ugyanis csak azokat\r\nszámolják. A nők esetében 20, a férfiak esetében 35 gyakorlatot kell végrehajtani a maximális\r\npont megszerzéséért. A gyakorlat akkor sikeres, ha a női legalább 1, a férfi felvételiző\r\nlegalább 11 gyakorlatot képes végrehajtani.', NULL), @@ -109,7 +109,7 @@ INSERT INTO `exercise_types` (`exercise_id`, `name`, `description`, `video`) VAL (9, 'Felülés hanyattfekvésből', 'Az NKE-RTK hallgatói közül Papp Zsófia 66 db-ot, Gál Valentin 80\r\ndb-ot csinált 1 perc leforgása alatt.\r\nElső ütemre megtörténik a felülés, ami akkor szabályos, ha valamelyik könyök érinti a\r\ntérdet. Második ütemre vissza kell térni a kiinduló helyzetbe. A maximális pont eléréséhez 1\r\nperc alatt 45 illetve 55 ismétlést kell végezni a nőknek illetve a férfiaknak. A minimumhoz 7\r\nés 25 ismétlés szükséges.', NULL), (10, 'Felülés hajlított térddel', 'Magyar Honvédség: A kiinduló helyzet hajlított ülés, ennek során a sarkak a talajon, a térd 90 fokban meghajlítva\r\nvan.\r\nA gyakorlat végrehajtására két perc áll rendelkezésre. Ennek során csak a szabályosan,\r\na fentiekben leírt módon végrehajtott gyakorlat értékelhető. Férfiaknál, nőknél egyaránt 90\r\ngyakorlatot kell végrehajtani a maximális pontért. Amennyiben a megadott időkeret alatt nem\r\nsikerül legalább 25 szabályos gyakorlatot végrehajtani, úgy az sikertelennek minősül.', NULL), (11, 'Síkfutás 2000m', 'A maximálisan megszerezhető pontot az a felvételiző gyűjtheti be, aki a távot nők\r\nesetében 10:00 perc alatt, férfiak esetében 7:35 perc alatt teljesíti. A gyakorlat sikeres\r\nteljesítésére nők esetében maximum 16:00 perc, férfiak esetében 13:30 perc áll rendelkezésre.', NULL); -/*!40000 ALTER TABLE `exercise_types` ENABLE KEYS */; +/*!40000 ALTER TABLE `exercise_type` ENABLE KEYS */; /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; /*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; From 530fe5ad89d8fddb121f3240ca404707db16dc91 Mon Sep 17 00:00:00 2001 From: ZalanBoss <48163754+ZalanBoss@users.noreply.github.com> Date: Tue, 12 May 2020 14:32:21 +0200 Subject: [PATCH 65/86] Test for ExerciseType (Error) --- .../api/controller/ExerciseTypeController.kt | 2 +- .../com/aitrainer/api/model/ExerciseType.kt | 11 +++--- .../aitrainer/api/test/ExerciseTypeTest.kt | 35 +++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt index 26f8739..8930c12 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt @@ -11,7 +11,7 @@ import javax.validation.Valid class ExerciseTypeController ( private val exerciseTypeRepository: ExerciseTypeRepository ) { @GetMapping("/exercise_type") - fun getAllExerciseTypes(): List = + fun getAllExerciseType(): List = exerciseTypeRepository.findAll() @PostMapping("/exercise_type") diff --git a/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt b/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt index 2d181e3..1d846d2 100644 --- a/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt +++ b/src/main/kotlin/com/aitrainer/api/model/ExerciseType.kt @@ -1,20 +1,21 @@ package com.aitrainer.api.model +import org.hibernate.type.BinaryType import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id import javax.validation.constraints.NotBlank +import javax.validation.constraints.Null @Entity data class ExerciseType ( - @get: NotBlank - val name: String = "", - val description: String = "", - val video: Long = 0, + @get: NotBlank var name: String = "", + @get: NotBlank var description: String = "", + @get: Null var video: BinaryType?, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val type_id: Long = 0 + val exerciseTypeId: Long = 0 ) diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt new file mode 100644 index 0000000..478cf6c --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt @@ -0,0 +1,35 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.model.ExerciseType +import com.aitrainer.api.repository.ExerciseTypeRepository +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import kotlin.test.assertEquals + +@SpringBootTest +class ExerciseTypeTest { + + @Autowired + private val exerciseTypeId: Long = 4 + private var insertedId: Long? = null + private lateinit var exerciseTypeRepository: ExerciseTypeRepository + + @Test + fun testGet() { + val id: Long = 7 + val extype: ExerciseType = exerciseTypeRepository.findById( id ).orElse(null) + assertEquals( extype.name, "4x10m-es ingafutás") + } + fun testInsert(){ + val newEx = ExerciseType( "Húzodszkodás", " A legtöbb húzodszkodás 24 óra alatt John Ort érte el 7600-al 2016-ban. ", null ) + val savedEx: ExerciseType = exerciseTypeRepository.save(newEx) + assertEquals(savedEx.name, "Húzodszkodás") + + this.insertedId = savedEx.exerciseTypeId + + val extype: ExerciseType = exerciseTypeRepository.findById( savedEx.exerciseTypeId ).orElse(null) + assertEquals( extype.name, "Húzodszkodás") + + } +} \ No newline at end of file From 5a4dda676b00257258915d33fcfe3fbd74bc045a Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 14:47:46 +0200 Subject: [PATCH 66/86] db fix, ExerciseTypeTest fix --- .gitlab-ci.yml | 3 ++- data/db/install.sql | 4 +--- src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt | 1 - src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 22c1e42..191645e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,8 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - #- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table CUSTOMER; DROP table EXERCISES; DROP table EXERCISE_TYPE; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: stage: test diff --git a/data/db/install.sql b/data/db/install.sql index 75fa298..e92664a 100644 --- a/data/db/install.sql +++ b/data/db/install.sql @@ -38,9 +38,7 @@ INSERT INTO `customer` (`customer_id`, `name`, `firstname`, `email`, `sex`, `age (10, 'Átlag 15 éves lány', '', NULL, 'w', 15), (11, 'Átlag 16 éves lány', '', NULL, 'w', 16), (12, 'Átlag 17 éves lány', '', NULL, 'w', 17), - (13, 'Átlag 18 éves lány', '', NULL, 'w', 18), - (14, 'Bossanyi', 'Tibor', '', 'm', 48), - (15, 'Bossanyi', 'Tibor', '', 'm', 48); + (13, 'Átlag 18 éves lány', '', NULL, 'w', 18); /*!40000 ALTER TABLE `customer` ENABLE KEYS */; -- Struktúra mentése tábla aitrainer. exercises diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index b76f1e6..f179371 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -13,7 +13,6 @@ class CustomerTests { @Autowired private lateinit var customerRepository: CustomerRepository - private val customerId: Long = 4 private var insertedId: Long? = null @Test diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt index 478cf6c..7dac582 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt @@ -11,8 +11,6 @@ import kotlin.test.assertEquals class ExerciseTypeTest { @Autowired - private val exerciseTypeId: Long = 4 - private var insertedId: Long? = null private lateinit var exerciseTypeRepository: ExerciseTypeRepository @Test @@ -26,7 +24,7 @@ class ExerciseTypeTest { val savedEx: ExerciseType = exerciseTypeRepository.save(newEx) assertEquals(savedEx.name, "Húzodszkodás") - this.insertedId = savedEx.exerciseTypeId + val insertedId: Long = savedEx.exerciseTypeId val extype: ExerciseType = exerciseTypeRepository.findById( savedEx.exerciseTypeId ).orElse(null) assertEquals( extype.name, "Húzodszkodás") From afa3042e7ccfcc5590f61809866abcd154490bd3 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Tue, 12 May 2020 13:23:54 +0000 Subject: [PATCH 67/86] Update .gitlab-ci.yml table names --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 191645e..4b74aeb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table CUSTOMER; DROP table EXERCISES; DROP table EXERCISE_TYPE; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_type; DROP table exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: From a5e560f78e303a6db110ad6ed8e6e1cf0ff235e0 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Tue, 12 May 2020 13:33:48 +0000 Subject: [PATCH 68/86] Update .gitlab-ci.yml delete old table name exercise_types --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b74aeb..824288f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_type; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_types; DROP table exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: From d942ab7c8849370fa97792290b5686f215a95b89 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Tue, 12 May 2020 13:42:12 +0000 Subject: [PATCH 69/86] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 824288f..3bca95a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_types; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table exercise_types; DROP table exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: From 17183bda07504eb0c9343f44c63ed751b9dc03c4 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 16:14:52 +0200 Subject: [PATCH 70/86] Exercise.getAllByCustomerId fix, HTTP test --- .gitlab-ci.yml | 2 +- .../aitrainer/api/controller/ExerciesController.kt | 11 ++++++++++- .../aitrainer/api/repository/ExercisesRepository.kt | 2 +- .../com/aitrainer/api/service/ExerciseService.kt | 4 ++-- .../kotlin/com/aitrainer/api/test/AitrainerDBTest.kt | 6 +++--- .../kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt | 3 ++- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 191645e..4b74aeb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table CUSTOMER; DROP table EXERCISES; DROP table EXERCISE_TYPE; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_type; DROP table exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt index 95f72fc..c0e8eed 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt @@ -6,10 +6,19 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import javax.validation.Valid +@RestController +@RequestMapping("/api") class ExerciesController(private val exercisesRepository: ExercisesRepository) { + @GetMapping("/exercises/{id}") + fun getExerciseById(@PathVariable(value = "id") exerciseId: Long): ResponseEntity { + return exercisesRepository.findById(exerciseId).map { exercise -> + ResponseEntity.ok(exercise) + }.orElse(ResponseEntity.notFound().build()) + } + @GetMapping("/exercises/customer/{id}") - fun getAllExersicesByCustomerId(customerId: Long): List = + fun getAllExersicesByCustomerId(@PathVariable( value = "id" ) customerId: Long? ): List = exercisesRepository.getAllByCustomerId(customerId) @PostMapping("/exercises") diff --git a/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt index d077144..05f7426 100644 --- a/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt +++ b/src/main/kotlin/com/aitrainer/api/repository/ExercisesRepository.kt @@ -6,5 +6,5 @@ import org.springframework.stereotype.Repository @Repository interface ExercisesRepository : JpaRepository { - fun getAllByCustomerId( customerId: Long ):List + fun getAllByCustomerId( customerId: Long? ):List } \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt b/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt index 0b951ef..e505bc3 100644 --- a/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt +++ b/src/main/kotlin/com/aitrainer/api/service/ExerciseService.kt @@ -5,6 +5,6 @@ import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param interface ExerciseService { - @Query("FROM Exercises WHERE customer_id = :customerId") - fun findAllByCustomerId( @Param("customerId") customerId: Long): List + @Query("FROM exercises WHERE customer_id = :customerId") + fun findAllByCustomerId( @Param("customerId") customerId: Long? ): List } \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt index e7bdfe5..a8a6c72 100644 --- a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt @@ -22,9 +22,9 @@ class AitrainerDBTest(configuration: ApplicationConfiguration) { this.username = this.conf.username.orEmpty() this.password = this.conf.password.orEmpty() this.datasourceUrl = this.conf.url.orEmpty() - assertTrue ("username should not be empty", { this.username.isNotEmpty() }) - assertTrue ("password should not be empty", { this.password.isNotEmpty() }) - assertTrue ("url should not be empty", { this.datasourceUrl.isNotEmpty() }) + assertTrue ("username should not be empty") { this.username.isNotEmpty() } + assertTrue ("password should not be empty") { this.password.isNotEmpty() } + assertTrue ("url should not be empty") { this.datasourceUrl.isNotEmpty() } this.getConnection() diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt index 7dac582..c9cd308 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt @@ -12,6 +12,7 @@ class ExerciseTypeTest { @Autowired private lateinit var exerciseTypeRepository: ExerciseTypeRepository + private var insertedId: Long = 0 @Test fun testGet() { @@ -24,7 +25,7 @@ class ExerciseTypeTest { val savedEx: ExerciseType = exerciseTypeRepository.save(newEx) assertEquals(savedEx.name, "Húzodszkodás") - val insertedId: Long = savedEx.exerciseTypeId + this.insertedId = savedEx.exerciseTypeId val extype: ExerciseType = exerciseTypeRepository.findById( savedEx.exerciseTypeId ).orElse(null) assertEquals( extype.name, "Húzodszkodás") From 8ccc7209ff3fee242ea973df1fb5e67ab05d9ef0 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor <1-bossanyit@users.noreply.andio.club> Date: Tue, 12 May 2020 14:48:05 +0000 Subject: [PATCH 71/86] Update .gitlab-ci.yml, drop tables --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3bca95a..824288f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table exercise_types; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_types; DROP table exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: From 9d8949130ff44bd2a78ddaa3184599aa4c3369c1 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 21:29:05 +0200 Subject: [PATCH 72/86] deploy stage files --- .gitlab-ci.yml | 13 +++++++------ ci-cd/.ssh/.scp | 1 + ci-cd/deploy.sh | 1 + 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 ci-cd/.ssh/.scp create mode 100644 ci-cd/deploy.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 824288f..3282e07 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ stages: - build - prepare - test -# - deploy + - deploy variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_types; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_type; DROP table exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: @@ -53,10 +53,11 @@ test: - export spring_profiles_active=test - ./gradlew check -#deploy: -# stage: deploy -# script: -# - ./deploy +deploy: + stage: deploy + script: + - apt-get update && apt-get --assume-yes install sshpass + - ci-cd/deploy.sh after_script: - echo "End CI" diff --git a/ci-cd/.ssh/.scp b/ci-cd/.ssh/.scp new file mode 100644 index 0000000..7f7f899 --- /dev/null +++ b/ci-cd/.ssh/.scp @@ -0,0 +1 @@ +tbi6012AndiBossanyi \ No newline at end of file diff --git a/ci-cd/deploy.sh b/ci-cd/deploy.sh new file mode 100644 index 0000000..8ccdbe5 --- /dev/null +++ b/ci-cd/deploy.sh @@ -0,0 +1 @@ +sshpass -f /ci-cd/.ssh/.scp scp build/libs/aitrainer_server-0.0.2.jar bosi@andio.shop:/home/bosi/deploy/aitrainer_server.jar \ No newline at end of file From fa0cf72f501d557f93fd40ce47f48ae611394177 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 21:32:42 +0200 Subject: [PATCH 73/86] deploy stage files --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3282e07..f621509 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ connect: script: - apt-get update && apt-get --assume-yes install mysql-client - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; show tables;" - - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table customer; DROP table exercises; DROP table exercise_type; DROP table exercise_ages;" + - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql -e "use $MYSQL_DATABASE; DROP table if exists customer; DROP table if exists exercises; DROP table if exists exercise_type; DROP table if exists exercise_ages;" - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mysql < "data/db/install.sql" #first time test: From 408d034babfdf2bce3c06999e0d3133e9ab29cce Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 21:39:57 +0200 Subject: [PATCH 74/86] deploy ci fix --- .gitlab-ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f621509..fe5341d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,4 @@ -services: - - mysql:latest + stages: - build @@ -55,9 +54,12 @@ test: deploy: stage: deploy + image: mysql:latest script: - apt-get update && apt-get --assume-yes install sshpass - ci-cd/deploy.sh + only: + - master after_script: - echo "End CI" From 0f215b5c232c8a325f499dbb58fc3ee16799360b Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 21:54:35 +0200 Subject: [PATCH 75/86] deploy ci fix, port change to scp --- ci-cd/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-cd/deploy.sh b/ci-cd/deploy.sh index 8ccdbe5..3e14dcb 100644 --- a/ci-cd/deploy.sh +++ b/ci-cd/deploy.sh @@ -1 +1 @@ -sshpass -f /ci-cd/.ssh/.scp scp build/libs/aitrainer_server-0.0.2.jar bosi@andio.shop:/home/bosi/deploy/aitrainer_server.jar \ No newline at end of file +sshpass -f /ci-cd/.ssh/.scp scp -p 6622 build/libs/aitrainer_server-0.0.2.jar bosi@andio.shop:/home/bosi/deploy/aitrainer_server.jar \ No newline at end of file From 0eba844cb7ee04b775f547bf3298da3c7cc5e65c Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 22:05:25 +0200 Subject: [PATCH 76/86] deploy ci fix, chmod +x to deploy.sh --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fe5341d..adfb668 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,6 +57,7 @@ deploy: image: mysql:latest script: - apt-get update && apt-get --assume-yes install sshpass + - chmod +x ci-cd/deploy.sh - ci-cd/deploy.sh only: - master From 679694bb4739e8ec872269aba52d51135cbca698 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Tue, 12 May 2020 22:20:11 +0200 Subject: [PATCH 77/86] deploy ci fix, fix upload path --- ci-cd/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-cd/deploy.sh b/ci-cd/deploy.sh index 3e14dcb..1e7a4ca 100644 --- a/ci-cd/deploy.sh +++ b/ci-cd/deploy.sh @@ -1 +1 @@ -sshpass -f /ci-cd/.ssh/.scp scp -p 6622 build/libs/aitrainer_server-0.0.2.jar bosi@andio.shop:/home/bosi/deploy/aitrainer_server.jar \ No newline at end of file +sshpass -f /ci-cd/.ssh/.scp scp -p 6622 build/libs/aitrainer_server-0.0.2.jar bosi@andio.shop:/home/bosi/aitrainer/deploy/aitrainer_server.jar \ No newline at end of file From 565680636a5f9b00d21092de9cc81bbca742868e Mon Sep 17 00:00:00 2001 From: ZalanBoss <48163754+ZalanBoss@users.noreply.github.com> Date: Sat, 16 May 2020 10:50:42 +0200 Subject: [PATCH 78/86] find Real customer api (inconvinence) --- .../com/aitrainer/api/controller/CustomerController.kt | 8 +++++++- src/main/kotlin/com/aitrainer/api/model/Customer.kt | 1 + .../com/aitrainer/api/repository/CustomerRepository.kt | 4 +++- .../com/aitrainer/api/service/CustomerService.kt | 10 ++++++++++ .../kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt | 2 +- 5 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/aitrainer/api/service/CustomerService.kt diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt index 6b385f6..560ded8 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt @@ -23,7 +23,13 @@ class CustomerController ( private val customerRepository: CustomerRepository ) return customerRepository.findById(customerId).map { customer -> ResponseEntity.ok(customer) }.orElse(ResponseEntity.notFound().build()) - } + } + + + @GetMapping("/customers/real") + fun getRealCustomers(active: String): List = + customerRepository.findRealCustomers(active) + @PutMapping("/customers/{id}") fun updateCustomerById(@PathVariable(value = "id") customerId: Long, diff --git a/src/main/kotlin/com/aitrainer/api/model/Customer.kt b/src/main/kotlin/com/aitrainer/api/model/Customer.kt index 984ed26..d932a5d 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Customer.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Customer.kt @@ -15,6 +15,7 @@ data class Customer ( var email: String = "", var age: Int = 0, var sex: String = "m", + var active: String = "N", @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val customer_id: Long = 0 diff --git a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt index cfb8431..f166db7 100644 --- a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt +++ b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt @@ -5,4 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface CustomerRepository : JpaRepository \ No newline at end of file +interface CustomerRepository : JpaRepository{ + fun findRealCustomers(active: String):List +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt b/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt new file mode 100644 index 0000000..ed8971d --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt @@ -0,0 +1,10 @@ +package com.aitrainer.api.service + +import com.aitrainer.api.model.Customer +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param + +interface CustomerService { + @Query("FROM customer WHERE active = :active") + fun findRealCustomers(@Param("active") active: String): List +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt index 7dac582..adcf926 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt @@ -20,7 +20,7 @@ class ExerciseTypeTest { assertEquals( extype.name, "4x10m-es ingafutás") } fun testInsert(){ - val newEx = ExerciseType( "Húzodszkodás", " A legtöbb húzodszkodás 24 óra alatt John Ort érte el 7600-al 2016-ban. ", null ) + val newEx = ExerciseType( "Húzodszkodás", " A legtöbb húzodszkodást 24 óra alatt John Ort érte el 7600-al 2016-ban. ", null ) val savedEx: ExerciseType = exerciseTypeRepository.save(newEx) assertEquals(savedEx.name, "Húzodszkodás") From b5a8727d6072a174cf54a1776ffa95cce00b4715 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sat, 16 May 2020 11:28:51 +0200 Subject: [PATCH 79/86] get Real Customers: fix findByActive --- .../com/aitrainer/api/ApiApplication.kt | 23 ++++++++++++++++--- .../api/controller/CustomerController.kt | 4 ++-- .../aitrainer/api/service/CustomerService.kt | 4 ++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt index da8c717..400a4fc 100644 --- a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt +++ b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt @@ -2,10 +2,27 @@ package com.aitrainer.api import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.builder.SpringApplicationBuilder +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + @SpringBootApplication +@RestController class ApiApplication -fun main(args: Array) { - SpringApplication.run(ApiApplication::class.java, *args) -} + @Override + fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder { + return application.sources(ApiApplication::class.java) + } + + + fun main(args: Array) { + SpringApplication.run(ApiApplication::class.java, *args) + } + + + @RequestMapping( "/") + fun hello(): String { + return "Hello Aitrainer API"; + } diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt index 560ded8..f9e065b 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt @@ -27,8 +27,8 @@ class CustomerController ( private val customerRepository: CustomerRepository ) @GetMapping("/customers/real") - fun getRealCustomers(active: String): List = - customerRepository.findRealCustomers(active) + fun getRealCustomers(active: String): List = + customerRepository.findByActive(active) @PutMapping("/customers/{id}") diff --git a/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt b/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt index ed8971d..bfebd01 100644 --- a/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt +++ b/src/main/kotlin/com/aitrainer/api/service/CustomerService.kt @@ -5,6 +5,6 @@ import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param interface CustomerService { - @Query("FROM customer WHERE active = :active") - fun findRealCustomers(@Param("active") active: String): List + @Query("FROM customer WHERE active = :active ") + fun findByActive(@Param("active") active: String? ): List } \ No newline at end of file From ff2c16ea7b506f6aeb15e4ec241e722a744e5097 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sat, 16 May 2020 11:35:46 +0200 Subject: [PATCH 80/86] get Real Customers: fix findByActive + database change --- data/db/install.sql | 1 + .../com/aitrainer/api/repository/CustomerRepository.kt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/db/install.sql b/data/db/install.sql index e92664a..91e6e7e 100644 --- a/data/db/install.sql +++ b/data/db/install.sql @@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS `customer` ( `email` char(100) DEFAULT NULL, `sex` enum('m','w') DEFAULT 'm', `age` tinyint DEFAULT NULL, + `active` enum('Y','N','D','S') DEFAULT 'N', PRIMARY KEY (`customer_id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; diff --git a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt index f166db7..cf0385d 100644 --- a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt +++ b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt @@ -5,6 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface CustomerRepository : JpaRepository{ - fun findRealCustomers(active: String):List -} \ No newline at end of file +interface CustomerRepository : JpaRepository { + fun findByActive( active: String? ):List +} From 2eb5a6ae03b1b3fab315ec6c35d11823da7997a4 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 20 May 2020 15:36:04 +0200 Subject: [PATCH 81/86] spfl4j logging --- build.gradle.kts | 8 +++- .../com/aitrainer/api/ApiApplication.kt | 6 ++- .../api/controller/ExerciseTypeController.kt | 24 ++++++++--- src/main/resources/application.properties | 4 +- src/main/resources/logback-spring.xml | 41 +++++++++++++++++++ src/main/resources/simplelogger.properties | 34 +++++++++++++++ .../com/aitrainer/api/test/AitrainerDBTest.kt | 4 +- .../com/aitrainer/api/test/CustomerTests.kt | 6 +-- .../aitrainer/api/test/ExerciseTypeTest.kt | 27 +++++++++--- 9 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/main/resources/simplelogger.properties diff --git a/build.gradle.kts b/build.gradle.kts index 51b59ac..7408179 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "com.aitrainer" -version = "0.0.3" +version = "0.0.4" java.sourceCompatibility = JavaVersion.VERSION_1_8 repositories { @@ -22,13 +22,17 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("org.apache.logging.log4j:log4j-core:2.13.3") + implementation("org.apache.logging.log4j:log4j-api:2.13.3") + implementation("org.slf4j:slf4j-api:1.7.30") + + runtimeOnly("mysql:mysql-connector-java") testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } testImplementation("junit:junit:4.13") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.3.72") - } tasks.withType { diff --git a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt index 400a4fc..742e079 100644 --- a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt +++ b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt @@ -1,5 +1,6 @@ package com.aitrainer.api +import org.slf4j.LoggerFactory import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.builder.SpringApplicationBuilder @@ -10,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController @SpringBootApplication @RestController class ApiApplication - + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) @Override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder { return application.sources(ApiApplication::class.java) @@ -18,11 +19,12 @@ class ApiApplication fun main(args: Array) { + logger.info(" ---- Start aitrainer API") SpringApplication.run(ApiApplication::class.java, *args) } @RequestMapping( "/") fun hello(): String { - return "Hello Aitrainer API"; + return "Hello Aitrainer API" } diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt index 8930c12..b6143b7 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt @@ -2,6 +2,8 @@ package com.aitrainer.api.controller import com.aitrainer.api.model.ExerciseType import com.aitrainer.api.repository.ExerciseTypeRepository +import org.slf4j.LoggerFactory + import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import javax.validation.Valid @@ -9,17 +11,27 @@ import javax.validation.Valid @RestController @RequestMapping("/api") class ExerciseTypeController ( private val exerciseTypeRepository: ExerciseTypeRepository ) { - + private val logger = LoggerFactory.getLogger(javaClass) + @GetMapping("/exercise_type") - fun getAllExerciseType(): List = - exerciseTypeRepository.findAll() + fun getAllExerciseType(): List { + val list: List = exerciseTypeRepository.findAll() + logger.info("Get All exercise types: $list") + return list + } + + @PostMapping("/exercise_type") - fun createNewExerciseType(@Valid @RequestBody exerciseType: ExerciseType): ExerciseType = - exerciseTypeRepository.save(exerciseType) + fun createNewExerciseType(@Valid @RequestBody exerciseType: ExerciseType): ExerciseType { + logger.info("Create new exercise type: $exerciseType") + return exerciseTypeRepository.save(exerciseType) + } + @GetMapping("/exercise_type/{id}") fun getExerciseTypeById(@PathVariable(value = "id") exerciseTypeId: Long): ResponseEntity { + logger.info("Get exercise type by id: $exerciseTypeId") return exerciseTypeRepository.findById(exerciseTypeId).map { exerciseType -> ResponseEntity.ok(exerciseType) }.orElse(ResponseEntity.notFound().build()) @@ -35,7 +47,7 @@ class ExerciseTypeController ( private val exerciseTypeRepository: ExerciseTypeR @PutMapping("/exercise_type/{id}") fun updateExerciseTypesById(@PathVariable(value = "id") exerciseTypeId: Long, @Valid @RequestBody newExerciseType: ExerciseType): ResponseEntity { - + logger.info("Update exercise type by id: $exerciseTypeId with object $newExerciseType") return exerciseTypeRepository.findById(exerciseTypeId).map { existingExerciseType -> val updatedExerciseType: ExerciseType = existingExerciseType .copy(name = newExerciseType.name, diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1f3f585..a43762e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,4 +10,6 @@ spring.datasource.password = andio2009 # The SQL dialect makes Hibernate generate better SQL for the chosen database -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect \ No newline at end of file +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect +logging.config=classpath:logback-spring.xml +logging.file=logs \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..23f9249 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,41 @@ + + + + + + ${logging.file}/aitrainer.log + + ${logging.file}/aitrainer.%d{yyyy-MM-dd}.%i.log.gz + + 50MB + + 30 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%thread] [%logger:%line] %msg%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%level] [%thread] [%logger:%line] %msg%n + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/simplelogger.properties b/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..05ff3de --- /dev/null +++ b/src/main/resources/simplelogger.properties @@ -0,0 +1,34 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=debug + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z + +# Set to true if you want to output the current thread name. +# Defaults to true. +#org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +#org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt index a8a6c72..6450923 100644 --- a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt @@ -1,8 +1,8 @@ package com.aitrainer.api.test import org.springframework.beans.factory.annotation.Autowired - -import java.sql.* +import java.sql.Connection +import java.sql.DriverManager import java.util.* import kotlin.test.assertNotNull import kotlin.test.assertTrue diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index f179371..d1c5a5a 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -1,12 +1,12 @@ package com.aitrainer.api.test -import org.springframework.boot.test.context.SpringBootTest -import com.aitrainer.api.repository.CustomerRepository import com.aitrainer.api.model.Customer +import com.aitrainer.api.repository.CustomerRepository import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import kotlin.test.assertEquals import kotlin.test.assertNotNull -import org.springframework.beans.factory.annotation.Autowired @SpringBootTest class CustomerTests { diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt index 1105330..f119762 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTypeTest.kt @@ -3,12 +3,14 @@ package com.aitrainer.api.test import com.aitrainer.api.model.ExerciseType import com.aitrainer.api.repository.ExerciseTypeRepository import org.junit.jupiter.api.Test +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import kotlin.test.assertEquals @SpringBootTest class ExerciseTypeTest { + private val logger = LoggerFactory.getLogger(javaClass) @Autowired private lateinit var exerciseTypeRepository: ExerciseTypeRepository @@ -17,18 +19,31 @@ class ExerciseTypeTest { @Test fun testGet() { val id: Long = 7 - val extype: ExerciseType = exerciseTypeRepository.findById( id ).orElse(null) - assertEquals( extype.name, "4x10m-es ingafutás") + val extype: ExerciseType = exerciseTypeRepository.findById(id).orElse(null) + assertEquals(extype.name, "4x10m-es ingafutás") } + + @Test + fun testGetAll() { + val exerciseTypes: List = exerciseTypeRepository.findAll() + assertEquals(exerciseTypes[0].exerciseTypeId, 1) + assertEquals(exerciseTypes[0].name, "Melső fekvőtámasz 1 perc") + } + + @Test fun testInsert(){ - val newEx = ExerciseType( "Húzodszkodás", " A legtöbb húzodszkodást 24 óra alatt John Ort érte el 7600-al 2016-ban. ", null ) + logger.info("Add 'Húzodzkodás 2") + val newEx = ExerciseType( "Húzodzkodás 2", " A legtöbb húzodszkodást 24 óra alatt John Ort érte el 7600-al 2016-ban. ", null ) val savedEx: ExerciseType = exerciseTypeRepository.save(newEx) - assertEquals(savedEx.name, "Húzodszkodás") + assertEquals(savedEx.name, "Húzodzkodás 2") this.insertedId = savedEx.exerciseTypeId + logger.info("Find 'Húzodzkodás 2") val extype: ExerciseType = exerciseTypeRepository.findById( savedEx.exerciseTypeId ).orElse(null) - assertEquals( extype.name, "Húzodszkodás") - + assertEquals( extype.name, "Húzodzkodás 2") + + logger.info("Delete 'Húzodzkodás 2") + exerciseTypeRepository.delete(extype) } } \ No newline at end of file From 5c85e2826942fe49c80a1260a999bd9746d82307 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Sun, 24 May 2020 08:21:40 +0200 Subject: [PATCH 82/86] Test case add new exercise. --- .../api/controller/ExerciesController.kt | 5 +-- .../api/controller/ExerciseTypeController.kt | 4 +-- .../com/aitrainer/api/model/Exercises.kt | 3 +- src/main/resources/simplelogger.properties | 34 ------------------- .../com/aitrainer/api/test/ExerciseTest.kt | 15 ++++++++ 5 files changed, 21 insertions(+), 40 deletions(-) delete mode 100644 src/main/resources/simplelogger.properties diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt index c0e8eed..36da824 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt @@ -22,8 +22,9 @@ class ExerciesController(private val exercisesRepository: ExercisesRepository) { exercisesRepository.getAllByCustomerId(customerId) @PostMapping("/exercises") - fun createNewExercise(@Valid @RequestBody exercises: Exercises): Exercises = - exercisesRepository.save(exercises) + fun createNewExercise(@Valid @RequestBody exercise: Exercises): Exercises { + return exercisesRepository.save(exercise) + } @PutMapping("/exercises/{id}") fun updateExerciseById(@PathVariable(value = "id") exerciseId: Long, diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt index b6143b7..85319e1 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeController.kt @@ -16,7 +16,7 @@ class ExerciseTypeController ( private val exerciseTypeRepository: ExerciseTypeR @GetMapping("/exercise_type") fun getAllExerciseType(): List { val list: List = exerciseTypeRepository.findAll() - logger.info("Get All exercise types: $list") + logger.info(" -- Get All exercise types..") return list } @@ -44,7 +44,7 @@ class ExerciseTypeController ( private val exerciseTypeRepository: ExerciseTypeR }.orElse(ResponseEntity.notFound().build()) }*/ - @PutMapping("/exercise_type/{id}") + @PostMapping("/exercise_type/{id}") fun updateExerciseTypesById(@PathVariable(value = "id") exerciseTypeId: Long, @Valid @RequestBody newExerciseType: ExerciseType): ResponseEntity { logger.info("Update exercise type by id: $exerciseTypeId with object $newExerciseType") diff --git a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt index bb2736c..6770c95 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt @@ -1,7 +1,6 @@ package com.aitrainer.api.model import org.springframework.lang.NonNull -import java.sql.Date import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType @@ -12,7 +11,7 @@ import javax.validation.constraints.Null data class Exercises ( @get: NonNull var exerciseTypeId: Long = 0, @get: NonNull var customerId: Long = 0, - @get: NonNull var datetimeExercise: Date? = null, + @get: NonNull var datetimeExercise: String? = null, @get: NonNull var quantity: Int = 0, @get: Null var restTime: Int?, // in seconds diff --git a/src/main/resources/simplelogger.properties b/src/main/resources/simplelogger.properties deleted file mode 100644 index 05ff3de..0000000 --- a/src/main/resources/simplelogger.properties +++ /dev/null @@ -1,34 +0,0 @@ -# SLF4J's SimpleLogger configuration file -# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. - -# Default logging detail level for all instances of SimpleLogger. -# Must be one of ("trace", "debug", "info", "warn", or "error"). -# If not specified, defaults to "info". -org.slf4j.simpleLogger.defaultLogLevel=debug - -# Logging detail level for a SimpleLogger instance named "xxxxx". -# Must be one of ("trace", "debug", "info", "warn", or "error"). -# If not specified, the default logging detail level is used. -#org.slf4j.simpleLogger.log.xxxxx= - -# Set to true if you want the current date and time to be included in output messages. -# Default is false, and will output the number of milliseconds elapsed since startup. -org.slf4j.simpleLogger.showDateTime=true - -# The date and time format to be used in the output messages. -# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. -# If the format is not specified or is invalid, the default format is used. -# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. -#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z - -# Set to true if you want to output the current thread name. -# Defaults to true. -#org.slf4j.simpleLogger.showThreadName=true - -# Set to true if you want the Logger instance name to be included in output messages. -# Defaults to true. -#org.slf4j.simpleLogger.showLogName=true - -# Set to true if you want the last component of the name to be included in output messages. -# Defaults to false. -org.slf4j.simpleLogger.showShortLogName=true \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt index 497423f..3196656 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import kotlin.test.assertEquals +import kotlin.test.assertTrue @SpringBootTest class ExerciseTest { @@ -25,4 +26,18 @@ class ExerciseTest { assertEquals( exercises2.size, 0) } + + @Test + fun testInsert() { + val exercise = Exercises( + exerciseTypeId = 3, + customerId = 11, + quantity = 100, + datetimeExercise = "2020-05-13 04:32:00", + restTime = null + ) + val exerciseNew = exerciseRepository.save(exercise) + assertTrue(exerciseNew.exerciseId > 2) + exerciseRepository.delete(exercise) + } } \ No newline at end of file From 7ef6f50c3bb6cbbe2d8de9cc591c46b041c263d2 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Mon, 1 Jun 2020 09:26:41 +0200 Subject: [PATCH 83/86] v0.0.5: db update, API customer_information introducing AOP --- build.gradle.kts | 12 +- data/db/install.sql | 149 +++-- data/db/install_0_0_1.sql | 115 ++++ data/db/update_0_0_2.sql | 55 ++ docker-compose_cowmail.yml | 631 ++++++++++++++++++ docker-compose_gitlab.yml | 143 ++++ readme.MD | 14 +- .../com/aitrainer/api/ApiApplication.kt | 8 +- .../api/controller/ApplicationProperties.kt | 45 ++ .../api/controller/ConfigurationController.kt | 27 + .../controller/CustomerControllerAspect.kt | 34 + .../CustomerInformationController.kt | 18 + .../api/controller/DatabaseCheckSingleton.kt | 105 +++ ...iesController.kt => ExerciseController.kt} | 4 +- .../controller/ExerciseControllerAspect.kt | 35 + .../ExerciseTypeControllerAspect.kt | 34 + .../com/aitrainer/api/model/Configuration.kt | 21 + .../com/aitrainer/api/model/Customer.kt | 4 + .../api/model/CustomerInformation.kt | 21 + .../com/aitrainer/api/model/Exercises.kt | 3 +- .../api/repository/ConfigurationRepository.kt | 10 + .../CustomerInformationRepository.kt | 11 + src/main/resources/application.properties | 5 +- .../com/aitrainer/api/test/AitrainerDBTest.kt | 49 -- .../api/test/ApplicationConfiguration.kt | 12 - .../api/test/ApplicationStartTest.kt | 34 + .../aitrainer/api/test/ConfigurationTest.kt | 31 + .../api/test/CustomerInformationTest.kt | 24 + .../com/aitrainer/api/test/ExerciseTest.kt | 3 +- .../com/aitrainer/api/test/PropertiesTest.kt | 27 + 30 files changed, 1538 insertions(+), 146 deletions(-) create mode 100644 data/db/install_0_0_1.sql create mode 100644 data/db/update_0_0_2.sql create mode 100644 docker-compose_cowmail.yml create mode 100644 docker-compose_gitlab.yml create mode 100644 src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt create mode 100644 src/main/kotlin/com/aitrainer/api/controller/ConfigurationController.kt create mode 100644 src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt create mode 100644 src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt create mode 100644 src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt rename src/main/kotlin/com/aitrainer/api/controller/{ExerciesController.kt => ExerciseController.kt} (92%) create mode 100644 src/main/kotlin/com/aitrainer/api/controller/ExerciseControllerAspect.kt create mode 100644 src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt create mode 100644 src/main/kotlin/com/aitrainer/api/model/Configuration.kt create mode 100644 src/main/kotlin/com/aitrainer/api/model/CustomerInformation.kt create mode 100644 src/main/kotlin/com/aitrainer/api/repository/ConfigurationRepository.kt create mode 100644 src/main/kotlin/com/aitrainer/api/repository/CustomerInformationRepository.kt delete mode 100644 src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt delete mode 100644 src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/ApplicationStartTest.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/ConfigurationTest.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/CustomerInformationTest.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/PropertiesTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7408179..f5ad0de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,15 +1,16 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { - id("org.springframework.boot") version "2.2.6.RELEASE" + id("org.springframework.boot") version "2.3.0.RELEASE" id("io.spring.dependency-management") version "1.0.9.RELEASE" kotlin("jvm") version "1.3.71" - kotlin("plugin.spring") version "1.3.71" - kotlin("plugin.jpa") version "1.3.71" + kotlin("plugin.spring") version "1.3.72" + kotlin("plugin.jpa") version "1.3.72" } group = "com.aitrainer" -version = "0.0.4" +version = "0.0.5" java.sourceCompatibility = JavaVersion.VERSION_1_8 repositories { @@ -19,6 +20,8 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-aop") + implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") @@ -33,6 +36,7 @@ dependencies { } testImplementation("junit:junit:4.13") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.3.72") + } tasks.withType { diff --git a/data/db/install.sql b/data/db/install.sql index 91e6e7e..08ac149 100644 --- a/data/db/install.sql +++ b/data/db/install.sql @@ -1,6 +1,6 @@ -- -------------------------------------------------------- -- Host: 127.0.0.1 --- Szerver verzió: 8.0.20 - MySQL Community Server - GPL +-- Szerver verzió: 10.4.11-MariaDB - mariadb.org binary distribution -- Szerver OS: Win64 -- HeidiSQL Verzió: 11.0.0.5919 -- -------------------------------------------------------- @@ -10,91 +10,112 @@ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -use aitrainer; - -- Struktúra mentése tábla aitrainer. customer CREATE TABLE IF NOT EXISTS `customer` ( - `customer_id` int NOT NULL AUTO_INCREMENT, - `name` char(100) NOT NULL, - `firstname` char(100) NOT NULL, - `email` char(100) DEFAULT NULL, - `sex` enum('m','w') DEFAULT 'm', - `age` tinyint DEFAULT NULL, - `active` enum('Y','N','D','S') DEFAULT 'N', + `customer_id` int(11) NOT NULL AUTO_INCREMENT, + `name` char(100) COLLATE utf8_hungarian_ci NOT NULL, + `firstname` char(100) COLLATE utf8_hungarian_ci NOT NULL, + `email` char(100) COLLATE utf8_hungarian_ci DEFAULT NULL, + `password` char(100) COLLATE utf8_hungarian_ci DEFAULT NULL, + `sex` enum('m','w') COLLATE utf8_hungarian_ci DEFAULT 'm', + `age` tinyint(4) DEFAULT NULL, + `active` enum('Y','N','D','S') COLLATE utf8_hungarian_ci DEFAULT 'N', + `date_add` datetime DEFAULT NULL, + `date_change` datetime DEFAULT NULL, + `data_policy_allowed` tinyint(4) DEFAULT 1, + `admin` tinyint(4) DEFAULT 0, PRIMARY KEY (`customer_id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; --- Tábla adatainak mentése aitrainer.customer: ~15 rows (hozzávetőleg) +-- Tábla adatainak mentése aitrainer.customer: ~13 rows (hozzávetőleg) /*!40000 ALTER TABLE `customer` DISABLE KEYS */; -INSERT INTO `customer` (`customer_id`, `name`, `firstname`, `email`, `sex`, `age`) VALUES - (1, 'Átlag 13 éves fiú', '', NULL, 'm', 13), - (2, 'Átlag 14 éves fiú', '', NULL, 'm', 14), - (3, 'Átlag 15 éves fiú', '', NULL, 'm', 15), - (4, 'Átlag 15 éves fiú', '', NULL, 'm', 15), - (5, 'Átlag 16 éves fiú', '', NULL, 'm', 16), - (6, 'Átlag 17 éves fiú', '', NULL, 'm', 17), - (7, 'Átlag 18 éves fiú', '', NULL, 'm', 18), - (8, 'Átlag 13 éves lány', '', NULL, 'w', 13), - (9, 'Átlag 14 éves lány', '', NULL, 'w', 14), - (10, 'Átlag 15 éves lány', '', NULL, 'w', 15), - (11, 'Átlag 16 éves lány', '', NULL, 'w', 16), - (12, 'Átlag 17 éves lány', '', NULL, 'w', 17), - (13, 'Átlag 18 éves lány', '', NULL, 'w', 18); +INSERT INTO `customer` (`customer_id`, `name`, `firstname`, `email`, `password`, `sex`, `age`, `active`, `date_add`, `date_change`, `data_policy_allowed`, `admin`) VALUES + (1, 'Átlag 13 éves fiú', '', NULL, NULL, 'm', 13, 'N', NULL, NULL, 1, 0), + (2, 'Átlag 14 éves fiú', '', NULL, NULL, 'm', 14, 'N', NULL, NULL, 1, 0), + (3, 'Átlag 15 éves fiú', '', NULL, NULL, 'm', 15, 'N', NULL, NULL, 1, 0), + (4, 'Átlag 15 éves fiú', '', NULL, NULL, 'm', 15, 'N', NULL, NULL, 1, 0), + (5, 'Átlag 16 éves fiú', '', NULL, NULL, 'm', 16, 'N', NULL, NULL, 1, 0), + (6, 'Átlag 17 éves fiú', '', NULL, NULL, 'm', 17, 'N', NULL, NULL, 1, 0), + (7, 'Átlag 18 éves fiú', '', NULL, NULL, 'm', 18, 'N', NULL, NULL, 1, 0), + (8, 'Átlag 13 éves lány', '', NULL, NULL, 'w', 13, 'N', NULL, NULL, 1, 0), + (9, 'Átlag 14 éves lány', '', NULL, NULL, 'w', 14, 'N', NULL, NULL, 1, 0), + (10, 'Átlag 15 éves lány', '', NULL, NULL, 'w', 15, 'N', NULL, NULL, 1, 0), + (11, 'Átlag 16 éves lány', '', NULL, NULL, 'w', 16, 'N', NULL, NULL, 1, 0), + (12, 'Átlag 17 éves lány', '', NULL, NULL, 'w', 17, 'N', NULL, NULL, 1, 0), + (13, 'Átlag 18 éves lány', '', NULL, NULL, 'w', 18, 'N', NULL, NULL, 1, 0); /*!40000 ALTER TABLE `customer` ENABLE KEYS */; +-- Struktúra mentése tábla aitrainer. customer_information +CREATE TABLE IF NOT EXISTS `customer_information` ( + `customer_information_id` int(11) NOT NULL AUTO_INCREMENT, + `title` char(50) COLLATE utf8_hungarian_ci DEFAULT '', + `description` mediumtext COLLATE utf8_hungarian_ci DEFAULT NULL, + `date_add` datetime DEFAULT NULL, + `display_begin` datetime DEFAULT NULL, + `display_end` datetime DEFAULT NULL, + PRIMARY KEY (`customer_information_id`) USING BTREE, + KEY `title` (`title`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.customer_information: ~0 rows (hozzávetőleg) +/*!40000 ALTER TABLE `customer_information` DISABLE KEYS */; +/*!40000 ALTER TABLE `customer_information` ENABLE KEYS */; + +INSERT INTO `customer_information` (`customer_information_id`, `title`, `description`, `date_add`, `display_begin`, `display_end`) VALUES (1, 'Fekvőtámasz világcsúcs', 'Világcsúcs fekvőtámasz: KJ Joseph 1 perc alatt 82 szabályos fekvőtámaszt végzett', '2020-06-01 08:00:00', '2020-06-01 08:00:00', '2023-07-01 08:00:00'); +INSERT INTO `customer_information` (`customer_information_id`, `title`, `description`, `date_add`, `display_begin`, `display_end`) VALUES (2, 'Húzódszkodás csúcs', '24 órás csúcstartója Joonas Mäkipelto 5050 gyakorlattal', '2020-06-01 08:00:00', '2020-06-01 08:00:00', '2023-07-01 08:00:00'); +INSERT INTO `customer_information` (`customer_information_id`, `title`, `description`, `date_add`, `display_begin`, `display_end`) VALUES (3, 'Fekvenyomás', '2015-ben a fekvenyomó világbajnokságot Smulter Fredrik finn súlyemelő 401 Kg-al nyerte', '2020-06-01 08:00:00', '2020-05-01 00:00:00', '2020-06-01 08:00:01'); + + -- Struktúra mentése tábla aitrainer. exercises CREATE TABLE IF NOT EXISTS `exercises` ( - `exercise_id` int NOT NULL AUTO_INCREMENT, - `exercise_type_id` int NOT NULL, - `customer_id` int NOT NULL, - `datetime_exercise` datetime NOT NULL, - `quantity` int DEFAULT NULL, - `rest_time` int DEFAULT NULL COMMENT 'in sec', + `exercise_id` int(11) NOT NULL AUTO_INCREMENT, + `exercise_type_id` int(11) NOT NULL, + `customer_id` int(11) NOT NULL, + `date_add` datetime NOT NULL, + `quantity` float DEFAULT NULL, + `unit` enum('kg','meter','repeat','minute') COLLATE utf8_hungarian_ci DEFAULT 'repeat', + `rest_time` int(11) DEFAULT NULL COMMENT 'in sec', PRIMARY KEY (`exercise_id`), - KEY `exercise_type_id` (`exercise_type_id`) + KEY `exercise_type_id` (`exercise_type_id`), + KEY `customer_id` (`customer_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; --- Tábla adatainak mentése aitrainer.exercises: ~1 rows (hozzávetőleg) +-- Tábla adatainak mentése aitrainer.exercises: ~0 rows (hozzávetőleg) /*!40000 ALTER TABLE `exercises` DISABLE KEYS */; -INSERT INTO `exercises` (`exercise_id`, `exercise_type_id`, `customer_id`, `datetime_exercise`, `quantity`, `rest_time`) VALUES - (1, 1, 1, '2020-05-01 00:00:00', 12, NULL); +INSERT INTO `exercises` (`exercise_id`, `exercise_type_id`, `customer_id`, `date_add`, `quantity`, `unit`, `rest_time`) VALUES + (1, 1, 1, '2020-05-01 00:00:00', 12, 'repeat', NULL); /*!40000 ALTER TABLE `exercises` ENABLE KEYS */; --- Struktúra mentése tábla aitrainer. exercise_ages -CREATE TABLE IF NOT EXISTS `exercise_ages` ( - `exercise_age_id` int NOT NULL AUTO_INCREMENT, - `exercise_type_id` int NOT NULL, - `name` char(100) NOT NULL, - `sex` enum('m','w') DEFAULT 'm', - `age` tinyint DEFAULT NULL, - `min_exercises` int DEFAULT NULL, - `avg_exercises` int DEFAULT NULL, - `max_exercises` int DEFAULT NULL, - PRIMARY KEY (`exercise_age_id`), - UNIQUE KEY `exercise_type_id_2` (`exercise_type_id`,`sex`,`age`) -) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; +-- Struktúra mentése tábla aitrainer. exercise_evaluation +CREATE TABLE IF NOT EXISTS `exercise_evaluation` ( + `evaluation_id` int(11) NOT NULL AUTO_INCREMENT, + `age_min` int(11) DEFAULT 0, + `age_max` int(11) DEFAULT 0, + `value_min` int(11) DEFAULT 0, + `value_max` int(11) DEFAULT 0, + `sex` enum('m','w') COLLATE utf8_hungarian_ci NOT NULL DEFAULT 'm', + `evaluation` enum('excellent','very good','good','average','weak','poor') COLLATE utf8_hungarian_ci NOT NULL DEFAULT 'average', + `description` mediumtext COLLATE utf8_hungarian_ci DEFAULT NULL, + PRIMARY KEY (`evaluation_id`) USING BTREE, + KEY `value_min_value_max` (`value_min`,`value_max`) USING BTREE, + KEY `age_min_age_max` (`age_min`,`age_max`) USING BTREE, + KEY `age_min_age_max_value_min_value_max` (`age_min`,`age_max`,`value_min`,`value_max`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; --- Tábla adatainak mentése aitrainer.exercise_ages: ~6 rows (hozzávetőleg) -/*!40000 ALTER TABLE `exercise_ages` DISABLE KEYS */; -INSERT INTO `exercise_ages` (`exercise_age_id`, `exercise_type_id`, `name`, `sex`, `age`, `min_exercises`, `avg_exercises`, `max_exercises`) VALUES - (1, 1, '', 'm', 13, 12, NULL, NULL), - (2, 1, '', 'm', 14, 14, NULL, NULL), - (3, 1, '', 'm', 15, 16, NULL, NULL), - (4, 1, '', 'm', 16, 18, NULL, NULL), - (7, 1, '', 'm', 17, 18, NULL, NULL), - (8, 1, '', 'm', 18, 18, NULL, NULL); -/*!40000 ALTER TABLE `exercise_ages` ENABLE KEYS */; +-- Tábla adatainak mentése aitrainer.exercise_evaluation: ~0 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercise_evaluation` DISABLE KEYS */; +/*!40000 ALTER TABLE `exercise_evaluation` ENABLE KEYS */; --- Struktúra mentése tábla aitrainer. exercise_types +-- Struktúra mentése tábla aitrainer. exercise_type CREATE TABLE IF NOT EXISTS `exercise_type` ( - `exercise_type_id` int NOT NULL AUTO_INCREMENT, - `name` char(100) NOT NULL, - `description` varchar(1000) DEFAULT NULL, - `video` mediumblob, + `exercise_type_id` int(11) NOT NULL AUTO_INCREMENT, + `name` char(100) COLLATE utf8_hungarian_ci NOT NULL, + `description` varchar(1000) COLLATE utf8_hungarian_ci DEFAULT NULL, + `video` mediumblob DEFAULT NULL, PRIMARY KEY (`exercise_type_id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; --- Tábla adatainak mentése aitrainer.exercise_types: ~11 rows (hozzávetőleg) +-- Tábla adatainak mentése aitrainer.exercise_type: ~11 rows (hozzávetőleg) /*!40000 ALTER TABLE `exercise_type` DISABLE KEYS */; INSERT INTO `exercise_type` (`exercise_type_id`, `name`, `description`, `video`) VALUES (1, 'Melső fekvőtámasz 1 perc', 'Ezt igazolja a 2016 márciusában beállított guinness rekord is,\r\namelyben KJ Joseph 1 perc alatt 82 szabályos karhajlítás-nyújtást\r\nvégzett.', NULL), diff --git a/data/db/install_0_0_1.sql b/data/db/install_0_0_1.sql new file mode 100644 index 0000000..36f5ee2 --- /dev/null +++ b/data/db/install_0_0_1.sql @@ -0,0 +1,115 @@ +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Szerver verzió: 8.0.20 - MySQL Community Server - GPL +-- Szerver OS: Win64 +-- HeidiSQL Verzió: 11.0.0.5919 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; + +use aitrainer; + +-- Struktúra mentése tábla aitrainer. customer +CREATE TABLE IF NOT EXISTS `customer` ( + `customer_id` int NOT NULL AUTO_INCREMENT, + `name` char(100) NOT NULL, + `firstname` char(100) NOT NULL, + `email` char(100) DEFAULT NULL, + `sex` enum('m','w') DEFAULT 'm', + `age` tinyint DEFAULT NULL, + `active` enum('Y','N','D','S') DEFAULT 'N', + PRIMARY KEY (`customer_id`) +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.customer: ~15 rows (hozzávetőleg) +/*!40000 ALTER TABLE `customer` DISABLE KEYS */; +INSERT INTO `customer` (`customer_id`, `name`, `firstname`, `email`, `sex`, `age`) VALUES + (1, 'Átlag 13 éves fiú', '', NULL, 'm', 13), + (2, 'Átlag 14 éves fiú', '', NULL, 'm', 14), + (3, 'Átlag 15 éves fiú', '', NULL, 'm', 15), + (4, 'Átlag 15 éves fiú', '', NULL, 'm', 15), + (5, 'Átlag 16 éves fiú', '', NULL, 'm', 16), + (6, 'Átlag 17 éves fiú', '', NULL, 'm', 17), + (7, 'Átlag 18 éves fiú', '', NULL, 'm', 18), + (8, 'Átlag 13 éves lány', '', NULL, 'w', 13), + (9, 'Átlag 14 éves lány', '', NULL, 'w', 14), + (10, 'Átlag 15 éves lány', '', NULL, 'w', 15), + (11, 'Átlag 16 éves lány', '', NULL, 'w', 16), + (12, 'Átlag 17 éves lány', '', NULL, 'w', 17), + (13, 'Átlag 18 éves lány', '', NULL, 'w', 18); +/*!40000 ALTER TABLE `customer` ENABLE KEYS */; + +-- Struktúra mentése tábla aitrainer. exercises +CREATE TABLE IF NOT EXISTS `exercises` ( + `exercise_id` int NOT NULL AUTO_INCREMENT, + `exercise_type_id` int NOT NULL, + `customer_id` int NOT NULL, + `datetime_exercise` datetime NOT NULL, + `quantity` float DEFAULT NULL, + `rest_time` int DEFAULT NULL COMMENT 'in sec', + PRIMARY KEY (`exercise_id`), + KEY `exercise_type_id` (`exercise_type_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.exercises: ~1 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercises` DISABLE KEYS */; +INSERT INTO `exercises` (`exercise_id`, `exercise_type_id`, `customer_id`, `datetime_exercise`, `quantity`, `rest_time`) VALUES + (1, 1, 1, '2020-05-01 00:00:00', 12, NULL); +/*!40000 ALTER TABLE `exercises` ENABLE KEYS */; + +-- Struktúra mentése tábla aitrainer. exercise_ages +CREATE TABLE IF NOT EXISTS `exercise_ages` ( + `exercise_age_id` int NOT NULL AUTO_INCREMENT, + `exercise_type_id` int NOT NULL, + `name` char(100) NOT NULL, + `sex` enum('m','w') DEFAULT 'm', + `age` tinyint DEFAULT NULL, + `min_exercises` int DEFAULT NULL, + `avg_exercises` int DEFAULT NULL, + `max_exercises` int DEFAULT NULL, + PRIMARY KEY (`exercise_age_id`), + UNIQUE KEY `exercise_type_id_2` (`exercise_type_id`,`sex`,`age`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.exercise_ages: ~6 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercise_ages` DISABLE KEYS */; +INSERT INTO `exercise_ages` (`exercise_age_id`, `exercise_type_id`, `name`, `sex`, `age`, `min_exercises`, `avg_exercises`, `max_exercises`) VALUES + (1, 1, '', 'm', 13, 12, NULL, NULL), + (2, 1, '', 'm', 14, 14, NULL, NULL), + (3, 1, '', 'm', 15, 16, NULL, NULL), + (4, 1, '', 'm', 16, 18, NULL, NULL), + (7, 1, '', 'm', 17, 18, NULL, NULL), + (8, 1, '', 'm', 18, 18, NULL, NULL); +/*!40000 ALTER TABLE `exercise_ages` ENABLE KEYS */; + +-- Struktúra mentése tábla aitrainer. exercise_types +CREATE TABLE IF NOT EXISTS `exercise_type` ( + `exercise_type_id` int NOT NULL AUTO_INCREMENT, + `name` char(100) NOT NULL, + `description` varchar(1000) DEFAULT NULL, + `video` mediumblob, + PRIMARY KEY (`exercise_type_id`) +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; + +-- Tábla adatainak mentése aitrainer.exercise_types: ~11 rows (hozzávetőleg) +/*!40000 ALTER TABLE `exercise_type` DISABLE KEYS */; +INSERT INTO `exercise_type` (`exercise_type_id`, `name`, `description`, `video`) VALUES + (1, 'Melső fekvőtámasz 1 perc', 'Ezt igazolja a 2016 márciusában beállított guinness rekord is,\r\namelyben KJ Joseph 1 perc alatt 82 szabályos karhajlítás-nyújtást\r\nvégzett.', NULL), + (2, 'Húzódzkodás', 'Ennek a gyakorlatnak a 24 órás csúcstartója Joonas Mäkipelto 5050\r\ngyakorlattal.', NULL), + (3, 'Melső fekvőtámasz 30mp', 'A gyakorlatot 30 másodperc alatt olyan sokszor kell végrehajtani, ahányszor a\r\nfelvételiző képes rá. Azonban törekedni kell a szabályos végrehajtásra, ugyanis csak azokat\r\nszámolják. A nők esetében 20, a férfiak esetében 35 gyakorlatot kell végrehajtani a maximális\r\npont megszerzéséért. A gyakorlat akkor sikeres, ha a női legalább 1, a férfi felvételiző\r\nlegalább 11 gyakorlatot képes végrehajtani.', NULL), + (4, 'Melső fekvőtámasz 2perc', 'Magyar Honvédség: A gyakorlat végrehajtására 2 perc áll rendelkezésre. Ennek során csak a szabályosan a\r\nfentiekben leírt módon végrehajtott gyakorlat értékelhető. Férfiaknál 70 karhajlítás nyújtást\r\nkell végrehajtani a maximális pontért.', NULL), + (5, 'Hajlított karú függés', 'A gyakorlat addig tart, amíg a végrehajtó szemmagassága a kiinduló helyzettől\r\nsüllyedve a keresztvas alá nem kerül. Az értékeléshez stopperórát alkalmaznak, és az\r\neredmény másodperc pontossággal kerül megállapításra. Nők esetében 45, férfiak\r\ntekintetében 73 másodperctől jár a maximális pontszám. A gyakorlat sikeres végrehajtásához\r\nlegalább 8, 10 másodpercig kell megtartaniuk a kiinduló helyzetet a női és a férfi\r\nfelvételizőknek.\r\n\r\nA NKE-RTK-án lévő hallgatók egyéni rekordjai Iván Viktor 90s, Kiss Regina 74s', NULL), + (6, 'Fekvenyomás', '2015-ben a fekvenyomó világbajnokságot Smulter Fredrik finn súlyemelő 401 Kg-al nyerte.\r\nA súlyzó tömege nők esetében 25 kg, a férfiak esetében 60 kg a rúddal együtt. Az\r\nértékelésénél a szabályosan végrehajtott gyakorlatokat értékelik csak.\r\nA legtöbb pontért 25 gyakorlatot kell végezni mind a nőknek, mind a férfiaknak. A minimum:\r\negy gyakorlat mindkét nem esetében.', NULL), + (7, '4x10m-es ingafutás', 'A legjobb pontszámért 9,4 s illetve 8,8s alatt kell teljesíteni a nőknek, férfiaknak. A\r\ngyakorlat sikertelen 11,8 s illetve 11,2 s-on túl.', NULL), + (8, 'Helyből távolugrás', 'Byron Jones 2015-ben a 3,73 méteres ugrásával érte el a világcsúcsot.\r\nA nőknek 220 cm-re, a férfiak 250 cm-re kell ugraniuk a maximális pontért. A\r\nminimális távolság 172cm illetve 198 cm.', NULL), + (9, 'Felülés hanyattfekvésből', 'Az NKE-RTK hallgatói közül Papp Zsófia 66 db-ot, Gál Valentin 80\r\ndb-ot csinált 1 perc leforgása alatt.\r\nElső ütemre megtörténik a felülés, ami akkor szabályos, ha valamelyik könyök érinti a\r\ntérdet. Második ütemre vissza kell térni a kiinduló helyzetbe. A maximális pont eléréséhez 1\r\nperc alatt 45 illetve 55 ismétlést kell végezni a nőknek illetve a férfiaknak. A minimumhoz 7\r\nés 25 ismétlés szükséges.', NULL), + (10, 'Felülés hajlított térddel', 'Magyar Honvédség: A kiinduló helyzet hajlított ülés, ennek során a sarkak a talajon, a térd 90 fokban meghajlítva\r\nvan.\r\nA gyakorlat végrehajtására két perc áll rendelkezésre. Ennek során csak a szabályosan,\r\na fentiekben leírt módon végrehajtott gyakorlat értékelhető. Férfiaknál, nőknél egyaránt 90\r\ngyakorlatot kell végrehajtani a maximális pontért. Amennyiben a megadott időkeret alatt nem\r\nsikerül legalább 25 szabályos gyakorlatot végrehajtani, úgy az sikertelennek minősül.', NULL), + (11, 'Síkfutás 2000m', 'A maximálisan megszerezhető pontot az a felvételiző gyűjtheti be, aki a távot nők\r\nesetében 10:00 perc alatt, férfiak esetében 7:35 perc alatt teljesíti. A gyakorlat sikeres\r\nteljesítésére nők esetében maximum 16:00 perc, férfiak esetében 13:30 perc áll rendelkezésre.', NULL); +/*!40000 ALTER TABLE `exercise_type` ENABLE KEYS */; + +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/data/db/update_0_0_2.sql b/data/db/update_0_0_2.sql new file mode 100644 index 0000000..0f8d31d --- /dev/null +++ b/data/db/update_0_0_2.sql @@ -0,0 +1,55 @@ +ALTER TABLE `exercises` +ADD COLUMN `unit` ENUM('kg','meter','repeat','minute') NULL DEFAULT 'repeat' AFTER `quantity`, +CHANGE COLUMN `quantity` `quantity` FLOAT NULL DEFAULT NULL AFTER `datetime_exercise`; + +ALTER TABLE `exercises` +CHANGE COLUMN `datetime_exercise` `date_add` DATETIME NOT NULL AFTER `customer_id`, +ADD INDEX `customer_id` (`customer_id`); + +ALTER TABLE `customer` + ADD COLUMN `password` CHAR(100) NULL DEFAULT NULL AFTER `email`, + ADD COLUMN `date_add` DATETIME NULL AFTER `active`, + ADD COLUMN `date_change` DATETIME NULL AFTER `date_add`, + ADD COLUMN `data_policy_allowed` TINYINT NULL DEFAULT '1' AFTER `date_change`, + ADD COLUMN `admin` TINYINT NULL DEFAULT '0' AFTER `data_policy_allowed`; + +CREATE TABLE `exercise_evaluation` ( + `evaluation_id` INT(11) NOT NULL AUTO_INCREMENT, + `age_min` INT(11) NULL DEFAULT '0', + `age_max` INT(11) NULL DEFAULT '0', + `value_min` INT(11) NULL DEFAULT '0', + `value_max` INT(11) NULL DEFAULT '0', + `sex` ENUM('m','w') NOT NULL DEFAULT 'm' COLLATE 'utf8_hungarian_ci', + `evaluation` ENUM('excellent','very good','good','average','weak','poor') NOT NULL DEFAULT 'average' COLLATE 'utf8_hungarian_ci', + `description` TEXT(65535) NULL DEFAULT NULL COLLATE 'utf8_hungarian_ci', + PRIMARY KEY (`evaluation_id`) USING BTREE, + INDEX `value_min_value_max` (`value_min`, `value_max`) USING BTREE, + INDEX `age_min_age_max` (`age_min`, `age_max`) USING BTREE, + INDEX `age_min_age_max_value_min_value_max` (`age_min`, `age_max`, `value_min`, `value_max`) USING BTREE +) +COLLATE='utf8_hungarian_ci' +ENGINE=InnoDB +; + +CREATE TABLE `customer_information` ( + `customer_information_id` INT(11) NOT NULL AUTO_INCREMENT, + `title` CHAR(50) NULL DEFAULT '' COLLATE 'utf8_hungarian_ci', + `description` TEXT(65535) NULL DEFAULT NULL COLLATE 'utf8_hungarian_ci', + `date_add` DATETIME NULL DEFAULT NULL, + `display_begin` DATETIME NULL DEFAULT NULL, + `display_end` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`customer_information_id`) USING BTREE, + INDEX `title` (`title`) USING BTREE +) +COLLATE='utf8_hungarian_ci' +ENGINE=InnoDB +; + +INSERT INTO `customer_information` (`customer_information_id`, `title`, `description`, `date_add`, `display_begin`, `display_end`) VALUES (1, 'Fekvőtámasz világcsúcs', 'Világcsúcs fekvőtámasz: KJ Joseph 1 perc alatt 82 szabályos fekvőtámaszt végzett', '2020-06-01 08:00:00', '2020-06-01 08:00:00', '2023-07-01 08:00:00'); +INSERT INTO `customer_information` (`customer_information_id`, `title`, `description`, `date_add`, `display_begin`, `display_end`) VALUES (2, 'Húzódszkodás csúcs', '24 órás csúcstartója Joonas Mäkipelto 5050 gyakorlattal', '2020-06-01 08:00:00', '2020-06-01 08:00:00', '2023-07-01 08:00:00'); +INSERT INTO `customer_information` (`customer_information_id`, `title`, `description`, `date_add`, `display_begin`, `display_end`) VALUES (3, 'Fekvenyomás', '2015-ben a fekvenyomó világbajnokságot Smulter Fredrik finn súlyemelő 401 Kg-al nyerte', '2020-06-01 08:00:00', '2020-05-01 00:00:00', '2020-06-01 08:00:01'); + +DROP TABLE exercise_ages; + +UPDATE configuration set config_value = "0.0.2" WHERE config_key = "db_version"; + diff --git a/docker-compose_cowmail.yml b/docker-compose_cowmail.yml new file mode 100644 index 0000000..8e424e8 --- /dev/null +++ b/docker-compose_cowmail.yml @@ -0,0 +1,631 @@ +version: '2.1' +services: + + unbound-mailcow: + image: mailcow/unbound:1.12 + environment: + - TZ=${TZ} + volumes: + - ./data/hooks/unbound:/hooks + - ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro + restart: always + tty: true + networks: + mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.254 + aliases: + - unbound + + mysql-mailcow: + image: mariadb:10.3 + depends_on: + - unbound-mailcow + stop_grace_period: 45s + volumes: + - mysql-vol-1:/var/lib/mysql/ + - mysql-socket-vol-1:/var/run/mysqld/ + - ./data/conf/mysql/:/etc/mysql/conf.d/:ro + environment: + - TZ=${TZ} + - MYSQL_ROOT_PASSWORD=${DBROOT} + - MYSQL_DATABASE=${DBNAME} + - MYSQL_USER=${DBUSER} + - MYSQL_PASSWORD=${DBPASS} + - MYSQL_INITDB_SKIP_TZINFO=1 + restart: always + ports: + - "${SQL_PORT:-127.0.0.1:13306}:3306" + networks: + mailcow-network: + aliases: + - mysql + + redis-mailcow: + image: redis:5-alpine + volumes: + - redis-vol-1:/data/ + restart: always + ports: + - "${REDIS_PORT:-127.0.0.1:7654}:6379" + environment: + - TZ=${TZ} + networks: + mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.249 + aliases: + - redis + + clamd-mailcow: + image: mailcow/clamd:1.36 + restart: always + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + environment: + - TZ=${TZ} + - SKIP_CLAMD=${SKIP_CLAMD:-n} + volumes: + - ./data/conf/clamav/:/etc/clamav/ + networks: + mailcow-network: + aliases: + - clamd + + rspamd-mailcow: + image: mailcow/rspamd:1.68 + stop_grace_period: 30s + depends_on: + - nginx-mailcow + - dovecot-mailcow + environment: + - TZ=${TZ} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - IPV6_NETWORK=${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + volumes: + - ./data/hooks/rspamd:/hooks + - ./data/conf/rspamd/custom/:/etc/rspamd/custom + - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d + - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d + - ./data/conf/rspamd/plugins.d/:/etc/rspamd/plugins.d + - ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro + - ./data/conf/rspamd/rspamd.conf.local:/etc/rspamd/rspamd.conf.local + - ./data/conf/rspamd/rspamd.conf.override:/etc/rspamd/rspamd.conf.override + - rspamd-vol-1:/var/lib/rspamd + restart: always + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + hostname: rspamd + networks: + mailcow-network: + aliases: + - rspamd + + php-fpm-mailcow: + image: mailcow/phpfpm:1.63 + command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" + depends_on: + - redis-mailcow + volumes: + - ./data/hooks/phpfpm:/hooks + - ./data/web:/web:rw + - ./data/conf/rspamd/dynmaps:/dynmaps:ro + - ./data/conf/rspamd/custom/:/rspamd_custom_maps + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ + - ./data/conf/sogo/:/etc/sogo/ + - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro + - ./data/conf/phpfpm/sogo-sso/:/etc/sogo-sso/ + - ./data/conf/phpfpm/php-fpm.d/pools.conf:/usr/local/etc/php-fpm.d/z-pools.conf + - ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini + - ./data/conf/phpfpm/php-conf.d/upload.ini:/usr/local/etc/php/conf.d/upload.ini + - ./data/conf/phpfpm/php-conf.d/other.ini:/usr/local/etc/php/conf.d/zzz-other.ini + - ./data/conf/dovecot/global_sieve_before:/global_sieve/before + - ./data/conf/dovecot/global_sieve_after:/global_sieve/after + - ./data/assets/templates:/tpls + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + environment: + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + - LOG_LINES=${LOG_LINES:-9999} + - TZ=${TZ} + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - IMAP_PORT=${IMAP_PORT:-143} + - IMAPS_PORT=${IMAPS_PORT:-993} + - POP_PORT=${POP_PORT:-110} + - POPS_PORT=${POPS_PORT:-995} + - SIEVE_PORT=${SIEVE_PORT:-4190} + - SUBMISSION_PORT=${SUBMISSION_PORT:-587} + - SMTPS_PORT=${SMTPS_PORT:-465} + - SMTP_PORT=${SMTP_PORT:-25} + - API_KEY=${API_KEY:-invalid} + - API_KEY_READ_ONLY=${API_KEY_READ_ONLY:-invalid} + - API_ALLOW_FROM=${API_ALLOW_FROM:-invalid} + - COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized} + - SKIP_SOLR=${SKIP_SOLR:-y} + - SKIP_CLAMD=${SKIP_CLAMD:-n} + - SKIP_SOGO=${SKIP_SOGO:-n} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + - MASTER=${MASTER:-y} + restart: always + networks: + mailcow-network: + aliases: + - phpfpm + + sogo-mailcow: + image: mailcow/sogo:1.74 + environment: + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - TZ=${TZ} + - LOG_LINES=${LOG_LINES:-9999} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - ACL_ANYONE=${ACL_ANYONE:-disallow} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - SOGO_EXPIRE_SESSION=${SOGO_EXPIRE_SESSION:-480} + - SKIP_SOGO=${SKIP_SOGO:-n} + - MASTER=${MASTER:-y} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + volumes: + - ./data/conf/sogo/:/etc/sogo/ + - ./data/web/inc/init_db.inc.php:/init_db.inc.php + - ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js + - mysql-socket-vol-1:/var/run/mysqld/ + - sogo-web-vol-1:/sogo_web + - sogo-userdata-backup-vol-1:/sogo_backup + restart: always + networks: + mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.248 + aliases: + - sogo + + dovecot-mailcow: + image: mailcow/dovecot:1.125 + depends_on: + - mysql-mailcow + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + cap_add: + - NET_BIND_SERVICE + volumes: + - ./data/hooks/dovecot:/hooks + - ./data/conf/dovecot:/etc/dovecot + - ./data/assets/ssl:/etc/ssl/mail/:ro + - ./data/conf/sogo/:/etc/sogo/ + - ./data/conf/phpfpm/sogo-sso/:/etc/phpfpm/ + - vmail-vol-1:/var/vmail + - vmail-attachments-vol-1:/var/attachments + - crypt-vol-1:/mail_crypt/ + - ./data/conf/rspamd/custom/:/etc/rspamd/custom + - ./data/assets/templates:/templates + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ + environment: + - LOG_LINES=${LOG_LINES:-9999} + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - TZ=${TZ} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440} + - ACL_ANYONE=${ACL_ANYONE:-disallow} + - SKIP_SOLR=${SKIP_SOLR:-y} + - MAILDIR_SUB=${MAILDIR_SUB:-} + - MASTER=${MASTER:-y} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + ports: + - "${DOVEADM_PORT:-127.0.0.1:19991}:12345" + - "${IMAP_PORT:-143}:143" + - "${IMAPS_PORT:-993}:993" + - "${POP_PORT:-110}:110" + - "${POPS_PORT:-995}:995" + - "${SIEVE_PORT:-4190}:4190" + restart: always + tty: true + ulimits: + nproc: 65535 + nofile: + soft: 20000 + hard: 40000 + hostname: ${MAILCOW_HOSTNAME} + networks: + mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.250 + aliases: + - dovecot + + postfix-mailcow: + image: mailcow/postfix:1.49 + depends_on: + - mysql-mailcow + volumes: + - ./data/hooks/postfix:/hooks + - ./data/conf/postfix:/opt/postfix/conf + - ./data/assets/ssl:/etc/ssl/mail/:ro + - postfix-vol-1:/var/spool/postfix + - crypt-vol-1:/var/lib/zeyple + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ + environment: + - LOG_LINES=${LOG_LINES:-9999} + - TZ=${TZ} + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + cap_add: + - NET_BIND_SERVICE + ports: + - "${SMTP_PORT:-25}:25" + - "${SMTPS_PORT:-465}:465" + - "${SUBMISSION_PORT:-587}:587" + restart: always + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + hostname: ${MAILCOW_HOSTNAME} + networks: + mailcow-network: + aliases: + - postfix + + memcached-mailcow: + image: memcached:alpine + restart: always + environment: + - TZ=${TZ} + networks: + mailcow-network: + aliases: + - memcached + + nginx-mailcow: + depends_on: + - sogo-mailcow + - php-fpm-mailcow + - redis-mailcow + image: nginx:mainline-alpine + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active && + envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && + envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && + envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && + envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && + . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_proxy_auth.active && + . /etc/nginx/conf.d/templates/sites.template.sh > /etc/nginx/conf.d/sites.active && + nginx -qt && + until ping phpfpm -c1 > /dev/null; do sleep 1; done && + until ping sogo -c1 > /dev/null; do sleep 1; done && + until ping redis -c1 > /dev/null; do sleep 1; done && + until ping rspamd -c1 > /dev/null; do sleep 1; done && + exec nginx -g 'daemon off;'" + environment: + - HTTPS_PORT=${HTTPS_PORT:-443} + - HTTP_PORT=${HTTP_PORT:-80} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - TZ=${TZ} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + volumes: + - ./data/web:/web:ro + - ./data/conf/rspamd/dynmaps:/dynmaps:ro + - ./data/assets/ssl/:/etc/ssl/mail/:ro + - ./data/conf/nginx/:/etc/nginx/conf.d/:rw + - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro + - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/ + ports: + - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" + - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}" + restart: always + networks: + mailcow-network: + aliases: + - nginx + + acme-mailcow: + depends_on: + - nginx-mailcow + image: mailcow/acme:1.70 + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + environment: + - LOG_LINES=${LOG_LINES:-9999} + - ADDITIONAL_SAN=${ADDITIONAL_SAN} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} + - ENABLE_SSL_SNI=${ENABLE_SSL_SNI:-n} + - SKIP_IP_CHECK=${SKIP_IP_CHECK:-n} + - SKIP_HTTP_VERIFICATION=${SKIP_HTTP_VERIFICATION:-n} + - ONLY_MAILCOW_HOSTNAME=${ONLY_MAILCOW_HOSTNAME:-n} + - LE_STAGING=${LE_STAGING:-n} + - TZ=${TZ} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + - SNAT_TO_SOURCE=${SNAT_TO_SOURCE:-n} + - SNAT6_TO_SOURCE=${SNAT6_TO_SOURCE:-n} + volumes: + - ./data/web/.well-known/acme-challenge:/var/www/acme:rw + - ./data/assets/ssl:/var/lib/acme/:rw + - ./data/assets/ssl-example:/var/lib/ssl-example/:ro + - mysql-socket-vol-1:/var/run/mysqld/ + restart: always + networks: + mailcow-network: + aliases: + - acme + + netfilter-mailcow: + image: mailcow/netfilter:1.36 + stop_grace_period: 30s + depends_on: + - dovecot-mailcow + - postfix-mailcow + - sogo-mailcow + - php-fpm-mailcow + - redis-mailcow + restart: always + privileged: true + environment: + - TZ=${TZ} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - IPV6_NETWORK=${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64} + - SNAT_TO_SOURCE=${SNAT_TO_SOURCE:-n} + - SNAT6_TO_SOURCE=${SNAT6_TO_SOURCE:-n} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + network_mode: "host" + volumes: + - /lib/modules:/lib/modules:ro + + watchdog-mailcow: + image: mailcow/watchdog:1.77 + # Debug + #command: /watchdog.sh + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + volumes: + - rspamd-vol-1:/var/lib/rspamd + - mysql-socket-vol-1:/var/run/mysqld/ + - ./data/assets/ssl:/etc/ssl/mail/:ro + restart: always + environment: + - IPV6_NETWORK=${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64} + - LOG_LINES=${LOG_LINES:-9999} + - TZ=${TZ} + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - DBROOT=${DBROOT} + - USE_WATCHDOG=${USE_WATCHDOG:-n} + - WATCHDOG_NOTIFY_EMAIL=${WATCHDOG_NOTIFY_EMAIL} + - WATCHDOG_NOTIFY_BAN=${WATCHDOG_NOTIFY_BAN:-y} + - WATCHDOG_EXTERNAL_CHECKS=${WATCHDOG_EXTERNAL_CHECKS:-n} + - WATCHDOG_MYSQL_REPLICATION_CHECKS=${WATCHDOG_MYSQL_REPLICATION_CHECKS:-n} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} + - IP_BY_DOCKER_API=${IP_BY_DOCKER_API:-0} + - CHECK_UNBOUND=${CHECK_UNBOUND:-1} + - SKIP_CLAMD=${SKIP_CLAMD:-n} + - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} + - SKIP_SOGO=${SKIP_SOGO:-n} + - HTTPS_PORT=${HTTPS_PORT:-443} + - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} + - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + - EXTERNAL_CHECKS_THRESHOLD=1 + - NGINX_THRESHOLD=5 + - UNBOUND_THRESHOLD=5 + - REDIS_THRESHOLD=5 + - MYSQL_THRESHOLD=5 + - MYSQL_REPLICATION_THRESHOLD=1 + - SOGO_THRESHOLD=3 + - POSTFIX_THRESHOLD=8 + - CLAMD_THRESHOLD=15 + - DOVECOT_THRESHOLD=12 + - DOVECOT_REPL_THRESHOLD=2 + - PHPFPM_THRESHOLD=5 + - RATELIMIT_THRESHOLD=1 + - FAIL2BAN_THRESHOLD=1 + - ACME_THRESHOLD=1 + - IPV6NAT_THRESHOLD=1 + - RSPAMD_THRESHOLD=5 + - OLEFY_THRESHOLD=5 + networks: + mailcow-network: + aliases: + - watchdog + + dockerapi-mailcow: + image: mailcow/dockerapi:1.37 + restart: always + oom_kill_disable: true + dns: + - ${IPV4_NETWORK:-172.22.1}.254 + environment: + - DBROOT=${DBROOT} + - TZ=${TZ} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + mailcow-network: + aliases: + - dockerapi + + solr-mailcow: + image: mailcow/solr:1.7 + restart: always + volumes: + - solr-vol-1:/opt/solr/server/solr/dovecot-fts/data + ports: + - "${SOLR_PORT:-127.0.0.1:18983}:8983" + environment: + - TZ=${TZ} + - SOLR_HEAP=${SOLR_HEAP:-1024} + - SKIP_SOLR=${SKIP_SOLR:-y} + networks: + mailcow-network: + aliases: + - solr + + olefy-mailcow: + image: mailcow/olefy:1.3 + restart: always + environment: + - TZ=${TZ} + - OLEFY_BINDADDRESS=0.0.0.0 + - OLEFY_BINDPORT=10055 + - OLEFY_TMPDIR=/tmp + - OLEFY_PYTHON_PATH=/usr/bin/python3 + - OLEFY_OLEVBA_PATH=/usr/bin/olevba3 + - OLEFY_LOGLVL=20 + - OLEFY_MINLENGTH=500 + - OLEFY_DEL_TMP=1 + networks: + mailcow-network: + aliases: + - olefy + + ipv6nat-mailcow: + depends_on: + - unbound-mailcow + - mysql-mailcow + - redis-mailcow + - clamd-mailcow + - rspamd-mailcow + - php-fpm-mailcow + - sogo-mailcow + - dovecot-mailcow + - postfix-mailcow + - memcached-mailcow + - nginx-mailcow + - acme-mailcow + - netfilter-mailcow + - watchdog-mailcow + - dockerapi-mailcow + - solr-mailcow + environment: + - TZ=${TZ} + image: robbertkl/ipv6nat + restart: always + privileged: true + network_mode: "host" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /lib/modules:/lib/modules:ro + + + gitlab: + image: 'gitlab/gitlab-ce:latest' + container_name: 'gitlab' + restart: always + hostname: 'localhost' + environment: + GITLAB_OMNIBUS_CONFIG: | + external_url 'https://aitrainer.app:8929' + gitlab_rails['smtp_enable'] = true + gitlab_rails['smtp_address'] = "email-smtp.eu-west-1.amazonaws.com" + gitlab_rails['smtp_port'] = 587 + gitlab_rails['smtp_user_name'] = "AKIAIWHHQDMPADT7ETHQ" + gitlab_rails['smtp_password'] = "AjCB8NA+61i/URp09gik0HHtbEuy48e4JXhuPaqGacFs" + gitlab_rails['smtp_domain'] = "aitrainer.app" + gitlab_rails['smtp_authentication'] = "login" + gitlab_rails['smtp_enable_starttls_auto'] = true + gitlab_rails['smtp_openssl_verify_mode'] = 'peer' + # Add any other gitlab.rb configuration here, each on its own line + gitlab_rails['gitlab_shell_ssh_port'] = 6622 + ports: + - '8929:8929' + - '443:443' + - '6622:22' + - '587:587' + volumes: + - '/srv/gitlab/config:/etc/gitlab' + - '/srv/gitlab/logs:/var/log/gitlab' + - '/srv/gitlab/data:/var/opt/gitlab' + + mysql: + image: mysql:latest + volumes: + - mysql-vol-0:/var/lib/mysql0 + restart: always + ports: + - 33061:33061 + environment: + MYSQL_ROOT_PASSWORD: /run/secrets/mysql_root_pwd + MYSQL_DATABASE: aitrainer + MYSQL_USER: aitrainer + MYSQL_PASSWORD: /run/secrets/mysql_user_pwd + networks: + - bosi_default + + phpmyadmin: + depends_on: + - mysql + image: phpmyadmin/phpmyadmin + restart: always + ports: + - '80:80' + environment: + PMA_HOST: mysql + MYSQL_ROOT_PASSWORD: andio2009 + networks: + - bosi_default + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /srv/gitlab-runner/config:/etc/gitlab-runner + + +networks: + bosi_default: + mailcow-network: + driver: bridge + driver_opts: + com.docker.network.bridge.name: br-mailcow + enable_ipv6: true + ipam: + driver: default + config: + - subnet: ${IPV4_NETWORK:-172.22.1}.0/24 + - subnet: ${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64} + +volumes: + # Storage for email files + vmail-vol-1: + # Storage for attachments (deduplicated) + vmail-attachments-vol-1: + mysql-vol-1: + mysql-vol-0: + mysql-socket-vol-1: + redis-vol-1: + rspamd-vol-1: + solr-vol-1: + postfix-vol-1: + crypt-vol-1: + sogo-web-vol-1: + sogo-userdata-backup-vol-1: + php: + +secrets: + mysql_root_pwd: + file: /.sec/mysql_root_pwd + mysql_user_pwd: + file: /.sec/mysql_user_pwd + + diff --git a/docker-compose_gitlab.yml b/docker-compose_gitlab.yml new file mode 100644 index 0000000..c79b49d --- /dev/null +++ b/docker-compose_gitlab.yml @@ -0,0 +1,143 @@ +version: '3.8' +services: + + demo: + image: ehazlett/docker-demo + deploy: + replicas: 1 + labels: + com.docker.lb.hosts: aitrainer.app + com.docker.lb.network: bosi-network + com.docker.lb.port: 8080 + com.docker.lb.ssl_cert: demo_app.example.org.cert + com.docker.lb.ssl_key: demo_app.example.org.key + environment: + METADATA: proxy-handles-tls + networks: + - demo-network + + gitlab: + image: 'gitlab/gitlab-ce:latest' + container_name: 'gitlab' + restart: always + hostname: 'localhost' + environment: + GITLAB_OMNIBUS_CONFIG: | + external_url 'https://aitrainer.app' + gitlab_rails['smtp_enable'] = true + gitlab_rails['smtp_address'] = "email-smtp.eu-west-1.amazonaws.com" + gitlab_rails['smtp_port'] = 587 + gitlab_rails['smtp_user_name'] = "AKIAIWHHQDMPADT7ETHQ" + gitlab_rails['smtp_password'] = "AjCB8NA+61i/URp09gik0HHtbEuy48e4JXhuPaqGacFs" + gitlab_rails['smtp_domain'] = "aitrainer.app" + gitlab_rails['smtp_authentication'] = "login" + gitlab_rails['smtp_enable_starttls_auto'] = true + gitlab_rails['smtp_openssl_verify_mode'] = 'peer' + # Add any other gitlab.rb configuration here, each on its own line + gitlab_rails['gitlab_shell_ssh_port'] = 6622 + ports: + - '80:80' + - '443:443' + - '6622:22' + - '587:587' + volumes: + - '/srv/gitlab/config:/etc/gitlab' + - '/srv/gitlab/logs:/var/log/gitlab' + - '/srv/gitlab/data:/var/opt/gitlab' + mysql: + image: mysql:latest + volumes: + - db_data:/var/lib/mysql_aitrainer + restart: always + ports: + - 33061:33061 + environment: + MYSQL_ROOT_PASSWORD: /run/secrets/mysql_root_pwd + MYSQL_DATABASE: aitrainer + MYSQL_USER: aitrainer + MYSQL_PASSWORD: /run/secrets/mysql_user_pwd + networks: + - bosi_default + + phpmyadmin: + depends_on: + - mysql + image: phpmyadmin/phpmyadmin + restart: always + ports: + - '8081:80' + environment: + PMA_HOST: mysql + MYSQL_ROOT_PASSWORD: andio2009 + networks: + - bosi_default + php: + image: php:7.2-fpm + volumes: + - php:/var/www/html + - ./php/php.ini:/usr/local/etc/php/php.ini + depends_on: + - mysql + gitlab-runner: + image: gitlab/gitlab-runner:latest + container_name: gitlab-runner + restart: always + networks: + - bosi_default + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /srv/gitlab-runner/config:/etc/gitlab-runner +secrets: + mysql_root_pwd: + file: /.sec/mysql_root_pwd + mysql_user_pwd: + file: /.sec/mysql_user_pwd +networks: + bosi_default: +volumes: + db_data: + php: + + + + openssl req \ + -new \ + -newkey rsa:4096 \ + -days 3650 \ + -nodes \ + -x509 \ + -subj "/C=US/ST=CA/L=SF/O=Docker-demo/CN=aitrainer.app" \ + -keyout aitrainer.app.key \ + -out aitrainer.app.cert + +version: "3.2" + +services: + demo: + image: proxy + command: --tls-cert=/run/secrets/cert.pem --tls-key=/run/secrets/key.pem + deploy: + replicas: 1 + labels: + com.docker.lb.hosts: aitrainer.app + com.docker.lb.network: proxy-network + com.docker.lb.port: 8029 + com.docker.lb.ssl_passthrough: "true" + environment: + METADATA: end-to-end-TLS + networks: + - proxy-network + secrets: + - source: aitrainer.app.cert + target: /run/secrets/cert.pem + - source: aitrainer.app.org.key + target: /run/secrets/key.pem + +networks: + demo-network: + driver: overlay +secrets: + aitrainer.app.cert: + file: ./aitrainer.app.cert + aitrainer.app.key: + file: ./aitrainer.app.key \ No newline at end of file diff --git a/readme.MD b/readme.MD index 3a4e2c2..451fa34 100644 --- a/readme.MD +++ b/readme.MD @@ -1,9 +1,13 @@ -aitrainer server API v0.0.2 +#aitrainer server API v0.0.5 connects the MYSQL Database -provide a RESTful API to the mobile app +provide a RESTful API for the mobile app -finished API: +##finished API -customers -exercise type impl.ed +* customers +* exercise_type +* exercise +* customer_information + +with automatic database update diff --git a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt index 742e079..0682bc7 100644 --- a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt +++ b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt @@ -4,7 +4,6 @@ import org.slf4j.LoggerFactory import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.builder.SpringApplicationBuilder -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -12,19 +11,14 @@ import org.springframework.web.bind.annotation.RestController @RestController class ApiApplication private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + @Override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder { return application.sources(ApiApplication::class.java) } - fun main(args: Array) { logger.info(" ---- Start aitrainer API") SpringApplication.run(ApiApplication::class.java, *args) } - - @RequestMapping( "/") - fun hello(): String { - return "Hello Aitrainer API" - } diff --git a/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt b/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt new file mode 100644 index 0000000..92070d1 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt @@ -0,0 +1,45 @@ +package com.aitrainer.api.controller + +import org.springframework.beans.factory.annotation.Value +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping() +class ApplicationProperties { + + @Value("\${application.version}") + private lateinit var version: String + + @Value("\${spring.datasource.url}") + private lateinit var datasourceUrl: String + + @Value("\${spring.datasource.username}") + private lateinit var datasourceUsername: String + + @Value("\${spring.datasource.password}") + private lateinit var datasourcePassword: String + + + + @GetMapping("/version") + fun getVersion(): String { + return this.version + } + + @GetMapping("/datasourceUrl") + fun getDatasourceUrl(): String { + return this.datasourceUrl + } + + @GetMapping("/datasourceUsername") + fun getDatasourceUsername(): String { + return this.datasourceUsername + } + + @GetMapping("/datasourcePassword") + fun getDatasourcePassword(): String { + return this.datasourcePassword + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/ConfigurationController.kt b/src/main/kotlin/com/aitrainer/api/controller/ConfigurationController.kt new file mode 100644 index 0000000..98e16fc --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/ConfigurationController.kt @@ -0,0 +1,27 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.model.Configuration + +import com.aitrainer.api.repository.ConfigurationRepository +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDateTime + +@RestController +class ConfigurationController ( private val configurationRepository: ConfigurationRepository) { + + fun getConfiguration(key: String): Configuration = + configurationRepository.findByConfigKey(key) + + fun updateConfiguration( newConfiguration: Configuration): Configuration { + val existingConfiguration: Configuration = configurationRepository.findByConfigKey(newConfiguration.configKey) + val updatedConfiguration: Configuration = existingConfiguration.copy( + configKey = newConfiguration.configKey, + configValue = newConfiguration.configValue, + //dateAdd = newConfiguration.dateAdd, + dateChange = LocalDateTime.now().toString()) + + configurationRepository.save(updatedConfiguration) + return existingConfiguration + } + +} diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt new file mode 100644 index 0000000..b4301e6 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt @@ -0,0 +1,34 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.ApiApplication +import com.aitrainer.api.repository.ConfigurationRepository +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.aspectj.lang.annotation.Pointcut +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + + +@Suppress("unused") +@Aspect +@Component +class CustomerControllerAspect { + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + @Autowired + private lateinit var properties: ApplicationProperties + + @Suppress("unused") + @Pointcut("execution(* com.aitrainer.api.controller.CustomerController.*())") + fun customerControllerAspect() { + } + + @Before("customerControllerAspect()") + fun loggingAop() { + Singleton.checkDBUpdate(configurationRepository, properties) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt new file mode 100644 index 0000000..1a40648 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt @@ -0,0 +1,18 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.model.CustomerInformation +import com.aitrainer.api.repository.CustomerInformationRepository +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDateTime + +@RestController +@RequestMapping("/api") +class CustomerInformationController( private val customerInformationRepository: CustomerInformationRepository ) { + + @GetMapping("/customer_information") + fun getCustomerInformation(dateTime: String): List = + customerInformationRepository.findByDisplayBeginLessThanAndDisplayEndGreaterThan(dateTime, dateTime ) + +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt b/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt new file mode 100644 index 0000000..ec4fc01 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt @@ -0,0 +1,105 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.ApiApplication +import com.aitrainer.api.model.Configuration +import com.aitrainer.api.repository.ConfigurationRepository +import org.hibernate.Hibernate.isInitialized +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.web.bind.annotation.RestController +import java.io.File +import java.sql.Connection +import java.sql.DriverManager +import java.sql.ResultSet +import java.util.Properties + +@RestController +object Singleton { + + private lateinit var connection: Connection + private lateinit var configurationRepository: ConfigurationRepository + private lateinit var properties: ApplicationProperties + + private var initialized: Boolean = false + + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + + // to use the singleton functionality avoiding the db access before each request + private var dbVersion: String = "" + private var appVersion: String = "" + + fun checkDBUpdate(configurationRepository: ConfigurationRepository, properties: ApplicationProperties) { + + if ( dbVersion.isNotEmpty() && appVersion.isNotEmpty()) { + // no db access + //println("DB up-to-date, no DB access") + return + } + + this.configurationRepository = configurationRepository + this.properties = properties + + val dbConfig: Configuration = configurationRepository.findByConfigKey("db_version") + val applicationVersion: String = properties.getVersion() + + this.dbVersion = dbConfig.configValue + this.appVersion = applicationVersion + + if ( dbConfig.configValue != applicationVersion ) { + + try { + + + val versionNumber: String = applicationVersion.replace(".", "_") + val fileName = "update_$versionNumber.sql" + val path = "./data/db/" + val file2Update = path+fileName + + var sqlRows = "" + File(file2Update).forEachLine { + sqlRows += it + if ( sqlRows.contains(";") ) { + execSQL(sqlRows) + sqlRows = "" + } + } + this.connection.commit() + logger.info("Database has been updated to $applicationVersion" ) + } catch (exception: Exception) { + logger.info("Database exception of $applicationVersion: " + exception.message ) + this.connection.rollback() + } + } + + } + + fun execSQL( sql: String ) { + + if (! this.initialized ) { + this.getConnection() + } + + with(this.connection) { + createStatement().execute(sql) + //commit() + } + } + + fun execQuery( sql: String ): ResultSet { + if (! this.initialized ) { + this.getConnection() + } + + return connection.createStatement().executeQuery(sql) + } + + private fun getConnection(): Connection { + val connectionProps = Properties() + connectionProps["user"] = this.properties.getDatasourceUsername() + connectionProps["password"] = this.properties.getDatasourcePassword() + this.connection = DriverManager.getConnection(this.properties.getDatasourceUrl(), connectionProps) + this.connection.autoCommit = false + this.initialized = true + return this.connection + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseController.kt similarity index 92% rename from src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt rename to src/main/kotlin/com/aitrainer/api/controller/ExerciseController.kt index 36da824..adda633 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciesController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseController.kt @@ -8,7 +8,7 @@ import javax.validation.Valid @RestController @RequestMapping("/api") -class ExerciesController(private val exercisesRepository: ExercisesRepository) { +class ExerciseController(private val exercisesRepository: ExercisesRepository) { @GetMapping("/exercises/{id}") fun getExerciseById(@PathVariable(value = "id") exerciseId: Long): ResponseEntity { @@ -33,7 +33,7 @@ class ExerciesController(private val exercisesRepository: ExercisesRepository) { val updatedExercises: Exercises = existingExercises.copy( exerciseTypeId = newExercises.exerciseTypeId, customerId = newExercises.customerId, - datetimeExercise = newExercises.datetimeExercise, + dateAdd = newExercises.dateAdd, quantity = newExercises.quantity, restTime = newExercises.restTime ) diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseControllerAspect.kt new file mode 100644 index 0000000..02089ed --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseControllerAspect.kt @@ -0,0 +1,35 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.ApiApplication +import com.aitrainer.api.repository.ConfigurationRepository +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.aspectj.lang.annotation.Pointcut +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Component +@Suppress("unused") +@Aspect +class ExerciseControllerAspect { + + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + @Autowired + private lateinit var properties: ApplicationProperties + + @Suppress("unused") + @Pointcut("execution(* com.aitrainer.api.controller.ExerciseController.*())") + fun exerciseControllerAspect() { + } + + @Before("exerciseControllerAspect()") + fun loggingAop() { + Singleton.checkDBUpdate(configurationRepository, properties) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt new file mode 100644 index 0000000..67531b3 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt @@ -0,0 +1,34 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.ApiApplication +import com.aitrainer.api.repository.ConfigurationRepository +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.aspectj.lang.annotation.Pointcut +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Suppress("unused") +@Aspect +@Component +class ExerciseTypeControllerAspect { + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + @Autowired + private lateinit var properties: ApplicationProperties + + @Suppress("unused") + @Pointcut("execution(* com.aitrainer.api.controller.ExerciseTypeController.*())") + fun exerciseTypeControllerAspect() { + } + + @Before("exerciseTypeControllerAspect()") + fun loggingAop() { + Singleton.checkDBUpdate(configurationRepository, properties) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/model/Configuration.kt b/src/main/kotlin/com/aitrainer/api/model/Configuration.kt new file mode 100644 index 0000000..0370a6c --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/Configuration.kt @@ -0,0 +1,21 @@ +package com.aitrainer.api.model + +import javax.persistence.Entity +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType +import javax.persistence.Id +import javax.validation.constraints.NotBlank +import javax.validation.constraints.Null + +@Entity +data class Configuration ( + + + @get: NotBlank var configKey: String = "", + @get: NotBlank var configValue: String = "", + var dateAdd: String, + var dateChange: String = "", + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val configuration_id: Long = 0 +) diff --git a/src/main/kotlin/com/aitrainer/api/model/Customer.kt b/src/main/kotlin/com/aitrainer/api/model/Customer.kt index d932a5d..490cd5a 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Customer.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Customer.kt @@ -16,6 +16,10 @@ data class Customer ( var age: Int = 0, var sex: String = "m", var active: String = "N", + var dateAdd: String? = null, + var dateChange: String? = null, + var dataPolicyAllowed: Int = 0, + var admin: Int = 0, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val customer_id: Long = 0 diff --git a/src/main/kotlin/com/aitrainer/api/model/CustomerInformation.kt b/src/main/kotlin/com/aitrainer/api/model/CustomerInformation.kt new file mode 100644 index 0000000..493ada5 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/CustomerInformation.kt @@ -0,0 +1,21 @@ +package com.aitrainer.api.model + +import javax.persistence.Entity +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType +import javax.persistence.Id +import javax.validation.constraints.NotBlank + +@Entity +class CustomerInformation ( + @get: NotBlank var title: String = "", + var description: String = "", + + var dateAdd: String? = null, + var displayBegin: String? = null, + var displayEnd: String? = null, + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val customerInformationId: Long = 0 +) \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt index 6770c95..d8a1d88 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Exercises.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Exercises.kt @@ -11,9 +11,10 @@ import javax.validation.constraints.Null data class Exercises ( @get: NonNull var exerciseTypeId: Long = 0, @get: NonNull var customerId: Long = 0, - @get: NonNull var datetimeExercise: String? = null, + @get: NonNull var dateAdd: String? = null, @get: NonNull var quantity: Int = 0, @get: Null var restTime: Int?, // in seconds + @get: NonNull var unit: String? = null, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val exerciseId: Long = 0 diff --git a/src/main/kotlin/com/aitrainer/api/repository/ConfigurationRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/ConfigurationRepository.kt new file mode 100644 index 0000000..30383c5 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/repository/ConfigurationRepository.kt @@ -0,0 +1,10 @@ +package com.aitrainer.api.repository + +import com.aitrainer.api.model.Configuration +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ConfigurationRepository : JpaRepository { + fun findByConfigKey( key: String ):Configuration +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/repository/CustomerInformationRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/CustomerInformationRepository.kt new file mode 100644 index 0000000..3e3f87f --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/repository/CustomerInformationRepository.kt @@ -0,0 +1,11 @@ +package com.aitrainer.api.repository + +import com.aitrainer.api.model.CustomerInformation +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface CustomerInformationRepository: JpaRepository { + fun findByDisplayBeginLessThanAndDisplayEndGreaterThan( dateTimeBegin: String, dateTimeEnd: String ): + List +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a43762e..c5131fe 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -11,5 +11,8 @@ spring.datasource.password = andio2009 # The SQL dialect makes Hibernate generate better SQL for the chosen database spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect + logging.config=classpath:logback-spring.xml -logging.file=logs \ No newline at end of file +logging.file=logs + +application.version=0.0.2 \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt b/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt deleted file mode 100644 index 6450923..0000000 --- a/src/test/kotlin/com/aitrainer/api/test/AitrainerDBTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.aitrainer.api.test - -import org.springframework.beans.factory.annotation.Autowired -import java.sql.Connection -import java.sql.DriverManager -import java.util.* -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - - -class AitrainerDBTest(configuration: ApplicationConfiguration) { - - - @Autowired - private lateinit var username: String - private lateinit var password: String - private lateinit var datasourceUrl: String - private lateinit var conn: Connection - private val conf: ApplicationConfiguration = configuration - - fun initDB() { - this.username = this.conf.username.orEmpty() - this.password = this.conf.password.orEmpty() - this.datasourceUrl = this.conf.url.orEmpty() - assertTrue ("username should not be empty") { this.username.isNotEmpty() } - assertTrue ("password should not be empty") { this.password.isNotEmpty() } - assertTrue ("url should not be empty") { this.datasourceUrl.isNotEmpty() } - - this.getConnection() - - assertNotNull({this.conn}, "MySQL connection should not be null") - } - - - - /** - * This method makes a connection to MySQL Server - * In this example, MySQL Server is running in the local host (so 127.0.0.1) - * at the standard port 3306 - */ - private fun getConnection() { - val connectionProps = Properties() - connectionProps["user"] = this.username - connectionProps["password"] = this.password - Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance() - this.conn = DriverManager.getConnection(this.datasourceUrl, connectionProps) - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt b/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt deleted file mode 100644 index 8d378d5..0000000 --- a/src/test/kotlin/com/aitrainer/api/test/ApplicationConfiguration.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.aitrainer.api.test - -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.stereotype.Component - -@Component -@ConfigurationProperties(prefix = "spring.datasource") -class ApplicationConfiguration { - var username: String? = null - var password: String? = null - var url: String? = null -} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ApplicationStartTest.kt b/src/test/kotlin/com/aitrainer/api/test/ApplicationStartTest.kt new file mode 100644 index 0000000..3beb06c --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/ApplicationStartTest.kt @@ -0,0 +1,34 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.controller.ApplicationProperties +import com.aitrainer.api.controller.Singleton +import com.aitrainer.api.repository.ConfigurationRepository +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import java.sql.ResultSet +import kotlin.test.assertEquals + +@SpringBootTest +class ApplicationStartTest { + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + @Autowired + private lateinit var properties: ApplicationProperties + + @Test + fun testDBCheck() { + Singleton.checkDBUpdate(this.configurationRepository, this.properties) + + var foundTable = false + val rs: ResultSet = Singleton.execQuery("Show tables;") + while ( rs.next() ) { + if ( rs.getString("tables_in_aitrainer") == "customer_information" ) { + foundTable = true + } + } + + assertEquals(foundTable, true) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ConfigurationTest.kt b/src/test/kotlin/com/aitrainer/api/test/ConfigurationTest.kt new file mode 100644 index 0000000..d7540b5 --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/ConfigurationTest.kt @@ -0,0 +1,31 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.controller.ConfigurationController +import com.aitrainer.api.model.Configuration +import com.aitrainer.api.repository.ConfigurationRepository +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import kotlin.test.assertEquals + +@SpringBootTest +class ConfigurationTest { + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + private lateinit var configurationController: ConfigurationController + + @Test + fun testUpdateConfig() { + val config: Configuration = configurationRepository.findByConfigKey("db_version") + config.configValue = "0.0.2" + + configurationController = ConfigurationController(configurationRepository) + val updatedConfig: Configuration = configurationController.updateConfiguration(config) + assertEquals(updatedConfig.configValue, "0.0.2") + + val foundConfig: Configuration = configurationRepository.findByConfigKey("db_version") + assertEquals(foundConfig.configValue, "0.0.2") + + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerInformationTest.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerInformationTest.kt new file mode 100644 index 0000000..0666d40 --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerInformationTest.kt @@ -0,0 +1,24 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.model.CustomerInformation +import com.aitrainer.api.repository.CustomerInformationRepository +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import kotlin.test.assertEquals + +@SpringBootTest +class CustomerInformationTest { + + @Autowired + private lateinit var customerInformationRepository: CustomerInformationRepository + + @Test + fun getActiveCustomerInformation() { + val dateTime = "2020-06-01 09:00:00" + val info: List = this.customerInformationRepository. + findByDisplayBeginLessThanAndDisplayEndGreaterThan(dateTime, dateTime ) + assertEquals(info.size, 2) + assertEquals(info.first().title, "Fekvőtámasz világcsúcs") + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt b/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt index 3196656..bbc2a55 100644 --- a/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/ExerciseTest.kt @@ -33,7 +33,8 @@ class ExerciseTest { exerciseTypeId = 3, customerId = 11, quantity = 100, - datetimeExercise = "2020-05-13 04:32:00", + dateAdd = "2020-05-13 04:32:00", + unit = "repeat", restTime = null ) val exerciseNew = exerciseRepository.save(exercise) diff --git a/src/test/kotlin/com/aitrainer/api/test/PropertiesTest.kt b/src/test/kotlin/com/aitrainer/api/test/PropertiesTest.kt new file mode 100644 index 0000000..26130b1 --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/PropertiesTest.kt @@ -0,0 +1,27 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.controller.ApplicationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import kotlin.test.assertEquals + +@SpringBootTest +class PropertiesTest { + + @Autowired + private lateinit var properties: ApplicationProperties + + @Test + fun testProperties() { + val version: String = properties.getVersion() + assertEquals(version, "0.0.2") + } + + @Test + fun testDatasourceUrl() { + val url: String = properties.getDatasourceUrl() + assertEquals(url, "jdbc:mysql://localhost:3306/aitrainer?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true") + } + +} \ No newline at end of file From 070acf4e47b04225fa4b7fbeae95aed35e816a6f Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Mon, 1 Jun 2020 09:49:12 +0200 Subject: [PATCH 84/86] customer information aspect --- data/db/install.sql | 2 ++ .../CustomerInformationController.kt | 7 ++-- .../CustomerInformationControllerAspect.kt | 35 +++++++++++++++++++ src/main/resources/application.properties | 1 + 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/aitrainer/api/controller/CustomerInformationControllerAspect.kt diff --git a/data/db/install.sql b/data/db/install.sql index 08ac149..b64bcff 100644 --- a/data/db/install.sql +++ b/data/db/install.sql @@ -10,6 +10,8 @@ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +use aitrainer; + -- Struktúra mentése tábla aitrainer. customer CREATE TABLE IF NOT EXISTS `customer` ( `customer_id` int(11) NOT NULL AUTO_INCREMENT, diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt index 1a40648..a5d9882 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationController.kt @@ -12,7 +12,10 @@ import java.time.LocalDateTime class CustomerInformationController( private val customerInformationRepository: CustomerInformationRepository ) { @GetMapping("/customer_information") - fun getCustomerInformation(dateTime: String): List = - customerInformationRepository.findByDisplayBeginLessThanAndDisplayEndGreaterThan(dateTime, dateTime ) + fun getCustomerInformation(): List { + val dateTime: String = LocalDateTime.now().toString() + return customerInformationRepository.findByDisplayBeginLessThanAndDisplayEndGreaterThan(dateTime, dateTime ) + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationControllerAspect.kt new file mode 100644 index 0000000..93b3a09 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerInformationControllerAspect.kt @@ -0,0 +1,35 @@ +package com.aitrainer.api.controller + +import com.aitrainer.api.ApiApplication +import com.aitrainer.api.repository.ConfigurationRepository +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.aspectj.lang.annotation.Pointcut +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Aspect +@Component +@Suppress("unused") +class CustomerInformationControllerAspect { + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + @Autowired + private lateinit var properties: ApplicationProperties + + @Suppress("unused") + @Pointcut("execution(* com.aitrainer.api.controller.CustomerInformationController.*())") + fun customerInformationAspect() { + } + + @Suppress("unused") + @Before("customerInformationAspect()") + fun dbCheckAop() { + Singleton.checkDBUpdate(configurationRepository, properties) + } + + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c5131fe..b85926c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,4 +15,5 @@ spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDial logging.config=classpath:logback-spring.xml logging.file=logs +# if the database structure has been changed, increment this version number application.version=0.0.2 \ No newline at end of file From dba2228939fd99eca2eb426a55cdd19877daf282 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Thu, 4 Jun 2020 07:37:52 +0200 Subject: [PATCH 85/86] new conf directory --- .../config/docker-compose_cowmail.yml | 0 .../config/docker-compose_gitlab.yml | 0 data/config/gitlab-nginx-ssl.conf | 113 ++++++++++++++++++ 3 files changed, 113 insertions(+) rename docker-compose_cowmail.yml => data/config/docker-compose_cowmail.yml (100%) rename docker-compose_gitlab.yml => data/config/docker-compose_gitlab.yml (100%) create mode 100644 data/config/gitlab-nginx-ssl.conf diff --git a/docker-compose_cowmail.yml b/data/config/docker-compose_cowmail.yml similarity index 100% rename from docker-compose_cowmail.yml rename to data/config/docker-compose_cowmail.yml diff --git a/docker-compose_gitlab.yml b/data/config/docker-compose_gitlab.yml similarity index 100% rename from docker-compose_gitlab.yml rename to data/config/docker-compose_gitlab.yml diff --git a/data/config/gitlab-nginx-ssl.conf b/data/config/gitlab-nginx-ssl.conf new file mode 100644 index 0000000..ece3736 --- /dev/null +++ b/data/config/gitlab-nginx-ssl.conf @@ -0,0 +1,113 @@ +## GitLab +## +## Modified from nginx http version +## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ +## Modified from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html +## +## Lines starting with two hashes (##) are comments with information. +## Lines starting with one hash (#) are configuration parameters that can be uncommented. +## +################################## +## CONTRIBUTING ## +################################## +## +## If you change this file in a Merge Request, please also create +## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests +## +################################### +## configuration ## +################################### +## +## See installation.md#using-https for additional HTTPS configuration details. + +upstream gitlab-workhorse { + server unix:/srv/gitlab/gitlab-workhorse/socket fail_timeout=0; +} + +## Redirects all HTTP traffic to the HTTPS host +server { + ## Either remove "default_server" from the listen line below, + ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab + ## to be served if you visit any address that your server responds to, eg. + ## the ip address of the server (http://x.x.x.x/) + listen 0.0.0.0:80; + listen [::]:80 ipv6only=on default_server; + server_name git.aitrainer.app ; ## Replace this with something like gitlab.example.com + server_tokens off; ## Don't show the nginx version number, a security best practice + return 301 https://$http_host$request_uri; + access_log /var/log/nginx/gitlab_access.log; + error_log /var/log/nginx/gitlab_error.log; +} + +## HTTPS host +server { + listen 0.0.0.0:443 ssl; + listen [::]:443 ipv6only=on ssl default_server; + server_name git.aitrainer.app ; ## Replace this with something like gitlab.example.com + server_tokens off; ## Don't show the nginx version number, a security best practice + root /opt/gitlab/embedded/service/gitlab-rails/public; + + ## Strong SSL Security + ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ + ssl on; + #ssl_certificate /etc/nginx/ssl/gitlab.crt; + #ssl_certificate_key /etc/nginx/ssl/gitlab.key; + ssl_certificate /etc/letsencrypt/live/git.aitrainer.app/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/git.aitrainer.app/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + + # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; + + ## See app/controllers/application_controller.rb for headers set + + ## [Optional] Enable HTTP Strict Transport Security + ## HSTS is a feature improving protection against MITM attacks + ## For more information see: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/ + # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; + + ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. + ## Replace with your ssl_trusted_certificate. For more info see: + ## - https://medium.com/devops-programming/4445f4862461 + ## - https://www.ruby-forum.com/topic/4419319 + ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx + # ssl_stapling on; + # ssl_stapling_verify on; + # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 5s; + + ## [Optional] Generate a stronger DHE parameter: + ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 + ## + # ssl_dhparam /etc/ssl/certs/dhparam.pem; + + ## Individual nginx logs for this GitLab vhost + access_log /var/log/nginx/gitlab_access.log; + error_log /var/log/nginx/gitlab_error.log; + + location / { + client_max_body_size 0; + gzip off; + + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; + + proxy_http_version 1.1; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://gitlab-workhorse; + } +} From c685dfb71c7df7d0586d13b053bf6633fad83973 Mon Sep 17 00:00:00 2001 From: Bossanyi Tibor Date: Wed, 10 Jun 2020 16:19:48 +0200 Subject: [PATCH 86/86] authenticaction, registration, login --- build.gradle.kts | 9 +- data/db/update_0_0_3.sql | 1 + .../com/aitrainer/api/ApiApplication.kt | 3 - .../api/controller/ApplicationProperties.kt | 2 +- .../api/controller/CustomerController.kt | 84 +++++++++++++++++-- .../controller/CustomerControllerAspect.kt | 18 ++-- .../api/controller/DatabaseCheckSingleton.kt | 5 +- .../ExerciseTypeControllerAspect.kt | 1 - .../com/aitrainer/api/model/Configuration.kt | 1 - .../com/aitrainer/api/model/Customer.kt | 8 +- .../kotlin/com/aitrainer/api/model/User.kt | 15 ++++ .../api/repository/CustomerRepository.kt | 4 +- .../AuthenticationControllerAspect.kt | 31 +++++++ .../security/JwtAuthenticationController.kt | 49 +++++++++++ .../security/JwtAuthenticationEntryPoint.kt | 23 +++++ .../com/aitrainer/api/security/JwtRequest.kt | 19 +++++ .../api/security/JwtRequestFilter.kt | 77 +++++++++++++++++ .../com/aitrainer/api/security/JwtResponse.kt | 12 +++ .../api/security/JwtSecurityConfig.kt | 65 ++++++++++++++ .../aitrainer/api/security/JwtTokenUtil.kt | 67 +++++++++++++++ .../com/aitrainer/api/service/ServiceBeans.kt | 19 +++++ .../api/service/UserDetailsServiceImpl.kt | 34 ++++++++ src/main/resources/application.properties | 4 +- .../aitrainer/api/test/AuthenticationTest.kt | 25 ++++++ .../com/aitrainer/api/test/CustomerTests.kt | 70 +++++++++++++++- 25 files changed, 608 insertions(+), 38 deletions(-) create mode 100644 data/db/update_0_0_3.sql create mode 100644 src/main/kotlin/com/aitrainer/api/model/User.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/AuthenticationControllerAspect.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationController.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationEntryPoint.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtRequest.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtRequestFilter.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtResponse.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtSecurityConfig.kt create mode 100644 src/main/kotlin/com/aitrainer/api/security/JwtTokenUtil.kt create mode 100644 src/main/kotlin/com/aitrainer/api/service/ServiceBeans.kt create mode 100644 src/main/kotlin/com/aitrainer/api/service/UserDetailsServiceImpl.kt create mode 100644 src/test/kotlin/com/aitrainer/api/test/AuthenticationTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index f5ad0de..52600c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,9 +4,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "2.3.0.RELEASE" id("io.spring.dependency-management") version "1.0.9.RELEASE" - kotlin("jvm") version "1.3.71" + kotlin("jvm") version "1.3.72" kotlin("plugin.spring") version "1.3.72" kotlin("plugin.jpa") version "1.3.72" + kotlin("plugin.serialization") version "1.3.70" } group = "com.aitrainer" @@ -22,12 +23,18 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-aop") implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.3.0.RELEASE") + implementation("org.springframework.security.oauth:spring-security-oauth2:2.5.0.RELEASE") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.apache.logging.log4j:log4j-core:2.13.3") implementation("org.apache.logging.log4j:log4j-api:2.13.3") implementation("org.slf4j:slf4j-api:1.7.30") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0") // JVM dependency + implementation("io.jsonwebtoken:jjwt:0.9.1") + runtimeOnly("mysql:mysql-connector-java") diff --git a/data/db/update_0_0_3.sql b/data/db/update_0_0_3.sql new file mode 100644 index 0000000..4e4cbfb --- /dev/null +++ b/data/db/update_0_0_3.sql @@ -0,0 +1 @@ +INSERT INTO `customer` (`name`, `firstname`, `email`, `password`, `sex`, `age`, `active`, `date_add`, `date_change`, `data_policy_allowed`, `admin`) VALUES ('Dummy User', NULL, 'bosi', '$2a$10$thOc8jS750c7xe9U9Qq3GuSPs/H0Pt2Ads05yzUlyzQBIj.Rk9QCy', 'm', 40, 'N', NULL, NULL, 1, 1); diff --git a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt index 0682bc7..00ffe92 100644 --- a/src/main/kotlin/com/aitrainer/api/ApiApplication.kt +++ b/src/main/kotlin/com/aitrainer/api/ApiApplication.kt @@ -4,11 +4,8 @@ import org.slf4j.LoggerFactory import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.builder.SpringApplicationBuilder -import org.springframework.web.bind.annotation.RestController - @SpringBootApplication -@RestController class ApiApplication private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) diff --git a/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt b/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt index 92070d1..927c727 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ApplicationProperties.kt @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping() +@RequestMapping class ApplicationProperties { @Value("\${application.version}") diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt index f9e065b..93d068d 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerController.kt @@ -1,39 +1,53 @@ package com.aitrainer.api.controller import com.aitrainer.api.model.Customer +import com.aitrainer.api.model.User +import com.aitrainer.api.service.ServiceBeans import com.aitrainer.api.repository.CustomerRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity +import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* import javax.validation.Valid + @RestController @RequestMapping("/api") class CustomerController ( private val customerRepository: CustomerRepository ) { - + + @Autowired + var serviceBeans: ServiceBeans? = null + + @Secured @GetMapping("/customers") - fun getAllCustomers(): List = + fun getAllCustomers(@RequestHeader headers: HttpHeaders): List = customerRepository.findAll() + @Secured @PostMapping("/customers") - fun createNewCustomer(@Valid @RequestBody customer: Customer): Customer = + fun createNewCustomer(@Valid @RequestBody customer: Customer, @RequestHeader headers: HttpHeaders): Customer = customerRepository.save(customer) + @Secured @GetMapping("/customers/{id}") - fun getCustomerById(@PathVariable(value = "id") customerId: Long): ResponseEntity { + fun getCustomerById(@PathVariable(value = "id") customerId: Long, @RequestHeader headers: HttpHeaders): ResponseEntity { return customerRepository.findById(customerId).map { customer -> ResponseEntity.ok(customer) }.orElse(ResponseEntity.notFound().build()) - } + } + @Secured @GetMapping("/customers/real") - fun getRealCustomers(active: String): List = + fun getRealCustomers(active: String, @RequestHeader headers: HttpHeaders): List = customerRepository.findByActive(active) - + @Secured @PutMapping("/customers/{id}") fun updateCustomerById(@PathVariable(value = "id") customerId: Long, - @Valid @RequestBody newCustomer: Customer): ResponseEntity { + @Valid @RequestBody newCustomer: Customer, + @RequestHeader headers: HttpHeaders): ResponseEntity { return customerRepository.findById(customerId).map { existingCustomer -> val updatedCustomer: Customer = existingCustomer @@ -43,6 +57,60 @@ class CustomerController ( private val customerRepository: CustomerRepository ) age = newCustomer.age) ResponseEntity.ok().body(customerRepository.save(updatedCustomer)) }.orElse(ResponseEntity.notFound().build()) + } + + + @PostMapping("/registration") + fun registration(@Valid @RequestBody json: String): ResponseEntity<*> { + val customer = Customer() + + val newUser: User = User().fromJson(json) + with (customer) { + email = newUser.username + password = serviceBeans!!.passwordEncoder().encode(newUser.password) + } + + val returnCustomer: Customer? = customerRepository.findByEmail(newUser.username).let { + if ( it == null) { + customerRepository.save(customer) + } else { + null + } + } + + return if ( returnCustomer != null ) { + ResponseEntity.ok().body(returnCustomer) + } else { + ResponseEntity.badRequest().body("Customer exists") + } } + + @GetMapping("/login") + fun login(@Valid @RequestBody json: String): ResponseEntity<*> { + val customer = Customer() + + val newUser: User = User().fromJson(json) + with (customer) { + email = newUser.username + password = newUser.password + } + val returnCustomer: Customer? = customerRepository.findByEmail(newUser.username).let { + if ( it == null) { + null + } else { + if (serviceBeans!!.passwordEncoder().matches(newUser.password, it.password)) { + it + } else { + null + } + + } + } + return if ( returnCustomer != null ) { + ResponseEntity.ok().body(returnCustomer) + } else { + ResponseEntity.badRequest().body("Customer does not exist or the password is wrong") + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt index b4301e6..656fb28 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/CustomerControllerAspect.kt @@ -2,15 +2,13 @@ package com.aitrainer.api.controller import com.aitrainer.api.ApiApplication import com.aitrainer.api.repository.ConfigurationRepository -import org.aspectj.lang.annotation.Around +import org.aspectj.lang.JoinPoint import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before -import org.aspectj.lang.annotation.Pointcut import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component - @Suppress("unused") @Aspect @Component @@ -22,13 +20,11 @@ class CustomerControllerAspect { @Autowired private lateinit var properties: ApplicationProperties - @Suppress("unused") - @Pointcut("execution(* com.aitrainer.api.controller.CustomerController.*())") - fun customerControllerAspect() { - } - - @Before("customerControllerAspect()") - fun loggingAop() { + @Before("execution(* com.aitrainer.api.controller.CustomerController.*(..))") + fun customerControllerAspect(joinPoint: JoinPoint) { + println("customer controller") Singleton.checkDBUpdate(configurationRepository, properties) } -} \ No newline at end of file + +} + diff --git a/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt b/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt index ec4fc01..6db8eda 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/DatabaseCheckSingleton.kt @@ -3,9 +3,7 @@ package com.aitrainer.api.controller import com.aitrainer.api.ApiApplication import com.aitrainer.api.model.Configuration import com.aitrainer.api.repository.ConfigurationRepository -import org.hibernate.Hibernate.isInitialized import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.RestController import java.io.File import java.sql.Connection @@ -73,8 +71,7 @@ object Singleton { } - fun execSQL( sql: String ) { - + private fun execSQL( sql: String ) { if (! this.initialized ) { this.getConnection() } diff --git a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt index 67531b3..acbb9ca 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/ExerciseTypeControllerAspect.kt @@ -2,7 +2,6 @@ package com.aitrainer.api.controller import com.aitrainer.api.ApiApplication import com.aitrainer.api.repository.ConfigurationRepository -import org.aspectj.lang.annotation.Around import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before import org.aspectj.lang.annotation.Pointcut diff --git a/src/main/kotlin/com/aitrainer/api/model/Configuration.kt b/src/main/kotlin/com/aitrainer/api/model/Configuration.kt index 0370a6c..de7b2b1 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Configuration.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Configuration.kt @@ -5,7 +5,6 @@ import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id import javax.validation.constraints.NotBlank -import javax.validation.constraints.Null @Entity data class Configuration ( diff --git a/src/main/kotlin/com/aitrainer/api/model/Customer.kt b/src/main/kotlin/com/aitrainer/api/model/Customer.kt index 490cd5a..9e3014a 100644 --- a/src/main/kotlin/com/aitrainer/api/model/Customer.kt +++ b/src/main/kotlin/com/aitrainer/api/model/Customer.kt @@ -4,13 +4,10 @@ import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id -import javax.validation.constraints.NotBlank @Entity data class Customer ( - @get: NotBlank - - var name: String = "", + var name: String = "", var firstname: String = "", var email: String = "", var age: Int = 0, @@ -20,7 +17,8 @@ data class Customer ( var dateChange: String? = null, var dataPolicyAllowed: Int = 0, var admin: Int = 0, - + var password: String = "", + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val customer_id: Long = 0 ) diff --git a/src/main/kotlin/com/aitrainer/api/model/User.kt b/src/main/kotlin/com/aitrainer/api/model/User.kt new file mode 100644 index 0000000..f1f4ce3 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/User.kt @@ -0,0 +1,15 @@ +package com.aitrainer.api.model + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +data class User ( + var username: String = "", + var password: String = "" +) { + @OptIn(UnstableDefault::class) + fun fromJson(json: String): User { + return Json.parse(serializer(), json) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt index cf0385d..4e6c10b 100644 --- a/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt +++ b/src/main/kotlin/com/aitrainer/api/repository/CustomerRepository.kt @@ -6,5 +6,7 @@ import org.springframework.stereotype.Repository @Repository interface CustomerRepository : JpaRepository { - fun findByActive( active: String? ):List + fun findByActive( active: String? ): List + + fun findByEmail(email: String?): Customer? } diff --git a/src/main/kotlin/com/aitrainer/api/security/AuthenticationControllerAspect.kt b/src/main/kotlin/com/aitrainer/api/security/AuthenticationControllerAspect.kt new file mode 100644 index 0000000..c106785 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/AuthenticationControllerAspect.kt @@ -0,0 +1,31 @@ +package com.aitrainer.api.security + +import com.aitrainer.api.ApiApplication +import com.aitrainer.api.controller.ApplicationProperties +import com.aitrainer.api.controller.Singleton +import com.aitrainer.api.repository.ConfigurationRepository +import org.aspectj.lang.JoinPoint +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Suppress("unused") +@Aspect +@Component +class AuthenticationControllerAspect { + private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName) + + @Autowired + private lateinit var configurationRepository: ConfigurationRepository + @Autowired + private lateinit var properties: ApplicationProperties + + @Before("execution(* com.aitrainer.api.security.JwtAuthenticationController.*(..))") + fun customerControllerAspect(joinPoint: JoinPoint) { + println("auth controller join") + Singleton.checkDBUpdate(configurationRepository, properties) + } + +} diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationController.kt b/src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationController.kt new file mode 100644 index 0000000..9064b5a --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationController.kt @@ -0,0 +1,49 @@ +package com.aitrainer.api.security + +import com.aitrainer.api.service.UserDetailsServiceImpl +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.BadCredentialsException +import org.springframework.security.authentication.DisabledException +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.web.bind.annotation.* +import org.springframework.stereotype.Component + + + +@Component +@RestController +//@CrossOrigin +@RequestMapping("/api") +class JwtAuthenticationController { + @Autowired + private val authenticationManager: AuthenticationManager? = null + + @Autowired + private val jwtTokenUtil: JwtTokenUtil? = null + + @Autowired + private val jwtUserDetailsService: UserDetailsServiceImpl? = null + + @PostMapping("/authenticate") + fun generateAuthenticationToken(@RequestBody authenticationRequest: JwtRequest): ResponseEntity<*> { + + authenticate(authenticationRequest.username!!, authenticationRequest.password!!) + + val userDetails = jwtUserDetailsService + ?.loadUserByUsername(authenticationRequest.username) + val token: String = jwtTokenUtil!!.generateToken(userDetails!!) + return ResponseEntity.ok(JwtResponse(token)) + } + + private fun authenticate(username: String, password: String) { + try { + authenticationManager!!.authenticate(UsernamePasswordAuthenticationToken(username, password)) + } catch (e: DisabledException) { + throw Exception("USER_DISABLED", e) + } catch (e: BadCredentialsException) { + throw Exception("INVALID_CREDENTIALS", e) + } + } +} diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationEntryPoint.kt b/src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationEntryPoint.kt new file mode 100644 index 0000000..2433af8 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtAuthenticationEntryPoint.kt @@ -0,0 +1,23 @@ +package com.aitrainer.api.security + +import org.springframework.security.web.AuthenticationEntryPoint +import org.springframework.stereotype.Component +import java.io.IOException +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.Serializable +import org.springframework.security.core.AuthenticationException + + +@Component +class JwtAuthenticationEntryPoint : AuthenticationEntryPoint, Serializable { + @Throws(IOException::class) + override fun commence(request: HttpServletRequest?, response: HttpServletResponse, + authException: AuthenticationException?) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized") + } + + companion object { + private const val serialVersionUID = -7858869558953243875L + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtRequest.kt b/src/main/kotlin/com/aitrainer/api/security/JwtRequest.kt new file mode 100644 index 0000000..58cbb19 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtRequest.kt @@ -0,0 +1,19 @@ +package com.aitrainer.api.security + +import java.io.Serializable + + +class JwtRequest : Serializable { + var username: String? = null + var password: String? = null + + //default constructor for JSON Parsing + constructor(username: String?, password: String?) { + this.username = username + this.password = password + } + + companion object { + private const val serialVersionUID = 5926468583005150707L + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtRequestFilter.kt b/src/main/kotlin/com/aitrainer/api/security/JwtRequestFilter.kt new file mode 100644 index 0000000..35cdd56 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtRequestFilter.kt @@ -0,0 +1,77 @@ +package com.aitrainer.api.security + +import com.aitrainer.api.service.UserDetailsServiceImpl +import io.jsonwebtoken.ExpiredJwtException +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import java.io.IOException +import javax.servlet.FilterChain +import javax.servlet.ServletException +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + + +@Component +class JwtRequestFilter : OncePerRequestFilter() { + @Autowired + private val jwtUserDetailsService: UserDetailsServiceImpl? = null + + @Autowired + private val jwtTokenUtil: JwtTokenUtil? = null + + //@Autowired + //private lateinit var authenticationController: JwtAuthenticationController + + @Throws(ServletException::class, IOException::class) + override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { + val requestTokenHeader = request.getHeader("Authorization") + var username: String? = null + var jwtToken: String? = null + // JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token + if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer")) { + jwtToken = requestTokenHeader.substring(7) + try { + username = jwtTokenUtil!!.getUsernameFromToken(jwtToken) + } catch (e: IllegalArgumentException) { + println("Unable to get JWT Token") + } catch (e: ExpiredJwtException) { + println("JWT Token has expired") + } + } else if (requestTokenHeader != null && requestTokenHeader.equals("1") ) { + logger.warn("Authenticate") + //val credentials: User = ObjectMapper().readValue(request.inputStream, User::class.java) + + } else { + logger.warn("JWT Token does not begin with Bearer String") + } + + //Once we get the token validate it. + if (username != null && SecurityContextHolder.getContext().authentication == null) { + val userDetails: UserDetails = jwtUserDetailsService!!.loadUserByUsername(username) + + // if token is valid configure Spring Security to manually set authentication + if (jwtTokenUtil!!.validateToken(jwtToken!!, userDetails)) { + val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities) + usernamePasswordAuthenticationToken.details = WebAuthenticationDetailsSource().buildDetails(request) + // After setting the Authentication in the context, we specify + // that the current user is authenticated. So it passes the Spring Security Configurations successfully. + SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken + } + } + chain.doFilter(request, response) + } + + /*private fun readUserCredentials(request: HttpServletRequest): UserCredentials? { + return try { + ObjectMapper().readValue(request.inputStream, UserCredentials::class.java) + } catch (ioe: IOException) { + throw BadCredentialsException("Invalid request", ioe) + } + }*/ +} diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtResponse.kt b/src/main/kotlin/com/aitrainer/api/security/JwtResponse.kt new file mode 100644 index 0000000..d7227fa --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtResponse.kt @@ -0,0 +1,12 @@ +package com.aitrainer.api.security + +import java.io.Serializable + + +class JwtResponse(val token: String) : Serializable { + + companion object { + private const val serialVersionUID = -8091879091924046844L + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtSecurityConfig.kt b/src/main/kotlin/com/aitrainer/api/security/JwtSecurityConfig.kt new file mode 100644 index 0000000..b299268 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtSecurityConfig.kt @@ -0,0 +1,65 @@ +package com.aitrainer.api.security + +import com.aitrainer.api.service.ServiceBeans +import com.aitrainer.api.service.UserDetailsServiceImpl +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +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 + + +@Configuration +@EnableWebSecurity +class JwtSecurityConfig : WebSecurityConfigurerAdapter() { + @Autowired + private val jwtAuthenticationEntryPoint: JwtAuthenticationEntryPoint? = null + + @Autowired + private val jwtUserDetailsService: UserDetailsServiceImpl? = null + + @Autowired + private val jwtRequestFilter: JwtRequestFilter? = null + + @Autowired + private val serviceBeans: ServiceBeans? = null + + override fun configure(auth: AuthenticationManagerBuilder?) { + auth!!.userDetailsService(jwtUserDetailsService).passwordEncoder(serviceBeans!!.passwordEncoder()) + } + + @Bean + @Throws(Exception::class) + override fun authenticationManagerBean(): AuthenticationManager { + return super.authenticationManagerBean() + } + + @Throws(Exception::class) + override fun configure(httpSecurity: HttpSecurity) { + + // We don't need CSRF for this example + httpSecurity. + csrf().disable(). + // dont authenticate this particular request + authorizeRequests().antMatchers("/api/authenticate").permitAll(). + // all other requests need to be authenticated + anyRequest().authenticated().and(). + // make sure we use stateless session; session won't be used to + // store user's state. + exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and(). + // Add a filter to validate the tokens with every request + //addFilterAt(JwtAuthenticationFilter(authenticationManagerBean()), UsernamePasswordAuthenticationFilter::class.java). + addFilterAfter(jwtRequestFilter, UsernamePasswordAuthenticationFilter::class.java). + sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + + + } + + +} diff --git a/src/main/kotlin/com/aitrainer/api/security/JwtTokenUtil.kt b/src/main/kotlin/com/aitrainer/api/security/JwtTokenUtil.kt new file mode 100644 index 0000000..8581cf3 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/security/JwtTokenUtil.kt @@ -0,0 +1,67 @@ +package com.aitrainer.api.security + +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.stereotype.Component +import java.util.* +import java.io.Serializable + +import io.jsonwebtoken.Claims +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm + + +@Component +class JwtTokenUtil : Serializable { + @Value("\${jwt.secret}") + private val secret: String? = null + fun getUsernameFromToken(token: String?): String { + return getClaimFromToken(token, Claims::getSubject) + } + + fun getIssuedAtDateFromToken(token: String?): Date { + return getClaimFromToken(token, Claims::getIssuedAt) + } + + fun getExpirationDateFromToken(token: String?): Date { + return getClaimFromToken(token, Claims::getExpiration) + } + + fun getClaimFromToken( token: String?, claimsResolver: ( Claims.()-> T ) ): T { + val claims: Claims = getAllClaimsFromToken(token) + return claims.claimsResolver() + } + + private fun getAllClaimsFromToken(token: String?): Claims { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).body + } + + private fun isTokenExpired(token: String): Boolean { + val expiration: Date = getExpirationDateFromToken(token) + return expiration.before(Date()) + } + + fun generateToken(userDetails: UserDetails): String { + val claims: Map = HashMap() + return doGenerateToken(claims, userDetails.username) + } + + private fun doGenerateToken(claims: Map, subject: String): String { + return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(Date(System.currentTimeMillis())) + .setExpiration(Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact() + } + + fun canTokenBeRefreshed(token: String): Boolean { + return !isTokenExpired(token) + } + + fun validateToken(token: String, userDetails: UserDetails): Boolean { + val username = getUsernameFromToken(token) + return username == userDetails.username && !isTokenExpired(token) + } + + companion object { + private const val serialVersionUID = -2550185165626007488L + const val JWT_TOKEN_VALIDITY = 5 * 60 * 60.toLong() + } +} diff --git a/src/main/kotlin/com/aitrainer/api/service/ServiceBeans.kt b/src/main/kotlin/com/aitrainer/api/service/ServiceBeans.kt new file mode 100644 index 0000000..8880030 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/service/ServiceBeans.kt @@ -0,0 +1,19 @@ +package com.aitrainer.api.service + +import org.springframework.context.annotation.Bean +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.stereotype.Component + +/* + Commonly used Beans + */ + +@Component +class ServiceBeans { + + @Bean + fun passwordEncoder(): PasswordEncoder { + return BCryptPasswordEncoder() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/service/UserDetailsServiceImpl.kt b/src/main/kotlin/com/aitrainer/api/service/UserDetailsServiceImpl.kt new file mode 100644 index 0000000..a3482e4 --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/service/UserDetailsServiceImpl.kt @@ -0,0 +1,34 @@ +package com.aitrainer.api.service + +import com.aitrainer.api.model.Customer +import com.aitrainer.api.repository.CustomerRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import kotlin.collections.HashSet + +@Service +class UserDetailsServiceImpl: UserDetailsService { + + @Autowired + private lateinit var customerRepository: CustomerRepository + + @Override + @Transactional(readOnly = true) + override fun loadUserByUsername(username: String?): UserDetails { + val customer: Customer? = customerRepository.findByEmail(username) + + val grantedAuthorities = HashSet() + grantedAuthorities.add(SimpleGrantedAuthority("user")) + + if (customer != null) { + return User(customer.email, customer.password, grantedAuthorities) + } else {throw Exception("User does not exist")} + + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b85926c..fa36823 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -16,4 +16,6 @@ logging.config=classpath:logback-spring.xml logging.file=logs # if the database structure has been changed, increment this version number -application.version=0.0.2 \ No newline at end of file +application.version=0.0.3 + +jwt.secret=aitrainer \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/AuthenticationTest.kt b/src/test/kotlin/com/aitrainer/api/test/AuthenticationTest.kt new file mode 100644 index 0000000..83c0b35 --- /dev/null +++ b/src/test/kotlin/com/aitrainer/api/test/AuthenticationTest.kt @@ -0,0 +1,25 @@ +package com.aitrainer.api.test + +import com.aitrainer.api.security.JwtAuthenticationController +import com.aitrainer.api.security.JwtRequest +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import kotlin.test.assertEquals + +@SpringBootTest +class AuthenticationTest { + + @Autowired + private lateinit var authController: JwtAuthenticationController + @Test + fun testAuthentication() { + val response: ResponseEntity<*> + val jwtRequest = JwtRequest("bosi", "andio2009") + response = authController.generateAuthenticationToken(jwtRequest) + assertEquals(response.statusCode, HttpStatus.OK) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt index d1c5a5a..86c6996 100644 --- a/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt +++ b/src/test/kotlin/com/aitrainer/api/test/CustomerTests.kt @@ -1,16 +1,32 @@ package com.aitrainer.api.test +import com.aitrainer.api.controller.CustomerController import com.aitrainer.api.model.Customer +import com.aitrainer.api.model.User import com.aitrainer.api.repository.CustomerRepository +import com.aitrainer.api.service.ServiceBeans +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import kotlin.test.assertEquals import kotlin.test.assertNotNull @SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class CustomerTests { + @Autowired + private var serviceBean: ServiceBeans? = null + + @BeforeAll + fun init() { + if ( serviceBean == null ) { serviceBean = ServiceBeans() } + } + @Autowired private lateinit var customerRepository: CustomerRepository private var insertedId: Long? = null @@ -49,6 +65,58 @@ class CustomerTests { assertEquals( customer.firstname, "Tiborka") customerRepository.delete(updatedCustomer) - } + } + @Test + fun testRegistration() { + val json = "{\"username\":\"bosi@example.com\",\"password\":\"94385\"}" + val user: User = User().fromJson(json) + assertEquals(user.username, "bosi@example.com") + val customer = Customer() + with(customer) { + email = user.username + password = user.password + } + val customerController = CustomerController(customerRepository) + customerController.serviceBeans = serviceBean + var response: ResponseEntity<*> = customerController.registration(json) + val newCustomer: Customer? = response.body as Customer + assertEquals(response.statusCode, HttpStatus.OK) + + val json2 = "{\"username\":\"bosi@example.com\",\"password\":\"934345\"}" + response = customerController.registration(json2) + assertEquals(response.statusCode, HttpStatus.BAD_REQUEST) + + if ( newCustomer != null) { + customerRepository.delete(newCustomer) + } + } + + @Test fun testLogin() { + val json = "{\"username\":\"bosi2@example.com\",\"password\":\"94333385\"}" + val user: User = User().fromJson(json) + val customer = Customer() + with(customer) { + email = user.username + password = user.password + } + val customerController = CustomerController(customerRepository) + customerController.serviceBeans = serviceBean + var response: ResponseEntity<*> = customerController.registration(json) + val newCustomer: Customer? = response.body as Customer + assertEquals(response.statusCode, HttpStatus.OK) + + response = customerController.login(json) + val loginedCustomer: Customer? = response.body as Customer + assertEquals(response.statusCode, HttpStatus.OK) + if ( loginedCustomer != null ) { + assertEquals(loginedCustomer.email, ("bosi2@example.com") ) + } else { + assert(true) + } + + if ( newCustomer != null) { + customerRepository.delete(newCustomer) + } + } } \ No newline at end of file