feat(subtitle): 자막 작업 저장 구조와 계약을 추가한다
This commit is contained in:
@@ -52,6 +52,9 @@ data class AudioContent(
|
||||
var content: String? = null
|
||||
var coverImage: String? = null
|
||||
|
||||
@Column(length = 36)
|
||||
var latestSubtitleJobId: String? = null
|
||||
|
||||
@OneToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "theme_id", nullable = false)
|
||||
var theme: AudioContentTheme? = null
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.adapter.persistence
|
||||
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.GeneratedValue
|
||||
import javax.persistence.GenerationType
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Table
|
||||
import javax.persistence.UniqueConstraint
|
||||
|
||||
@Entity
|
||||
@Table(name = "subtitle", uniqueConstraints = [UniqueConstraint(columnNames = ["job_id", "language"])])
|
||||
class Subtitle(
|
||||
@Column(name = "job_id", nullable = false, length = 36)
|
||||
val jobId: String,
|
||||
@Column(nullable = false, length = 35)
|
||||
val language: String,
|
||||
@Column(nullable = false, length = 16)
|
||||
val format: String,
|
||||
@Column(nullable = false, length = 255)
|
||||
val s3Bucket: String,
|
||||
@Column(nullable = false, length = 255)
|
||||
val s3Key: String
|
||||
) {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long? = null
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.adapter.persistence
|
||||
|
||||
import org.springframework.data.domain.Persistable
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoUnit
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Table
|
||||
import javax.persistence.Transient
|
||||
|
||||
@Entity
|
||||
@Table(name = "subtitle_callback_receipt")
|
||||
class SubtitleCallbackReceipt(
|
||||
@Id
|
||||
@Column(length = 255)
|
||||
val eventId: String,
|
||||
@Column(nullable = false, length = 36)
|
||||
val jobId: String,
|
||||
@Column(nullable = false, length = 64)
|
||||
val bodyHash: String
|
||||
) : Persistable<String> {
|
||||
@Column(nullable = false, columnDefinition = "TIMESTAMP")
|
||||
val createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)
|
||||
|
||||
override fun getId(): String = eventId
|
||||
|
||||
@Transient
|
||||
override fun isNew(): Boolean = true
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.adapter.persistence
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface SubtitleCallbackReceiptRepository : JpaRepository<SubtitleCallbackReceipt, String>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.adapter.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.subtitle.domain.SubtitleJobStatus
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoUnit
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.EnumType
|
||||
import javax.persistence.Enumerated
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Table
|
||||
import javax.persistence.UniqueConstraint
|
||||
|
||||
@Entity
|
||||
@Table(name = "subtitle_job", uniqueConstraints = [UniqueConstraint(columnNames = ["source_bucket", "source_key"])])
|
||||
class SubtitleJob(
|
||||
@Id
|
||||
@Column(length = 36)
|
||||
val jobId: String,
|
||||
@Column(nullable = false)
|
||||
val contentId: Long,
|
||||
@Column(name = "source_bucket", nullable = false)
|
||||
val sourceBucket: String,
|
||||
@Column(name = "source_key", nullable = false)
|
||||
val sourceKey: String,
|
||||
@Column(nullable = false)
|
||||
val outputKey: String,
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 32)
|
||||
var status: SubtitleJobStatus = SubtitleJobStatus.PENDING
|
||||
) {
|
||||
var errorCode: String? = null
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
var errorMessage: String? = null
|
||||
var sourceUploadErrorCode: String? = null
|
||||
|
||||
@Column(columnDefinition = "TIMESTAMP")
|
||||
var sourceUploadErrorAt: LocalDateTime? = null
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TIMESTAMP")
|
||||
val createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TIMESTAMP")
|
||||
var updatedAt: LocalDateTime = createdAt
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.adapter.persistence
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Lock
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import javax.persistence.LockModeType
|
||||
|
||||
interface SubtitleJobRepository : JpaRepository<SubtitleJob, String> {
|
||||
fun findBySourceBucketAndSourceKey(sourceBucket: String, sourceKey: String): SubtitleJob?
|
||||
fun findFirstByContentIdOrderByCreatedAtAsc(contentId: Long): SubtitleJob?
|
||||
|
||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||
@Query("select j from SubtitleJob j where j.jobId = :jobId")
|
||||
fun findForUpdate(@Param("jobId") jobId: String): SubtitleJob?
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.adapter.persistence
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface SubtitleRepository : JpaRepository<Subtitle, Long> {
|
||||
fun findAllByJobId(jobId: String): List<Subtitle>
|
||||
fun findByJobIdAndLanguage(jobId: String, language: String): Subtitle?
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.domain
|
||||
|
||||
enum class SubtitleJobStatus {
|
||||
PENDING, PROCESSING, COMPLETED, PARTIAL_COMPLETED, FAILED;
|
||||
|
||||
val terminal: Boolean
|
||||
get() = this == COMPLETED || this == PARTIAL_COMPLETED || this == FAILED
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package kr.co.vividnext.sodalive.v2.subtitle.dto
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.subtitle.domain.SubtitleJobStatus
|
||||
|
||||
data class SubtitleSourceResponse(val bucket: String, val key: String, val versionId: String? = null)
|
||||
|
||||
data class SubtitleJobResponse(
|
||||
val jobId: String,
|
||||
val contentId: Long,
|
||||
val status: SubtitleJobStatus,
|
||||
val source: SubtitleSourceResponse
|
||||
)
|
||||
|
||||
data class SubtitleStartedRequest(val transcribeJobName: String, val status: String)
|
||||
|
||||
data class SubtitleResultRequest(val language: String, val format: String, val s3Bucket: String, val s3Key: String)
|
||||
|
||||
data class SubtitleCallbackRequest(
|
||||
val eventId: String,
|
||||
val jobId: String,
|
||||
val contentId: Long,
|
||||
val transcribeJobName: String,
|
||||
val status: String,
|
||||
val subtitles: List<SubtitleResultRequest>,
|
||||
val errorCode: String?,
|
||||
val errorMessage: String?
|
||||
)
|
||||
|
||||
data class ContentSubtitleResponse(val language: String, val format: String, val url: String)
|
||||
Reference in New Issue
Block a user