From 0094f830b9d5e132fe374646b966fad7d991991c Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Mon, 18 Nov 2024 17:35:32 +0100 Subject: [PATCH 01/13] api spec --- .../application/rest/TrainNumberController.kt | 63 +++++++++++++++++++ .../application/rest/TrainNumberDto.kt | 28 +++++++++ 2 files changed, 91 insertions(+) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt new file mode 100644 index 00000000..571d389d --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt @@ -0,0 +1,63 @@ +package ch.sbb.backend.application.rest + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.MediaType.APPLICATION_JSON_VALUE +import org.springframework.http.ResponseEntity +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.* + +@Validated +@RestController +@RequestMapping("api/v1/train-numbers") +@Tag(name = "Train numbers", description = "API for train numbers") +class TrainNumberController { + + @Operation(summary = "Add train numbers") + @ApiResponse(responseCode = "200", description = "Train numbers successfully added") + @ApiResponse( + responseCode = "400", description = "Invalid input", content = [ + Content( + mediaType = APPLICATION_JSON_VALUE, + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @ApiResponse(responseCode = "401", description = "Unauthorized") + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @PostMapping(consumes = ["application/json"]) + fun addTrainNumbers(@RequestBody trainNumbers: List) { + + } + + @Operation(summary = "Get all train numbers") + @ApiResponse(responseCode = "200", description = "Train numbers successfully retrieved") + @ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(schema = Schema(hidden = true))] + ) + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @ResponseBody + @GetMapping(produces = [APPLICATION_JSON_VALUE]) + fun getAllTrainNumbers(): ResponseEntity> { + return ResponseEntity.ok().build() + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt new file mode 100644 index 00000000..0e03567e --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt @@ -0,0 +1,28 @@ +package ch.sbb.backend.application.rest + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.OffsetDateTime + +@Schema(name = "Train number") +data class TrainNumberDto( + + @Schema( + description = "Train number alphanumeric", + example = "123" + ) + val number: String, + + @Schema( + description = "Identifies a railway undertaking code (UIC RICS Code: https://uic.org/rics)", + example = "1085" + ) + val ru: String, + + @Schema( + type = "number", + format = "double", + description = "Timestamp of the start of train operation in seconds since epoch or timestamp", + example = "1727188352.001" + ) + val start: OffsetDateTime, +) From ce552aef3b5bbf8d3c90c7155b30a29b45daebcf Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Wed, 20 Nov 2024 11:13:27 +0100 Subject: [PATCH 02/13] api spec --- .../rest/TrainIdentifierController.kt | 110 ++++++++++++++++++ .../application/rest/TrainIdentifierDto.kt | 18 +++ .../application/rest/TrainNumberController.kt | 63 ---------- .../application/rest/TrainNumberDto.kt | 28 ----- 4 files changed, 128 insertions(+), 91 deletions(-) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt new file mode 100644 index 00000000..46797d70 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt @@ -0,0 +1,110 @@ +package ch.sbb.backend.application.rest + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.constraints.Size +import org.springframework.http.MediaType.APPLICATION_JSON_VALUE +import org.springframework.http.ResponseEntity +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.* +import java.time.LocalDate + +@Validated +@RestController +@RequestMapping("api/v1/train-identifiers") +@Tag(name = "Train identifiers", description = "API for train identifiers") +class TrainIdentifierController { + + @Operation(summary = "Update/add train identifier") + @ApiResponse(responseCode = "200", description = "Train identifier successfully added") + @ApiResponse( + responseCode = "400", description = "Invalid input", content = [ + Content( + mediaType = APPLICATION_JSON_VALUE, + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @ApiResponse(responseCode = "401", description = "Unauthorized") + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) +// todo api spec path ru + @PutMapping(path = ["/{ru}"], consumes = ["application/json"]) + fun updateTrainIdentifier( + @Parameter(description = "Identifies a railway undertaking code (UIC RICS Code: https://uic.org/rics)") + @PathVariable("ru") @Size(min= 4, max = 4) ru: String, @RequestBody trainIdentifier: TrainIdentifierDto) { + println("$ru $trainIdentifier") + + } + + @Operation(summary = "Batch update/add train identifiers") + @ApiResponse(responseCode = "200", description = "Train identifier successfully updated") + @ApiResponse( + responseCode = "400", description = "Invalid input", content = [ + Content( + mediaType = APPLICATION_JSON_VALUE, + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @ApiResponse(responseCode = "401", description = "Unauthorized") + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @PutMapping(path = ["/{ru}/batch"], consumes = ["application/json"]) + fun batchUpdateTrainIdentifiers(@PathVariable("ru") @Size(min= 4, max = 4) ru: String, @RequestBody trainIdentifiers: List) { + println("$ru $trainIdentifiers") + } + + @Operation(summary = "Delete train identifier") + @ApiResponse(responseCode = "200", description = "Train identifier successfully deleted") + @ApiResponse(responseCode = "401", description = "Unauthorized") + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @DeleteMapping(path = ["/{ru}/{operationDate}/{trainIdentifier}"]) + fun deleteTrainIdentifier(@PathVariable("ru") @Size(min= 4, max = 4) ru: String, @PathVariable("operationDate") operationDate: LocalDate, @PathVariable("trainIdentifier") trainIdentifier: String) { + println("$ru $operationDate $trainIdentifier") + } + + @Operation(summary = "Get all train identifiers") + @ApiResponse(responseCode = "200", description = "Train identifiers successfully retrieved") + @ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(schema = Schema(hidden = true))] + ) + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @ResponseBody + @GetMapping(produces = [APPLICATION_JSON_VALUE]) + fun getAllTrainIdentifiers(): ResponseEntity> { + return ResponseEntity.ok().build() + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt new file mode 100644 index 00000000..fa2c36e3 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt @@ -0,0 +1,18 @@ +package ch.sbb.backend.application.rest + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate +import java.time.OffsetDateTime + +@Schema(name = "Train identifier") +data class TrainIdentifierDto( + + @Schema(description = "Train identifier", example = "819") + val identifier: String, + + @Schema(description = "Operation date of the train", type = "string", format = "date") + val operationDate: LocalDate, + + @Schema(description = "Timestamp of the start of train operation") + val startDateTime: OffsetDateTime, +) diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt deleted file mode 100644 index 571d389d..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberController.kt +++ /dev/null @@ -1,63 +0,0 @@ -package ch.sbb.backend.application.rest - -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.media.Content -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.MediaType.APPLICATION_JSON_VALUE -import org.springframework.http.ResponseEntity -import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.* - -@Validated -@RestController -@RequestMapping("api/v1/train-numbers") -@Tag(name = "Train numbers", description = "API for train numbers") -class TrainNumberController { - - @Operation(summary = "Add train numbers") - @ApiResponse(responseCode = "200", description = "Train numbers successfully added") - @ApiResponse( - responseCode = "400", description = "Invalid input", content = [ - Content( - mediaType = APPLICATION_JSON_VALUE, - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @ApiResponse(responseCode = "401", description = "Unauthorized") - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @PostMapping(consumes = ["application/json"]) - fun addTrainNumbers(@RequestBody trainNumbers: List) { - - } - - @Operation(summary = "Get all train numbers") - @ApiResponse(responseCode = "200", description = "Train numbers successfully retrieved") - @ApiResponse( - responseCode = "401", - description = "Unauthorized", - content = [Content(schema = Schema(hidden = true))] - ) - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @ResponseBody - @GetMapping(produces = [APPLICATION_JSON_VALUE]) - fun getAllTrainNumbers(): ResponseEntity> { - return ResponseEntity.ok().build() - } -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt deleted file mode 100644 index 0e03567e..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainNumberDto.kt +++ /dev/null @@ -1,28 +0,0 @@ -package ch.sbb.backend.application.rest - -import io.swagger.v3.oas.annotations.media.Schema -import java.time.OffsetDateTime - -@Schema(name = "Train number") -data class TrainNumberDto( - - @Schema( - description = "Train number alphanumeric", - example = "123" - ) - val number: String, - - @Schema( - description = "Identifies a railway undertaking code (UIC RICS Code: https://uic.org/rics)", - example = "1085" - ) - val ru: String, - - @Schema( - type = "number", - format = "double", - description = "Timestamp of the start of train operation in seconds since epoch or timestamp", - example = "1727188352.001" - ) - val start: OffsetDateTime, -) From 2f89ca699d9956e271b14463d4ca3d9245e04daa Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Tue, 3 Dec 2024 14:43:48 +0100 Subject: [PATCH 03/13] feat: preload module --- preload/README.md | 9 + preload/compose.yaml | 9 + preload/pom.xml | 186 ++++++++++++++++++ .../ch/sbb/das/preload/PreloadApplication.kt | 13 ++ .../entities/TrainIdentifierEntity.kt | 19 ++ .../entities/TrainIdentifierId.kt | 33 ++++ .../TrainIdentifiersRepository.kt | 10 + .../src/main/resources/application-local.yaml | 7 + preload/src/main/resources/application.yaml | 16 ++ preload/src/main/resources/schema.sql | 13 ++ .../TrainIdentifierEntityIntegrationTest.kt | 73 +++++++ preload/src/test/resources/application.yaml | 8 + 12 files changed, 396 insertions(+) create mode 100644 preload/README.md create mode 100644 preload/compose.yaml create mode 100644 preload/pom.xml create mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt create mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt create mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt create mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt create mode 100644 preload/src/main/resources/application-local.yaml create mode 100644 preload/src/main/resources/application.yaml create mode 100644 preload/src/main/resources/schema.sql create mode 100644 preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt create mode 100644 preload/src/test/resources/application.yaml diff --git a/preload/README.md b/preload/README.md new file mode 100644 index 00000000..4087120b --- /dev/null +++ b/preload/README.md @@ -0,0 +1,9 @@ +# Preload Backend + +## Introduction + + +## Getting-Started +1. Run a Docker Daemon +2. Add environment variables +3. Run `PreloadApplication` diff --git a/preload/compose.yaml b/preload/compose.yaml new file mode 100644 index 00000000..65a0a1f3 --- /dev/null +++ b/preload/compose.yaml @@ -0,0 +1,9 @@ +services: + postgres: + image: 'postgres:latest' + environment: + - 'POSTGRES_DB=preload' + - 'POSTGRES_PASSWORD=secret' + - 'POSTGRES_USER=myuser' + ports: + - '5432:5432' diff --git a/preload/pom.xml b/preload/pom.xml new file mode 100644 index 00000000..f9cc13d3 --- /dev/null +++ b/preload/pom.xml @@ -0,0 +1,186 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.0 + + + ch.sbb.das + preload + 0.1.0-SNAPSHOT + preload + DAS Preload Backend + + + + + + + + + + + + + + + 21 + 2.0.21 + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + + + org.springframework.boot + spring-boot-docker-compose + runtime + true + + + org.postgresql + postgresql + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.jetbrains.kotlin + kotlin-test-junit5 + test + + + org.springframework.security + spring-security-test + test + + + com.tngtech.archunit + archunit-junit5 + 1.3.0 + test + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + jpa + spring + + + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + **/*.jar + + + + prepare-agent + + prepare-agent + + + + report + package + + report + + + + XML + + + + + + + + + diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt b/preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt new file mode 100644 index 00000000..2cea4270 --- /dev/null +++ b/preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt @@ -0,0 +1,13 @@ +package ch.sbb.das.preload + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.boot.web.servlet.ServletComponentScan + +@ServletComponentScan +@SpringBootApplication +class PreloadApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt new file mode 100644 index 00000000..dcccc919 --- /dev/null +++ b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt @@ -0,0 +1,19 @@ +package ch.sbb.das.preload.infrastructure.entities + +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.IdClass +import java.time.LocalDate +import java.time.OffsetDateTime + +@Entity(name = "train_identifier") +@IdClass(TrainIdentifierId::class) +class TrainIdentifierEntity( + @Id + var identifier: String, + @Id + var operationDate: LocalDate, + @Id + var ru: String, + var startDateTime: OffsetDateTime +) diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt new file mode 100644 index 00000000..7fc5a6a9 --- /dev/null +++ b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt @@ -0,0 +1,33 @@ +package ch.sbb.das.preload.infrastructure.entities + +import java.io.Serializable +import java.time.LocalDate + +class TrainIdentifierId( + val identifier: String = "", + val operationDate: LocalDate = LocalDate.now(), + val ru: String = "" +) : Serializable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TrainIdentifierId + + if (identifier != other.identifier) return false + if (operationDate != other.operationDate) return false + if (ru != other.ru) return false + + return true + } + + override fun hashCode(): Int { + var result = identifier.hashCode() + result = 31 * result + operationDate.hashCode() + result = 31 * result + ru.hashCode() + return result + } +} + + diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt new file mode 100644 index 00000000..b4c9cd74 --- /dev/null +++ b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt @@ -0,0 +1,10 @@ +package ch.sbb.das.preload.infrastructure.repositories + +import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierEntity +import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierId +import org.springframework.data.repository.ListCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface TrainIdentifiersRepository : ListCrudRepository { +} diff --git a/preload/src/main/resources/application-local.yaml b/preload/src/main/resources/application-local.yaml new file mode 100644 index 00000000..3efa4863 --- /dev/null +++ b/preload/src/main/resources/application-local.yaml @@ -0,0 +1,7 @@ +spring: + docker: + compose: + file: ./preload/compose.yaml + sql: + init: + mode: always diff --git a/preload/src/main/resources/application.yaml b/preload/src/main/resources/application.yaml new file mode 100644 index 00000000..1f08e220 --- /dev/null +++ b/preload/src/main/resources/application.yaml @@ -0,0 +1,16 @@ +info: + app: + version: '@project.version@' + +spring: + profiles: + active: ${STAGE:local} + application: + name: preload + datasource: + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + url: ${DB_URL} + sql: + init: + mode: always diff --git a/preload/src/main/resources/schema.sql b/preload/src/main/resources/schema.sql new file mode 100644 index 00000000..ad1f8407 --- /dev/null +++ b/preload/src/main/resources/schema.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS train_identifier +( + identifier TEXT NOT NULL, + operation_date DATE NOT NULL, + ru TEXT NOT NULL, + start_date_time TIMESTAMP NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS train_identifier_idx + ON train_identifier ( + identifier, + operation_date, ru + ); diff --git a/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt b/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt new file mode 100644 index 00000000..206327b7 --- /dev/null +++ b/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt @@ -0,0 +1,73 @@ +package ch.sbb.das.preload + +import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierEntity +import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierId +import ch.sbb.das.preload.infrastructure.repositories.TrainIdentifiersRepository +import org.assertj.core.api.Assertions.assertThat +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager +import org.springframework.boot.testcontainers.service.connection.ServiceConnection +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers +import org.testcontainers.utility.DockerImageName +import java.time.LocalDate +import java.time.OffsetDateTime +import kotlin.test.Test + +@DataJpaTest +@Testcontainers +class TrainIdentifierEntityIntegrationTest { + + companion object { + @Container + @ServiceConnection + @JvmStatic + val postgres = PostgreSQLContainer(DockerImageName.parse("postgres:latest")) + } + + @Autowired + lateinit var trainIdentifiersRepository: TrainIdentifiersRepository + + @Autowired + lateinit var em: TestEntityManager + + @Test + fun givenNewTrainIdentifier_whenSave_thenSuccess() { + val identifier = "1111" + val operationDate = LocalDate.now() + val ru = "22" + val startTime = OffsetDateTime.now() + + val train = TrainIdentifierEntity(identifier, operationDate, ru, startTime) + trainIdentifiersRepository.save(train) + + val result = em.find( + TrainIdentifierEntity::class.java, + TrainIdentifierId(identifier, operationDate, ru) + ) + assertThat(result.startDateTime).isEqualTo(startTime) + } + + @Test + fun givenTrainIdentifierCreated_whenUpdate_thenSuccess() { + val identifier = "1111" + val operationDate = LocalDate.now() + val ru = "22" + val startTime = OffsetDateTime.now() + + val train = TrainIdentifierEntity(identifier, operationDate, ru, startTime) + em.persist(train) + + val newStartTime = OffsetDateTime.now() + train.startDateTime = newStartTime + em.persist(train) + + val result = em.find( + TrainIdentifierEntity::class.java, + TrainIdentifierId(identifier, operationDate, ru) + ) + assertThat(result.startDateTime).isEqualTo(newStartTime) + } +} diff --git a/preload/src/test/resources/application.yaml b/preload/src/test/resources/application.yaml new file mode 100644 index 00000000..b27affd1 --- /dev/null +++ b/preload/src/test/resources/application.yaml @@ -0,0 +1,8 @@ +info: + app: + version: test + +spring: + sql: + init: + mode: always From cbfdb13666ed747d109b04f69629ec3026584258 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Tue, 3 Dec 2024 14:46:09 +0100 Subject: [PATCH 04/13] chore: remove old api --- .../rest/TrainIdentifierController.kt | 110 ------------------ .../application/rest/TrainIdentifierDto.kt | 18 --- 2 files changed, 128 deletions(-) delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt deleted file mode 100644 index 46797d70..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierController.kt +++ /dev/null @@ -1,110 +0,0 @@ -package ch.sbb.backend.application.rest - -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.media.Content -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.validation.constraints.Size -import org.springframework.http.MediaType.APPLICATION_JSON_VALUE -import org.springframework.http.ResponseEntity -import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.* -import java.time.LocalDate - -@Validated -@RestController -@RequestMapping("api/v1/train-identifiers") -@Tag(name = "Train identifiers", description = "API for train identifiers") -class TrainIdentifierController { - - @Operation(summary = "Update/add train identifier") - @ApiResponse(responseCode = "200", description = "Train identifier successfully added") - @ApiResponse( - responseCode = "400", description = "Invalid input", content = [ - Content( - mediaType = APPLICATION_JSON_VALUE, - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @ApiResponse(responseCode = "401", description = "Unauthorized") - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) -// todo api spec path ru - @PutMapping(path = ["/{ru}"], consumes = ["application/json"]) - fun updateTrainIdentifier( - @Parameter(description = "Identifies a railway undertaking code (UIC RICS Code: https://uic.org/rics)") - @PathVariable("ru") @Size(min= 4, max = 4) ru: String, @RequestBody trainIdentifier: TrainIdentifierDto) { - println("$ru $trainIdentifier") - - } - - @Operation(summary = "Batch update/add train identifiers") - @ApiResponse(responseCode = "200", description = "Train identifier successfully updated") - @ApiResponse( - responseCode = "400", description = "Invalid input", content = [ - Content( - mediaType = APPLICATION_JSON_VALUE, - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @ApiResponse(responseCode = "401", description = "Unauthorized") - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @PutMapping(path = ["/{ru}/batch"], consumes = ["application/json"]) - fun batchUpdateTrainIdentifiers(@PathVariable("ru") @Size(min= 4, max = 4) ru: String, @RequestBody trainIdentifiers: List) { - println("$ru $trainIdentifiers") - } - - @Operation(summary = "Delete train identifier") - @ApiResponse(responseCode = "200", description = "Train identifier successfully deleted") - @ApiResponse(responseCode = "401", description = "Unauthorized") - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @DeleteMapping(path = ["/{ru}/{operationDate}/{trainIdentifier}"]) - fun deleteTrainIdentifier(@PathVariable("ru") @Size(min= 4, max = 4) ru: String, @PathVariable("operationDate") operationDate: LocalDate, @PathVariable("trainIdentifier") trainIdentifier: String) { - println("$ru $operationDate $trainIdentifier") - } - - @Operation(summary = "Get all train identifiers") - @ApiResponse(responseCode = "200", description = "Train identifiers successfully retrieved") - @ApiResponse( - responseCode = "401", - description = "Unauthorized", - content = [Content(schema = Schema(hidden = true))] - ) - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @ResponseBody - @GetMapping(produces = [APPLICATION_JSON_VALUE]) - fun getAllTrainIdentifiers(): ResponseEntity> { - return ResponseEntity.ok().build() - } -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt b/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt deleted file mode 100644 index fa2c36e3..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/TrainIdentifierDto.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ch.sbb.backend.application.rest - -import io.swagger.v3.oas.annotations.media.Schema -import java.time.LocalDate -import java.time.OffsetDateTime - -@Schema(name = "Train identifier") -data class TrainIdentifierDto( - - @Schema(description = "Train identifier", example = "819") - val identifier: String, - - @Schema(description = "Operation date of the train", type = "string", format = "date") - val operationDate: LocalDate, - - @Schema(description = "Timestamp of the start of train operation") - val startDateTime: OffsetDateTime, -) From 573eb19607367b9b8fbb1662be7c871314be1dd8 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Tue, 3 Dec 2024 14:52:44 +0100 Subject: [PATCH 05/13] ci: preload build --- .github/workflows/ci-backend.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index 4864e301..e87981ec 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -7,25 +7,30 @@ # documentation. name: Backend Java CI with Maven - -defaults: - run: - working-directory: backend on: push: paths: - 'backend/**' + - 'preload/**' branches: [ "main" ] pull_request: paths: - 'backend/**' + - 'preload/**' branches: [ "main" ] jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + directory: ['backend', 'preload'] + + defaults: + run: + working-directory: ${{ matrix.directory }} steps: - uses: actions/checkout@v4 @@ -43,7 +48,7 @@ jobs: - name: Create container image if: github.ref == 'refs/heads/main' env: - IMAGE_ID: ghcr.io/${{ github.repository }}/backend + IMAGE_ID: ghcr.io/${{ github.repository }}/${{ matrix.directory }} VERSION: main run: | # Convert to lowercase @@ -58,4 +63,4 @@ jobs: - name: Update dependency graph uses: advanced-security/maven-dependency-submission-action@v4 with: - directory: backend + directory: ${{ matrix.directory }} From b9556b4c25377b747a58e6cbacaa7741d349dc76 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Tue, 10 Dec 2024 09:07:28 +0100 Subject: [PATCH 06/13] feat: tour api --- .../sbb/backend/tours/application/TourDto.kt | 40 +++++++++++++++++ .../tours/application/ToursController.kt | 44 +++++++++++++++++++ .../application/TrainIdentificationDto.kt | 26 +++++++++++ .../backend/tours/application/TrainRunDto.kt | 36 +++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/tours/application/TourDto.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/tours/application/ToursController.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainIdentificationDto.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainRunDto.kt diff --git a/backend/src/main/kotlin/ch/sbb/backend/tours/application/TourDto.kt b/backend/src/main/kotlin/ch/sbb/backend/tours/application/TourDto.kt new file mode 100644 index 00000000..ad7faef2 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/tours/application/TourDto.kt @@ -0,0 +1,40 @@ +package ch.sbb.backend.tours.application + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate + + +@Schema(name = "Tour") +data class TourDto( + @Schema( + description = "Identification of the user (e.g. email address)", + example = "michael.mueller@sbb.ch" + ) + val userId: String, + + @Schema( + description = "Identification of the tour", + example = "123456" + ) + val tourId: String, + + @Schema( + description = "Tour date", + example = "2024-12-24" + ) + val tourDate: LocalDate, + + + @Schema( + description = "Company code", + example = "1085" + ) + val company: String, + + + @Schema( + description = "train runs", + ) + val trainRuns: List, + + ) diff --git a/backend/src/main/kotlin/ch/sbb/backend/tours/application/ToursController.kt b/backend/src/main/kotlin/ch/sbb/backend/tours/application/ToursController.kt new file mode 100644 index 00000000..b71c4ce9 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/tours/application/ToursController.kt @@ -0,0 +1,44 @@ +package ch.sbb.backend.tours.application + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.MediaType.APPLICATION_JSON_VALUE +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@Validated +@RestController +@RequestMapping("api/v1/tours") +@Tag(name = "Tours", description = "API for tours") +class ToursController { + + @Operation(summary = "Update tours") + @ApiResponse(responseCode = "200", description = "Tours successfully updated") + @ApiResponse( + responseCode = "400", description = "Invalid input", content = [ + Content( + mediaType = APPLICATION_JSON_VALUE, + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @ApiResponse(responseCode = "401", description = "Unauthorized") + @ApiResponse( + responseCode = "500", description = "Internal server error", content = [ + Content( + mediaType = "application/json", + schema = Schema(ref = "#/components/schemas/ErrorResponse") + ) + ] + ) + @PutMapping(consumes = ["application/json"]) + fun updateTours(@RequestBody tours: List) { + + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainIdentificationDto.kt b/backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainIdentificationDto.kt new file mode 100644 index 00000000..691c8251 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainIdentificationDto.kt @@ -0,0 +1,26 @@ +package ch.sbb.backend.tours.application + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate + +@Schema(name = "Train Identification") +class TrainIdentificationDto( + @Schema( + description = "Identifies the train", + example = "123456" + ) + var operationalTrainNumber: String, + + @Schema( + description = "Start date", + example = "2024-12-24" + ) + var startDate: LocalDate, + + @Schema( + description = "Company code of the railway undertaking", + example = "1085" + ) + var company: String, + + ) diff --git a/backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainRunDto.kt b/backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainRunDto.kt new file mode 100644 index 00000000..c0d2e0d8 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/tours/application/TrainRunDto.kt @@ -0,0 +1,36 @@ +package ch.sbb.backend.tours.application + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.OffsetDateTime + +@Schema(name = "Train run") +class TrainRunDto( + @Schema( + description = "Train identification", + ) + val trainIdentification: TrainIdentificationDto, + + @Schema( + description = "UIC-Code of the start location, combination of uicCountryCode and numberShort. Size: 7", + example = "8518771" + ) + val startUic: String, + + @Schema( + description = "UIC-Code of the end location, combination of uicCountryCode and numberShort. Size: 7", + example = "8518771" + ) + val endUic: String, + + @Schema( + description = "Start date time", + example = "2024-12-24T12:00:00Z" + ) + var startDateTime: OffsetDateTime, + + @Schema( + description = "End date time", + example = "2024-12-24T12:00:00Z" + ) + var endDateTime: OffsetDateTime, +) From 024208bbe5b444eb57ab451a23a8698a1f57cb33 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Tue, 10 Dec 2024 22:58:25 +0100 Subject: [PATCH 07/13] feat: service point ddd refactoring --- backend/pom.xml | 22 +++ .../application/ServicePointsService.kt | 13 -- .../domain/servicepoints/ServicePoint.kt | 19 -- .../entities/ServicePointEntity.kt | 12 -- .../repositories/ServicePointsRepository.kt | 10 -- .../services/ServicePointsServiceImpl.kt | 33 ---- .../application/ServicePointReponse.kt} | 4 +- .../application}/ServicePointsController.kt | 40 +---- .../servicepoints/domain/ServicePoint.kt | 8 + .../repository/DomainServicePointService.kt | 24 +++ .../repository/ServicePointRepository.kt | 10 ++ .../domain/service/ServicePointService.kt | 10 ++ .../configuration/BeanConfiguration.kt | 17 ++ .../configuration/PostgresConfiguration.kt | 7 + .../entities/ServicePointEntity.kt | 25 +++ .../PostresServicePointRepository.kt | 26 +++ .../SpringDataJpaServicePointRepository.kt | 10 ++ .../infrastructure/rest/AtlasClient.kt | 6 + .../infrastructure/tasks/ServicePointTask.kt | 6 + .../src/test/kotlin/ch/sbb/backend/BaseIT.kt | 15 ++ .../BaseIT.kt => BaseTestcontainersTest.kt} | 13 +- .../rest/ServicePointsControllerTest.kt | 165 ------------------ .../ServicePointsControllerTest.kt | 34 ++++ .../DomainServicePointServiceTest.kt | 28 +++ .../ch/sbb/das/preload/PreloadApplication.kt | 13 -- 25 files changed, 259 insertions(+), 311 deletions(-) delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/application/ServicePointsService.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/domain/servicepoints/ServicePoint.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/infrastructure/entities/ServicePointEntity.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/infrastructure/repositories/ServicePointsRepository.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/infrastructure/services/ServicePointsServiceImpl.kt rename backend/src/main/kotlin/ch/sbb/backend/{api/ServicePointDto.kt => servicepoints/application/ServicePointReponse.kt} (86%) rename backend/src/main/kotlin/ch/sbb/backend/{application/rest => servicepoints/application}/ServicePointsController.kt (60%) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/ServicePoint.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/ServicePointRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/ServicePointService.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/entities/ServicePointEntity.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/SpringDataJpaServicePointRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/rest/AtlasClient.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/tasks/ServicePointTask.kt create mode 100644 backend/src/test/kotlin/ch/sbb/backend/BaseIT.kt rename backend/src/test/kotlin/ch/sbb/backend/{application/rest/BaseIT.kt => BaseTestcontainersTest.kt} (54%) delete mode 100644 backend/src/test/kotlin/ch/sbb/backend/application/rest/ServicePointsControllerTest.kt create mode 100644 backend/src/test/kotlin/ch/sbb/backend/servicepoints/ServicePointsControllerTest.kt create mode 100644 backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt delete mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt diff --git a/backend/pom.xml b/backend/pom.xml index 4e18f74c..9628551f 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -29,6 +29,7 @@ 21 2.0.21 + 1.3.0 @@ -63,6 +64,10 @@ org.jetbrains.kotlin kotlin-stdlib + + org.springframework.modulith + spring-modulith-starter-core + org.springdoc @@ -117,8 +122,25 @@ postgresql test + + org.springframework.modulith + spring-modulith-starter-test + test + + + + + org.springframework.modulith + spring-modulith-bom + ${spring-modulith.version} + pom + import + + + + ${project.basedir}/src/main/kotlin ${project.basedir}/src/test/kotlin diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/ServicePointsService.kt b/backend/src/main/kotlin/ch/sbb/backend/application/ServicePointsService.kt deleted file mode 100644 index 416c55ef..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/application/ServicePointsService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package ch.sbb.backend.application - -import ch.sbb.backend.api.ServicePointDto -import ch.sbb.backend.domain.servicepoints.ServicePoint - -interface ServicePointsService { - - fun findByUic(uic: Int): ServicePoint? - - fun update(servicePoints: List): List - - fun getAll(): List -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/servicepoints/ServicePoint.kt b/backend/src/main/kotlin/ch/sbb/backend/domain/servicepoints/ServicePoint.kt deleted file mode 100644 index 4bd4a375..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/servicepoints/ServicePoint.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ch.sbb.backend.domain.servicepoints - -import ch.sbb.backend.api.ServicePointDto - -data class ServicePoint( - val uic: Int, - val designation: String, - val abbreviation: String -) { - fun toApi(): ServicePointDto { - return ServicePointDto(uic, designation, abbreviation) - } - - companion object { - fun toApi(servicePoints: List): List { - return servicePoints.map { it.toApi() } - } - } -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/entities/ServicePointEntity.kt b/backend/src/main/kotlin/ch/sbb/backend/infrastructure/entities/ServicePointEntity.kt deleted file mode 100644 index 8b2c5d56..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/entities/ServicePointEntity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ch.sbb.backend.infrastructure.entities - -import jakarta.persistence.Entity -import jakarta.persistence.Id - -@Entity(name = "service_points") - class ServicePointEntity( - @Id - var uic: Int, - var designation: String, - var abbreviation: String -) diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/repositories/ServicePointsRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/infrastructure/repositories/ServicePointsRepository.kt deleted file mode 100644 index 241260c9..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/repositories/ServicePointsRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package ch.sbb.backend.infrastructure.repositories - -import ch.sbb.backend.infrastructure.entities.ServicePointEntity -import org.springframework.data.repository.ListCrudRepository -import org.springframework.stereotype.Repository - -@Repository -interface ServicePointsRepository : ListCrudRepository { - fun findByUic(uic: Int): ServicePointEntity? -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/services/ServicePointsServiceImpl.kt b/backend/src/main/kotlin/ch/sbb/backend/infrastructure/services/ServicePointsServiceImpl.kt deleted file mode 100644 index 204a75a8..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/services/ServicePointsServiceImpl.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ch.sbb.backend.infrastructure.services - -import ch.sbb.backend.api.ServicePointDto -import ch.sbb.backend.application.ServicePointsService -import ch.sbb.backend.domain.servicepoints.ServicePoint -import ch.sbb.backend.infrastructure.entities.ServicePointEntity -import ch.sbb.backend.infrastructure.repositories.ServicePointsRepository -import org.springframework.stereotype.Service - -@Service -class ServicePointsServiceImpl(private val servicePointsRepository: ServicePointsRepository) : - ServicePointsService { - override fun findByUic(uic: Int): ServicePoint? { - return servicePointsRepository.findByUic(uic)?.mapToServicePoint() - } - - override fun update(servicePoints: List): List { - return servicePointsRepository.saveAll(servicePoints.map { it.toEntity() }) - .map { it.mapToServicePoint() } - } - - override fun getAll(): List { - return servicePointsRepository.findAll().map { it.mapToServicePoint() } - } - - fun ServicePointEntity.mapToServicePoint(): ServicePoint { - return ServicePoint(uic, designation, abbreviation) - } - - fun ServicePointDto.toEntity(): ServicePointEntity { - return ServicePointEntity(uic, designation, abbreviation) - } -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/api/ServicePointDto.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/application/ServicePointReponse.kt similarity index 86% rename from backend/src/main/kotlin/ch/sbb/backend/api/ServicePointDto.kt rename to backend/src/main/kotlin/ch/sbb/backend/servicepoints/application/ServicePointReponse.kt index 234e3941..1bf258a1 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/api/ServicePointDto.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/application/ServicePointReponse.kt @@ -1,9 +1,9 @@ -package ch.sbb.backend.api +package ch.sbb.backend.servicepoints.application import io.swagger.v3.oas.annotations.media.Schema @Schema(name = "ServicePoint") -data class ServicePointDto( +data class ServicePointReponse( @Schema( description = "UIC-Code, combination of uicCountryCode and numberShort. Size: 7", diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/ServicePointsController.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/application/ServicePointsController.kt similarity index 60% rename from backend/src/main/kotlin/ch/sbb/backend/application/rest/ServicePointsController.kt rename to backend/src/main/kotlin/ch/sbb/backend/servicepoints/application/ServicePointsController.kt index f6068870..4399bc6e 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/ServicePointsController.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/application/ServicePointsController.kt @@ -1,8 +1,6 @@ -package ch.sbb.backend.application.rest +package ch.sbb.backend.servicepoints.application -import ch.sbb.backend.api.ServicePointDto -import ch.sbb.backend.application.ServicePointsService -import ch.sbb.backend.domain.servicepoints.ServicePoint +import ch.sbb.backend.servicepoints.domain.service.ServicePointService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema @@ -17,31 +15,7 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping("api/v1/service-points") @Tag(name = "Service Points", description = "API for service points") -class ServicePointsController(private val servicePointsService: ServicePointsService) { - - @Operation(summary = "Update service points") - @ApiResponse(responseCode = "200", description = "Service points successfully updated") - @ApiResponse( - responseCode = "400", description = "Invalid input", content = [ - Content( - mediaType = APPLICATION_JSON_VALUE, - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @ApiResponse(responseCode = "401", description = "Unauthorized") - @ApiResponse( - responseCode = "500", description = "Internal server error", content = [ - Content( - mediaType = "application/json", - schema = Schema(ref = "#/components/schemas/ErrorResponse") - ) - ] - ) - @PutMapping(consumes = ["application/json"]) - fun updateServicePoints(@RequestBody servicePoints: List) { - servicePointsService.update(servicePoints) - } +class ServicePointsController(private val servicePointService: ServicePointService) { @Operation(summary = "Get all service points") @ApiResponse(responseCode = "200", description = "Service points successfully retrieved") @@ -60,8 +34,8 @@ class ServicePointsController(private val servicePointsService: ServicePointsSer ) @ResponseBody @GetMapping(produces = [APPLICATION_JSON_VALUE]) - fun getAllServicePoints(): ResponseEntity> { - return ResponseEntity.ok(ServicePoint.toApi(servicePointsService.getAll())) + fun getAllServicePoints(): ResponseEntity> { + return ResponseEntity.ok(servicePointService.getAll().map { ServicePointReponse(it.uic, it.designation, it.abbreviation) }) } @Operation(summary = "Get service point by UIC") @@ -86,8 +60,8 @@ class ServicePointsController(private val servicePointsService: ServicePointsSer ) @ResponseBody @GetMapping("/{uic}", produces = [APPLICATION_JSON_VALUE]) - fun getServicePoint(@PathVariable uic: Int): ResponseEntity { - return servicePointsService.findByUic(uic)?.let { ResponseEntity.ok(it.toApi()) } + fun getServicePoint(@PathVariable uic: Int): ResponseEntity { + return servicePointService.findByUic(uic)?.let { ResponseEntity.ok(ServicePointReponse(it.uic, it.abbreviation, it.designation)) } ?: ResponseEntity.notFound().build() } } diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/ServicePoint.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/ServicePoint.kt new file mode 100644 index 00000000..9c3a0aa0 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/ServicePoint.kt @@ -0,0 +1,8 @@ +package ch.sbb.backend.servicepoints.domain + + +data class ServicePoint( + val uic: Int, + val designation: String, + val abbreviation: String +) diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt new file mode 100644 index 00000000..2aa334c3 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt @@ -0,0 +1,24 @@ +package ch.sbb.backend.servicepoints.domain.repository + +import ch.sbb.backend.servicepoints.domain.ServicePoint +import ch.sbb.backend.servicepoints.domain.service.ServicePointService + +class DomainServicePointService(private val servicePointRepository: ServicePointRepository) : + ServicePointService { + override fun getAll(): List { + return servicePointRepository.findAll() + } + + override fun findByUic(uic: Int): ServicePoint? { + return servicePointRepository.findByUic(uic) + } + + override fun updateAll(servicePoints: List) { + servicePointRepository.saveAll(servicePoints) + } + + override fun create(servicePoint: ServicePoint) { + servicePointRepository.save(servicePoint) + } + +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/ServicePointRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/ServicePointRepository.kt new file mode 100644 index 00000000..1f2063d3 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/ServicePointRepository.kt @@ -0,0 +1,10 @@ +package ch.sbb.backend.servicepoints.domain.repository + +import ch.sbb.backend.servicepoints.domain.ServicePoint + +interface ServicePointRepository { + fun findByUic(uic: Int): ServicePoint? + fun findAll(): List + fun saveAll(servicePoints: List) + fun save(servicePoint: ServicePoint) +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/ServicePointService.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/ServicePointService.kt new file mode 100644 index 00000000..b8fb703a --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/ServicePointService.kt @@ -0,0 +1,10 @@ +package ch.sbb.backend.servicepoints.domain.service + +import ch.sbb.backend.servicepoints.domain.ServicePoint + +interface ServicePointService { + fun getAll(): List + fun findByUic(uic: Int): ServicePoint? + fun updateAll(servicePoints: List) + fun create(servicePoint: ServicePoint) +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt new file mode 100644 index 00000000..e6a302d7 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt @@ -0,0 +1,17 @@ +package ch.sbb.backend.servicepoints.infrastructure.configuration + +import ch.sbb.backend.servicepoints.domain.repository.DomainServicePointService +import ch.sbb.backend.servicepoints.domain.repository.ServicePointRepository +import ch.sbb.backend.servicepoints.domain.service.ServicePointService +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class BeanConfiguration { + + @Bean + fun orderService(servicePointRepository: ServicePointRepository): ServicePointService { + return DomainServicePointService(servicePointRepository) + } + +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt new file mode 100644 index 00000000..dacf529a --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt @@ -0,0 +1,7 @@ +package ch.sbb.backend.servicepoints.infrastructure.configuration + +import ch.sbb.backend.servicepoints.infrastructure.repositories.SpringDataJpaServicePointRepository +import org.springframework.data.jpa.repository.config.EnableJpaRepositories + +@EnableJpaRepositories(basePackageClasses = [SpringDataJpaServicePointRepository::class]) +class PostgresConfiguration diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/entities/ServicePointEntity.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/entities/ServicePointEntity.kt new file mode 100644 index 00000000..17dcb66b --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/entities/ServicePointEntity.kt @@ -0,0 +1,25 @@ +package ch.sbb.backend.servicepoints.infrastructure.entities + +import ch.sbb.backend.servicepoints.domain.ServicePoint +import jakarta.persistence.Entity +import jakarta.persistence.Id + +@Entity(name = "service_points") +class ServicePointEntity( + @Id + var uic: Int, + var designation: String, + var abbreviation: String +) { + + + constructor(servicePoint: ServicePoint) : this( + servicePoint.uic, + servicePoint.designation, + servicePoint.abbreviation + ) + + fun toServicePoint():ServicePoint { + return ServicePoint(uic,designation, abbreviation) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt new file mode 100644 index 00000000..d2ce23ee --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt @@ -0,0 +1,26 @@ +package ch.sbb.backend.servicepoints.infrastructure.repositories + +import ch.sbb.backend.servicepoints.domain.ServicePoint +import ch.sbb.backend.servicepoints.domain.repository.ServicePointRepository +import ch.sbb.backend.servicepoints.infrastructure.entities.ServicePointEntity +import org.springframework.stereotype.Component + +@Component +class PostresServicePointRepository(private val servicePointRepository: SpringDataJpaServicePointRepository) : + ServicePointRepository { + override fun findByUic(uic: Int): ServicePoint? { + return servicePointRepository.findByUic(uic)?.toServicePoint() + } + + override fun findAll(): List { + return servicePointRepository.findAll().map { it.toServicePoint() } + } + + override fun saveAll(servicePoints: List) { + servicePointRepository.saveAll(servicePoints.map { ServicePointEntity(it) }) + } + + override fun save(servicePoint: ServicePoint) { + servicePointRepository.save(ServicePointEntity(servicePoint)) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/SpringDataJpaServicePointRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/SpringDataJpaServicePointRepository.kt new file mode 100644 index 00000000..3f8abec9 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/SpringDataJpaServicePointRepository.kt @@ -0,0 +1,10 @@ +package ch.sbb.backend.servicepoints.infrastructure.repositories + +import ch.sbb.backend.servicepoints.infrastructure.entities.ServicePointEntity +import org.springframework.data.repository.ListCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface SpringDataJpaServicePointRepository : ListCrudRepository { + fun findByUic(uic: Int): ServicePointEntity? +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/rest/AtlasClient.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/rest/AtlasClient.kt new file mode 100644 index 00000000..289f9ebd --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/rest/AtlasClient.kt @@ -0,0 +1,6 @@ +package ch.sbb.backend.servicepoints.infrastructure.rest + +// To be implemented +// REST client to retrieve service points from api +class AtlasClient { +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/tasks/ServicePointTask.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/tasks/ServicePointTask.kt new file mode 100644 index 00000000..03852b3e --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/tasks/ServicePointTask.kt @@ -0,0 +1,6 @@ +package ch.sbb.backend.servicepoints.infrastructure.tasks + +// To be implemented +// scheduled task to retrieve service points +class ServicePointTask { +} diff --git a/backend/src/test/kotlin/ch/sbb/backend/BaseIT.kt b/backend/src/test/kotlin/ch/sbb/backend/BaseIT.kt new file mode 100644 index 00000000..664d6fab --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/BaseIT.kt @@ -0,0 +1,15 @@ +package ch.sbb.backend + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.web.servlet.MockMvc + +@SpringBootTest +@AutoConfigureMockMvc +class BaseIT: BaseTestcontainersTest() { + + @Autowired + protected lateinit var mockMvc: MockMvc + +} diff --git a/backend/src/test/kotlin/ch/sbb/backend/application/rest/BaseIT.kt b/backend/src/test/kotlin/ch/sbb/backend/BaseTestcontainersTest.kt similarity index 54% rename from backend/src/test/kotlin/ch/sbb/backend/application/rest/BaseIT.kt rename to backend/src/test/kotlin/ch/sbb/backend/BaseTestcontainersTest.kt index 78b53d78..f8cabfac 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/application/rest/BaseIT.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/BaseTestcontainersTest.kt @@ -1,22 +1,13 @@ -package ch.sbb.backend.application.rest +package ch.sbb.backend -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.testcontainers.service.connection.ServiceConnection -import org.springframework.test.web.servlet.MockMvc import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.junit.jupiter.Container import org.testcontainers.junit.jupiter.Testcontainers import org.testcontainers.utility.DockerImageName -@SpringBootTest -@AutoConfigureMockMvc @Testcontainers -class BaseIT { - - @Autowired - protected lateinit var mockMvc: MockMvc +open class BaseTestcontainersTest { companion object { @Container diff --git a/backend/src/test/kotlin/ch/sbb/backend/application/rest/ServicePointsControllerTest.kt b/backend/src/test/kotlin/ch/sbb/backend/application/rest/ServicePointsControllerTest.kt deleted file mode 100644 index 19e3dbcc..00000000 --- a/backend/src/test/kotlin/ch/sbb/backend/application/rest/ServicePointsControllerTest.kt +++ /dev/null @@ -1,165 +0,0 @@ -package ch.sbb.backend.application.rest - -import org.junit.jupiter.api.Test -import org.springframework.http.MediaType -import org.springframework.security.test.context.support.WithMockUser -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status - - -class ServicePointsControllerTest : BaseIT() { - - @Test - @WithMockUser(authorities = ["ROLE_admin"]) - fun `should update and find service point`() { - val servicePointsJson = """ - [ - { - "uic": 8518771, - "designation": "Biel/Bienne Bözingenfeld/Champ", - "abbreviation": "BIBD" - } - ] - """.trimIndent() - - mockMvc.perform( - put("/api/v1/service-points") - .contentType(MediaType.APPLICATION_JSON) - .content(servicePointsJson) - ) - .andExpect(status().isOk) - - mockMvc.perform( - get("/api/v1/service-points/8518771") - ) - .andExpect(status().isOk) - .andExpect(jsonPath("$.uic").value(8518771)) - .andExpect(jsonPath("$.designation").value("Biel/Bienne Bözingenfeld/Champ")) - .andExpect(jsonPath("$.abbreviation").value("BIBD")) - } - - @Test - @WithMockUser(authorities = ["ROLE_admin"]) - fun `should update and find all service point`() { - val servicePointsJson = """ - [ - { - "uic": 8518771, - "designation": "Biel/Bienne Bözingenfeld/Champ", - "abbreviation": "BIBD" - }, - { - "uic": 8583629, - "designation": "Hinterhunziken", - "abbreviation": "HHZ" - } - ] - """.trimIndent() - - mockMvc.perform( - put("/api/v1/service-points") - .contentType(MediaType.APPLICATION_JSON) - .content(servicePointsJson) - ) - .andExpect(status().isOk) - - mockMvc.perform( - get("/api/v1/service-points") - ) - .andExpect(status().isOk) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].uic").value(8583629)) - .andExpect(jsonPath("$[0].designation").value("Hinterhunziken")) - .andExpect(jsonPath("$[0].abbreviation").value("HHZ")) - .andExpect(jsonPath("$[1].uic").value(8518771)) - .andExpect(jsonPath("$[1].designation").value("Biel/Bienne Bözingenfeld/Champ")) - .andExpect(jsonPath("$[1].abbreviation").value("BIBD")) - } - - @Test - @WithMockUser(authorities = ["ROLE_admin"]) - fun `should update existing service points`() { - val servicePointsJson = """ - [ - { - "uic": 8518771, - "designation": "Biel/Bienne Bözingenfeld/Champ", - "abbreviation": "BIBD" - }, - { - "uic": 8583629, - "designation": "Hinterhunziken", - "abbreviation": "HHZ" - } - ] - """.trimIndent() - - mockMvc.perform( - put("/api/v1/service-points") - .contentType(MediaType.APPLICATION_JSON) - .content(servicePointsJson) - ) - .andExpect(status().isOk) - - - val updatedServicePointJson = """ - [ - { - "uic": 8518771, - "designation": "Biel/Bienne Bözingenfeld", - "abbreviation": "BIBD" - } - ] - """.trimIndent() - - mockMvc.perform( - put("/api/v1/service-points") - .contentType(MediaType.APPLICATION_JSON) - .content(updatedServicePointJson) - ) - .andExpect(status().isOk) - - mockMvc.perform( - get("/api/v1/service-points") - ) - .andExpect(status().isOk) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].uic").value(8583629)) - .andExpect(jsonPath("$[0].designation").value("Hinterhunziken")) - .andExpect(jsonPath("$[0].abbreviation").value("HHZ")) - .andExpect(jsonPath("$[1].uic").value(8518771)) - .andExpect(jsonPath("$[1].designation").value("Biel/Bienne Bözingenfeld")) - .andExpect(jsonPath("$[1].abbreviation").value("BIBD")) - } - - @Test - @WithMockUser(authorities = ["ROLE_admin"]) - fun `should respond with not found`() { - mockMvc.perform( - get("/api/v1/service-points/11111") - ) - .andExpect(status().isNotFound) - } - - @Test - @WithMockUser(authorities = ["ROLE_admin"]) - fun `should respond with bad request`() { - val servicePointsJson = """ - [ - { - "uic": 8518771, - "designation": "Biel/Bienne Bözingenfeld/Champ" - } - ] - """.trimIndent() - - mockMvc.perform( - put("/api/v1/service-points") - .contentType(MediaType.APPLICATION_JSON) - .content(servicePointsJson) - ) - .andExpect(status().isBadRequest) - } -} diff --git a/backend/src/test/kotlin/ch/sbb/backend/servicepoints/ServicePointsControllerTest.kt b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/ServicePointsControllerTest.kt new file mode 100644 index 00000000..9305d509 --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/ServicePointsControllerTest.kt @@ -0,0 +1,34 @@ +package ch.sbb.backend.servicepoints + +import ch.sbb.backend.BaseIT +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + + +class ServicePointsControllerTest : BaseIT() { + + @Test + @WithMockUser(authorities = ["ROLE_admin"]) + fun `should respond with not found`() { + mockMvc.perform( + get("/api/v1/service-points/11111") + ) + .andExpect(status().isNotFound) + } + + + @Test + @WithMockUser(authorities = ["ROLE_admin"]) + fun `should respond with empty`() { + mockMvc.perform( + get("/api/v1/service-points") + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.length()").value(0)) + } +} diff --git a/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt new file mode 100644 index 00000000..102c39ba --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt @@ -0,0 +1,28 @@ +package ch.sbb.backend.servicepoints.domain.repository + +import ch.sbb.backend.servicepoints.domain.ServicePoint +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* + + +class DomainServicePointServiceTest { + + private lateinit var servicePointRepository: ServicePointRepository + private lateinit var tested: DomainServicePointService + + @BeforeEach + fun setUp() { + servicePointRepository = mock(ServicePointRepository::class.java) + tested = DomainServicePointService(servicePointRepository) + } + + @Test + fun shouldUpdateServicePoints_thenSave() { + val servicePoints: List = + listOf(ServicePoint(1, "designation", "abbreviation")) + tested.updateAll(servicePoints) + verify(servicePointRepository).saveAll(servicePoints) + + } +} diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt b/preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt deleted file mode 100644 index e1591096..00000000 --- a/preload/src/main/kotlin/ch/sbb/das/preload/PreloadApplication.kt +++ /dev/null @@ -1,13 +0,0 @@ -package ch.sbb.das.preload - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication -import org.springframework.boot.web.servlet.ServletComponentScan - -@ServletComponentScan -@SpringBootApplication -class PreloadApplication - -fun main(args: Array) { - runApplication(*args) -} From e49260dea6df9ed7016eeba59f067d17eb790b84 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Tue, 10 Dec 2024 23:05:44 +0100 Subject: [PATCH 08/13] feat: preload ddd refactoring --- .../entities/TrainIdentificationEntity.kt | 19 ++++++ .../entities/TrainIdentificationId.kt | 33 ++++++++++ backend/src/main/resources/schema.sql | 21 ++++++- ...rainIdentificationEntityIntegrationTest.kt | 60 +++++++++++++++++++ .../entities/TrainIdentifierEntity.kt | 19 ------ .../entities/TrainIdentifierId.kt | 33 ---------- .../TrainIdentifiersRepository.kt | 9 --- ...ainIdentificationEntityIntegrationTest.kt} | 24 ++++---- 8 files changed, 142 insertions(+), 76 deletions(-) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationId.kt create mode 100644 backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt delete mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt delete mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt delete mode 100644 preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt rename preload/src/test/kotlin/ch/sbb/das/preload/{TrainIdentifierEntityIntegrationTest.kt => TrainIdentificationEntityIntegrationTest.kt} (70%) diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt new file mode 100644 index 00000000..d56d188d --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt @@ -0,0 +1,19 @@ +package ch.sbb.backend.preload.infrastructure.entities + +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.IdClass +import java.time.LocalDate +import java.time.OffsetDateTime + +@Entity(name = "train_identification") +@IdClass(TrainIdentificationId::class) +class TrainIdentificationEntity( + @Id + var operationalTrainNumber: String, + @Id + var startDate: LocalDate, + @Id + var company: String, + var startDateTime: OffsetDateTime +) diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationId.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationId.kt new file mode 100644 index 00000000..0008bc4a --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationId.kt @@ -0,0 +1,33 @@ +package ch.sbb.backend.preload.infrastructure.entities + +import java.io.Serializable +import java.time.LocalDate + +class TrainIdentificationId( + val operationalTrainNumber: String = "", + val startDate: LocalDate = LocalDate.now(), + val company: String = "" +) : Serializable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TrainIdentificationId + + if (operationalTrainNumber != other.operationalTrainNumber) return false + if (startDate != other.startDate) return false + if (company != other.company) return false + + return true + } + + override fun hashCode(): Int { + var result = operationalTrainNumber.hashCode() + result = 31 * result + startDate.hashCode() + result = 31 * result + company.hashCode() + return result + } +} + + diff --git a/backend/src/main/resources/schema.sql b/backend/src/main/resources/schema.sql index 43579562..a1d75ca4 100644 --- a/backend/src/main/resources/schema.sql +++ b/backend/src/main/resources/schema.sql @@ -1,5 +1,20 @@ -CREATE TABLE IF NOT EXISTS service_points ( - uic INTEGER PRIMARY KEY, - designation TEXT NOT NULL, +CREATE TABLE IF NOT EXISTS service_points +( + uic INTEGER PRIMARY KEY, + designation TEXT NOT NULL, abbreviation TEXT NOT NULL ); + +CREATE TABLE IF NOT EXISTS train_identification +( + operational_train_number TEXT NOT NULL, + start_date DATE NOT NULL, + company TEXT NOT NULL, + start_date_time TIMESTAMP NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS train_identification_idx + ON train_identification ( + operational_train_number, + start_date, company + ); diff --git a/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt b/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt new file mode 100644 index 00000000..5d8198c0 --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt @@ -0,0 +1,60 @@ +package ch.sbb.backend.preload.infrastructure.repositories + +import ch.sbb.backend.BaseTestcontainersTest +import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationEntity +import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationId +import org.assertj.core.api.Assertions.assertThat +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager +import java.time.LocalDate +import java.time.OffsetDateTime +import kotlin.test.Test + +@DataJpaTest +class TrainIdentificationEntityIntegrationTest : BaseTestcontainersTest() { + + @Autowired + lateinit var trainIdentificationRepository: TrainIdentificationRepository + + @Autowired + lateinit var em: TestEntityManager + + @Test + fun givenNewTrainIdentifier_whenSave_thenSuccess() { + val identifier = "1111" + val operationDate = LocalDate.now() + val ru = "22" + val startTime = OffsetDateTime.now() + + val train = TrainIdentificationEntity(identifier, operationDate, ru, startTime) + trainIdentificationRepository.save(train) + + val result = em.find( + TrainIdentificationEntity::class.java, + TrainIdentificationId(identifier, operationDate, ru) + ) + assertThat(result.startDateTime).isEqualTo(startTime) + } + + @Test + fun givenTrainIdentifierCreated_whenUpdate_thenSuccess() { + val identifier = "1111" + val operationDate = LocalDate.now() + val ru = "22" + val startTime = OffsetDateTime.now() + + val train = TrainIdentificationEntity(identifier, operationDate, ru, startTime) + em.persist(train) + + val newStartTime = OffsetDateTime.now() + train.startDateTime = newStartTime + em.persist(train) + + val result = em.find( + TrainIdentificationEntity::class.java, + TrainIdentificationId(identifier, operationDate, ru) + ) + assertThat(result.startDateTime).isEqualTo(newStartTime) + } +} diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt deleted file mode 100644 index dcccc919..00000000 --- a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ch.sbb.das.preload.infrastructure.entities - -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.IdClass -import java.time.LocalDate -import java.time.OffsetDateTime - -@Entity(name = "train_identifier") -@IdClass(TrainIdentifierId::class) -class TrainIdentifierEntity( - @Id - var identifier: String, - @Id - var operationDate: LocalDate, - @Id - var ru: String, - var startDateTime: OffsetDateTime -) diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt deleted file mode 100644 index 7fc5a6a9..00000000 --- a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/entities/TrainIdentifierId.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ch.sbb.das.preload.infrastructure.entities - -import java.io.Serializable -import java.time.LocalDate - -class TrainIdentifierId( - val identifier: String = "", - val operationDate: LocalDate = LocalDate.now(), - val ru: String = "" -) : Serializable { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TrainIdentifierId - - if (identifier != other.identifier) return false - if (operationDate != other.operationDate) return false - if (ru != other.ru) return false - - return true - } - - override fun hashCode(): Int { - var result = identifier.hashCode() - result = 31 * result + operationDate.hashCode() - result = 31 * result + ru.hashCode() - return result - } -} - - diff --git a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt b/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt deleted file mode 100644 index 2579596f..00000000 --- a/preload/src/main/kotlin/ch/sbb/das/preload/infrastructure/repositories/TrainIdentifiersRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ch.sbb.das.preload.infrastructure.repositories - -import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierEntity -import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierId -import org.springframework.data.repository.ListCrudRepository -import org.springframework.stereotype.Repository - -@Repository -interface TrainIdentifiersRepository : ListCrudRepository diff --git a/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt b/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentificationEntityIntegrationTest.kt similarity index 70% rename from preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt rename to preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentificationEntityIntegrationTest.kt index 206327b7..75ea28e9 100644 --- a/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentifierEntityIntegrationTest.kt +++ b/preload/src/test/kotlin/ch/sbb/das/preload/TrainIdentificationEntityIntegrationTest.kt @@ -1,8 +1,8 @@ package ch.sbb.das.preload -import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierEntity -import ch.sbb.das.preload.infrastructure.entities.TrainIdentifierId -import ch.sbb.das.preload.infrastructure.repositories.TrainIdentifiersRepository +import ch.sbb.das.preload.infrastructure.entities.TrainIdentificationEntity +import ch.sbb.das.preload.infrastructure.entities.TrainIdentificationId +import ch.sbb.das.preload.infrastructure.repositories.TrainIdentificationRepository import org.assertj.core.api.Assertions.assertThat import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest @@ -18,7 +18,7 @@ import kotlin.test.Test @DataJpaTest @Testcontainers -class TrainIdentifierEntityIntegrationTest { +class TrainIdentificationEntityIntegrationTest { companion object { @Container @@ -28,7 +28,7 @@ class TrainIdentifierEntityIntegrationTest { } @Autowired - lateinit var trainIdentifiersRepository: TrainIdentifiersRepository + lateinit var trainIdentificationRepository: TrainIdentificationRepository @Autowired lateinit var em: TestEntityManager @@ -40,12 +40,12 @@ class TrainIdentifierEntityIntegrationTest { val ru = "22" val startTime = OffsetDateTime.now() - val train = TrainIdentifierEntity(identifier, operationDate, ru, startTime) - trainIdentifiersRepository.save(train) + val train = TrainIdentificationEntity(identifier, operationDate, ru, startTime) + trainIdentificationRepository.save(train) val result = em.find( - TrainIdentifierEntity::class.java, - TrainIdentifierId(identifier, operationDate, ru) + TrainIdentificationEntity::class.java, + TrainIdentificationId(identifier, operationDate, ru) ) assertThat(result.startDateTime).isEqualTo(startTime) } @@ -57,7 +57,7 @@ class TrainIdentifierEntityIntegrationTest { val ru = "22" val startTime = OffsetDateTime.now() - val train = TrainIdentifierEntity(identifier, operationDate, ru, startTime) + val train = TrainIdentificationEntity(identifier, operationDate, ru, startTime) em.persist(train) val newStartTime = OffsetDateTime.now() @@ -65,8 +65,8 @@ class TrainIdentifierEntityIntegrationTest { em.persist(train) val result = em.find( - TrainIdentifierEntity::class.java, - TrainIdentifierId(identifier, operationDate, ru) + TrainIdentificationEntity::class.java, + TrainIdentificationId(identifier, operationDate, ru) ) assertThat(result.startDateTime).isEqualTo(newStartTime) } From 6e305383ee295b73d178830d9d88b7fca1f9aaa5 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Thu, 19 Dec 2024 18:58:13 +0100 Subject: [PATCH 09/13] chore: preload ddd --- .../backend/preload/domain/TrainIdentification.kt | 11 +++++++++++ .../repository/TrainIdentificationRepository.kt | 7 +++++++ .../entities/TrainIdentificationEntity.kt | 10 +++++++++- .../PostgreSQLTrainIdentificationRepository.kt | 14 ++++++++++++++ .../SpringDataJpaTrainIdentificationRepository.kt | 10 ++++++++++ ...Configuration.kt => PostgreSQLConfiguration.kt} | 2 +- ...tory.kt => PostgreSQLServicePointRepository.kt} | 2 +- .../TrainIdentificationEntityIntegrationTest.kt | 4 +++- 8 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/preload/domain/TrainIdentification.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/preload/domain/repository/TrainIdentificationRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/PostgreSQLTrainIdentificationRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/SpringDataJpaTrainIdentificationRepository.kt rename backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/{PostgresConfiguration.kt => PostgreSQLConfiguration.kt} (91%) rename backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/{PostresServicePointRepository.kt => PostgreSQLServicePointRepository.kt} (88%) diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/domain/TrainIdentification.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/domain/TrainIdentification.kt new file mode 100644 index 00000000..44cf92d6 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/domain/TrainIdentification.kt @@ -0,0 +1,11 @@ +package ch.sbb.backend.preload.domain + +import java.time.LocalDate +import java.time.OffsetDateTime + +data class TrainIdentification( + val operationalTrainNumber: String, + val startDate: LocalDate, + val company: String, + val startDateTime: OffsetDateTime +) diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/domain/repository/TrainIdentificationRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/domain/repository/TrainIdentificationRepository.kt new file mode 100644 index 00000000..1a11b6fa --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/domain/repository/TrainIdentificationRepository.kt @@ -0,0 +1,7 @@ +package ch.sbb.backend.preload.domain.repository + +import ch.sbb.backend.preload.domain.TrainIdentification + +interface TrainIdentificationRepository { + fun save(trainIdentification: TrainIdentification) +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt index d56d188d..4729f466 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt @@ -1,5 +1,6 @@ package ch.sbb.backend.preload.infrastructure.entities +import ch.sbb.backend.preload.domain.TrainIdentification import jakarta.persistence.Entity import jakarta.persistence.Id import jakarta.persistence.IdClass @@ -16,4 +17,11 @@ class TrainIdentificationEntity( @Id var company: String, var startDateTime: OffsetDateTime -) +) { + constructor(trainIdentification: TrainIdentification) : this( + trainIdentification.operationalTrainNumber, + trainIdentification.startDate, + trainIdentification.company, + trainIdentification.startDateTime + ) +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/PostgreSQLTrainIdentificationRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/PostgreSQLTrainIdentificationRepository.kt new file mode 100644 index 00000000..a6d6afd2 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/PostgreSQLTrainIdentificationRepository.kt @@ -0,0 +1,14 @@ +package ch.sbb.backend.preload.infrastructure.repositories + +import ch.sbb.backend.preload.domain.TrainIdentification +import ch.sbb.backend.preload.domain.repository.TrainIdentificationRepository +import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationEntity +import org.springframework.stereotype.Component + +@Component +class PostgreSQLTrainIdentificationRepository(private val trainIdentificationRepository: SpringDataJpaTrainIdentificationRepository) : + TrainIdentificationRepository { + override fun save(trainIdentification: TrainIdentification) { + trainIdentificationRepository.save(TrainIdentificationEntity(trainIdentification)) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/SpringDataJpaTrainIdentificationRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/SpringDataJpaTrainIdentificationRepository.kt new file mode 100644 index 00000000..0ed8497c --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/repositories/SpringDataJpaTrainIdentificationRepository.kt @@ -0,0 +1,10 @@ +package ch.sbb.backend.preload.infrastructure.repositories + +import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationEntity +import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationId +import org.springframework.data.repository.ListCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface SpringDataJpaTrainIdentificationRepository : + ListCrudRepository {} diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgreSQLConfiguration.kt similarity index 91% rename from backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt rename to backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgreSQLConfiguration.kt index dacf529a..33c8a247 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgresConfiguration.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgreSQLConfiguration.kt @@ -4,4 +4,4 @@ import ch.sbb.backend.servicepoints.infrastructure.repositories.SpringDataJpaSer import org.springframework.data.jpa.repository.config.EnableJpaRepositories @EnableJpaRepositories(basePackageClasses = [SpringDataJpaServicePointRepository::class]) -class PostgresConfiguration +class PostgreSQLConfiguration diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostgreSQLServicePointRepository.kt similarity index 88% rename from backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt rename to backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostgreSQLServicePointRepository.kt index d2ce23ee..1a96a772 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostresServicePointRepository.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostgreSQLServicePointRepository.kt @@ -6,7 +6,7 @@ import ch.sbb.backend.servicepoints.infrastructure.entities.ServicePointEntity import org.springframework.stereotype.Component @Component -class PostresServicePointRepository(private val servicePointRepository: SpringDataJpaServicePointRepository) : +class PostgreSQLServicePointRepository(private val servicePointRepository: SpringDataJpaServicePointRepository) : ServicePointRepository { override fun findByUic(uic: Int): ServicePoint? { return servicePointRepository.findByUic(uic)?.toServicePoint() diff --git a/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt b/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt index 5d8198c0..17933c5d 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt @@ -1,6 +1,8 @@ package ch.sbb.backend.preload.infrastructure.repositories import ch.sbb.backend.BaseTestcontainersTest +import ch.sbb.backend.preload.domain.TrainIdentification +import ch.sbb.backend.preload.domain.repository.TrainIdentificationRepository import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationEntity import ch.sbb.backend.preload.infrastructure.entities.TrainIdentificationId import org.assertj.core.api.Assertions.assertThat @@ -15,7 +17,7 @@ import kotlin.test.Test class TrainIdentificationEntityIntegrationTest : BaseTestcontainersTest() { @Autowired - lateinit var trainIdentificationRepository: TrainIdentificationRepository + lateinit var trainIdentificationRepository: SpringDataJpaTrainIdentificationRepository @Autowired lateinit var em: TestEntityManager From fbde3aa257fb8d3889a707f3efcc10f53e80805c Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Fri, 20 Dec 2024 14:47:20 +0100 Subject: [PATCH 10/13] logging ddd --- .../configuration => common}/OpenApiConfig.kt | 2 +- .../{application => common}/RequestLogger.kt | 3 +- .../RequestLoggingFilter.kt | 2 +- .../WebSecurityConfig.kt | 5 +- .../ch/sbb/backend/domain/logging/LogEntry.kt | 22 -------- .../ch/sbb/backend/domain/logging/LogLevel.kt | 10 ---- .../sbb/backend/domain/logging/LogService.kt | 5 -- .../domain/logging/MultiTenantLogService.kt | 43 --------------- .../backend/domain/tenancy/TenantService.kt | 8 --- .../configuration/LogDestination.kt | 5 -- .../application/TenantContext.kt | 4 +- .../application/rest/LogEntryRequest.kt | 10 +++- .../application/rest/LogLevelRequest.kt | 2 +- .../application/rest/LoggingController.kt | 8 +-- .../backend/logging/domain/LogDestination.kt | 5 ++ .../ch/sbb/backend/logging/domain/LogEntry.kt | 11 ++++ .../ch/sbb/backend/logging/domain/LogLevel.kt | 12 ++++ .../domain}/Tenant.kt | 2 +- .../tenancy => logging/domain}/TenantId.kt | 2 +- .../domain/repository/LoggingRepository.kt | 7 +++ .../domain/repository/TenantRepository.kt | 8 +++ .../domain/service/DomainLoggingService.kt | 12 ++++ .../domain/service/DomainTenantService.kt | 14 +++++ .../logging/domain/service/LoggingService.kt | 7 +++ .../logging/domain/service/TenantService.kt | 8 +++ .../infrastructure/ConfigTenantRepository.kt} | 26 +++++---- .../infrastructure/SplunkLoggingRepository.kt | 13 +++++ .../config/LoggingBeanConfiguration.kt | 24 ++++++++ .../infrastructure/config}/TenantConfig.kt | 3 +- .../config}/TenantJwsKeySelector.kt | 11 ++-- .../infrastructure/rest/SplunkHecClient.kt} | 24 ++++++-- .../infrastructure/rest}/SplunkRequest.kt | 2 +- .../DomainServicePointService.kt | 4 +- ...n.kt => ServicePointsBeanConfiguration.kt} | 6 +- .../ch/sbb/backend/archunit/ArchUnitTest.kt | 14 ----- .../kotlin/ch/sbb/backend/ddd/ArchUnitTest.kt | 55 +++++++++++++++++++ .../ch/sbb/backend/ddd/ModularityTests.kt | 15 +++++ .../rest => logging}/LoggingControllerTest.kt | 19 ++++--- .../TenantConfigTest.kt | 7 +-- .../DomainServicePointServiceTest.kt | 4 +- 40 files changed, 276 insertions(+), 168 deletions(-) rename backend/src/main/kotlin/ch/sbb/backend/{infrastructure/configuration => common}/OpenApiConfig.kt (98%) rename backend/src/main/kotlin/ch/sbb/backend/{application => common}/RequestLogger.kt (97%) rename backend/src/main/kotlin/ch/sbb/backend/{application => common}/RequestLoggingFilter.kt (95%) rename backend/src/main/kotlin/ch/sbb/backend/{infrastructure/configuration => common}/WebSecurityConfig.kt (97%) delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogEntry.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogLevel.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogService.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/domain/logging/MultiTenantLogService.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantService.kt delete mode 100644 backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/LogDestination.kt rename backend/src/main/kotlin/ch/sbb/backend/{ => logging}/application/TenantContext.kt (85%) rename backend/src/main/kotlin/ch/sbb/backend/{ => logging}/application/rest/LogEntryRequest.kt (77%) rename backend/src/main/kotlin/ch/sbb/backend/{ => logging}/application/rest/LogLevelRequest.kt (67%) rename backend/src/main/kotlin/ch/sbb/backend/{ => logging}/application/rest/LoggingController.kt (86%) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogDestination.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogEntry.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogLevel.kt rename backend/src/main/kotlin/ch/sbb/backend/{infrastructure/configuration => logging/domain}/Tenant.kt (75%) rename backend/src/main/kotlin/ch/sbb/backend/{domain/tenancy => logging/domain}/TenantId.kt (83%) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/LoggingRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/TenantRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainLoggingService.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainTenantService.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/LoggingService.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/TenantService.kt rename backend/src/main/kotlin/ch/sbb/backend/{domain/tenancy/ConfigTenantService.kt => logging/infrastructure/ConfigTenantRepository.kt} (52%) create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/SplunkLoggingRepository.kt create mode 100644 backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/LoggingBeanConfiguration.kt rename backend/src/main/kotlin/ch/sbb/backend/{infrastructure/configuration => logging/infrastructure/config}/TenantConfig.kt (78%) rename backend/src/main/kotlin/ch/sbb/backend/{infrastructure/configuration => logging/infrastructure/config}/TenantJwsKeySelector.kt (84%) rename backend/src/main/kotlin/ch/sbb/backend/{domain/logging/SplunkLogService.kt => logging/infrastructure/rest/SplunkHecClient.kt} (60%) rename backend/src/main/kotlin/ch/sbb/backend/{domain/logging => logging/infrastructure/rest}/SplunkRequest.kt (90%) rename backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/{repository => service}/DomainServicePointService.kt (83%) rename backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/{BeanConfiguration.kt => ServicePointsBeanConfiguration.kt} (66%) delete mode 100644 backend/src/test/kotlin/ch/sbb/backend/archunit/ArchUnitTest.kt create mode 100644 backend/src/test/kotlin/ch/sbb/backend/ddd/ArchUnitTest.kt create mode 100644 backend/src/test/kotlin/ch/sbb/backend/ddd/ModularityTests.kt rename backend/src/test/kotlin/ch/sbb/backend/{application/rest => logging}/LoggingControllerTest.kt (90%) rename backend/src/test/kotlin/ch/sbb/backend/{infrastructure/configuration => logging}/TenantConfigTest.kt (84%) diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/OpenApiConfig.kt b/backend/src/main/kotlin/ch/sbb/backend/common/OpenApiConfig.kt similarity index 98% rename from backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/OpenApiConfig.kt rename to backend/src/main/kotlin/ch/sbb/backend/common/OpenApiConfig.kt index 8ff59abc..c8879b8c 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/OpenApiConfig.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/common/OpenApiConfig.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.infrastructure.configuration +package ch.sbb.backend.common import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.OpenAPI diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/RequestLogger.kt b/backend/src/main/kotlin/ch/sbb/backend/common/RequestLogger.kt similarity index 97% rename from backend/src/main/kotlin/ch/sbb/backend/application/RequestLogger.kt rename to backend/src/main/kotlin/ch/sbb/backend/common/RequestLogger.kt index 6904f12b..e065094f 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/RequestLogger.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/common/RequestLogger.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.application +package ch.sbb.backend.common import jakarta.servlet.http.HttpServletRequest import org.slf4j.Logger @@ -7,7 +7,6 @@ import org.slf4j.event.Level import org.springframework.http.HttpHeaders import org.springframework.util.StopWatch - class RequestLogger( private val stopWatch: StopWatch, private val level: Level diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/RequestLoggingFilter.kt b/backend/src/main/kotlin/ch/sbb/backend/common/RequestLoggingFilter.kt similarity index 95% rename from backend/src/main/kotlin/ch/sbb/backend/application/RequestLoggingFilter.kt rename to backend/src/main/kotlin/ch/sbb/backend/common/RequestLoggingFilter.kt index 1ed0e2ed..c693897d 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/RequestLoggingFilter.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/common/RequestLoggingFilter.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.application +package ch.sbb.backend.common import jakarta.servlet.Filter import jakarta.servlet.FilterChain diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/WebSecurityConfig.kt b/backend/src/main/kotlin/ch/sbb/backend/common/WebSecurityConfig.kt similarity index 97% rename from backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/WebSecurityConfig.kt rename to backend/src/main/kotlin/ch/sbb/backend/common/WebSecurityConfig.kt index aa0a5671..a2f9f002 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/WebSecurityConfig.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/common/WebSecurityConfig.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.infrastructure.configuration +package ch.sbb.backend.common import com.nimbusds.jose.proc.SecurityContext import com.nimbusds.jwt.proc.DefaultJWTProcessor @@ -17,7 +17,6 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAut import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter import org.springframework.security.web.SecurityFilterChain - @Configuration @EnableWebSecurity class WebSecurityConfig { @@ -33,7 +32,7 @@ class WebSecurityConfig { @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { http { - authorizeRequests { + authorizeHttpRequests { authorize("/swagger-ui/**", permitAll) authorize("/v3/api-docs/**", permitAll) authorize("/actuator/health/**", permitAll) diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogEntry.kt b/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogEntry.kt deleted file mode 100644 index e663543e..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogEntry.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ch.sbb.backend.domain.logging - -import java.time.OffsetDateTime - -data class LogEntry( - private val time: OffsetDateTime, - private val source: String, - private val message: String, - private val level: LogLevel, - private val metadata: Map? = emptyMap() -) { - fun toSplunkRequest(): SplunkRequest { - val fields = metadata?.toMutableMap() ?: mutableMapOf() - fields["level"] = level.name - return SplunkRequest( - event = message, - fields = fields, - source = source, - time = time, - ) - } -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogLevel.kt b/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogLevel.kt deleted file mode 100644 index cda070f5..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogLevel.kt +++ /dev/null @@ -1,10 +0,0 @@ -package ch.sbb.backend.domain.logging - -enum class LogLevel { - TRACE, - DEBUG, - INFO, - WARNING, - ERROR, - FATAL -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogService.kt b/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogService.kt deleted file mode 100644 index 525fc4d7..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/LogService.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ch.sbb.backend.domain.logging - -interface LogService { - fun logs(logEntries: List) -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/MultiTenantLogService.kt b/backend/src/main/kotlin/ch/sbb/backend/domain/logging/MultiTenantLogService.kt deleted file mode 100644 index 5ec9b437..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/MultiTenantLogService.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ch.sbb.backend.domain.logging - -import ch.sbb.backend.application.TenantContext -import ch.sbb.backend.application.rest.LogEntryRequest -import ch.sbb.backend.application.rest.LogLevelRequest -import ch.sbb.backend.domain.tenancy.ConfigTenantService -import ch.sbb.backend.domain.tenancy.TenantId -import ch.sbb.backend.infrastructure.configuration.LogDestination -import org.springframework.stereotype.Service - -@Service -class MultitenantLogService( - private val tenantService: ConfigTenantService, - private val splunkLogService: SplunkLogService -) { - - fun logs(logs: List) { - getLogService(TenantContext.current().tenantId).logs(logs.map { - LogEntry( - it.time, it.source, it.message, level(it.level), it.metadata - ) - }) - } - - private fun level(level: LogLevelRequest): LogLevel { - return when (level) { - LogLevelRequest.TRACE -> LogLevel.TRACE - LogLevelRequest.DEBUG -> LogLevel.DEBUG - LogLevelRequest.INFO -> LogLevel.INFO - LogLevelRequest.WARNING -> LogLevel.WARNING - LogLevelRequest.ERROR -> LogLevel.ERROR - LogLevelRequest.FATAL -> LogLevel.FATAL - } - } - - private fun getLogService(tenantId: TenantId): LogService { - val logDestination = tenantService.getById(tenantId).logDestination - return when (logDestination) { - LogDestination.SPLUNK -> splunkLogService - } - } -} - diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantService.kt b/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantService.kt deleted file mode 100644 index ac507d29..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantService.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ch.sbb.backend.domain.tenancy - -import ch.sbb.backend.infrastructure.configuration.Tenant - -interface TenantService { - fun getByIssuerUri(issuerUri: String): Tenant - fun getById(tenantId: TenantId): Tenant -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/LogDestination.kt b/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/LogDestination.kt deleted file mode 100644 index 0fbf5119..00000000 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/LogDestination.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ch.sbb.backend.infrastructure.configuration - -enum class LogDestination { - SPLUNK -} diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/TenantContext.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/application/TenantContext.kt similarity index 85% rename from backend/src/main/kotlin/ch/sbb/backend/application/TenantContext.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/application/TenantContext.kt index 28d88725..90785803 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/TenantContext.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/application/TenantContext.kt @@ -1,6 +1,6 @@ -package ch.sbb.backend.application +package ch.sbb.backend.logging.application -import ch.sbb.backend.domain.tenancy.TenantId +import ch.sbb.backend.logging.domain.TenantId import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.oauth2.jwt.Jwt diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/LogEntryRequest.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LogEntryRequest.kt similarity index 77% rename from backend/src/main/kotlin/ch/sbb/backend/application/rest/LogEntryRequest.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LogEntryRequest.kt index 4bf0e7d8..b662da43 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/LogEntryRequest.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LogEntryRequest.kt @@ -1,5 +1,7 @@ -package ch.sbb.backend.application.rest +package ch.sbb.backend.logging.application.rest +import ch.sbb.backend.logging.domain.LogEntry +import ch.sbb.backend.logging.domain.LogLevel import io.swagger.v3.oas.annotations.media.Schema import java.time.OffsetDateTime @@ -30,4 +32,8 @@ data class LogEntryRequest( example = "{\"deviceId\": \"abcde123\", \"appVersion\": \"1.2.0\"}" ) val metadata: Map? = emptyMap() -) +) { + fun toLogEntry(): LogEntry { + return LogEntry(time, source, message, LogLevel(level.name), metadata) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/LogLevelRequest.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LogLevelRequest.kt similarity index 67% rename from backend/src/main/kotlin/ch/sbb/backend/application/rest/LogLevelRequest.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LogLevelRequest.kt index 5da22869..94506144 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/LogLevelRequest.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LogLevelRequest.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.application.rest +package ch.sbb.backend.logging.application.rest enum class LogLevelRequest { TRACE, diff --git a/backend/src/main/kotlin/ch/sbb/backend/application/rest/LoggingController.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LoggingController.kt similarity index 86% rename from backend/src/main/kotlin/ch/sbb/backend/application/rest/LoggingController.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LoggingController.kt index d5226e26..ac8cab63 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/application/rest/LoggingController.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/application/rest/LoggingController.kt @@ -1,6 +1,6 @@ -package ch.sbb.backend.application.rest +package ch.sbb.backend.logging.application.rest -import ch.sbb.backend.domain.logging.MultitenantLogService +import ch.sbb.backend.logging.domain.service.LoggingService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema @@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("api/v1/logging") @Tag(name = "Logging", description = "API for logging") -class LoggingController(private val multitenantLogService: MultitenantLogService) { +class LoggingController(private val loggingService: LoggingService) { @Operation(summary = "Log messages from clients") @ApiResponse(responseCode = "200", description = "Logs successfully saved") @@ -39,6 +39,6 @@ class LoggingController(private val multitenantLogService: MultitenantLogService ) @PostMapping("/logs", consumes = ["application/json"]) fun logs(@RequestBody logs: List) { - multitenantLogService.logs(logs) + loggingService.saveAll(logs.map { it.toLogEntry() }) } } diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogDestination.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogDestination.kt new file mode 100644 index 00000000..667c6886 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogDestination.kt @@ -0,0 +1,5 @@ +package ch.sbb.backend.logging.domain + +enum class LogDestination { + SPLUNK +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogEntry.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogEntry.kt new file mode 100644 index 00000000..8a85c5ab --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogEntry.kt @@ -0,0 +1,11 @@ +package ch.sbb.backend.logging.domain + +import java.time.OffsetDateTime + +data class LogEntry( + val time: OffsetDateTime, + val source: String, + val message: String, + val level: LogLevel, + val metadata: Map? = emptyMap() +) diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogLevel.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogLevel.kt new file mode 100644 index 00000000..3c2cd97c --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/LogLevel.kt @@ -0,0 +1,12 @@ +package ch.sbb.backend.logging.domain + +data class LogLevel(val value: String) { + companion object { + val TRACE = LogLevel("TRACE") + val DEBUG = LogLevel("DEBUG") + val INFO = LogLevel("INFO") + val WARNING = LogLevel("WARNING") + val ERROR = LogLevel("ERROR") + val FATAL = LogLevel("FATAL") + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/Tenant.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/Tenant.kt similarity index 75% rename from backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/Tenant.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/domain/Tenant.kt index d6cdb54a..41fed3cd 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/Tenant.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/Tenant.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.infrastructure.configuration +package ch.sbb.backend.logging.domain data class Tenant( var name: String, diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantId.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/TenantId.kt similarity index 83% rename from backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantId.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/domain/TenantId.kt index 8edaa0e7..b8f12f48 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/TenantId.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/TenantId.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.domain.tenancy +package ch.sbb.backend.logging.domain import java.util.* diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/LoggingRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/LoggingRepository.kt new file mode 100644 index 00000000..3041a63c --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/LoggingRepository.kt @@ -0,0 +1,7 @@ +package ch.sbb.backend.logging.domain.repository + +import ch.sbb.backend.logging.domain.LogEntry + +interface LoggingRepository { + fun saveAll(logs: List) +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/TenantRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/TenantRepository.kt new file mode 100644 index 00000000..cdfe8792 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/repository/TenantRepository.kt @@ -0,0 +1,8 @@ +package ch.sbb.backend.logging.domain.repository + +import ch.sbb.backend.logging.domain.Tenant + +interface TenantRepository { + fun current(): Tenant + fun getByIssuerUri(issuerUri: String): Tenant +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainLoggingService.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainLoggingService.kt new file mode 100644 index 00000000..0310939d --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainLoggingService.kt @@ -0,0 +1,12 @@ +package ch.sbb.backend.logging.domain.service + +import ch.sbb.backend.logging.domain.LogEntry +import ch.sbb.backend.logging.domain.repository.LoggingRepository + +class DomainLoggingService( + private val loggingRepository: LoggingRepository +) : LoggingService { + override fun saveAll(logEntries: List) { + loggingRepository.saveAll(logEntries) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainTenantService.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainTenantService.kt new file mode 100644 index 00000000..7da0ff01 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/DomainTenantService.kt @@ -0,0 +1,14 @@ +package ch.sbb.backend.logging.domain.service + +import ch.sbb.backend.logging.domain.Tenant +import ch.sbb.backend.logging.domain.repository.TenantRepository + +class DomainTenantService(private val tenantRepository: TenantRepository) : TenantService { + override fun getByIssuerUri(issuerUri: String): Tenant { + return tenantRepository.getByIssuerUri(issuerUri) + } + + override fun current(): Tenant { + return tenantRepository.current() + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/LoggingService.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/LoggingService.kt new file mode 100644 index 00000000..8575852e --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/LoggingService.kt @@ -0,0 +1,7 @@ +package ch.sbb.backend.logging.domain.service + +import ch.sbb.backend.logging.domain.LogEntry + +interface LoggingService { + fun saveAll(logEntries: List) +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/TenantService.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/TenantService.kt new file mode 100644 index 00000000..9a53c0b3 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/domain/service/TenantService.kt @@ -0,0 +1,8 @@ +package ch.sbb.backend.logging.domain.service + +import ch.sbb.backend.logging.domain.Tenant + +interface TenantService { + fun getByIssuerUri(issuerUri: String): Tenant + fun current(): Tenant +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/ConfigTenantService.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/ConfigTenantRepository.kt similarity index 52% rename from backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/ConfigTenantService.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/ConfigTenantRepository.kt index 15f1a061..b8890a91 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/tenancy/ConfigTenantService.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/ConfigTenantRepository.kt @@ -1,18 +1,22 @@ -package ch.sbb.backend.domain.tenancy +package ch.sbb.backend.logging.infrastructure -import ch.sbb.backend.infrastructure.configuration.Tenant -import ch.sbb.backend.infrastructure.configuration.TenantConfig +import ch.sbb.backend.logging.application.TenantContext +import ch.sbb.backend.logging.domain.Tenant +import ch.sbb.backend.logging.domain.TenantId +import ch.sbb.backend.logging.domain.repository.TenantRepository +import ch.sbb.backend.logging.infrastructure.config.TenantConfig import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger -import org.springframework.stereotype.Service +import org.springframework.stereotype.Component -/** - * Service providing tenant information based on the iss claim of the JWT token. - */ -@Service -class ConfigTenantService(private val tenantConfig: TenantConfig) : TenantService { +@Component +class ConfigTenantRepository(private val tenantConfig: TenantConfig) : TenantRepository { - private val logger: Logger = LogManager.getLogger(ConfigTenantService::class.java) + private val logger: Logger = LogManager.getLogger(ConfigTenantRepository::class.java) + + override fun current(): Tenant { + return getById(TenantContext.current().tenantId) + } override fun getByIssuerUri(issuerUri: String): Tenant { val tenant: Tenant = @@ -25,7 +29,7 @@ class ConfigTenantService(private val tenantConfig: TenantConfig) : TenantServic return tenant } - override fun getById(tenantId: TenantId): Tenant { + private fun getById(tenantId: TenantId): Tenant { return tenantConfig.tenants.stream().filter { t -> tenantId == TenantId(t.id) } .findAny() .orElseThrow { IllegalArgumentException("unknown tenant") } diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/SplunkLoggingRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/SplunkLoggingRepository.kt new file mode 100644 index 00000000..8d0d11d5 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/SplunkLoggingRepository.kt @@ -0,0 +1,13 @@ +package ch.sbb.backend.logging.infrastructure + +import ch.sbb.backend.logging.domain.LogEntry +import ch.sbb.backend.logging.domain.repository.LoggingRepository +import ch.sbb.backend.logging.infrastructure.rest.SplunkHecClient +import org.springframework.stereotype.Component + +@Component +class SplunkLoggingRepository(private val splunkHecClient: SplunkHecClient) : LoggingRepository { + override fun saveAll(logs: List) { + splunkHecClient.sendLogs(logs) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/LoggingBeanConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/LoggingBeanConfiguration.kt new file mode 100644 index 00000000..534aa6c7 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/LoggingBeanConfiguration.kt @@ -0,0 +1,24 @@ +package ch.sbb.backend.logging.infrastructure.config + +import ch.sbb.backend.logging.domain.repository.LoggingRepository +import ch.sbb.backend.logging.domain.repository.TenantRepository +import ch.sbb.backend.logging.domain.service.DomainLoggingService +import ch.sbb.backend.logging.domain.service.DomainTenantService +import ch.sbb.backend.logging.domain.service.LoggingService +import ch.sbb.backend.logging.domain.service.TenantService +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class LoggingBeanConfiguration { + + @Bean + fun logService(loggingRepository: LoggingRepository): LoggingService { + return DomainLoggingService(loggingRepository) + } + + @Bean + fun teanantService(tenantRepository: TenantRepository): TenantService { + return DomainTenantService(tenantRepository) + } +} diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/TenantConfig.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/TenantConfig.kt similarity index 78% rename from backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/TenantConfig.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/TenantConfig.kt index ce70f27f..e17a6e2b 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/TenantConfig.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/TenantConfig.kt @@ -1,5 +1,6 @@ -package ch.sbb.backend.infrastructure.configuration +package ch.sbb.backend.logging.infrastructure.config +import ch.sbb.backend.logging.domain.Tenant import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Configuration diff --git a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/TenantJwsKeySelector.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/TenantJwsKeySelector.kt similarity index 84% rename from backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/TenantJwsKeySelector.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/TenantJwsKeySelector.kt index 3d769e84..de120e68 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/infrastructure/configuration/TenantJwsKeySelector.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/config/TenantJwsKeySelector.kt @@ -1,6 +1,7 @@ -package ch.sbb.backend.infrastructure.configuration +package ch.sbb.backend.logging.infrastructure.config -import ch.sbb.backend.domain.tenancy.ConfigTenantService +import ch.sbb.backend.logging.domain.Tenant +import ch.sbb.backend.logging.domain.repository.TenantRepository import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector import com.nimbusds.jose.proc.JWSKeySelector @@ -19,7 +20,7 @@ import java.util.concurrent.ConcurrentHashMap * For a more detailed description see [Spring Security Documentation](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/multitenancy.html#_parsing_the_claim_only_once). */ @Component -class TenantJWSKeySelector(private val tenantService: ConfigTenantService) : +class TenantJWSKeySelector(private val tenantRepository: TenantRepository) : JWTClaimsSetAwareJWSKeySelector { private val selectors: MutableMap> = ConcurrentHashMap() @@ -34,8 +35,8 @@ class TenantJWSKeySelector(private val tenantService: ConfigTenantService) : } private fun fromTenant(issuerUri: String): JWSKeySelector { - val tenant: Tenant = tenantService.getByIssuerUri(issuerUri) - return fromUri(tenant.jwkSetUri!!) + val tenant: Tenant = tenantRepository.getByIssuerUri(issuerUri) + return fromUri(tenant.jwkSetUri) } private fun fromUri(uri: String): JWSKeySelector { diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/SplunkLogService.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/rest/SplunkHecClient.kt similarity index 60% rename from backend/src/main/kotlin/ch/sbb/backend/domain/logging/SplunkLogService.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/rest/SplunkHecClient.kt index b1ed99cc..842b9f66 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/SplunkLogService.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/rest/SplunkHecClient.kt @@ -1,5 +1,6 @@ -package ch.sbb.backend.domain.logging +package ch.sbb.backend.logging.infrastructure.rest +import ch.sbb.backend.logging.domain.LogEntry import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpStatus @@ -9,17 +10,17 @@ import org.springframework.web.reactive.function.client.WebClientResponseExcepti import org.springframework.web.server.ResponseStatusException @Service -class SplunkLogService( +class SplunkHecClient( @Value("\${splunk.url}") private val url: String, @Value("\${splunk.token}") private val token: String -) : LogService { - private val log = LoggerFactory.getLogger(SplunkLogService::class.java) +) { + private val log = LoggerFactory.getLogger(SplunkHecClient::class.java) private val webClient: WebClient = WebClient.create(url) - override fun logs(logEntries: List) { + fun sendLogs(logEntries: List) { webClient.post() .headers { it["Authorization"] = "Splunk $token" } - .bodyValue(logEntries.map { it.toSplunkRequest() }) + .bodyValue(logEntries.map { mapToRequest(it) }) .retrieve() .bodyToMono(Object::class.java) .doOnError(WebClientResponseException::class.java) { @@ -28,4 +29,15 @@ class SplunkLogService( } .block() } + + private fun mapToRequest(logEntry: LogEntry): SplunkRequest { + val fields = logEntry.metadata?.toMutableMap() ?: mutableMapOf() + fields["level"] = logEntry.level.value + return SplunkRequest( + event = logEntry.message, + fields = fields, + source = logEntry.source, + time = logEntry.time, + ) + } } diff --git a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/SplunkRequest.kt b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/rest/SplunkRequest.kt similarity index 90% rename from backend/src/main/kotlin/ch/sbb/backend/domain/logging/SplunkRequest.kt rename to backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/rest/SplunkRequest.kt index dfc0332c..640103a3 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/domain/logging/SplunkRequest.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/logging/infrastructure/rest/SplunkRequest.kt @@ -1,4 +1,4 @@ -package ch.sbb.backend.domain.logging +package ch.sbb.backend.logging.infrastructure.rest import com.fasterxml.jackson.annotation.JsonInclude import java.time.OffsetDateTime diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/DomainServicePointService.kt similarity index 83% rename from backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt rename to backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/DomainServicePointService.kt index 2aa334c3..20fa6058 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointService.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/DomainServicePointService.kt @@ -1,7 +1,7 @@ -package ch.sbb.backend.servicepoints.domain.repository +package ch.sbb.backend.servicepoints.domain.service import ch.sbb.backend.servicepoints.domain.ServicePoint -import ch.sbb.backend.servicepoints.domain.service.ServicePointService +import ch.sbb.backend.servicepoints.domain.repository.ServicePointRepository class DomainServicePointService(private val servicePointRepository: ServicePointRepository) : ServicePointService { diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/ServicePointsBeanConfiguration.kt similarity index 66% rename from backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt rename to backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/ServicePointsBeanConfiguration.kt index e6a302d7..73d5f2d2 100644 --- a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/BeanConfiguration.kt +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/ServicePointsBeanConfiguration.kt @@ -1,16 +1,16 @@ package ch.sbb.backend.servicepoints.infrastructure.configuration -import ch.sbb.backend.servicepoints.domain.repository.DomainServicePointService +import ch.sbb.backend.servicepoints.domain.service.DomainServicePointService import ch.sbb.backend.servicepoints.domain.repository.ServicePointRepository import ch.sbb.backend.servicepoints.domain.service.ServicePointService import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration -class BeanConfiguration { +class ServicePointsBeanConfiguration { @Bean - fun orderService(servicePointRepository: ServicePointRepository): ServicePointService { + fun servicePointService(servicePointRepository: ServicePointRepository): ServicePointService { return DomainServicePointService(servicePointRepository) } diff --git a/backend/src/test/kotlin/ch/sbb/backend/archunit/ArchUnitTest.kt b/backend/src/test/kotlin/ch/sbb/backend/archunit/ArchUnitTest.kt deleted file mode 100644 index 65f29603..00000000 --- a/backend/src/test/kotlin/ch/sbb/backend/archunit/ArchUnitTest.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ch.sbb.backend.archunit - -import com.tngtech.archunit.junit.AnalyzeClasses -import com.tngtech.archunit.junit.ArchTest -import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes - -@AnalyzeClasses(packages = ["ch.sbb.backend"]) -class ArchUnitTest { - - @ArchTest - val `controller classes should be in application` = classes() - .that().haveSimpleNameEndingWith("Controller") - .should().resideInAPackage("..application..") -} diff --git a/backend/src/test/kotlin/ch/sbb/backend/ddd/ArchUnitTest.kt b/backend/src/test/kotlin/ch/sbb/backend/ddd/ArchUnitTest.kt new file mode 100644 index 00000000..d9b5cedf --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/ddd/ArchUnitTest.kt @@ -0,0 +1,55 @@ +package ch.sbb.backend.ddd + +import com.tngtech.archunit.core.importer.ImportOption +import com.tngtech.archunit.junit.AnalyzeClasses +import com.tngtech.archunit.junit.ArchTest +import com.tngtech.archunit.lang.ArchRule +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses +import com.tngtech.archunit.library.Architectures.layeredArchitecture +import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices + + +@AnalyzeClasses( + packages = ["ch.sbb.backend"], + importOptions = [ImportOption.DoNotIncludeTests::class] +) +class ArchUnitTest { + + @ArchTest + val CORRECT_LAYERED_ARCHITECTURE: ArchRule = layeredArchitecture() + .consideringAllDependencies() + .layer("infrastructure").definedBy("..infrastructure..") + .layer("application").definedBy("..application..") + .layer("domain").definedBy("..domain..") + .whereLayer("infrastructure").mayOnlyBeAccessedByLayers("infrastructure") + .whereLayer("application").mayOnlyBeAccessedByLayers("infrastructure") + .whereLayer("domain").mayOnlyBeAccessedByLayers("infrastructure", "application") + + @ArchTest + val APPLICATION_FREE_OF_CYCLES: ArchRule = slices() + .matching("..application.(**)") + .should().beFreeOfCycles() + + @ArchTest + val DOMAIN_FREE_OF_CYCLES: ArchRule = slices() + .matching("..domain.(**)") + .should().beFreeOfCycles() + + @ArchTest + val INFRASTRUCTURE_FREE_OF_CYCLES: ArchRule = slices() + .matching("..infrastructure.(**)") + .should().beFreeOfCycles() + + @ArchTest + val NO_FRAMEWORK_CODE_IN_DOMAIN: ArchRule = noClasses() + .that().resideInAPackage("..domain..") + .should().dependOnClassesThat().resideOutsideOfPackages( + "ch.sbb..", + "java..", + "javax..", + "org.slf4j..", + "kotlin..", + "org.jetbrains.annotations.." + ) + .because("our domain core should be independent of frameworks") +} diff --git a/backend/src/test/kotlin/ch/sbb/backend/ddd/ModularityTests.kt b/backend/src/test/kotlin/ch/sbb/backend/ddd/ModularityTests.kt new file mode 100644 index 00000000..27f11a0f --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/ddd/ModularityTests.kt @@ -0,0 +1,15 @@ +package ch.sbb.backend.ddd + +import ch.sbb.backend.BackendApplication +import org.junit.jupiter.api.Test +import org.springframework.modulith.core.ApplicationModules + +class ModularityTests { + + @Test + fun verifiesModularStructure() { + val modules: ApplicationModules = ApplicationModules.of(BackendApplication::class.java) + modules.forEach { println(it) } + modules.verify() + } +} diff --git a/backend/src/test/kotlin/ch/sbb/backend/application/rest/LoggingControllerTest.kt b/backend/src/test/kotlin/ch/sbb/backend/logging/LoggingControllerTest.kt similarity index 90% rename from backend/src/test/kotlin/ch/sbb/backend/application/rest/LoggingControllerTest.kt rename to backend/src/test/kotlin/ch/sbb/backend/logging/LoggingControllerTest.kt index 952a28c4..34b9a5eb 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/application/rest/LoggingControllerTest.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/logging/LoggingControllerTest.kt @@ -1,22 +1,23 @@ -package ch.sbb.backend.application.rest +package ch.sbb.backend.logging -import ch.sbb.backend.domain.logging.LogEntry -import ch.sbb.backend.domain.logging.LogLevel -import ch.sbb.backend.domain.logging.SplunkLogService +import ch.sbb.backend.BaseIT +import ch.sbb.backend.logging.domain.LogEntry +import ch.sbb.backend.logging.domain.LogLevel +import ch.sbb.backend.logging.infrastructure.rest.SplunkHecClient import org.junit.jupiter.api.Test import org.mockito.Mockito.verify -import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt +import org.springframework.test.context.bean.override.mockito.MockitoBean import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import java.time.OffsetDateTime class LoggingControllerTest : BaseIT() { - @MockBean - private lateinit var splunkLogService: SplunkLogService + @MockitoBean + private lateinit var splunkHecClient: SplunkHecClient @Test fun `should log messages with seconds since epoch timestamp`() { @@ -59,7 +60,7 @@ class LoggingControllerTest : BaseIT() { ) ) - verify(splunkLogService).logs(expectedLogs) + verify(splunkHecClient).sendLogs(expectedLogs) } @Test @@ -120,7 +121,7 @@ class LoggingControllerTest : BaseIT() { ) ) - verify(splunkLogService).logs(expectedLogs) + verify(splunkHecClient).sendLogs(expectedLogs) } @Test diff --git a/backend/src/test/kotlin/ch/sbb/backend/infrastructure/configuration/TenantConfigTest.kt b/backend/src/test/kotlin/ch/sbb/backend/logging/TenantConfigTest.kt similarity index 84% rename from backend/src/test/kotlin/ch/sbb/backend/infrastructure/configuration/TenantConfigTest.kt rename to backend/src/test/kotlin/ch/sbb/backend/logging/TenantConfigTest.kt index d55211c0..ee1acce7 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/infrastructure/configuration/TenantConfigTest.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/logging/TenantConfigTest.kt @@ -1,12 +1,11 @@ -package ch.sbb.backend.infrastructure.configuration +package ch.sbb.backend.logging -import ch.sbb.backend.application.rest.BaseIT +import ch.sbb.backend.BaseIT +import ch.sbb.backend.logging.infrastructure.config.TenantConfig import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -@SpringBootTest class TenantConfigTest: BaseIT() { @Autowired diff --git a/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt index 102c39ba..c0480f32 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt @@ -1,9 +1,11 @@ package ch.sbb.backend.servicepoints.domain.repository import ch.sbb.backend.servicepoints.domain.ServicePoint +import ch.sbb.backend.servicepoints.domain.service.DomainServicePointService import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.mockito.Mockito.* +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify class DomainServicePointServiceTest { From 981fad4a885e6fc9a62868d7f308e08ab1784830 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Mon, 6 Jan 2025 13:54:57 +0100 Subject: [PATCH 11/13] ci: fix build --- .github/workflows/ci-backend.yml | 22 ++++++---------------- .github/workflows/ci-sfera-mock.yml | 5 ----- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index e87981ec..07354a40 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -7,30 +7,25 @@ # documentation. name: Backend Java CI with Maven - + +defaults: + run: + working-directory: backend + on: push: paths: - 'backend/**' - - 'preload/**' branches: [ "main" ] pull_request: paths: - 'backend/**' - - 'preload/**' branches: [ "main" ] jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - directory: ['backend', 'preload'] - - defaults: - run: - working-directory: ${{ matrix.directory }} steps: - uses: actions/checkout@v4 @@ -48,7 +43,7 @@ jobs: - name: Create container image if: github.ref == 'refs/heads/main' env: - IMAGE_ID: ghcr.io/${{ github.repository }}/${{ matrix.directory }} + IMAGE_ID: ghcr.io/${{ github.repository }}/backend VERSION: main run: | # Convert to lowercase @@ -59,8 +54,3 @@ jobs: -Dspring-boot.build-image.imageName=$IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph - uses: advanced-security/maven-dependency-submission-action@v4 - with: - directory: ${{ matrix.directory }} diff --git a/.github/workflows/ci-sfera-mock.yml b/.github/workflows/ci-sfera-mock.yml index dc7f25ba..4102012c 100644 --- a/.github/workflows/ci-sfera-mock.yml +++ b/.github/workflows/ci-sfera-mock.yml @@ -54,8 +54,3 @@ jobs: -Dspring-boot.build-image.imageName=$IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph - uses: advanced-security/maven-dependency-submission-action@v4 - with: - directory: sfera-mock From 3b3cc93cda23d9b6a302fccd37b0f9e42d5c3127 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Mon, 6 Jan 2025 14:10:58 +0100 Subject: [PATCH 12/13] fix: logging tests --- .../DomainLoggingServiceTest.kt} | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) rename backend/src/test/kotlin/ch/sbb/backend/{domain/logging/MultitenantLogServiceTest.kt => logging/DomainLoggingServiceTest.kt} (53%) diff --git a/backend/src/test/kotlin/ch/sbb/backend/domain/logging/MultitenantLogServiceTest.kt b/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt similarity index 53% rename from backend/src/test/kotlin/ch/sbb/backend/domain/logging/MultitenantLogServiceTest.kt rename to backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt index 324bfe67..70e609ac 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/domain/logging/MultitenantLogServiceTest.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt @@ -1,11 +1,12 @@ -package ch.sbb.backend.domain.logging +package ch.sbb.backend.logging -import ch.sbb.backend.application.rest.LogEntryRequest -import ch.sbb.backend.application.rest.LogLevelRequest -import ch.sbb.backend.domain.tenancy.ConfigTenantService -import ch.sbb.backend.domain.tenancy.TenantId -import ch.sbb.backend.infrastructure.configuration.LogDestination -import ch.sbb.backend.infrastructure.configuration.Tenant +import ch.sbb.backend.logging.domain.LogDestination +import ch.sbb.backend.logging.domain.LogEntry +import ch.sbb.backend.logging.domain.LogLevel +import ch.sbb.backend.logging.domain.Tenant +import ch.sbb.backend.logging.domain.repository.TenantRepository +import ch.sbb.backend.logging.domain.service.DomainLoggingService +import ch.sbb.backend.logging.infrastructure.SplunkLoggingRepository import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.Mockito.* @@ -17,19 +18,19 @@ import java.time.Instant import java.time.OffsetDateTime import java.util.* -class MultitenantLogServiceTest { +class DomainLoggingServiceTest { - private lateinit var sut: MultitenantLogService - private lateinit var tenantService: ConfigTenantService - private lateinit var splunkLogService: SplunkLogService + private lateinit var sut: DomainLoggingService + private lateinit var tenantRepository: TenantRepository + private lateinit var splunkLoggingRepository: SplunkLoggingRepository private val tid = UUID.randomUUID().toString() @BeforeEach fun setUp() { - tenantService = mock(ConfigTenantService::class.java) - splunkLogService = mock(SplunkLogService::class.java) - sut = MultitenantLogService(tenantService, splunkLogService) + tenantRepository = mock(TenantRepository::class.java) + splunkLoggingRepository = mock(SplunkLoggingRepository::class.java) + sut = DomainLoggingService(splunkLoggingRepository) val securityContext: SecurityContext = mock(SecurityContext::class.java) val jwt = Jwt( "token", @@ -40,26 +41,25 @@ class MultitenantLogServiceTest { ) val authentication = mock(Authentication::class.java) `when`(authentication.principal).thenReturn(jwt) - `when`(securityContext.getAuthentication()).thenReturn(authentication) + `when`(securityContext.authentication).thenReturn(authentication) SecurityContextHolder.setContext(securityContext) } @Test fun `should log messages to splunk`() { - val tenantId = TenantId(tid) val tenantConfig = Tenant("test", "10", "", "", LogDestination.SPLUNK) - `when`(tenantService.getById(tenantId)).thenReturn(tenantConfig) + `when`(tenantRepository.current()).thenReturn(tenantConfig) val timestamp = OffsetDateTime.now() val logEntries = listOf( - LogEntryRequest(timestamp, "source", "message", LogLevelRequest.INFO) + LogEntry(timestamp, "source", "message", LogLevel.INFO) ) - sut.logs(logEntries) + sut.saveAll(logEntries) val expectedLogs = listOf( LogEntry(timestamp, "source", "message", LogLevel.INFO) ) - verify(splunkLogService, times(1)).logs(expectedLogs) + verify(splunkLoggingRepository, times(1)).saveAll(expectedLogs) } } From 6966b0937773c41c52bb2f9d5915ad281691bee4 Mon Sep 17 00:00:00 2001 From: mghilardelli Date: Mon, 6 Jan 2025 15:26:22 +0100 Subject: [PATCH 13/13] chore: clean test --- .../logging/DomainLoggingServiceTest.kt | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt b/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt index 70e609ac..de2029be 100644 --- a/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt +++ b/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt @@ -10,13 +10,7 @@ import ch.sbb.backend.logging.infrastructure.SplunkLoggingRepository import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.Mockito.* -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.jwt.Jwt -import java.time.Instant import java.time.OffsetDateTime -import java.util.* class DomainLoggingServiceTest { @@ -24,25 +18,11 @@ class DomainLoggingServiceTest { private lateinit var tenantRepository: TenantRepository private lateinit var splunkLoggingRepository: SplunkLoggingRepository - private val tid = UUID.randomUUID().toString() - @BeforeEach fun setUp() { tenantRepository = mock(TenantRepository::class.java) splunkLoggingRepository = mock(SplunkLoggingRepository::class.java) sut = DomainLoggingService(splunkLoggingRepository) - val securityContext: SecurityContext = mock(SecurityContext::class.java) - val jwt = Jwt( - "token", - Instant.now(), - Instant.now(), - mapOf("header" to "value"), - mapOf("tid" to tid) - ) - val authentication = mock(Authentication::class.java) - `when`(authentication.principal).thenReturn(jwt) - `when`(securityContext.authentication).thenReturn(authentication) - SecurityContextHolder.setContext(securityContext) } @Test