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 diff --git a/backend/pom.xml b/backend/pom.xml index 47d1d810..96a9010b 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/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/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/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/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/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/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 new file mode 100644 index 00000000..4729f466 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/preload/infrastructure/entities/TrainIdentificationEntity.kt @@ -0,0 +1,27 @@ +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 +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 +) { + constructor(trainIdentification: TrainIdentification) : this( + trainIdentification.operationalTrainNumber, + trainIdentification.startDate, + trainIdentification.company, + trainIdentification.startDateTime + ) +} 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/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/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 3b4b7869..eff7cf8c 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. Length: 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/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/DomainServicePointService.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/DomainServicePointService.kt new file mode 100644 index 00000000..20fa6058 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/domain/service/DomainServicePointService.kt @@ -0,0 +1,24 @@ +package ch.sbb.backend.servicepoints.domain.service + +import ch.sbb.backend.servicepoints.domain.ServicePoint +import ch.sbb.backend.servicepoints.domain.repository.ServicePointRepository + +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/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/PostgreSQLConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgreSQLConfiguration.kt new file mode 100644 index 00000000..33c8a247 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/PostgreSQLConfiguration.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 PostgreSQLConfiguration diff --git a/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/ServicePointsBeanConfiguration.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/ServicePointsBeanConfiguration.kt new file mode 100644 index 00000000..73d5f2d2 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/configuration/ServicePointsBeanConfiguration.kt @@ -0,0 +1,17 @@ +package ch.sbb.backend.servicepoints.infrastructure.configuration + +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 ServicePointsBeanConfiguration { + + @Bean + fun servicePointService(servicePointRepository: ServicePointRepository): ServicePointService { + return DomainServicePointService(servicePointRepository) + } + +} 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/PostgreSQLServicePointRepository.kt b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostgreSQLServicePointRepository.kt new file mode 100644 index 00000000..1a96a772 --- /dev/null +++ b/backend/src/main/kotlin/ch/sbb/backend/servicepoints/infrastructure/repositories/PostgreSQLServicePointRepository.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 PostgreSQLServicePointRepository(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/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/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/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/domain/logging/MultitenantLogServiceTest.kt b/backend/src/test/kotlin/ch/sbb/backend/domain/logging/MultitenantLogServiceTest.kt deleted file mode 100644 index 324bfe67..00000000 --- a/backend/src/test/kotlin/ch/sbb/backend/domain/logging/MultitenantLogServiceTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package ch.sbb.backend.domain.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 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 MultitenantLogServiceTest { - - private lateinit var sut: MultitenantLogService - private lateinit var tenantService: ConfigTenantService - private lateinit var splunkLogService: SplunkLogService - - private val tid = UUID.randomUUID().toString() - - @BeforeEach - fun setUp() { - tenantService = mock(ConfigTenantService::class.java) - splunkLogService = mock(SplunkLogService::class.java) - sut = MultitenantLogService(tenantService, splunkLogService) - 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.getAuthentication()).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) - - val timestamp = OffsetDateTime.now() - val logEntries = listOf( - LogEntryRequest(timestamp, "source", "message", LogLevelRequest.INFO) - ) - - sut.logs(logEntries) - - val expectedLogs = listOf( - LogEntry(timestamp, "source", "message", LogLevel.INFO) - ) - verify(splunkLogService, times(1)).logs(expectedLogs) - } -} diff --git a/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt b/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt new file mode 100644 index 00000000..de2029be --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/logging/DomainLoggingServiceTest.kt @@ -0,0 +1,45 @@ +package ch.sbb.backend.logging + +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.* +import java.time.OffsetDateTime + +class DomainLoggingServiceTest { + + private lateinit var sut: DomainLoggingService + private lateinit var tenantRepository: TenantRepository + private lateinit var splunkLoggingRepository: SplunkLoggingRepository + + @BeforeEach + fun setUp() { + tenantRepository = mock(TenantRepository::class.java) + splunkLoggingRepository = mock(SplunkLoggingRepository::class.java) + sut = DomainLoggingService(splunkLoggingRepository) + } + + @Test + fun `should log messages to splunk`() { + val tenantConfig = Tenant("test", "10", "", "", LogDestination.SPLUNK) + `when`(tenantRepository.current()).thenReturn(tenantConfig) + + val timestamp = OffsetDateTime.now() + val logEntries = listOf( + LogEntry(timestamp, "source", "message", LogLevel.INFO) + ) + + sut.saveAll(logEntries) + + val expectedLogs = listOf( + LogEntry(timestamp, "source", "message", LogLevel.INFO) + ) + verify(splunkLoggingRepository, times(1)).saveAll(expectedLogs) + } +} 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/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt b/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt new file mode 100644 index 00000000..17933c5d --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/preload/infrastructure/repositories/TrainIdentificationEntityIntegrationTest.kt @@ -0,0 +1,62 @@ +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 +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: SpringDataJpaTrainIdentificationRepository + + @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/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..c0480f32 --- /dev/null +++ b/backend/src/test/kotlin/ch/sbb/backend/servicepoints/domain/repository/DomainServicePointServiceTest.kt @@ -0,0 +1,30 @@ +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.mock +import org.mockito.Mockito.verify + + +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) -} 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) }