This commit is contained in:
Brage Skjønborg 2026-01-04 21:51:35 +01:00
parent 6d4fb2d35f
commit 45b1327345
20 changed files with 294 additions and 40 deletions

View File

@ -29,8 +29,10 @@ repositories {
dependencies { dependencies {
/*Spring boot*/ /*Spring boot*/
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter:2.7.0") implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-websocket:2.6.3") implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework:spring-tx")
implementation("io.github.microutils:kotlin-logging-jvm:2.0.11") implementation("io.github.microutils:kotlin-logging-jvm:2.0.11")

View File

@ -0,0 +1,28 @@
package no.iktdev.mediaprocessing.converter.controller
import org.springframework.boot.actuate.health.HealthEndpoint
import org.springframework.boot.actuate.health.Status
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/system")
class ReadinessController(
private val healthEndpoint: HealthEndpoint
) {
@GetMapping("/ready")
fun ready(): ResponseEntity<String> {
val health = healthEndpoint.health()
return if (health.status == Status.UP) {
ResponseEntity.ok("READY")
} else {
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("NOT_READY: ${health.status}")
}
}
}

View File

@ -6,12 +6,15 @@ spring:
enabled: true enabled: true
locations: classpath:flyway locations: classpath:flyway
baseline-on-migrate: true baseline-on-migrate: true
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
logging: logging:
level: level:

View File

@ -25,4 +25,9 @@ management:
endpoints: endpoints:
web: web:
exposure: exposure:
include: mappings include:
- mappings
- health
endpoint:
health:
show-details: always

View File

@ -22,12 +22,12 @@ repositories {
} }
val exposedVersion = "0.61.0"
dependencies { dependencies {
/*Spring boot*/ /*Spring boot*/
implementation("org.springframework.boot:spring-boot-starter") implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework:spring-tx") implementation("org.springframework:spring-tx")
@ -49,11 +49,6 @@ dependencies {
implementation(project(mapOf("path" to ":shared:ffmpeg"))) implementation(project(mapOf("path" to ":shared:ffmpeg")))
implementation(project(mapOf("path" to ":shared:common"))) implementation(project(mapOf("path" to ":shared:common")))
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
implementation ("mysql:mysql-connector-java:8.0.29")
implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("org.jetbrains.kotlin:kotlin-stdlib")

View File

@ -1,16 +1,28 @@
package no.iktdev.mediaprocessing.coordinator package no.iktdev.mediaprocessing.coordinator
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder
import no.iktdev.mediaprocessing.ffmpeg.dsl.AudioCodec import no.iktdev.mediaprocessing.ffmpeg.dsl.AudioCodec
import no.iktdev.mediaprocessing.ffmpeg.dsl.VideoCodec import no.iktdev.mediaprocessing.ffmpeg.dsl.VideoCodec
import no.iktdev.mediaprocessing.shared.common.silentTry
import java.io.File import java.io.File
data class PeferenceConfig(
val processer: ProcesserPreference
)
data class ProcesserPreference( data class ProcesserPreference(
val videoPreference: VideoPreference? = null, val videoPreference: VideoPreference? = null,
val audioPreference: AudioPreference? = null val audioPreference: AudioPreference? = null
) {
companion object {
fun default(): ProcesserPreference {
return ProcesserPreference(
videoPreference = VideoPreference(VideoCodec.Hevc(), false),
audioPreference = AudioPreference("jpn", AudioCodec.Aac())
) )
}
}
}
data class VideoPreference( data class VideoPreference(
val codec: VideoCodec, val codec: VideoCodec,
@ -25,22 +37,26 @@ data class AudioPreference(
object Preference { object Preference {
fun getProcesserPreference(): ProcesserPreference { fun getProcesserPreference(): ProcesserPreference {
var preference: ProcesserPreference = ProcesserPreference() var preference: ProcesserPreference = ProcesserPreference.default()
CoordinatorEnv.preference.ifExists { CoordinatorEnv.preference.ifExists({
val text = readText() val text = readText()
try { try {
val result = Gson().fromJson(text, ProcesserPreference::class.java) val result = Gson().fromJson(text, PeferenceConfig::class.java)
preference = result preference = result.processer
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }, orElse = {
CoordinatorEnv.preference.writeText(Gson().toJson(PeferenceConfig(preference)))
})
return preference return preference
} }
} }
private fun File.ifExists(block: File.() -> Unit) { private fun File.ifExists(block: File.() -> Unit, orElse: () -> Unit = {}) {
if (this.exists()) { if (this.exists()) {
block() block()
} else {
orElse()
} }
} }

View File

@ -0,0 +1,28 @@
package no.iktdev.mediaprocessing.coordinator.controller
import org.springframework.boot.actuate.health.HealthEndpoint
import org.springframework.boot.actuate.health.Status
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/system")
class ReadinessController(
private val healthEndpoint: HealthEndpoint
) {
@GetMapping("/ready")
fun ready(): ResponseEntity<String> {
val health = healthEndpoint.health()
return if (health.status == Status.UP) {
ResponseEntity.ok("READY")
} else {
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("NOT_READY: ${health.status}")
}
}
}

View File

@ -1,8 +0,0 @@
spring.output.ansi.enabled=always
logging.level.org.apache.kafka=INFO
logging.level.root=INFO
logging.level.Exposed=OFF
logging.level.org.springframework.web.socket.config.WebSocketMessageBrokerStats = WARN
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/actuator

View File

@ -0,0 +1,25 @@
spring:
output:
ansi:
enabled: always
flyway:
enabled: true
locations: classpath:flyway
baseline-on-migrate: true
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
logging:
level:
root: INFO
org.apache.kafka: INFO
Exposed: OFF
org.springframework.web.socket.config.WebSocketMessageBrokerStats: WARN

View File

@ -25,4 +25,9 @@ management:
endpoints: endpoints:
web: web:
exposure: exposure:
include: mappings include:
- mappings
- health
endpoint:
health:
show-details: always

View File

@ -24,9 +24,10 @@ repositories {
dependencies { dependencies {
/*Spring boot*/ /*Spring boot*/
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework:spring-tx")
// implementation("org.springframework.kafka:spring-kafka:3.0.1") // implementation("org.springframework.kafka:spring-kafka:3.0.1")

View File

@ -0,0 +1,28 @@
package no.iktdev.mediaprocessing.processer.controller
import org.springframework.boot.actuate.health.HealthEndpoint
import org.springframework.boot.actuate.health.Status
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/system")
class ReadinessController(
private val healthEndpoint: HealthEndpoint
) {
@GetMapping("/ready")
fun ready(): ResponseEntity<String> {
val health = healthEndpoint.health()
return if (health.status == Status.UP) {
ResponseEntity.ok("READY")
} else {
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("NOT_READY: ${health.status}")
}
}
}

View File

@ -1,5 +0,0 @@
spring.output.ansi.enabled=always
logging.level.org.apache.kafka=INFO
logging.level.root=INFO
logging.level.Exposed=OFF
logging.level.org.springframework.web.socket.config.WebSocketMessageBrokerStats = WARN

View File

@ -0,0 +1,24 @@
spring:
output:
ansi:
enabled: always
flyway:
enabled: true
locations: classpath:flyway
baseline-on-migrate: true
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
logging:
level:
root: INFO
org.apache.kafka: INFO
Exposed: OFF
org.springframework.web.socket.config.WebSocketMessageBrokerStats: WARN

View File

@ -0,0 +1,33 @@
spring:
main:
allow-bean-definition-overriding: true
flyway:
enabled: false
locations: classpath:flyway
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
output:
ansi:
enabled: always
springdoc:
swagger-ui:
path: /open/swagger-ui
logging:
level:
org.springframework.web.socket.config.WebSocketMessageBrokerStats: WARN
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping: DEBUG
management:
endpoints:
web:
exposure:
include:
- mappings
- health
endpoint:
health:
show-details: always

View File

@ -27,6 +27,11 @@ val exposedVersion = "0.61.0"
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.github.pgreze:kotlin-process:1.3.1") implementation("com.github.pgreze:kotlin-process:1.3.1")
implementation("io.github.microutils:kotlin-logging-jvm:2.0.11") implementation("io.github.microutils:kotlin-logging-jvm:2.0.11")
implementation(libs.exfl) implementation(libs.exfl)
@ -34,9 +39,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("com.google.code.gson:gson:2.8.9") implementation("com.google.code.gson:gson:2.8.9")
implementation("org.json:json:20231013") implementation("org.json:json:20231013")
implementation("org.springframework.boot:spring-boot-starter-websocket:2.6.3")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.flywaydb:flyway-core") implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-mysql") implementation("org.flywaydb:flyway-mysql")

View File

@ -0,0 +1,27 @@
package no.iktdev.mediaprocessing.shared.common.database
import org.jetbrains.exposed.sql.transactions.transaction
import org.springframework.boot.actuate.health.Health
import org.springframework.boot.actuate.health.HealthIndicator
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.stereotype.Component
import javax.sql.DataSource
@Component
@ConditionalOnBean(DataSource::class)
class ExposedHealthIndicator : HealthIndicator {
override fun health(): Health {
return try {
transaction {
exec("SELECT 1") { rs ->
if (rs.next()) rs.getInt(1) else null
}
}
Health.up().build()
} catch (e: Exception) {
Health.down(e).build()
}
}
}

View File

@ -0,0 +1,22 @@
package no.iktdev.mediaprocessing.shared.common.database.queries
import no.iktdev.mediaprocessing.shared.common.database.tables.FilesTable
import no.iktdev.mediaprocessing.shared.common.database.withTransaction
import no.iktdev.mediaprocessing.shared.common.dto.FileTableItem
import org.jetbrains.exposed.sql.selectAll
class FilesTableQueries {
fun getFiles(): List<FileTableItem> {
return withTransaction {
FilesTable.selectAll()
.mapNotNull {
FileTableItem(
name = it[FilesTable.name],
uri = it[FilesTable.uri],
checksum = it[FilesTable.checksum],
identifiedAt = it[FilesTable.identifiedAt],
)
}
}.getOrElse { emptyList() }
}
}

View File

@ -0,0 +1,12 @@
package no.iktdev.mediaprocessing.shared.common.database.tables
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.javatime.datetime
object FilesTable: IntIdTable("FILES") {
val name: Column<String> = varchar("NAME", 255)
val uri: Column<String> = text("URI")
val checksum: Column<String> = char("CHECKSUM", 64)
val identifiedAt: Column<java.time.LocalDateTime> = datetime("IDENTIFIED_AT")
}

View File

@ -0,0 +1,10 @@
package no.iktdev.mediaprocessing.shared.common.dto
import java.time.LocalDateTime
data class FileTableItem(
val name: String,
val uri: String,
val checksum: String,
val identifiedAt: LocalDateTime,
)