-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: News 엔티티 추가 (#2) #6
Changes from all commits
44280aa
dc4798e
a38bc10
a6df6b6
4716d20
3a69a57
4b27435
2f3a6f7
0d3e3f8
1ef615c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package kr.galaxyhub.sc.common.domain | ||
|
||
import com.github.f4b6a3.ulid.UlidCreator | ||
import jakarta.persistence.Column | ||
import jakarta.persistence.Id | ||
import jakarta.persistence.MappedSuperclass | ||
import jakarta.persistence.PostLoad | ||
import jakarta.persistence.PostPersist | ||
import java.util.Objects | ||
import java.util.UUID | ||
import org.hibernate.proxy.HibernateProxy | ||
import org.springframework.data.domain.Persistable | ||
|
||
@MappedSuperclass | ||
abstract class PrimaryKeyEntity : Persistable<UUID> { | ||
|
||
@Id | ||
@Column(columnDefinition = "uuid") | ||
private val id: UUID = UlidCreator.getMonotonicUlid().toUuid() | ||
|
||
@Transient | ||
private var _isNew = true | ||
|
||
override fun getId(): UUID = id | ||
|
||
override fun isNew(): Boolean = _isNew | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (other == null) { | ||
return false | ||
} | ||
if (other !is HibernateProxy && this::class != other::class) { | ||
return false | ||
} | ||
return id == getIdentifier(other) | ||
} | ||
|
||
private fun getIdentifier(obj: Any): Any { | ||
return when (obj) { | ||
is HibernateProxy -> obj.hibernateLazyInitializer.identifier | ||
else -> (obj as PrimaryKeyEntity).id | ||
} | ||
} | ||
|
||
override fun hashCode(): Int = Objects.hashCode(id) | ||
|
||
@PostPersist | ||
@PostLoad | ||
protected fun load() { | ||
_isNew = false | ||
} | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
import jakarta.persistence.Column | ||
import jakarta.persistence.Embedded | ||
import jakarta.persistence.Entity | ||
import jakarta.persistence.EnumType | ||
import jakarta.persistence.Enumerated | ||
import jakarta.persistence.FetchType | ||
import jakarta.persistence.GeneratedValue | ||
import jakarta.persistence.GenerationType | ||
import jakarta.persistence.Id | ||
import jakarta.persistence.JoinColumn | ||
import jakarta.persistence.Lob | ||
import jakarta.persistence.ManyToOne | ||
|
||
@Entity | ||
class Content( | ||
news: News, | ||
newsInformation: NewsInformation, | ||
language: Language, | ||
content: String, | ||
) { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val sequence: Long? = null | ||
|
||
@ManyToOne(fetch = FetchType.LAZY, optional = false) | ||
@JoinColumn(name = "news_id", nullable = false) | ||
var news: News = news | ||
protected set | ||
|
||
@Embedded | ||
var newsInformation: NewsInformation = newsInformation | ||
protected set | ||
|
||
@Enumerated(EnumType.STRING) | ||
@Column(name = "language", nullable = false) | ||
var language: Language = language | ||
protected set | ||
|
||
@Lob | ||
@Column(name = "content", nullable = false) | ||
var content: String = content | ||
protected set | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
import jakarta.persistence.AttributeConverter | ||
import jakarta.persistence.Converter | ||
import java.util.EnumSet | ||
|
||
@Converter | ||
class EnumSetLanguageConverter : AttributeConverter<EnumSet<Language>, String> { | ||
|
||
override fun convertToDatabaseColumn(attribute: EnumSet<Language>): String { | ||
return attribute.joinToString(separator = SEPARATOR) | ||
} | ||
|
||
override fun convertToEntityAttribute(dbData: String?): EnumSet<Language> { | ||
return if (dbData.isNullOrBlank()) { | ||
EnumSet.noneOf(Language::class.java) | ||
} else { | ||
dbData.split(SEPARATOR) | ||
.map { Language.valueOf(it) } | ||
.toCollection(EnumSet.noneOf(Language::class.java)) | ||
} | ||
} | ||
|
||
companion object { | ||
|
||
private const val SEPARATOR = "," | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
enum class Language { | ||
ENGLISH, | ||
KOREAN, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
import jakarta.persistence.CascadeType | ||
import jakarta.persistence.Column | ||
import jakarta.persistence.Convert | ||
import jakarta.persistence.Embedded | ||
import jakarta.persistence.Entity | ||
import jakarta.persistence.EnumType | ||
import jakarta.persistence.Enumerated | ||
import jakarta.persistence.FetchType | ||
import jakarta.persistence.OneToMany | ||
import java.time.ZonedDateTime | ||
import java.util.EnumSet | ||
import kr.galaxyhub.sc.common.domain.PrimaryKeyEntity | ||
|
||
@Entity | ||
class News( | ||
newsType: NewsType, | ||
originId: Long, | ||
originUrl: String, | ||
publishedAt: ZonedDateTime, | ||
) : PrimaryKeyEntity() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 원문의 URL 를 담을 수 있는 컬럼이 있다면 좋을 것 같습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 원문 링크를 함께 제공하여 다음과 같은 효과를 얻을 수 있습니다.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 한번 검토해주시면 감사드립니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
@Enumerated(EnumType.STRING) | ||
@Column(name = "news_type", nullable = false) | ||
var newsType: NewsType = newsType | ||
protected set | ||
|
||
@Column(name = "origin_id", nullable = false) | ||
var originId: Long = originId | ||
protected set | ||
|
||
@Column(name = "originUrl", nullable = false) | ||
var originUrl: String = originUrl | ||
protected set | ||
|
||
@Column(name = "published_at", nullable = false) | ||
var publishedAt: ZonedDateTime = publishedAt | ||
protected set | ||
|
||
@Embedded | ||
var newsInformation: NewsInformation = NewsInformation.EMPTY | ||
protected set | ||
|
||
@Convert(converter = EnumSetLanguageConverter::class) | ||
@Column(name = "support_languages", nullable = false) | ||
private val mutableSupportLanguages: EnumSet<Language> = EnumSet.noneOf(Language::class.java) | ||
val supportLanguages: Set<Language> get() = mutableSupportLanguages.toHashSet() | ||
|
||
@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], mappedBy = "news") | ||
private val contents: MutableList<Content> = mutableListOf() | ||
|
||
fun addContent(content: Content) { | ||
validateAddContent(content) | ||
if (mutableSupportLanguages.isEmpty()) { | ||
newsInformation = content.newsInformation | ||
} | ||
contents.add(content) | ||
mutableSupportLanguages.add(content.language) | ||
} | ||
|
||
private fun validateAddContent(content: Content) { | ||
if (content.news != this) { | ||
throw IllegalArgumentException("컨텐츠에 등록된 뉴스가 동일하지 않습니다.") // TODO 명확한 예외 정의할 것 | ||
} | ||
if (mutableSupportLanguages.contains(content.language)) { | ||
throw IllegalArgumentException("이미 해당 언어로 작성된 뉴스가 있습니다.") // TODO 명확한 예외 정의할 것 | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
import jakarta.persistence.Column | ||
import jakarta.persistence.Embeddable | ||
|
||
@Embeddable | ||
data class NewsInformation( | ||
@Column(name = "title", nullable = true) | ||
val title: String?, | ||
|
||
@Column(name = "excerpt", nullable = true) | ||
val excerpt: String? | ||
) { | ||
companion object { | ||
|
||
val EMPTY = NewsInformation(null, null) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
enum class NewsType { | ||
PATCH_NOTE, | ||
NEWS, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package kr.galaxyhub.sc.news.domain | ||
|
||
import io.kotest.assertions.assertSoftly | ||
import io.kotest.core.spec.style.DescribeSpec | ||
import io.kotest.inspectors.forAll | ||
import io.kotest.matchers.collections.shouldContainExactly | ||
import io.kotest.matchers.collections.shouldHaveSize | ||
import io.kotest.matchers.shouldBe | ||
import io.kotest.matchers.string.shouldContain | ||
import java.util.EnumSet | ||
|
||
class EnumSetLanguageConverterTest : DescribeSpec({ | ||
|
||
val enumSetLanguageConverter = EnumSetLanguageConverter() | ||
|
||
describe("convertToDatabaseColumn") { | ||
context("요소가 한 개 이면") { | ||
val attribute = EnumSet.of(Language.ENGLISH) | ||
|
||
it(",가 없는 문자열을 반환한다.") { | ||
enumSetLanguageConverter.convertToDatabaseColumn(attribute) shouldBe "ENGLISH" | ||
} | ||
} | ||
Comment on lines
+16
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
context("요소가 여러 개 이면") { | ||
val attribute = EnumSet.of(Language.ENGLISH, Language.KOREAN) | ||
|
||
it(",가 있는 문자열을 반환한다.") { | ||
val expect = enumSetLanguageConverter.convertToDatabaseColumn(attribute) | ||
assertSoftly { | ||
expect shouldContain "," | ||
expect shouldContain "ENGLISH" | ||
expect shouldContain "KOREAN" | ||
} | ||
} | ||
} | ||
|
||
context("요소가 없으면") { | ||
val attribute = EnumSet.noneOf(Language::class.java) | ||
|
||
it("빈 문자열을 반환한다.") { | ||
enumSetLanguageConverter.convertToDatabaseColumn(attribute) shouldBe "" | ||
} | ||
} | ||
} | ||
|
||
describe("convertToEntityAttribute") { | ||
context(",가 없는 문자열이면") { | ||
val dbData = "ENGLISH" | ||
|
||
it("한 개의 요소를 반환한다.") { | ||
val expect = enumSetLanguageConverter.convertToEntityAttribute(dbData) | ||
|
||
assertSoftly { | ||
expect shouldHaveSize 1 | ||
expect shouldContainExactly setOf(Language.ENGLISH) | ||
} | ||
} | ||
} | ||
|
||
context(",가 있는 문자열이면") { | ||
val dbData = "ENGLISH,KOREAN" | ||
|
||
it("여러 개의 요소를 반환한다.") { | ||
val expect = enumSetLanguageConverter.convertToEntityAttribute(dbData) | ||
|
||
assertSoftly { | ||
expect shouldHaveSize 2 | ||
expect shouldContainExactly setOf(Language.ENGLISH, Language.KOREAN) | ||
} | ||
} | ||
} | ||
|
||
context("null 또는 빈 문자열이면") { | ||
val dbData = listOf(null, "", " ", "\t", "\n") | ||
|
||
it("비어 있는 요소를 반환한다.") { | ||
dbData.forAll { | ||
enumSetLanguageConverter.convertToEntityAttribute(it) shouldHaveSize 0 | ||
} | ||
} | ||
} | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 클래스는 식별자를 UUID로 제공하기 위함인데 링크 참고하시면 이해되실 겁니다!