feat: Reactionを追加

This commit is contained in:
usbharu 2024-09-08 00:58:18 +09:00
parent 6c83306f0d
commit 72c9b8b7c5
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
17 changed files with 116 additions and 34 deletions

View File

@ -0,0 +1,22 @@
package dev.usbharu.hideout.core.domain.event.reaction
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.domain.model.reaction.ReactionId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ReactionEventFactory(private val reaction: Reaction) {
fun createEvent(reactionEvent: ReactionEvent): DomainEvent<ReactionEventBody> =
DomainEvent.create(reactionEvent.eventName, ReactionEventBody(reaction))
}
class ReactionEventBody(
reaction: Reaction
) : DomainEventBody(mapOf("reactionId" to reaction.id)) {
fun getReactionId(): ReactionId = toMap()["reactionId"] as ReactionId
}
enum class ReactionEvent(val eventName: String) {
CREATE("ReactionCreate"),
DELETE("ReactionDelete"),
}

View File

@ -18,7 +18,7 @@ package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory
import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.* import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.*
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.domain.model.support.domain.Domain
@ -52,7 +52,7 @@ class Actor(
var lastUpdateAt: Instant = createdAt, var lastUpdateAt: Instant = createdAt,
alsoKnownAs: Set<ActorId> = emptySet(), alsoKnownAs: Set<ActorId> = emptySet(),
moveTo: ActorId? = null, moveTo: ActorId? = null,
emojiIds: Set<EmojiId>, emojiIds: Set<CustomEmojiId>,
deleted: Boolean, deleted: Boolean,
icon: MediaId?, icon: MediaId?,
banner: MediaId?, banner: MediaId?,

View File

@ -36,7 +36,7 @@ sealed class Emoji {
} }
data class CustomEmoji( data class CustomEmoji(
val id: EmojiId, val id: CustomEmojiId,
override val name: String, override val name: String,
override val domain: Domain, override val domain: Domain,
val instanceId: InstanceId, val instanceId: InstanceId,

View File

@ -17,7 +17,7 @@
package dev.usbharu.hideout.core.domain.model.emoji package dev.usbharu.hideout.core.domain.model.emoji
@JvmInline @JvmInline
value class EmojiId(val emojiId: Long) { value class CustomEmojiId(val emojiId: Long) {
init { init {
require(0 <= emojiId) require(0 <= emojiId)
} }

View File

@ -20,7 +20,7 @@ import dev.usbharu.hideout.core.domain.event.post.PostDomainEventFactory
import dev.usbharu.hideout.core.domain.event.post.PostEvent import dev.usbharu.hideout.core.domain.event.post.PostEvent
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
@ -138,7 +138,7 @@ class Post(
return content.text return content.text
} }
val emojiIds: List<EmojiId> val emojiIds: List<CustomEmojiId>
get() { get() {
if (hide) { if (hide) {
return PostContent.empty.emojiIds return PostContent.empty.emojiIds
@ -217,7 +217,7 @@ class Post(
override fun hashCode(): Int = id.hashCode() override fun hashCode(): Int = id.hashCode()
fun reconstructWith(mediaIds: List<MediaId>, emojis: List<EmojiId>, visibleActors: Set<ActorId>): Post { fun reconstructWith(mediaIds: List<MediaId>, emojis: List<CustomEmojiId>, visibleActors: Set<ActorId>): Post {
return Post( return Post(
id = id, id = id,
actorId = actorId, actorId = actorId,

View File

@ -16,9 +16,9 @@
package dev.usbharu.hideout.core.domain.model.post package dev.usbharu.hideout.core.domain.model.post
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
data class PostContent(val text: String, val content: String, val emojiIds: List<EmojiId>) { data class PostContent(val text: String, val content: String, val emojiIds: List<CustomEmojiId>) {
companion object { companion object {
val empty = PostContent("", "", emptyList()) val empty = PostContent("", "", emptyList())

View File

@ -0,0 +1,49 @@
package dev.usbharu.hideout.core.domain.model.reaction
import dev.usbharu.hideout.core.domain.event.reaction.ReactionEvent
import dev.usbharu.hideout.core.domain.event.reaction.ReactionEventFactory
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import java.time.Instant
class Reaction(
val id: ReactionId,
val postId: PostId,
val actorId: ActorId,
val customEmojiId: CustomEmojiId?,
val unicodeEmoji: UnicodeEmoji,
val createdAt: Instant
) : DomainEventStorable() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Reaction
return id == other.id
}
override fun hashCode(): Int {
return id.hashCode()
}
companion object {
fun create(
id: ReactionId,
postId: PostId,
actorId: ActorId,
customEmojiId: CustomEmojiId?,
unicodeEmoji: UnicodeEmoji,
createdAt: Instant
): Reaction {
return Reaction(
id, postId, actorId, customEmojiId, unicodeEmoji, createdAt
).apply { addDomainEvent(ReactionEventFactory(this).createEvent(ReactionEvent.CREATE)) }
}
}
}

View File

@ -0,0 +1,4 @@
package dev.usbharu.hideout.core.domain.model.reaction
@JvmInline
value class ReactionId(val value: Long)

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.core.domain.model.reaction
interface ReactionRepository {
suspend fun save(reaction: Reaction): Reaction
suspend fun findById(reactionId: String): Reaction?
suspend fun delete(reaction: Reaction)
}

View File

@ -1,7 +1,7 @@
package dev.usbharu.hideout.core.domain.model.timelineobject package dev.usbharu.hideout.core.domain.model.timelineobject
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.filter.FilterResult import dev.usbharu.hideout.core.domain.model.filter.FilterResult
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
@ -28,13 +28,13 @@ class TimelineObject(
visibility: Visibility, visibility: Visibility,
isPureRepost: Boolean, isPureRepost: Boolean,
mediaIds: List<MediaId>, mediaIds: List<MediaId>,
emojiIds: List<EmojiId>, emojiIds: List<CustomEmojiId>,
visibleActors: List<ActorId>, visibleActors: List<ActorId>,
hasMediaInRepost: Boolean, hasMediaInRepost: Boolean,
lastUpdatedAt: Instant, lastUpdatedAt: Instant,
var warnFilters: List<TimelineObjectWarnFilter>, var warnFilters: List<TimelineObjectWarnFilter>,
) { ) {
var isPureRepost = isPureRepost var isPureRepost = isPureRepost
private set private set
var visibleActors = visibleActors var visibleActors = visibleActors

View File

@ -17,7 +17,7 @@
package dev.usbharu.hideout.core.infrastructure.exposed package dev.usbharu.hideout.core.infrastructure.exposed
import dev.usbharu.hideout.core.domain.model.actor.* import dev.usbharu.hideout.core.domain.model.actor.*
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.domain.model.support.domain.Domain
@ -57,7 +57,7 @@ class ActorResultRowMapper : ResultRowMapper<Actor> {
emojiIds = resultRow[Actors.emojis] emojiIds = resultRow[Actors.emojis]
.split(",") .split(",")
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.map { EmojiId(it.toLong()) } .map { CustomEmojiId(it.toLong()) }
.toSet(), .toSet(),
deleted = resultRow[Actors.deleted], deleted = resultRow[Actors.deleted],
icon = resultRow[Actors.icon]?.let { MediaId(it) }, icon = resultRow[Actors.icon]?.let { MediaId(it) },

View File

@ -17,7 +17,7 @@
package dev.usbharu.hideout.core.infrastructure.exposed package dev.usbharu.hideout.core.infrastructure.exposed
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts
@ -55,7 +55,7 @@ class PostQueryMapper(private val postResultRowMapper: ResultRowMapper<Post>) :
.mapNotNull { resultRow: ResultRow -> .mapNotNull { resultRow: ResultRow ->
resultRow resultRow
.getOrNull(PostsEmojis.emojiId) .getOrNull(PostsEmojis.emojiId)
?.let { emojiId -> EmojiId(emojiId) } ?.let { emojiId -> CustomEmojiId(emojiId) }
}, },
visibleActors = it.mapNotNull { resultRow: ResultRow -> visibleActors = it.mapNotNull { resultRow: ResultRow ->
resultRow resultRow

View File

@ -17,8 +17,8 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.domain.model.support.domain.Domain
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
@ -81,7 +81,7 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository,
} }
fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji( fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
id = EmojiId(this[CustomEmojis.id]), id = CustomEmojiId(this[CustomEmojis.id]),
name = this[CustomEmojis.name], name = this[CustomEmojis.name],
domain = Domain(this[CustomEmojis.domain]), domain = Domain(this[CustomEmojis.domain]),
instanceId = InstanceId(this[CustomEmojis.instanceId]), instanceId = InstanceId(this[CustomEmojis.instanceId]),
@ -92,7 +92,7 @@ fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? { fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? {
return CustomEmoji( return CustomEmoji(
id = EmojiId(this.getOrNull(CustomEmojis.id) ?: return null), id = CustomEmojiId(this.getOrNull(CustomEmojis.id) ?: return null),
name = this.getOrNull(CustomEmojis.name) ?: return null, name = this.getOrNull(CustomEmojis.name) ?: return null,
domain = Domain(this.getOrNull(CustomEmojis.domain) ?: return null), domain = Domain(this.getOrNull(CustomEmojis.domain) ?: return null),
instanceId = InstanceId(this.getOrNull(CustomEmojis.instanceId) ?: return null), instanceId = InstanceId(this.getOrNull(CustomEmojis.instanceId) ?: return null),

View File

@ -1,7 +1,7 @@
package dev.usbharu.hideout.core.infrastructure.mongorepository package dev.usbharu.hideout.core.infrastructure.mongorepository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.filter.FilterId import dev.usbharu.hideout.core.domain.model.filter.FilterId
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
@ -151,7 +151,7 @@ data class SpringDataMongoTimelineObject(
visibility = visibility, visibility = visibility,
isPureRepost = isPureRepost, isPureRepost = isPureRepost,
mediaIds = mediaIds.map { MediaId(it) }, mediaIds = mediaIds.map { MediaId(it) },
emojiIds = emojiIds.map { EmojiId(it) }, emojiIds = emojiIds.map { CustomEmojiId(it) },
visibleActors = visibleActors.map { ActorId(it) }, visibleActors = visibleActors.map { ActorId(it) },
hasMediaInRepost = hasMediaInRepost, hasMediaInRepost = hasMediaInRepost,
lastUpdatedAt = Instant.ofEpochSecond(lastUpdatedAt), lastUpdatedAt = Instant.ofEpochSecond(lastUpdatedAt),

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.core.domain.model.actor package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.domain.model.support.domain.Domain
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
@ -35,7 +35,7 @@ object TestActorFactory {
suspend: Boolean = false, suspend: Boolean = false,
alsoKnownAs: Set<ActorId> = emptySet(), alsoKnownAs: Set<ActorId> = emptySet(),
moveTo: Long? = null, moveTo: Long? = null,
emojiIds: Set<EmojiId> = emptySet(), emojiIds: Set<CustomEmojiId> = emptySet(),
deleted: Boolean = false, deleted: Boolean = false,
roles: Set<Role> = emptySet(), roles: Set<Role> = emptySet(),
): Actor { ): Actor {

View File

@ -3,18 +3,18 @@ package dev.usbharu.hideout.core.domain.model.emoji
import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Assertions.assertDoesNotThrow
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class EmojiIdTest { class CustomEmojiIdTest {
@Test @Test
fun emojiIdは0以上である必要がある() { fun emojiIdは0以上である必要がある() {
org.junit.jupiter.api.assertThrows<IllegalArgumentException> { org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
EmojiId(-1) CustomEmojiId(-1)
} }
} }
@Test @Test
fun emojiIdは0以上なら設定できる() { fun emojiIdは0以上なら設定できる() {
assertDoesNotThrow { assertDoesNotThrow {
EmojiId(1) CustomEmojiId(1)
} }
} }
} }

View File

@ -4,7 +4,7 @@ import dev.usbharu.hideout.core.domain.event.post.PostEvent
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -447,7 +447,7 @@ class PostTest {
@Test @Test
fun `emojiIds hideがtrueの時empty`() { fun `emojiIds hideがtrueの時empty`() {
val actor = TestActorFactory.create() val actor = TestActorFactory.create()
val emojiIds = listOf(EmojiId(1), EmojiId(2)) val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
val post = Post.create( val post = Post.create(
id = PostId(1), id = PostId(1),
actorId = actor.id, actorId = actor.id,
@ -473,7 +473,7 @@ class PostTest {
@Test @Test
fun `emojiIds hideがfalseの時中身が返される`() { fun `emojiIds hideがfalseの時中身が返される`() {
val actor = TestActorFactory.create() val actor = TestActorFactory.create()
val emojiIds = listOf(EmojiId(1), EmojiId(2)) val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
val post = Post.create( val post = Post.create(
id = PostId(1), id = PostId(1),
actorId = actor.id, actorId = actor.id,
@ -500,7 +500,7 @@ class PostTest {
val post = TestPostFactory.create() val post = TestPostFactory.create()
val mediaIds = listOf<MediaId>(MediaId(1)) val mediaIds = listOf<MediaId>(MediaId(1))
val visibleActors = setOf<ActorId>((ActorId(2))) val visibleActors = setOf<ActorId>((ActorId(2)))
val emojis = listOf<EmojiId>(EmojiId(3)) val emojis = listOf<CustomEmojiId>(CustomEmojiId(3))
val reconstructWith = post.reconstructWith(mediaIds, emojis, visibleActors) val reconstructWith = post.reconstructWith(mediaIds, emojis, visibleActors)
assertEquals(mediaIds, reconstructWith.mediaIds) assertEquals(mediaIds, reconstructWith.mediaIds)
@ -511,7 +511,7 @@ class PostTest {
@Test @Test
fun `mediaIds hideがtrueの時emptyが返される`() { fun `mediaIds hideがtrueの時emptyが返される`() {
val actor = TestActorFactory.create() val actor = TestActorFactory.create()
val emojiIds = listOf(EmojiId(1), EmojiId(2)) val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
val mediaIds = listOf(MediaId(1)) val mediaIds = listOf(MediaId(1))
val post = Post.create( val post = Post.create(
id = PostId(1), id = PostId(1),
@ -538,7 +538,7 @@ class PostTest {
@Test @Test
fun `mediaIds hideがfalseの時中身が返される`() { fun `mediaIds hideがfalseの時中身が返される`() {
val actor = TestActorFactory.create() val actor = TestActorFactory.create()
val emojiIds = listOf(EmojiId(1), EmojiId(2)) val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
val mediaIds = listOf(MediaId(2)) val mediaIds = listOf(MediaId(2))
val post = Post.create( val post = Post.create(
id = PostId(1), id = PostId(1),
@ -603,7 +603,7 @@ class PostTest {
fun `restore 指定された引数で再構成されCHECKUPDATEイベントが発生する`() { fun `restore 指定された引数で再構成されCHECKUPDATEイベントが発生する`() {
val post = TestPostFactory.create(deleted = true) val post = TestPostFactory.create(deleted = true)
val postContent = PostContent("aiueo", "aiueo", listOf(EmojiId(1))) val postContent = PostContent("aiueo", "aiueo", listOf(CustomEmojiId(1)))
val overview = PostOverview("overview") val overview = PostOverview("overview")
val mediaIds = listOf(MediaId(1)) val mediaIds = listOf(MediaId(1))
post.restore( post.restore(
@ -622,7 +622,7 @@ class PostTest {
fun deletedがfalseの時失敗する() { fun deletedがfalseの時失敗する() {
val post = TestPostFactory.create(deleted = false) val post = TestPostFactory.create(deleted = false)
val postContent = PostContent("aiueo", "aiueo", listOf(EmojiId(1))) val postContent = PostContent("aiueo", "aiueo", listOf(CustomEmojiId(1)))
val overview = PostOverview("overview") val overview = PostOverview("overview")
val mediaIds = listOf(MediaId(1)) val mediaIds = listOf(MediaId(1))
assertThrows<IllegalArgumentException> { assertThrows<IllegalArgumentException> {