diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
new file mode 100644
index 0000000..371a5e7
--- /dev/null
+++ b/.github/badges/jacoco.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..3b10a3c
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
+
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
\ No newline at end of file
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..38ac900
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,8 @@
+changelog:
+ exclude:
+ authors:
+ - kitautomation
+ categories:
+ - title: Changes
+ labels:
+ - "*"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..bc4db44
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,104 @@
+name: CICD
+
+on:
+ push:
+ branches: [ main ]
+ tags:
+ - 'v*.*.*'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+# Set docker image names.
+ - name: Setup env variables
+ run: |
+ echo "DOCKER_SERVICE=kvalitetsit/kithugs" >> $GITHUB_ENV
+ echo "DOCKER_DOCUMENTATION=kvalitetsit/kithugs-documentation" >> $GITHUB_ENV
+
+# Checkout source code
+ - uses: actions/checkout@v3
+
+# Fail if DOCKER_SERVICE is kithugs and repo is not kithugs. This step can be deleted once
+ - name: Initial build
+ run: ./build/failOnFirstBuild.sh
+
+# Cache maven stuff
+ - name: Cache local Maven repository
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+# if below step is skipped this build is a tag build. Can be used for skipping other steps.
+ - name: Is Tag Build
+ id: tag
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
+
+# Login to docker hub using secrets in GitHub.
+ - name: Login to docker
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_HUB_USER }}
+ password: ${{ secrets.DOCKER_HUB_PASSWORD }}
+
+ - name: Build and test
+ run: ./build/build.sh
+
+ - name: Tag service dev docker image
+ run: ./build/docker-tag.sh ${{ env.DOCKER_SERVICE }}:latest ${{ env.DOCKER_SERVICE }}:dev
+
+ - name: Push service dev docker image
+ run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:dev
+
+ - name: Tag service git id docker image
+ run: ./build/docker-tag.sh ${{ env.DOCKER_SERVICE }}:latest ${{ env.DOCKER_SERVICE }}:${{ github.sha }}
+
+ - name: Push service git id docker image.
+ run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:${{ github.sha }}
+
+ - name: Tag documentation dev docker image
+ run: ./build/docker-tag.sh ${{ env.DOCKER_DOCUMENTATION }}:latest ${{ env.DOCKER_DOCUMENTATION }}:dev
+
+ - name: Push documentation dev docker image
+ run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:dev
+
+ - name: Tag documentation git id docker image
+ run: ./build/docker-tag.sh ${{ env.DOCKER_DOCUMENTATION }}:latest ${{ env.DOCKER_DOCUMENTATION }}:${{ github.sha }}
+
+ - name: Push documentation git id docker image.
+ run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:${{ github.sha }}
+
+ - name: Push latest service docker image
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:latest
+
+ - name: Tag version service docker image
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ run: ./build/docker-tag.sh ${{ env.DOCKER_SERVICE }}:latest ${{ env.DOCKER_SERVICE }}:${{ steps.tag.outputs.VERSION }}
+
+ - name: Push version service docker image.
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ run: ./build/docker-push.sh ${{ env.DOCKER_SERVICE }}:${{ steps.tag.outputs.VERSION }}
+
+ - name: Push latest documentation docker image
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:latest
+
+ - name: Tag version documentation docker image
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ run: ./build/docker-tag.sh ${{ env.DOCKER_DOCUMENTATION }}:latest ${{ env.DOCKER_DOCUMENTATION }}:${{ steps.tag.outputs.VERSION }}
+
+ - name: Push version documentation docker image.
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ run: ./build/docker-push.sh ${{ env.DOCKER_DOCUMENTATION }}:${{ steps.tag.outputs.VERSION }}
+
+ - name: Create Release Notes
+ uses: softprops/action-gh-release@v1
+ if: ${{ steps.tag.conclusion != 'skipped' }}
+ with:
+ generate_release_notes: true
diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml
new file mode 100644
index 0000000..24257f4
--- /dev/null
+++ b/.github/workflows/dependabot-auto-merge.yml
@@ -0,0 +1,28 @@
+name: Dependabot auto-merge
+on: pull_request
+
+permissions:
+ pull-requests: write
+ contents: write
+
+jobs:
+ dependabot:
+ runs-on: ubuntu-latest
+ if: ${{ github.actor == 'dependabot[bot]' && github.repository == 'KvalitetsIT/kithugs' }}
+ steps:
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v1.6.0
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+ - name: Approve PR
+ run: gh pr review --approve "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+ - name: Enable Auto-Merge
+ if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
+ run: gh pr merge --auto --merge "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
diff --git a/.github/workflows/pr_ci.yml b/.github/workflows/pr_ci.yml
new file mode 100644
index 0000000..2dd2c1b
--- /dev/null
+++ b/.github/workflows/pr_ci.yml
@@ -0,0 +1,42 @@
+name: Pull Request CI
+
+on:
+ pull_request: ~
+
+jobs:
+ pr_build:
+ runs-on: ubuntu-latest
+
+ steps:
+# Checkout source code
+ - uses: actions/checkout@v3
+
+# Cache maven stuff
+ - name: Cache local Maven repository
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Build and test
+ run: ./build/build.sh
+
+ - name: Upload Jacoco coverage report
+ uses: actions/upload-artifact@v3
+ with:
+ name: jacoco-report
+ path: testreport/target/site/jacoco-aggregate/
+
+ - name: Save PR number
+ run: |
+ mkdir -p ./pr
+ echo ${{ github.event.number }} > ./pr/NR
+
+ - uses: actions/upload-artifact@v3
+ with:
+ name: pr
+ path: |
+ pr/
+ testreport/target/site/jacoco-aggregate/jacoco.csv
diff --git a/.github/workflows/pr_ci_coverage.yaml b/.github/workflows/pr_ci_coverage.yaml
new file mode 100644
index 0000000..99acd3f
--- /dev/null
+++ b/.github/workflows/pr_ci_coverage.yaml
@@ -0,0 +1,63 @@
+name: Update coverage
+
+on:
+ workflow_run:
+ workflows: ["Pull Request CI"]
+ types:
+ - completed
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ if: >
+ ${{ github.event.workflow_run.event == 'pull_request' &&
+ github.event.workflow_run.conclusion == 'success' }}
+ steps:
+ - name: 'Download artifact'
+ uses: actions/github-script@v6
+ with:
+ script: |
+ var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{github.event.workflow_run.id }},
+ });
+ var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "pr"
+ })[0];
+ var download = await github.rest.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: 'zip',
+ });
+ var fs = require('fs');
+ fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
+ - run: unzip pr.zip
+
+ - name: Generate Jacoco Badge
+ id: jacoco
+ uses: cicirello/jacoco-badge-generator@v2.9.0
+ with:
+ jacoco-csv-file: testreport/target/site/jacoco-aggregate/jacoco.csv
+
+ - name: Log coverage percentage
+ run: |
+ echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
+
+ # - name: Update coverage badge
+ # run: ./build/badge-update.sh
+
+ - name: 'Comment on PR with code coverage'
+ uses: actions/github-script@v6
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ var fs = require('fs');
+ var issue_number = Number(fs.readFileSync('./pr/NR'));
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue_number,
+ body: 'Code coverage after this PR: ' + ${{ steps.jacoco.outputs.coverage }}
+ });
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5360cac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# maven target folder
+target
+
+# IntelliJ stuff
+*.iml
+.idea
+
+# Eclipse stuff
+.project
+.settings/
+.classpath
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6876fd3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 KvalitetsIT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..20f3ea8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+![Build Status](https://github.com/KvalitetsIT/kithugs/workflows/CICD/badge.svg)
+# kithugs
+
+Template repository showing how to be a good Java Spring Boot citizen in a k8s cluster.
+
+## A good citizen
+
+Below is a set of recommendations for being a good service. The recommendations are not tied to a specific language or
+framework.
+
+1. Configuration through environment variables.
+2. Expose readiness endpoint
+3. Expose endpoint that Prometheus can scrape
+4. Be stateless
+5. Support multiple instances
+6. Always be in a releasable state
+7. Automate build and deployment.
+
+Some of above recommendations are heavily inspired by [https://12factor.net/](https://12factor.net/). It is recommended
+read [https://12factor.net/](https://12factor.net/) for more inspiration and further details. Some points go
+further than just being a good service and also touches areas like operations.
+
+## Getting started
+
+Run `./setup.sh GIT_REPOSITORY_NAME`.
+
+Above does a search/replace in relevant files.
+
+## Endpoints
+
+### Service
+
+The service is listening for connections on port 8080.
+
+Spring boot actuator is listening for connections on port 8081. This is used as prometheus scrape endpoint and health monitoring.
+
+Prometheus scrape endpoint: `http://localhost:8081/actuator/prometheus`
+Health URL that can be used for readiness probe: `http://localhost:8081/actuator/health`
+
+### Documentation
+
+Documentation of the API is build as a separate Docker image. Documentation is build using Swagger. The documentation
+image is post-fixed with `-documentation`. The file `documentation/docker/compose/docker-compose.yml` contains a setup
+that starts both the service and documentation image. The documentation can be accessed at `http://localhost/test`
+and the service can be called through the Swagger UI.
+
+In the docker-compose setup is also an example on how to set custom endpoints for the Swagger documentation service.
+
+## Dependency updates
+
+Out of the box we use GitHub Actions as our CI/CD platform and that can also handle dependency updates. We utilize
+GitHubs [Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates)
+to create PR's with dependency updates. Further we have a job that automatically approves and merges dependencies. By
+default, it is only enabled in the template repository. You can enable this by removing ` && github.repository == 'KvalitetsIT/kithugs'`
+from [dependabot-auto-merge.yml](.github/workflows/dependabot-auto-merge.yml). Before enabling it please consider below.
+
+- If no branch protection rule is configured dependency udpates that fails the automatic build and test will get merged.
+- You will not have a chance to review the changes in the dependency updates before it gets merged.
+- Enable auto-merge must be enabled in the repository.
+
+## Configuration
+
+| Environment variable | Description | Required |
+|----------------------|------------------------------------------------------------------------------------------------------|----------|
+| JDBC_URL | JDBC connection URL | Yes |
+| JDBC_USER | JDBC user | Yes |
+| JDBC_PASS | JDBC password | Yes |
+| LOG_LEVEL | Log Level for applikation log. Defaults to INFO. | No |
+| LOG_LEVEL_FRAMEWORK | Log level for framework. Defaults to INFO. | No |
+| CORRELATION_ID | HTTP header to take correlation id from. Used to correlate log messages. Defaults to "x-request-id". | No |
diff --git a/build/badge-update.sh b/build/badge-update.sh
new file mode 100755
index 0000000..2b1350d
--- /dev/null
+++ b/build/badge-update.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [[ $(git status -s) != '' ]]; then
+ git config user.name "Github Actions"
+ git config user.email "development@kvalitetsit.dk"
+ git add .github/badges/jacoco.svg
+ git commit -m "Update coverage badge"
+ git push
+else
+ echo 'Badge not updated. No work to do.'
+fi
diff --git a/build/build.sh b/build/build.sh
new file mode 100755
index 0000000..d5a0c46
--- /dev/null
+++ b/build/build.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Build resource container and start it
+docker build -t resources -f ./integrationtest/docker/Dockerfile-resources --no-cache ./integrationtest/docker
+docker run -d --name kithugs-resources resources
+
+# Build inside docker container
+docker run -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/.docker/config.json:/root/.docker/config.json -v $(pwd):/src -v $HOME/.m2:/root/.m2 --volumes-from kithugs-resources maven:3-ibm-semeru-17-focal /src/build/maven.sh
diff --git a/build/docker-push.sh b/build/docker-push.sh
new file mode 100755
index 0000000..fa8c7fd
--- /dev/null
+++ b/build/docker-push.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+TAG_NAME=$1
+
+if [ -z $TAG_NAME ]; then
+ echo "TAG_NAME variable not set."
+ exit 1
+fi
+
+docker push $TAG_NAME
diff --git a/build/docker-tag.sh b/build/docker-tag.sh
new file mode 100755
index 0000000..e572b4c
--- /dev/null
+++ b/build/docker-tag.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+FROM_TAG=$1
+TO_TAG=$2
+
+if [ -z "$FROM_TAG" ]; then
+ echo "FROM_TAG variable not set."
+ exit 1
+fi
+
+if [ -z "$TO_TAG" ]; then
+ echo "TO_TAG variable not set."
+ exit 1
+fi
+
+docker tag $FROM_TAG $TO_TAG
diff --git a/build/failOnFirstBuild.sh b/build/failOnFirstBuild.sh
new file mode 100755
index 0000000..63ac8e4
--- /dev/null
+++ b/build/failOnFirstBuild.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+echo "${GITHUB_REPOSITORY}"
+echo "${DOCKER_SERVICE}"
+if [ "${GITHUB_REPOSITORY}" != "KvalitetsIT/kithugs" ] && [ "${DOCKER_SERVICE}" = "kvalitetsit/kithugs" ]; then
+ echo "Please run setup.sh REPOSITORY_NAME"
+ exit 1
+fi
diff --git a/build/maven.sh b/build/maven.sh
new file mode 100755
index 0000000..da21e1a
--- /dev/null
+++ b/build/maven.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+apt-get update
+apt-get install -y docker.io
+
+SRC_FOLDER=src
+
+if [ -d $SRC_FOLDER ]; then
+ cd $SRC_FOLDER
+
+ mvn clean install -Pdocker-test
+else
+ echo "$SRC_FOLDER folder not found."
+ exit 1
+fi
+
diff --git a/documentation/docker/Dockerfile b/documentation/docker/Dockerfile
new file mode 100644
index 0000000..f210819
--- /dev/null
+++ b/documentation/docker/Dockerfile
@@ -0,0 +1,20 @@
+FROM swaggerapi/swagger-ui:v4.12.0
+
+COPY /maven/*.yaml /usr/share/nginx/html/
+
+COPY /versions /kit/versions
+COPY /config/setVersion.sh /kit/setVersion.sh
+COPY /config/setServers.sh /kit/setServers.sh
+COPY /config/config.sh /config.sh
+
+COPY /maven/runningVersion.json /kit/runningVersion.json
+
+RUN apk update && \
+ apk add jq && \
+ wget -O /usr/bin/yq "https://github.com/mikefarah/yq/releases/download/3.3.2/yq_linux_amd64" && \
+ chmod +x /usr/bin/yq && \
+ chmod +x /kit/setVersion.sh && \
+ chmod +x /kit/setServers.sh && \
+ chmod +x /config.sh
+
+ENTRYPOINT ["sh", "/config.sh"]
\ No newline at end of file
diff --git a/documentation/docker/compose/docker-compose.yml b/documentation/docker/compose/docker-compose.yml
new file mode 100644
index 0000000..9db711d
--- /dev/null
+++ b/documentation/docker/compose/docker-compose.yml
@@ -0,0 +1,41 @@
+version: '2.1'
+services:
+ mariadb:
+ image: mariadb:10.6
+ environment:
+ - MYSQL_ROOT_PASSWORD=rootroot
+ - MYSQL_DATABASE=hellodb
+ - MYSQL_USER=hellouser
+ - MYSQL_PASSWORD=secret1234
+ healthcheck:
+ test: mysql --user=hellouser --password=secret1234 -e 'show databases;'
+ interval: 2s
+ timeout: 2s
+ retries: 10
+ helloservice:
+ image: kvalitetsit/kithugs:latest
+ environment:
+ - jdbc_url=jdbc:mariadb://mariadb:3306/hellodb
+ - jdbc_user=hellouser
+ - jdbc_pass=secret1234
+ - usercontext_header_name=X-Test-Auth
+
+ - userattributes_role_key=UserRoles
+ - userattributes_org_key=organisation
+
+ - userrole_admin_values=adminrole
+ - userrole_user_values=userrole1,userrole2
+ - userrole_monitor_values=monitorrole
+ - userrole_provisioner_values=provisionerrole
+ depends_on:
+ mariadb:
+ condition: service_healthy
+ ports:
+ - 8080:8080
+ documenatation-and-test:
+ image: kvalitetsit/kithugs-documentation:latest
+ environment:
+ - BASE_URL=/test
+ - 'SERVER_URLS=[{"url": "http://localhost:8080", "name": "HelloService"}]'
+ ports:
+ - 80:8080
diff --git a/documentation/docker/config/config.sh b/documentation/docker/config/config.sh
new file mode 100644
index 0000000..9bb63cb
--- /dev/null
+++ b/documentation/docker/config/config.sh
@@ -0,0 +1,14 @@
+#! /bin/sh
+
+export DOC_FILES=/usr/share/nginx/html/*.yaml
+
+echo "Running set version"
+/kit/setVersion.sh
+echo "Running set servers"
+/kit/setServers.sh
+
+echo "Sets env URLS to list of versions"
+export URLS=$(cat /kit/env)
+
+echo "Restarting nginx"
+/docker-entrypoint.sh nginx -g 'daemon off;'
\ No newline at end of file
diff --git a/documentation/docker/config/setServers.sh b/documentation/docker/config/setServers.sh
new file mode 100644
index 0000000..5235f60
--- /dev/null
+++ b/documentation/docker/config/setServers.sh
@@ -0,0 +1,20 @@
+#! /bin/sh
+
+if [[ -z "${SERVER_URLS}" ]]; then
+ echo "SERVER_URLS NOT set"
+else
+ for file in $DOC_FILES
+ do
+ yq d -i $file 'servers[*]'
+
+ for i in $(echo $SERVER_URLS | jq -r '. | keys | .[]'); do
+
+ yq w -i $file 'servers[+].url' $(echo $SERVER_URLS | jq -r ".[$i].url")
+ yq w -i $file "servers[$i].description" $(echo $SERVER_URLS | jq -r ".[$i].name")
+
+ done
+
+ done
+fi
+
+
diff --git a/documentation/docker/config/setVersion.sh b/documentation/docker/config/setVersion.sh
new file mode 100644
index 0000000..1c010eb
--- /dev/null
+++ b/documentation/docker/config/setVersion.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+function updateFile {
+ local f="$1"
+ cmp -s "$f" "$f.tmp"
+ if [ $? = 0 ] ; then
+ /bin/rm $f.tmp
+ else
+ /bin/mv "$f.tmp" "$f"
+ fi
+}
+
+echo "Add Dev version to list of versions"
+GIT_BRANCH=$(cat /kit/runningVersion.json | jq -r '."git.commit.id.describe"')
+echo "[]" > /kit/env
+
+if (echo "$GIT_BRANCH" | grep -Eq ^v[0-9]*\\.[0-9]*\\.[0-9]*$); then
+ echo "Release version"
+else
+ echo "Is dev version"
+
+ url="${BASE_URL}/${GIT_BRANCH}.yaml"
+
+ cat /kit/env | jq --arg u $url '. += [{"name": "Dev", "url": $u }] | sort_by(.name)' > /kit/env.tmp
+ updateFile /kit/env
+fi
+
+echo "Creating file with version and path"
+(
+ IFS=$'\n'
+ for version in $(cat kit/versions)
+ do
+ url="${BASE_URL}/${version}.yaml"
+
+ cat /kit/env | jq --arg n $version --arg u $url '. += [{"name": $n, "url": $u}]' > /kit/env.tmp
+ updateFile /kit/env
+ done
+)
+
+
+
+echo "Updates version in doc file"
+for file in $DOC_FILES
+do
+ file_name=${file##*/}
+ yq w -i $file 'info.version' $(echo ${file_name%.*} | cut -d "v" -f 2)
+done
\ No newline at end of file
diff --git a/documentation/docker/getOldDoc.sh b/documentation/docker/getOldDoc.sh
new file mode 100755
index 0000000..af059db
--- /dev/null
+++ b/documentation/docker/getOldDoc.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+if docker pull kvalitetsit/kithugs-documentation:latest; then
+ echo "Copy from old documentation image."
+ docker cp $(docker create kvalitetsit/kithugs-documentation:latest):/usr/share/nginx/html target/old
+fi
diff --git a/documentation/docker/versions b/documentation/docker/versions
new file mode 100644
index 0000000..f979ade
--- /dev/null
+++ b/documentation/docker/versions
@@ -0,0 +1 @@
+v0.9.0
diff --git a/documentation/pom.xml b/documentation/pom.xml
new file mode 100644
index 0000000..b8bc6c3
--- /dev/null
+++ b/documentation/pom.xml
@@ -0,0 +1,239 @@
+
+ 4.0.0
+
+ kithugs
+ dk.kvalitetsit.kithugs
+ 0.0.1-SNAPSHOT
+
+ documentation
+
+
+
+
+ org.openapitools
+ jackson-databind-nullable
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+
+
+
+ io.swagger
+ swagger-annotations
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+
+
+
+ org.springframework
+ spring-web
+
+
+
+
+ com.google.code.findbugs
+ jsr305
+ test
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ test
+
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ 4.11.0
+ test
+
+
+
+ com.google.code.gson
+ gson
+ test
+
+
+
+ io.gsonfire
+ gson-fire
+ test
+
+
+
+ javax.annotation
+ javax.annotation-api
+ test
+
+
+
+
+
+ exec-maven-plugin
+ org.codehaus.mojo
+ 3.1.0
+
+
+ Get old doc versions
+ compile
+
+ exec
+
+
+ docker/getOldDoc.sh
+
+
+
+
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+ kvalitetsit/kithugs-documentation
+
+
+ dev
+ latest
+ ${git.commit.id}
+
+ ${project.basedir}/docker
+
+
+
+
+
+ ${git.commit.id.describe}.yaml
+
+
+
+ runningVersion.json
+
+
+
+
+ target/old
+ /
+
+
+
+ artifact
+ /app
+
+
+
+
+ true
+
+
+
+ build-image
+ package
+
+ build
+
+
+
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+ 6.6.0
+
+
+
+ generate
+
+
+ ${project.basedir}/src/main/resources/helloservice.yaml
+ java
+
+ src/test/java
+ java8
+
+
+ false
+ false
+ true
+ false
+
+
+
+ server
+
+ generate
+
+
+ ${project.basedir}/src/main/resources/helloservice.yaml
+ spring
+
+ src/main/java
+ java8
+ true
+ true
+ spring-cloud
+ true
+
+
+ false
+ false
+ false
+
+
+
+
+
+ pl.project13.maven
+ git-commit-id-plugin
+
+
+ get-the-git-infos
+
+ revision
+
+ initialize
+
+
+
+ true
+ ${project.build.outputDirectory}/git.properties
+
+ git.commit.id.describe
+
+ full
+
+ true
+
+ json
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+
+ test-jar
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/documentation/src/main/resources/helloservice.yaml b/documentation/src/main/resources/helloservice.yaml
new file mode 100644
index 0000000..c287f95
--- /dev/null
+++ b/documentation/src/main/resources/helloservice.yaml
@@ -0,0 +1,125 @@
+openapi: 3.0.0
+info:
+ title: Kithugs
+ description: API description for KITHUGS.
+ version: "1.0.0"
+ contact:
+ email: development@kvalitetitsit.dk
+tags:
+ - name: KITHUGS
+ description: KITHUGS related API's
+
+servers:
+ - url: '{protocol}://{environment}:{port}'
+ variables:
+ protocol:
+ enum:
+ - http
+ - https
+ default: http
+ environment:
+ enum:
+ - localhost # Docker-compose setup
+ default: localhost # Development
+ port:
+ enum:
+ - 8080
+ default: 8080
+paths:
+ /v1/hello:
+ get:
+ tags:
+ - KITHUGS
+ parameters:
+ - name: name
+ in: query
+ required: true
+ description: Name. If name is "NOT_VALID" an validation error occurs.
+ schema:
+ type: string
+ maxLength: 10
+ example: 'John Doe'
+ summary: Call the thugs service
+ description: Post your name and get some hugs or meet some thugs.
+ responses:
+ '200':
+ description: Ok
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/helloResponse'
+ '400':
+ $ref: '#/components/responses/400'
+
+
+components:
+ responses:
+ '400':
+ description: "Bad Request. This could be because: * One of the required parameters/properties are missing or is empty
* Length of input is exceeding maximum length
(See a possible detailed error message in the in the response.)"
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/detailedError'
+ '401':
+ description: "Unauthorized. This could be because:
* The calling system has not been properly authenticated."
+ '403':
+ description: "Forbidden. This could be because:
* The requested information does not belong the organisation of the user
* The calling user does not have the required roles"
+
+ schemas:
+ helloResponse:
+ type: object
+ required:
+ - name
+ - now
+ properties:
+ name:
+ description: Your name
+ type: string
+ example: "John Doe"
+ now:
+ description: Time request was received
+ type: string
+ format: date-time
+ i_can_be_null:
+ type: string
+ nullable: true
+
+ detailedError:
+ allOf:
+ - $ref: '#/components/schemas/basicError'
+ - type: object
+ required:
+ - detailed_error
+ - detailed_error_code
+ properties:
+ detailed_error:
+ description: Detailed error text. This could be a text describing an validation error.
+ type: string
+ detailed_error_code:
+ description:
+ type: string
+ enum:
+ - 10
+ - 20
+
+ basicError:
+ type: object
+ required:
+ - timestamp
+ - status
+ - error
+ - path
+ properties:
+ error:
+ description: Error message.
+ type: string
+ path:
+ description: Path
+ type: string
+ status:
+ description: HTTP status code
+ type: integer
+ timestamp:
+ description: Time of error
+ type: string
+ format: date-time
diff --git a/integrationtest/docker/Dockerfile b/integrationtest/docker/Dockerfile
new file mode 100644
index 0000000..e32ccbe
--- /dev/null
+++ b/integrationtest/docker/Dockerfile
@@ -0,0 +1,12 @@
+FROM kvalitetsit/kithugs:dev
+
+COPY /maven/integrationtest.jar /app/lib/integrationtest.jar
+COPY /maven/org.jacoco.agent-*.jar /jacoco/jacoco-agent.jar
+
+USER root
+
+RUN apt-get update && \
+ apt-get install -y unzip && \
+ unzip /jacoco/jacoco-agent.jar jacocoagent.jar -d /jacoco/
+
+USER appuser
\ No newline at end of file
diff --git a/integrationtest/docker/Dockerfile-resources b/integrationtest/docker/Dockerfile-resources
new file mode 100644
index 0000000..88f3ea4
--- /dev/null
+++ b/integrationtest/docker/Dockerfile-resources
@@ -0,0 +1,9 @@
+FROM bash:latest
+
+# Jacoco agent jar file.
+VOLUME /jacoco-report
+
+# Loop script to avoid termination of container.
+ADD ./loop.sh /loop.sh
+
+CMD [ "/loop.sh" ]
diff --git a/integrationtest/docker/loop.sh b/integrationtest/docker/loop.sh
new file mode 100755
index 0000000..0fc183b
--- /dev/null
+++ b/integrationtest/docker/loop.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+chmod 777 /jacoco-report
+
+while true; do
+ sleep 1
+done
+
diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml
new file mode 100644
index 0000000..f645cb2
--- /dev/null
+++ b/integrationtest/pom.xml
@@ -0,0 +1,257 @@
+
+
+
+ kithugs
+ dk.kvalitetsit.kithugs
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ integrationtest
+
+
+
+ org.testcontainers
+ mockserver
+ test
+
+
+
+ org.testcontainers
+ mariadb
+ test
+
+
+
+ org.mock-server
+ mockserver-client-java
+ test
+
+
+
+
+ org.jacoco
+ org.jacoco.agent
+
+
+
+ org.mariadb.jdbc
+ mariadb-java-client
+ test
+
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ test
+
+
+
+ org.glassfish.jersey.core
+ jersey-common
+ test
+
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ test
+
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-jackson
+ test
+
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
+
+ dk.kvalitetsit.kithugs
+ web
+ test
+
+
+
+ dk.kvalitetsit.kithugs
+ documentation
+ ${project.version}
+ test-jar
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ 4.11.0
+ test
+
+
+
+ com.google.code.findbugs
+ jsr305
+ test
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ test
+
+
+
+ com.google.code.gson
+ gson
+ test
+
+
+
+ io.gsonfire
+ gson-fire
+ test
+
+
+
+
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+ local/kithugs-qa
+
+
+ dev
+ latest
+ ${git.commit.id}
+
+ ${basedir}/docker
+
+ artifact-with-dependencies
+ /app
+
+
+
+
+ true
+
+
+
+ build-image
+ package
+
+ build
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ exec-merge
+ verify
+
+ merge
+
+
+
+
+ /jacoco-report
+
+ **/*.exec
+
+
+
+ /tmp
+
+ **/*.exec
+
+
+
+
+
+
+
+
+
+
+
+
+ build
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ false
+
+
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
+
+
+ docker-test
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ true
+
+
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrationtest/src/main/resources/db/migration/V901__extra_data_for_integration_test.sql b/integrationtest/src/main/resources/db/migration/V901__extra_data_for_integration_test.sql
new file mode 100644
index 0000000..d932408
--- /dev/null
+++ b/integrationtest/src/main/resources/db/migration/V901__extra_data_for_integration_test.sql
@@ -0,0 +1 @@
+insert into hello_table(name) values('Some Name');
diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/AbstractIntegrationTest.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/AbstractIntegrationTest.java
new file mode 100644
index 0000000..cfb54cb
--- /dev/null
+++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/AbstractIntegrationTest.java
@@ -0,0 +1,54 @@
+package dk.kvalitetsit.hello.integrationtest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+public abstract class AbstractIntegrationTest {
+ private static final Logger logger = LoggerFactory.getLogger(AbstractIntegrationTest.class);
+
+ private static GenericContainer helloService;
+ private static String apiBasePath;
+
+ static {
+ Runtime.getRuntime().addShutdownHook(new Thread()
+ {
+ public void run()
+ {
+ if(helloService != null) {
+ logger.info("Stopping hello service container: " + helloService.getContainerId());
+ helloService.getDockerClient().stopContainerCmd(helloService.getContainerId()).exec();
+ }
+ }
+ });
+
+ try {
+ setup();
+ } catch (IOException | URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void setup() throws IOException, URISyntaxException {
+ var runInDocker = Boolean.getBoolean("runInDocker");
+ logger.info("Running integration test in docker container: " + runInDocker);
+
+ ServiceStarter serviceStarter;
+ serviceStarter = new ServiceStarter();
+ if(runInDocker) {
+ helloService = serviceStarter.startServicesInDocker();
+ apiBasePath = "http://" + helloService.getContainerIpAddress() + ":" + helloService.getMappedPort(8080);
+ }
+ else {
+ serviceStarter.startServices();
+ apiBasePath = "http://localhost:8080";
+ }
+ }
+
+ String getApiBasePath() {
+ return apiBasePath;
+ }
+}
diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/HelloIT.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/HelloIT.java
new file mode 100644
index 0000000..8e58fb3
--- /dev/null
+++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/HelloIT.java
@@ -0,0 +1,63 @@
+package dk.kvalitetsit.hello.integrationtest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openapitools.client.ApiClient;
+import org.openapitools.client.ApiException;
+import org.openapitools.client.JSON;
+import org.openapitools.client.api.KithugsApi;
+import org.openapitools.client.model.DetailedError;
+
+import java.util.UUID;
+
+import static org.junit.Assert.*;
+
+public class HelloIT extends AbstractIntegrationTest {
+
+ private final KithugsApi helloApi;
+ private final JSON json;
+
+ public HelloIT() {
+ var apiClient = new ApiClient();
+ apiClient.setBasePath(getApiBasePath());
+
+ json = apiClient.getJSON();
+ helloApi = new KithugsApi(apiClient);
+ }
+
+ @Test
+ public void testCallService() throws ApiException {
+ var input = "John Dow";
+
+ var result = helloApi.v1HelloGet(input);
+
+ assertNotNull(result);
+ assertEquals(input, result.getName());
+ assertNull(result.getiCanBeNull());
+ assertNotNull(result.getNow());
+ }
+
+ @Test
+ public void testCallServiceNameTooLong() throws ApiException {
+ var input = "John Doe Is Too Long";
+
+ var thrownException = Assert.assertThrows(ApiException.class, () -> helloApi.v1HelloGet(input));
+ assertEquals(500, thrownException.getCode());
+ }
+
+ @Test
+ public void testCallServiceNameValidationError() throws ApiException {
+ var input = "NOT_VALID";
+
+ var thrownException = Assert.assertThrows(ApiException.class, () -> helloApi.v1HelloGet(input));
+ assertEquals(400, thrownException.getCode());
+
+ DetailedError detailedError = JSON.deserialize(thrownException.getResponseBody(), DetailedError.class);
+ assertEquals("Bad Request", detailedError.getError());
+ assertEquals("/v1/hello", detailedError.getPath());
+ assertEquals("NOT_VALID is not a valid name.", detailedError.getDetailedError());
+ assertEquals(DetailedError.DetailedErrorCodeEnum._10, detailedError.getDetailedErrorCode());
+ assertNotNull(detailedError.getTimestamp());
+ assertEquals(400, detailedError.getStatus().longValue());
+ }
+}
diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/ServiceStarter.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/ServiceStarter.java
new file mode 100644
index 0000000..b5c0024
--- /dev/null
+++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/ServiceStarter.java
@@ -0,0 +1,110 @@
+package dk.kvalitetsit.hello.integrationtest;
+
+import com.github.dockerjava.api.model.VolumesFrom;
+import dk.kvalitetsit.hello.Application;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.MariaDBContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import java.util.Collections;
+
+public class ServiceStarter {
+ private static final Logger logger = LoggerFactory.getLogger(ServiceStarter.class);
+ private static final Logger serviceLogger = LoggerFactory.getLogger("kithugs");
+ private static final Logger mariadbLogger = LoggerFactory.getLogger("mariadb");
+
+ private Network dockerNetwork;
+ private String jdbcUrl;
+
+ public void startServices() {
+ dockerNetwork = Network.newNetwork();
+
+ setupDatabaseContainer();
+
+ System.setProperty("JDBC.URL", jdbcUrl);
+ System.setProperty("JDBC.USER", "hellouser");
+ System.setProperty("JDBC.PASS", "secret1234");
+
+ SpringApplication.run(Application.class);
+ }
+
+ public GenericContainer> startServicesInDocker() {
+ dockerNetwork = Network.newNetwork();
+
+ setupDatabaseContainer();
+
+ var resourcesContainerName = "kithugs-resources";
+ var resourcesRunning = containerRunning(resourcesContainerName);
+ logger.info("Resource container is running: " + resourcesRunning);
+
+ GenericContainer> service;
+
+ // Start service
+ if (resourcesRunning) {
+ VolumesFrom volumesFrom = new VolumesFrom(resourcesContainerName);
+ service = new GenericContainer<>("local/kithugs-qa:dev")
+ .withCreateContainerCmdModifier(modifier -> modifier.withVolumesFrom(volumesFrom))
+ .withEnv("JVM_OPTS", "-javaagent:/jacoco/jacocoagent.jar=output=file,destfile=/jacoco-report/jacoco-it.exec,dumponexit=true,append=true -cp integrationtest.jar");
+ } else {
+ service = new GenericContainer<>("local/kithugs-qa:dev")
+ .withFileSystemBind("/tmp", "/jacoco-report/")
+ .withEnv("JVM_OPTS", "-javaagent:/jacoco/jacocoagent.jar=output=file,destfile=/jacoco-report/jacoco-it.exec,dumponexit=true -cp integrationtest.jar");
+ }
+
+ service.withNetwork(dockerNetwork)
+ .withNetworkAliases("kithugs")
+
+ .withEnv("LOG_LEVEL", "INFO")
+
+ .withEnv("JDBC_URL", "jdbc:mariadb://mariadb:3306/hellodb")
+ .withEnv("JDBC_USER", "hellouser")
+ .withEnv("JDBC_PASS", "secret1234")
+
+ .withEnv("spring.flyway.locations", "classpath:db/migration,filesystem:/app/sql")
+ .withClasspathResourceMapping("db/migration/V901__extra_data_for_integration_test.sql", "/app/sql/V901__extra_data_for_integration_test.sql", BindMode.READ_ONLY)
+// .withEnv("JVM_OPTS", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000")
+
+ .withExposedPorts(8081,8080)
+ .waitingFor(Wait.forHttp("/actuator").forPort(8081).forStatusCode(200));
+ service.start();
+ attachLogger(serviceLogger, service);
+
+ return service;
+ }
+
+ private boolean containerRunning(String containerName) {
+ return DockerClientFactory
+ .instance()
+ .client()
+ .listContainersCmd()
+ .withNameFilter(Collections.singleton(containerName))
+ .exec()
+ .size() != 0;
+ }
+
+ private void setupDatabaseContainer() {
+ // Database server for Organisation.
+ MariaDBContainer> mariadb = new MariaDBContainer<>("mariadb:10.6")
+ .withDatabaseName("hellodb")
+ .withUsername("hellouser")
+ .withPassword("secret1234")
+ .withNetwork(dockerNetwork)
+ .withNetworkAliases("mariadb");
+ mariadb.start();
+ jdbcUrl = mariadb.getJdbcUrl();
+ attachLogger(mariadbLogger, mariadb);
+ }
+
+ private void attachLogger(Logger logger, GenericContainer> container) {
+ ServiceStarter.logger.info("Attaching logger to container: " + container.getContainerInfo().getName());
+ Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(logger);
+ container.followOutput(logConsumer);
+ }
+}
diff --git a/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/TestApplication.java b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/TestApplication.java
new file mode 100644
index 0000000..70bfda0
--- /dev/null
+++ b/integrationtest/src/test/java/dk/kvalitetsit/hello/integrationtest/TestApplication.java
@@ -0,0 +1,11 @@
+package dk.kvalitetsit.hello.integrationtest;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+@SpringBootApplication
+public class TestApplication extends SpringBootServletInitializer {
+ public static void main(String[] args) {
+ new ServiceStarter().startServices();
+ }
+}
diff --git a/integrationtest/src/test/resources/logback-test.xml b/integrationtest/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..54b6517
--- /dev/null
+++ b/integrationtest/src/test/resources/logback-test.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - correlation-id: %X{correlation-id} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d694a58
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,248 @@
+
+
+ 4.0.0
+ pom
+
+
+ web
+ service
+ integrationtest
+ testreport
+ documentation
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.2
+
+ dk.kvalitetsit.kithugs
+ kithugs
+ 0.0.1-SNAPSHOT
+ kithugs
+ KITHUGS service
+
+
+ 17
+ 1.18.3
+ 2.0.2
+ 2.0.0
+ 0.8.10
+
+
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+
+
+
+
+
+ dk.kvalitetsit.kithugs
+ service
+ ${project.version}
+
+
+
+ dk.kvalitetsit.kithugs
+ integrationtest
+ ${project.version}
+
+
+
+ dk.kvalitetsit.kithugs
+ web
+ ${project.version}
+
+
+
+ dk.kvalitetsit.kithugs
+ documentation
+ ${project.version}
+
+
+
+
+ net.logstash.logback
+ logstash-logback-encoder
+ 7.4
+
+
+
+
+ dk.kvalitetsit
+ spring-prometheus-app-info
+ ${spring-prometheus-app-info-version}
+
+
+
+
+ dk.kvalitetsit
+ spring-request-id-logger
+ ${spring-request-id-logger-version}
+
+
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.15
+
+
+
+ io.swagger
+ swagger-annotations
+ 1.6.11
+
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.1.1
+
+
+
+
+
+ org.testcontainers
+ mockserver
+ ${testcontainers.version}
+
+
+
+ org.testcontainers
+ mariadb
+ ${testcontainers.version}
+
+
+
+ org.mock-server
+ mockserver
+ 3.12
+
+
+
+ org.mock-server
+ mockserver-client-java
+ 5.15.0
+
+
+
+
+ org.jacoco
+ org.jacoco.agent
+ ${jacoco-version}
+
+
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ test
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.11.0
+ test
+
+
+
+ io.gsonfire
+ gson-fire
+ 1.8.5
+ test
+
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+ test
+
+
+
+
+
+
+
+ ${project.artifactId}
+
+
+
+ io.fabric8
+ docker-maven-plugin
+ 0.43.2
+
+
+
+
+
+
+ pl.project13.maven
+ git-commit-id-plugin
+ 4.9.10
+
+
+
+ revision
+
+
+
+
+ git
+ dd.MM.yyyy '@' HH:mm:ss z
+ true
+ false
+ ${project.basedir}/.git
+ false
+ true
+ target/classes/git.properties
+ false
+ 7
+ true
+ false
+
+ false
+ false
+ -dirty
+ false
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.1.2
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-version}
+
+
+
+ prepare-agent
+
+
+
+
+
+
+
diff --git a/service/pom.xml b/service/pom.xml
new file mode 100644
index 0000000..759628d
--- /dev/null
+++ b/service/pom.xml
@@ -0,0 +1,108 @@
+
+
+
+ kithugs
+ dk.kvalitetsit.kithugs
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ service
+
+
+
+ dk.kvalitetsit.kithugs
+ documentation
+
+
+
+
+ org.springframework
+ spring-context
+
+
+
+ org.springframework
+ spring-web
+
+
+
+ org.springframework
+ spring-beans
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+
+ org.mariadb.jdbc
+ mariadb-java-client
+
+
+
+ org.flywaydb
+ flyway-mysql
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ dk.kvalitetsit
+ spring-prometheus-app-info
+
+
+
+
+ dk.kvalitetsit
+ spring-request-id-logger
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ provided
+
+
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+ org.testcontainers
+ mariadb
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
\ No newline at end of file
diff --git a/service/src/main/java/dk/kvalitetsit/hello/configuration/DatabaseConfiguration.java b/service/src/main/java/dk/kvalitetsit/hello/configuration/DatabaseConfiguration.java
new file mode 100644
index 0000000..b493300
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/configuration/DatabaseConfiguration.java
@@ -0,0 +1,30 @@
+package dk.kvalitetsit.hello.configuration;
+
+import dk.kvalitetsit.hello.dao.HelloDao;
+import dk.kvalitetsit.hello.dao.HelloDaoImpl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableTransactionManagement
+public class DatabaseConfiguration {
+ @Bean
+ public HelloDao helloDao(DataSource dataSource) {
+ return new HelloDaoImpl(dataSource);
+ }
+
+ @Bean
+ public DataSource dataSource(@Value("${jdbc.url}") String jdbcUrl, @Value("${jdbc.user}") String jdbcUser, @Value("${jdbc.pass}") String jdbcPass) {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setUrl(jdbcUrl);
+ dataSource.setUsername(jdbcUser);
+ dataSource.setPassword(jdbcPass);
+
+ return dataSource;
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/dk/kvalitetsit/hello/configuration/HelloConfiguration.java b/service/src/main/java/dk/kvalitetsit/hello/configuration/HelloConfiguration.java
new file mode 100644
index 0000000..cf68c5e
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/configuration/HelloConfiguration.java
@@ -0,0 +1,14 @@
+package dk.kvalitetsit.hello.configuration;
+
+import dk.kvalitetsit.hello.service.HelloService;
+import dk.kvalitetsit.hello.service.HelloServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class HelloConfiguration{
+ @Bean
+ public HelloService helloService() {
+ return new HelloServiceImpl();
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/configuration/JacksonConfiguration.java b/service/src/main/java/dk/kvalitetsit/hello/configuration/JacksonConfiguration.java
new file mode 100644
index 0000000..457913e
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/configuration/JacksonConfiguration.java
@@ -0,0 +1,14 @@
+package dk.kvalitetsit.hello.configuration;
+
+import com.fasterxml.jackson.databind.Module;
+import org.openapitools.jackson.nullable.JsonNullableModule;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JacksonConfiguration {
+ @Bean
+ public Module jsonNullableModule() {
+ return new JsonNullableModule();
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/ErrorController.java b/service/src/main/java/dk/kvalitetsit/hello/controller/ErrorController.java
new file mode 100644
index 0000000..6366a14
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/controller/ErrorController.java
@@ -0,0 +1,31 @@
+package dk.kvalitetsit.hello.controller;
+
+import dk.kvalitetsit.hello.controller.exception.AbstractApiException;
+import jakarta.servlet.http.HttpServletRequest;
+import org.openapitools.model.DetailedError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class ErrorController {
+ private static final Logger logger = LoggerFactory.getLogger(ErrorController.class);
+
+ @ExceptionHandler(AbstractApiException.class)
+ public ResponseEntity handleApiException(AbstractApiException e, HttpServletRequest request) {
+ logger.debug("Handling ApiException: {}", e.getHttpStatus());
+ var error = new DetailedError()
+ .path(request.getRequestURI())
+ .timestamp(OffsetDateTime.now())
+ .status(e.getHttpStatus().value())
+ .error(e.getHttpStatus().getReasonPhrase())
+ .detailedErrorCode(e.getDetailedErrorCode())
+ .detailedError(e.getDetailedError());
+
+ return ResponseEntity.status(e.getHttpStatus().value()).body(error);
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/HelloController.java b/service/src/main/java/dk/kvalitetsit/hello/controller/HelloController.java
new file mode 100644
index 0000000..69d44c8
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/controller/HelloController.java
@@ -0,0 +1,44 @@
+package dk.kvalitetsit.hello.controller;
+
+import dk.kvalitetsit.hello.controller.exception.BadRequestException;
+import dk.kvalitetsit.hello.service.HelloService;
+import dk.kvalitetsit.hello.service.model.HelloServiceInput;
+import org.openapitools.api.KithugsApi;
+import org.openapitools.model.DetailedError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+// CORS - Consider if this is needed in your application. Only here to make Swagger UI work.
+@CrossOrigin(origins = "http://localhost")
+public class HelloController implements KithugsApi {
+ private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
+ private final HelloService helloService;
+
+ public HelloController(HelloService helloService) {
+ this.helloService = helloService;
+ }
+
+ @Override
+ public ResponseEntity v1HelloGet(String name) {
+ logger.debug("Enter GET hello.");
+
+ // Just for demonstrating error response. Actual validation should most likely be somewhere else.
+ if(name.equalsIgnoreCase("NOT_VALID")) {
+ throw new BadRequestException(DetailedError.DetailedErrorCodeEnum._10, "%s is not a valid name.".formatted(name));
+ }
+
+ var serviceInput = new HelloServiceInput(name);
+
+ var serviceResponse = helloService.helloServiceBusinessLogic(serviceInput);
+
+ var helloResponse = new org.openapitools.model.HelloResponse();
+ helloResponse.setName(serviceResponse.name());
+ helloResponse.setNow(serviceResponse.now().toOffsetDateTime());
+
+ return ResponseEntity.ok(helloResponse);
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/exception/AbstractApiException.java b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/AbstractApiException.java
new file mode 100644
index 0000000..998c586
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/AbstractApiException.java
@@ -0,0 +1,28 @@
+package dk.kvalitetsit.hello.controller.exception;
+
+import org.openapitools.model.DetailedError;
+import org.springframework.http.HttpStatus;
+
+public class AbstractApiException extends RuntimeException {
+ private final HttpStatus httpStatus;
+ private final DetailedError.DetailedErrorCodeEnum detailedErrorCodeEnum;
+ private final String detailedError;
+
+ public AbstractApiException(HttpStatus httpStatus, DetailedError.DetailedErrorCodeEnum detailedErrorCodeEnum, String detailedErrorCode) {
+ this.httpStatus = httpStatus;
+ this.detailedErrorCodeEnum = detailedErrorCodeEnum;
+ this.detailedError = detailedErrorCode;
+ }
+
+ public HttpStatus getHttpStatus() {
+ return httpStatus;
+ }
+
+ public DetailedError.DetailedErrorCodeEnum getDetailedErrorCode() {
+ return detailedErrorCodeEnum;
+ }
+
+ public String getDetailedError() {
+ return detailedError;
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/controller/exception/BadRequestException.java b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/BadRequestException.java
new file mode 100644
index 0000000..9db7b75
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/controller/exception/BadRequestException.java
@@ -0,0 +1,11 @@
+package dk.kvalitetsit.hello.controller.exception;
+
+import org.openapitools.model.DetailedError;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+public class BadRequestException extends AbstractApiException {
+ public BadRequestException(DetailedError.DetailedErrorCodeEnum errorCode, String errorText) {
+ super(HttpStatus.BAD_REQUEST, errorCode, errorText);
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDao.java b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDao.java
new file mode 100644
index 0000000..bf3b54a
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDao.java
@@ -0,0 +1,11 @@
+package dk.kvalitetsit.hello.dao;
+
+import dk.kvalitetsit.hello.dao.entity.HelloEntity;
+
+import java.util.List;
+
+public interface HelloDao {
+ void insert(HelloEntity helloEntity);
+
+ List findAll();
+}
\ No newline at end of file
diff --git a/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDaoImpl.java b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDaoImpl.java
new file mode 100644
index 0000000..29c53e5
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/dao/HelloDaoImpl.java
@@ -0,0 +1,39 @@
+package dk.kvalitetsit.hello.dao;
+
+import dk.kvalitetsit.hello.dao.entity.HelloEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.core.DataClassRowMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.List;
+
+public class HelloDaoImpl implements HelloDao {
+ private static final Logger logger = LoggerFactory.getLogger(HelloDaoImpl.class);
+ private final NamedParameterJdbcTemplate template;
+
+ public HelloDaoImpl(DataSource dataSource) {
+ template = new NamedParameterJdbcTemplate(dataSource);
+ }
+
+ @Override
+ public void insert(HelloEntity helloEntity) {
+ logger.info("Inserting new entry in database.");
+
+ var sql = "insert into hello_table(name) values(:name)";
+
+ var parameterMap = new HashMap();
+ parameterMap.put("name", helloEntity.name());
+
+ template.update(sql, parameterMap);
+ }
+
+ @Override
+ public List findAll() {
+ var sql = "select * from hello_table";
+
+ return template.query(sql, new DataClassRowMapper<>(HelloEntity.class));
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/dao/entity/HelloEntity.java b/service/src/main/java/dk/kvalitetsit/hello/dao/entity/HelloEntity.java
new file mode 100644
index 0000000..4f5576d
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/dao/entity/HelloEntity.java
@@ -0,0 +1,7 @@
+package dk.kvalitetsit.hello.dao.entity;
+
+public record HelloEntity(Long id, String name) {
+ public static HelloEntity createInstance(String name) {
+ return new HelloEntity(null, name);
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/HelloService.java b/service/src/main/java/dk/kvalitetsit/hello/service/HelloService.java
new file mode 100644
index 0000000..e2771ee
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/service/HelloService.java
@@ -0,0 +1,8 @@
+package dk.kvalitetsit.hello.service;
+
+import dk.kvalitetsit.hello.service.model.HelloServiceOutput;
+import dk.kvalitetsit.hello.service.model.HelloServiceInput;
+
+public interface HelloService {
+ HelloServiceOutput helloServiceBusinessLogic(HelloServiceInput input);
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/HelloServiceImpl.java b/service/src/main/java/dk/kvalitetsit/hello/service/HelloServiceImpl.java
new file mode 100644
index 0000000..8fbfdee
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/service/HelloServiceImpl.java
@@ -0,0 +1,13 @@
+package dk.kvalitetsit.hello.service;
+
+import dk.kvalitetsit.hello.service.model.HelloServiceInput;
+import dk.kvalitetsit.hello.service.model.HelloServiceOutput;
+
+import java.time.ZonedDateTime;
+
+public class HelloServiceImpl implements HelloService {
+ @Override
+ public HelloServiceOutput helloServiceBusinessLogic(HelloServiceInput input) {
+ return new HelloServiceOutput(input.name(), ZonedDateTime.now());
+ }
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceInput.java b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceInput.java
new file mode 100644
index 0000000..7c5e6d5
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceInput.java
@@ -0,0 +1,4 @@
+package dk.kvalitetsit.hello.service.model;
+
+public record HelloServiceInput(String name) {
+}
diff --git a/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceOutput.java b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceOutput.java
new file mode 100644
index 0000000..5ef82e1
--- /dev/null
+++ b/service/src/main/java/dk/kvalitetsit/hello/service/model/HelloServiceOutput.java
@@ -0,0 +1,6 @@
+package dk.kvalitetsit.hello.service.model;
+
+import java.time.ZonedDateTime;
+
+public record HelloServiceOutput(String name, ZonedDateTime now) {
+}
diff --git a/service/src/main/resources/db/migration/V10__create_hello_table.sql b/service/src/main/resources/db/migration/V10__create_hello_table.sql
new file mode 100644
index 0000000..dfabed6
--- /dev/null
+++ b/service/src/main/resources/db/migration/V10__create_hello_table.sql
@@ -0,0 +1,6 @@
+CREATE TABLE hello_table (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ name varchar(100) not null,
+ PRIMARY KEY (id)
+)
+;
diff --git a/service/src/test/java/dk/kvalitetsit/hello/configuration/TestConfiguration.java b/service/src/test/java/dk/kvalitetsit/hello/configuration/TestConfiguration.java
new file mode 100644
index 0000000..bccb73e
--- /dev/null
+++ b/service/src/test/java/dk/kvalitetsit/hello/configuration/TestConfiguration.java
@@ -0,0 +1,20 @@
+package dk.kvalitetsit.hello.configuration;
+
+import dk.kvalitetsit.hello.dao.HelloDao;
+import dk.kvalitetsit.hello.dao.HelloDaoImpl;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableAutoConfiguration
+public class TestConfiguration {
+ // Configure beans used for test
+
+ @Bean
+ public HelloDao helloDao(DataSource dataSource) {
+ return new HelloDaoImpl(dataSource);
+ }
+}
diff --git a/service/src/test/java/dk/kvalitetsit/hello/controller/HelloControllerTest.java b/service/src/test/java/dk/kvalitetsit/hello/controller/HelloControllerTest.java
new file mode 100644
index 0000000..7014a37
--- /dev/null
+++ b/service/src/test/java/dk/kvalitetsit/hello/controller/HelloControllerTest.java
@@ -0,0 +1,48 @@
+package dk.kvalitetsit.hello.controller;
+
+import dk.kvalitetsit.hello.service.HelloService;
+import dk.kvalitetsit.hello.service.model.HelloServiceInput;
+import dk.kvalitetsit.hello.service.model.HelloServiceOutput;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+
+import static junit.framework.TestCase.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.times;
+
+public class HelloControllerTest {
+ private HelloController helloController;
+ private HelloService helloService;
+
+ @Before
+ public void setup() {
+ helloService = Mockito.mock(HelloService.class);
+
+ helloController = new HelloController(helloService);
+ }
+
+ @Test
+ public void testCallController() {
+ var name = UUID.randomUUID().toString();
+
+ var expectedDate = ZonedDateTime.now();
+ Mockito.when(helloService.helloServiceBusinessLogic(Mockito.any())).then(a -> new HelloServiceOutput(a.getArgument(0, HelloServiceInput.class).name(), expectedDate));
+
+ var result = helloController.v1HelloGet(name);
+
+ assertNotNull(result);
+ assertEquals(name, result.getBody().getName());
+ assertEquals(expectedDate.toOffsetDateTime(), result.getBody().getNow());
+
+ var inputArgumentCaptor = ArgumentCaptor.forClass(HelloServiceInput.class);
+ Mockito.verify(helloService, times(1)).helloServiceBusinessLogic(inputArgumentCaptor.capture());
+
+ assertNotNull(inputArgumentCaptor.getValue());
+ assertEquals(name, inputArgumentCaptor.getValue().name());
+ }
+}
diff --git a/service/src/test/java/dk/kvalitetsit/hello/dao/AbstractDaoTest.java b/service/src/test/java/dk/kvalitetsit/hello/dao/AbstractDaoTest.java
new file mode 100644
index 0000000..fe61a55
--- /dev/null
+++ b/service/src/test/java/dk/kvalitetsit/hello/dao/AbstractDaoTest.java
@@ -0,0 +1,44 @@
+package dk.kvalitetsit.hello.dao;
+
+import dk.kvalitetsit.hello.configuration.TestConfiguration;
+import dk.kvalitetsit.hello.configuration.DatabaseConfiguration;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.containers.MariaDBContainer;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@PropertySource("test.properties")
+@ContextConfiguration(
+ classes = { TestConfiguration.class, DatabaseConfiguration.class},
+ loader = AnnotationConfigContextLoader.class)
+@Transactional
+abstract public class AbstractDaoTest {
+ private static Object initialized = null;
+
+ @BeforeClass
+ public static void setupMariadbJdbcUrl() {
+ if (initialized == null) {
+ var username = "hellouser";
+ var password = "secret1234";
+
+ MariaDBContainer mariadb = new MariaDBContainer("mariadb:10.6")
+ .withDatabaseName("hellodb")
+ .withUsername(username)
+ .withPassword(password);
+ mariadb.start();
+
+ String jdbcUrl = mariadb.getJdbcUrl();
+ System.setProperty("jdbc.url", jdbcUrl);
+ System.setProperty("jdbc.user", username);
+ System.setProperty("jdbc.pass", password);
+
+ initialized = new Object();
+ }
+ }
+}
+
diff --git a/service/src/test/java/dk/kvalitetsit/hello/dao/HelloDaoTest.java b/service/src/test/java/dk/kvalitetsit/hello/dao/HelloDaoTest.java
new file mode 100644
index 0000000..d47256e
--- /dev/null
+++ b/service/src/test/java/dk/kvalitetsit/hello/dao/HelloDaoTest.java
@@ -0,0 +1,27 @@
+package dk.kvalitetsit.hello.dao;
+
+import dk.kvalitetsit.hello.dao.entity.HelloEntity;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.UUID;
+
+import static org.junit.Assert.*;
+
+public class HelloDaoTest extends AbstractDaoTest {
+ @Autowired
+ private HelloDao helloDao;
+
+ @Test
+ public void testByMessageId() {
+ var input = HelloEntity.createInstance(UUID.randomUUID().toString());
+
+ helloDao.insert(input);
+
+ var result = helloDao.findAll();
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals(input.name(), result.get(0).name());
+ assertNotNull(result.get(0).id());
+ }
+}
diff --git a/service/src/test/java/dk/kvalitetsit/hello/service/HelloServiceImplTest.java b/service/src/test/java/dk/kvalitetsit/hello/service/HelloServiceImplTest.java
new file mode 100644
index 0000000..43a8bdf
--- /dev/null
+++ b/service/src/test/java/dk/kvalitetsit/hello/service/HelloServiceImplTest.java
@@ -0,0 +1,29 @@
+package dk.kvalitetsit.hello.service;
+
+import dk.kvalitetsit.hello.service.model.HelloServiceInput;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class HelloServiceImplTest {
+ private HelloService helloService;
+
+ @Before
+ public void setup() {
+ helloService = new HelloServiceImpl();
+ }
+
+ @Test
+ public void testValidInput() {
+ var input = new HelloServiceInput(UUID.randomUUID().toString());
+
+ var result = helloService.helloServiceBusinessLogic(input);
+ assertNotNull(result);
+ assertNotNull(result.now());
+ assertEquals(input.name(), result.name());
+ }
+}
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000..79bc0ed
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if ! command -v tree &> /dev/null; then
+ echo "Command 'tree' not found. Please install tree."
+ exit 1
+fi
+
+if [ -z $1 ]; then
+ echo "Run setup.sh REPOSITORY_NAME"
+ exit 1
+fi
+
+ignore_files=".git|setup.sh"
+
+for input_file in `tree -I "${ignore_files}" -Ffai --noreport`
+do
+ if [ ! -d "${input_file}" ]; then
+ echo "Processing file: ${input_file}"
+ sed -i -e "s/kithugs/$1/g" "${input_file}"
+ fi
+done
+
+# Clean up / implode
+rm setup.sh
\ No newline at end of file
diff --git a/testreport/pom.xml b/testreport/pom.xml
new file mode 100644
index 0000000..e652988
--- /dev/null
+++ b/testreport/pom.xml
@@ -0,0 +1,48 @@
+
+
+
+ kithugs
+ dk.kvalitetsit.kithugs
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ testreport
+
+
+
+ dk.kvalitetsit.kithugs
+ service
+
+
+
+ dk.kvalitetsit.kithugs
+ integrationtest
+
+
+
+ dk.kvalitetsit.kithugs
+ web
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ report-aggregate
+ verify
+
+ report-aggregate
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/docker/Dockerfile b/web/docker/Dockerfile
new file mode 100644
index 0000000..e9ed22c
--- /dev/null
+++ b/web/docker/Dockerfile
@@ -0,0 +1,18 @@
+FROM ibm-semeru-runtimes:open-17-jre
+
+RUN apt-get update && apt-get install -y tzdata
+
+RUN groupadd --gid 11000 appuser && \
+ useradd --gid 11000 --uid 11001 -m appuser
+
+WORKDIR /home/appuser
+COPY entrypoint.sh .
+
+COPY config/logback.xml logback-spring.xml
+COPY /maven/web-exec.jar web.jar
+
+EXPOSE 8080
+EXPOSE 8081
+
+USER appuser
+ENTRYPOINT ["/home/appuser/entrypoint.sh"]
\ No newline at end of file
diff --git a/web/docker/config/logback.xml b/web/docker/config/logback.xml
new file mode 100644
index 0000000..b79923f
--- /dev/null
+++ b/web/docker/config/logback.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ {
+ "logger": "%logger",
+ "level": "%level",
+ "correlation-id": "%X{correlation-id}",
+ "thread": "%thread",
+ "message": "%m"
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh
new file mode 100755
index 0000000..0f16b2b
--- /dev/null
+++ b/web/docker/entrypoint.sh
@@ -0,0 +1,34 @@
+#! /bin/bash
+
+if [ "$TZ" = "" ]
+then
+ echo "Using default timezone (UTC)"
+fi
+
+if [[ -z $LOGGING_CONFIG ]]; then
+ echo "Default logback configuration file: /home/appuser/logback-spring.xml"
+ export LOGGING_CONFIG=/home/appuser/logback-spring.xml
+fi
+
+if [[ -z $LOG_LEVEL ]]; then
+ echo "Default LOG_LEVEL = INFO"
+ export LOG_LEVEL=INFO
+fi
+
+if [[ -z $LOG_LEVEL_FRAMEWORK ]]; then
+ echo "Default LOG_LEVEL_FRAMEWORK = INFO"
+ export LOG_LEVEL_FRAMEWORK=INFO
+fi
+
+if [[ -z $CORRELATION_ID ]]; then
+ echo "Default CORRELATION_ID = correlation-id"
+ export CORRELATION_ID=x-request-id
+fi
+
+JAR_FILE=web.jar
+
+echo "Starting service with the following command."
+echo "java $JVM_OPTS -jar $JAR_FILE"
+
+# start the application
+exec java $JVM_OPTS -jar $JAR_FILE
diff --git a/web/pom.xml b/web/pom.xml
new file mode 100644
index 0000000..f0ff6f6
--- /dev/null
+++ b/web/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+ kithugs
+ dk.kvalitetsit.kithugs
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ web
+
+
+
+ dk.kvalitetsit.kithugs
+ service
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ net.logstash.logback
+ logstash-logback-encoder
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ exec
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ .
+ ./lib/
+
+
+
+
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+ kvalitetsit/kithugs
+
+
+ latest
+ dev
+ ${git.commit.id}
+
+ ${basedir}/docker
+
+
+
+
+
+
+
+
+ artifact
+
+
+
+
+
+
+
+ build-image
+ install
+
+ build
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/main/java/dk/kvalitetsit/hello/Application.java b/web/src/main/java/dk/kvalitetsit/hello/Application.java
new file mode 100644
index 0000000..a833cb2
--- /dev/null
+++ b/web/src/main/java/dk/kvalitetsit/hello/Application.java
@@ -0,0 +1,11 @@
+package dk.kvalitetsit.hello;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/web/src/test/java/dk/kvalitetsit/hello/ApplicationTests.java b/web/src/test/java/dk/kvalitetsit/hello/ApplicationTests.java
new file mode 100644
index 0000000..6b46805
--- /dev/null
+++ b/web/src/test/java/dk/kvalitetsit/hello/ApplicationTests.java
@@ -0,0 +1,14 @@
+package dk.kvalitetsit.hello;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+@Disabled
+class ApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+}
diff --git a/web/src/test/resources/application.properties b/web/src/test/resources/application.properties
new file mode 100644
index 0000000..17666cf
--- /dev/null
+++ b/web/src/test/resources/application.properties
@@ -0,0 +1,9 @@
+video.api.endpoint=http://localhost
+
+static.files.path=/start
+background.sub.path=path
+background.name=name
+style.sub.path=another_path
+style.name=another_name
+video.portal=portal
+organisation.api.endpoint=http://localhost
\ No newline at end of file