From 55af96bd6c5e975044780bd9eff7031ac79e48c9 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:35:53 +0900 Subject: [PATCH 01/26] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=81=AE=E3=82=A8=E3=83=B3=E3=83=86=E3=82=A3=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=81=A8Repository=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/model/emoji/CustomEmoji.kt | 24 +++++++++++++++++++ .../model/emoji/CustomEmojiRepository.kt | 7 ++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt new file mode 100644 index 00000000..f4e69e54 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt @@ -0,0 +1,24 @@ +package dev.usbharu.hideout.core.domain.model.emoji + +import java.time.Instant + +sealed class Emoji { + abstract val domain: String + abstract val name: String +} + +data class CustomEmoji( + val id: Long, + override val name: String, + override val domain: String, + val instanceId: Long, + val url: String, + val category: String, + val createdAt: Instant +) : Emoji() + +data class UnicodeEmoji( + override val name: String +) : Emoji() { + override val domain: String = "unicode.org" +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt new file mode 100644 index 00000000..59aa34aa --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.domain.model.emoji + +interface CustomEmojiRepository { + suspend fun save(customEmoji: CustomEmoji): CustomEmoji + suspend fun findById(id: Long): CustomEmoji? + suspend fun delete(customEmoji: CustomEmoji) +} From 086539e2a60fa71d01aec86d49f20c243c5aa918 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:36:14 +0900 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/db/migration/V1__Init_DB.sql | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 1badf0ae..eb04132f 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -1,3 +1,15 @@ +create table if not exists emojis +( + id bigint primary key, + "name" varchar(1000) not null, + domain varchar(1000) not null, + instance_id bigint null, + url varchar(255) not null unique, + category varchar(255), + created_at timestamp not null default current_timestamp, + unique ("name", instance_id) +); + create table if not exists instance ( id bigint primary key, @@ -13,6 +25,10 @@ create table if not exists instance moderation_note varchar(10000) not null, created_at timestamp not null ); + +alter table emojis + add constraint fk_emojis_instance_id__id foreign key (instance_id) references instance (id) on delete cascade on update cascade; + create table if not exists actors ( id bigint primary key, @@ -34,7 +50,8 @@ create table if not exists actors following_count int not null, followers_count int not null, posts_count int not null, - last_post_at timestamp null default null, + last_post_at timestamp null default null, + emojis varchar(300) not null default '', unique ("name", "domain"), constraint fk_actors_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict ); @@ -81,7 +98,8 @@ create table if not exists posts reply_id bigint null, "sensitive" boolean default false not null, ap_id varchar(100) not null unique, - deleted boolean default false not null + deleted boolean default false not null, + emojis varchar(3000) not null default '' ); alter table posts add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict; From b070b8c71d69c3f4464d7251203277f63bff909d Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:13:36 +0900 Subject: [PATCH 03/26] =?UTF-8?q?feat:=20Actor=E3=81=AB=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E6=83=85=E5=A0=B1=E3=82=92=E5=90=AB=E3=82=81=E3=82=8B?= =?UTF-8?q?=E3=81=93=E3=81=A8=E3=81=8C=E5=8F=AF=E8=83=BD=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/core/domain/model/actor/Actor.kt | 12 ++++++++---- .../exposedrepository/ActorRepositoryImpl.kt | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt index 0377df95..39344589 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt @@ -26,7 +26,8 @@ data class Actor private constructor( val followersCount: Int = 0, val followingCount: Int = 0, val postsCount: Int = 0, - val lastPostDate: Instant? = null + val lastPostDate: Instant? = null, + val emojis: List = emptyList() ) { @Component @@ -55,7 +56,8 @@ data class Actor private constructor( followersCount: Int = 0, followingCount: Int = 0, postsCount: Int = 0, - lastPostDate: Instant? = null + lastPostDate: Instant? = null, + emojis: List = emptyList() ): Actor { if (id == 0L) { return Actor( @@ -78,7 +80,8 @@ data class Actor private constructor( followersCount = followersCount, followingCount = followingCount, postsCount = postsCount, - lastPostDate = lastPostDate + lastPostDate = lastPostDate, + emojis = emojis ) } @@ -188,7 +191,8 @@ data class Actor private constructor( followersCount = followersCount, followingCount = followingCount, postsCount = postsCount, - lastPostDate = lastPostDate + lastPostDate = lastPostDate, + emojis = emojis ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt index f4a70693..e0d7eca2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt @@ -45,6 +45,7 @@ class ActorRepositoryImpl( it[followingCount] = actor.followingCount it[postsCount] = actor.postsCount it[lastPostAt] = actor.lastPostDate + it[emojis] = actor.emojis.joinToString(",") } } else { Actors.update({ Actors.id eq actor.id }) { @@ -67,6 +68,7 @@ class ActorRepositoryImpl( it[followingCount] = actor.followingCount it[postsCount] = actor.postsCount it[lastPostAt] = actor.lastPostDate + it[emojis] = actor.emojis.joinToString(",") } } return@query actor @@ -152,7 +154,7 @@ object Actors : Table("actors") { val followersCount = integer("followers_count") val postsCount = integer("posts_count") val lastPostAt = timestamp("last_post_at").nullable() - + val emojis = varchar("emojis", 3000) override val primaryKey: PrimaryKey = PrimaryKey(id) init { From 80f4e9fb04b089d9d2b4ce83bf04b38fb1389c7f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:14:02 +0900 Subject: [PATCH 04/26] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=82=92=E8=AD=98=E5=88=A5=E3=81=99=E3=82=8BID=E3=81=AE?= =?UTF-8?q?=E7=94=9F=E6=88=90=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/core/domain/model/emoji/CustomEmoji.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt index f4e69e54..9d6598e2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt @@ -5,6 +5,7 @@ import java.time.Instant sealed class Emoji { abstract val domain: String abstract val name: String + abstract fun id(): String } data class CustomEmoji( @@ -15,10 +16,17 @@ data class CustomEmoji( val url: String, val category: String, val createdAt: Instant -) : Emoji() +) : Emoji() { + override fun id(): String { + return id.toString() + } +} data class UnicodeEmoji( override val name: String ) : Emoji() { override val domain: String = "unicode.org" + override fun id(): String { + return name + } } From cf52ebfd12005f7a06c853eecad6f2e0331d32b4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:15:00 +0900 Subject: [PATCH 05/26] =?UTF-8?q?feat:=20Actor=E3=81=AEMapper=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/core/infrastructure/exposed/UserResultRowMapper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt index a5b58c60..6f8f4161 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt @@ -31,6 +31,7 @@ class UserResultRowMapper(private val actorBuilder: Actor.UserBuilder) : ResultR followersCount = resultRow[Actors.followersCount], postsCount = resultRow[Actors.postsCount], lastPostDate = resultRow[Actors.lastPostAt], + emojis = resultRow[Actors.emojis].split(",").filter { it.isNotEmpty() }.map { it.toLong() } ) } } From 0607fe2bc5f50ad9049394ed1bcba3213583c5d4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:41:27 +0900 Subject: [PATCH 06/26] =?UTF-8?q?feat:=20=E6=8A=95=E7=A8=BF=E3=81=AB?= =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=88=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/core/domain/model/post/Post.kt | 9 +++-- .../infrastructure/exposed/PostQueryMapper.kt | 5 ++- .../exposedrepository/PostRepositoryImpl.kt | 34 ++++++++++++++++--- .../resources/db/migration/V1__Init_DB.sql | 13 +++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt index cc9edc51..cad1606f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt @@ -17,7 +17,8 @@ data class Post private constructor( val sensitive: Boolean = false, val apId: String = url, val mediaIds: List = emptyList(), - val delted: Boolean = false + val delted: Boolean = false, + val emojiIds: List = emptyList() ) { @Component @@ -35,7 +36,8 @@ data class Post private constructor( replyId: Long? = null, sensitive: Boolean = false, apId: String = url, - mediaIds: List = emptyList() + mediaIds: List = emptyList(), + emojiIds: List = emptyList() ): Post { require(id >= 0) { "id must be greater than or equal to 0." } @@ -74,7 +76,8 @@ data class Post private constructor( sensitive = sensitive, apId = apId, mediaIds = mediaIds, - delted = false + delted = false, + emojiIds = emojiIds ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt index a21fcf4d..f28c4bfc 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts +import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsEmojis import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsMedia import org.jetbrains.exposed.sql.Query import org.springframework.stereotype.Component @@ -15,7 +16,9 @@ class PostQueryMapper(private val postResultRowMapper: ResultRowMapper) : .map { it.value } .map { it.first().let(postResultRowMapper::map) - .copy(mediaIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsMedia.mediaId) }) + .copy( + mediaIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsMedia.mediaId) }, + emojiIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsEmojis.emojiId) }) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt index 2a1899fd..a026ed5e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt @@ -41,14 +41,25 @@ class PostRepositoryImpl( this[PostsMedia.postId] = post.id this[PostsMedia.mediaId] = it } + PostsEmojis.batchInsert(post.emojiIds) { + this[PostsEmojis.postId] = post.id + this[PostsEmojis.emojiId] = it + } } else { PostsMedia.deleteWhere { postId eq post.id } + PostsEmojis.deleteWhere { + postId eq post.id + } PostsMedia.batchInsert(post.mediaIds) { this[PostsMedia.postId] = post.id this[PostsMedia.mediaId] = it } + PostsEmojis.batchInsert(post.emojiIds) { + this[PostsEmojis.postId] = post.id + this[PostsEmojis.emojiId] = it + } Posts.update({ Posts.id eq post.id }) { it[actorId] = post.actorId it[overview] = post.overview @@ -67,21 +78,27 @@ class PostRepositoryImpl( } override suspend fun findById(id: Long): Post? = query { - return@query Posts.leftJoin(PostsMedia) + return@query Posts + .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) .select { Posts.id eq id } .let(postQueryMapper::map) .singleOrNull() } override suspend fun findByUrl(url: String): Post? = query { - return@query Posts.leftJoin(PostsMedia) + return@query Posts + .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) .select { Posts.url eq url } .let(postQueryMapper::map) .singleOrNull() } override suspend fun findByApId(apId: String): Post? = query { - return@query Posts.leftJoin(PostsMedia) + return@query Posts + .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) .select { Posts.apId eq apId } .let(postQueryMapper::map) .singleOrNull() @@ -92,7 +109,10 @@ class PostRepositoryImpl( } override suspend fun findByActorId(actorId: Long): List = query { - return@query Posts.select { Posts.actorId eq actorId }.let(postQueryMapper::map) + return@query Posts + .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) + .select { Posts.actorId eq actorId }.let(postQueryMapper::map) } override suspend fun delete(id: Long): Unit = query { @@ -125,3 +145,9 @@ object PostsMedia : Table("posts_media") { val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) override val primaryKey = PrimaryKey(postId, mediaId) } + +object PostsEmojis : Table("posts_emojis") { + val postId = long("post_id").references(Posts.id) + val emojiId = long("emoji_id").references(Posts.id) + override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) +} diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index eb04132f..5f989b5b 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -117,6 +117,19 @@ alter table posts_media add constraint fk_posts_media_post_id__id foreign key (post_id) references posts (id) on delete cascade on update cascade; alter table posts_media add constraint fk_posts_media_media_id__id foreign key (media_id) references media (id) on delete cascade on update cascade; + +create table if not exists posts_emojis +( + post_id bigint not null, + emoji_id bigint not null, + constraint pk_postsemoji primary key (post_id, emoji_id) +); + +alter table posts_emojis + add constraint fk_posts_emojis_post_id__id foreign key (post_id) references posts (id) on delete cascade on update cascade; +alter table posts_emojis + add constraint fk_posts_emojis_emoji_id__id foreign key (emoji_id) references emojis (id) on delete cascade on update cascade; + create table if not exists reactions ( id bigserial primary key, From 32e60dc6f8813a7fb6b543c724892af5529bb889 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 02:29:37 +0900 Subject: [PATCH 07/26] =?UTF-8?q?feat:=20=E3=82=AB=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=83=A0=E7=B5=B5=E6=96=87=E5=AD=97=E3=81=AE=E3=83=AA=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/model/emoji/CustomEmoji.kt | 4 +- .../core/domain/model/reaction/Reaction.kt | 4 +- .../CustomEmojiRepositoryImpl.kt | 78 +++++++++++++++++++ .../ReactionRepositoryImpl.kt | 65 ++++++++++++---- .../resources/db/migration/V1__Init_DB.sql | 12 ++- 5 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt index 9d6598e2..6323d42e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt @@ -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 { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt index 02997373..ad2fefec 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt @@ -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) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt new file mode 100644 index 00000000..3296f5d5 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt @@ -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) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt index 1a3a14ff..2c4274b7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt @@ -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 = 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 = 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("emoji_id") + val customEmojiId = long("custom_emoji_id").references(CustomEmojis.id).nullable() + val unicodeEmoji = varchar("unicode_emoji", 255).nullable() val postId: Column = long("post_id").references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) val actorId: Column = long("actor_id").references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) init { - uniqueIndex(emojiId, postId, actorId) + uniqueIndex(customEmojiId, postId, actorId) } } diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 5f989b5b..2b79292d 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -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, From a3890368279a167594d447680441fccdfb66c457 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:02:28 +0900 Subject: [PATCH 08/26] =?UTF-8?q?feat:=20ReactionService=E3=82=92=E3=82=AB?= =?UTF-8?q?=E3=82=B9=E3=82=BF=E3=83=A0=E7=B5=B5=E6=96=87=E5=AD=97=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/objects/emoji/EmojiService.kt | 9 +++ .../service/objects/emoji/EmojiServiceImpl.kt | 63 +++++++++++++++++++ .../model/emoji/CustomEmojiRepository.kt | 2 + .../CustomEmojiRepositoryImpl.kt | 15 ++++- .../service/reaction/ReactionServiceImpl.kt | 5 +- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt new file mode 100644 index 00000000..4dd6e2d1 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt @@ -0,0 +1,9 @@ +package dev.usbharu.hideout.activitypub.service.objects.emoji + +import dev.usbharu.hideout.activitypub.domain.model.Emoji +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji + +interface EmojiService { + suspend fun fetchEmoji(url: String): Pair + suspend fun fetchEmoji(emoji: Emoji): Pair +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt new file mode 100644 index 00000000..3c990b59 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt @@ -0,0 +1,63 @@ +package dev.usbharu.hideout.activitypub.service.objects.emoji + +import dev.usbharu.hideout.activitypub.domain.model.Emoji +import dev.usbharu.hideout.activitypub.service.common.APResourceResolveServiceImpl +import dev.usbharu.hideout.activitypub.service.common.resolve +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository +import dev.usbharu.hideout.core.service.instance.InstanceService +import dev.usbharu.hideout.core.service.media.MediaService +import dev.usbharu.hideout.core.service.media.RemoteMedia +import org.springframework.stereotype.Service +import java.net.URL +import java.time.Instant + +@Service +class EmojiServiceImpl( + private val customEmojiRepository: CustomEmojiRepository, + private val instanceService: InstanceService, + private val mediaService: MediaService, + private val apResourceResolveServiceImpl: APResourceResolveServiceImpl +) : EmojiService { + override suspend fun fetchEmoji(url: String): Pair { + val emoji = apResourceResolveServiceImpl.resolve(url, null as Long?) + return fetchEmoji(emoji) + } + + override suspend fun fetchEmoji(emoji: Emoji): Pair { + return emoji to save(emoji) + } + + private suspend fun save(emoji: Emoji): CustomEmoji { + val domain = URL(emoji.id).host + val name = emoji.name.trim(':') + val customEmoji = customEmojiRepository.findByNameAndDomain(name, domain) + + if (customEmoji != null) { + return customEmoji + } + + val instance = instanceService.fetchInstance(emoji.id) + + val media = mediaService.uploadRemoteMedia( + RemoteMedia( + emoji.name, + emoji.icon.url, + emoji.icon.mediaType.orEmpty(), + null + ) + ) + + val customEmoji1 = CustomEmoji( + customEmojiRepository.generateId(), + name, + domain, + instance.id, + media.url, + null, + Instant.now() + ) + + return customEmojiRepository.save(customEmoji1) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt index 59aa34aa..df7e87f3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.core.domain.model.emoji interface CustomEmojiRepository { + suspend fun generateId(): Long suspend fun save(customEmoji: CustomEmoji): CustomEmoji suspend fun findById(id: Long): CustomEmoji? suspend fun delete(customEmoji: CustomEmoji) + suspend fun findByNameAndDomain(name: String, domain: String): CustomEmoji? } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt index 3296f5d5..0df58975 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt @@ -1,5 +1,6 @@ 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.CustomEmojiRepository import org.jetbrains.exposed.sql.* @@ -8,8 +9,13 @@ import org.jetbrains.exposed.sql.javatime.CurrentTimestamp import org.jetbrains.exposed.sql.javatime.timestamp import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService) : CustomEmojiRepository, + AbstractRepository() { + override suspend fun generateId(): Long = idGenerateService.generateId() -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) { @@ -43,6 +49,13 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository, AbstractRepository() { CustomEmojis.deleteWhere { CustomEmojis.id eq customEmoji.id } } + override suspend fun findByNameAndDomain(name: String, domain: String): CustomEmoji? = query { + return@query CustomEmojis + .select { CustomEmojis.name eq name and (CustomEmojis.domain eq domain) } + .singleOrNull() + ?.toCustomEmoji() + } + override val logger: Logger get() = Companion.logger diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt index 2a731029..a6ffeb3b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.core.service.reaction import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService +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.exceptions.ExposedSQLException @@ -17,7 +18,7 @@ class ReactionServiceImpl( if (reactionRepository.existByPostIdAndActorIdAndEmojiId(postId, actorId, 0).not()) { try { reactionRepository.save( - Reaction(reactionRepository.generateId(), 0, postId, actorId) + Reaction(reactionRepository.generateId(), UnicodeEmoji("❤"), postId, actorId) ) } catch (_: ExposedSQLException) { } @@ -42,7 +43,7 @@ class ReactionServiceImpl( reactionRepository.delete(findByPostIdAndUserIdAndEmojiId) } - val reaction = Reaction(reactionRepository.generateId(), 0, postId, actorId) + val reaction = Reaction(reactionRepository.generateId(), UnicodeEmoji("❤"), postId, actorId) reactionRepository.save(reaction) apReactionService.reaction(reaction) } From 4b59802c8df50addf1d9ae96639a8ebb789ded75 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:20:41 +0900 Subject: [PATCH 09/26] style: fix lint --- .../kotlin/federation/InboxCommonTest.kt | 9 +++---- .../hideout/core/domain/model/actor/Actor.kt | 4 +-- .../core/domain/model/emoji/CustomEmoji.kt | 6 +++++ .../infrastructure/exposed/PostQueryMapper.kt | 3 ++- .../usbharu/hideout/EqualsAndToStringTest.kt | 2 ++ .../like/APReactionServiceImplTest.kt | 5 ++-- .../reaction/ReactionServiceImplTest.kt | 27 ++++++++++--------- 7 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/e2eTest/kotlin/federation/InboxCommonTest.kt b/src/e2eTest/kotlin/federation/InboxCommonTest.kt index 33d595af..67ad80af 100644 --- a/src/e2eTest/kotlin/federation/InboxCommonTest.kt +++ b/src/e2eTest/kotlin/federation/InboxCommonTest.kt @@ -112,18 +112,17 @@ class InboxCommonTest { @BeforeAll @JvmStatic - fun beforeAll(@Autowired flyway: Flyway) { + fun beforeAll() { server = MockServer.feature("classpath:federation/InboxxCommonMockServerTest.feature").http(0).build() _remotePort = server.port.toString() - - flyway.clean() - flyway.migrate() } @AfterAll @JvmStatic - fun afterAll() { + fun afterAll(@Autowired flyway: Flyway) { server.stop() + flyway.clean() + flyway.migrate() } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt index 39344589..59ab16d3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt @@ -210,7 +210,6 @@ data class Actor private constructor( fun decrementPostsCount(): Actor = this.copy(postsCount = this.postsCount - 1) fun withLastPostAt(lastPostDate: Instant): Actor = this.copy(lastPostDate = lastPostDate) - override fun toString(): String { return "Actor(" + "id=$id, " + @@ -232,7 +231,8 @@ data class Actor private constructor( "followersCount=$followersCount, " + "followingCount=$followingCount, " + "postsCount=$postsCount, " + - "lastPostDate=$lastPostDate" + + "lastPostDate=$lastPostDate, " + + "emojis=$emojis" + ")" } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt index 6323d42e..d02e9103 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt @@ -6,6 +6,12 @@ sealed class Emoji { abstract val domain: String abstract val name: String abstract fun id(): String + override fun toString(): String { + return "Emoji(" + + "domain='$domain', " + + "name='$name'" + + ")" + } } data class CustomEmoji( diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt index f28c4bfc..478812da 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt @@ -18,7 +18,8 @@ class PostQueryMapper(private val postResultRowMapper: ResultRowMapper) : it.first().let(postResultRowMapper::map) .copy( mediaIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsMedia.mediaId) }, - emojiIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsEmojis.emojiId) }) + emojiIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsEmojis.emojiId) } + ) } } } diff --git a/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt b/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt index 11360eb6..7d2454c1 100644 --- a/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout import com.fasterxml.jackson.module.kotlin.isKotlinClass import com.jparams.verifier.tostring.ToStringVerifier import com.jparams.verifier.tostring.preset.Presets +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji import nl.jqno.equalsverifier.EqualsVerifier import nl.jqno.equalsverifier.Warning import nl.jqno.equalsverifier.internal.reflection.PackageScanner @@ -95,6 +96,7 @@ class EqualsAndToStringTest { .filter { it.superclass == Any::class.java || it.superclass?.packageName?.startsWith("dev.usbharu") ?: true } + .filterNot { it == UnicodeEmoji::class.java } .map { dynamicTest(it.name) { diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionServiceImplTest.kt index 716762b3..b6327fab 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionServiceImplTest.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.activitypub.service.activity.like import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.external.job.DeliverReactionJob @@ -48,7 +49,7 @@ class APReactionServiceImplTest { apReactionServiceImpl.reaction( Reaction( id = TwitterSnowflakeIdGenerateService.generateId(), - emojiId = 0, + emoji = UnicodeEmoji("❤"), postId = post.id, actorId = user.id ) @@ -88,7 +89,7 @@ class APReactionServiceImplTest { apReactionServiceImpl.removeReaction( Reaction( id = TwitterSnowflakeIdGenerateService.generateId(), - emojiId = 0, + emoji = UnicodeEmoji("❤"), postId = post.id, actorId = user.id ) diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt index 8dc8f5ec..e56d1443 100644 --- a/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.service.reaction import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService +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 kotlinx.coroutines.test.runTest @@ -40,7 +41,7 @@ class ReactionServiceImplTest { reactionServiceImpl.receiveReaction("❤", "example.com", post.actorId, post.id) - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.actorId))) + verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } @Test @@ -56,7 +57,7 @@ class ReactionServiceImplTest { eq( Reaction( id = generateId, - emojiId = 0, + emoji = UnicodeEmoji("❤"), postId = post.id, actorId = post.actorId ) @@ -72,7 +73,7 @@ class ReactionServiceImplTest { reactionServiceImpl.receiveReaction("❤", "example.com", post.actorId, post.id) - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.actorId))) + verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } @Test @@ -98,8 +99,8 @@ class ReactionServiceImplTest { reactionServiceImpl.sendReaction("❤", post.actorId, post.id) - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.actorId))) - verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, 0, post.id, post.actorId))) + verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) + verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } @Test @@ -107,7 +108,7 @@ class ReactionServiceImplTest { val post = PostBuilder.of() val id = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( - Reaction(id, 0, post.id, post.actorId) + Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId) ) val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) @@ -115,22 +116,22 @@ class ReactionServiceImplTest { reactionServiceImpl.sendReaction("❤", post.actorId, post.id) - verify(reactionRepository, times(1)).delete(eq(Reaction(id, 0, post.id, post.actorId))) - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.actorId))) - verify(apReactionService, times(1)).removeReaction(eq(Reaction(id, 0, post.id, post.actorId))) - verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, 0, post.id, post.actorId))) + verify(reactionRepository, times(1)).delete(eq(Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId))) + verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) + verify(apReactionService, times(1)).removeReaction(eq(Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId))) + verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } @Test fun `removeReaction リアクションが存在する場合削除して配送`() = runTest { val post = PostBuilder.of() whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( - Reaction(0, 0, post.id, post.actorId) + Reaction(0, UnicodeEmoji("❤"), post.id, post.actorId) ) reactionServiceImpl.removeReaction(post.actorId, post.id) - verify(reactionRepository, times(1)).delete(eq(Reaction(0, 0, post.id, post.actorId))) - verify(apReactionService, times(1)).removeReaction(eq(Reaction(0, 0, post.id, post.actorId))) + verify(reactionRepository, times(1)).delete(eq(Reaction(0, UnicodeEmoji("❤"), post.id, post.actorId))) + verify(apReactionService, times(1)).removeReaction(eq(Reaction(0, UnicodeEmoji("❤"), post.id, post.actorId))) } } From 0a54bdfc1518c5ceedbba0bde72a3655303d370f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:20:58 +0900 Subject: [PATCH 10/26] =?UTF-8?q?fix:=20SQL=E3=81=AE=E3=83=9F=E3=82=B9?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/migration/V1__Init_DB.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 2b79292d..bb5e01f5 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -143,7 +143,7 @@ alter table reactions 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; + add constraint fk_reactions_custom_emoji_id__id foreign key (custom_emoji_id) references emojis (id) on delete cascade on update cascade; create table if not exists timelines ( From 43033b6f4babbe0b75e78cd91ab85748c60758c7 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:02:23 +0900 Subject: [PATCH 11/26] =?UTF-8?q?feat:=20=E5=8F=97=E3=81=91=E5=8F=96?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AEEmoji?= =?UTF-8?q?=E3=82=92=E8=AA=8D=E8=AD=98=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/activitypub/domain/model/Note.kt | 31 +++-- .../service/objects/note/APNoteService.kt | 18 ++- .../exposedrepository/PostRepositoryImpl.kt | 2 +- .../domain/model/NoteSerializeTest.kt | 108 +++++++++++++++++- .../objects/note/APNoteServiceImplTest.kt | 7 +- 5 files changed, 145 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt index 2b16e1c4..b3383ef5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt @@ -1,6 +1,8 @@ package dev.usbharu.hideout.activitypub.domain.model +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import dev.usbharu.hideout.activitypub.domain.model.objects.Object +import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer open class Note @Suppress("LongParameterList") @@ -14,7 +16,9 @@ constructor( val cc: List = emptyList(), val sensitive: Boolean = false, val inReplyTo: String? = null, - val attachment: List = emptyList() + val attachment: List = emptyList(), + @JsonDeserialize(contentUsing = ObjectDeserializer::class) + val tag: List = emptyList() ) : Object( type = add(type, "Note") ), @@ -36,6 +40,7 @@ constructor( if (sensitive != other.sensitive) return false if (inReplyTo != other.inReplyTo) return false if (attachment != other.attachment) return false + if (tag != other.tag) return false return true } @@ -51,21 +56,23 @@ constructor( result = 31 * result + sensitive.hashCode() result = 31 * result + (inReplyTo?.hashCode() ?: 0) result = 31 * result + attachment.hashCode() + result = 31 * result + tag.hashCode() return result } override fun toString(): String { return "Note(" + - "id='$id', " + - "attributedTo='$attributedTo', " + - "content='$content', " + - "published='$published', " + - "to=$to, " + - "cc=$cc, " + - "sensitive=$sensitive, " + - "inReplyTo=$inReplyTo, " + - "attachment=$attachment" + - ")" + - " ${super.toString()}" + "id='$id', " + + "attributedTo='$attributedTo', " + + "content='$content', " + + "published='$published', " + + "to=$to, " + + "cc=$cc, " + + "sensitive=$sensitive, " + + "inReplyTo=$inReplyTo, " + + "attachment=$attachment, " + + "tag=$tag" + + ")" + + " ${super.toString()}" } } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt index 7d284e0f..80e2889e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt @@ -1,10 +1,12 @@ package dev.usbharu.hideout.activitypub.service.objects.note import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException +import dev.usbharu.hideout.activitypub.domain.model.Emoji import dev.usbharu.hideout.activitypub.domain.model.Note import dev.usbharu.hideout.activitypub.query.NoteQueryService import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService import dev.usbharu.hideout.activitypub.service.common.resolve +import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.PostRepository @@ -32,7 +34,8 @@ class APNoteServiceImpl( private val apResourceResolveService: APResourceResolveService, private val postBuilder: Post.PostBuilder, private val noteQueryService: NoteQueryService, - private val mediaService: MediaService + private val mediaService: MediaService, + private val emojiService: EmojiService ) : APNoteService { @@ -101,6 +104,16 @@ class APNoteServiceImpl( postRepository.findByUrl(it) } + val emojis = note.tag + .filterIsInstance() + .map { + emojiService.fetchEmoji(it).second + } + .map { + it.id + } + + val mediaList = note.attachment.map { mediaService.uploadRemoteMedia( RemoteMedia( @@ -123,7 +136,8 @@ class APNoteServiceImpl( replyId = reply?.id, sensitive = note.sensitive, apId = note.id, - mediaIds = mediaList + mediaIds = mediaList, + emojiIds = emojis ) ) return note to createRemote diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt index a026ed5e..4df40c2d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt @@ -148,6 +148,6 @@ object PostsMedia : Table("posts_media") { object PostsEmojis : Table("posts_emojis") { val postId = long("post_id").references(Posts.id) - val emojiId = long("emoji_id").references(Posts.id) + val emojiId = long("emoji_id").references(CustomEmojis.id) override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) } diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt index 9e1397a9..cfca4a97 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.activitypub.domain.model import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public import dev.usbharu.hideout.application.config.ActivityPubConfig +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -51,11 +52,7 @@ class NoteSerializeTest { "attachment": [], "sensitive": false, "tag": [ - { - "type": "Mention", - "href": "https://calckey.jp/users/9bu1xzwjyb", - "name": "@trapezial@calckey.jp" - } + ] }""" @@ -77,4 +74,105 @@ class NoteSerializeTest { ) assertEquals(note, readValue) } + + @Test + fun 絵文字付きNoteのデシリアライズができる() { + val json = """{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "sensitive": "as:sensitive", + "Hashtag": "as:Hashtag", + "quoteUrl": "as:quoteUrl", + "toot": "http://joinmastodon.org/ns#", + "Emoji": "toot:Emoji", + "featured": "toot:featured", + "discoverable": "toot:discoverable", + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "misskey": "https://misskey-hub.net/ns#", + "_misskey_content": "misskey:_misskey_content", + "_misskey_quote": "misskey:_misskey_quote", + "_misskey_reaction": "misskey:_misskey_reaction", + "_misskey_votes": "misskey:_misskey_votes", + "_misskey_summary": "misskey:_misskey_summary", + "isCat": "misskey:isCat", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "id": "https://misskey.usbharu.dev/notes/9nj1omt1rn", + "type": "Note", + "attributedTo": "https://misskey.usbharu.dev/users/97ws8y3rj6", + "content": "

​:oyasumi:​

", + "_misskey_content": ":oyasumi:", + "source": { + "content": ":oyasumi:", + "mediaType": "text/x.misskeymarkdown" + }, + "published": "2023-12-21T17:32:36.853Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://misskey.usbharu.dev/users/97ws8y3rj6/followers" + ], + "inReplyTo": null, + "attachment": [], + "sensitive": false, + "tag": [ + { + "id": "https://misskey.usbharu.dev/emojis/oyasumi", + "type": "Emoji", + "name": ":oyasumi:", + "updated": "2023-04-07T08:21:25.246Z", + "icon": { + "type": "Image", + "mediaType": "image/png", + "url": "https://s3misskey.usbharu.dev/misskey-minio/misskey-minio/data/cf8db710-1d70-4076-8a00-dbb28131096e.png" + } + } + ] +}""" + + + val objectMapper = ActivityPubConfig().objectMapper() + + val expected = Note( + type = emptyList(), + id = "https://misskey.usbharu.dev/notes/9nj1omt1rn", + attributedTo = "https://misskey.usbharu.dev/users/97ws8y3rj6", + content = "

\u200B:oyasumi:\u200B

", + published = "2023-12-21T17:32:36.853Z", + to = listOf("https://www.w3.org/ns/activitystreams#Public"), + cc = listOf("https://misskey.usbharu.dev/users/97ws8y3rj6/followers"), + sensitive = false, + inReplyTo = null, + attachment = emptyList(), + tag = listOf( + Emoji( + type = emptyList(), + name = ":oyasumi:", + id = "https://misskey.usbharu.dev/emojis/oyasumi", + updated = "2023-04-07T08:21:25.246Z", + icon = Image( + type = emptyList(), + mediaType = "image/png", + url = "https://s3misskey.usbharu.dev/misskey-minio/misskey-minio/data/cf8db710-1d70-4076-8a00-dbb28131096e.png" + ) + ) + ) + ) + + expected.context = listOf( + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ) + + val note = objectMapper.readValue(json) + + assertThat(note).isEqualTo(expected) + } } diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt index cece7a5b..e5e1c957 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt @@ -73,6 +73,7 @@ class APNoteServiceImplTest { apResourceResolveService = mock(), postBuilder = Post.PostBuilder(CharacterLimit()), noteQueryService = noteQueryService, + mock(), mock() ) @@ -142,7 +143,8 @@ class APNoteServiceImplTest { apResourceResolveService = apResourceResolveService, postBuilder = Post.PostBuilder(CharacterLimit()), noteQueryService = noteQueryService, - mock() + mock(), + mock { } ) val actual = apNoteServiceImpl.fetchNote(url) @@ -190,6 +192,7 @@ class APNoteServiceImplTest { apResourceResolveService = apResourceResolveService, postBuilder = Post.PostBuilder(CharacterLimit()), noteQueryService = noteQueryService, + mock(), mock() ) @@ -240,6 +243,7 @@ class APNoteServiceImplTest { apResourceResolveService = mock(), postBuilder = postBuilder, noteQueryService = noteQueryService, + mock(), mock() ) @@ -292,6 +296,7 @@ class APNoteServiceImplTest { apResourceResolveService = mock(), postBuilder = postBuilder, noteQueryService = noteQueryService, + mock(), mock() ) From 2d84ad073f6cf23856e08cbe4c552cf0f3dd7389 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:02:52 +0900 Subject: [PATCH 12/26] =?UTF-8?q?fix:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=A0=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/migration/V1__Init_DB.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index bb5e01f5..101b0519 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -98,8 +98,7 @@ create table if not exists posts reply_id bigint null, "sensitive" boolean default false not null, ap_id varchar(100) not null unique, - deleted boolean default false not null, - emojis varchar(3000) not null default '' + deleted boolean default false not null ); alter table posts add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict; From 8af489c93c753d07d1878f22b1d0ac315674935a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:32:11 +0900 Subject: [PATCH 13/26] =?UTF-8?q?feat:=20Mastodon=20=E4=BA=92=E6=8F=9BAPI?= =?UTF-8?q?=E3=81=A7=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=A0=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/model/timeline/Timeline.kt | 3 ++- .../ExposedTimelineRepository.kt | 7 +++++- .../ExposedGenerateTimelineService.kt | 3 ++- .../timeline/MongoGenerateTimelineService.kt | 3 ++- .../core/service/timeline/TimelineService.kt | 6 +++-- .../exposedquery/StatusQueryServiceImpl.kt | 25 +++++++++++++++++-- .../interfaces/api/status/StatusQuery.kt | 3 ++- .../resources/db/migration/V1__Init_DB.sql | 7 +++--- 8 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt index cc9b181e..d3605ea5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt @@ -21,5 +21,6 @@ data class Timeline( val sensitive: Boolean, val isLocal: Boolean, val isPureRepost: Boolean = false, - val mediaIds: List = emptyList() + val mediaIds: List = emptyList(), + val emojiIds: List = emptyList() ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt index 2630f733..7a630374 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt @@ -37,6 +37,7 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService it[isLocal] = timeline.isLocal it[isPureRepost] = timeline.isPureRepost it[mediaIds] = timeline.mediaIds.joinToString(",") + it[emojiIds] = timeline.emojiIds.joinToString(",") } } else { Timelines.update({ Timelines.id eq timeline.id }) { @@ -52,6 +53,7 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService it[isLocal] = timeline.isLocal it[isPureRepost] = timeline.isPureRepost it[mediaIds] = timeline.mediaIds.joinToString(",") + it[emojiIds] = timeline.emojiIds.joinToString(",") } } return@query timeline @@ -72,6 +74,7 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService this[Timelines.isLocal] = it.isLocal this[Timelines.isPureRepost] = it.isPureRepost this[Timelines.mediaIds] = it.mediaIds.joinToString(",") + this[Timelines.emojiIds] = it.emojiIds.joinToString(",") } return@query timelines } @@ -104,7 +107,8 @@ fun ResultRow.toTimeline(): Timeline { sensitive = this[Timelines.sensitive], isLocal = this[Timelines.isLocal], isPureRepost = this[Timelines.isPureRepost], - mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() } + mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() }, + emojiIds = this[Timelines.emojiIds].split(",").mapNotNull { it.toLongOrNull() } ) } @@ -122,6 +126,7 @@ object Timelines : Table("timelines") { val isLocal = bool("is_local") val isPureRepost = bool("is_pure_repost") val mediaIds = varchar("media_ids", 255) + val emojiIds = varchar("emoji_ids", 255) override val primaryKey: PrimaryKey = PrimaryKey(id) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt index 03d2f49a..7d83a9ac 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt @@ -45,7 +45,8 @@ class ExposedGenerateTimelineService(private val statusQueryService: StatusQuery it[Timelines.postId], it[Timelines.replyId], it[Timelines.repostId], - it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() } + it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() }, + it[Timelines.emojiIds].split(",").mapNotNull { s -> s.toLongOrNull() } ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt index f3accd56..541f24c3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt @@ -57,7 +57,8 @@ class MongoGenerateTimelineService( it.postId, it.replyId, it.repostId, - it.mediaIds + it.mediaIds, + it.emojiIds ) } ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt index a6d8b185..18d1632c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt @@ -37,7 +37,8 @@ class TimelineService( sensitive = post.sensitive, isLocal = isLocal, isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), - mediaIds = post.mediaIds + mediaIds = post.mediaIds, + emojiIds = post.emojiIds ) }.toMutableList() if (post.visibility == Visibility.PUBLIC) { @@ -55,7 +56,8 @@ class TimelineService( sensitive = post.sensitive, isLocal = isLocal, isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), - mediaIds = post.mediaIds + mediaIds = post.mediaIds, + emojiIds = post.emojiIds ) ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt index 831c693a..937f2d9b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.mastodon.infrastructure.exposedquery +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments import dev.usbharu.hideout.core.infrastructure.exposedrepository.* import dev.usbharu.hideout.domain.mastodon.model.generated.Account @@ -12,6 +13,7 @@ import org.jetbrains.exposed.sql.andWhere import org.jetbrains.exposed.sql.select import org.springframework.stereotype.Repository import java.time.Instant +import dev.usbharu.hideout.domain.mastodon.model.generated.CustomEmoji as MastodonEmoji @Suppress("IncompleteDestructuring") @Repository @@ -23,6 +25,10 @@ class StatusQueryServiceImpl : StatusQueryService { postIdSet.addAll(statusQueries.flatMap { listOfNotNull(it.postId, it.replyId, it.repostId) }) val mediaIdSet = mutableSetOf() mediaIdSet.addAll(statusQueries.flatMap { it.mediaIds }) + + val emojiIdSet = mutableSetOf() + emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds }) + val postMap = Posts .leftJoin(Actors) .select { Posts.id inList postIdSet } @@ -32,12 +38,16 @@ class StatusQueryServiceImpl : StatusQueryService { it[Media.id] to it.toMedia().toMediaAttachments() } + val emojiMap = CustomEmojis.select { CustomEmojis.id inList emojiIdSet }.associate { + it[CustomEmojis.id] to it.toCustomEmoji().toMastodonEmoji() + } return statusQueries.mapNotNull { statusQuery -> postMap[statusQuery.postId]?.copy( inReplyToId = statusQuery.replyId?.toString(), inReplyToAccountId = postMap[statusQuery.replyId]?.account?.id, reblog = postMap[statusQuery.repostId], - mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] } + mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] }, + emojis = statusQuery.emojiIds.mapNotNull { emojiMap[it] } ) } } @@ -120,6 +130,8 @@ class StatusQueryServiceImpl : StatusQueryService { private suspend fun findByPostIdsWithMedia(ids: List): List { val pairs = Posts .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) + .leftJoin(CustomEmojis) .leftJoin(Actors) .leftJoin(Media) .select { Posts.id inList ids } @@ -129,13 +141,22 @@ class StatusQueryServiceImpl : StatusQueryService { toStatus(it.first()).copy( mediaAttachments = it.mapNotNull { resultRow -> resultRow.toMediaOrNull()?.toMediaAttachments() - } + }, + emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmoji()?.toMastodonEmoji() } ) to it.first()[Posts.repostId] } return resolveReplyAndRepost(pairs) } } +private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji( + shortcode = this.name, + url = this.url, + staticUrl = this.url, + visibleInPicker = true, + category = this.category.orEmpty() +) + private fun toStatus(it: ResultRow) = Status( id = it[Posts.id].toString(), uri = it[Posts.apId], diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt index ea5c1517..c53117ff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt @@ -4,5 +4,6 @@ data class StatusQuery( val postId: Long, val replyId: Long?, val repostId: Long?, - val mediaIds: List + val mediaIds: List, + val emojiIds: List ) diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 101b0519..e331c9f8 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -98,7 +98,7 @@ create table if not exists posts reply_id bigint null, "sensitive" boolean default false not null, ap_id varchar(100) not null unique, - deleted boolean default false not null + deleted boolean default false not null ); alter table posts add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict; @@ -150,7 +150,7 @@ create table if not exists timelines user_id bigint not null, timeline_id bigint not null, post_id bigint not null, - post_actor_id bigint not null, + post_actor_id bigint not null, created_at bigint not null, reply_id bigint null, repost_id bigint null, @@ -158,7 +158,8 @@ create table if not exists timelines "sensitive" boolean not null, is_local boolean not null, is_pure_repost boolean not null, - media_ids varchar(255) not null + media_ids varchar(255) not null, + emoji_ids varchar(255) not null ); create table if not exists application_authorization From 51837ea3795dbed413f7edb3d1f4d2879771bc93 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:13:39 +0900 Subject: [PATCH 14/26] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E5=8F=97=E4=BF=A1=E3=81=8C=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/objects/ObjectDeserializer.kt | 5 +++-- .../service/activity/like/APLikeProcessor.kt | 17 ++++++++++++++--- .../core/service/reaction/ReactionService.kt | 5 +++-- .../service/reaction/ReactionServiceImpl.kt | 14 +++++++++----- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt index caa6caff..dd0b413b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt @@ -9,7 +9,7 @@ import dev.usbharu.hideout.activitypub.service.common.ExtendedActivityVocabulary class ObjectDeserializer : JsonDeserializer() { @Suppress("LongMethod", "CyclomaticComplexMethod") - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object { + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object? { requireNotNull(p) val treeNode: JsonNode = requireNotNull(p.codec?.readTree(p)) if (treeNode.isValueNode) { @@ -24,7 +24,7 @@ class ObjectDeserializer : JsonDeserializer() { ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } } } else if (type.isValueNode) { - ExtendedActivityVocabulary.values().first { it.name.equals(type.asText(), true) } + ExtendedActivityVocabulary.values().firstOrNull() { it.name.equals(type.asText(), true) } } else { TODO() } @@ -85,6 +85,7 @@ class ObjectDeserializer : JsonDeserializer() { ExtendedActivityVocabulary.Video -> TODO() ExtendedActivityVocabulary.Mention -> TODO() ExtendedActivityVocabulary.Emoji -> p.codec.treeToValue(treeNode, Emoji::class.java) + null -> null } } else { TODO() diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt index 994e2135..e230e230 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt @@ -1,13 +1,16 @@ package dev.usbharu.hideout.activitypub.service.activity.like import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException +import dev.usbharu.hideout.activitypub.domain.model.Emoji import dev.usbharu.hideout.activitypub.domain.model.Like import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji import dev.usbharu.hideout.core.service.reaction.ReactionService import org.springframework.stereotype.Service @@ -16,7 +19,8 @@ class APLikeProcessor( transaction: Transaction, private val apUserService: APUserService, private val apNoteService: APNoteService, - private val reactionService: ReactionService + private val reactionService: ReactionService, + private val emojiService: EmojiService ) : AbstractActivityPubProcessor(transaction) { override suspend fun internalProcess(activity: ActivityPubProcessContext) { @@ -29,9 +33,16 @@ class APLikeProcessor( try { val post = apNoteService.fetchNoteWithEntity(target).second + + val emoji = if (content.startsWith(":")) { + val tag = activity.activity.tag + (tag.firstOrNull { it is Emoji } as Emoji?)?.let { emojiService.fetchEmoji(it).second } + } else { + UnicodeEmoji(content) + } + reactionService.receiveReaction( - content, - actor.substringAfter("://").substringBefore("/"), + emoji ?: UnicodeEmoji("❤"), personWithEntity.second.id, post.id ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt index 38121bb6..115bac21 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt @@ -1,11 +1,12 @@ package dev.usbharu.hideout.core.service.reaction +import dev.usbharu.hideout.core.domain.model.emoji.Emoji import org.springframework.stereotype.Service @Service interface ReactionService { - suspend fun receiveReaction(name: String, domain: String, actorId: Long, postId: Long) + suspend fun receiveReaction(emoji: Emoji, actorId: Long, postId: Long) suspend fun receiveRemoveReaction(actorId: Long, postId: Long) - suspend fun sendReaction(name: String, actorId: Long, postId: Long) + suspend fun sendReaction(emoji: Emoji, actorId: Long, postId: Long) suspend fun removeReaction(actorId: Long, postId: Long) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt index a6ffeb3b..daf38ca3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt @@ -1,7 +1,7 @@ package dev.usbharu.hideout.core.service.reaction import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji +import dev.usbharu.hideout.core.domain.model.emoji.Emoji import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository import org.jetbrains.exposed.exceptions.ExposedSQLException @@ -14,11 +14,15 @@ class ReactionServiceImpl( private val reactionRepository: ReactionRepository, private val apReactionService: APReactionService ) : ReactionService { - override suspend fun receiveReaction(name: String, domain: String, actorId: Long, postId: Long) { + override suspend fun receiveReaction( + emoji: Emoji, + actorId: Long, + postId: Long + ) { if (reactionRepository.existByPostIdAndActorIdAndEmojiId(postId, actorId, 0).not()) { try { reactionRepository.save( - Reaction(reactionRepository.generateId(), UnicodeEmoji("❤"), postId, actorId) + Reaction(reactionRepository.generateId(), emoji, postId, actorId) ) } catch (_: ExposedSQLException) { } @@ -34,7 +38,7 @@ class ReactionServiceImpl( reactionRepository.delete(reaction) } - override suspend fun sendReaction(name: String, actorId: Long, postId: Long) { + override suspend fun sendReaction(emoji: Emoji, actorId: Long, postId: Long) { val findByPostIdAndUserIdAndEmojiId = reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0) @@ -43,7 +47,7 @@ class ReactionServiceImpl( reactionRepository.delete(findByPostIdAndUserIdAndEmojiId) } - val reaction = Reaction(reactionRepository.generateId(), UnicodeEmoji("❤"), postId, actorId) + val reaction = Reaction(reactionRepository.generateId(), emoji, postId, actorId) reactionRepository.save(reaction) apReactionService.reaction(reaction) } From 9c5aaedf4dd88958a2e76a09c56b7de5285995e0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:45:55 +0900 Subject: [PATCH 15/26] =?UTF-8?q?feat:=20DB=E3=81=B8=E3=81=AE=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=83=AD=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=92=E6=98=8E=E7=A4=BA=E7=9A=84=E3=81=AB=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E5=BE=8CSelect=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/infrastructure/exposedrepository/AbstractRepository.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt index 3e22db17..383a93a5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository import dev.usbharu.hideout.core.domain.exception.SpringDataAccessExceptionSQLExceptionTranslator +import org.jetbrains.exposed.sql.transactions.TransactionManager import org.slf4j.Logger import org.springframework.beans.factory.annotation.Value import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator @@ -37,6 +38,7 @@ ${Throwable().stackTrace.joinToString("\n")} if (traceQueryException) { logger.trace("FAILED EXECUTE SQL", e) } + TransactionManager.currentOrNull()?.rollback() if (e.cause !is SQLException) { throw e } From fff1296d4c59c892657935243c220ee5e87e2cf0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:13:39 +0900 Subject: [PATCH 16/26] =?UTF-8?q?fix:=20ResourceAccessException=E3=82=92SQ?= =?UTF-8?q?LException=E3=81=AB=E3=81=97=E3=81=A6=E5=86=8Dthrow=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/common/AbstractActivityPubProcessor.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt index e26e909d..2ba9d969 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt @@ -5,8 +5,10 @@ import dev.usbharu.hideout.activitypub.domain.exception.FailedProcessException import dev.usbharu.hideout.activitypub.domain.exception.HttpSignatureUnauthorizedException import dev.usbharu.hideout.activitypub.domain.model.objects.Object import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.sql.SQLException abstract class AbstractActivityPubProcessor( private val transaction: Transaction, @@ -21,7 +23,11 @@ abstract class AbstractActivityPubProcessor( logger.info("START ActivityPub process. {}", this.type()) try { transaction.transaction { - internalProcess(activity) + try { + internalProcess(activity) + } catch (e: ResourceAccessException) { + throw SQLException(e) + } } } catch (e: ActivityPubProcessException) { logger.warn("FAILED ActivityPub process", e) From 87cd17216527a8a509ee7abbb0f70b1eb8a47153 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:22:53 +0900 Subject: [PATCH 17/26] =?UTF-8?q?test:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/service/reaction/ReactionServiceImplTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt index e56d1443..2f54b06f 100644 --- a/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt @@ -39,7 +39,7 @@ class ReactionServiceImplTest { val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) - reactionServiceImpl.receiveReaction("❤", "example.com", post.actorId, post.id) + reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } @@ -71,7 +71,7 @@ class ReactionServiceImplTest { } whenever(reactionRepository.generateId()).doReturn(generateId) - reactionServiceImpl.receiveReaction("❤", "example.com", post.actorId, post.id) + reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } @@ -83,7 +83,7 @@ class ReactionServiceImplTest { true ) - reactionServiceImpl.receiveReaction("❤", "example.com", post.actorId, post.id) + reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) verify(reactionRepository, never()).save(any()) } @@ -97,7 +97,7 @@ class ReactionServiceImplTest { val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) - reactionServiceImpl.sendReaction("❤", post.actorId, post.id) + reactionServiceImpl.sendReaction(UnicodeEmoji("❤"), post.actorId, post.id) verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) @@ -113,7 +113,7 @@ class ReactionServiceImplTest { val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) - reactionServiceImpl.sendReaction("❤", post.actorId, post.id) + reactionServiceImpl.sendReaction(UnicodeEmoji("❤"), post.actorId, post.id) verify(reactionRepository, times(1)).delete(eq(Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId))) From bed0b84d16b9e7fc64a47f89647a948132d63beb Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:23:18 +0900 Subject: [PATCH 18/26] =?UTF-8?q?fix:=20Object=E3=81=AE=E3=83=87=E3=82=A4?= =?UTF-8?q?=E3=82=B7=E3=83=AA=E3=82=A2=E3=83=A9=E3=82=A4=E3=82=BA=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F=E3=81=A8=E3=81=8Dnull?= =?UTF-8?q?=E3=82=92=E8=BF=94=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/objects/ObjectDeserializer.kt | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt index dd0b413b..74076768 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt @@ -26,69 +26,69 @@ class ObjectDeserializer : JsonDeserializer() { } else if (type.isValueNode) { ExtendedActivityVocabulary.values().firstOrNull() { it.name.equals(type.asText(), true) } } else { - TODO() + null } return when (activityType) { ExtendedActivityVocabulary.Follow -> p.codec.treeToValue(treeNode, Follow::class.java) ExtendedActivityVocabulary.Note -> p.codec.treeToValue(treeNode, Note::class.java) ExtendedActivityVocabulary.Object -> p.codec.treeToValue(treeNode, Object::class.java) - ExtendedActivityVocabulary.Link -> TODO() - ExtendedActivityVocabulary.Activity -> TODO() - ExtendedActivityVocabulary.IntransitiveActivity -> TODO() - ExtendedActivityVocabulary.Collection -> TODO() - ExtendedActivityVocabulary.OrderedCollection -> TODO() - ExtendedActivityVocabulary.CollectionPage -> TODO() - ExtendedActivityVocabulary.OrderedCollectionPage -> TODO() + ExtendedActivityVocabulary.Link -> null + ExtendedActivityVocabulary.Activity -> null + ExtendedActivityVocabulary.IntransitiveActivity -> null + ExtendedActivityVocabulary.Collection -> null + ExtendedActivityVocabulary.OrderedCollection -> null + ExtendedActivityVocabulary.CollectionPage -> null + ExtendedActivityVocabulary.OrderedCollectionPage -> null ExtendedActivityVocabulary.Accept -> p.codec.treeToValue(treeNode, Accept::class.java) - ExtendedActivityVocabulary.Add -> TODO() - ExtendedActivityVocabulary.Announce -> TODO() - ExtendedActivityVocabulary.Arrive -> TODO() + ExtendedActivityVocabulary.Add -> null + ExtendedActivityVocabulary.Announce -> null + ExtendedActivityVocabulary.Arrive -> null ExtendedActivityVocabulary.Block -> p.codec.treeToValue(treeNode, Block::class.java) ExtendedActivityVocabulary.Create -> p.codec.treeToValue(treeNode, Create::class.java) ExtendedActivityVocabulary.Delete -> p.codec.treeToValue(treeNode, Delete::class.java) - ExtendedActivityVocabulary.Dislike -> TODO() - ExtendedActivityVocabulary.Flag -> TODO() - ExtendedActivityVocabulary.Ignore -> TODO() - ExtendedActivityVocabulary.Invite -> TODO() - ExtendedActivityVocabulary.Join -> TODO() - ExtendedActivityVocabulary.Leave -> TODO() + ExtendedActivityVocabulary.Dislike -> null + ExtendedActivityVocabulary.Flag -> null + ExtendedActivityVocabulary.Ignore -> null + ExtendedActivityVocabulary.Invite -> null + ExtendedActivityVocabulary.Join -> null + ExtendedActivityVocabulary.Leave -> null ExtendedActivityVocabulary.Like -> p.codec.treeToValue(treeNode, Like::class.java) - ExtendedActivityVocabulary.Listen -> TODO() - ExtendedActivityVocabulary.Move -> TODO() - ExtendedActivityVocabulary.Offer -> TODO() - ExtendedActivityVocabulary.Question -> TODO() + ExtendedActivityVocabulary.Listen -> null + ExtendedActivityVocabulary.Move -> null + ExtendedActivityVocabulary.Offer -> null + ExtendedActivityVocabulary.Question -> null ExtendedActivityVocabulary.Reject -> p.codec.treeToValue(treeNode, Reject::class.java) - ExtendedActivityVocabulary.Read -> TODO() - ExtendedActivityVocabulary.Remove -> TODO() - ExtendedActivityVocabulary.TentativeReject -> TODO() - ExtendedActivityVocabulary.TentativeAccept -> TODO() - ExtendedActivityVocabulary.Travel -> TODO() + ExtendedActivityVocabulary.Read -> null + ExtendedActivityVocabulary.Remove -> null + ExtendedActivityVocabulary.TentativeReject -> null + ExtendedActivityVocabulary.TentativeAccept -> null + ExtendedActivityVocabulary.Travel -> null ExtendedActivityVocabulary.Undo -> p.codec.treeToValue(treeNode, Undo::class.java) - ExtendedActivityVocabulary.Update -> TODO() - ExtendedActivityVocabulary.View -> TODO() - ExtendedActivityVocabulary.Application -> TODO() - ExtendedActivityVocabulary.Group -> TODO() - ExtendedActivityVocabulary.Organization -> TODO() + ExtendedActivityVocabulary.Update -> null + ExtendedActivityVocabulary.View -> null + ExtendedActivityVocabulary.Application -> null + ExtendedActivityVocabulary.Group -> null + ExtendedActivityVocabulary.Organization -> null ExtendedActivityVocabulary.Person -> p.codec.treeToValue(treeNode, Person::class.java) - ExtendedActivityVocabulary.Service -> TODO() - ExtendedActivityVocabulary.Article -> TODO() - ExtendedActivityVocabulary.Audio -> TODO() + ExtendedActivityVocabulary.Service -> null + ExtendedActivityVocabulary.Article -> null + ExtendedActivityVocabulary.Audio -> null ExtendedActivityVocabulary.Document -> p.codec.treeToValue(treeNode, Document::class.java) - ExtendedActivityVocabulary.Event -> TODO() + ExtendedActivityVocabulary.Event -> null ExtendedActivityVocabulary.Image -> p.codec.treeToValue(treeNode, Image::class.java) - ExtendedActivityVocabulary.Page -> TODO() - ExtendedActivityVocabulary.Place -> TODO() - ExtendedActivityVocabulary.Profile -> TODO() - ExtendedActivityVocabulary.Relationship -> TODO() + ExtendedActivityVocabulary.Page -> null + ExtendedActivityVocabulary.Place -> null + ExtendedActivityVocabulary.Profile -> null + ExtendedActivityVocabulary.Relationship -> null ExtendedActivityVocabulary.Tombstone -> p.codec.treeToValue(treeNode, Tombstone::class.java) - ExtendedActivityVocabulary.Video -> TODO() - ExtendedActivityVocabulary.Mention -> TODO() + ExtendedActivityVocabulary.Video -> null + ExtendedActivityVocabulary.Mention -> null ExtendedActivityVocabulary.Emoji -> p.codec.treeToValue(treeNode, Emoji::class.java) null -> null } } else { - TODO() + return null } } } From 458a1d771753de5dde8466aba90531cfa2f13f08 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 28 Dec 2023 11:35:49 +0900 Subject: [PATCH 19/26] =?UTF-8?q?fix:=20=E3=83=AA=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E5=8F=97=E4=BF=A1=E6=99=82=E3=81=AE?= =?UTF-8?q?=E9=87=8D=E8=A4=87=E6=A4=9C=E6=9F=BB=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/reaction/ReactionRepository.kt | 3 ++ .../ReactionRepositoryImpl.kt | 32 +++++++++++++++++++ .../service/reaction/ReactionServiceImpl.kt | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt index 0b6183b5..8005bbd2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.core.domain.model.reaction +import dev.usbharu.hideout.core.domain.model.emoji.Emoji import org.springframework.stereotype.Repository @Repository @@ -13,5 +14,7 @@ interface ReactionRepository { suspend fun findByPostId(postId: Long): List suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction? suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean + suspend fun existByPostIdAndActorIdAndUnicodeEmoji(postId: Long, actorId: Long, unicodeEmoji: String): Boolean + suspend fun existByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Boolean suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt index 2c4274b7..6847ab14 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt @@ -2,6 +2,7 @@ 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.Emoji 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 @@ -103,6 +104,37 @@ class ReactionRepositoryImpl( }.empty().not() } + override suspend fun existByPostIdAndActorIdAndUnicodeEmoji( + postId: Long, + actorId: Long, + unicodeEmoji: String + ): Boolean = query { + return@query Reactions.select { + Reactions.postId + .eq(postId) + .and(Reactions.actorId.eq(actorId)) + .and(Reactions.unicodeEmoji.eq(unicodeEmoji)) + }.empty().not() + } + + override suspend fun existByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Boolean = query { + val query = Reactions.select { + Reactions.postId + .eq(postId) + .and(Reactions.actorId.eq(actorId)) + } + + + if (emoji is UnicodeEmoji) { + query.andWhere { Reactions.unicodeEmoji eq emoji.name } + } else { + emoji as CustomEmoji + query.andWhere { Reactions.customEmojiId eq emoji.id } + } + + return@query query.empty().not() + } + override suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List = query { return@query Reactions.leftJoin(CustomEmojis) .select { Reactions.postId eq postId and (Reactions.actorId eq actorId) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt index daf38ca3..0d623846 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt @@ -19,7 +19,7 @@ class ReactionServiceImpl( actorId: Long, postId: Long ) { - if (reactionRepository.existByPostIdAndActorIdAndEmojiId(postId, actorId, 0).not()) { + if (reactionRepository.existByPostIdAndActorIdAndEmoji(postId, actorId, emoji).not()) { try { reactionRepository.save( Reaction(reactionRepository.generateId(), emoji, postId, actorId) From 855055d49a93991a240300558a7ab15d57e2572f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:06:55 +0900 Subject: [PATCH 20/26] =?UTF-8?q?feat:=20=E3=83=AA=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E4=B8=80=E4=BA=BA1?= =?UTF-8?q?=E6=8A=95=E7=A8=BF=E3=81=82=E3=81=9F=E3=82=8A=E4=B8=80=E3=81=A4?= =?UTF-8?q?=E3=81=BE=E3=81=A7=E3=81=AB=E5=88=B6=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/reaction/ReactionRepository.kt | 3 ++ .../ReactionRepositoryImpl.kt | 29 +++++++++++++++++++ .../service/reaction/ReactionServiceImpl.kt | 15 +++++----- .../resources/db/migration/V1__Init_DB.sql | 5 ++-- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt index 8005bbd2..4d3964eb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt @@ -11,10 +11,13 @@ interface ReactionRepository { suspend fun delete(reaction: Reaction): Reaction suspend fun deleteByPostId(postId: Long): Int suspend fun deleteByActorId(actorId: Long): Int + suspend fun deleteByPostIdAndActorId(postId: Long, actorId: Long) + suspend fun deleteByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji) suspend fun findByPostId(postId: Long): List suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction? suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean suspend fun existByPostIdAndActorIdAndUnicodeEmoji(postId: Long, actorId: Long, unicodeEmoji: String): Boolean suspend fun existByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Boolean + suspend fun existByPostIdAndActor(postId: Long, actorId: Long): Boolean suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt index 6847ab14..b932a54f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt @@ -79,6 +79,29 @@ class ReactionRepositoryImpl( } } + override suspend fun deleteByPostIdAndActorId(postId: Long, actorId: Long): Unit = query { + Reactions.deleteWhere { + Reactions.postId eq postId and (Reactions.actorId eq actorId) + } + } + + override suspend fun deleteByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Unit = query { + if (emoji is CustomEmoji) { + Reactions.deleteWhere { + Reactions.postId.eq(postId) + .and(Reactions.actorId.eq(actorId)) + .and(Reactions.customEmojiId.eq(emoji.id)) + } + } else { + Reactions.deleteWhere { + Reactions.postId.eq(postId) + .and(Reactions.actorId.eq(actorId)) + .and(Reactions.unicodeEmoji.eq(emoji.name)) + } + } + + } + override suspend fun findByPostId(postId: Long): List = query { return@query Reactions.leftJoin(CustomEmojis).select { Reactions.postId eq postId }.map { it.toReaction() } } @@ -135,6 +158,12 @@ class ReactionRepositoryImpl( return@query query.empty().not() } + override suspend fun existByPostIdAndActor(postId: Long, actorId: Long): Boolean = query { + Reactions.select { + Reactions.postId.eq(postId).and(Reactions.actorId.eq(actorId)) + }.empty().not() + } + override suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List = query { return@query Reactions.leftJoin(CustomEmojis) .select { Reactions.postId eq postId and (Reactions.actorId eq actorId) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt index 0d623846..927dd9fb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt @@ -1,10 +1,10 @@ package dev.usbharu.hideout.core.service.reaction import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService +import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException import dev.usbharu.hideout.core.domain.model.emoji.Emoji import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import org.jetbrains.exposed.exceptions.ExposedSQLException import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -19,13 +19,12 @@ class ReactionServiceImpl( actorId: Long, postId: Long ) { - if (reactionRepository.existByPostIdAndActorIdAndEmoji(postId, actorId, emoji).not()) { - try { - reactionRepository.save( - Reaction(reactionRepository.generateId(), emoji, postId, actorId) - ) - } catch (_: ExposedSQLException) { - } + if (reactionRepository.existByPostIdAndActor(postId, actorId)) { + reactionRepository.deleteByPostIdAndActorId(postId, actorId) + } + try { + reactionRepository.save(Reaction(reactionRepository.generateId(), emoji, postId, actorId)) + } catch (_: DuplicateException) { } } diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index e331c9f8..2e9e638b 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -131,11 +131,12 @@ alter table posts_emojis create table if not exists reactions ( - id bigserial primary key, + id bigint 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 + actor_id bigint not null, + unique (post_id, actor_id) ); alter table reactions add constraint fk_reactions_post_id__id foreign key (post_id) references posts (id) on delete restrict on update restrict; From a015566e52d9982b88ce90360ca230f28f2c0bc0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 7 Jan 2024 12:55:52 +0900 Subject: [PATCH 21/26] =?UTF-8?q?feat:=20emoji-kt=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 11 +++++ .../dev/usbharu/hideout/util/EmojiUtil.kt | 18 ++++++++ .../dev/usbharu/hideout/util/EmojiUtilTest.kt | 41 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 18d64acb..ce4ceb3d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -142,6 +142,15 @@ repositories { password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN") } } + maven { + name = "GitHubPackages2" + url = uri("https://maven.pkg.github.com/multim-dev/emoji-kt") + credentials { + + username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME") + password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN") + } + } } kotlin { @@ -205,6 +214,8 @@ dependencies { implementation("org.bytedeco:javacv-platform:1.5.9") implementation("org.flywaydb:flyway-core") + implementation("dev.usbharu:emoji-kt:2.0.0") + implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt new file mode 100644 index 00000000..28eda408 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.util + +import Emojis + +object EmojiUtil { + + + val emojiMap by lazy { + Emojis.allEmojis + .associate { it.code.replace(" ", "-") to it.char } + .filterValues { it != "™" } + + } + + fun isEmoji(string: String): Boolean { + return emojiMap.any { it.value == string } + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt b/src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt new file mode 100644 index 00000000..a34b8aa2 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt @@ -0,0 +1,41 @@ +package dev.usbharu.hideout.util + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class EmojiUtilTest { + + @Test + fun 絵文字を判定できる() { + val emoji = "❤" + val actual = EmojiUtil.isEmoji(emoji) + + assertThat(actual).isTrue() + } + + @Test + fun ただの文字を判定できる() { + val moji = "blobblinkhyper" + val actual = EmojiUtil.isEmoji(moji) + + assertThat(actual).isFalse() + } + + @ParameterizedTest + @ValueSource(strings = ["❤", "🌄", "🤗", "⛺", "🧑‍🤝‍🧑", "🖐🏿"]) + fun `絵文字判定`(s: String) { + val actual = EmojiUtil.isEmoji(s) + + assertThat(actual).isTrue() + } + + @ParameterizedTest + @ValueSource(strings = ["™", "㍂", "㌠"]) + fun `文字判定`(s: String) { + val actual = EmojiUtil.isEmoji(s) + + assertThat(actual).isFalse() + } +} From a5618e330733a1a5bfce6ea3945109f2ddb402e6 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 7 Jan 2024 12:56:32 +0900 Subject: [PATCH 22/26] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3API?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/objects/emoji/EmojiService.kt | 1 + .../service/objects/emoji/EmojiServiceImpl.kt | 20 ++++- .../exposedquery/StatusQueryServiceImpl.kt | 4 + .../status/MastodonStatusesApiContoller.kt | 12 +++ .../mastodon/query/StatusQueryService.kt | 2 + .../service/status/StatusesApiService.kt | 87 ++++++++++++++++++- src/main/resources/openapi/mastodon.yaml | 73 ++++++++++++++++ 7 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt index 4dd6e2d1..430908b1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt @@ -6,4 +6,5 @@ import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji interface EmojiService { suspend fun fetchEmoji(url: String): Pair suspend fun fetchEmoji(emoji: Emoji): Pair + suspend fun findByEmojiName(emojiName: String): CustomEmoji? } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt index 3c990b59..90a2e1a7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.activitypub.service.objects.emoji import dev.usbharu.hideout.activitypub.domain.model.Emoji import dev.usbharu.hideout.activitypub.service.common.APResourceResolveServiceImpl import dev.usbharu.hideout.activitypub.service.common.resolve +import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository import dev.usbharu.hideout.core.service.instance.InstanceService @@ -17,7 +18,8 @@ class EmojiServiceImpl( private val customEmojiRepository: CustomEmojiRepository, private val instanceService: InstanceService, private val mediaService: MediaService, - private val apResourceResolveServiceImpl: APResourceResolveServiceImpl + private val apResourceResolveServiceImpl: APResourceResolveServiceImpl, + private val applicationConfig: ApplicationConfig ) : EmojiService { override suspend fun fetchEmoji(url: String): Pair { val emoji = apResourceResolveServiceImpl.resolve(url, null as Long?) @@ -60,4 +62,20 @@ class EmojiServiceImpl( return customEmojiRepository.save(customEmoji1) } + + override suspend fun findByEmojiName(emojiName: String): CustomEmoji? { + val split = emojiName.trim(':').split("@") + + return when (split.size) { + 1 -> { + customEmojiRepository.findByNameAndDomain(split.first(), applicationConfig.url.host) + } + + 2 -> { + customEmojiRepository.findByNameAndDomain(split.first(), split[1]) + } + + else -> throw IllegalArgumentException("Unknown Emoji Format. $emojiName") + } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt index 937f2d9b..40e92ee8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt @@ -108,6 +108,10 @@ class StatusQueryServiceImpl : StatusQueryService { return resolveReplyAndRepost(pairs) } + override suspend fun findByPostId(id: Long): Status { + TODO("Not yet implemented") + } + private fun resolveReplyAndRepost(pairs: List>): List { val statuses = pairs.map { it.first } return pairs diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt index 2b2e65eb..705f94f7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt @@ -24,4 +24,16 @@ class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiSe HttpStatus.OK ) } + + override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity { + return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji) + } + + override suspend fun apiV1StatusesIdEmojiReactionsEmojiPut(id: String, emoji: String): ResponseEntity { + return super.apiV1StatusesIdEmojiReactionsEmojiPut(id, emoji) + } + + override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity { + return super.apiV1StatusesIdGet(id) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt index 2b4e2a31..fe78a70d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt @@ -36,4 +36,6 @@ interface StatusQueryService { tagged: String? = null, includeFollowers: Boolean = false ): List + + suspend fun findByPostId(id: Long): Status } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt index 1a095d82..de96e340 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt @@ -1,17 +1,24 @@ package dev.usbharu.hideout.mastodon.service.status +import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments import dev.usbharu.hideout.core.domain.model.post.PostRepository +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.service.post.PostCreateDto import dev.usbharu.hideout.core.service.post.PostService +import dev.usbharu.hideout.core.service.reaction.ReactionService import dev.usbharu.hideout.domain.mastodon.model.generated.Status +import dev.usbharu.hideout.domain.mastodon.model.generated.Status.Visibility.* import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest import dev.usbharu.hideout.mastodon.interfaces.api.status.toPostVisibility import dev.usbharu.hideout.mastodon.interfaces.api.status.toStatusVisibility +import dev.usbharu.hideout.mastodon.query.StatusQueryService import dev.usbharu.hideout.mastodon.service.account.AccountService +import dev.usbharu.hideout.util.EmojiUtil import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.time.Instant @@ -22,6 +29,23 @@ interface StatusesApiService { statusesRequest: StatusesRequest, userId: Long ): Status + + suspend fun findById( + id: Long, + userId: Long? + ): Status? + + suspend fun emojiReactions( + postId: Long, + userId: Long, + emojiName: String + ): Status? + + suspend fun removeEmojiReactions( + postId: Long, + userId: Long, + emojiName: String + ): Status? } @Service @@ -31,7 +55,11 @@ class StatsesApiServiceImpl( private val mediaRepository: MediaRepository, private val transaction: Transaction, private val actorRepository: ActorRepository, - private val postRepository: PostRepository + private val postRepository: PostRepository, + private val statusQueryService: StatusQueryService, + private val relationshipRepository: RelationshipRepository, + private val reactionService: ReactionService, + private val emojiService: EmojiService ) : StatusesApiService { override suspend fun postStatus( @@ -95,6 +123,63 @@ class StatsesApiServiceImpl( ) } + override suspend fun findById(id: Long, userId: Long?): Status? { + val status = statusQueryService.findByPostId(id) + + return status(status, userId) + } + + private suspend fun status( + status: Status, + userId: Long? + ): Status? { + return when (status.visibility) { + public -> status + unlisted -> status + private -> { + if (userId == null) { + return null + } + + val relationship = + relationshipRepository.findByUserIdAndTargetUserId(userId, status.account.id.toLong()) + ?: return null + if (relationship.following) { + return status + } + return null + } + + direct -> null + } + } + + override suspend fun emojiReactions(postId: Long, userId: Long, emojiName: String): Status? { + + status(statusQueryService.findByPostId(postId), userId) ?: return null + + val emoji = try { + if (EmojiUtil.isEmoji(emojiName)) { + UnicodeEmoji(emojiName) + } else { + emojiService.findByEmojiName(emojiName)!! + } + } catch (e: IllegalStateException) { + UnicodeEmoji("❤") + } catch (e: NullPointerException) { + UnicodeEmoji("❤") + } + reactionService.sendReaction(emoji, userId, postId) + return statusQueryService.findByPostId(postId) + } + + override suspend fun removeEmojiReactions(postId: Long, userId: Long, emojiName: String): Status? { + + reactionService.removeReaction(userId, postId) + + return status(statusQueryService.findByPostId(postId), userId) + } + companion object { private val logger = LoggerFactory.getLogger(StatusesApiService::class.java) } diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 15df90d9..3a6ce32c 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -152,6 +152,79 @@ paths: schema: $ref: "#/components/schemas/Status" + /api/v1/statuses/{id}: + get: + tags: + - status + security: + - OAuth2: + - "write:statuses" + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Status" + + /api/v1/statuses/{id}/emoji_reactions/{emoji}: + put: + tags: + - status + security: + - OAuth2: + - "write:statuses" + parameters: + - in: path + name: id + required: true + schema: + type: string + - in: path + name: emoji + required: true + schema: + type: string + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Status" + + delete: + tags: + - status + security: + - OAuth2: + - "write:statuses" + parameters: + - in: path + name: id + required: true + schema: + type: string + - in: path + name: emoji + required: true + schema: + type: string + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Status" + + /api/v1/apps: post: tags: From f06961b0bdbc7624f3f273658c56c00a4e547ba4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:06:09 +0900 Subject: [PATCH 23/26] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3API?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/mastodon/status/StatusTest.kt | 72 ++++++++++++++++++- .../resources/sql/test-custom-emoji.sql | 3 + .../CustomEmojiRepositoryImpl.kt | 12 ++++ .../exposedquery/StatusQueryServiceImpl.kt | 17 ++++- .../status/MastodonStatusesApiContoller.kt | 10 ++- 5 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 src/intTest/resources/sql/test-custom-emoji.sql diff --git a/src/intTest/kotlin/mastodon/status/StatusTest.kt b/src/intTest/kotlin/mastodon/status/StatusTest.kt index 54b61c9d..93785f56 100644 --- a/src/intTest/kotlin/mastodon/status/StatusTest.kt +++ b/src/intTest/kotlin/mastodon/status/StatusTest.kt @@ -1,7 +1,15 @@ package mastodon.status import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji +import dev.usbharu.hideout.core.infrastructure.exposedrepository.CustomEmojis +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions +import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction +import org.assertj.core.api.Assertions.assertThat import org.flywaydb.core.Flyway +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.select import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -13,19 +21,24 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.test.context.support.WithAnonymousUser import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers import org.springframework.test.context.jdbc.Sql import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.put import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.transaction.annotation.Transactional import org.springframework.web.context.WebApplicationContext +import java.time.Instant @SpringBootTest(classes = [SpringApplication::class]) @AutoConfigureMockMvc @Transactional @Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@Sql("/sql/test-post.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@Sql("/sql/test-custom-emoji.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) class StatusTest { @Autowired @@ -124,7 +137,6 @@ class StatusTest { } @Test - @Sql("/sql/test-post.sql") fun in_reply_to_idを指定したら返信として処理される() { mockMvc .post("/api/v1/statuses") { @@ -145,6 +157,64 @@ class StatusTest { .andExpect { jsonPath("\$.in_reply_to_id") { value("1") } } } + @Test + fun ユニコード絵文字をリアクションできる() { + mockMvc + .put("/api/v1/statuses/1/emoji_reactions/😭") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + + val reaction = Reactions.select { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction() + assertThat(reaction.emoji).isEqualTo(UnicodeEmoji("😭")) + assertThat(reaction.postId).isEqualTo(1) + assertThat(reaction.actorId).isEqualTo(1) + } + + @Test + fun 存在しない絵文字はフォールバックされる() { + mockMvc + .put("/api/v1/statuses/1/emoji_reactions/hoge") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + + val reaction = Reactions.select { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction() + assertThat(reaction.emoji).isEqualTo(UnicodeEmoji("❤")) + assertThat(reaction.postId).isEqualTo(1) + assertThat(reaction.actorId).isEqualTo(1) + } + + @Test + fun カスタム絵文字をリアクションできる() { + mockMvc + .put("/api/v1/statuses/1/emoji_reactions/kotlin") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + + val reaction = + Reactions.leftJoin(CustomEmojis).select { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single() + .toReaction() + assertThat(reaction.emoji).isEqualTo( + CustomEmoji( + 1, + "kotlin", + "example.com", + null, + "https://example.com/emojis/kotlin", + null, + Instant.ofEpochMilli(1704700290036) + ) + ) + } + companion object { @JvmStatic @AfterAll diff --git a/src/intTest/resources/sql/test-custom-emoji.sql b/src/intTest/resources/sql/test-custom-emoji.sql new file mode 100644 index 00000000..4d70e3b4 --- /dev/null +++ b/src/intTest/resources/sql/test-custom-emoji.sql @@ -0,0 +1,3 @@ +insert into emojis(id, name, domain, instance_id, url, category, created_at) +VALUES (1, 'kotlin', 'example.com', null, 'https://example.com/emojis/kotlin', null, + TIMESTAMP '2024-01-08 16:51:30.036'); diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt index 0df58975..07c735ff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt @@ -74,6 +74,18 @@ fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji( this[CustomEmojis.createdAt] ) +fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? { + return CustomEmoji( + id = this.getOrNull(CustomEmojis.id) ?: return null, + name = this.getOrNull(CustomEmojis.name) ?: return null, + domain = this.getOrNull(CustomEmojis.domain) ?: return null, + instanceId = this[CustomEmojis.instanceId], + url = this.getOrNull(CustomEmojis.url) ?: return null, + category = this[CustomEmojis.category], + createdAt = this.getOrNull(CustomEmojis.createdAt) ?: return null + ) +} + object CustomEmojis : Table("emojis") { val id = long("id") val name = varchar("name", 1000) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt index 40e92ee8..86509c08 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt @@ -109,7 +109,22 @@ class StatusQueryServiceImpl : StatusQueryService { } override suspend fun findByPostId(id: Long): Status { - TODO("Not yet implemented") + val map = Posts + .leftJoin(PostsMedia) + .leftJoin(Actors) + .leftJoin(Media) + .select { Posts.id eq id } + .groupBy { it[Posts.id] } + .map { it.value } + .map { + toStatus(it.first()).copy( + mediaAttachments = it.mapNotNull { resultRow -> + resultRow.toMediaOrNull()?.toMediaAttachments() + }, + emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() } + ) to it.first()[Posts.repostId] + } + return resolveReplyAndRepost(map).single() } private fun resolveReplyAndRepost(pairs: List>): List { diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt index 705f94f7..6c4a6709 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt @@ -26,11 +26,17 @@ class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiSe } override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity { - return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji) + val uid = + (SecurityContextHolder.getContext().authentication.principal as Jwt).getClaim("uid").toLong() + + return ResponseEntity.ok(statusesApiService.removeEmojiReactions(id.toLong(), uid, emoji)) } override suspend fun apiV1StatusesIdEmojiReactionsEmojiPut(id: String, emoji: String): ResponseEntity { - return super.apiV1StatusesIdEmojiReactionsEmojiPut(id, emoji) + val uid = + (SecurityContextHolder.getContext().authentication.principal as Jwt).getClaim("uid").toLong() + + return ResponseEntity.ok(statusesApiService.emojiReactions(id.toLong(), uid, emoji)) } override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity { From c83b990a7880501aa9357b931a2f43f119afa0e7 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:30:23 +0900 Subject: [PATCH 24/26] =?UTF-8?q?test:=20=E3=83=AA=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E9=87=8D=E8=A4=87=E6=99=82=E3=81=AE?= =?UTF-8?q?=E6=8C=99=E5=8B=95=E3=81=AE=E5=A4=89=E6=9B=B4=E3=82=92=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=AB=E5=8F=8D=E6=98=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reaction/ReactionServiceImplTest.kt | 45 ++++--------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt index 2f54b06f..f3863d64 100644 --- a/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt @@ -7,7 +7,6 @@ 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 kotlinx.coroutines.test.runTest -import org.jetbrains.exposed.exceptions.ExposedSQLException import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.InjectMocks @@ -33,7 +32,7 @@ class ReactionServiceImplTest { val post = PostBuilder.of() - whenever(reactionRepository.existByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( + whenever(reactionRepository.existByPostIdAndActor(eq(post.id), eq(post.actorId))).doReturn( false ) val generateId = TwitterSnowflakeIdGenerateService.generateId() @@ -44,48 +43,22 @@ class ReactionServiceImplTest { verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) } - @Test - fun `receiveReaction リアクションが既に作成されていることを検知出来ずに例外が発生した場合は何もしない`() = runTest { - val post = PostBuilder.of() - - whenever(reactionRepository.existByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( - false - ) - val generateId = TwitterSnowflakeIdGenerateService.generateId() - whenever( - reactionRepository.save( - eq( - Reaction( - id = generateId, - emoji = UnicodeEmoji("❤"), - postId = post.id, - actorId = post.actorId - ) - ) - ) - ).doAnswer { - throw ExposedSQLException( - null, - emptyList(), mock() - ) - } - whenever(reactionRepository.generateId()).doReturn(generateId) - - reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) - - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) - } @Test - fun `receiveReaction リアクションが既に作成されている場合は何もしない`() = runTest() { + fun `receiveReaction リアクションが既に作成されている場合削除して新しく作成`() = runTest() { val post = PostBuilder.of() - whenever(reactionRepository.existByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( + whenever(reactionRepository.existByPostIdAndActor(eq(post.id), eq(post.actorId))).doReturn( true ) + val generateId = TwitterSnowflakeIdGenerateService.generateId() + + whenever(reactionRepository.generateId()).doReturn(generateId) + reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) - verify(reactionRepository, never()).save(any()) + verify(reactionRepository, times(1)).deleteByPostIdAndActorId(post.id, post.actorId) + verify(reactionRepository, times(1)).save(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId)) } @Test From 905f0f7c2f88a920be8d397fd0f73490a65766d8 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:39:55 +0900 Subject: [PATCH 25/26] style: fix lint --- .../model/objects/ObjectDeserializer.kt | 2 +- .../service/activity/like/APLikeProcessor.kt | 2 +- .../common/APResourceResolveServiceImpl.kt | 4 +--- .../service/inbox/InboxJobProcessor.kt | 2 +- .../service/objects/emoji/EmojiServiceImpl.kt | 18 ++++++++--------- .../service/objects/note/APNoteService.kt | 1 - .../exposed/ExposedTransaction.kt | 6 ------ .../core/domain/model/emoji/CustomEmoji.kt | 10 ++++------ .../core/domain/model/instance/Nodeinfo.kt | 4 +--- .../model/reaction/ReactionRepository.kt | 2 +- .../RelationshipRepositoryImpl.kt | 6 +++--- .../CustomEmojiRepositoryImpl.kt | 20 +++++++++---------- .../ReactionRepositoryImpl.kt | 16 +++++++-------- .../springframework/oauth2/UserDetailsImpl.kt | 10 +++++----- .../hideout/core/service/media/SavedMedia.kt | 4 +--- .../exposedquery/StatusQueryServiceImpl.kt | 2 +- .../status/MastodonStatusesApiContoller.kt | 4 +--- .../service/status/StatusesApiService.kt | 7 +++---- .../dev/usbharu/hideout/util/EmojiUtil.kt | 6 +----- .../dev/usbharu/hideout/util/TempFileUtil.kt | 4 +--- 20 files changed, 51 insertions(+), 79 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt index 74076768..f35b0c4f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt @@ -24,7 +24,7 @@ class ObjectDeserializer : JsonDeserializer() { ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } } } else if (type.isValueNode) { - ExtendedActivityVocabulary.values().firstOrNull() { it.name.equals(type.asText(), true) } + ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(type.asText(), true) } } else { null } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt index e230e230..47819d9b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt @@ -36,7 +36,7 @@ class APLikeProcessor( val emoji = if (content.startsWith(":")) { val tag = activity.activity.tag - (tag.firstOrNull { it is Emoji } as Emoji?)?.let { emojiService.fetchEmoji(it).second } + (tag.firstOrNull { it is Emoji } as? Emoji)?.let { emojiService.fetchEmoji(it).second } } else { UnicodeEmoji(content) } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt index 246b02d8..05eed6d7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt @@ -83,8 +83,6 @@ class APResourceResolveServiceImpl( return objects == other.objects } - override fun hashCode(): Int { - return objects.hashCode() - } + override fun hashCode(): Int = objects.hashCode() } } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt index 2a884ae0..fbe38266 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt @@ -112,7 +112,7 @@ class InboxJobProcessor( logger.debug("Is verifying success? {}", verify) val activityPubProcessor = - activityPubProcessorList.firstOrNull { it.isSupported(param.type) } as ActivityPubProcessor? + activityPubProcessorList.firstOrNull { it.isSupported(param.type) } as? ActivityPubProcessor if (activityPubProcessor == null) { logger.warn("ActivityType {} is not support.", param.type) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt index 90a2e1a7..d54f508a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt @@ -26,9 +26,7 @@ class EmojiServiceImpl( return fetchEmoji(emoji) } - override suspend fun fetchEmoji(emoji: Emoji): Pair { - return emoji to save(emoji) - } + override suspend fun fetchEmoji(emoji: Emoji): Pair = emoji to save(emoji) private suspend fun save(emoji: Emoji): CustomEmoji { val domain = URL(emoji.id).host @@ -51,13 +49,13 @@ class EmojiServiceImpl( ) val customEmoji1 = CustomEmoji( - customEmojiRepository.generateId(), - name, - domain, - instance.id, - media.url, - null, - Instant.now() + id = customEmojiRepository.generateId(), + name = name, + domain = domain, + instanceId = instance.id, + url = media.url, + category = null, + createdAt = Instant.now() ) return customEmojiRepository.save(customEmoji1) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt index 80e2889e..43d0b7f1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt @@ -113,7 +113,6 @@ class APNoteServiceImpl( it.id } - val mediaList = note.attachment.map { mediaService.uploadRemoteMedia( RemoteMedia( diff --git a/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt b/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt index 05fe6305..535375c5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt @@ -13,12 +13,6 @@ import java.sql.Connection @Service class ExposedTransaction : Transaction { override suspend fun transaction(block: suspend () -> T): T { -// return newSuspendedTransaction(MDCContext(), transactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED) { -// warnLongQueriesDuration = 1000 -// addLogger(Slf4jSqlDebugLogger) -// block() -// } - return transaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) { debug = true warnLongQueriesDuration = 1000 diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt index d02e9103..fb4579aa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt @@ -5,6 +5,8 @@ import java.time.Instant sealed class Emoji { abstract val domain: String abstract val name: String + + @Suppress("FunctionMinLength") abstract fun id(): String override fun toString(): String { return "Emoji(" + @@ -23,16 +25,12 @@ data class CustomEmoji( val category: String?, val createdAt: Instant ) : Emoji() { - override fun id(): String { - return id.toString() - } + override fun id(): String = id.toString() } data class UnicodeEmoji( override val name: String ) : Emoji() { override val domain: String = "unicode.org" - override fun id(): String { - return name - } + override fun id(): String = name } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt index e2e44267..a17fc724 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt @@ -12,9 +12,7 @@ class Nodeinfo private constructor() { return links == other.links } - override fun hashCode(): Int { - return links.hashCode() - } + override fun hashCode(): Int = links.hashCode() } class Links private constructor() { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt index 4d3964eb..df35e349 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt @@ -4,7 +4,7 @@ import dev.usbharu.hideout.core.domain.model.emoji.Emoji import org.springframework.stereotype.Repository @Repository -@Suppress("FunctionMaxLength") +@Suppress("FunctionMaxLength", "TooManyFunction") interface ReactionRepository { suspend fun generateId(): Long suspend fun save(reaction: Reaction): Reaction diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt index a8085686..9cea8aaa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt @@ -11,6 +11,9 @@ import org.springframework.stereotype.Service @Service class RelationshipRepositoryImpl : RelationshipRepository, AbstractRepository() { + override val logger: Logger + get() = Companion.logger + override suspend fun save(relationship: Relationship): Relationship = query { val singleOrNull = Relationships.select { (Relationships.actorId eq relationship.actorId).and( @@ -94,9 +97,6 @@ class RelationshipRepositoryImpl : RelationshipRepository, AbstractRepository() return@query query.map { it.toRelationships() } } - override val logger: Logger - get() = Companion.logger - companion object { private val logger = LoggerFactory.getLogger(RelationshipRepositoryImpl::class.java) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt index 07c735ff..3310c138 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt @@ -14,6 +14,9 @@ import org.springframework.stereotype.Repository @Repository class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService) : CustomEmojiRepository, AbstractRepository() { + override val logger: Logger + get() = Companion.logger + override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun save(customEmoji: CustomEmoji): CustomEmoji = query { @@ -56,22 +59,19 @@ class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService ?.toCustomEmoji() } - 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] + id = this[CustomEmojis.id], + name = this[CustomEmojis.name], + domain = this[CustomEmojis.domain], + instanceId = this[CustomEmojis.instanceId], + url = this[CustomEmojis.url], + category = this[CustomEmojis.category], + createdAt = this[CustomEmojis.createdAt] ) fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt index b932a54f..50887de2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt @@ -99,7 +99,6 @@ class ReactionRepositoryImpl( .and(Reactions.unicodeEmoji.eq(emoji.name)) } } - } override suspend fun findByPostId(postId: Long): List = query { @@ -147,7 +146,6 @@ class ReactionRepositoryImpl( .and(Reactions.actorId.eq(actorId)) } - if (emoji is UnicodeEmoji) { query.andWhere { Reactions.unicodeEmoji eq emoji.name } } else { @@ -178,13 +176,13 @@ 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] + id = this[Reactions.customEmojiId]!!, + name = this[CustomEmojis.name], + domain = this[CustomEmojis.domain], + instanceId = this[CustomEmojis.instanceId], + url = this[CustomEmojis.url], + category = this[CustomEmojis.category], + createdAt = this[CustomEmojis.createdAt] ) } else if (this[Reactions.unicodeEmoji] != null) { UnicodeEmoji(this[Reactions.unicodeEmoji]!!) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt index cb06f2ce..f41d3fe4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt @@ -26,11 +26,6 @@ class UserDetailsImpl( accountNonLocked: Boolean, authorities: MutableCollection? ) : User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities) { - companion object { - @Serial - private const val serialVersionUID: Long = -899168205656607781L - } - override fun toString(): String { return "UserDetailsImpl(" + "id=$id" + @@ -53,6 +48,11 @@ class UserDetailsImpl( result = 31 * result + id.hashCode() return result } + + companion object { + @Serial + private const val serialVersionUID: Long = -899168205656607781L + } } @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt index 50a2caac..7a5eaa1c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt @@ -10,9 +10,7 @@ sealed class SavedMedia(val success: Boolean) { return success == other.success } - override fun hashCode(): Int { - return success.hashCode() - } + override fun hashCode(): Int = success.hashCode() } class SuccessSavedMedia( diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt index 86509c08..e3c47e6e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt @@ -161,7 +161,7 @@ class StatusQueryServiceImpl : StatusQueryService { mediaAttachments = it.mapNotNull { resultRow -> resultRow.toMediaOrNull()?.toMediaAttachments() }, - emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmoji()?.toMastodonEmoji() } + emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() } ) to it.first()[Posts.repostId] } return resolveReplyAndRepost(pairs) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt index 6c4a6709..cc6443ce 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt @@ -39,7 +39,5 @@ class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiSe return ResponseEntity.ok(statusesApiService.emojiReactions(id.toLong(), uid, emoji)) } - override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity { - return super.apiV1StatusesIdGet(id) - } + override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity = super.apiV1StatusesIdGet(id) } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt index de96e340..563f2059 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt @@ -49,6 +49,7 @@ interface StatusesApiService { } @Service +@Suppress("LongParameterList") class StatsesApiServiceImpl( private val postService: PostService, private val accountService: AccountService, @@ -155,7 +156,6 @@ class StatsesApiServiceImpl( } override suspend fun emojiReactions(postId: Long, userId: Long, emojiName: String): Status? { - status(statusQueryService.findByPostId(postId), userId) ?: return null val emoji = try { @@ -164,9 +164,9 @@ class StatsesApiServiceImpl( } else { emojiService.findByEmojiName(emojiName)!! } - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { UnicodeEmoji("❤") - } catch (e: NullPointerException) { + } catch (_: NullPointerException) { UnicodeEmoji("❤") } reactionService.sendReaction(emoji, userId, postId) @@ -174,7 +174,6 @@ class StatsesApiServiceImpl( } override suspend fun removeEmojiReactions(postId: Long, userId: Long, emojiName: String): Status? { - reactionService.removeReaction(userId, postId) return status(statusQueryService.findByPostId(postId), userId) diff --git a/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt index 28eda408..4e5c5393 100644 --- a/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt +++ b/src/main/kotlin/dev/usbharu/hideout/util/EmojiUtil.kt @@ -4,15 +4,11 @@ import Emojis object EmojiUtil { - val emojiMap by lazy { Emojis.allEmojis .associate { it.code.replace(" ", "-") to it.char } .filterValues { it != "™" } - } - fun isEmoji(string: String): Boolean { - return emojiMap.any { it.value == string } - } + fun isEmoji(string: String): Boolean = emojiMap.any { it.value == string } } diff --git a/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt index 8a506767..7e57e31e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt +++ b/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt @@ -19,7 +19,5 @@ class TempFile(val path: T) : AutoCloseable { return path == other.path } - override fun hashCode(): Int { - return path?.hashCode() ?: 0 - } + override fun hashCode(): Int = path?.hashCode() ?: 0 } From 606d6f4eccaf151e3f95bd679d8b5cdca73aecd6 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:57:37 +0900 Subject: [PATCH 26/26] test: fix test timezone --- src/intTest/resources/sql/test-custom-emoji.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intTest/resources/sql/test-custom-emoji.sql b/src/intTest/resources/sql/test-custom-emoji.sql index 4d70e3b4..83c2747a 100644 --- a/src/intTest/resources/sql/test-custom-emoji.sql +++ b/src/intTest/resources/sql/test-custom-emoji.sql @@ -1,3 +1,3 @@ insert into emojis(id, name, domain, instance_id, url, category, created_at) VALUES (1, 'kotlin', 'example.com', null, 'https://example.com/emojis/kotlin', null, - TIMESTAMP '2024-01-08 16:51:30.036'); + TIMESTAMP '2024-01-08 07:51:30.036Z');