diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 0ae4237..604b43e 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -2,7 +2,7 @@ name: Release Pipeline on: release: - types: published + types: [ published ] env: DOCKER_IMAGE: wire-bot/poll @@ -129,3 +129,53 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_RELEASE }} # Notify every release if: always() + + quay_publish: + name: Quay Publish Pipeline + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Set Release Version + # use latest tag as release version + run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV + + # extract metadata for labels https://github.com/crazy-max/ghaction-docker-meta + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v1 + with: + images: quay.io/wire/poll-bot + + # setup docker actions https://github.com/docker/build-push-action + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + # login to GCR repo + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + push: true + build-args: | + release_version=${{ env.RELEASE_VERSION }} + + # Send webhook to Wire using Slack Bot + - name: Webhook to Wire + uses: 8398a7/action-slack@v2 + with: + status: ${{ job.status }} + author_name: ${{ env.SERVICE_NAME }} Quay Production Publish + env: + SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_RELEASE }} + # Send message only if previous step failed + if: always() diff --git a/.github/workflows/quay.yml b/.github/workflows/quay.yml new file mode 100644 index 0000000..0f8d033 --- /dev/null +++ b/.github/workflows/quay.yml @@ -0,0 +1,55 @@ +name: Quay Deployment + +on: + # manual dispatch + workflow_dispatch: + inputs: + tag: + description: 'Docker image tag.' + required: true +jobs: + publish: + name: Deploy to staging + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + # extract metadata for labels https://github.com/crazy-max/ghaction-docker-meta + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v1 + with: + images: quay.io/wire/poll-bot + + # setup docker actions https://github.com/docker/build-push-action + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + # login to GCR repo + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + tags: quay.io/wire/poll-bot:${{ github.event.inputs.tag }} + labels: ${{ steps.docker_meta.outputs.labels }} + push: true + build-args: | + release_version=${{ github.event.inputs.tag }} + # Send webhook to Wire using Slack Bot + - name: Webhook to Wire + uses: 8398a7/action-slack@v2 + with: + status: ${{ job.status }} + author_name: Poll Bot Quay Custom Tag Pipeline + env: + SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_CI }} + # Send message only if previous step failed + if: always() diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 0fc0924..f7fbc30 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -7,7 +7,7 @@ on: env: DOCKER_IMAGE: wire-bot/poll - SERVICE_NAME: poll + SERVICE_NAME: polls jobs: publish: @@ -67,7 +67,7 @@ jobs: # Get the GKE credentials so we can deploy to the cluster - name: Obtain k8s credentials env: - GKE_CLUSTER: anayotto + GKE_CLUSTER: dagobah GKE_ZONE: europe-west1-c run: | gcloud container clusters get-credentials "$GKE_CLUSTER" --zone "$GKE_ZONE" @@ -77,8 +77,8 @@ jobs: env: SERVICE: ${{ env.SERVICE_NAME }} run: | - kubectl delete pod -l name=$SERVICE -n staging - kubectl describe pod -l name=$SERVICE -n staging + kubectl delete pod -l app=$SERVICE -n staging + kubectl describe pod -l app=$SERVICE -n staging # Send webhook to Wire using Slack Bot - name: Webhook to Wire diff --git a/build.gradle.kts b/build.gradle.kts index 3410f77..0d39b00 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.4.10" + kotlin("jvm") version "1.5.0" application distribution id("net.nemerosa.versioning") version "2.14.0" @@ -15,56 +15,58 @@ application { } repositories { - jcenter() + mavenCentral() } dependencies { // stdlib implementation(kotlin("stdlib-jdk8")) // extension functions - implementation("pw.forst.tools", "katlib", "1.1.2") - + implementation("pw.forst", "katlib", "2.0.1") // Ktor server dependencies - val ktorVersion = "1.4.1" + val ktorVersion = "1.5.4" implementation("io.ktor", "ktor-server-core", ktorVersion) implementation("io.ktor", "ktor-server-netty", ktorVersion) implementation("io.ktor", "ktor-jackson", ktorVersion) implementation("io.ktor", "ktor-websockets", ktorVersion) - + // explicitly set the reflect library to same version as the kotlin + implementation("org.jetbrains.kotlin", "kotlin-reflect", "1.5.0") // Ktor client dependencies implementation("io.ktor", "ktor-client-json", ktorVersion) - implementation("io.ktor", "ktor-client-jackson", ktorVersion) + implementation("io.ktor", "ktor-client-jackson", ktorVersion) { + exclude("org.jetbrains.kotlin", "kotlin-reflect") + } implementation("io.ktor", "ktor-client-apache", ktorVersion) implementation("io.ktor", "ktor-client-logging-jvm", ktorVersion) // Prometheus metrics implementation("io.ktor", "ktor-metrics-micrometer", ktorVersion) - implementation("io.micrometer", "micrometer-registry-prometheus", "1.5.5") + implementation("io.micrometer", "micrometer-registry-prometheus", "1.6.6") // logging - implementation("io.github.microutils", "kotlin-logging", "2.0.3") + implementation("io.github.microutils", "kotlin-logging", "2.0.6") // if-else in logback.xml implementation("org.codehaus.janino", "janino", "3.1.2") implementation("ch.qos.logback", "logback-classic", "1.2.3") // DI - val kodeinVersion = "6.5.5" - implementation("org.kodein.di", "kodein-di-generic-jvm", kodeinVersion) + val kodeinVersion = "7.5.0" + implementation("org.kodein.di", "kodein-di-jvm", kodeinVersion) implementation("org.kodein.di", "kodein-di-framework-ktor-server-jvm", kodeinVersion) // database - implementation("org.postgresql", "postgresql", "42.2.2") + implementation("org.postgresql", "postgresql", "42.2.20") - val exposedVersion = "0.27.1" + val exposedVersion = "0.31.1" implementation("org.jetbrains.exposed", "exposed-core", exposedVersion) implementation("org.jetbrains.exposed", "exposed-dao", exposedVersion) implementation("org.jetbrains.exposed", "exposed-jdbc", exposedVersion) implementation("org.jetbrains.exposed", "exposed-java-time", exposedVersion) - implementation("pw.forst", "exposed-upsert", "1.0") + implementation("pw.forst", "exposed-upsert", "1.1.0") // database migrations from the code - implementation("org.flywaydb", "flyway-core", "7.0.0") + implementation("org.flywaydb", "flyway-core", "7.8.2") } tasks { diff --git a/docker-compose.yml b/docker-compose.yml index 4f951b0..ba425ad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.7' +version: '3.8' services: bot: build: @@ -11,26 +11,17 @@ services: env_file: .env ports: - 8080:8080 - networks: - - poll-bot-net depends_on: - db db: - image: postgres:12.2 + image: postgres:13.1 container_name: poll-bot-db env_file: .env ports: - 5432:5432 - networks: - - poll-bot-net volumes: - poll-bot-db:/var/lib/postgresql/data/ - -networks: - poll-bot-net: - driver: bridge - volumes: poll-bot-db: diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d9ca16..0f80bbf 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.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7..43369d7 100755 --- a/gradlew +++ b/gradlew @@ -26,22 +26,22 @@ # 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 +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`" +SAVED="$(pwd)" +cd "$(dirname \"$PRG\")/" >/dev/null || true +APP_HOME="$(pwd -P)" cd "$SAVED" >/dev/null APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +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"' @@ -49,15 +49,15 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn () { - echo "$*" +warn() { + echo "$*" } -die () { - echo - echo "$*" - echo - exit 1 +die() { + echo + echo "$*" + echo + exit 1 } # OS specific support (must be 'true' or 'false'). @@ -65,117 +65,118 @@ cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +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 +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 + 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. + 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" +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\"" + 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)" +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 - # 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 + 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 " " +save() { + for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done + echo " " } -APP_ARGS=`save "$@"` +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" diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9..107acd3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,28 +64,14 @@ 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% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/integrationTests/.dockerignore b/integrationTests/.dockerignore deleted file mode 100644 index 6a3190d..0000000 --- a/integrationTests/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -.gradle -.idea -build -.gitignore -LICENSE -*.md -Dockerfile -docker-compose.yml diff --git a/integrationTests/.env-integration b/integrationTests/.env-integration deleted file mode 100644 index b7ca413..0000000 --- a/integrationTests/.env-integration +++ /dev/null @@ -1,15 +0,0 @@ -# database -POSTGRES_USER=bot-integration-tests -POSTGRES_PASSWORD=integration-password -POSTGRES_DB=poll-bot-integration - -# application -DB_USER=bot-integration-tests -DB_PASSWORD=integration-password -DB_URL=jdbc:postgresql://db:5432/poll-bot-integration -SERVICE_TOKEN=integration-token -USE_WEB_SOCKETS=false -PROXY_DOMAIN=http://integration-tests:8081 - -# integration tests -BOT_API=http://bot:8080 diff --git a/integrationTests/Dockerfile b/integrationTests/Dockerfile deleted file mode 100644 index 9ea266b..0000000 --- a/integrationTests/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine AS build -LABEL description="Integration tests for Wire Poll Bot" -LABEL project="wire-bots:polls-integration-tests" - -ENV PROJECT_ROOT /src -WORKDIR $PROJECT_ROOT - -# Copy gradle settings -COPY build.gradle.kts settings.gradle.kts gradle.properties gradlew $PROJECT_ROOT/ -# Make sure gradlew is executable -RUN chmod +x gradlew -# Copy gradle specification -COPY gradle $PROJECT_ROOT/gradle -# Download gradle -RUN ./gradlew --version - -# Copy project and build -COPY . $PROJECT_ROOT -RUN ./gradlew assemble --info --no-daemon - -# Execute tests -EXPOSE 8081 -CMD ./gradlew test --no-daemon --info diff --git a/integrationTests/Makefile b/integrationTests/Makefile deleted file mode 100644 index eb06297..0000000 --- a/integrationTests/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -run: - ./gradlew run - -integration-tests-pipeline: - docker-compose stop; \ - echo "y" | docker-compose rm; \ - docker-compose up -d bot; \ - docker-compose up --abort-on-container-exit integration-tests; \ - RESULT=$$?; \ - docker-compose stop; \ - exit $$RESULT diff --git a/integrationTests/README.md b/integrationTests/README.md deleted file mode 100644 index cc1c9ea..0000000 --- a/integrationTests/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Wire Poll Bot Integration tests -[Wire](https://wire.com/) bot for the polls - integration test. -The black box tests use REST API of the bot and are emulating the Roman proxy. - -This test structure assumes that the bot is running somewhere -and hooks are pointed to this API. - -## Execution -For execution inside `docker-compose` environment please run `make integration-tests-pipeline`. - -## Environment -```kotlin - /** - * Bot API URL. - */ - const val BOT_API = "BOT_API" - - /** - * Token which is used for the auth of proxy. - */ - const val SERVICE_TOKEN = "SERVICE_TOKEN" -``` - -## Dev Stack -* HTTP Server - [Ktor](https://ktor.io/) -* HTTP Client - [CIO](https://ktor.io/clients/http-client/engines.html) under [Ktor](https://ktor.io/) -* Dependency Injection - [Kodein](https://github.com/Kodein-Framework/Kodein-DI) -* Build system - [Gradle](https://gradle.org/) diff --git a/integrationTests/build.gradle.kts b/integrationTests/build.gradle.kts deleted file mode 100644 index cc96d44..0000000 --- a/integrationTests/build.gradle.kts +++ /dev/null @@ -1,75 +0,0 @@ -plugins { - kotlin("jvm") version "1.3.70" - application - distribution -} - -group = "com.wire.bots.polls.integration-tests" -version = "0.1" - -val mainClass = "com.wire.bots.polls.PollBotKt" - -application { - mainClassName = mainClass -} - -repositories { - jcenter() -} - -dependencies { - // stdlib - implementation(kotlin("stdlib-jdk8")) - // extension functions - implementation("ai.blindspot.ktoolz", "ktoolz", "1.0.3") - - // Ktor server dependencies - val ktorVersion = "1.3.1" - implementation("io.ktor", "ktor-server-core", ktorVersion) - implementation("io.ktor", "ktor-server-netty", ktorVersion) - implementation("io.ktor", "ktor-jackson", ktorVersion) - implementation("io.ktor", "ktor-websockets", ktorVersion) - - // Ktor client dependencies - implementation("io.ktor", "ktor-client-json", ktorVersion) - implementation("io.ktor", "ktor-client-jackson", ktorVersion) - implementation("io.ktor", "ktor-client-websockets", ktorVersion) - implementation("io.ktor", "ktor-client-cio", ktorVersion) - - // logging - implementation("io.github.microutils", "kotlin-logging", "1.7.8") - implementation("org.slf4j", "slf4j-simple", "1.6.1") - - // DI - val kodeinVersion = "6.5.0" - implementation("org.kodein.di", "kodein-di-generic-jvm", kodeinVersion) - implementation("org.kodein.di", "kodein-di-framework-ktor-server-jvm", kodeinVersion) - - // unit testing - testImplementation(kotlin("test")) - testImplementation(kotlin("test-junit5")) - testImplementation("org.junit.jupiter", "junit-jupiter-engine", "5.6.0") -} - -tasks { - compileKotlin { - kotlinOptions.jvmTarget = "1.8" - } - compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" - } - - test { - useJUnitPlatform() - } - - register("fatJar") { - manifest { - attributes["Main-Class"] = mainClass - } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - archiveFileName.set("polls.jar") - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) - from(sourceSets.main.get().output) - } -} diff --git a/integrationTests/docker-compose.yml b/integrationTests/docker-compose.yml deleted file mode 100644 index 19cf384..0000000 --- a/integrationTests/docker-compose.yml +++ /dev/null @@ -1,42 +0,0 @@ -version: '3.3' -services: - bot: - build: - context: ../ - dockerfile: Dockerfile - image: lukaswire/polls-integration - container_name: poll-bot-integration - env_file: .env-integration - ports: - - 8080:8080 - depends_on: - - db - networks: - - bot-integration-tests-network - - integration-tests: - build: - context: . - dockerfile: Dockerfile - image: lukaswire/polls-integration-tests - container_name: poll-bot-integration-tests - env_file: .env-integration - ports: - - 8081:8081 - depends_on: - - bot - networks: - - bot-integration-tests-network - - db: - image: postgres:12.2 - container_name: poll-bot-db-integration-tests - env_file: .env-integration - ports: - - 5432:5432 - networks: - - bot-integration-tests-network - -networks: - bot-integration-tests-network: - driver: bridge diff --git a/integrationTests/gradle.properties b/integrationTests/gradle.properties deleted file mode 100644 index 7fc6f1f..0000000 --- a/integrationTests/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -kotlin.code.style=official diff --git a/integrationTests/gradle/wrapper/gradle-wrapper.jar b/integrationTests/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 87b738c..0000000 Binary files a/integrationTests/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/integrationTests/gradle/wrapper/gradle-wrapper.properties b/integrationTests/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index b7c8c5d..0000000 --- a/integrationTests/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/integrationTests/gradlew b/integrationTests/gradlew deleted file mode 100755 index af6708f..0000000 --- a/integrationTests/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/integrationTests/gradlew.bat b/integrationTests/gradlew.bat deleted file mode 100644 index 6d57edc..0000000 --- a/integrationTests/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" - -@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/integrationTests/settings.gradle.kts b/integrationTests/settings.gradle.kts deleted file mode 100644 index a3ce49f..0000000 --- a/integrationTests/settings.gradle.kts +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = "polls" - diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/PollBot.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/PollBot.kt deleted file mode 100644 index 6dfe39e..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/PollBot.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.wire.bots.polls.integration_tests - -import com.wire.bots.polls.integration_tests.setup.init -import io.ktor.application.Application -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import io.ktor.util.KtorExperimentalAPI - -@KtorExperimentalAPI -fun main(args: Array) { - embeddedServer(Netty, port = 8081, module = Application::init).start() -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/BotApiConfiguration.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/BotApiConfiguration.kt deleted file mode 100644 index 5dfec7a..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/BotApiConfiguration.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wire.bots.polls.integration_tests.dto - -/** - * Configuration saying where does the bot run. - */ -data class BotApiConfiguration( - /** - * URL of the bot eg. localhost:8080 - */ - val baseUrl: String, - /** - * Token for bearer auth. - */ - val token: String -) diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/Conversation.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/Conversation.kt deleted file mode 100644 index ed05463..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/Conversation.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.wire.bots.polls.integration_tests.dto - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.wire.bots.polls.integration_tests.dto.serialization.ConversationDeserializer - -/** - * Conversation API - receiving JSON from the bot. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonDeserialize(using = ConversationDeserializer::class) -data class Conversation( - val type: String, - val text: String? = null, - val image: String? = null, - val poll: Poll? = null -) - -/** - * Poll. - */ -interface Poll { - /** - * ID of the poll - */ - val id: String -} - -/** - * Poll representation for the proxy. - */ -data class PollCreation( - override val id: String, - /** - * Question asked. - */ - val body: String, - /** - * Ordered options. - */ - val buttons: List -) : Poll { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as PollCreation - - if (body != other.body) return false - if (buttons != other.buttons) return false - - return true - } - - override fun hashCode(): Int { - var result = body.hashCode() - result = 31 * result + buttons.hashCode() - return result - } -} - -/** - * Returns representation of this poll in the message. - */ -fun PollCreation.toCreateString() = "/poll \"$body\" ${buttons.joinToString(" ") { "\"$it\"" }}" - -data class PollConfirmation( - override val id: String, - /** - * Option voted. - */ - val offset: Int, - /** - * User who voted. - */ - val userId: String -) : Poll - -fun pollConfirmationMessage(poll: PollConfirmation) = Conversation( - type = "poll.action.confirmation", - poll = poll -) - -fun pollCreationMessage(poll: PollCreation) = Conversation( - type = "poll.new", - poll = poll -) - -fun textMessage(text: String) = Conversation( - type = "text", - text = text -) - -//{ -// "type": "string", -// "text": "string", -// "image": "string", -// "poll": { -// "id": "string", -// "body": "string", -// "buttons": [ -// "string" -// ], -// "offset": 0 -//} -//} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/ProxyMessage.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/ProxyMessage.kt deleted file mode 100644 index 2b5cdd6..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/ProxyMessage.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.wire.bots.polls.integration_tests.dto - -/** - * Message received by the bot from the proxy. - * - */ -data class ProxyMessage( - /** - * ID of the bot = the bot should accept the message only when the ID matches. - */ - val botId: String, - /** - * Type of the message. - */ - val type: String, - /** - * User who sent a message. - */ - val userId: String? = null, - /** - * Message ID. - */ - val messageId: String? = null, - /** - * Token that should be used for the reply. - */ - val token: String? = null, - /** - * Text of the message. - */ - val text: String? = null, - /** - * Id of the quoted message, when the user replies on something, this is id of something. - */ - val refMessageId: String? = null, - /** - * When this and [refMessageId] is filled, the user liked the message with id [refMessageId]. - */ - val reaction: String? = null, - - /** - * For votes. - */ - val poll: Poll? = null -) { - data class Poll( - val id: String, - val offset: Int - ) -} - - -/** - * To send bot request request. - */ -fun botRequest(userId: String, botId: String, token: String) = ProxyMessage( - botId = botId, - userId = userId, - type = "conversation.bot_request", - token = token -) - -/** - * To send init request. - */ -fun init(userId: String, botId: String, token: String) = ProxyMessage( - botId = botId, - userId = userId, - type = "conversation.init", - token = token -) - -/** - * To send new text. - */ -fun newText(userId: String, botId: String, token: String, text: String) = ProxyMessage( - botId = botId, - userId = userId, - type = "conversation.new_text", - token = token, - text = text -) - - -/** - * To send vote. - */ -fun voteUsingText(userId: String, botId: String, token: String, pollId: String, option: Int) = ProxyMessage( - botId = botId, - userId = userId, - type = "conversation.new_text", - refMessageId = pollId, - text = option.toString(), - token = token -) - -/** - * To send vote. - */ -fun voteUsingObject(userId: String, botId: String, token: String, pollId: String, option: Int) = ProxyMessage( - botId = botId, - userId = userId, - type = "conversation.poll.action", - token = token, - poll = ProxyMessage.Poll( - id = pollId, - offset = option - ) -) - - -/** - * To send reaction. - */ -fun reaction(userId: String, botId: String, token: String, refMessageId: String) = ProxyMessage( - botId = botId, - userId = userId, - type = "conversation.reaction", - refMessageId = refMessageId, - text = "some-emoji", - token = token -) diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/ProxyResponseMessage.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/ProxyResponseMessage.kt deleted file mode 100644 index a36f8aa..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/ProxyResponseMessage.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wire.bots.polls.integration_tests.dto - -/** - * Respond received from the proxy to every message from the bot. - */ -data class ProxyResponseMessage( - /** - * ID of the message bot sent. - */ - val messageId: String -) diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/serialization/ConversationDeserializer.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/serialization/ConversationDeserializer.kt deleted file mode 100644 index d79a1de..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/dto/serialization/ConversationDeserializer.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.wire.bots.polls.integration_tests.dto.serialization - -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import com.wire.bots.polls.integration_tests.dto.Conversation -import com.wire.bots.polls.integration_tests.dto.Poll -import com.wire.bots.polls.integration_tests.dto.PollConfirmation -import com.wire.bots.polls.integration_tests.dto.PollCreation - -/** - * Custom deserialization for the [Conversation] class. - */ -class ConversationDeserializer : JsonDeserializer() { - - override fun deserialize(jp: JsonParser, ctxt: DeserializationContext?): Conversation { - val root = jp.readValueAsTree() - - val pollNode = root["poll"] ?: return root.toConversation() - - val mapper = jacksonObjectMapper() - val poll = when (root["type"]?.asText()) { - "poll.action.confirmation" -> mapper.readValue(pollNode.toString()) - "poll.new" -> mapper.readValue(pollNode.toString()) - else -> throw IllegalArgumentException("Invalid type for the poll!") - } - - return root.toConversation(poll) - } - - private fun JsonNode.toConversation(poll: Poll? = null) = Conversation( - type = this["type"].asText(), - text = this["text"]?.asText(), - image = this["image"]?.asText(), - poll = poll - ) -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/routing/Routing.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/routing/Routing.kt deleted file mode 100644 index 3b6c58c..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/routing/Routing.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.wire.bots.polls.integration_tests.routing - -import pw.forst.tools.katlib.whenNull -import com.wire.bots.polls.integration_tests.dto.Conversation -import com.wire.bots.polls.integration_tests.dto.ProxyResponseMessage -import com.wire.bots.polls.integration_tests.store.tokenStorage -import io.ktor.application.call -import io.ktor.http.HttpStatusCode -import io.ktor.request.header -import io.ktor.request.receive -import io.ktor.response.respond -import io.ktor.routing.Routing -import io.ktor.routing.get -import io.ktor.routing.post -import java.util.UUID - -/** - * Register routes to the KTor. - */ -fun Routing.registerRoutes() { - - get("/") { - call.respond("Integration test is up and running.") - } - - post("/conversation") { - val conversation = call.receive() - val token = call.request - .header("Authorization") - ?.substringAfter("Bearer ") - .whenNull { - call.respond(HttpStatusCode.Unauthorized, "Missing header Authorization") - } ?: return@post - - // store the received payload under the token id - if(tokenStorage.containsKey(token)) { - // if it's second time sending under the same token, it's stats when all users voted - tokenStorage["$token-stats"] - } else { - tokenStorage[token] = conversation - } - // just generate random id, because bot does not read that - call.respond(ProxyResponseMessage(UUID.randomUUID().toString())) - } -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/services/BotApiService.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/services/BotApiService.kt deleted file mode 100644 index 02338bb..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/services/BotApiService.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.wire.bots.polls.integration_tests.services - -import com.wire.bots.polls.integration_tests.dto.BotApiConfiguration -import com.wire.bots.polls.integration_tests.dto.ProxyMessage -import io.ktor.client.HttpClient -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.url -import io.ktor.client.statement.HttpResponse -import io.ktor.http.ContentType -import io.ktor.http.contentType -import io.ktor.http.isSuccess -import mu.KLogging - -/** - * Service for communication with bot. - */ -class BotApiService(private val client: HttpClient, private val botApi: BotApiConfiguration) { - - private companion object : KLogging() { - const val messagesPath = "/messages" - } - - /** - * Sends conversation message to the bot. - */ - suspend fun send(message: ProxyMessage) { - val endpoint = botApi.baseUrl + messagesPath - - val response = client.post(body = message) { - url(endpoint) - contentType(ContentType.Application.Json) - header("Authorization", "Bearer ${botApi.token}") - } - - if (!response.status.isSuccess()) { - throw Exception("Response is not success! Actual response: ${response.status}.") - } - } -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/ConfigurationDependencyInjection.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/ConfigurationDependencyInjection.kt deleted file mode 100644 index dedea67..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/ConfigurationDependencyInjection.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.wire.bots.polls.integration_tests.setup - -import pw.forst.tools.katlib.getEnv -import pw.forst.tools.katlib.whenNull -import com.wire.bots.polls.integration_tests.dto.BotApiConfiguration -import com.wire.bots.polls.integration_tests.setup.EnvConfigVariables.BOT_API -import com.wire.bots.polls.integration_tests.setup.EnvConfigVariables.SERVICE_TOKEN -import mu.KLogging -import org.kodein.di.Kodein.MainBuilder -import org.kodein.di.generic.bind -import org.kodein.di.generic.instance -import org.kodein.di.generic.singleton - -private val logger = KLogging().logger("EnvironmentLoaderLogger") - -private fun getEnvOrLogDefault(env: String, defaultValue: String) = getEnv(env).whenNull { - logger.warn { "Env variable $env not set! Using default value - $defaultValue" } -} ?: defaultValue - -/** - * Loads the DI container with configuration from the system environment. - */ -fun MainBuilder.bindConfiguration() { - - bind() with singleton { - BotApiConfiguration( - baseUrl = instance("bot-api-url"), - token = instance("proxy-auth") - ) - } - - bind("bot-api-url") with singleton { - getEnvOrLogDefault(BOT_API, "http://localhost:8080") - } - - bind("proxy-auth") with singleton { - getEnvOrLogDefault(SERVICE_TOKEN, "local-token") - } -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/DependencyInjection.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/DependencyInjection.kt deleted file mode 100644 index 477e557..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/DependencyInjection.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.wire.bots.polls.integration_tests.setup - -import com.wire.bots.polls.integration_tests.services.BotApiService -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.features.json.JacksonSerializer -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.websocket.WebSockets -import io.ktor.util.KtorExperimentalAPI -import mu.KLogger -import mu.KLogging -import org.kodein.di.Kodein.MainBuilder -import org.kodein.di.generic.bind -import org.kodein.di.generic.instance -import org.kodein.di.generic.singleton - -@KtorExperimentalAPI -fun MainBuilder.configureContainer() { - - bind() with singleton { - HttpClient(CIO) { - install(WebSockets) - install(JsonFeature) { - serializer = JacksonSerializer() - } - } - } - - bind("routing-logger") with singleton { KLogging().logger("Routing") } - bind("install-logger") with singleton { KLogging().logger("KtorStartup") } - - bind() with singleton { BotApiService(instance(), instance()) } -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/EnvConfigVariables.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/EnvConfigVariables.kt deleted file mode 100644 index d76b1f5..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/EnvConfigVariables.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.wire.bots.polls.integration_tests.setup - -/** - * Contains variables that are loaded from the environment. - */ -object EnvConfigVariables { - /** - * Bot API URL. - */ - const val BOT_API = "BOT_API" - - /** - * Token which is used for the auth of proxy. - */ - const val SERVICE_TOKEN = "SERVICE_TOKEN" -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/KodeinSetup.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/KodeinSetup.kt deleted file mode 100644 index ccb69a1..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/KodeinSetup.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.wire.bots.polls.integration_tests.setup - -import io.ktor.application.Application -import io.ktor.util.KtorExperimentalAPI -import org.kodein.di.ktor.kodein - -/** - * Inits and sets up DI container. - */ -@KtorExperimentalAPI -fun Application.setupKodein() { - kodein { - bindConfiguration() - configureContainer() - } -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/KtorInstallation.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/KtorInstallation.kt deleted file mode 100644 index f7c256d..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/setup/KtorInstallation.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.wire.bots.polls.integration_tests.setup - -import com.fasterxml.jackson.databind.SerializationFeature -import com.wire.bots.polls.integration_tests.routing.registerRoutes -import io.ktor.application.Application -import io.ktor.application.install -import io.ktor.features.CallLogging -import io.ktor.features.ContentNegotiation -import io.ktor.features.DefaultHeaders -import io.ktor.http.cio.websocket.pingPeriod -import io.ktor.http.cio.websocket.timeout -import io.ktor.jackson.jackson -import io.ktor.routing.routing -import io.ktor.util.KtorExperimentalAPI -import io.ktor.websocket.WebSockets -import mu.KLogger -import org.kodein.di.generic.instance -import org.kodein.di.ktor.kodein -import java.text.DateFormat -import java.time.Duration - - -/** - * Loads the application. - */ -@KtorExperimentalAPI -fun Application.init() { - setupKodein() - // now kodein is running and can be used - val k by kodein() - val logger by k.instance("install-logger") - logger.debug { "DI container started." } - - // configure Ktor - installFrameworks() - - // register routing - routing { - registerRoutes() - } -} - -/** - * Configure Ktor and install necessary extensions. - */ -fun Application.installFrameworks() { - install(ContentNegotiation) { - jackson { - // enable pretty print for JSONs - enable(SerializationFeature.INDENT_OUTPUT) - dateFormat = DateFormat.getDateTimeInstance() - } - } - - install(DefaultHeaders) - install(CallLogging) - - install(WebSockets) { - // enable ping - to keep the connection alive - pingPeriod = Duration.ofSeconds(30) - timeout = Duration.ofSeconds(15) - // disabled (max value) - the connection will be closed if surpassed this length. - maxFrameSize = Long.MAX_VALUE - masking = false - } - -} diff --git a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/store/TokenStorage.kt b/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/store/TokenStorage.kt deleted file mode 100644 index 8efd4af..0000000 --- a/integrationTests/src/main/kotlin/com/wire/bots/polls/integration_tests/store/TokenStorage.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.wire.bots.polls.integration_tests.store - -import com.wire.bots.polls.integration_tests.dto.Conversation - -/** - * Map of expected results from the bot API: - * token - expected response from bot - */ -val tokenStorage = mutableMapOf() diff --git a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/Application.kt b/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/Application.kt deleted file mode 100644 index ccc008c..0000000 --- a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/Application.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.wire.bots.polls.integration_tests - -import com.wire.bots.polls.integration_tests.services.BotApiService -import org.kodein.di.generic.instance -import org.kodein.di.ktor.kodein - -/** - * Object storing access for the running application. - */ -object Application { - - /** - * Running engine. - */ - val engine by lazy { startServer() } - - /** - * Current running Application. - */ - val application by lazy { engine.application } - - /** - * Kodein from the running application. - */ - val appKodein by lazy { application.kodein() } - - /** - * Connection to the bot. - */ - val botService by lazy { val api by appKodein.instance(); api } - - /** - * Stops the application. - */ - fun teardown() = engine.stop(500L, 1000L) -} diff --git a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/BotActions.kt b/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/BotActions.kt deleted file mode 100644 index d302cd9..0000000 --- a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/BotActions.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.wire.bots.polls.integration_tests - -import com.wire.bots.polls.integration_tests.dto.PollConfirmation -import com.wire.bots.polls.integration_tests.dto.PollCreation -import com.wire.bots.polls.integration_tests.dto.ProxyMessage -import com.wire.bots.polls.integration_tests.dto.newText -import com.wire.bots.polls.integration_tests.dto.pollConfirmationMessage -import com.wire.bots.polls.integration_tests.dto.toCreateString -import com.wire.bots.polls.integration_tests.dto.voteUsingObject -import com.wire.bots.polls.integration_tests.store.tokenStorage -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import kotlin.test.assertEquals - -const val TIMEOUT = 500L - -fun vote(pollId: String, votingUserId: String, option: Int) { - val token = randomStringUUID() - - vote(token, pollId, votingUserId, option) { - voteUsingObject( - userId = votingUserId, - botId = randomStringUUID(), - token = token, - pollId = pollId, - option = option - ) - } -} - -fun vote(token: String, pollId: String, votingUserId: String, option: Int, voteOption: () -> ProxyMessage) { - val vote = voteOption() - - val expected = pollConfirmationMessage( - PollConfirmation( - id = pollId, - offset = option, - userId = votingUserId - ) - ) - - runBlocking { - Application.botService.send(vote) - delay(TIMEOUT) - } - - assertEquals(expected, tokenStorage[token]) -} - -fun createPoll(): PollCreation { - val token = randomStringUUID() - - val pollCreation = PollCreation( - id = randomStringUUID(), // we don't care about this - body = "Who is the best?", - buttons = listOf("Dejan", "Lukas", "Whole Wire") - ) - - val pollTextMessage = newText( - userId = randomStringUUID(), - botId = randomStringUUID(), - token = token, - text = pollCreation.toCreateString() - ) - - runBlocking { - Application.botService.send(pollTextMessage) - delay(TIMEOUT) - } - - val receivedMessage = requireNotNull(tokenStorage[token]) { - "There was no data under the token! That means bot did not send anything" - } - - assertEquals("poll.new", receivedMessage.type) - // the equal in PollCreation is overridden so it does not use Id to compare objects - assertEquals(pollCreation, receivedMessage.poll) - - return requireNotNull(receivedMessage.poll as? PollCreation) { "Poll in received message was not poll creation. This is weird." } -} diff --git a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/FlowTest.kt b/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/FlowTest.kt deleted file mode 100644 index eeae656..0000000 --- a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/FlowTest.kt +++ /dev/null @@ -1,205 +0,0 @@ -package com.wire.bots.polls.integration_tests - -import pw.forst.tools.katlib.newLine -import com.wire.bots.polls.integration_tests.dto.botRequest -import com.wire.bots.polls.integration_tests.dto.init -import com.wire.bots.polls.integration_tests.dto.reaction -import com.wire.bots.polls.integration_tests.dto.textMessage -import com.wire.bots.polls.integration_tests.dto.voteUsingObject -import com.wire.bots.polls.integration_tests.dto.voteUsingText -import com.wire.bots.polls.integration_tests.store.tokenStorage -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import mu.KLogging -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import kotlin.test.Test -import kotlin.test.assertEquals - - -class FlowTest { - - companion object : KLogging() { - @BeforeAll - @JvmStatic - fun beforeAll() { - // this call initializes the engine and thus starts the application - Application.engine - } - - @AfterAll - @JvmStatic - fun afterAll() { - // stops the running application - Application.teardown() - print("tearing down") - } - } - - @Test - fun `test new bot request`() { - val botMessage = botRequest( - userId = randomStringUUID(), - botId = randomStringUUID(), - token = randomStringUUID() - ) - - // bot won't send new message to that so we are just checking whether bot responds with OK - runBlocking { - Application.botService.send(botMessage) - } - } - - @Test - fun `test init conversation`() { - val token = randomStringUUID() - val initMessage = init( - userId = randomStringUUID(), - botId = randomStringUUID(), - token = token - ) - - val expectedMessage = textMessage("To create poll please text: /poll \"Question\" \"Option 1\" \"Option 2\"") - - runBlocking { - Application.botService.send(initMessage) - } - - assertEquals(expectedMessage, tokenStorage[token]) - } - - @Test - fun `test create poll`() { - createPoll() - } - - @Test - fun `test create poll and vote via text`() { - val createdPoll = createPoll() - - val token = randomStringUUID() - val option = 0 - val votingUser = randomStringUUID() - - - vote(token, createdPoll.id, votingUser, option) { - voteUsingText( - userId = votingUser, - botId = randomStringUUID(), - token = token, - pollId = createdPoll.id, - option = option - ) - } - } - - @Test - fun `test create poll and vote via object`() { - val createdPoll = createPoll() - - val token = randomStringUUID() - val option = 1 - val votingUser = randomStringUUID() - - vote(token, createdPoll.id, votingUser, option) { - voteUsingObject( - userId = votingUser, - botId = randomStringUUID(), - token = token, - pollId = createdPoll.id, - option = option - ) - } - } - - @Test - fun `test create poll and vote twice`() { - val createdPoll = createPoll() - - val votingUser = randomStringUUID() - - vote(createdPoll.id, votingUser, 0) - vote(createdPoll.id, votingUser, 1) - } - - @Test - fun `test vote and get results`() { - val createdPoll = createPoll() - val option = 1 - val votingUser = randomStringUUID() - vote(createdPoll.id, votingUser, option) - - val token = randomStringUUID() - - val reactionMessage = reaction( - userId = votingUser, - botId = randomStringUUID(), - token = token, - refMessageId = createdPoll.id - ) - - val usersVoting = "0 - 0 votes${newLine}1 - 1 vote${newLine}2 - 0 votes" - val expected = textMessage( - "Results for pollId: `${createdPoll.id}`$newLine```$newLine$usersVoting$newLine```" - ) - - runBlocking { - Application.botService.send(reactionMessage) - delay(TIMEOUT) - } - - assertEquals(expected, tokenStorage[token]) - } - - @Test - fun `test change vote and get results`() { - val createdPoll = createPoll() - val votingUser = randomStringUUID() - - vote(createdPoll.id, votingUser, 1) - - val token = randomStringUUID() - val reactionMessage = reaction( - userId = votingUser, - botId = randomStringUUID(), - token = token, - refMessageId = createdPoll.id - ) - - val usersVoting = "0 - 0 votes${newLine}1 - 1 vote${newLine}2 - 0 votes" - val expected = textMessage( - "Results for pollId: `${createdPoll.id}`$newLine```$newLine$usersVoting$newLine```" - ) - - runBlocking { - Application.botService.send(reactionMessage) - delay(TIMEOUT) - } - - assertEquals(expected, tokenStorage[token]) - - // now change the vote - vote(createdPoll.id, votingUser, 0) - - val tokenWithChanged = randomStringUUID() - val reactionMessageWithChangedVote = reaction( - userId = votingUser, - botId = randomStringUUID(), - token = tokenWithChanged, - refMessageId = createdPoll.id - ) - - val usersVotingWithChangedVote = "0 - 1 vote${newLine}1 - 0 votes${newLine}2 - 0 votes" - val expectedWithChangedVote = textMessage( - "Results for pollId: `${createdPoll.id}`$newLine```$newLine$usersVotingWithChangedVote$newLine```" - ) - - runBlocking { - Application.botService.send(reactionMessageWithChangedVote) - delay(TIMEOUT) - } - - assertEquals(expectedWithChangedVote, tokenStorage[tokenWithChanged]) - - } -} diff --git a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/ServerStart.kt b/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/ServerStart.kt deleted file mode 100644 index 21e6c08..0000000 --- a/integrationTests/src/test/kotlin/com/wire/bots/polls/integration_tests/ServerStart.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.wire.bots.polls.integration_tests - -import com.wire.bots.polls.integration_tests.setup.init -import io.ktor.application.Application -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import java.util.UUID - -/** - * Starts the Ktor api - */ -@Suppress("EXPERIMENTAL_API_USAGE") // because we don't want to propagate that further -fun startServer() = embeddedServer(Netty, port = 8081, module = Application::init).start() - - -/** - * Generates UUID and returns it's string representation. - */ -fun randomStringUUID() = UUID.randomUUID().toString() diff --git a/src/main/kotlin/com/wire/bots/polls/dao/PollRepository.kt b/src/main/kotlin/com/wire/bots/polls/dao/PollRepository.kt index f7ff8c4..0df80d7 100644 --- a/src/main/kotlin/com/wire/bots/polls/dao/PollRepository.kt +++ b/src/main/kotlin/com/wire/bots/polls/dao/PollRepository.kt @@ -13,7 +13,7 @@ import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import pw.forst.exposed.insertOrUpdate -import pw.forst.tools.katlib.mapToSet +import pw.forst.katlib.mapToSet /** * Simple repository for handling database transactions on one place. diff --git a/src/main/kotlin/com/wire/bots/polls/dto/roman/Message.kt b/src/main/kotlin/com/wire/bots/polls/dto/roman/Message.kt index ea6333e..daa9e32 100644 --- a/src/main/kotlin/com/wire/bots/polls/dto/roman/Message.kt +++ b/src/main/kotlin/com/wire/bots/polls/dto/roman/Message.kt @@ -39,7 +39,7 @@ data class Message( /** * Text of the message. */ - val text: String?, + val text: Text?, /** * Id of the quoted message, when the user replies on something, this is id of something. */ @@ -69,12 +69,12 @@ data class Message( * Type of the file */ val mimeType: String?, - - /** - * Mentions in the code - */ - val mentions: List? ) { + data class Text( + val data: String, + val mentions: List? + ) + /** * Poll representation for the proxy. */ @@ -102,34 +102,65 @@ data class Message( /* JSON from the swagger { - "botId": "string", + "botId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "type": "string", - "userId": "string", - "messageId": "string", - "conversationId": "string", - "token": "string", - "text": "string", - "image": "string", - "attachment": "string", + "userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "handle": "string", "locale": "string", + "token": "string", + "messageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "refMessageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "conversation": "string", + "text": { + "data": "string", + "mentions": [ + { + "userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "offset": 0, + "length": 0 + } + ] + }, + "attachment": { + "data": "string", + "name": "string", + "mimeType": "string", + "size": 0, + "duration": 0, + "levels": [ + "string" + ], + "height": 0, + "width": 0, + "meta": { + "assetId": "string", + "assetToken": "string", + "sha256": "string", + "otrKey": "string" + } + }, "poll": { - "id": "string", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "type": "string", "buttons": [ "string" ], "offset": 0, - "userId": "string" + "userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, - "refMessageId": "string", - "mimeType": "string", - "mentions": [ - { - "userId": "string", - "offset": 0, - "length": 0 + "call": { + "version": "string", + "type": "string", + "resp": true, + "sessid": "string", + "props": { + "additionalProp1": "string", + "additionalProp2": "string", + "additionalProp3": "string" } - ] + }, + "emoji": "string" } + */ diff --git a/src/main/kotlin/com/wire/bots/polls/parser/PollFactory.kt b/src/main/kotlin/com/wire/bots/polls/parser/PollFactory.kt index 6199387..6320c09 100644 --- a/src/main/kotlin/com/wire/bots/polls/parser/PollFactory.kt +++ b/src/main/kotlin/com/wire/bots/polls/parser/PollFactory.kt @@ -3,8 +3,8 @@ package com.wire.bots.polls.parser import com.wire.bots.polls.dto.PollDto import com.wire.bots.polls.dto.UsersInput import mu.KLogging -import pw.forst.tools.katlib.newLine -import pw.forst.tools.katlib.whenNull +import pw.forst.katlib.newLine +import pw.forst.katlib.whenNull /** * Class used for creating the polls from the text. Parsing and creating the poll objects. diff --git a/src/main/kotlin/com/wire/bots/polls/routing/MessagesRoute.kt b/src/main/kotlin/com/wire/bots/polls/routing/MessagesRoute.kt index 955b438..b5d5ed2 100644 --- a/src/main/kotlin/com/wire/bots/polls/routing/MessagesRoute.kt +++ b/src/main/kotlin/com/wire/bots/polls/routing/MessagesRoute.kt @@ -11,13 +11,14 @@ import io.ktor.request.receive import io.ktor.response.respond import io.ktor.routing.Routing import io.ktor.routing.post -import org.kodein.di.LazyKodein -import org.kodein.di.generic.instance +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI /** * Messages API. */ -fun Routing.messages(k: LazyKodein) { +fun Routing.messages() { + val k = closestDI() val handler by k.instance() val authService by k.instance() diff --git a/src/main/kotlin/com/wire/bots/polls/routing/Routing.kt b/src/main/kotlin/com/wire/bots/polls/routing/Routing.kt index 56e7ed7..2ff9f08 100644 --- a/src/main/kotlin/com/wire/bots/polls/routing/Routing.kt +++ b/src/main/kotlin/com/wire/bots/polls/routing/Routing.kt @@ -2,7 +2,6 @@ package com.wire.bots.polls.routing import com.wire.bots.polls.utils.createLogger import io.ktor.routing.Routing -import org.kodein.di.ktor.kodein internal val routingLogger by lazy { createLogger("RoutingLogger") } @@ -10,8 +9,6 @@ internal val routingLogger by lazy { createLogger("RoutingLogger") } * Register routes to the KTor. */ fun Routing.registerRoutes() { - val k by kodein() - - serviceRoutes(k) - messages(k) + serviceRoutes() + messages() } diff --git a/src/main/kotlin/com/wire/bots/polls/routing/ServiceRoutes.kt b/src/main/kotlin/com/wire/bots/polls/routing/ServiceRoutes.kt index 63c1ff2..b5066d9 100644 --- a/src/main/kotlin/com/wire/bots/polls/routing/ServiceRoutes.kt +++ b/src/main/kotlin/com/wire/bots/polls/routing/ServiceRoutes.kt @@ -8,13 +8,14 @@ import io.ktor.response.respondTextWriter import io.ktor.routing.Routing import io.ktor.routing.get import io.micrometer.prometheus.PrometheusMeterRegistry -import org.kodein.di.LazyKodein -import org.kodein.di.generic.instance +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI /** * Registers prometheus data. */ -fun Routing.serviceRoutes(k: LazyKodein) { +fun Routing.serviceRoutes() { + val k = closestDI() val version by k.instance("version") val registry by k.instance() diff --git a/src/main/kotlin/com/wire/bots/polls/services/AuthService.kt b/src/main/kotlin/com/wire/bots/polls/services/AuthService.kt index 31197ea..dd0e651 100644 --- a/src/main/kotlin/com/wire/bots/polls/services/AuthService.kt +++ b/src/main/kotlin/com/wire/bots/polls/services/AuthService.kt @@ -2,7 +2,7 @@ package com.wire.bots.polls.services import io.ktor.http.Headers import mu.KLogging -import pw.forst.tools.katlib.whenNull +import pw.forst.katlib.whenNull /** * Authentication service. diff --git a/src/main/kotlin/com/wire/bots/polls/services/MessagesHandlingService.kt b/src/main/kotlin/com/wire/bots/polls/services/MessagesHandlingService.kt index 3a393cb..5bb06c9 100644 --- a/src/main/kotlin/com/wire/bots/polls/services/MessagesHandlingService.kt +++ b/src/main/kotlin/com/wire/bots/polls/services/MessagesHandlingService.kt @@ -88,25 +88,26 @@ class MessagesHandlingService( // it is a reply on something refMessageId != null && text != null -> when { // request for stats - text.trim().startsWith("/stats") -> pollService.sendStats(token, refMessageId) + text.data.trim().startsWith("/stats") -> pollService.sendStats(token, refMessageId) // integer vote where the text contains offset - text.trim().toIntOrNull() != null -> vote(token, userId, refMessageId, text) + text.data.trim().toIntOrNull() != null -> vote(token, userId, refMessageId, text.data) else -> ignore { "Ignoring the message as it is reply unrelated to the bot" } } // text message with just text text != null -> { + val trimmed = text.data.trim() when { // poll request - text.trim().startsWith("/poll") -> - pollService.createPoll(token, UsersInput(userId, text, mentions ?: emptyList()), botId) + trimmed.startsWith("/poll") -> + pollService.createPoll(token, UsersInput(userId, trimmed, text.mentions ?: emptyList()), botId) // stats request - text.trim().startsWith("/stats") -> pollService.sendStatsForLatest(token, botId) + trimmed.startsWith("/stats") -> pollService.sendStatsForLatest(token, botId) // send version when asked - text.trim().startsWith("/version") -> userCommunicationService.sendVersion(token) + trimmed.startsWith("/version") -> userCommunicationService.sendVersion(token) // send version when asked - text.trim().startsWith("/help") -> userCommunicationService.sendHelp(token) + trimmed.startsWith("/help") -> userCommunicationService.sendHelp(token) // easter egg, good bot is good - text == "good bot" -> userCommunicationService.goodBot(token) + trimmed == "good bot" -> userCommunicationService.goodBot(token) else -> ignore { "Ignoring the message, unrecognized command." } } } diff --git a/src/main/kotlin/com/wire/bots/polls/services/PollService.kt b/src/main/kotlin/com/wire/bots/polls/services/PollService.kt index 0df5bc1..39abb5c 100644 --- a/src/main/kotlin/com/wire/bots/polls/services/PollService.kt +++ b/src/main/kotlin/com/wire/bots/polls/services/PollService.kt @@ -7,8 +7,8 @@ import com.wire.bots.polls.dto.bot.confirmVote import com.wire.bots.polls.dto.bot.newPoll import com.wire.bots.polls.parser.PollFactory import mu.KLogging -import pw.forst.tools.katlib.whenNull -import pw.forst.tools.katlib.whenTrue +import pw.forst.katlib.whenNull +import pw.forst.katlib.whenTrue import java.util.UUID /** diff --git a/src/main/kotlin/com/wire/bots/polls/services/ProxySenderService.kt b/src/main/kotlin/com/wire/bots/polls/services/ProxySenderService.kt index 009b984..0a24108 100644 --- a/src/main/kotlin/com/wire/bots/polls/services/ProxySenderService.kt +++ b/src/main/kotlin/com/wire/bots/polls/services/ProxySenderService.kt @@ -14,7 +14,7 @@ import io.ktor.http.ContentType import io.ktor.http.contentType import io.ktor.http.isSuccess import mu.KLogging -import pw.forst.tools.katlib.createJson +import pw.forst.katlib.createJson import java.nio.charset.Charset /** diff --git a/src/main/kotlin/com/wire/bots/polls/services/StatsFormattingService.kt b/src/main/kotlin/com/wire/bots/polls/services/StatsFormattingService.kt index ec83ec0..5f47230 100644 --- a/src/main/kotlin/com/wire/bots/polls/services/StatsFormattingService.kt +++ b/src/main/kotlin/com/wire/bots/polls/services/StatsFormattingService.kt @@ -4,8 +4,8 @@ import com.wire.bots.polls.dao.PollRepository import com.wire.bots.polls.dto.bot.BotMessage import com.wire.bots.polls.dto.bot.statsMessage import mu.KLogging -import pw.forst.tools.katlib.newLine -import pw.forst.tools.katlib.whenNull +import pw.forst.katlib.newLine +import pw.forst.katlib.whenNull class StatsFormattingService( private val repository: PollRepository diff --git a/src/main/kotlin/com/wire/bots/polls/setup/ConfigurationDependencyInjection.kt b/src/main/kotlin/com/wire/bots/polls/setup/ConfigurationDependencyInjection.kt index 2ee5036..dd61764 100644 --- a/src/main/kotlin/com/wire/bots/polls/setup/ConfigurationDependencyInjection.kt +++ b/src/main/kotlin/com/wire/bots/polls/setup/ConfigurationDependencyInjection.kt @@ -8,11 +8,11 @@ import com.wire.bots.polls.setup.EnvConfigVariables.DB_USER import com.wire.bots.polls.setup.EnvConfigVariables.PROXY_DOMAIN import com.wire.bots.polls.setup.EnvConfigVariables.SERVICE_TOKEN import com.wire.bots.polls.utils.createLogger -import org.kodein.di.Kodein.MainBuilder -import org.kodein.di.generic.bind -import org.kodein.di.generic.singleton -import pw.forst.tools.katlib.getEnv -import pw.forst.tools.katlib.whenNull +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.singleton +import pw.forst.katlib.getEnv +import pw.forst.katlib.whenNull import java.io.File private val logger = createLogger("EnvironmentLoaderLogger") @@ -33,7 +33,7 @@ private fun loadVersion(defaultVersion: String): String = runCatching { * Loads the DI container with configuration from the system environment. */ // TODO load all config from the file and then allow the replacement with env variables -fun MainBuilder.bindConfiguration() { +fun DI.MainBuilder.bindConfiguration() { // The default values used in this configuration are for the local development. diff --git a/src/main/kotlin/com/wire/bots/polls/setup/DependencyInjection.kt b/src/main/kotlin/com/wire/bots/polls/setup/DependencyInjection.kt index de1467b..62b3e1a 100644 --- a/src/main/kotlin/com/wire/bots/polls/setup/DependencyInjection.kt +++ b/src/main/kotlin/com/wire/bots/polls/setup/DependencyInjection.kt @@ -16,12 +16,12 @@ import io.ktor.client.HttpClient import io.micrometer.prometheus.PrometheusConfig import io.micrometer.prometheus.PrometheusMeterRegistry import mu.KLogger -import org.kodein.di.Kodein.MainBuilder -import org.kodein.di.generic.bind -import org.kodein.di.generic.instance -import org.kodein.di.generic.singleton +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.instance +import org.kodein.di.singleton -fun MainBuilder.configureContainer() { +fun DI.MainBuilder.configureContainer() { bind() with singleton { PollValidation() } diff --git a/src/main/kotlin/com/wire/bots/polls/setup/KodeinSetup.kt b/src/main/kotlin/com/wire/bots/polls/setup/KodeinSetup.kt index 4cad3fa..be7d894 100644 --- a/src/main/kotlin/com/wire/bots/polls/setup/KodeinSetup.kt +++ b/src/main/kotlin/com/wire/bots/polls/setup/KodeinSetup.kt @@ -1,13 +1,13 @@ package com.wire.bots.polls.setup import io.ktor.application.Application -import org.kodein.di.ktor.kodein +import org.kodein.di.ktor.di /** * Inits and sets up DI container. */ fun Application.setupKodein() { - kodein { + di { bindConfiguration() configureContainer() } diff --git a/src/main/kotlin/com/wire/bots/polls/setup/KtorInstallation.kt b/src/main/kotlin/com/wire/bots/polls/setup/KtorInstallation.kt index 4b23891..2132489 100644 --- a/src/main/kotlin/com/wire/bots/polls/setup/KtorInstallation.kt +++ b/src/main/kotlin/com/wire/bots/polls/setup/KtorInstallation.kt @@ -23,9 +23,8 @@ import io.ktor.routing.routing import io.micrometer.core.instrument.distribution.DistributionStatisticConfig import io.micrometer.prometheus.PrometheusMeterRegistry import org.flywaydb.core.Flyway -import org.kodein.di.LazyKodein -import org.kodein.di.generic.instance -import org.kodein.di.ktor.kodein +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI import org.slf4j.event.Level import java.text.DateFormat import java.util.UUID @@ -39,14 +38,13 @@ private val installationLogger = createLogger("ApplicationSetup") fun Application.init() { setupKodein() // now kodein is running and can be used - val k by kodein() installationLogger.debug { "DI container started." } // connect to the database - connectDatabase(k) + connectDatabase() // configure Ktor - installFrameworks(k) + installFrameworks() // register routing routing { @@ -57,9 +55,9 @@ fun Application.init() { /** * Connect bot to the database. */ -fun connectDatabase(k: LazyKodein) { +fun Application.connectDatabase() { installationLogger.info { "Connecting to the DB" } - val dbConfig by k.instance() + val dbConfig by closestDI().instance() DatabaseSetup.connect(dbConfig) if (DatabaseSetup.isConnected()) { @@ -91,7 +89,7 @@ fun migrateDatabase(dbConfig: DatabaseConfiguration) { /** * Configure Ktor and install necessary extensions. */ -fun Application.installFrameworks(k: LazyKodein) { +fun Application.installFrameworks() { install(ContentNegotiation) { jackson { // enable pretty print for JSONs @@ -128,9 +126,9 @@ fun Application.installFrameworks(k: LazyKodein) { } } - registerExceptionHandlers(k) + registerExceptionHandlers() - val prometheusRegistry by k.instance() + val prometheusRegistry by closestDI().instance() install(MicrometerMetrics) { registry = prometheusRegistry distributionStatisticConfig = DistributionStatisticConfig.Builder() diff --git a/src/main/kotlin/com/wire/bots/polls/setup/errors/ExceptionHandling.kt b/src/main/kotlin/com/wire/bots/polls/setup/errors/ExceptionHandling.kt index 5e66868..8e68eb7 100644 --- a/src/main/kotlin/com/wire/bots/polls/setup/errors/ExceptionHandling.kt +++ b/src/main/kotlin/com/wire/bots/polls/setup/errors/ExceptionHandling.kt @@ -10,16 +10,16 @@ import io.ktor.features.StatusPages import io.ktor.http.HttpStatusCode import io.ktor.response.respond import io.micrometer.prometheus.PrometheusMeterRegistry -import org.kodein.di.LazyKodein -import org.kodein.di.generic.instance +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI private val logger = createLogger("ExceptionHandler") /** * Registers exception handling. */ -fun Application.registerExceptionHandlers(k: LazyKodein) { - val registry by k.instance() +fun Application.registerExceptionHandlers() { + val registry by closestDI().instance() install(StatusPages) { exception { cause -> diff --git a/src/main/kotlin/com/wire/bots/polls/setup/logging/JsonLoggingLayout.kt b/src/main/kotlin/com/wire/bots/polls/setup/logging/JsonLoggingLayout.kt index 61ed097..2f476ae 100644 --- a/src/main/kotlin/com/wire/bots/polls/setup/logging/JsonLoggingLayout.kt +++ b/src/main/kotlin/com/wire/bots/polls/setup/logging/JsonLoggingLayout.kt @@ -6,7 +6,7 @@ import ch.qos.logback.classic.spi.IThrowableProxy import ch.qos.logback.classic.spi.ThrowableProxyUtil import ch.qos.logback.core.CoreConstants import ch.qos.logback.core.LayoutBase -import pw.forst.tools.katlib.createJson +import pw.forst.katlib.createJson import java.time.Instant import java.time.ZoneOffset import java.time.format.DateTimeFormatter