Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be61a6dfbc | |||
| 7029074c62 | |||
| 5abb6b2204 | |||
| 104636199f | |||
| 9124ccb204 | |||
| 6715354be3 | |||
| 35c7712f85 | |||
| 84d09f6dbb | |||
| c5ff6e555a | |||
| e4a70e0e43 | |||
| 6522809dce | |||
| 19f46bd01f | |||
| e48cf674a5 |
12
.github/workflows/release.yml
vendored
Normal file
12
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: swordsteel/hlaeja-common-workflows/.github/workflows/release.yml@master
|
||||||
|
secrets:
|
||||||
|
CI_BOT_PAT: ${{ secrets.CI_BOT_PAT }}
|
||||||
|
with:
|
||||||
|
TYPE: service
|
||||||
12
.github/workflows/run-checks.yml
vendored
Normal file
12
.github/workflows/run-checks.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: Pull request validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
uses: swordsteel/hlaeja-common-workflows/.github/workflows/run-checks.yml@master
|
||||||
|
secrets:
|
||||||
|
CI_BOT_PAT: ${{ secrets.CI_BOT_PAT }}
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,5 +39,5 @@ out/
|
|||||||
### Kotlin ###
|
### Kotlin ###
|
||||||
.kotlin
|
.kotlin
|
||||||
|
|
||||||
### cert ###
|
#### Hlæja ###
|
||||||
cert/
|
/cert/
|
||||||
|
|||||||
85
README.md
85
README.md
@@ -4,66 +4,61 @@ Classes and endpoints, to shape and to steer, Devices and sensors, their purpose
|
|||||||
|
|
||||||
## Properties for deployment
|
## Properties for deployment
|
||||||
|
|
||||||
| name | required | info |
|
| name | required | info |
|
||||||
|-------------------------------|----------|--------------------------|
|
|-------------------------------|:--------:|------------------------------|
|
||||||
| spring.profiles.active | * | Spring Boot environment |
|
| spring.profiles.active | ✓ | Spring Boot environment |
|
||||||
| server.port | * | HTTP port |
|
| server.port | ✓ | HTTP port |
|
||||||
| server.ssl.enabled | * | HTTP Enable SSL |
|
| server.ssl.enabled | ✓ | HTTP Enable SSL |
|
||||||
| server.ssl.key-store | * | HTTP Keystore |
|
| server.ssl.key-store | ✓ | HTTP Keystore |
|
||||||
| server.ssl.key-store-type | * | HTTP Cert Type |
|
| server.ssl.key-store-type | ✓ | HTTP Cert Type |
|
||||||
| server.ssl.key-store-password | ** | HTTP Cert Pass |
|
| server.ssl.key-store-password | ✗ | HTTP Cert Pass |
|
||||||
| jwt.public-key | * | JWT public key |
|
| spring.cache.type | | Cache type (redis) |
|
||||||
| device-registry.url | * | Device Register URL |
|
| spring.data.redis.host | ✓ | Redis host |
|
||||||
| device-data.url | * | Device Data URL |
|
| spring.data.redis.port | | Redis port |
|
||||||
| device-configuration.url | * | Device Configuration URL |
|
| spring.data.redis.database | ✓ | Redis database |
|
||||||
|
| spring.data.redis.password | ✗ | Redis password |
|
||||||
|
| cache.time-to-live | | Cache time to live (minutes) |
|
||||||
|
| jwt.public-key | ✓ | JWT public key |
|
||||||
|
| device-registry.url | ✓ | Device Register URL |
|
||||||
|
| device-data.url | ✓ | Device Data URL |
|
||||||
|
| device-configuration.url | ✓ | Device Configuration URL |
|
||||||
|
|
||||||
Required: * can be stored as text, and ** need to be stored as secret.
|
*Required: ✓ can be stored as text, and ✗ need to be stored as secret.*
|
||||||
|
|
||||||
## Releasing Service
|
## Releasing Service
|
||||||
|
|
||||||
Run `release.sh` script from `master` branch.
|
Run release pipeline from `master` branch.
|
||||||
|
|
||||||
## Development Configuration
|
## Development Configuration
|
||||||
|
|
||||||
### Developer Keystore
|
### Developer Keystore
|
||||||
|
|
||||||
1. Open `hosts` file:
|
We use a keystore to enable HTTPS for our API. To set up your developer environment for local development, please refer to [generate keystore](https://github.com/swordsteel/hlaeja-development/blob/master/doc/keystore.md) documentation. When generating and exporting the certificate for local development, please store it in the `./cert/keystore.p12` folder at the project root.
|
||||||
* On Unix-like systems (Linux, macOS), this directory is typically `/etc/hosts`.
|
|
||||||
* On Windows, this directory is typically `%SystemRoot%\System32\drivers\etc\hosts`.
|
|
||||||
|
|
||||||
2. Add the following lines to the `hosts` file:
|
|
||||||
```text
|
|
||||||
127.0.0.1 deviceapi # Hlæja Device API
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Generate Keystores
|
|
||||||
```shell
|
|
||||||
keytool -genkeypair -alias device-api -keyalg RSA -keysize 2048 -validity 3650 -dname "CN=deviceapi" -keypass password -keystore ./cert/keystore.p12 -storetype PKCS12 -storepass password
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Export the public certificate
|
|
||||||
```shell
|
|
||||||
keytool -export -alias device-api -keystore ./cert/keystore.p12 -storepass password -file ./cert/device-api.cer -rfc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Public RSA Key
|
### Public RSA Key
|
||||||
|
|
||||||
To validate devices, copy file named `public_key.pem` from `./cert` generated for local development in **Hlæja Device Register** in to `./cert`.
|
This service uses the public key from **[Hlæja Device Register](https://github.com/swordsteel/hlaeja-device-registry)** to identify devices. To set up device identification for local development, copy the `public_key.pem` file from the `./cert` directory in **Hlæja Device Register** into the `./cert` directory of this project.
|
||||||
|
|
||||||
### Global gradle properties
|
*Note: For more information on generating RSA keys, please refer to our [generate RSA key](https://github.com/swordsteel/hlaeja-development/blob/master/doc/rsa_key.md) documentation.*
|
||||||
|
|
||||||
To authenticate with Gradle to access repositories that require authentication, you can set your user and token in the `gradle.properties` file.
|
### Global Settings
|
||||||
|
|
||||||
Here's how you can do it:
|
This services rely on a set of global settings to configure development environments. These settings, managed through Gradle properties or environment variables.
|
||||||
|
|
||||||
1. Open or create the `gradle.properties` file in your Gradle user home directory:
|
*Note: For more information on global properties, please refer to our [global settings](https://github.com/swordsteel/hlaeja-development/blob/master/doc/global_settings.md) documentation.*
|
||||||
|
|
||||||
- On Unix-like systems (Linux, macOS), this directory is typically `~/.gradle/`.
|
#### Gradle Properties
|
||||||
- On Windows, this directory is typically `C:\Users\<YourUsername>\.gradle\`.
|
|
||||||
|
|
||||||
2. Add the following lines to the `gradle.properties` file:
|
```properties
|
||||||
```properties
|
repository.user=your_user
|
||||||
repository.user=your_user
|
repository.token=your_token_value
|
||||||
repository.token=your_token_value
|
influxdb.token=your_token_value
|
||||||
```
|
```
|
||||||
or use environment variables `REPOSITORY_USER` and `REPOSITORY_TOKEN`
|
|
||||||
|
#### Environment Variables
|
||||||
|
|
||||||
|
```properties
|
||||||
|
REPOSITORY_USER=your_user
|
||||||
|
REPOSITORY_TOKEN=your_token_value
|
||||||
|
INFLUXDB_TOKEN=your_token_value
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(hlaeja.plugins.kotlin.jvm)
|
alias(hlaeja.plugins.kotlin.jvm)
|
||||||
alias(hlaeja.plugins.kotlin.spring)
|
alias(hlaeja.plugins.kotlin.spring)
|
||||||
alias(hlaeja.plugins.ltd.hlaeja.plugin.certificate)
|
alias(hlaeja.plugins.spring.boot)
|
||||||
alias(hlaeja.plugins.ltd.hlaeja.plugin.service)
|
|
||||||
alias(hlaeja.plugins.spring.dependency.management)
|
alias(hlaeja.plugins.spring.dependency.management)
|
||||||
alias(hlaeja.plugins.springframework.boot)
|
alias(hlaeja.plugins.certificate)
|
||||||
|
alias(hlaeja.plugins.service)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -13,13 +13,13 @@ dependencies {
|
|||||||
implementation(hlaeja.kotlin.logging)
|
implementation(hlaeja.kotlin.logging)
|
||||||
implementation(hlaeja.kotlin.reflect)
|
implementation(hlaeja.kotlin.reflect)
|
||||||
implementation(hlaeja.kotlinx.coroutines)
|
implementation(hlaeja.kotlinx.coroutines)
|
||||||
implementation(hlaeja.library.hlaeja.common.messages)
|
implementation(hlaeja.library.common.messages)
|
||||||
|
implementation(hlaeja.library.jwt)
|
||||||
implementation(hlaeja.springboot.starter.actuator)
|
implementation(hlaeja.springboot.starter.actuator)
|
||||||
|
implementation(hlaeja.springboot.starter.cache)
|
||||||
|
implementation(hlaeja.springboot.starter.redis)
|
||||||
implementation(hlaeja.springboot.starter.webflux)
|
implementation(hlaeja.springboot.starter.webflux)
|
||||||
|
|
||||||
runtimeOnly(hlaeja.jjwt.impl)
|
|
||||||
runtimeOnly(hlaeja.jjwt.jackson)
|
|
||||||
|
|
||||||
testImplementation(hlaeja.kotlin.test.junit5)
|
testImplementation(hlaeja.kotlin.test.junit5)
|
||||||
testImplementation(hlaeja.kotlinx.coroutines.test)
|
testImplementation(hlaeja.kotlinx.coroutines.test)
|
||||||
testImplementation(hlaeja.mockk)
|
testImplementation(hlaeja.mockk)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
org.gradle.jvmargs=-Xmx1g
|
org.gradle.jvmargs=-Xmx1g
|
||||||
version=0.2.0
|
version=0.5.0-SNAPSHOT
|
||||||
catalog=0.6.0
|
catalog=0.11.0-SNAPSHOT
|
||||||
docker.port.expose=8443
|
docker.port.expose=8443
|
||||||
container.port.expose=8443
|
container.port.expose=8443
|
||||||
container.port.host=9000
|
container.port.host=9000
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
9
gradlew
vendored
9
gradlew
vendored
@@ -86,8 +86,7 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -115,7 +114,7 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -206,7 +205,7 @@ fi
|
|||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
@@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-classpath "$CLASSPATH" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
4
gradlew.bat
vendored
4
gradlew.bat
vendored
@@ -70,11 +70,11 @@ goto fail
|
|||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
5
http/actuator.http
Normal file
5
http/actuator.http
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
### get actuator
|
||||||
|
GET {{hostname}}/actuator
|
||||||
|
|
||||||
|
### get actuator health
|
||||||
|
GET {{hostname}}/actuator/health
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
### get measurement
|
### get configuration
|
||||||
GET {{hostname}}/configuration
|
GET {{hostname}}/configuration
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"development": {
|
"development": {
|
||||||
"hostname": "https://localhost:8443"
|
"hostname": "https://localhost:8443",
|
||||||
},
|
"identity": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
"docker": {
|
},
|
||||||
"hostname": "https://localhost:9000"
|
"docker": {
|
||||||
}
|
"hostname": "https://localhost:9000",
|
||||||
|
"identity": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
### get measurement
|
### get measurement
|
||||||
GET {{hostname}}/measurement
|
GET {{hostname}}/measurement
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|
||||||
### add measurement for all
|
### add measurement for all
|
||||||
POST {{hostname}}/measurement
|
POST {{hostname}}/measurement
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"button0": 0,
|
"button0": 0,
|
||||||
@@ -16,8 +16,8 @@ Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVW
|
|||||||
### add measurement for one
|
### add measurement for one
|
||||||
POST {{hostname}}/measurement
|
POST {{hostname}}/measurement
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"button0": 0
|
"button0": 1
|
||||||
}
|
}
|
||||||
|
|||||||
89
release.sh
89
release.sh
@@ -1,89 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
### This should be a pipeline, but for this example let use this ###
|
|
||||||
|
|
||||||
check_active_branch() {
|
|
||||||
if [ "$(git rev-parse --abbrev-ref HEAD)" != "$1" ]; then
|
|
||||||
echo "Error: The current branch is not $1."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_uncommitted_changes() {
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
|
||||||
echo "Error: There are uncommitted changes in the repository."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
prepare_environment() {
|
|
||||||
git fetch origin
|
|
||||||
}
|
|
||||||
|
|
||||||
check_last_commit() {
|
|
||||||
last_commit_message=$(git log -1 --pretty=format:%s)
|
|
||||||
if [ "$last_commit_message" = "[RELEASE] - bump version" ]; then
|
|
||||||
echo "Warning: Nothing to release!!!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_differences() {
|
|
||||||
if ! git diff --quiet origin/"$1" "$1"; then
|
|
||||||
echo "Error: The branches origin/$1 and $1 have differences."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
un_snapshot_version() {
|
|
||||||
sed -i "s/\($1\s*=\s*[0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/" gradle.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
current_version() {
|
|
||||||
awk -F '=' '/version\s*=\s*[0-9.]*/ {gsub(/^ +| +$/,"",$2); print $2}' gradle.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
stage_files() {
|
|
||||||
for file in "$@"; do
|
|
||||||
if git diff --exit-code --quiet -- "$file"; then
|
|
||||||
echo "No changes in $file"
|
|
||||||
else
|
|
||||||
git add "$file"
|
|
||||||
echo "Changes in $file staged for commit"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
commit_change() {
|
|
||||||
stage_files gradle.properties
|
|
||||||
git commit -m "[RELEASE] - $1"
|
|
||||||
git push --porcelain origin master
|
|
||||||
}
|
|
||||||
|
|
||||||
add_release_tag() {
|
|
||||||
gitTag="v$(current_version)"
|
|
||||||
git tag -a "$gitTag" -m "Release version $gitTag"
|
|
||||||
git push --porcelain origin "$gitTag"
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot_version() {
|
|
||||||
new_version="$(current_version | awk -F '.' '{print $1 "." $2+1 ".0"}')"
|
|
||||||
sed -i "s/\(version\s*=\s*\)[0-9.]*/\1$new_version-SNAPSHOT/" gradle.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
# check and prepare for release
|
|
||||||
check_active_branch master
|
|
||||||
check_uncommitted_changes
|
|
||||||
prepare_environment
|
|
||||||
check_last_commit
|
|
||||||
check_differences master
|
|
||||||
|
|
||||||
# un-snapshot version for release
|
|
||||||
un_snapshot_version version
|
|
||||||
un_snapshot_version catalog
|
|
||||||
|
|
||||||
# release changes and prepare for next release
|
|
||||||
commit_change "release version: $(current_version)"
|
|
||||||
add_release_tag
|
|
||||||
snapshot_version
|
|
||||||
commit_change 'bump version'
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package ltd.hlaeja
|
package ltd.hlaeja
|
||||||
|
|
||||||
|
import ltd.hlaeja.property.CacheProperty
|
||||||
import ltd.hlaeja.property.DeviceConfigurationProperty
|
import ltd.hlaeja.property.DeviceConfigurationProperty
|
||||||
import ltd.hlaeja.property.DeviceDataProperty
|
import ltd.hlaeja.property.DeviceDataProperty
|
||||||
import ltd.hlaeja.property.DeviceRegistryProperty
|
import ltd.hlaeja.property.DeviceRegistryProperty
|
||||||
@@ -7,8 +8,11 @@ import ltd.hlaeja.property.JwtProperty
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.cache.annotation.EnableCaching
|
||||||
|
|
||||||
|
@EnableCaching
|
||||||
@EnableConfigurationProperties(
|
@EnableConfigurationProperties(
|
||||||
|
CacheProperty::class,
|
||||||
DeviceConfigurationProperty::class,
|
DeviceConfigurationProperty::class,
|
||||||
DeviceDataProperty::class,
|
DeviceDataProperty::class,
|
||||||
DeviceRegistryProperty::class,
|
DeviceRegistryProperty::class,
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package ltd.hlaeja.configuration
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
import ltd.hlaeja.exception.CacheException
|
||||||
|
import ltd.hlaeja.property.CacheProperty
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheConfiguration
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheManager
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class RedisCacheConfiguration(
|
||||||
|
private val cacheProperty: CacheProperty,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun cacheManager(
|
||||||
|
redisTemplate: RedisTemplate<String, String>,
|
||||||
|
): RedisCacheManager = redisTemplate.connectionFactory
|
||||||
|
?.let { RedisCacheManager.builder(it).cacheDefaults(getRedisCacheConfiguration()).build() }
|
||||||
|
?: throw CacheException("Redis connection factory is not set")
|
||||||
|
|
||||||
|
private fun getRedisCacheConfiguration(): RedisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
|
.entryTtl(Duration.ofMinutes(cacheProperty.timeToLive))
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package ltd.hlaeja.controller
|
package ltd.hlaeja.controller
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||||
import ltd.hlaeja.service.DeviceConfigurationService
|
import ltd.hlaeja.service.DeviceConfigurationService
|
||||||
import ltd.hlaeja.service.JwtService
|
import ltd.hlaeja.service.DeviceRegistryService
|
||||||
import ltd.hlaeja.util.toDeviceResponse
|
import ltd.hlaeja.util.toDeviceResponse
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.RequestHeader
|
import org.springframework.web.bind.annotation.RequestHeader
|
||||||
@@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/configuration")
|
@RequestMapping("/configuration")
|
||||||
class ConfigurationController(
|
class ConfigurationController(
|
||||||
private val configurationService: DeviceConfigurationService,
|
private val configurationService: DeviceConfigurationService,
|
||||||
private val jwtService: JwtService,
|
private val deviceRegistry: DeviceRegistryService,
|
||||||
|
private val publicJwtService: PublicJwtService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getNodeConfiguration(
|
suspend fun getNodeConfiguration(
|
||||||
@RequestHeader("Identity") identityToken: String,
|
@RequestHeader("Identity") identityToken: String,
|
||||||
): Map<String, String> = jwtService.getIdentity(identityToken)
|
): Map<String, String> = readIdentityToken(identityToken)
|
||||||
|
.let { deviceRegistry.getIdentityFromDevice(it) }
|
||||||
.let { configurationService.getConfiguration(it.node).toDeviceResponse() }
|
.let { configurationService.getConfiguration(it.node).toDeviceResponse() }
|
||||||
|
|
||||||
|
private fun readIdentityToken(identityToken: String): UUID = publicJwtService
|
||||||
|
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package ltd.hlaeja.controller
|
package ltd.hlaeja.controller
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||||
import ltd.hlaeja.library.deviceData.MeasurementData
|
import ltd.hlaeja.library.deviceData.MeasurementData
|
||||||
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
import ltd.hlaeja.service.DeviceDataService
|
import ltd.hlaeja.service.DeviceDataService
|
||||||
import ltd.hlaeja.service.JwtService
|
import ltd.hlaeja.service.DeviceRegistryService
|
||||||
import org.springframework.http.HttpStatus.CREATED
|
import org.springframework.http.HttpStatus.CREATED
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
@@ -16,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/measurement")
|
@RequestMapping("/measurement")
|
||||||
class MeasurementController(
|
class MeasurementController(
|
||||||
private val dataService: DeviceDataService,
|
private val dataService: DeviceDataService,
|
||||||
private val jwtService: JwtService,
|
private val deviceRegistry: DeviceRegistryService,
|
||||||
|
private val publicJwtService: PublicJwtService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getNodeMeasurement(
|
suspend fun getNodeMeasurement(
|
||||||
@RequestHeader("Identity") identityToken: String,
|
@RequestHeader("Identity") identityToken: String,
|
||||||
): Map<String, Number> = jwtService.getIdentity(identityToken)
|
): Map<String, Number> = readIdentityToken(identityToken)
|
||||||
.let { dataService.getMeasurement(it.client, it.node).fields }
|
.let { dataService.getMeasurement(it.client, it.node).fields }
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@@ -31,7 +35,7 @@ class MeasurementController(
|
|||||||
@RequestHeader("Identity") identityToken: String,
|
@RequestHeader("Identity") identityToken: String,
|
||||||
@RequestBody measurement: Map<String, Number>,
|
@RequestBody measurement: Map<String, Number>,
|
||||||
) {
|
) {
|
||||||
return jwtService.getIdentity(identityToken)
|
return readIdentityToken(identityToken)
|
||||||
.let {
|
.let {
|
||||||
dataService.addMeasurement(
|
dataService.addMeasurement(
|
||||||
it.client,
|
it.client,
|
||||||
@@ -45,4 +49,8 @@ class MeasurementController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun readIdentityToken(identityToken: String): Identity.Response = publicJwtService
|
||||||
|
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
|
||||||
|
.let { deviceRegistry.getIdentityFromDevice(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/main/kotlin/ltd/hlaeja/exception/CacheException.kt
Normal file
23
src/main/kotlin/ltd/hlaeja/exception/CacheException.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package ltd.hlaeja.exception
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class CacheException : Exception {
|
||||||
|
|
||||||
|
constructor() : super()
|
||||||
|
|
||||||
|
constructor(message: String) : super(message)
|
||||||
|
|
||||||
|
constructor(cause: Throwable) : super(cause)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: String,
|
||||||
|
cause: Throwable,
|
||||||
|
) : super(message, cause)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: String,
|
||||||
|
cause: Throwable,
|
||||||
|
enableSuppression: Boolean,
|
||||||
|
writableStackTrace: Boolean,
|
||||||
|
) : super(message, cause, enableSuppression, writableStackTrace)
|
||||||
|
}
|
||||||
8
src/main/kotlin/ltd/hlaeja/property/CacheProperty.kt
Normal file
8
src/main/kotlin/ltd/hlaeja/property/CacheProperty.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package ltd.hlaeja.property
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "cache")
|
||||||
|
data class CacheProperty(
|
||||||
|
val timeToLive: Long,
|
||||||
|
)
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
package ltd.hlaeja.service
|
package ltd.hlaeja.service
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import ltd.hlaeja.library.deviceConfiguration.Node
|
import ltd.hlaeja.library.deviceConfiguration.Node
|
||||||
import ltd.hlaeja.property.DeviceConfigurationProperty
|
import ltd.hlaeja.property.DeviceConfigurationProperty
|
||||||
import ltd.hlaeja.util.logCall
|
import ltd.hlaeja.util.deviceConfigurationGetConfiguration
|
||||||
import org.springframework.http.HttpStatus.NOT_FOUND
|
import org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
|
||||||
import org.springframework.http.HttpStatus.NO_CONTENT
|
|
||||||
import org.springframework.http.HttpStatus.REQUEST_TIMEOUT
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.ErrorResponseException
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
import org.springframework.web.reactive.function.client.awaitBodyOrNull
|
import org.springframework.web.reactive.function.client.WebClientRequestException
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class DeviceConfigurationService(
|
class DeviceConfigurationService(
|
||||||
private val webClient: WebClient,
|
private val webClient: WebClient,
|
||||||
@@ -20,9 +22,12 @@ class DeviceConfigurationService(
|
|||||||
|
|
||||||
suspend fun getConfiguration(
|
suspend fun getConfiguration(
|
||||||
node: UUID,
|
node: UUID,
|
||||||
): Node.Response = webClient.get()
|
): Node.Response = try {
|
||||||
.uri("${deviceConfigurationProperty.url}/node-$node".also(::logCall))
|
webClient.deviceConfigurationGetConfiguration(node, deviceConfigurationProperty)
|
||||||
.retrieve()
|
} catch (e: ErrorResponseException) {
|
||||||
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NO_CONTENT) }
|
throw e
|
||||||
.awaitBodyOrNull<Node.Response>() ?: throw ResponseStatusException(REQUEST_TIMEOUT)
|
} catch (e: WebClientRequestException) {
|
||||||
|
log.error(e) { "Error device registry" }
|
||||||
|
throw ResponseStatusException(SERVICE_UNAVAILABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
package ltd.hlaeja.service
|
package ltd.hlaeja.service
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import ltd.hlaeja.library.deviceData.MeasurementData
|
import ltd.hlaeja.library.deviceData.MeasurementData
|
||||||
import ltd.hlaeja.property.DeviceDataProperty
|
import ltd.hlaeja.property.DeviceDataProperty
|
||||||
import ltd.hlaeja.util.logCall
|
import ltd.hlaeja.util.deviceDataAddMeasurement
|
||||||
import org.springframework.http.HttpStatus.NOT_FOUND
|
import ltd.hlaeja.util.deviceDataGetMeasurement
|
||||||
import org.springframework.http.HttpStatus.NO_CONTENT
|
import org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
|
||||||
import org.springframework.http.HttpStatus.REQUEST_TIMEOUT
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.ErrorResponseException
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
import org.springframework.web.reactive.function.client.awaitBodilessEntity
|
import org.springframework.web.reactive.function.client.WebClientRequestException
|
||||||
import org.springframework.web.reactive.function.client.awaitBodyOrNull
|
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class DeviceDataService(
|
class DeviceDataService(
|
||||||
private val webClient: WebClient,
|
private val webClient: WebClient,
|
||||||
@@ -22,18 +25,24 @@ class DeviceDataService(
|
|||||||
suspend fun getMeasurement(
|
suspend fun getMeasurement(
|
||||||
client: UUID,
|
client: UUID,
|
||||||
node: UUID,
|
node: UUID,
|
||||||
): MeasurementData.Response = webClient.get()
|
): MeasurementData.Response = try {
|
||||||
.uri("${deviceDataProperty.url}/client-$client/node-$node".also(::logCall))
|
webClient.deviceDataGetMeasurement(client, node, deviceDataProperty)
|
||||||
.retrieve()
|
} catch (e: ErrorResponseException) {
|
||||||
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NO_CONTENT) }
|
throw e
|
||||||
.awaitBodyOrNull<MeasurementData.Response>() ?: throw ResponseStatusException(REQUEST_TIMEOUT)
|
} catch (e: WebClientRequestException) {
|
||||||
|
log.error(e) { "Error device registry" }
|
||||||
|
throw ResponseStatusException(SERVICE_UNAVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addMeasurement(
|
suspend fun addMeasurement(
|
||||||
client: UUID,
|
client: UUID,
|
||||||
request: MeasurementData.Request,
|
request: MeasurementData.Request,
|
||||||
) = webClient.post()
|
): ResponseEntity<Void> = try {
|
||||||
.uri("${deviceDataProperty.url}/client-$client".also(::logCall))
|
webClient.deviceDataAddMeasurement(client, request, deviceDataProperty)
|
||||||
.bodyValue(request)
|
} catch (e: ErrorResponseException) {
|
||||||
.retrieve()
|
throw e
|
||||||
.awaitBodilessEntity()
|
} catch (e: WebClientRequestException) {
|
||||||
|
log.error(e) { "Error device registry" }
|
||||||
|
throw ResponseStatusException(SERVICE_UNAVAILABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,35 @@
|
|||||||
package ltd.hlaeja.service
|
package ltd.hlaeja.service
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
import ltd.hlaeja.property.DeviceRegistryProperty
|
import ltd.hlaeja.property.DeviceRegistryProperty
|
||||||
import ltd.hlaeja.util.logCall
|
import ltd.hlaeja.util.deviceRegistryIdentityDevice
|
||||||
import org.springframework.http.HttpStatus.NOT_ACCEPTABLE
|
import org.springframework.cache.annotation.Cacheable
|
||||||
import org.springframework.http.HttpStatus.NOT_FOUND
|
import org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
|
||||||
import org.springframework.http.HttpStatus.REQUEST_TIMEOUT
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.ErrorResponseException
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
import org.springframework.web.reactive.function.client.awaitBodyOrNull
|
import org.springframework.web.reactive.function.client.WebClientRequestException
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class DeviceRegistryService(
|
class DeviceRegistryService(
|
||||||
private val webClient: WebClient,
|
private val webClient: WebClient,
|
||||||
private val deviceRegistryProperty: DeviceRegistryProperty,
|
private val deviceRegistryProperty: DeviceRegistryProperty,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Cacheable(value = ["identity"], key = "#device")
|
||||||
suspend fun getIdentityFromDevice(
|
suspend fun getIdentityFromDevice(
|
||||||
device: UUID,
|
device: UUID,
|
||||||
): Identity.Response = webClient.get()
|
): Identity.Response = try {
|
||||||
.uri("${deviceRegistryProperty.url}/identity/device-$device".also(::logCall))
|
webClient.deviceRegistryIdentityDevice(device, deviceRegistryProperty)
|
||||||
.retrieve()
|
} catch (e: ErrorResponseException) {
|
||||||
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NOT_ACCEPTABLE) }
|
throw e
|
||||||
.awaitBodyOrNull<Identity.Response>() ?: throw ResponseStatusException(REQUEST_TIMEOUT)
|
} catch (e: WebClientRequestException) {
|
||||||
|
log.error(e) { "Error device identity" }
|
||||||
|
throw ResponseStatusException(SERVICE_UNAVAILABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package ltd.hlaeja.service
|
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtParser
|
|
||||||
import io.jsonwebtoken.Jwts
|
|
||||||
import java.util.UUID
|
|
||||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
|
||||||
import ltd.hlaeja.property.JwtProperty
|
|
||||||
import ltd.hlaeja.util.PublicKeyProvider
|
|
||||||
import mu.KotlinLogging
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class JwtService(
|
|
||||||
jwtProperty: JwtProperty,
|
|
||||||
private val deviceRegistry: DeviceRegistryService,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val parser: JwtParser = Jwts.parser()
|
|
||||||
.verifyWith(PublicKeyProvider.load(jwtProperty.publicKey))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
suspend fun getIdentity(
|
|
||||||
identityToken: String,
|
|
||||||
): Identity.Response = readIdentity(identityToken)
|
|
||||||
.let { deviceRegistry.getIdentityFromDevice(it) }
|
|
||||||
|
|
||||||
private suspend fun readIdentity(
|
|
||||||
identity: String,
|
|
||||||
): UUID = parser.parseSignedClaims(identity)
|
|
||||||
.let { UUID.fromString(it.payload["device"] as String) }
|
|
||||||
.also { log.debug("Identified client device: {}", it) }
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package ltd.hlaeja.util
|
package ltd.hlaeja.util
|
||||||
|
|
||||||
import mu.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
fun logCall(url: String) {
|
fun logCall(url: String) = log.debug { "calling: $url" }
|
||||||
log.debug("calling: {}", url)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package ltd.hlaeja.util
|
|
||||||
|
|
||||||
import java.security.KeyFactory
|
|
||||||
import java.security.interfaces.RSAPublicKey
|
|
||||||
import java.security.spec.X509EncodedKeySpec
|
|
||||||
import java.util.Base64.getDecoder
|
|
||||||
import ltd.hlaeja.exception.KeyProviderException
|
|
||||||
|
|
||||||
object PublicKeyProvider {
|
|
||||||
|
|
||||||
fun load(
|
|
||||||
pemFile: String,
|
|
||||||
): RSAPublicKey = readPublicPemFile(pemFile)
|
|
||||||
.let(::makePublicKey)
|
|
||||||
|
|
||||||
private fun makePublicKey(
|
|
||||||
publicKeyBytes: ByteArray,
|
|
||||||
): RSAPublicKey = KeyFactory.getInstance("RSA")
|
|
||||||
.generatePublic(X509EncodedKeySpec(publicKeyBytes)) as RSAPublicKey
|
|
||||||
|
|
||||||
private fun readPublicPemFile(
|
|
||||||
publicKey: String,
|
|
||||||
): ByteArray = javaClass.classLoader
|
|
||||||
.getResource(publicKey)
|
|
||||||
?.readText()
|
|
||||||
?.let(::getPublicKeyByteArray)
|
|
||||||
?: throw KeyProviderException("Could not load public key")
|
|
||||||
|
|
||||||
private fun getPublicKeyByteArray(
|
|
||||||
keyText: String,
|
|
||||||
): ByteArray = keyText.replace(Regex("[\r\n]+"), "")
|
|
||||||
.removePrefix("-----BEGIN PUBLIC KEY-----")
|
|
||||||
.removeSuffix("-----END PUBLIC KEY-----")
|
|
||||||
.let { getDecoder().decode(it) }
|
|
||||||
}
|
|
||||||
56
src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt
Normal file
56
src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package ltd.hlaeja.util
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import ltd.hlaeja.library.deviceConfiguration.Node
|
||||||
|
import ltd.hlaeja.library.deviceData.MeasurementData
|
||||||
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
|
import ltd.hlaeja.property.DeviceConfigurationProperty
|
||||||
|
import ltd.hlaeja.property.DeviceDataProperty
|
||||||
|
import ltd.hlaeja.property.DeviceRegistryProperty
|
||||||
|
import org.springframework.http.HttpStatus.NOT_ACCEPTABLE
|
||||||
|
import org.springframework.http.HttpStatus.NOT_FOUND
|
||||||
|
import org.springframework.http.HttpStatus.NO_CONTENT
|
||||||
|
import org.springframework.http.HttpStatus.REQUEST_TIMEOUT
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
|
import org.springframework.web.reactive.function.client.awaitBodilessEntity
|
||||||
|
import org.springframework.web.reactive.function.client.awaitBodyOrNull
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
|
suspend fun WebClient.deviceRegistryIdentityDevice(
|
||||||
|
device: UUID,
|
||||||
|
property: DeviceRegistryProperty,
|
||||||
|
): Identity.Response = get()
|
||||||
|
.uri("${property.url}/identity/device-$device".also(::logCall))
|
||||||
|
.retrieve()
|
||||||
|
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NOT_ACCEPTABLE) }
|
||||||
|
.awaitBodyOrNull<Identity.Response>() ?: throw ResponseStatusException(REQUEST_TIMEOUT)
|
||||||
|
|
||||||
|
suspend fun WebClient.deviceDataGetMeasurement(
|
||||||
|
client: UUID,
|
||||||
|
node: UUID,
|
||||||
|
property: DeviceDataProperty,
|
||||||
|
): MeasurementData.Response = get()
|
||||||
|
.uri("${property.url}/client-$client/node-$node".also(::logCall))
|
||||||
|
.retrieve()
|
||||||
|
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NO_CONTENT) }
|
||||||
|
.awaitBodyOrNull<MeasurementData.Response>() ?: throw ResponseStatusException(REQUEST_TIMEOUT)
|
||||||
|
|
||||||
|
suspend fun WebClient.deviceDataAddMeasurement(
|
||||||
|
client: UUID,
|
||||||
|
request: MeasurementData.Request,
|
||||||
|
property: DeviceDataProperty,
|
||||||
|
): ResponseEntity<Void> = post()
|
||||||
|
.uri("${property.url}/client-$client".also(::logCall))
|
||||||
|
.bodyValue(request)
|
||||||
|
.retrieve()
|
||||||
|
.awaitBodilessEntity()
|
||||||
|
|
||||||
|
suspend fun WebClient.deviceConfigurationGetConfiguration(
|
||||||
|
node: UUID,
|
||||||
|
property: DeviceConfigurationProperty,
|
||||||
|
): Node.Response = get()
|
||||||
|
.uri("${property.url}/node-$node".also(::logCall))
|
||||||
|
.retrieve()
|
||||||
|
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NO_CONTENT) }
|
||||||
|
.awaitBodyOrNull<Node.Response>() ?: throw ResponseStatusException(REQUEST_TIMEOUT)
|
||||||
@@ -20,11 +20,6 @@
|
|||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "Application build os version."
|
"description": "Application build os version."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "jwt.public-key",
|
|
||||||
"type": "java.lang.String",
|
|
||||||
"description": "Jwt public key file."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "device-registry.url",
|
"name": "device-registry.url",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
@@ -39,6 +34,11 @@
|
|||||||
"name": "device-configuration.url",
|
"name": "device-configuration.url",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "Url for device configuration service."
|
"description": "Url for device configuration service."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cache.time-to-live",
|
||||||
|
"type": "java.lang.Long",
|
||||||
|
"description": "Cache expiration in minutes."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,29 @@ spring:
|
|||||||
os:
|
os:
|
||||||
name: "%APP_BUILD_OS_NAME%"
|
name: "%APP_BUILD_OS_NAME%"
|
||||||
version: "%APP_BUILD_OS_VERSION%"
|
version: "%APP_BUILD_OS_VERSION%"
|
||||||
|
cache:
|
||||||
|
type: redis
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
port: 6379
|
||||||
|
database: 1
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
access:
|
||||||
|
default: none
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: "health,info"
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
show-details: always
|
||||||
|
access: read_only
|
||||||
|
info:
|
||||||
|
access: read_only
|
||||||
|
|
||||||
|
cache:
|
||||||
|
time-to-live: 10
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
public-key: cert/public_key.pem
|
public-key: cert/public_key.pem
|
||||||
@@ -21,6 +44,9 @@ spring:
|
|||||||
config:
|
config:
|
||||||
activate:
|
activate:
|
||||||
on-profile: development
|
on-profile: development
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8443
|
port: 8443
|
||||||
@@ -47,6 +73,9 @@ spring:
|
|||||||
config:
|
config:
|
||||||
activate:
|
activate:
|
||||||
on-profile: docker
|
on-profile: docker
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: Redis
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8443
|
port: 8443
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
</root>
|
|
||||||
<logger level="DEBUG" name="ltd.hlaeja"/>
|
|
||||||
</configuration>
|
|
||||||
10
src/test/resources/application.yml
Normal file
10
src/test/resources/application.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
jwt:
|
||||||
|
public-key: cert/valid-public-key.pem
|
||||||
|
device-registry:
|
||||||
|
url: http://localhost:9010
|
||||||
|
device-data:
|
||||||
|
url: http://localhost:9020
|
||||||
|
device-configuration:
|
||||||
|
url: http://localhost:9030
|
||||||
|
cache:
|
||||||
|
time-to-live: 10
|
||||||
9
src/test/resources/cert/valid-public-key.pem
Normal file
9
src/test/resources/cert/valid-public-key.pem
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ZdlbISX729m5Ur1pVhg
|
||||||
|
XIvazcgUt0T0G32ML0tfwQ4aWTfwPII0SQRThaN6eiiBMRa0V8JMih1LT8JmGgst
|
||||||
|
dEx2nhMbVs/Osu8MhmP86c+HB/jPa1+0IR1TZKXoZoF52D2ZtoVf+mOWggAcm1R+
|
||||||
|
V0Fj2cR/pgLkVt3GKUE2OokFC1iFUQFjThd1EzKcOv53TUek8FY8t66npQ4t3unD
|
||||||
|
bXZKoGXMuXCqZVykMbGTUQFRuT3NAOXRrJP+UDeY2uM2Yk98J+8FtLDYD6jpmyi0
|
||||||
|
ghv6k8pK1w1n5NI3atVv5ZMUeQZ36AXL8SZi1105mamhLVQ0e0JixoMOPh7ziFyv
|
||||||
|
uwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
Reference in New Issue
Block a user