3 Commits

Author SHA1 Message Date
7184854ed4 [RELEASE] - release version: 0.3.0 2024-12-28 07:43:29 +01:00
df8e1a7b48 update for getDevice
- add get device in DeviceController
- add get device in DeviceService
- update logging
- update and cleanup in README.md
2024-12-27 22:56:27 +01:00
e7dbbc7a78 [RELEASE] - bump version 2024-12-10 23:25:29 +01:00
9 changed files with 135 additions and 63 deletions

View File

@@ -5,14 +5,14 @@ Classes crafted, identities bestowed, Each device recorded, their functions unfo
## Properties for deployment ## Properties for deployment
| name | required | info | | name | required | info |
|------------------------|----------|-------------------------| |------------------------|:--------:|-------------------------|
| spring.profiles.active | * | Spring Boot environment | | spring.profiles.active | ✓ | Spring Boot environment |
| spring.r2dbc.url | * | Postgres host url | | spring.r2dbc.url | ✓ | Postgres host url |
| spring.r2dbc.username | * | Postgres username | | spring.r2dbc.username | ✓ | Postgres username |
| spring.r2dbc.password | ** | Postgres password | | spring.r2dbc.password | ✗ | Postgres password |
| jwt.private-key | | JWT private cert | | jwt.private-key | ✓ | JWT private cert |
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
@@ -20,34 +20,28 @@ Run `release.sh` script from `master` branch.
## Development Information ## Development Information
### Generate Private and Public RSA Key ### Private RSA Key
OpenSSL Project is dedicated to providing a simple installation of OpenSSL for Microsoft Windows. [Download](https://slproweb.com/products/Win32OpenSSL.html) This service uses RAS keys to create identities for devices. The private key is used here to generate identities, while the public key is used by **[Hlæja Device API](https://github.com/swordsteel/hlaeja-device-api)** to identify a device and accept data.
Generate an RSA private key, of size 2048, and output it to a file named `private_key.pem` in to `./keys` *For instructions on how to set these up, please refer to our [generate RSA key](https://github.com/swordsteel/hlaeja-development/blob/master/doc/rsa_key.md) documentation.*
```shell ### Global Setting
openssl genrsa -out private_key.pem 2048
The following global settings are used in Hlaeja Device Registry. You can configure these settings using either Gradle properties or alternatively environment variables.
*For instructions on how to set these up, please refer to our [set global settings](https://github.com/swordsteel/hlaeja-development/blob/master/doc/global_settings.md) documentation.*
#### Gradle Properties
```properties
repository.user=your_user
repository.token=your_token_value
``` ```
Extract the public key from `private_key.pem` from `./keys`, and output it to a file named `public_key.pem` in to `./keys` #### Environment Variables
```shell ```properties
openssl rsa -in private_key.pem -pubout -out public_key.pem REPOSITORY_USER=your_user
REPOSITORY_TOKEN=your_token_value
``` ```
### Global gradle properties
To authenticate with Gradle to access repositories that require authentication, you can set your user and token in the `gradle.properties` file.
Here's how you can do it:
1. Open or create the `gradle.properties` file in your Gradle user home directory:
- On Unix-like systems (Linux, macOS), this directory is typically `~/.gradle/`.
- On Windows, this directory is typically `C:\Users\<YourUsername>\.gradle\`.
2. Add the following lines to the `gradle.properties` file:
```properties
repository.user=your_user
repository.token=your_token_value
```
or use environment variables `REPOSITORY_USER` and `REPOSITORY_TOKEN`

View File

@@ -1,4 +1,4 @@
kotlin.code.style=official kotlin.code.style=official
version=0.2.0 version=0.3.0
catalog=0.6.0 catalog=0.7.0
container.port.host=9010 container.port.host=9010

View File

@@ -5,3 +5,6 @@ Content-Type: application/json
{ {
"type": "00000000-0000-0000-0000-000000000000" "type": "00000000-0000-0000-0000-000000000000"
} }
### register device for a type
GET {{hostname}}/device-00000000-0000-0000-0000-000000000000

View File

@@ -1,9 +1,12 @@
package ltd.hlaeja.controller package ltd.hlaeja.controller
import java.util.UUID
import ltd.hlaeja.library.deviceRegistry.Device import ltd.hlaeja.library.deviceRegistry.Device
import ltd.hlaeja.service.DeviceService import ltd.hlaeja.service.DeviceService
import ltd.hlaeja.service.JwtService import ltd.hlaeja.service.JwtService
import ltd.hlaeja.util.toDeviceResponse import ltd.hlaeja.util.toDeviceResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -19,4 +22,10 @@ class DeviceController(
@RequestBody request: Device.Request, @RequestBody request: Device.Request,
): Device.Response = deviceService.addDevice(request.type) ): Device.Response = deviceService.addDevice(request.type)
.toDeviceResponse(jwtService) .toDeviceResponse(jwtService)
@GetMapping("/device-{device}")
suspend fun getDevice(
@PathVariable device: UUID,
): Device.Response = deviceService.getDevice(device)
.toDeviceResponse(jwtService)
} }

View File

@@ -1,11 +1,13 @@
package ltd.hlaeja.service package ltd.hlaeja.service
import io.github.oshai.kotlinlogging.KotlinLogging
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.UUID import java.util.UUID
import ltd.hlaeja.entity.DeviceEntity import ltd.hlaeja.entity.DeviceEntity
import ltd.hlaeja.repository.DeviceRepository import ltd.hlaeja.repository.DeviceRepository
import mu.KotlinLogging import org.springframework.http.HttpStatus.NOT_FOUND
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.web.server.ResponseStatusException
private val log = KotlinLogging.logger {} private val log = KotlinLogging.logger {}
@@ -18,4 +20,8 @@ class DeviceService(
type: UUID, type: UUID,
): DeviceEntity = deviceRepository.save(DeviceEntity(null, ZonedDateTime.now(), type)) ): DeviceEntity = deviceRepository.save(DeviceEntity(null, ZonedDateTime.now(), type))
.also { log.debug { "Added device ${it.id}" } } .also { log.debug { "Added device ${it.id}" } }
suspend fun getDevice(device: UUID): DeviceEntity = deviceRepository.findById(device)
?.also { log.debug { "Get device ${it.id}" } }
?: throw ResponseStatusException(NOT_FOUND)
} }

View File

@@ -1,9 +1,9 @@
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.entity.NodeEntity import ltd.hlaeja.entity.NodeEntity
import ltd.hlaeja.repository.NodeRepository import ltd.hlaeja.repository.NodeRepository
import mu.KotlinLogging
import org.springframework.http.HttpStatus.NOT_FOUND import org.springframework.http.HttpStatus.NOT_FOUND
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.web.server.ResponseStatusException import org.springframework.web.server.ResponseStatusException

View File

@@ -1,9 +1,9 @@
package ltd.hlaeja.service package ltd.hlaeja.service
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import ltd.hlaeja.entity.TypeEntity import ltd.hlaeja.entity.TypeEntity
import ltd.hlaeja.repository.TypeRepository import ltd.hlaeja.repository.TypeRepository
import mu.KotlinLogging
import org.springframework.dao.DuplicateKeyException import org.springframework.dao.DuplicateKeyException
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -22,9 +22,9 @@ class TypeService(
entity: TypeEntity, entity: TypeEntity,
): TypeEntity = try { ): TypeEntity = try {
typeRepository.save(entity) typeRepository.save(entity)
.also { log.debug("Added new type: {}", it.id) } .also { log.debug { "Added new type: $it.id" } }
} catch (e: DuplicateKeyException) { } catch (e: DuplicateKeyException) {
log.warn(e.localizedMessage) log.warn { e.localizedMessage }
throw ResponseStatusException(HttpStatus.CONFLICT) throw ResponseStatusException(HttpStatus.CONFLICT)
} }
} }

View File

@@ -14,6 +14,7 @@ import ltd.hlaeja.service.DeviceService
import ltd.hlaeja.service.JwtService import ltd.hlaeja.service.JwtService
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.springframework.web.server.ResponseStatusException import org.springframework.web.server.ResponseStatusException
@@ -36,6 +37,9 @@ class DeviceControllerTest {
controller = DeviceController(deviceService, jwtService) controller = DeviceController(deviceService, jwtService)
} }
@Nested
inner class AddDeviceTest {
@Test @Test
fun `add device - success`() = runTest { fun `add device - success`() = runTest {
// given // given
@@ -67,4 +71,25 @@ class DeviceControllerTest {
// then // then
assertThat(exception.message).isEqualTo("417 EXPECTATION_FAILED") assertThat(exception.message).isEqualTo("417 EXPECTATION_FAILED")
} }
}
@Nested
inner class GetDeviceTest {
@Test
fun `get device - success`() = runTest {
// given
coEvery { deviceService.getDevice(any()) } returns DeviceEntity(uuid, timestamp, uuid)
coEvery { jwtService.makeIdentity(any()) } returns PAYLOAD
// when
val response = controller.getDevice(uuid)
// then
coVerify(exactly = 1) { deviceService.getDevice(any()) }
coVerify(exactly = 1) { jwtService.makeIdentity(any()) }
assertThat(response.identity).isEqualTo(PAYLOAD)
}
}
} }

View File

@@ -17,6 +17,8 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.web.server.ResponseStatusException
class DeviceServiceTest { class DeviceServiceTest {
companion object { companion object {
@@ -42,7 +44,7 @@ class DeviceServiceTest {
} }
@Test @Test
fun `add new type success`() = runTest { fun `add new device success`() = runTest {
// given // given
coEvery { repository.save(any()) } answers { call -> coEvery { repository.save(any()) } answers { call ->
(call.invocation.args[0] as DeviceEntity).copy(id = device) (call.invocation.args[0] as DeviceEntity).copy(id = device)
@@ -58,4 +60,37 @@ class DeviceServiceTest {
assertThat(result.timestamp.toString()).isEqualTo("2000-01-01T00:00:00.001Z[UTC]") assertThat(result.timestamp.toString()).isEqualTo("2000-01-01T00:00:00.001Z[UTC]")
assertThat(result.type).isEqualTo(type) assertThat(result.type).isEqualTo(type)
} }
@Test
fun `get device - success`() = runTest {
// given
val device = UUID.fromString("00000000-0000-0000-0000-000000000000")
val entity: DeviceEntity = mockk()
coEvery { repository.findById(any()) } returns entity
coEvery { entity.id } returns device
// when
val result = service.getDevice(type)
// then
coVerify(exactly = 1) { repository.findById(any()) }
assertThat(result.id).isEqualTo(device)
}
@Test
fun `get device - fail not found`() = runTest {
// given
val device = UUID.fromString("00000000-0000-0000-0000-000000000000")
coEvery { repository.findById(any()) } returns null
// when
val exception = assertThrows<ResponseStatusException> {
service.getDevice(device)
}
// then
assertThat(exception.message).isEqualTo("404 NOT_FOUND")
}
} }