Init
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
package space.nstart.pcp.pcp_stations_service
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class PcpStationsServiceApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<PcpStationsServiceApplication>(*args)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package space.nstart.pcp.pcp_stations_service.configuration
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.validation.FieldError
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.support.WebExchangeBindException
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class CustomValidationException(message: String) : RuntimeException(message)
|
||||
class CustomErrorException(message: String) : RuntimeException(message)
|
||||
|
||||
|
||||
@ControllerAdvice
|
||||
class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(CustomValidationException::class)
|
||||
fun handleValidation(ex: CustomValidationException): ResponseEntity<Map<String, String>> {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(mapOf("error" to ex.message!!))
|
||||
}
|
||||
@ExceptionHandler(CustomErrorException::class)
|
||||
fun handleError(ex: CustomErrorException): ResponseEntity<Map<String, String>> {
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(mapOf("error" to ex.message!!))
|
||||
}
|
||||
@ExceptionHandler(WebExchangeBindException::class)
|
||||
fun handleValidationExceptions(ex: WebExchangeBindException): Mono<ResponseEntity<Map<String, String>>> {
|
||||
val errors = mutableMapOf<String, String>()
|
||||
ex.bindingResult.fieldErrors.forEach { error ->
|
||||
errors[error.field] = error.defaultMessage ?: "Validation error"
|
||||
}
|
||||
return Mono.just(ResponseEntity.badRequest().body(errors))
|
||||
}
|
||||
@ExceptionHandler(MethodArgumentNotValidException::class)
|
||||
fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> {
|
||||
val errors = mutableMapOf<String, String>()
|
||||
ex.bindingResult.allErrors.forEach { error ->
|
||||
val fieldName = (error as FieldError).field
|
||||
val errorMessage = error.defaultMessage
|
||||
errors[fieldName] = errorMessage ?: "Validation error"
|
||||
}
|
||||
return ResponseEntity.badRequest().body(errors)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package space.nstart.pcp.pcp_stations_service.configuration
|
||||
|
||||
import jakarta.validation.Validation
|
||||
import jakarta.validation.Validator
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class ValidationConfig {
|
||||
@Bean
|
||||
fun validator(): Validator {
|
||||
return Validation.buildDefaultValidatorFactory().validator
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package space.nstart.pcp.pcp_stations_service.controller
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.nstart.pcp.pcp_stations_service.service.StationService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.StationDTO
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/stations")
|
||||
class StationController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var stationService: StationService
|
||||
|
||||
@GetMapping()
|
||||
fun all() = stationService.all()
|
||||
|
||||
@GetMapping("/{station_id}")
|
||||
fun byId(@PathVariable("station_id") id : UUID): StationDTO = stationService.byId(id)
|
||||
|
||||
@PostMapping()
|
||||
fun add(@RequestBody body : StationDTO): StationDTO = stationService.add(body)
|
||||
|
||||
@DeleteMapping("{station_id}")
|
||||
fun delete(@PathVariable("station_id") id : UUID): Int = stationService.delete(id)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package space.nstart.pcp.pcp_stations_service.entity
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.StationDTO
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
@Table(name = "stations")
|
||||
class StationEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val stationId : UUID? = null,
|
||||
@Column(nullable = false)
|
||||
val number : Int = 0,
|
||||
@Column(nullable = false)
|
||||
val name : String = "",
|
||||
@Column(nullable = false)
|
||||
val longitude : Double = 0.0,
|
||||
@Column(nullable = false)
|
||||
val latitude : Double = 0.0,
|
||||
@Column(nullable = false)
|
||||
val height : Double = 0.0,
|
||||
@Column(name = "elevation_min", nullable = false)
|
||||
val elevationMin : Double = 0.0,
|
||||
@Column(name = "elevation_max", nullable = false)
|
||||
val elevationMax : Double = 0.0,
|
||||
){
|
||||
constructor(s : StationDTO) : this(
|
||||
s.id,
|
||||
s.number,
|
||||
s.name,
|
||||
s.position.long,
|
||||
s.position.lat,
|
||||
s.position.height,
|
||||
s.elevationMin,
|
||||
s.elevationMax
|
||||
)
|
||||
|
||||
fun toDTO() = StationDTO(
|
||||
stationId,
|
||||
number,
|
||||
name,
|
||||
PositionDTO(latitude, longitude, height),
|
||||
elevationMin,
|
||||
elevationMax
|
||||
)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package space.nstart.pcp.pcp_stations_service.repository
|
||||
|
||||
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import space.nstart.pcp.pcp_stations_service.entity.StationEntity
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
interface StationRepository : JpaRepository<StationEntity, UUID> {
|
||||
|
||||
fun findByStationId(id : UUID) : Optional<StationEntity>
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("DELETE FROM stations WHERE station_id =:id", nativeQuery = true)
|
||||
fun deleteByStationId(@Param("id") id : UUID) : Int
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package space.nstart.pcp.pcp_stations_service.service
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Service
|
||||
import space.nstart.pcp.pcp_stations_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.pcp_stations_service.entity.StationEntity
|
||||
import space.nstart.pcp.pcp_stations_service.repository.StationRepository
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.StationDTO
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
class StationService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private lateinit var stationRepository: StationRepository
|
||||
|
||||
|
||||
|
||||
fun all() = stationRepository.findAll().map { it.toDTO() }
|
||||
|
||||
fun add(station : StationDTO): StationDTO = stationRepository.save(StationEntity(station)).toDTO()
|
||||
|
||||
fun byId(id : UUID): StationDTO =
|
||||
stationRepository.findByStationId(id)
|
||||
.map { it.toDTO() }
|
||||
.orElseThrow { CustomErrorException("Станция $id не найдена") }
|
||||
|
||||
fun delete(id : UUID): Int = stationRepository.deleteByStationId(id)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
spring:
|
||||
application:
|
||||
name: pcp-stations-service
|
||||
profiles:
|
||||
default: local
|
||||
config:
|
||||
import: "configserver:"
|
||||
cloud:
|
||||
config:
|
||||
uri: ${CONFIG_SERVER_URI:http://localhost:8888}
|
||||
fail-fast: ${CONFIG_SERVER_FAIL_FAST:true}
|
||||
profile: ${SPRING_CLOUD_CONFIG_PROFILE:${SPRING_PROFILES_ACTIVE:${spring.profiles.default}}}
|
||||
label: ${SPRING_CLOUD_CONFIG_LABEL:master}
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stations (
|
||||
station_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
number INT NOT NULL,
|
||||
name VARCHAR(55) NOT NULL,
|
||||
latitude DOUBLE PRECISION NOT NULL,
|
||||
longitude DOUBLE PRECISION NOT NULL,
|
||||
height DOUBLE PRECISION NOT NULL,
|
||||
elevation_min DOUBLE PRECISION NOT NULL,
|
||||
elevation_max DOUBLE PRECISION NOT NULL
|
||||
);
|
||||
CREATE INDEX idx_stations_number ON stations(number);
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package space.nstart.pcp.pcp_stations_service
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
|
||||
@SpringBootTest
|
||||
class PcpStationsServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package space.nstart.pcp.pcp_stations_service.controller
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.verify
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.web.server.LocalServerPort
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import space.nstart.pcp.pcp_stations_service.service.StationService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.StationDTO
|
||||
import java.util.UUID
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = [
|
||||
"spring.cloud.config.enabled=false",
|
||||
"spring.cloud.config.import-check.enabled=false",
|
||||
"spring.boot.admin.client.enabled=false"
|
||||
]
|
||||
)
|
||||
class StationControllerTest {
|
||||
|
||||
@LocalServerPort
|
||||
private var port: Int = 0
|
||||
|
||||
@MockitoBean
|
||||
private lateinit var stationService: StationService
|
||||
|
||||
@Test
|
||||
fun `station save endpoint returns dto with position`() {
|
||||
val station = sampleStation()
|
||||
doReturn(station).`when`(stationService).add(anyStation())
|
||||
|
||||
val actual = WebClient.create("http://localhost:$port")
|
||||
.post()
|
||||
.uri("/api/stations")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(station)
|
||||
.retrieve()
|
||||
.bodyToMono(StationDTO::class.java)
|
||||
.block()!!
|
||||
|
||||
assertEquals(station.id, actual.id)
|
||||
assertEquals(station.position.lat, actual.position.lat)
|
||||
assertEquals(station.position.long, actual.position.long)
|
||||
verify(stationService).add(anyStation())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `station get by id endpoint returns dto without optional wrapper`() {
|
||||
val station = sampleStation()
|
||||
val stationId = station.id!!
|
||||
doReturn(station).`when`(stationService).byId(stationId)
|
||||
|
||||
val actual = WebClient.create("http://localhost:$port")
|
||||
.get()
|
||||
.uri("/api/stations/$stationId")
|
||||
.retrieve()
|
||||
.bodyToMono(StationDTO::class.java)
|
||||
.block()!!
|
||||
|
||||
assertEquals(stationId, actual.id)
|
||||
assertEquals(station.name, actual.name)
|
||||
verify(stationService).byId(stationId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `station delete endpoint returns deleted rows count`() {
|
||||
val stationId = UUID.fromString("3ad079f2-625f-47bc-a2b8-4e0c5f7d5c16")
|
||||
doReturn(1).`when`(stationService).delete(stationId)
|
||||
|
||||
val actual = WebClient.create("http://localhost:$port")
|
||||
.delete()
|
||||
.uri("/api/stations/$stationId")
|
||||
.retrieve()
|
||||
.bodyToMono(Int::class.java)
|
||||
.block()!!
|
||||
|
||||
assertEquals(1, actual)
|
||||
verify(stationService).delete(stationId)
|
||||
}
|
||||
|
||||
private fun sampleStation() = StationDTO(
|
||||
id = UUID.fromString("2f1d1d8b-588d-4d4e-a456-80a5c93e09dd"),
|
||||
number = 7,
|
||||
name = "Station 7",
|
||||
position = PositionDTO(
|
||||
lat = 55.75,
|
||||
long = 37.62,
|
||||
height = 180.0
|
||||
),
|
||||
elevationMin = 5.0,
|
||||
elevationMax = 88.0
|
||||
)
|
||||
|
||||
private fun anyStation(): StationDTO = any(StationDTO::class.java) ?: StationDTO()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
spring:
|
||||
config:
|
||||
import: "optional:configserver:"
|
||||
cloud:
|
||||
config:
|
||||
enabled: false
|
||||
datasource:
|
||||
url: jdbc:h2:mem:pcp_stations;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: create-drop
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.H2Dialect
|
||||
flyway:
|
||||
enabled: false
|
||||
Reference in New Issue
Block a user