Init
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class PcpSatelliteCatalogServiceApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<PcpSatelliteCatalogServiceApplication>(*args)
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_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
|
||||
|
||||
class CustomValidationException(message: String) : RuntimeException(message)
|
||||
|
||||
@ControllerAdvice
|
||||
class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(CustomValidationException::class)
|
||||
fun handleValidation(ex: CustomValidationException): ResponseEntity<Map<String, String>> =
|
||||
ResponseEntity.badRequest().body(mapOf("error" to (ex.message ?: "validation error")))
|
||||
|
||||
@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 ?: "request"
|
||||
errors[fieldName] = error.defaultMessage ?: "Validation error"
|
||||
}
|
||||
return ResponseEntity.badRequest().body(errors)
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.configuration
|
||||
|
||||
import org.apache.kafka.clients.admin.AdminClientConfig
|
||||
import org.apache.kafka.clients.admin.NewTopic
|
||||
import org.apache.kafka.clients.producer.ProducerConfig
|
||||
import org.apache.kafka.common.serialization.StringSerializer
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory
|
||||
import org.springframework.kafka.core.KafkaAdmin
|
||||
import org.springframework.kafka.core.KafkaTemplate
|
||||
import org.springframework.kafka.core.ProducerFactory
|
||||
|
||||
@ConditionalOnProperty(name = ["spring.kafka.bootstrap-servers"], matchIfMissing = false)
|
||||
@Configuration
|
||||
class KafkaConfig {
|
||||
|
||||
@Value("\${spring.kafka.bootstrap-servers}")
|
||||
private lateinit var bootstrapServers: String
|
||||
|
||||
@Value("\${app.kafka.topics.satellites:pcp.satellites}")
|
||||
private lateinit var satelliteEventsTopicName: String
|
||||
|
||||
@Bean
|
||||
fun satelliteEventsTopic(): NewTopic = NewTopic(satelliteEventsTopicName, 1, 1)
|
||||
|
||||
@Bean
|
||||
fun kafkaAdmin(): KafkaAdmin =
|
||||
KafkaAdmin(
|
||||
mutableMapOf<String, Any>(
|
||||
AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers
|
||||
)
|
||||
)
|
||||
|
||||
@Bean
|
||||
fun producerFactory(): ProducerFactory<String, Any> =
|
||||
DefaultKafkaProducerFactory(
|
||||
mutableMapOf<String, Any>(
|
||||
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers,
|
||||
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java,
|
||||
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java
|
||||
)
|
||||
)
|
||||
|
||||
@Bean
|
||||
fun kafkaTemplate(): KafkaTemplate<String, Any> = KafkaTemplate(producerFactory())
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_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 = Validation.buildDefaultValidatorFactory().validator
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.controller
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.http.ResponseEntity
|
||||
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.PutMapping
|
||||
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_satellite_catalog_service.service.SatelliteCatalogService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteBatchRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteCreateDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteUpdateDTO
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/satellites")
|
||||
class SatelliteCatalogController(
|
||||
private val satelliteCatalogService: SatelliteCatalogService
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
fun all() = satelliteCatalogService.all()
|
||||
|
||||
@GetMapping("/{satellite_id}")
|
||||
fun byId(@PathVariable("satellite_id") satelliteId: Long) =
|
||||
satelliteCatalogService.byId(satelliteId)
|
||||
|
||||
@GetMapping("/by-norad/{norad_id}")
|
||||
fun byNoradId(@PathVariable("norad_id") noradId: Long) =
|
||||
satelliteCatalogService.byNoradId(noradId)
|
||||
|
||||
@PostMapping
|
||||
fun create(@Valid @RequestBody request: SatelliteCreateDTO) =
|
||||
satelliteCatalogService.createSatellite(request)
|
||||
|
||||
@PutMapping("/{satellite_id}")
|
||||
fun update(
|
||||
@PathVariable("satellite_id") satelliteId: Long,
|
||||
@Valid @RequestBody request: SatelliteUpdateDTO
|
||||
) = satelliteCatalogService.updateSatellite(satelliteId, request)
|
||||
|
||||
@DeleteMapping("/{satellite_id}")
|
||||
fun delete(@PathVariable("satellite_id") satelliteId: Long): ResponseEntity<Void> {
|
||||
satelliteCatalogService.deleteSatellite(satelliteId)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@GetMapping("/{satellite_id}/observation-profile")
|
||||
fun observationProfile(@PathVariable("satellite_id") satelliteId: Long) =
|
||||
satelliteCatalogService.observationProfile(satelliteId)
|
||||
|
||||
@PostMapping("/{satellite_id}/observation-profile")
|
||||
fun createObservationProfile(
|
||||
@PathVariable("satellite_id") satelliteId: Long,
|
||||
@Valid @RequestBody request: SatelliteObservationProfileDTO
|
||||
) = satelliteCatalogService.createObservationProfile(satelliteId, request)
|
||||
|
||||
@PutMapping("/{satellite_id}/observation-profile")
|
||||
fun updateObservationProfile(
|
||||
@PathVariable("satellite_id") satelliteId: Long,
|
||||
@Valid @RequestBody request: SatelliteObservationProfileDTO
|
||||
) = satelliteCatalogService.updateObservationProfile(satelliteId, request)
|
||||
|
||||
@DeleteMapping("/{satellite_id}/observation-profile")
|
||||
fun deleteObservationProfile(@PathVariable("satellite_id") satelliteId: Long): ResponseEntity<Void> {
|
||||
satelliteCatalogService.deleteObservationProfile(satelliteId)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@GetMapping("/{satellite_id}/slot-profile")
|
||||
fun slotProfile(@PathVariable("satellite_id") satelliteId: Long) =
|
||||
satelliteCatalogService.slotProfile(satelliteId)
|
||||
|
||||
@PostMapping("/{satellite_id}/slot-profile")
|
||||
fun createSlotProfile(
|
||||
@PathVariable("satellite_id") satelliteId: Long,
|
||||
@Valid @RequestBody request: SatelliteSlotProfileDTO
|
||||
) = satelliteCatalogService.createSlotProfile(satelliteId, request)
|
||||
|
||||
@PutMapping("/{satellite_id}/slot-profile")
|
||||
fun updateSlotProfile(
|
||||
@PathVariable("satellite_id") satelliteId: Long,
|
||||
@Valid @RequestBody request: SatelliteSlotProfileDTO
|
||||
) = satelliteCatalogService.updateSlotProfile(satelliteId, request)
|
||||
|
||||
@DeleteMapping("/{satellite_id}/slot-profile")
|
||||
fun deleteSlotProfile(@PathVariable("satellite_id") satelliteId: Long): ResponseEntity<Void> {
|
||||
satelliteCatalogService.deleteSlotProfile(satelliteId)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@PostMapping("/batch")
|
||||
fun batch(@Valid @RequestBody request: SatelliteBatchRequestDTO) =
|
||||
satelliteCatalogService.batch(request)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.controller
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.http.ResponseEntity
|
||||
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.PutMapping
|
||||
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_satellite_catalog_service.service.SatelliteCatalogService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupCreateDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupUpdateDTO
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/satellite-groups")
|
||||
class SatelliteGroupController(
|
||||
private val satelliteCatalogService: SatelliteCatalogService
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
fun all() = satelliteCatalogService.allGroups()
|
||||
|
||||
@GetMapping("/{group_id}")
|
||||
fun byId(@PathVariable("group_id") groupId: Long) =
|
||||
satelliteCatalogService.groupById(groupId)
|
||||
|
||||
@PostMapping
|
||||
fun create(@Valid @RequestBody request: SatelliteGroupCreateDTO) =
|
||||
satelliteCatalogService.createGroup(request)
|
||||
|
||||
@PutMapping("/{group_id}")
|
||||
fun update(
|
||||
@PathVariable("group_id") groupId: Long,
|
||||
@Valid @RequestBody request: SatelliteGroupUpdateDTO
|
||||
) = satelliteCatalogService.updateGroup(groupId, request)
|
||||
|
||||
@DeleteMapping("/{group_id}")
|
||||
fun delete(@PathVariable("group_id") groupId: Long): ResponseEntity<Void> {
|
||||
satelliteCatalogService.deleteGroup(groupId)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.entity
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteVisualizationDTO
|
||||
|
||||
@Entity
|
||||
@Table(name = "satellite")
|
||||
class SatelliteEntity(
|
||||
@Id
|
||||
val id: Long = 0,
|
||||
@Column(name = "norad_id")
|
||||
val noradId: Long? = null,
|
||||
@Column(nullable = false)
|
||||
val code: String = "",
|
||||
@Column(nullable = false)
|
||||
val name: String = "",
|
||||
@Column(name = "type_code", nullable = false)
|
||||
val typeCode: String = "",
|
||||
@Column(nullable = false)
|
||||
val active: Boolean = true,
|
||||
@Column(name = "scan_tle", nullable = false)
|
||||
val scanTle: Boolean = false,
|
||||
@Column(nullable = false)
|
||||
val red: Short = 255,
|
||||
@Column(nullable = false)
|
||||
val green: Short = 0,
|
||||
@Column(nullable = false)
|
||||
val blue: Short = 0
|
||||
) {
|
||||
fun toSummaryDTO() = SatelliteSummaryDTO(
|
||||
id = id,
|
||||
noradId = noradId,
|
||||
code = code,
|
||||
name = name,
|
||||
typeCode = typeCode,
|
||||
active = active,
|
||||
scanTle = scanTle,
|
||||
visualization = SatelliteVisualizationDTO(
|
||||
red = red,
|
||||
green = green,
|
||||
blue = blue
|
||||
)
|
||||
)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_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
|
||||
|
||||
@Entity
|
||||
@Table(name = "satellite_group")
|
||||
class SatelliteGroupEntity(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id: Long = 0,
|
||||
@Column(nullable = false)
|
||||
val name: String = ""
|
||||
)
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.entity
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.IdClass
|
||||
import jakarta.persistence.Table
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
@Table(name = "satellite_group_satellite")
|
||||
@IdClass(SatelliteGroupSatelliteId::class)
|
||||
class SatelliteGroupSatelliteEntity(
|
||||
@Id
|
||||
@Column(name = "group_id")
|
||||
val groupId: Long = 0,
|
||||
@Id
|
||||
@Column(name = "satellite_id")
|
||||
val satelliteId: Long = 0
|
||||
)
|
||||
|
||||
data class SatelliteGroupSatelliteId(
|
||||
val groupId: Long = 0,
|
||||
val satelliteId: Long = 0
|
||||
) : Serializable
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.entity
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileDTO
|
||||
|
||||
@Entity
|
||||
@Table(name = "satellite_observation_profile")
|
||||
class SatelliteObservationProfileEntity(
|
||||
@Id
|
||||
@Column(name = "satellite_id")
|
||||
val satelliteId: Long = 0,
|
||||
@Column(name = "capture_angle", nullable = false)
|
||||
val captureAngle: Double = 1.5,
|
||||
@Column(name = "sun_angle_min", nullable = false)
|
||||
val sunAngleMin: Double = -90.0,
|
||||
@Column(name = "duration_min_seconds", nullable = false)
|
||||
val durationMinSeconds: Long = 10,
|
||||
@Column(name = "duration_max_seconds", nullable = false)
|
||||
val durationMaxSeconds: Long = 300,
|
||||
@Column(name = "mmi_seconds", nullable = false)
|
||||
val mmiSeconds: Long = 10,
|
||||
@Column(name = "daily_max_duration_seconds", nullable = false)
|
||||
val dailyMaxDurationSeconds: Long = 35 * 60,
|
||||
@Column(name = "revolution_max_duration_seconds", nullable = false)
|
||||
val revolutionMaxDurationSeconds: Long = 5 * 60
|
||||
) {
|
||||
fun toDTO() = SatelliteObservationProfileDTO(
|
||||
captureAngle = captureAngle,
|
||||
sunAngleMin = sunAngleMin,
|
||||
durationMinSeconds = durationMinSeconds,
|
||||
durationMaxSeconds = durationMaxSeconds,
|
||||
mmiSeconds = mmiSeconds,
|
||||
dailyMaxDurationSeconds = dailyMaxDurationSeconds,
|
||||
revolutionMaxDurationSeconds = revolutionMaxDurationSeconds
|
||||
)
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.entity
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.IdClass
|
||||
import jakarta.persistence.Table
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
@Table(name = "satellite_slot_angle")
|
||||
@IdClass(SatelliteSlotAngleId::class)
|
||||
class SatelliteSlotAngleEntity(
|
||||
@Id
|
||||
@Column(name = "satellite_id")
|
||||
val satelliteId: Long = 0,
|
||||
@Id
|
||||
@Column(name = "position")
|
||||
val position: Int = 0,
|
||||
@Column(nullable = false)
|
||||
val angleBegin: Double = 0.0,
|
||||
@Column(nullable = false)
|
||||
val angleEnd: Double = 0.0
|
||||
)
|
||||
|
||||
data class SatelliteSlotAngleId(
|
||||
val satelliteId: Long = 0,
|
||||
val position: Int = 0
|
||||
) : Serializable
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.entity
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "satellite_slot_profile")
|
||||
class SatelliteSlotProfileEntity(
|
||||
@Id
|
||||
@Column(name = "satellite_id")
|
||||
val satelliteId: Long = 0,
|
||||
@Column(name = "cycle_revs", nullable = false)
|
||||
val cycleRevs: Long = 0,
|
||||
@Column(name = "tn_calc")
|
||||
val tnCalc: LocalDateTime? = null,
|
||||
@Column(name = "slot_duration", nullable = false)
|
||||
val slotDuration: Long = 10,
|
||||
@Column(name = "duration_calc_days", nullable = false)
|
||||
val durationCalcDays: Long = 0,
|
||||
@Column(name = "max_chain_length", nullable = false)
|
||||
val maxChainLength: Int = 0
|
||||
)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.repository
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteGroupEntity
|
||||
|
||||
interface SatelliteGroupRepository : JpaRepository<SatelliteGroupEntity, Long> {
|
||||
fun findAllByOrderByIdAsc(): List<SatelliteGroupEntity>
|
||||
fun existsByName(name: String): Boolean
|
||||
fun existsByNameAndIdNot(name: String, id: Long): Boolean
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.repository
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteGroupSatelliteEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteGroupSatelliteId
|
||||
|
||||
interface SatelliteGroupSatelliteRepository : JpaRepository<SatelliteGroupSatelliteEntity, SatelliteGroupSatelliteId> {
|
||||
fun findAllByGroupIdInOrderByGroupIdAscSatelliteIdAsc(groupIds: Collection<Long>): List<SatelliteGroupSatelliteEntity>
|
||||
fun deleteAllByGroupId(groupId: Long)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.repository
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteObservationProfileEntity
|
||||
|
||||
interface SatelliteObservationProfileRepository : JpaRepository<SatelliteObservationProfileEntity, Long> {
|
||||
fun findAllBySatelliteIdIn(satelliteIds: Collection<Long>): List<SatelliteObservationProfileEntity>
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.repository
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteEntity
|
||||
|
||||
interface SatelliteRepository : JpaRepository<SatelliteEntity, Long> {
|
||||
fun findAllByOrderByIdAsc(): List<SatelliteEntity>
|
||||
fun findByNoradId(noradId: Long): SatelliteEntity?
|
||||
fun findAllByIdIn(ids: Collection<Long>): List<SatelliteEntity>
|
||||
fun existsByNoradId(noradId: Long): Boolean
|
||||
fun existsByNoradIdAndIdNot(noradId: Long, id: Long): Boolean
|
||||
fun existsByCode(code: String): Boolean
|
||||
fun existsByCodeAndIdNot(code: String, id: Long): Boolean
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.repository
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteSlotAngleEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteSlotAngleId
|
||||
|
||||
interface SatelliteSlotAngleRepository : JpaRepository<SatelliteSlotAngleEntity, SatelliteSlotAngleId> {
|
||||
fun findAllBySatelliteIdInOrderBySatelliteIdAscPositionAsc(satelliteIds: Collection<Long>): List<SatelliteSlotAngleEntity>
|
||||
fun deleteAllBySatelliteId(satelliteId: Long)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.repository
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteSlotProfileEntity
|
||||
|
||||
interface SatelliteSlotProfileRepository : JpaRepository<SatelliteSlotProfileEntity, Long> {
|
||||
fun findAllBySatelliteIdIn(satelliteIds: Collection<Long>): List<SatelliteSlotProfileEntity>
|
||||
}
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.service
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.transaction.support.TransactionSynchronization
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.configuration.CustomValidationException
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteGroupEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteGroupSatelliteEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteObservationProfileEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteSlotAngleEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.entity.SatelliteSlotProfileEntity
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.repository.SatelliteGroupRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.repository.SatelliteGroupSatelliteRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.repository.SatelliteObservationProfileRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.repository.SatelliteRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.repository.SatelliteSlotAngleRepository
|
||||
import space.nstart.pcp.pcp_satellite_catalog_service.repository.SatelliteSlotProfileRepository
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteBatchRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteCreateDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDeletedEventDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupCreateDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupUpdateDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotAngleDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteUpdateDTO
|
||||
|
||||
@Service
|
||||
class SatelliteCatalogService(
|
||||
private val satelliteRepository: SatelliteRepository,
|
||||
private val observationProfileRepository: SatelliteObservationProfileRepository,
|
||||
private val slotProfileRepository: SatelliteSlotProfileRepository,
|
||||
private val slotAngleRepository: SatelliteSlotAngleRepository,
|
||||
private val satelliteGroupRepository: SatelliteGroupRepository,
|
||||
private val satelliteGroupSatelliteRepository: SatelliteGroupSatelliteRepository,
|
||||
private val satelliteDeletedKafkaPublisher: SatelliteDeletedKafkaPublisher
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
fun all(): List<SatelliteSummaryDTO> =
|
||||
satelliteRepository.findAllByOrderByIdAsc().map { it.toSummaryDTO() }
|
||||
|
||||
fun byId(id: Long): SatelliteDTO =
|
||||
assembleSatellites(listOf(requireSatellite(id))).getValue(id)
|
||||
|
||||
fun byNoradId(noradId: Long): SatelliteDTO {
|
||||
val satellite = satelliteRepository.findByNoradId(noradId)
|
||||
?: throw CustomValidationException("Satellite with NORAD ID $noradId not found")
|
||||
return assembleSatellites(listOf(satellite)).getValue(satellite.id)
|
||||
}
|
||||
|
||||
fun observationProfile(id: Long): SatelliteObservationProfileDTO {
|
||||
requireSatellite(id)
|
||||
return observationProfileRepository.findById(id).orElseThrow {
|
||||
CustomValidationException("Observation profile for satellite $id not found")
|
||||
}.toDTO()
|
||||
}
|
||||
|
||||
fun slotProfile(id: Long): SatelliteSlotProfileDTO {
|
||||
requireSatellite(id)
|
||||
return assembleSlotProfiles(listOf(id))[id]
|
||||
?: throw CustomValidationException("Slot profile for satellite $id not found")
|
||||
}
|
||||
|
||||
fun batch(request: SatelliteBatchRequestDTO): List<SatelliteDTO> {
|
||||
val uniqueIds = request.ids.distinct()
|
||||
if (uniqueIds.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val satellites = satelliteRepository.findAllByIdIn(uniqueIds)
|
||||
val byId = assembleSatellites(satellites)
|
||||
return uniqueIds.map { id ->
|
||||
byId[id] ?: throw CustomValidationException("Satellite $id not found")
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createSatellite(request: SatelliteCreateDTO): SatelliteDTO {
|
||||
validateSatelliteCreate(request)
|
||||
satelliteRepository.save(request.toEntity())
|
||||
return byId(request.id)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateSatellite(id: Long, request: SatelliteUpdateDTO): SatelliteDTO {
|
||||
requireSatellite(id)
|
||||
validateSatelliteUpdate(id, request)
|
||||
satelliteRepository.save(request.toEntity(id))
|
||||
return byId(id)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteSatellite(id: Long) {
|
||||
val satellite = requireSatellite(id)
|
||||
val event = SatelliteDeletedEventDTO(
|
||||
satelliteId = satellite.id,
|
||||
noradId = satellite.noradId,
|
||||
code = satellite.code,
|
||||
name = satellite.name
|
||||
)
|
||||
satelliteRepository.deleteById(id)
|
||||
logger.info(
|
||||
"Deleted satellite from catalog: satelliteId={}, noradId={}, code={}, name={}",
|
||||
event.satelliteId,
|
||||
event.noradId,
|
||||
event.code,
|
||||
event.name
|
||||
)
|
||||
publishSatelliteDeletedAfterCommit(event)
|
||||
}
|
||||
|
||||
private fun publishSatelliteDeletedAfterCommit(event: SatelliteDeletedEventDTO) {
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
object : TransactionSynchronization {
|
||||
override fun afterCommit() {
|
||||
satelliteDeletedKafkaPublisher.publish(event)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createObservationProfile(id: Long, request: SatelliteObservationProfileDTO): SatelliteObservationProfileDTO {
|
||||
requireSatellite(id)
|
||||
if (observationProfileRepository.existsById(id)) {
|
||||
throw CustomValidationException("Observation profile for satellite $id already exists")
|
||||
}
|
||||
return observationProfileRepository.save(request.toEntity(id)).toDTO()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateObservationProfile(id: Long, request: SatelliteObservationProfileDTO): SatelliteObservationProfileDTO {
|
||||
requireSatellite(id)
|
||||
if (!observationProfileRepository.existsById(id)) {
|
||||
throw CustomValidationException("Observation profile for satellite $id not found")
|
||||
}
|
||||
return observationProfileRepository.save(request.toEntity(id)).toDTO()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteObservationProfile(id: Long) {
|
||||
requireSatellite(id)
|
||||
if (!observationProfileRepository.existsById(id)) {
|
||||
throw CustomValidationException("Observation profile for satellite $id not found")
|
||||
}
|
||||
observationProfileRepository.deleteById(id)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createSlotProfile(id: Long, request: SatelliteSlotProfileDTO): SatelliteSlotProfileDTO {
|
||||
requireSatellite(id)
|
||||
if (slotProfileRepository.existsById(id)) {
|
||||
throw CustomValidationException("Slot profile for satellite $id already exists")
|
||||
}
|
||||
return saveSlotProfile(id, request)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateSlotProfile(id: Long, request: SatelliteSlotProfileDTO): SatelliteSlotProfileDTO {
|
||||
requireSatellite(id)
|
||||
if (!slotProfileRepository.existsById(id)) {
|
||||
throw CustomValidationException("Slot profile for satellite $id not found")
|
||||
}
|
||||
return saveSlotProfile(id, request)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteSlotProfile(id: Long) {
|
||||
requireSatellite(id)
|
||||
if (!slotProfileRepository.existsById(id)) {
|
||||
throw CustomValidationException("Slot profile for satellite $id not found")
|
||||
}
|
||||
slotAngleRepository.deleteAllBySatelliteId(id)
|
||||
slotProfileRepository.deleteById(id)
|
||||
}
|
||||
|
||||
fun allGroups(): List<SatelliteGroupDTO> =
|
||||
assembleGroups(satelliteGroupRepository.findAllByOrderByIdAsc()).values.toList()
|
||||
|
||||
fun groupById(id: Long): SatelliteGroupDTO =
|
||||
assembleGroups(listOf(requireGroup(id))).getValue(id)
|
||||
|
||||
@Transactional
|
||||
fun createGroup(request: SatelliteGroupCreateDTO): SatelliteGroupDTO {
|
||||
validateGroupCreate(request)
|
||||
val satelliteIds = request.satelliteIds.distinct()
|
||||
validateSatelliteIdsExist(satelliteIds)
|
||||
val group = satelliteGroupRepository.save(SatelliteGroupEntity(name = request.name))
|
||||
saveGroupSatellites(group.id, satelliteIds)
|
||||
return groupById(group.id)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateGroup(id: Long, request: SatelliteGroupUpdateDTO): SatelliteGroupDTO {
|
||||
requireGroup(id)
|
||||
validateGroupUpdate(id, request)
|
||||
val satelliteIds = request.satelliteIds.distinct()
|
||||
validateSatelliteIdsExist(satelliteIds)
|
||||
satelliteGroupRepository.save(SatelliteGroupEntity(id = id, name = request.name))
|
||||
satelliteGroupSatelliteRepository.deleteAllByGroupId(id)
|
||||
saveGroupSatellites(id, satelliteIds)
|
||||
return groupById(id)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteGroup(id: Long) {
|
||||
requireGroup(id)
|
||||
satelliteGroupRepository.deleteById(id)
|
||||
}
|
||||
|
||||
private fun saveSlotProfile(id: Long, request: SatelliteSlotProfileDTO): SatelliteSlotProfileDTO {
|
||||
slotProfileRepository.save(request.toEntity(id))
|
||||
slotAngleRepository.deleteAllBySatelliteId(id)
|
||||
slotAngleRepository.saveAll(
|
||||
request.defaultAngles.mapIndexed { index, angle ->
|
||||
SatelliteSlotAngleEntity(
|
||||
satelliteId = id,
|
||||
position = index + 1,
|
||||
angleBegin = angle.angleBegin,
|
||||
angleEnd = angle.angleEnd
|
||||
)
|
||||
}
|
||||
)
|
||||
return assembleSlotProfiles(listOf(id)).getValue(id)
|
||||
}
|
||||
|
||||
private fun saveGroupSatellites(groupId: Long, satelliteIds: List<Long>) {
|
||||
if (satelliteIds.isEmpty()) {
|
||||
return
|
||||
}
|
||||
satelliteGroupSatelliteRepository.saveAll(
|
||||
satelliteIds.map { satelliteId ->
|
||||
SatelliteGroupSatelliteEntity(
|
||||
groupId = groupId,
|
||||
satelliteId = satelliteId
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateSatelliteCreate(request: SatelliteCreateDTO) {
|
||||
if (satelliteRepository.existsById(request.id)) {
|
||||
throw CustomValidationException("Satellite ${request.id} already exists")
|
||||
}
|
||||
if (satelliteRepository.existsByCode(request.code)) {
|
||||
throw CustomValidationException("Satellite code ${request.code} already exists")
|
||||
}
|
||||
request.noradId?.let { noradId ->
|
||||
if (satelliteRepository.existsByNoradId(noradId)) {
|
||||
throw CustomValidationException("Satellite with NORAD ID $noradId already exists")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateSatelliteUpdate(id: Long, request: SatelliteUpdateDTO) {
|
||||
if (satelliteRepository.existsByCodeAndIdNot(request.code, id)) {
|
||||
throw CustomValidationException("Satellite code ${request.code} already exists")
|
||||
}
|
||||
request.noradId?.let { noradId ->
|
||||
if (satelliteRepository.existsByNoradIdAndIdNot(noradId, id)) {
|
||||
throw CustomValidationException("Satellite with NORAD ID $noradId already exists")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateGroupCreate(request: SatelliteGroupCreateDTO) {
|
||||
if (satelliteGroupRepository.existsByName(request.name)) {
|
||||
throw CustomValidationException("Satellite group ${request.name} already exists")
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateGroupUpdate(id: Long, request: SatelliteGroupUpdateDTO) {
|
||||
if (satelliteGroupRepository.existsByNameAndIdNot(request.name, id)) {
|
||||
throw CustomValidationException("Satellite group ${request.name} already exists")
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateSatelliteIdsExist(ids: List<Long>) {
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val existingIds = satelliteRepository.findAllByIdIn(ids).map { it.id }.toSet()
|
||||
val missingIds = ids.filterNot(existingIds::contains)
|
||||
if (missingIds.isNotEmpty()) {
|
||||
throw CustomValidationException("Satellites not found: ${missingIds.joinToString(", ")}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requireSatellite(id: Long): SatelliteEntity =
|
||||
satelliteRepository.findById(id).orElseThrow {
|
||||
CustomValidationException("Satellite $id not found")
|
||||
}
|
||||
|
||||
private fun requireGroup(id: Long): SatelliteGroupEntity =
|
||||
satelliteGroupRepository.findById(id).orElseThrow {
|
||||
CustomValidationException("Satellite group $id not found")
|
||||
}
|
||||
|
||||
private fun assembleSatellites(satellites: List<SatelliteEntity>): Map<Long, SatelliteDTO> {
|
||||
val ids = satellites.map { it.id }
|
||||
val observationProfiles = observationProfileRepository.findAllBySatelliteIdIn(ids)
|
||||
.associateBy { it.satelliteId }
|
||||
val slotProfiles = assembleSlotProfiles(ids)
|
||||
|
||||
return satellites.associate { satellite ->
|
||||
val observationProfile = observationProfiles[satellite.id]?.toDTO()
|
||||
satellite.id to SatelliteDTO(
|
||||
id = satellite.id,
|
||||
noradId = satellite.noradId,
|
||||
code = satellite.code,
|
||||
name = satellite.name,
|
||||
typeCode = satellite.typeCode,
|
||||
active = satellite.active,
|
||||
scanTle = satellite.scanTle,
|
||||
visualization = satellite.toSummaryDTO().visualization,
|
||||
observationProfile = observationProfile,
|
||||
slotProfile = slotProfiles[satellite.id]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assembleSlotProfiles(ids: List<Long>): Map<Long, SatelliteSlotProfileDTO> {
|
||||
val slotProfiles = slotProfileRepository.findAllBySatelliteIdIn(ids)
|
||||
.associateBy { it.satelliteId }
|
||||
val anglesBySatelliteId = slotAngleRepository.findAllBySatelliteIdInOrderBySatelliteIdAscPositionAsc(ids)
|
||||
.groupBy({ it.satelliteId }) {
|
||||
SatelliteSlotAngleDTO(
|
||||
angleBegin = it.angleBegin,
|
||||
angleEnd = it.angleEnd
|
||||
)
|
||||
}
|
||||
|
||||
return slotProfiles.mapValues { (_, profile) ->
|
||||
SatelliteSlotProfileDTO(
|
||||
cycleRevs = profile.cycleRevs,
|
||||
tnCalc = profile.tnCalc,
|
||||
slotDuration = profile.slotDuration,
|
||||
durationCalcDays = profile.durationCalcDays,
|
||||
maxChainLength = profile.maxChainLength,
|
||||
defaultAngles = anglesBySatelliteId[profile.satelliteId].orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assembleGroups(groups: List<SatelliteGroupEntity>): Map<Long, SatelliteGroupDTO> {
|
||||
val groupIds = groups.map { it.id }
|
||||
val memberships = satelliteGroupSatelliteRepository.findAllByGroupIdInOrderByGroupIdAscSatelliteIdAsc(groupIds)
|
||||
.groupBy({ it.groupId }, { it.satelliteId })
|
||||
|
||||
return groups.associate { group ->
|
||||
group.id to SatelliteGroupDTO(
|
||||
id = group.id,
|
||||
name = group.name,
|
||||
satelliteIds = memberships[group.id].orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SatelliteCreateDTO.toEntity() = SatelliteEntity(
|
||||
id = id,
|
||||
noradId = noradId,
|
||||
code = code,
|
||||
name = name,
|
||||
typeCode = typeCode,
|
||||
active = active,
|
||||
scanTle = scanTle,
|
||||
red = visualization.red,
|
||||
green = visualization.green,
|
||||
blue = visualization.blue
|
||||
)
|
||||
|
||||
private fun SatelliteUpdateDTO.toEntity(id: Long) = SatelliteEntity(
|
||||
id = id,
|
||||
noradId = noradId,
|
||||
code = code,
|
||||
name = name,
|
||||
typeCode = typeCode,
|
||||
active = active,
|
||||
scanTle = scanTle,
|
||||
red = visualization.red,
|
||||
green = visualization.green,
|
||||
blue = visualization.blue
|
||||
)
|
||||
|
||||
private fun SatelliteObservationProfileDTO.toEntity(satelliteId: Long) = SatelliteObservationProfileEntity(
|
||||
satelliteId = satelliteId,
|
||||
captureAngle = captureAngle,
|
||||
sunAngleMin = sunAngleMin,
|
||||
durationMinSeconds = durationMinSeconds,
|
||||
durationMaxSeconds = durationMaxSeconds,
|
||||
mmiSeconds = mmiSeconds,
|
||||
dailyMaxDurationSeconds = dailyMaxDurationSeconds,
|
||||
revolutionMaxDurationSeconds = revolutionMaxDurationSeconds
|
||||
)
|
||||
|
||||
private fun SatelliteSlotProfileDTO.toEntity(satelliteId: Long) = SatelliteSlotProfileEntity(
|
||||
satelliteId = satelliteId,
|
||||
cycleRevs = cycleRevs,
|
||||
tnCalc = tnCalc,
|
||||
slotDuration = slotDuration,
|
||||
durationCalcDays = durationCalcDays,
|
||||
maxChainLength = maxChainLength
|
||||
)
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package space.nstart.pcp.pcp_satellite_catalog_service.service
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import org.apache.kafka.clients.producer.ProducerRecord
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.kafka.core.KafkaTemplate
|
||||
import org.springframework.stereotype.Component
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDeletedEventDTO
|
||||
import space.nstart.pcp.pcp_types_lib.message.KafkaMessage
|
||||
import space.nstart.pcp.pcp_types_lib.message.PcpKafkaEvent
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@Component
|
||||
class SatelliteDeletedKafkaPublisher(
|
||||
kafkaTemplateProvider: ObjectProvider<KafkaTemplate<String, Any>>,
|
||||
objectMapperProvider: ObjectProvider<ObjectMapper>,
|
||||
@param:Value("\${app.kafka.topics.satellites:pcp.satellites}") private val topic: String,
|
||||
@param:Value("\${spring.application.name:pcp-satellite-catalog-service}") private val applicationName: String
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
private val kafkaTemplate = kafkaTemplateProvider.ifAvailable
|
||||
private val objectMapper = objectMapperProvider.ifAvailable ?: jacksonObjectMapper().findAndRegisterModules()
|
||||
|
||||
fun publish(event: SatelliteDeletedEventDTO) {
|
||||
val template = kafkaTemplate
|
||||
if (template == null || topic.isBlank()) {
|
||||
logger.warn(
|
||||
"SatelliteDeletedEvent was not published because Kafka is not configured: satelliteId={}, noradId={}, topic={}",
|
||||
event.satelliteId,
|
||||
event.noradId,
|
||||
topic
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val message = KafkaMessage(
|
||||
type = PcpKafkaEvent.SatelliteDeletedEvent,
|
||||
data = event
|
||||
).apply {
|
||||
source = applicationName
|
||||
}
|
||||
|
||||
val payload = try {
|
||||
objectMapper.writeValueAsString(message)
|
||||
} catch (exception: JsonProcessingException) {
|
||||
throw IllegalStateException("Failed to serialize SatelliteDeletedEvent for satellite ${event.satelliteId}", exception)
|
||||
}
|
||||
|
||||
val record = ProducerRecord<String, Any>(topic, event.satelliteId.toString(), payload)
|
||||
record.headers().add(TYPE_HEADER, PcpKafkaEvent.SatelliteDeletedEvent.name.toByteArray(StandardCharsets.UTF_8))
|
||||
template.send(record)
|
||||
logger.info(
|
||||
"Published SatelliteDeletedEvent: satelliteId={}, noradId={}, topic={}, eventType={}",
|
||||
event.satelliteId,
|
||||
event.noradId,
|
||||
topic,
|
||||
PcpKafkaEvent.SatelliteDeletedEvent
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_HEADER = "type"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
spring:
|
||||
application:
|
||||
name: pcp-satellite-catalog-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:dev}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
CREATE TABLE IF NOT EXISTS satellite (
|
||||
id BIGINT PRIMARY KEY,
|
||||
norad_id BIGINT NULL,
|
||||
code VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
type_code VARCHAR(64) NOT NULL,
|
||||
active BOOLEAN NOT NULL,
|
||||
scan_tle BOOLEAN NOT NULL,
|
||||
red SMALLINT NOT NULL,
|
||||
green SMALLINT NOT NULL,
|
||||
blue SMALLINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS satellite_observation_profile (
|
||||
satellite_id BIGINT PRIMARY KEY REFERENCES satellite(id) ON DELETE CASCADE,
|
||||
capture_angle DOUBLE PRECISION NOT NULL,
|
||||
sun_angle_min DOUBLE PRECISION NOT NULL,
|
||||
duration_min_seconds BIGINT NOT NULL,
|
||||
duration_max_seconds BIGINT NOT NULL,
|
||||
mmi_seconds BIGINT NOT NULL,
|
||||
daily_max_duration_seconds BIGINT NOT NULL,
|
||||
revolution_max_duration_seconds BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS satellite_slot_profile (
|
||||
satellite_id BIGINT PRIMARY KEY REFERENCES satellite(id) ON DELETE CASCADE,
|
||||
cycle_revs BIGINT NOT NULL,
|
||||
tn_calc TIMESTAMP NULL,
|
||||
duration_calc_days BIGINT NOT NULL,
|
||||
max_chain_length INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS satellite_slot_angle (
|
||||
satellite_id BIGINT NOT NULL REFERENCES satellite(id) ON DELETE CASCADE,
|
||||
position INT NOT NULL,
|
||||
angle_begin DOUBLE PRECISION NOT NULL,
|
||||
angle_end DOUBLE PRECISION NOT NULL,
|
||||
PRIMARY KEY (satellite_id, position)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_satellite_norad_id ON satellite(norad_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_satellite_slot_angle_satellite_id ON satellite_slot_angle(satellite_id);
|
||||
|
||||
INSERT INTO satellite (id, norad_id, code, name, type_code, active, scan_tle, red, green, blue) VALUES
|
||||
(1, NULL, 'EMISSIO-01', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(2, NULL, 'EMISSIO-02', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(3, NULL, 'EMISSIO-03', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(4, NULL, 'EMISSIO-04', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(5, NULL, 'EMISSIO-05', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(6, NULL, 'EMISSIO-06', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(7, NULL, 'REFLEXIO-07', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 78),
|
||||
(8, NULL, 'REFLEXIO-08', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 78),
|
||||
(9, NULL, 'REFLEXIO-09', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 78),
|
||||
(10, NULL, 'REFLEXIO-10', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 78),
|
||||
(11, NULL, 'REFLEXIO-11', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 78),
|
||||
(12, NULL, 'REFLEXIO-12', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 86),
|
||||
(13, NULL, 'REFLEXIO-13', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 86),
|
||||
(14, NULL, 'REFLEXIO-14', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 86),
|
||||
(15, NULL, 'REFLEXIO-15', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 86),
|
||||
(16, NULL, 'REFLEXIO-16', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 86),
|
||||
(17, NULL, 'REFLEXIO-17', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 94),
|
||||
(18, NULL, 'REFLEXIO-18', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 94),
|
||||
(19, NULL, 'REFLEXIO-19', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 94),
|
||||
(20, NULL, 'REFLEXIO-20', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 94),
|
||||
(21, NULL, 'REFLEXIO-21', 'Reflexio', 'REFLEXIO', TRUE, FALSE, 125, 125, 94),
|
||||
(22, NULL, 'EMISSIO-22', 'Emissio', 'EMISSIO', TRUE, FALSE, 0, 125, 0),
|
||||
(666, NULL, 'REFLEXIO-EX-01', 'Reflexio_ex', 'REFLEXIO_EXPERIMENTAL', TRUE, FALSE, 255, 0, 0),
|
||||
(667, NULL, 'REFLEXIO-EX-02', 'Reflexio_ex2', 'REFLEXIO_EXPERIMENTAL', TRUE, FALSE, 0, 255, 0),
|
||||
(668, NULL, 'REFLEXIO-EX-03', 'Reflexio_ex3', 'REFLEXIO_EXPERIMENTAL', TRUE, FALSE, 0, 0, 255),
|
||||
(669, NULL, 'REFLEXIO-EX-04', 'Reflexio_ex4', 'REFLEXIO_EXPERIMENTAL', TRUE, FALSE, 255, 255, 0),
|
||||
(56756, 56756, 'KONDOR-FKA-01', 'KONDOR-FKA NO. 1', 'KONDOR_FKA', TRUE, TRUE, 255, 0, 0),
|
||||
(62138, 62138, 'KONDOR-FKA-02', 'KONDOR-FKA NO. 2', 'KONDOR_FKA', TRUE, TRUE, 255, 125, 0);
|
||||
|
||||
INSERT INTO satellite_observation_profile (
|
||||
satellite_id,
|
||||
capture_angle,
|
||||
sun_angle_min,
|
||||
duration_min_seconds,
|
||||
duration_max_seconds,
|
||||
mmi_seconds,
|
||||
daily_max_duration_seconds,
|
||||
revolution_max_duration_seconds
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
1.5,
|
||||
-90.0,
|
||||
10,
|
||||
300,
|
||||
10,
|
||||
2100,
|
||||
300
|
||||
FROM satellite;
|
||||
|
||||
INSERT INTO satellite_slot_profile (satellite_id, cycle_revs, tn_calc, duration_calc_days, max_chain_length) VALUES
|
||||
(1, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(2, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(3, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(4, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(5, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(6, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(7, 275, '2026-03-20T03:00:00', 18, 3),
|
||||
(8, 275, '2026-03-20T03:18:50.782779625', 18, 3),
|
||||
(9, 275, '2026-03-20T03:37:40.764922495', 18, 3),
|
||||
(10, 275, '2026-03-20T03:56:32.881376251', 18, 3),
|
||||
(11, 275, '2026-03-20T04:15:27.865948215', 18, 3),
|
||||
(12, 275, '2026-03-20T04:36:17.199469748', 18, 3),
|
||||
(13, 275, '2026-03-20T04:36:17.199469748', 18, 3),
|
||||
(14, 275, '2026-03-20T04:36:17.199469748', 18, 3),
|
||||
(15, 275, '2026-03-20T04:36:17.199469748', 18, 3),
|
||||
(16, 275, '2026-03-20T04:36:17.199469748', 18, 3),
|
||||
(17, 275, '2026-03-20T06:12:34.090912147', 18, 3),
|
||||
(18, 275, '2026-03-20T06:12:34.090912147', 18, 3),
|
||||
(19, 275, '2026-03-20T06:12:34.090912147', 18, 3),
|
||||
(20, 275, '2026-03-20T06:12:34.090912147', 18, 3),
|
||||
(21, 275, '2026-03-20T06:12:34.090912147', 18, 3),
|
||||
(22, 275, '2026-03-20T07:20:00', 18, 3),
|
||||
(666, 500, '2028-06-01T00:00:00', 31, 3),
|
||||
(667, 500, '2028-06-01T00:00:00', 31, 3),
|
||||
(668, 500, '2028-06-01T00:00:00', 16, 3),
|
||||
(669, 500, '2028-06-01T00:00:00', 16, 3),
|
||||
(56756, 243, '2025-10-27T01:27:51.000031078', 16, 2),
|
||||
(62138, 243, '2025-10-27T00:29:23.162630', 16, 3);
|
||||
|
||||
INSERT INTO satellite_slot_angle (satellite_id, position, angle_begin, angle_end)
|
||||
SELECT satellite_id, position, angle_begin, angle_end
|
||||
FROM (VALUES
|
||||
(56756),
|
||||
(62138)
|
||||
) AS satellites(satellite_id)
|
||||
CROSS JOIN (VALUES
|
||||
(1, 20.00, 23.00),
|
||||
(2, 23.00, 26.00),
|
||||
(3, 26.00, 29.00),
|
||||
(4, 29.00, 32.00),
|
||||
(5, 32.00, 35.00),
|
||||
(6, 35.00, 38.00),
|
||||
(7, 38.00, 41.00),
|
||||
(8, 41.00, 44.00),
|
||||
(9, 44.00, 47.00),
|
||||
(10, 47.00, 50.00)
|
||||
) AS angles(position, angle_begin, angle_end);
|
||||
|
||||
INSERT INTO satellite_slot_angle (satellite_id, position, angle_begin, angle_end)
|
||||
SELECT satellite_id, position, angle_begin, angle_end
|
||||
FROM (VALUES
|
||||
(1),
|
||||
(2),
|
||||
(3),
|
||||
(4),
|
||||
(5),
|
||||
(6),
|
||||
(22)
|
||||
) AS satellites(satellite_id)
|
||||
CROSS JOIN (VALUES
|
||||
(1, 18.50, 21.58),
|
||||
(2, 20.85, 23.82),
|
||||
(3, 23.12, 25.98),
|
||||
(4, 25.30, 28.05),
|
||||
(5, 27.40, 30.03),
|
||||
(6, 29.41, 31.92),
|
||||
(7, 31.33, 33.73),
|
||||
(8, 33.16, 35.45),
|
||||
(9, 34.91, 37.08),
|
||||
(10, 36.57, 38.63),
|
||||
(11, 38.15, 40.11),
|
||||
(12, 39.65, 41.52),
|
||||
(13, 41.08, 42.85),
|
||||
(14, 42.43, 44.11),
|
||||
(15, 43.71, 45.30),
|
||||
(16, 44.93, 46.44),
|
||||
(17, 46.08, 47.51),
|
||||
(18, 47.18, 48.54),
|
||||
(19, 48.22, 49.51),
|
||||
(20, 49.20, 50.42)
|
||||
) AS angles(position, angle_begin, angle_end);
|
||||
|
||||
INSERT INTO satellite_slot_angle (satellite_id, position, angle_begin, angle_end)
|
||||
SELECT satellite_id, position, angle_begin, angle_end
|
||||
FROM (VALUES
|
||||
(7),
|
||||
(8),
|
||||
(9),
|
||||
(10),
|
||||
(11),
|
||||
(12),
|
||||
(13),
|
||||
(14),
|
||||
(15),
|
||||
(16),
|
||||
(17),
|
||||
(18),
|
||||
(19),
|
||||
(20),
|
||||
(21)
|
||||
) AS satellites(satellite_id)
|
||||
CROSS JOIN (VALUES
|
||||
(1, 25.00, 23.70),
|
||||
(2, 23.70, 22.40),
|
||||
(3, 22.40, 21.10),
|
||||
(4, 21.10, 19.80),
|
||||
(5, 19.80, 18.50),
|
||||
(6, 18.50, 17.20),
|
||||
(7, 17.20, 15.90),
|
||||
(8, 15.90, 14.60),
|
||||
(9, 14.60, 13.30),
|
||||
(10, 13.30, 12.00),
|
||||
(11, 12.00, 10.70),
|
||||
(12, 10.70, 9.40),
|
||||
(13, 9.40, 8.10),
|
||||
(14, 8.10, 6.80),
|
||||
(15, 6.80, 5.50),
|
||||
(16, 5.50, 4.20),
|
||||
(17, 4.20, 2.90),
|
||||
(18, 2.90, 1.60),
|
||||
(19, 1.60, 0.30),
|
||||
(20, 0.30, -1.00),
|
||||
(21, -1.00, -2.30),
|
||||
(22, -2.30, -3.60),
|
||||
(23, -3.60, -4.90),
|
||||
(24, -4.90, -6.20),
|
||||
(25, -6.20, -7.50),
|
||||
(26, -7.50, -8.80),
|
||||
(27, -8.80, -10.10),
|
||||
(28, -10.10, -11.40),
|
||||
(29, -11.40, -12.70),
|
||||
(30, -12.70, -14.00),
|
||||
(31, -14.00, -15.30),
|
||||
(32, -15.30, -16.60),
|
||||
(33, -16.60, -17.90),
|
||||
(34, -17.90, -19.20),
|
||||
(35, -19.20, -20.50),
|
||||
(36, -20.50, -21.80),
|
||||
(37, -21.80, -23.10),
|
||||
(38, -23.10, -24.40),
|
||||
(39, -24.40, -25.70)
|
||||
) AS angles(position, angle_begin, angle_end);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
INSERT INTO satellite_slot_angle (satellite_id, position, angle_begin, angle_end)
|
||||
SELECT satellite_id, position, angle_begin, angle_end
|
||||
FROM (VALUES
|
||||
(666),
|
||||
(667),
|
||||
(668),
|
||||
(669)
|
||||
) AS satellites(satellite_id)
|
||||
CROSS JOIN (VALUES
|
||||
(40, 30.00, 28.70),
|
||||
(41, 28.70, 27.40),
|
||||
(42, 27.40, 26.10),
|
||||
(43, 26.10, 24.80),
|
||||
(44, 24.80, 23.50),
|
||||
(45, 23.50, 22.20),
|
||||
(46, 22.20, 20.90),
|
||||
(47, 20.90, 19.60),
|
||||
(48, 19.60, 18.30),
|
||||
(49, 18.30, 17.00),
|
||||
(50, 17.00, 15.70),
|
||||
(51, 15.70, 14.40),
|
||||
(52, 14.40, 13.10),
|
||||
(53, 13.10, 11.80),
|
||||
(54, 11.80, 10.50),
|
||||
(55, 10.50, 9.20),
|
||||
(56, 9.20, 7.90),
|
||||
(57, 7.90, 6.60),
|
||||
(58, 6.60, 5.30),
|
||||
(59, 5.30, 4.00),
|
||||
(60, 4.00, 2.70),
|
||||
(61, 2.70, 1.40),
|
||||
(62, 1.40, 0.10),
|
||||
(63, 0.10, -1.20),
|
||||
(64, -1.20, -2.50),
|
||||
(65, -2.50, -3.80),
|
||||
(66, -3.80, -5.10),
|
||||
(67, -5.10, -6.40),
|
||||
(68, -6.40, -7.70),
|
||||
(69, -7.70, -9.00),
|
||||
(70, -9.00, -10.30),
|
||||
(71, -10.30, -11.60),
|
||||
(72, -11.60, -12.90),
|
||||
(73, -12.90, -14.20),
|
||||
(74, -14.20, -15.50),
|
||||
(75, -15.50, -16.80),
|
||||
(76, -16.80, -18.10),
|
||||
(77, -18.10, -19.40),
|
||||
(78, -19.40, -20.70),
|
||||
(79, -20.70, -22.00),
|
||||
(80, -22.00, -23.30),
|
||||
(81, -23.30, -24.60),
|
||||
(82, -24.60, -25.90),
|
||||
(83, -25.90, -27.20),
|
||||
(84, -27.20, -28.50),
|
||||
(85, -28.50, -29.80),
|
||||
(86, -29.80, -31.10)
|
||||
) AS angles(position, angle_begin, angle_end);
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
CREATE TABLE IF NOT EXISTS satellite_group (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS satellite_group_satellite (
|
||||
group_id BIGINT NOT NULL REFERENCES satellite_group(id) ON DELETE CASCADE,
|
||||
satellite_id BIGINT NOT NULL REFERENCES satellite(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (group_id, satellite_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_satellite_group_name ON satellite_group(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_satellite_group_satellite_satellite_id
|
||||
ON satellite_group_satellite(satellite_id);
|
||||
|
||||
INSERT INTO satellite_group (name)
|
||||
SELECT 'KONDOR'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM satellite_group
|
||||
WHERE name = 'KONDOR'
|
||||
);
|
||||
|
||||
INSERT INTO satellite_group_satellite (group_id, satellite_id)
|
||||
SELECT satellite_group.id, satellites.satellite_id
|
||||
FROM satellite_group
|
||||
CROSS JOIN (
|
||||
VALUES
|
||||
(56756),
|
||||
(62138)
|
||||
) AS satellites(satellite_id)
|
||||
WHERE satellite_group.name = 'KONDOR'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM satellite_group_satellite
|
||||
WHERE satellite_group_satellite.group_id = satellite_group.id
|
||||
AND satellite_group_satellite.satellite_id = satellites.satellite_id
|
||||
);
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE satellite_slot_profile
|
||||
ADD COLUMN slot_duration BIGINT NOT NULL DEFAULT 10;
|
||||
Reference in New Issue
Block a user