feat: カスタム絵文字のリアクションの対応

This commit is contained in:
usbharu 2023-12-22 02:29:37 +09:00
parent 0607fe2bc5
commit 32e60dc6f8
5 changed files with 143 additions and 20 deletions

View File

@ -12,9 +12,9 @@ data class CustomEmoji(
val id: Long,
override val name: String,
override val domain: String,
val instanceId: Long,
val instanceId: Long?,
val url: String,
val category: String,
val category: String?,
val createdAt: Instant
) : Emoji() {
override fun id(): String {

View File

@ -1,3 +1,5 @@
package dev.usbharu.hideout.core.domain.model.reaction
data class Reaction(val id: Long, val emojiId: Long, val postId: Long, val actorId: Long)
import dev.usbharu.hideout.core.domain.model.emoji.Emoji
data class Reaction(val id: Long, val emoji: Emoji, val postId: Long, val actorId: Long)

View File

@ -0,0 +1,78 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.CurrentTimestamp
import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class CustomEmojiRepositoryImpl : CustomEmojiRepository, AbstractRepository() {
override suspend fun save(customEmoji: CustomEmoji): CustomEmoji = query {
val singleOrNull = CustomEmojis.select { CustomEmojis.id eq customEmoji.id }.forUpdate().singleOrNull()
if (singleOrNull == null) {
CustomEmojis.insert {
it[CustomEmojis.id] = customEmoji.id
it[CustomEmojis.name] = customEmoji.name
it[CustomEmojis.domain] = customEmoji.domain
it[CustomEmojis.instanceId] = customEmoji.instanceId
it[CustomEmojis.url] = customEmoji.url
it[CustomEmojis.category] = customEmoji.category
it[CustomEmojis.createdAt] = customEmoji.createdAt
}
} else {
CustomEmojis.update({ CustomEmojis.id eq customEmoji.id }) {
it[CustomEmojis.name] = customEmoji.name
it[CustomEmojis.domain] = customEmoji.domain
it[CustomEmojis.instanceId] = customEmoji.instanceId
it[CustomEmojis.url] = customEmoji.url
it[CustomEmojis.category] = customEmoji.category
it[CustomEmojis.createdAt] = customEmoji.createdAt
}
}
return@query customEmoji
}
override suspend fun findById(id: Long): CustomEmoji? = query {
return@query CustomEmojis.select { CustomEmojis.id eq id }.singleOrNull()?.toCustomEmoji()
}
override suspend fun delete(customEmoji: CustomEmoji): Unit = query {
CustomEmojis.deleteWhere { CustomEmojis.id eq customEmoji.id }
}
override val logger: Logger
get() = Companion.logger
companion object {
private val logger = LoggerFactory.getLogger(CustomEmojiRepositoryImpl::class.java)
}
}
fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
this[CustomEmojis.id],
this[CustomEmojis.name],
this[CustomEmojis.domain],
this[CustomEmojis.instanceId],
this[CustomEmojis.url],
this[CustomEmojis.category],
this[CustomEmojis.createdAt]
)
object CustomEmojis : Table("emojis") {
val id = long("id")
val name = varchar("name", 1000)
val domain = varchar("domain", 1000)
val instanceId = long("instance_id").references(Instance.id).nullable()
val url = varchar("url", 255).uniqueIndex()
val category = varchar("category", 255).nullable()
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp())
override val primaryKey: PrimaryKey = PrimaryKey(id)
init {
uniqueIndex(name, instanceId)
}
}

View File

@ -1,6 +1,8 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import org.jetbrains.exposed.dao.id.LongIdTable
@ -23,13 +25,25 @@ class ReactionRepositoryImpl(
if (Reactions.select { Reactions.id eq reaction.id }.forUpdate().empty()) {
Reactions.insert {
it[id] = reaction.id
it[emojiId] = reaction.emojiId
if (reaction.emoji is CustomEmoji) {
it[customEmojiId] = reaction.emoji.id
it[unicodeEmoji] = null
} else {
it[customEmojiId] = null
it[unicodeEmoji] = reaction.emoji.name
}
it[postId] = reaction.postId
it[actorId] = reaction.actorId
}
} else {
Reactions.update({ Reactions.id eq reaction.id }) {
it[emojiId] = reaction.emojiId
if (reaction.emoji is CustomEmoji) {
it[customEmojiId] = reaction.emoji.id
it[unicodeEmoji] = null
} else {
it[customEmojiId] = null
it[unicodeEmoji] = reaction.emoji.name
}
it[postId] = reaction.postId
it[actorId] = reaction.actorId
}
@ -38,9 +52,16 @@ class ReactionRepositoryImpl(
}
override suspend fun delete(reaction: Reaction): Reaction = query {
Reactions.deleteWhere {
id.eq(reaction.id).and(postId.eq(reaction.postId)).and(actorId.eq(reaction.actorId))
.and(emojiId.eq(reaction.emojiId))
if (reaction.emoji is CustomEmoji) {
Reactions.deleteWhere {
id.eq(reaction.id).and(postId.eq(reaction.postId)).and(actorId.eq(reaction.actorId))
.and(customEmojiId.eq(reaction.emoji.id))
}
} else {
Reactions.deleteWhere {
id.eq(reaction.id).and(postId.eq(reaction.postId)).and(actorId.eq(reaction.actorId))
.and(unicodeEmoji.eq(reaction.emoji.name))
}
}
return@query reaction
}
@ -58,14 +79,14 @@ class ReactionRepositoryImpl(
}
override suspend fun findByPostId(postId: Long): List<Reaction> = query {
return@query Reactions.select { Reactions.postId eq postId }.map { it.toReaction() }
return@query Reactions.leftJoin(CustomEmojis).select { Reactions.postId eq postId }.map { it.toReaction() }
}
override suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction? =
query {
return@query Reactions.select {
return@query Reactions.leftJoin(CustomEmojis).select {
Reactions.postId eq postId and (Reactions.actorId eq actorId).and(
Reactions.emojiId.eq(
Reactions.customEmojiId.eq(
emojiId
)
)
@ -78,12 +99,13 @@ class ReactionRepositoryImpl(
Reactions.postId
.eq(postId)
.and(Reactions.actorId.eq(actorId))
.and(Reactions.emojiId.eq(emojiId))
.and(Reactions.customEmojiId.eq(emojiId))
}.empty().not()
}
override suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List<Reaction> = query {
return@query Reactions.select { Reactions.postId eq postId and (Reactions.actorId eq actorId) }
return@query Reactions.leftJoin(CustomEmojis)
.select { Reactions.postId eq postId and (Reactions.actorId eq actorId) }
.map { it.toReaction() }
}
@ -93,22 +115,39 @@ class ReactionRepositoryImpl(
}
fun ResultRow.toReaction(): Reaction {
val emoji = if (this[Reactions.customEmojiId] != null) {
CustomEmoji(
this[Reactions.customEmojiId]!!,
this[CustomEmojis.name],
this[CustomEmojis.domain],
this[CustomEmojis.instanceId],
this[CustomEmojis.url],
this[CustomEmojis.category],
this[CustomEmojis.createdAt]
)
} else if (this[Reactions.unicodeEmoji] != null) {
UnicodeEmoji(this[Reactions.unicodeEmoji]!!)
} else {
throw IllegalStateException("customEmojiId and unicodeEmoji is null.")
}
return Reaction(
this[Reactions.id].value,
this[Reactions.emojiId],
emoji,
this[Reactions.postId],
this[Reactions.actorId]
)
}
object Reactions : LongIdTable("reactions") {
val emojiId: Column<Long> = long("emoji_id")
val customEmojiId = long("custom_emoji_id").references(CustomEmojis.id).nullable()
val unicodeEmoji = varchar("unicode_emoji", 255).nullable()
val postId: Column<Long> =
long("post_id").references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val actorId: Column<Long> =
long("actor_id").references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
init {
uniqueIndex(emojiId, postId, actorId)
uniqueIndex(customEmojiId, postId, actorId)
}
}

View File

@ -132,15 +132,19 @@ alter table posts_emojis
create table if not exists reactions
(
id bigserial primary key,
emoji_id bigint not null,
post_id bigint not null,
actor_id bigint not null
id bigserial primary key,
unicode_emoji varchar(255) null default null,
custom_emoji_id bigint null default null,
post_id bigint not null,
actor_id bigint not null
);
alter table reactions
add constraint fk_reactions_post_id__id foreign key (post_id) references posts (id) on delete restrict on update restrict;
alter table reactions
add constraint fk_reactions_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict;
alter table reactions
add constraint fk_reactions_emoji_id__id foreign key (emoji_id) references emojis (id) on delete cascade on update cascade;
create table if not exists timelines
(
id bigint primary key,