From 5b523fccc9f1f57709dd73269d4175455338f48e Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:08:37 +0900 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E3=81=AE?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=BF=E3=83=BC=E3=83=95=E3=82=A7=E3=82=A4?= =?UTF-8?q?=E3=82=B9=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/notification/Notification.kt | 12 +++++++ .../NotificationManagimentService.kt | 7 ++++ .../notification/NotificationRequest.kt | 36 +++++++++++++++++++ .../notification/NotificationService.kt | 8 +++++ 4 files changed, 63 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt new file mode 100644 index 00000000..e1b48e5d --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt @@ -0,0 +1,12 @@ +package dev.usbharu.hideout.core.domain.model.notification + +import java.time.Instant + +data class Notification( + val userId: Long, + val sourceActorId: Long?, + val postId: Long?, + val text: String?, + val reactionId: Long?, + val createdAt: Instant +) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt new file mode 100644 index 00000000..3281087d --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.relationship.Relationship + +interface NotificationManagimentService { + fun sendNotification(relationship: Relationship, notificationRequest: NotificationRequest): Boolean +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt new file mode 100644 index 00000000..3080c32e --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt @@ -0,0 +1,36 @@ +package dev.usbharu.hideout.core.service.notification + +sealed class NotificationRequest(open val userId: Long, open val sourceActorId: Long) + +interface PostId { + val postId: Long +} + +data class MentionNotificationRequest( + override val userId: Long, override val sourceActorId: Long, override val postId: Long +) : NotificationRequest( + userId, sourceActorId +), PostId + +data class PostNotificationRequest( + override val userId: Long, override val sourceActorId: Long, override val postId: Long + +) : NotificationRequest(userId, sourceActorId), PostId + +data class RepostNotificationRequest( + override val userId: Long, override val sourceActorId: Long, override val postId: Long +) : NotificationRequest(userId, sourceActorId), PostId + +data class FollowNotificationRequest( + override val userId: Long, override val sourceActorId: Long, override val postId: Long + +) : NotificationRequest(userId, sourceActorId), PostId + +data class FollowRequestNotificationRequest( + override val userId: Long, override val sourceActorId: Long, override val postId: Long +) : NotificationRequest(userId, sourceActorId), PostId + +data class ReactionNotificationRequest( + override val userId: Long, override val sourceActorId: Long, override val postId: Long, val reactionId: Long + +) : NotificationRequest(userId, sourceActorId), PostId diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt new file mode 100644 index 00000000..9c453e49 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification + +interface NotificationService { + suspend fun publishNotify(notificationRequest: NotificationRequest): Notification + suspend fun unpublishNotify(notificationId: Long) +} From 864440877a334a33ff16d8ad4881a8e8a9594482 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:09:51 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20Notification=E3=81=ABid=E3=82=92?= =?UTF-8?q?=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/notification/Notification.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt index e1b48e5d..2026778c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.domain.model.notification import java.time.Instant data class Notification( + val id: Long, val userId: Long, val sourceActorId: Long?, val postId: Long?, From ccbcdd880dc243e0d36e6a0ef4ed079e6de33c39 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:22:41 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20Notification=E3=81=ABRepository?= =?UTF-8?q?=E3=81=A8Exposed=E5=AE=9F=E8=A3=85=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExposedNotificationRepository.kt | 81 +++++++++++++++++++ .../notification/NotificationRepository.kt | 8 ++ 2 files changed, 89 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt new file mode 100644 index 00000000..71e8acee --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt @@ -0,0 +1,81 @@ +package dev.usbharu.hideout.core.domain.model.notification + +import dev.usbharu.hideout.application.service.id.IdGenerateService +import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.javatime.timestamp +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedNotificationRepository(private val idGenerateService: IdGenerateService) : NotificationRepository, + AbstractRepository() { + override val logger: Logger + get() = Companion.logger + + override suspend fun generateId(): Long = idGenerateService.generateId() + + override suspend fun save(notification: Notification): Notification = query { + val singleOrNull = Notifications.select { + Notifications.id eq notification.id + }.forUpdate().singleOrNull() + if (singleOrNull == null) { + Notifications.insert { + it[id] = notification.id + it[userId] = notification.userId + it[sourceActorId] = notification.sourceActorId + it[postId] = notification.postId + it[text] = notification.text + it[reactionId] = notification.reactionId + it[createdAt] = notification.createdAt + } + } else { + Notifications.update({ Notifications.id eq notification.id }) { + it[userId] = notification.userId + it[sourceActorId] = notification.sourceActorId + it[postId] = notification.postId + it[text] = notification.text + it[reactionId] = notification.reactionId + it[createdAt] = notification.createdAt + } + } + notification + } + + override suspend fun findById(id: Long): Notification? = query { + Notifications.select { Notifications.id eq id }.singleOrNull()?.toNotifications() + } + + override suspend fun deleteById(id: Long) { + Notifications.deleteWhere { Notifications.id eq id } + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedNotificationRepository::class.java) + } +} + +fun ResultRow.toNotifications() = Notification( + this[Notifications.id], + this[Notifications.userId], + this[Notifications.sourceActorId], + this[Notifications.postId], + this[Notifications.text], + this[Notifications.reactionId], + this[Notifications.createdAt], +) + +object Notifications : Table("notifications") { + val id = long("id") + val userId = long("user_id").references(Actors.id) + val sourceActorId = long("source_actor_id").references(Actors.id).nullable() + val postId = long("post_id").references(Posts.id).nullable() + val text = varchar("text", 3000).nullable() + val reactionId = long("reaction_id").references(Reactions.id).nullable() + val createdAt = timestamp("created_at") +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt new file mode 100644 index 00000000..97f99fa9 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.core.domain.model.notification + +interface NotificationRepository { + suspend fun generateId(): Long + suspend fun save(notification: Notification): Notification + suspend fun findById(id: Long): Notification? + suspend fun deleteById(id: Long) +} From f614dc7eaf4780821fecd9ddfa0ec7f780575743 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 26 Jan 2024 23:59:59 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E3=81=AE?= =?UTF-8?q?=E4=BB=95=E7=B5=84=E3=81=BF=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExposedNotificationRepository.kt | 4 + .../domain/model/notification/Notification.kt | 1 + .../model/reaction/ReactionRepository.kt | 1 + .../ReactionRepositoryImpl.kt | 4 + .../notification/NotificationRequest.kt | 93 ++++++++++++++++--- .../notification/NotificationService.kt | 2 +- .../notification/NotificationServiceImpl.kt | 70 ++++++++++++++ .../service/notification/NotificationStore.kt | 18 ++++ ...ationshipNotificationManagementService.kt} | 2 +- ...onshipNotificationManagementServiceImpl.kt | 10 ++ 10 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt rename src/main/kotlin/dev/usbharu/hideout/core/service/notification/{NotificationManagimentService.kt => RelationshipNotificationManagementService.kt} (81%) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt index 71e8acee..ab3c23b6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt @@ -27,6 +27,7 @@ class ExposedNotificationRepository(private val idGenerateService: IdGenerateSer if (singleOrNull == null) { Notifications.insert { it[id] = notification.id + it[type] = notification.type it[userId] = notification.userId it[sourceActorId] = notification.sourceActorId it[postId] = notification.postId @@ -36,6 +37,7 @@ class ExposedNotificationRepository(private val idGenerateService: IdGenerateSer } } else { Notifications.update({ Notifications.id eq notification.id }) { + it[type] = notification.type it[userId] = notification.userId it[sourceActorId] = notification.sourceActorId it[postId] = notification.postId @@ -62,6 +64,7 @@ class ExposedNotificationRepository(private val idGenerateService: IdGenerateSer fun ResultRow.toNotifications() = Notification( this[Notifications.id], + this[Notifications.type], this[Notifications.userId], this[Notifications.sourceActorId], this[Notifications.postId], @@ -72,6 +75,7 @@ fun ResultRow.toNotifications() = Notification( object Notifications : Table("notifications") { val id = long("id") + val type = varchar("type", 100) val userId = long("user_id").references(Actors.id) val sourceActorId = long("source_actor_id").references(Actors.id).nullable() val postId = long("post_id").references(Posts.id).nullable() diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt index 2026778c..395c143d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt @@ -4,6 +4,7 @@ import java.time.Instant data class Notification( val id: Long, + val type: String, val userId: Long, val sourceActorId: Long?, val postId: Long?, 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 df35e349..b76f5f0a 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 @@ -13,6 +13,7 @@ interface ReactionRepository { suspend fun deleteByActorId(actorId: Long): Int suspend fun deleteByPostIdAndActorId(postId: Long, actorId: Long) suspend fun deleteByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji) + suspend fun findById(id: Long): Reaction? 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 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 50887de2..04cc9031 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 @@ -101,6 +101,10 @@ class ReactionRepositoryImpl( } } + override suspend fun findById(id: Long): Reaction? = query { + return@query Reactions.select { Reactions.id eq id }.singleOrNull()?.toReaction() + } + override suspend fun findByPostId(postId: Long): List = query { return@query Reactions.leftJoin(CustomEmojis).select { Reactions.postId eq postId }.map { it.toReaction() } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt index 3080c32e..d089b0b3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt @@ -1,6 +1,11 @@ package dev.usbharu.hideout.core.service.notification -sealed class NotificationRequest(open val userId: Long, open val sourceActorId: Long) +import dev.usbharu.hideout.core.domain.model.notification.Notification +import java.time.Instant + +sealed class NotificationRequest(open val userId: Long, open val sourceActorId: Long?, val type: String) { + abstract fun buildNotification(id: Long, createdAt: Instant): Notification +} interface PostId { val postId: Long @@ -9,28 +14,94 @@ interface PostId { data class MentionNotificationRequest( override val userId: Long, override val sourceActorId: Long, override val postId: Long ) : NotificationRequest( - userId, sourceActorId -), PostId + userId, sourceActorId, + "mention" +), PostId { + override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( + id, + type, + userId, + sourceActorId, + postId, + null, + null, + createdAt + ) +} data class PostNotificationRequest( override val userId: Long, override val sourceActorId: Long, override val postId: Long -) : NotificationRequest(userId, sourceActorId), PostId +) : NotificationRequest(userId, sourceActorId, "post"), PostId { + override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( + id, + type, + userId, + sourceActorId, + postId, + null, + null, + createdAt + ) +} data class RepostNotificationRequest( override val userId: Long, override val sourceActorId: Long, override val postId: Long -) : NotificationRequest(userId, sourceActorId), PostId +) : NotificationRequest(userId, sourceActorId, "repost"), PostId { + override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( + id, + type, + userId, + sourceActorId, + postId, + null, + null, + createdAt + ) +} data class FollowNotificationRequest( - override val userId: Long, override val sourceActorId: Long, override val postId: Long - -) : NotificationRequest(userId, sourceActorId), PostId + override val userId: Long, override val sourceActorId: Long +) : NotificationRequest(userId, sourceActorId, "follow") { + override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( + id, + type, + userId, + sourceActorId, + null, + null, + null, + createdAt + ) +} data class FollowRequestNotificationRequest( - override val userId: Long, override val sourceActorId: Long, override val postId: Long -) : NotificationRequest(userId, sourceActorId), PostId + override val userId: Long, override val sourceActorId: Long +) : NotificationRequest(userId, sourceActorId, "follow-request") { + override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( + id, + type, + userId, + sourceActorId, + null, + null, + null, + createdAt + ) +} data class ReactionNotificationRequest( override val userId: Long, override val sourceActorId: Long, override val postId: Long, val reactionId: Long -) : NotificationRequest(userId, sourceActorId), PostId +) : NotificationRequest(userId, sourceActorId, "reaction"), PostId { + override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( + id, + type, + userId, + sourceActorId, + postId, + null, + reactionId, + createdAt + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt index 9c453e49..aa5e3a9e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt @@ -3,6 +3,6 @@ package dev.usbharu.hideout.core.service.notification import dev.usbharu.hideout.core.domain.model.notification.Notification interface NotificationService { - suspend fun publishNotify(notificationRequest: NotificationRequest): Notification + suspend fun publishNotify(notificationRequest: NotificationRequest): Notification? suspend fun unpublishNotify(notificationId: Long) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt new file mode 100644 index 00000000..7e37e7d7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt @@ -0,0 +1,70 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.notification.Notification +import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository +import dev.usbharu.hideout.core.domain.model.post.PostRepository +import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import org.springframework.stereotype.Service +import java.time.Instant + +@Service +class NotificationServiceImpl( + private val relationshipNotificationManagementService: RelationshipNotificationManagementService, + private val relationshipRepository: RelationshipRepository, + private val notificationStoreList: List, + private val notificationRepository: NotificationRepository, + private val actorRepository: ActorRepository, + private val postRepository: PostRepository, + private val reactionRepository: ReactionRepository +) : NotificationService { + @Suppress("ReplaceNotNullAssertionWithElvisReturn") + override suspend fun publishNotify(notificationRequest: NotificationRequest): Notification? { + + // とりあえず個人間のRelationshipに基づいてきめる。今後増やす + if (!relationship(notificationRequest)) { + return null + } + + val id = notificationRepository.generateId() + val createdAt = Instant.now() + + val notification = notificationRequest.buildNotification(id, createdAt) + + val savedNotification = notificationRepository.save(notification) + + // saveで参照整合性違反が発生するはずなので + val user = actorRepository.findById(savedNotification.userId)!! + val sourceActor = savedNotification.sourceActorId?.let { actorRepository.findById(it) } + + val post = savedNotification.postId?.let { postRepository.findById(it) } + val reaction = savedNotification.reactionId?.let { reactionRepository.findById(it) } + + for (it in notificationStoreList) { + it.publishNotification(savedNotification, user, sourceActor, post, reaction) + } + + return savedNotification + } + + override suspend fun unpublishNotify(notificationId: Long) { + notificationRepository.deleteById(notificationId) + for (notificationStore in notificationStoreList) { + notificationStore.unpulishNotification(notificationId) + } + } + + /** + * 個人間のRelationshipに基づいて通知を送信するか判断します + * + * @param notificationRequest + * @return trueの場合送信する + */ + private suspend fun relationship(notificationRequest: NotificationRequest): Boolean { + val targetActorId = notificationRequest.sourceActorId ?: return true + val relationship = + relationshipRepository.findByUserIdAndTargetUserId(notificationRequest.userId, targetActorId) ?: return true + return relationshipNotificationManagementService.sendNotification(relationship, notificationRequest) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt new file mode 100644 index 00000000..6528e5ba --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.core.domain.model.notification.Notification +import dev.usbharu.hideout.core.domain.model.post.Post +import dev.usbharu.hideout.core.domain.model.reaction.Reaction + +interface NotificationStore { + suspend fun publishNotification( + notification: Notification, + user: Actor, + sourceActor: Actor?, + post: Post?, + reaction: Reaction? + ): Boolean + + suspend fun unpulishNotification(notificationId: Long): Boolean +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementService.kt similarity index 81% rename from src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt rename to src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementService.kt index 3281087d..facd0a60 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationManagimentService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementService.kt @@ -2,6 +2,6 @@ package dev.usbharu.hideout.core.service.notification import dev.usbharu.hideout.core.domain.model.relationship.Relationship -interface NotificationManagimentService { +interface RelationshipNotificationManagementService { fun sendNotification(relationship: Relationship, notificationRequest: NotificationRequest): Boolean } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt new file mode 100644 index 00000000..ca1600e0 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import org.springframework.stereotype.Service + +@Service +class RelationshipNotificationManagementServiceImpl : RelationshipNotificationManagementService { + override fun sendNotification(relationship: Relationship, notificationRequest: NotificationRequest): Boolean = + relationship.muting.not() +} From 94943c009e2a9c75a48133d0bf4b468ee30e71a1 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 11:27:04 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20=E5=BF=98=E3=82=8C=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=83=97=E3=83=A9=E3=82=A4=E3=83=9E=E3=83=AA?= =?UTF-8?q?=E3=82=AD=E3=83=BC=E3=81=AE=E6=8C=87=E5=AE=9A=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 --- .../domain/model/notification/ExposedNotificationRepository.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt index ab3c23b6..9c82f69e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt @@ -82,4 +82,6 @@ object Notifications : Table("notifications") { val text = varchar("text", 3000).nullable() val reactionId = long("reaction_id").references(Reactions.id).nullable() val createdAt = timestamp("created_at") + + override val primaryKey: PrimaryKey = PrimaryKey(id) } From 95064e012341a8894478f1b0a07c38a507de5531 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 11:27:45 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20notifications=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=96=E3=83=AB=E5=AE=9A=E7=BE=A9=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 --- .../resources/db/migration/V1__Init_DB.sql | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 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 fe745aed..286568f6 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -90,7 +90,7 @@ create table if not exists posts id bigint primary key, actor_id bigint not null, overview varchar(100) null, - content varchar(5000) not null, + content varchar(5000) not null, text varchar(3000) not null, created_at bigint not null, visibility int default 0 not null, @@ -253,4 +253,20 @@ create table if not exists deleted_actors public_key varchar(10000) not null, deleted_at timestamp not null, unique ("name", domain) +); + +create table if not exists notifications +( + id bigint primary key, + type varchar(100) not null, + user_id bigint not null, + source_actor_id bigint null, + post_id bigint null, + text varchar(3000) null, + reaction_id bigint null, + created_at timestamp not null, + constraint fk_notifications_user_id__id foreign key (user_id) references actors (id) on delete cascade on update cascade, + constraint fk_notifications_source_actor__id foreign key (source_actor_id) references actors (id) on delete cascade on update cascade, + constraint fk_notifications_post_id__id foreign key (post_id) references posts (id) on delete cascade on update cascade, + constraint fk_notifications_reaction_id__id foreign key (reaction_id) references reactions (id) on delete cascade on update cascade ) From 0e39b226bc64780680abce7fefc33b0edd5a0199 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:47:50 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E3=82=92?= =?UTF-8?q?=E9=80=81=E4=BF=A1=E3=81=99=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/reaction/ReactionServiceImpl.kt | 22 +++++++++++++++++-- .../relationship/RelationshipServiceImpl.kt | 9 +++++++- 2 files changed, 28 insertions(+), 3 deletions(-) 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 927dd9fb..48191c55 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 @@ -3,8 +3,11 @@ 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.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository +import dev.usbharu.hideout.core.service.notification.NotificationService +import dev.usbharu.hideout.core.service.notification.ReactionNotificationRequest import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -12,7 +15,9 @@ import org.springframework.stereotype.Service @Service class ReactionServiceImpl( private val reactionRepository: ReactionRepository, - private val apReactionService: APReactionService + private val apReactionService: APReactionService, + private val notificationService: NotificationService, + private val postRepository: PostRepository ) : ReactionService { override suspend fun receiveReaction( emoji: Emoji, @@ -23,7 +28,16 @@ class ReactionServiceImpl( reactionRepository.deleteByPostIdAndActorId(postId, actorId) } try { - reactionRepository.save(Reaction(reactionRepository.generateId(), emoji, postId, actorId)) + val reaction = reactionRepository.save(Reaction(reactionRepository.generateId(), emoji, postId, actorId)) + + notificationService.publishNotify( + ReactionNotificationRequest( + postRepository.findById(postId)!!.actorId, + actorId, + postId, + reaction.id + ) + ) } catch (_: DuplicateException) { } } @@ -49,6 +63,10 @@ class ReactionServiceImpl( val reaction = Reaction(reactionRepository.generateId(), emoji, postId, actorId) reactionRepository.save(reaction) apReactionService.reaction(reaction) + + val id = postRepository.findById(postId)!!.actorId + + notificationService.publishNotify(ReactionNotificationRequest(id, actorId, postId, reaction.id)) } override suspend fun removeReaction(actorId: Long, postId: Long) { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt index cd83150e..4de70434 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt @@ -12,6 +12,9 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.Relationship import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.service.follow.SendFollowDto +import dev.usbharu.hideout.core.service.notification.FollowNotificationRequest +import dev.usbharu.hideout.core.service.notification.FollowRequestNotificationRequest +import dev.usbharu.hideout.core.service.notification.NotificationService import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -24,7 +27,8 @@ class RelationshipServiceImpl( private val apSendAcceptService: ApSendAcceptService, private val apSendRejectService: ApSendRejectService, private val apSendUndoService: APSendUndoService, - private val actorRepository: ActorRepository + private val actorRepository: ActorRepository, + private val notificationService: NotificationService ) : RelationshipService { override suspend fun followRequest(actorId: Long, targetId: Long) { logger.info("START Follow Request userId: {} targetId: {}", actorId, targetId) @@ -82,6 +86,8 @@ class RelationshipServiceImpl( val target = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) if (target.locked.not()) { acceptFollowRequest(targetId, actorId) + } else { + notificationService.publishNotify(FollowRequestNotificationRequest(targetId, actorId)) } } @@ -185,6 +191,7 @@ class RelationshipServiceImpl( if (isRemoteActor(remoteActor)) { apSendAcceptService.sendAcceptFollow(user, remoteActor) } + notificationService.publishNotify(FollowNotificationRequest(actorId, targetId)) } override suspend fun rejectFollowRequest(actorId: Long, targetId: Long) { From 5948ce10a1c59c0cec4655c948389575ee8f17b1 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 13:12:05 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20NotificationStore=E3=81=AE?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F=E3=83=B3=E3=83=89=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=81=A8=E3=83=AD=E3=82=B0=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 --- .../notification/NotificationServiceImpl.kt | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt index 7e37e7d7..f208587e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt @@ -1,11 +1,13 @@ package dev.usbharu.hideout.core.service.notification +import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.notification.Notification import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.time.Instant @@ -17,13 +19,22 @@ class NotificationServiceImpl( private val notificationRepository: NotificationRepository, private val actorRepository: ActorRepository, private val postRepository: PostRepository, - private val reactionRepository: ReactionRepository + private val reactionRepository: ReactionRepository, + private val applicationConfig: ApplicationConfig ) : NotificationService { - @Suppress("ReplaceNotNullAssertionWithElvisReturn") override suspend fun publishNotify(notificationRequest: NotificationRequest): Notification? { + logger.debug("NOTIFICATION REQUEST user: {} type: {}", notificationRequest.userId, notificationRequest.type) + logger.trace("NotificationRequest: {}", notificationRequest) + + val user = actorRepository.findById(notificationRequest.userId) + if (user == null || user.domain != applicationConfig.url.host) { + logger.debug("NOTIFICATION REQUEST is rejected. (Remote Actor or user not found.)") + return null + } // とりあえず個人間のRelationshipに基づいてきめる。今後増やす if (!relationship(notificationRequest)) { + logger.debug("NOTIFICATION REQUEST is rejected. (relationship)") return null } @@ -34,16 +45,27 @@ class NotificationServiceImpl( val savedNotification = notificationRepository.save(notification) - // saveで参照整合性違反が発生するはずなので - val user = actorRepository.findById(savedNotification.userId)!! val sourceActor = savedNotification.sourceActorId?.let { actorRepository.findById(it) } val post = savedNotification.postId?.let { postRepository.findById(it) } val reaction = savedNotification.reactionId?.let { reactionRepository.findById(it) } + logger.info( + "NOTIFICATION id: {} user: {} type: {}", + savedNotification.id, + savedNotification.userId, + savedNotification.type + ) + + logger.debug("push to {} notification store.", notificationStoreList.size) for (it in notificationStoreList) { - it.publishNotification(savedNotification, user, sourceActor, post, reaction) + try { + it.publishNotification(savedNotification, user, sourceActor, post, reaction) + } catch (e: Exception) { + logger.warn("FAILED Publish to notification.", e) + } } + logger.debug("SUCCESS Notification id: {}", savedNotification.id) return savedNotification } @@ -67,4 +89,8 @@ class NotificationServiceImpl( relationshipRepository.findByUserIdAndTargetUserId(notificationRequest.userId, targetActorId) ?: return true return relationshipNotificationManagementService.sendNotification(relationship, notificationRequest) } + + companion object { + private val logger = LoggerFactory.getLogger(NotificationServiceImpl::class.java) + } } From f1ad420a9d24d41ea9913add9c61776421b31d93 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:31:06 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20Mastodon=E3=81=AE=E9=80=9A?= =?UTF-8?q?=E7=9F=A5API=E3=81=AENotificationStore=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 --- .../domain/model/MastodonNotification.kt | 17 ++ .../model/MastodonNotificationRepository.kt | 7 + .../mastodon/domain/model/NotificationType.kt | 15 ++ .../ExposedMastodonNotificationRepository.kt | 86 ++++++++++ .../MongoMastodonNotificationRepository.kt | 8 + ...goMastodonNotificationRepositoryWrapper.kt | 24 +++ .../notification/MastodonNotificationStore.kt | 65 +++++++ src/main/resources/openapi/mastodon.yaml | 159 ++++++++++++++++++ 8 files changed, 381 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt new file mode 100644 index 00000000..fd449a87 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt @@ -0,0 +1,17 @@ +package dev.usbharu.hideout.mastodon.domain.model + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document +import java.time.Instant + +@Document +data class MastodonNotification( + @Id + val id: Long, + val type: NotificationType, + val createdAt: Instant, + val accountId: Long, + val statusId: Long?, + val reportId: Long?, + val relationshipServeranceEvent: Long? +) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt new file mode 100644 index 00000000..7c43c7a4 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.mastodon.domain.model + +interface MastodonNotificationRepository { + suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification + suspend fun deleteById(id: Long) + suspend fun findById(id: Long): MastodonNotification? +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt new file mode 100644 index 00000000..7c78de58 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt @@ -0,0 +1,15 @@ +package dev.usbharu.hideout.mastodon.domain.model + +enum class NotificationType { + mention, + status, + reblog, + follow, + follow_request, + favourite, + poll, + update, + admin_sign_up, + admin_report, + severed_relationships; +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt new file mode 100644 index 00000000..1bfc17dc --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt @@ -0,0 +1,86 @@ +package dev.usbharu.hideout.mastodon.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository +import dev.usbharu.hideout.mastodon.domain.model.NotificationType +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.javatime.timestamp +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Repository + +@Repository +@Qualifier("jdbc") +@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true) +class ExposedMastodonNotificationRepository : MastodonNotificationRepository, AbstractRepository() { + override val logger: Logger + get() = Companion.logger + + override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = query { + val singleOrNull = + MastodonNotifications.select { MastodonNotifications.id eq mastodonNotification.id }.singleOrNull() + if (singleOrNull == null) { + MastodonNotifications.insert { + it[MastodonNotifications.id] = mastodonNotification.id + it[MastodonNotifications.type] = mastodonNotification.type.name + it[MastodonNotifications.createdAt] = mastodonNotification.createdAt + it[MastodonNotifications.accountId] = mastodonNotification.accountId + it[MastodonNotifications.statusId] = mastodonNotification.statusId + it[MastodonNotifications.reportId] = mastodonNotification.reportId + it[MastodonNotifications.relationshipServeranceEventId] = + mastodonNotification.relationshipServeranceEvent + } + } else { + MastodonNotifications.update({ MastodonNotifications.id eq mastodonNotification.id }) { + it[MastodonNotifications.type] = mastodonNotification.type.name + it[MastodonNotifications.createdAt] = mastodonNotification.createdAt + it[MastodonNotifications.accountId] = mastodonNotification.accountId + it[MastodonNotifications.statusId] = mastodonNotification.statusId + it[MastodonNotifications.reportId] = mastodonNotification.reportId + it[MastodonNotifications.relationshipServeranceEventId] = + mastodonNotification.relationshipServeranceEvent + } + } + mastodonNotification + } + + override suspend fun deleteById(id: Long): Unit = query { + MastodonNotifications.deleteWhere { + MastodonNotifications.id eq id + } + } + + override suspend fun findById(id: Long): MastodonNotification? = query { + MastodonNotifications.select { MastodonNotifications.id eq id }.singleOrNull()?.toMastodonNotification() + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedMastodonNotificationRepository::class.java) + } +} + +fun ResultRow.toMastodonNotification(): MastodonNotification { + return MastodonNotification( + this[MastodonNotifications.id], + NotificationType.valueOf(this[MastodonNotifications.type]), + this[MastodonNotifications.createdAt], + this[MastodonNotifications.accountId], + this[MastodonNotifications.statusId], + this[MastodonNotifications.reportId], + this[MastodonNotifications.relationshipServeranceEventId], + ) +} + +object MastodonNotifications : Table("mastodon_notifications") { + val id = long("id") + val type = varchar("type", 100) + val createdAt = timestamp("created_at") + val accountId = long("account_id") + val statusId = long("status_id").nullable() + val reportId = long("report_id").nullable() + val relationshipServeranceEventId = long("relationship_serverance_event_id").nullable() +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt new file mode 100644 index 00000000..1cde3f26 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.mastodon.infrastructure.mongorepository + +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification +import org.springframework.data.mongodb.repository.MongoRepository + +interface MongoMastodonNotificationRepository : MongoRepository { + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt new file mode 100644 index 00000000..84c7bcfd --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt @@ -0,0 +1,24 @@ +package dev.usbharu.hideout.mastodon.infrastructure.mongorepository + +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Repository +import kotlin.jvm.optionals.getOrNull + +@Repository +@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) +class MongoMastodonNotificationRepositoryWrapper(private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository) : + MastodonNotificationRepository { + override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification { + return mongoMastodonNotificationRepository.save(mastodonNotification) + } + + override suspend fun deleteById(id: Long) { + mongoMastodonNotificationRepository.deleteById(id) + } + + override suspend fun findById(id: Long): MastodonNotification? { + return mongoMastodonNotificationRepository.findById(id).getOrNull() + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt new file mode 100644 index 00000000..604daa51 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt @@ -0,0 +1,65 @@ +package dev.usbharu.hideout.mastodon.service.notification + +import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.core.domain.model.notification.Notification +import dev.usbharu.hideout.core.domain.model.post.Post +import dev.usbharu.hideout.core.domain.model.reaction.Reaction +import dev.usbharu.hideout.core.service.notification.NotificationStore +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository +import dev.usbharu.hideout.mastodon.domain.model.NotificationType +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class MastodonNotificationStore(private val mastodonNotificationRepository: MastodonNotificationRepository) : + NotificationStore { + override suspend fun publishNotification( + notification: Notification, + user: Actor, + sourceActor: Actor?, + post: Post?, + reaction: Reaction? + ): Boolean { + val notificationType = when (notification.type) { + "mention" -> NotificationType.mention + "post" -> NotificationType.status + "repost" -> NotificationType.reblog + "follow" -> NotificationType.follow + "follow-request" -> NotificationType.follow_request + "reaction" -> NotificationType.favourite + else -> { + logger.debug("Notification type does not support. type: {}", notification.type) + return false + } + } + + if (notification.sourceActorId == null) { + logger.debug("Notification does not support. notification.sourceActorId is null") + return false + } + + val mastodonNotification = MastodonNotification( + id = notification.id, + type = notificationType, + createdAt = notification.createdAt, + accountId = notification.sourceActorId, + statusId = notification.postId, + reportId = null, + relationshipServeranceEvent = null + ) + + mastodonNotificationRepository.save(mastodonNotification) + + return true + } + + override suspend fun unpulishNotification(notificationId: Long): Boolean { + mastodonNotificationRepository.deleteById(notificationId) + return true + } + + companion object { + private val logger = LoggerFactory.getLogger(MastodonNotificationStore::class.java) + } +} diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 829da25c..c628586e 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -19,6 +19,8 @@ tags: description: timeline - name: media description: media + - name: notification + description: notification paths: /api/v2/instance: @@ -803,6 +805,122 @@ paths: schema: $ref: "#/components/schemas/Relationship" + /api/v1/notifications: + get: + tags: + - notifications + security: + - OAuth2: + - "read:notifications" + parameters: + - in: query + name: max_id + required: false + schema: + type: string + - in: query + name: since_id + required: false + schema: + type: string + - in: query + name: min_id + required: false + schema: + type: string + - in: query + name: limit + required: false + schema: + type: integer + - in: query + name: types[] + required: false + schema: + type: array + items: + type: string + - in: query + name: exclude_types[] + required: false + schema: + type: array + items: + type: string + - in: query + name: account_id + required: false + schema: + type: array + items: + type: string + responses: + 200: + description: 成功 + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Notification" + + /api/v1/notifications/{id}: + get: + tags: + - notifications + security: + - OAuth2: + - "read:notifications" + parameters: + - in: path + required: true + name: id + schema: + type: string + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Notification" + + /api/v1/notifications/clear: + post: + tags: + - notifications + security: + - OAuth2: + - "write:notifications" + responses: + 200: + description: 成功 + content: + application/json: + schema: + type: object + + /api/v1/notifications/{id}/dismiss: + post: + tags: + - notifications + security: + - OAuth2: + - "write:notifications" + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + 200: + description: 成功 + content: + application/json: + schema: + type: object + components: schemas: V1MediaRequest: @@ -1935,6 +2053,47 @@ components: value: type: string + Report: + type: object + + RelationshipServeranceEvent: + type: object + + Notification: + type: object + properties: + id: + type: string + type: + type: string + enum: + - mention + - status + - reblog + - follow + - follow_request + - favourite + - poll + - update + - admin.sign_up + - admin.report + - severed_relationships + created_at: + type: string + account: + $ref: "#/components/schemas/Account" + status: + $ref: "#/components/schemas/Status" + report: + $ref: "#/components/schemas/Report" + relationship_severance_event: + $ref: "#/components/schemas/RelationshipServeranceEvent" + required: + - id + - type + - created_at + - account + securitySchemes: OAuth2: type: oauth2 From 9e20fec4ddb4be2318cae55263066fe5ae1d4f4a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:52:52 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E3=81=AEMastod?= =?UTF-8?q?on=E4=BA=92=E6=8F=9BAPI=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/MastodonNotification.kt | 1 + .../model/MastodonNotificationRepository.kt | 12 ++ .../mastodon/domain/model/NotificationType.kt | 18 +++ .../ExposedMastodonNotificationRepository.kt | 45 +++++++ .../MongoMastodonNotificationRepository.kt | 5 + ...goMastodonNotificationRepositoryWrapper.kt | 49 +++++++- .../MastodonNotificationApiController.kt | 56 +++++++++ .../notification/MastodonNotificationStore.kt | 1 + .../notification/NotificationApiService.kt | 23 ++++ .../NotificationApiServiceImpl.kt | 111 ++++++++++++++++++ 10 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt index fd449a87..baba08e8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt @@ -8,6 +8,7 @@ import java.time.Instant data class MastodonNotification( @Id val id: Long, + val userId: Long, val type: NotificationType, val createdAt: Instant, val accountId: Long, diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt index 7c43c7a4..42d769da 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt @@ -4,4 +4,16 @@ interface MastodonNotificationRepository { suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification suspend fun deleteById(id: Long) suspend fun findById(id: Long): MastodonNotification? + suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId( + loginUser: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int, + typesTmp: MutableList, + accountId: List + ): List + + suspend fun deleteByUserId(userId: Long) + suspend fun deleteByUserIdAndId(userId: Long, id: Long) } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt index 7c78de58..fd7caf90 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt @@ -12,4 +12,22 @@ enum class NotificationType { admin_sign_up, admin_report, severed_relationships; + + companion object { + fun parse(string: String): NotificationType? = when (string) { + + "mention" -> mention + "status" -> status + "reblog" -> reblog + "follow" -> follow + "follow_request" -> follow_request + "favourite" -> favourite + "poll" -> poll + "update" -> update + "admin.aign_up" -> admin_sign_up + "admin.report" -> admin_report + "servered_relationships" -> severed_relationships + else -> null + } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt index 1bfc17dc..e42bbc9f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.mastodon.infrastructure.exposedrepository import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Timelines import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository import dev.usbharu.hideout.mastodon.domain.model.NotificationType @@ -58,6 +59,48 @@ class ExposedMastodonNotificationRepository : MastodonNotificationRepository, Ab MastodonNotifications.select { MastodonNotifications.id eq id }.singleOrNull()?.toMastodonNotification() } + override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId( + loginUser: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int, + typesTmp: MutableList, + accountId: List + ): List = query { + val query = MastodonNotifications.select { + MastodonNotifications.userId eq loginUser + } + + + if (maxId != null) { + query.andWhere { MastodonNotifications.id lessEq maxId } + } + if (minId != null) { + query.andWhere { MastodonNotifications.id greaterEq minId } + } + if (sinceId != null) { + query.andWhere { MastodonNotifications.id greaterEq sinceId } + } + val result = query + .limit(limit) + .orderBy(Timelines.createdAt, SortOrder.DESC) + + return@query result.map { it.toMastodonNotification() } + } + + override suspend fun deleteByUserId(userId: Long) { + MastodonNotifications.deleteWhere { + MastodonNotifications.userId eq userId + } + } + + override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { + MastodonNotifications.deleteWhere { + MastodonNotifications.userId eq userId and (MastodonNotifications.id eq id) + } + } + companion object { private val logger = LoggerFactory.getLogger(ExposedMastodonNotificationRepository::class.java) } @@ -66,6 +109,7 @@ class ExposedMastodonNotificationRepository : MastodonNotificationRepository, Ab fun ResultRow.toMastodonNotification(): MastodonNotification { return MastodonNotification( this[MastodonNotifications.id], + this[MastodonNotifications.userId], NotificationType.valueOf(this[MastodonNotifications.type]), this[MastodonNotifications.createdAt], this[MastodonNotifications.accountId], @@ -77,6 +121,7 @@ fun ResultRow.toMastodonNotification(): MastodonNotification { object MastodonNotifications : Table("mastodon_notifications") { val id = long("id") + val userId = long("user_id") val type = varchar("type", 100) val createdAt = timestamp("created_at") val accountId = long("account_id") diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt index 1cde3f26..141a37b2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt @@ -5,4 +5,9 @@ import org.springframework.data.mongodb.repository.MongoRepository interface MongoMastodonNotificationRepository : MongoRepository { + + fun deleteByUserId(userId: Long): Long + + + fun deleteByIdAndUserId(id: Long, userId: Long): Long } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt index 84c7bcfd..1016279f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt @@ -2,13 +2,21 @@ package dev.usbharu.hideout.mastodon.infrastructure.mongorepository import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository +import dev.usbharu.hideout.mastodon.domain.model.NotificationType import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.data.domain.Sort +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query import org.springframework.stereotype.Repository import kotlin.jvm.optionals.getOrNull @Repository @ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) -class MongoMastodonNotificationRepositoryWrapper(private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository) : +class MongoMastodonNotificationRepositoryWrapper( + private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository, + private val mongoTemplate: MongoTemplate +) : MastodonNotificationRepository { override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification { return mongoMastodonNotificationRepository.save(mastodonNotification) @@ -21,4 +29,43 @@ class MongoMastodonNotificationRepositoryWrapper(private val mongoMastodonNotifi override suspend fun findById(id: Long): MastodonNotification? { return mongoMastodonNotificationRepository.findById(id).getOrNull() } + + override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId( + loginUser: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int, + typesTmp: MutableList, + accountId: List + ): List { + val query = Query() + + if (maxId != null) { + val criteria = Criteria.where("id").lte(maxId) + query.addCriteria(criteria) + } + + if (minId != null) { + val criteria = Criteria.where("id").gte(minId) + query.addCriteria(criteria) + } + if (sinceId != null) { + val criteria = Criteria.where("id").gte(sinceId) + query.addCriteria(criteria) + } + + query.limit(limit) + query.with(Sort.by(Sort.Direction.DESC, "createdAt")) + + return mongoTemplate.find(query, MastodonNotification::class.java) + } + + override suspend fun deleteByUserId(userId: Long) { + mongoMastodonNotificationRepository.deleteByUserId(userId) + } + + override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { + mongoMastodonNotificationRepository.deleteByIdAndUserId(id, userId) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt new file mode 100644 index 00000000..c8ea7e78 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt @@ -0,0 +1,56 @@ +package dev.usbharu.hideout.mastodon.interfaces.api.notification + +import dev.usbharu.hideout.controller.mastodon.generated.NotificationsApi +import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder +import dev.usbharu.hideout.domain.mastodon.model.generated.Notification +import dev.usbharu.hideout.mastodon.domain.model.NotificationType +import dev.usbharu.hideout.mastodon.service.notification.NotificationApiService +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.runBlocking +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller + +@Controller +class MastodonNotificationApiController( + private val loginUserContextHolder: LoginUserContextHolder, + private val notificationApiService: NotificationApiService +) : NotificationsApi { + override suspend fun apiV1NotificationsClearPost(): ResponseEntity { + notificationApiService.clearAll(loginUserContextHolder.getLoginUserId()) + return ResponseEntity.ok(null) + } + + override fun apiV1NotificationsGet( + maxId: String?, + sinceId: String?, + minId: String?, + limit: Int?, + types: List?, + excludeTypes: List?, + accountId: List? + ): ResponseEntity> = runBlocking { + val notificationFlow = notificationApiService.notifications( + loginUserContextHolder.getLoginUserId(), + maxId?.toLong(), + minId?.toLong(), + sinceId?.toLong(), + limit ?: 20, + types.orEmpty().mapNotNull { NotificationType.parse(it) }, + excludeTypes = excludeTypes.orEmpty().mapNotNull { NotificationType.parse(it) }, + accountId = accountId.orEmpty().mapNotNull { it.toLongOrNull() } + ).asFlow() + ResponseEntity.ok(notificationFlow) + } + + override suspend fun apiV1NotificationsIdDismissPost(id: String): ResponseEntity { + notificationApiService.dismiss(loginUserContextHolder.getLoginUserId(), id.toLong()) + return ResponseEntity.ok(null) + } + + override suspend fun apiV1NotificationsIdGet(id: String): ResponseEntity { + val notification = notificationApiService.fingById(loginUserContextHolder.getLoginUserId(), id.toLong()) + + return ResponseEntity.ok(notification) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt index 604daa51..80e66e14 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt @@ -41,6 +41,7 @@ class MastodonNotificationStore(private val mastodonNotificationRepository: Mast val mastodonNotification = MastodonNotification( id = notification.id, + notification.userId, type = notificationType, createdAt = notification.createdAt, accountId = notification.sourceActorId, diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt new file mode 100644 index 00000000..152b6bab --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt @@ -0,0 +1,23 @@ +package dev.usbharu.hideout.mastodon.service.notification + +import dev.usbharu.hideout.domain.mastodon.model.generated.Notification +import dev.usbharu.hideout.mastodon.domain.model.NotificationType + +interface NotificationApiService { + suspend fun notifications( + loginUser: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int, + types: List, + excludeTypes: List, + accountId: List + ): List + + suspend fun fingById(loginUser: Long, notificationId: Long): Notification? + + suspend fun clearAll(loginUser: Long) + + suspend fun dismiss(loginUser: Long, notificationId: Long) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt new file mode 100644 index 00000000..709b306e --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt @@ -0,0 +1,111 @@ +package dev.usbharu.hideout.mastodon.service.notification + +import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.domain.mastodon.model.generated.Notification +import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository +import dev.usbharu.hideout.mastodon.domain.model.NotificationType +import dev.usbharu.hideout.mastodon.domain.model.NotificationType.* +import dev.usbharu.hideout.mastodon.query.StatusQueryService +import dev.usbharu.hideout.mastodon.service.account.AccountService +import org.springframework.stereotype.Service + +@Service +class NotificationApiServiceImpl( + private val mastodonNotificationRepository: MastodonNotificationRepository, + private val transaction: Transaction, + private val accountService: AccountService, + private val statusQueryService: StatusQueryService +) : + NotificationApiService { + override suspend fun notifications( + loginUser: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int, + types: List, + excludeTypes: List, + accountId: List + ): List = transaction.transaction { + + val typesTmp = mutableListOf() + + typesTmp.addAll(types) + typesTmp.removeAll(excludeTypes) + + val mastodonNotifications = + mastodonNotificationRepository.findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId( + loginUser, + maxId, + minId, + sinceId, + limit, + typesTmp, + accountId + ) + + val accounts = accountService.findByIds(mastodonNotifications.map { + it.accountId + }).associateBy { it.id.toLong() } + + val statuses = statusQueryService.findByPostIds(mastodonNotifications.mapNotNull { it.statusId }) + .associateBy { it.id.toLong() } + + mastodonNotifications.map { + Notification( + id = it.id.toString(), + type = convertNotificationType(it.type), + createdAt = it.createdAt.toString(), + account = accounts.getValue(it.accountId), + status = statuses[it.statusId], + report = null, + relationshipSeveranceEvent = null + ) + } + } + + override suspend fun fingById(loginUser: Long, notificationId: Long): Notification? { + val findById = mastodonNotificationRepository.findById(notificationId) ?: return null + + if (findById.userId != loginUser) { + return null + } + + val account = accountService.findById(findById.accountId) + val status = findById.statusId?.let { statusQueryService.findByPostId(it) } + + return Notification( + id = findById.id.toString(), + type = convertNotificationType(findById.type), + createdAt = findById.createdAt.toString(), + account = account, + status = status, + report = null, + relationshipSeveranceEvent = null + ) + } + + override suspend fun clearAll(loginUser: Long) { + mastodonNotificationRepository.deleteByUserId(loginUser) + } + + override suspend fun dismiss(loginUser: Long, notificationId: Long) { + mastodonNotificationRepository.deleteByUserIdAndId(loginUser, notificationId) + } + + + private fun convertNotificationType(notificationType: NotificationType): Notification.Type = + when (notificationType) { + mention -> Notification.Type.mention + status -> Notification.Type.status + reblog -> Notification.Type.reblog + follow -> Notification.Type.follow + follow_request -> Notification.Type.follow + favourite -> Notification.Type.followRequest + poll -> Notification.Type.poll + update -> Notification.Type.update + admin_sign_up -> Notification.Type.adminPeriodSignUp + admin_report -> Notification.Type.adminPeriodReport + severed_relationships -> Notification.Type.severedRelationships + } +} From 0d2b9e1b64e0ba30007db60f7901d2d654e9a0ed Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:17:48 +0900 Subject: [PATCH 11/18] =?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 --- .../usbharu/hideout/EqualsAndToStringTest.kt | 3 +++ .../reaction/ReactionServiceImplTest.kt | 18 +++++++++++++++++- .../RelationshipServiceImplTest.kt | 5 +++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt b/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt index 7d2454c1..1a339cc3 100644 --- a/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt @@ -96,6 +96,9 @@ class EqualsAndToStringTest { .filter { it.superclass == Any::class.java || it.superclass?.packageName?.startsWith("dev.usbharu") ?: true } + .filterNot { + it.superclass.isSealed + } .filterNot { it == UnicodeEmoji::class.java } .map { 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 f3863d64..98558744 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 @@ -4,8 +4,10 @@ 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.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository +import dev.usbharu.hideout.core.service.notification.NotificationService import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -18,6 +20,12 @@ import utils.PostBuilder @ExtendWith(MockitoExtension::class) class ReactionServiceImplTest { + @Mock + private lateinit var notificationService: NotificationService + + @Mock + private lateinit var postRepository: PostRepository + @Mock private lateinit var reactionRepository: ReactionRepository @@ -35,6 +43,9 @@ class ReactionServiceImplTest { whenever(reactionRepository.existByPostIdAndActor(eq(post.id), eq(post.actorId))).doReturn( false ) + whenever(postRepository.findById(eq(post.id))).doReturn(post) + whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } + val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) @@ -50,7 +61,8 @@ class ReactionServiceImplTest { whenever(reactionRepository.existByPostIdAndActor(eq(post.id), eq(post.actorId))).doReturn( true ) - + whenever(postRepository.findById(eq(post.id))).doReturn(post) + whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) @@ -67,6 +79,8 @@ class ReactionServiceImplTest { whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( null ) + whenever(postRepository.findById(eq(post.id))).doReturn(post) + whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) @@ -83,6 +97,8 @@ class ReactionServiceImplTest { whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId) ) + whenever(postRepository.findById(eq(post.id))).doReturn(post) + whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } val generateId = TwitterSnowflakeIdGenerateService.generateId() whenever(reactionRepository.generateId()).doReturn(generateId) diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt index 44e43a17..cd338e15 100644 --- a/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt @@ -10,6 +10,7 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.Relationship import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.service.follow.SendFollowDto +import dev.usbharu.hideout.core.service.notification.NotificationService import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -25,6 +26,10 @@ import java.net.URL @ExtendWith(MockitoExtension::class) class RelationshipServiceImplTest { + + @Mock + private lateinit var notificationService: NotificationService + @Spy private val applicationConfig = ApplicationConfig(URL("https://example.com")) From 4221ef4b2f06bc7cc4c8d6149d8ab99a6d01a3c3 Mon Sep 17 00:00:00 2001 From: usbharu Date: Sun, 28 Jan 2024 11:42:03 +0900 Subject: [PATCH 12/18] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../notification/NotificationRequest.kt | 29 ++++++++++++++----- .../mastodon/domain/model/NotificationType.kt | 1 - .../ExposedMastodonNotificationRepository.kt | 1 - .../MongoMastodonNotificationRepository.kt | 2 -- .../NotificationApiServiceImpl.kt | 10 +++---- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt index d089b0b3..3e2b8b5c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt @@ -12,11 +12,15 @@ interface PostId { } data class MentionNotificationRequest( - override val userId: Long, override val sourceActorId: Long, override val postId: Long + override val userId: Long, + override val sourceActorId: Long, + override val postId: Long ) : NotificationRequest( - userId, sourceActorId, + userId, + sourceActorId, "mention" -), PostId { +), + PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( id, type, @@ -30,7 +34,9 @@ data class MentionNotificationRequest( } data class PostNotificationRequest( - override val userId: Long, override val sourceActorId: Long, override val postId: Long + override val userId: Long, + override val sourceActorId: Long, + override val postId: Long ) : NotificationRequest(userId, sourceActorId, "post"), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( @@ -46,7 +52,9 @@ data class PostNotificationRequest( } data class RepostNotificationRequest( - override val userId: Long, override val sourceActorId: Long, override val postId: Long + override val userId: Long, + override val sourceActorId: Long, + override val postId: Long ) : NotificationRequest(userId, sourceActorId, "repost"), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( id, @@ -61,7 +69,8 @@ data class RepostNotificationRequest( } data class FollowNotificationRequest( - override val userId: Long, override val sourceActorId: Long + override val userId: Long, + override val sourceActorId: Long ) : NotificationRequest(userId, sourceActorId, "follow") { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( id, @@ -76,7 +85,8 @@ data class FollowNotificationRequest( } data class FollowRequestNotificationRequest( - override val userId: Long, override val sourceActorId: Long + override val userId: Long, + override val sourceActorId: Long ) : NotificationRequest(userId, sourceActorId, "follow-request") { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( id, @@ -91,7 +101,10 @@ data class FollowRequestNotificationRequest( } data class ReactionNotificationRequest( - override val userId: Long, override val sourceActorId: Long, override val postId: Long, val reactionId: Long + override val userId: Long, + override val sourceActorId: Long, + override val postId: Long, + val reactionId: Long ) : NotificationRequest(userId, sourceActorId, "reaction"), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt index fd7caf90..8a1be504 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt @@ -15,7 +15,6 @@ enum class NotificationType { companion object { fun parse(string: String): NotificationType? = when (string) { - "mention" -> mention "status" -> status "reblog" -> reblog diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt index e42bbc9f..520d913a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt @@ -72,7 +72,6 @@ class ExposedMastodonNotificationRepository : MastodonNotificationRepository, Ab MastodonNotifications.userId eq loginUser } - if (maxId != null) { query.andWhere { MastodonNotifications.id lessEq maxId } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt index 141a37b2..dcbe2c81 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt @@ -5,9 +5,7 @@ import org.springframework.data.mongodb.repository.MongoRepository interface MongoMastodonNotificationRepository : MongoRepository { - fun deleteByUserId(userId: Long): Long - fun deleteByIdAndUserId(id: Long, userId: Long): Long } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt index 709b306e..01b1bfb1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt @@ -27,7 +27,6 @@ class NotificationApiServiceImpl( excludeTypes: List, accountId: List ): List = transaction.transaction { - val typesTmp = mutableListOf() typesTmp.addAll(types) @@ -44,9 +43,11 @@ class NotificationApiServiceImpl( accountId ) - val accounts = accountService.findByIds(mastodonNotifications.map { - it.accountId - }).associateBy { it.id.toLong() } + val accounts = accountService.findByIds( + mastodonNotifications.map { + it.accountId + } + ).associateBy { it.id.toLong() } val statuses = statusQueryService.findByPostIds(mastodonNotifications.mapNotNull { it.statusId }) .associateBy { it.id.toLong() } @@ -93,7 +94,6 @@ class NotificationApiServiceImpl( mastodonNotificationRepository.deleteByUserIdAndId(loginUser, notificationId) } - private fun convertNotificationType(notificationType: NotificationType): Notification.Type = when (notificationType) { mention -> Notification.Type.mention From bcefc270ae30061a4bd28a6a148ce38070cdaa9a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 28 Jan 2024 12:17:22 +0900 Subject: [PATCH 13/18] =?UTF-8?q?fix:=20admin.sign=5Fup=E3=81=8Cadmin.aign?= =?UTF-8?q?=5Fup=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/mastodon/domain/model/NotificationType.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt index 8a1be504..b4eb3a45 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt @@ -23,7 +23,7 @@ enum class NotificationType { "favourite" -> favourite "poll" -> poll "update" -> update - "admin.aign_up" -> admin_sign_up + "admin.sign_up" -> admin_sign_up "admin.report" -> admin_report "servered_relationships" -> severed_relationships else -> null From ed941a654741fda5a8840dde7d1b46699bb87f5f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 28 Jan 2024 12:17:41 +0900 Subject: [PATCH 14/18] =?UTF-8?q?test:=20NotificationType=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/NotificationTypeTest.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt diff --git a/src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt b/src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt new file mode 100644 index 00000000..87244005 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt @@ -0,0 +1,43 @@ +package dev.usbharu.hideout.mastodon.domain.model + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.provider.ValueSource +import java.util.stream.Stream +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class NotificationTypeTest { + @ParameterizedTest + @MethodSource("parseSuccessProvider") + fun parseに成功する(s: String, notificationType: NotificationType) { + assertEquals(notificationType, NotificationType.parse(s)) + } + + @ParameterizedTest + @ValueSource(strings = ["hoge", "fuga", "0x1234", "follow_reject", "test", "mentiooon", "emoji_reaction", "reaction"]) + fun parseに失敗する(s: String) { + assertNull(NotificationType.parse(s)) + } + + companion object { + @JvmStatic + fun parseSuccessProvider(): Stream { + return Stream.of( + arguments("mention", NotificationType.mention), + arguments("status", NotificationType.status), + arguments("reblog", NotificationType.reblog), + arguments("follow", NotificationType.follow), + arguments("follow_request", NotificationType.follow_request), + arguments("favourite", NotificationType.favourite), + arguments("poll", NotificationType.poll), + arguments("update", NotificationType.update), + arguments("admin.sign_up", NotificationType.admin_sign_up), + arguments("admin.report", NotificationType.admin_report), + arguments("servered_relationships", NotificationType.severed_relationships) + ) + } + } +} From c18c52d45ea9e88961b0ecf769ffdee026797826 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:17:15 +0900 Subject: [PATCH 15/18] =?UTF-8?q?test:=20NotificationRequest=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FollowNotificationRequestTest.kt | 28 ++++++ .../FollowRequestNotificationRequestTest.kt | 28 ++++++ .../MentionNotificationRequestTest.kt | 28 ++++++ .../NotificationServiceImplTest.kt | 97 +++++++++++++++++++ .../PostNotificationRequestTest.kt | 28 ++++++ .../ReactionNotificationRequestTest.kt | 28 ++++++ .../RepostNotificationRequestTest.kt | 28 ++++++ 7 files changed, 265 insertions(+) create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt new file mode 100644 index 00000000..1e886b32 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import java.time.Instant + +class FollowNotificationRequestTest { + + @Test + fun buildNotification() { + val createdAt = Instant.now() + val actual = FollowNotificationRequest(1, 2).buildNotification(1, createdAt) + + Assertions.assertThat(actual).isEqualTo( + Notification( + id = 1, + type = "follow", + userId = 1, + sourceActorId = 2, + postId = null, + text = null, + reactionId = null, + createdAt = createdAt + ) + ) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt new file mode 100644 index 00000000..370e24be --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import java.time.Instant + +class FollowRequestNotificationRequestTest { + + @Test + fun buildNotification() { + val createdAt = Instant.now() + val actual = FollowRequestNotificationRequest(1, 2).buildNotification(1, createdAt) + + Assertions.assertThat(actual).isEqualTo( + Notification( + id = 1, + type = "follow-request", + userId = 1, + sourceActorId = 2, + postId = null, + text = null, + reactionId = null, + createdAt = createdAt + ) + ) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt new file mode 100644 index 00000000..05172802 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import java.time.Instant + +class MentionNotificationRequestTest { + + @Test + fun buildNotification() { + val createdAt = Instant.now() + val actual = MentionNotificationRequest(1, 2, 3).buildNotification(1, createdAt) + + assertThat(actual).isEqualTo( + Notification( + id = 1, + type = "mention", + userId = 1, + sourceActorId = 2, + postId = 3, + text = null, + reactionId = null, + createdAt = createdAt + ) + ) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt new file mode 100644 index 00000000..7ac0bacd --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt @@ -0,0 +1,97 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.notification.Notification +import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository +import dev.usbharu.hideout.core.domain.model.post.PostRepository +import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Spy +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.* +import utils.UserBuilder +import java.net.URL + +@ExtendWith(MockitoExtension::class) +class NotificationServiceImplTest { + + + @Mock + private lateinit var relationshipNotificationManagementService: RelationshipNotificationManagementService + + @Mock + private lateinit var relationshipRepository: RelationshipRepository + + @Spy + private val notificationStoreList: MutableList = mutableListOf() + + @Mock + private lateinit var notificationRepository: NotificationRepository + + @Mock + private lateinit var actorRepository: ActorRepository + + @Mock + private lateinit var postRepository: PostRepository + + @Mock + private lateinit var reactionRepository: ReactionRepository + + @Spy + private val applicationConfig = ApplicationConfig(URL("https://example.com")) + + @InjectMocks + private lateinit var notificationServiceImpl: NotificationServiceImpl + + @Test + fun `publishNotifi ローカルユーザーへの通知を発行する`() = runTest { + + val actor = UserBuilder.localUserOf(domain = "example.com") + + whenever(actorRepository.findById(eq(1))).doReturn(actor) + + val id = TwitterSnowflakeIdGenerateService.generateId() + + whenever(notificationRepository.generateId()).doReturn(id) + + whenever(notificationRepository.save(any())).doAnswer { it.arguments[0] as Notification } + + + val actual = notificationServiceImpl.publishNotify(PostNotificationRequest(1, 2, 3)) + + assertThat(actual).isNotNull() + + verify(notificationRepository, times(1)).save(any()) + } + + @Test + fun `publishNotify ユーザーが存在しないときは発行しない`() = runTest { + val actual = notificationServiceImpl.publishNotify(PostNotificationRequest(1, 2, 3)) + + assertThat(actual).isNull() + } + + @Test + fun `publishNotify ユーザーがリモートユーザーの場合は発行しない`() = runTest { + val actor = UserBuilder.remoteUserOf(domain = "remote.example.com") + + whenever(actorRepository.findById(eq(1))).doReturn(actor) + + val actual = notificationServiceImpl.publishNotify(PostNotificationRequest(1, 2, 3)) + + assertThat(actual).isNull() + } + + @Test + fun unpublishNotify() = runTest { + notificationServiceImpl.unpublishNotify(1) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt new file mode 100644 index 00000000..d4ce9bb7 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import java.time.Instant + +class PostNotificationRequestTest { + + @Test + fun buildNotification() { + val createdAt = Instant.now() + val actual = PostNotificationRequest(1, 2, 3).buildNotification(1, createdAt) + + Assertions.assertThat(actual).isEqualTo( + Notification( + id = 1, + type = "post", + userId = 1, + sourceActorId = 2, + postId = 3, + text = null, + reactionId = null, + createdAt = createdAt + ) + ) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt new file mode 100644 index 00000000..1d662bc8 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import java.time.Instant + +class ReactionNotificationRequestTest { + + @Test + fun buildNotification() { + val createdAt = Instant.now() + val actual = ReactionNotificationRequest(1, 2, 3, 4).buildNotification(1, createdAt) + + Assertions.assertThat(actual).isEqualTo( + Notification( + id = 1, + type = "reaction", + userId = 1, + sourceActorId = 2, + postId = 3, + text = null, + reactionId = 4, + createdAt = createdAt + ) + ) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt new file mode 100644 index 00000000..f81f8786 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.notification.Notification +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import java.time.Instant + +class RepostNotificationRequestTest { + + @Test + fun buildNotification() { + val createdAt = Instant.now() + val actual = RepostNotificationRequest(1, 2, 3).buildNotification(1, createdAt) + + Assertions.assertThat(actual).isEqualTo( + Notification( + id = 1, + type = "repost", + userId = 1, + sourceActorId = 2, + postId = 3, + text = null, + reactionId = null, + createdAt = createdAt + ) + ) + } +} From d949591ab6cc54f94a2fff2c4159807af8073d58 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:17:38 +0900 Subject: [PATCH 16/18] =?UTF-8?q?test:=20RelationshipNotificationManagemen?= =?UTF-8?q?tServiceImpl=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ipNotificationManagementServiceImplTest.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt new file mode 100644 index 00000000..0293920a --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt @@ -0,0 +1,25 @@ +package dev.usbharu.hideout.core.service.notification + +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import org.junit.jupiter.api.Test +import kotlin.test.assertTrue + +class RelationshipNotificationManagementServiceImplTest { + @Test + fun `sendNotification ミューとしていない場合送信する`() { + val notification = RelationshipNotificationManagementServiceImpl().sendNotification( + Relationship( + 1, + 2, + false, + false, + false, + false, + false + ), PostNotificationRequest(1, 2, 3) + ) + + assertTrue(notification) + + } +} From 74cf38e164bad3d6a666d6b6d29d9b131153b479 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:18:07 +0900 Subject: [PATCH 17/18] =?UTF-8?q?fix:=20ReactionRepositoryImpl=E3=81=AEfin?= =?UTF-8?q?dById=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=99=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/exposedrepository/ReactionRepositoryImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 04cc9031..ee6cbf3b 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 @@ -102,7 +102,7 @@ class ReactionRepositoryImpl( } override suspend fun findById(id: Long): Reaction? = query { - return@query Reactions.select { Reactions.id eq id }.singleOrNull()?.toReaction() + return@query Reactions.leftJoin(CustomEmojis).select { Reactions.id eq id }.singleOrNull()?.toReaction() } override suspend fun findByPostId(postId: Long): List = query { From 25b4b97adba46f6b9360b3676fb7c249653df6da Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:49:44 +0900 Subject: [PATCH 18/18] style: fix lint --- .../application/config/HttpClientConfig.kt | 14 +-- .../application/config/SecurityConfig.kt | 4 +- .../domain/exception/media/MediaException.kt | 1 + .../ExposedNotificationRepository.kt | 16 ++-- .../model/reaction/ReactionRepository.kt | 2 +- .../relationship/RelationshipRepository.kt | 1 + .../notification/NotificationRequest.kt | 96 +++++++++---------- .../notification/NotificationServiceImpl.kt | 1 + .../post/DefaultPostContentFormatter.kt | 12 +-- .../core/service/post/PostServiceImpl.kt | 8 +- .../model/MastodonNotificationRepository.kt | 2 + .../mastodon/domain/model/NotificationType.kt | 1 + .../ExposedMastodonNotificationRepository.kt | 22 ++--- ...goMastodonNotificationRepositoryWrapper.kt | 14 +-- .../MastodonNotificationApiController.kt | 12 +-- .../notification/MastodonNotificationStore.kt | 2 +- .../notification/NotificationApiService.kt | 1 + 17 files changed, 105 insertions(+), 104 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt index f3b036a1..01209557 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt @@ -14,13 +14,13 @@ class HttpClientConfig { @Bean fun httpClient(buildProperties: BuildProperties, applicationConfig: ApplicationConfig): HttpClient = HttpClient(CIO).config { - install(Logging) { - logger = Logger.DEFAULT - level = LogLevel.ALL - } - install(HttpCache) { - } - expectSuccess = true + install(Logging) { + logger = Logger.DEFAULT + level = LogLevel.ALL + } + install(HttpCache) { + } + expectSuccess = true install(UserAgent) { agent = "Hideout/${buildProperties.version} (${applicationConfig.url})" } diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index b79bd78f..075fc1bb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -272,7 +272,9 @@ class SecurityConfig { fun jwtTokenCustomizer(): OAuth2TokenCustomizer { return OAuth2TokenCustomizer { context: JwtEncodingContext -> - if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType && context.authorization?.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE) { + if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType && + context.authorization?.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE + ) { val userDetailsImpl = context.getPrincipal().principal as UserDetailsImpl context.claims.claim("uid", userDetailsImpl.id.toString()) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt index 221c92e5..f5e368db 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.domain.exception.media import java.io.Serial +@Suppress("UnnecessaryAbstractClass") abstract class MediaException : RuntimeException { constructor() : super() constructor(message: String?) : super(message) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt index 9c82f69e..03e28d0a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt @@ -63,14 +63,14 @@ class ExposedNotificationRepository(private val idGenerateService: IdGenerateSer } fun ResultRow.toNotifications() = Notification( - this[Notifications.id], - this[Notifications.type], - this[Notifications.userId], - this[Notifications.sourceActorId], - this[Notifications.postId], - this[Notifications.text], - this[Notifications.reactionId], - this[Notifications.createdAt], + id = this[Notifications.id], + type = this[Notifications.type], + userId = this[Notifications.userId], + sourceActorId = this[Notifications.sourceActorId], + postId = this[Notifications.postId], + text = this[Notifications.text], + reactionId = this[Notifications.reactionId], + createdAt = this[Notifications.createdAt], ) object Notifications : Table("notifications") { 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 b76f5f0a..64901759 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", "TooManyFunction") +@Suppress("FunctionMaxLength", "TooManyFunctions") 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/RelationshipRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt index 7dca9785..843e9eac 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt @@ -43,6 +43,7 @@ interface RelationshipRepository { ignoreFollowRequest: Boolean ): List + @Suppress("FunctionMaxLength") suspend fun findByActorIdAntMutingAndMaxIdAndSinceId( actorId: Long, muting: Boolean, diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt index 3e2b8b5c..0ea02754 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt @@ -22,14 +22,14 @@ data class MentionNotificationRequest( ), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id, - type, - userId, - sourceActorId, - postId, - null, - null, - createdAt + id = id, + type = type, + userId = userId, + sourceActorId = sourceActorId, + postId = postId, + text = null, + reactionId = null, + createdAt = createdAt ) } @@ -40,14 +40,14 @@ data class PostNotificationRequest( ) : NotificationRequest(userId, sourceActorId, "post"), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id, - type, - userId, - sourceActorId, - postId, - null, - null, - createdAt + id = id, + type = type, + userId = userId, + sourceActorId = sourceActorId, + postId = postId, + text = null, + reactionId = null, + createdAt = createdAt ) } @@ -57,14 +57,14 @@ data class RepostNotificationRequest( override val postId: Long ) : NotificationRequest(userId, sourceActorId, "repost"), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id, - type, - userId, - sourceActorId, - postId, - null, - null, - createdAt + id = id, + type = type, + userId = userId, + sourceActorId = sourceActorId, + postId = postId, + text = null, + reactionId = null, + createdAt = createdAt ) } @@ -73,14 +73,14 @@ data class FollowNotificationRequest( override val sourceActorId: Long ) : NotificationRequest(userId, sourceActorId, "follow") { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id, - type, - userId, - sourceActorId, - null, - null, - null, - createdAt + id = id, + type = type, + userId = userId, + sourceActorId = sourceActorId, + postId = null, + text = null, + reactionId = null, + createdAt = createdAt ) } @@ -89,14 +89,14 @@ data class FollowRequestNotificationRequest( override val sourceActorId: Long ) : NotificationRequest(userId, sourceActorId, "follow-request") { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id, - type, - userId, - sourceActorId, - null, - null, - null, - createdAt + id = id, + type = type, + userId = userId, + sourceActorId = sourceActorId, + postId = null, + text = null, + reactionId = null, + createdAt = createdAt ) } @@ -108,13 +108,13 @@ data class ReactionNotificationRequest( ) : NotificationRequest(userId, sourceActorId, "reaction"), PostId { override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id, - type, - userId, - sourceActorId, - postId, - null, - reactionId, - createdAt + id = id, + type = type, + userId = userId, + sourceActorId = sourceActorId, + postId = postId, + text = null, + reactionId = reactionId, + createdAt = createdAt ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt index f208587e..f2c049a7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt @@ -59,6 +59,7 @@ class NotificationServiceImpl( logger.debug("push to {} notification store.", notificationStoreList.size) for (it in notificationStoreList) { + @Suppress("TooGenericExceptionCaught") try { it.publishNotification(savedNotification, user, sourceActor, post, reaction) } catch (e: Exception) { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt index 21252a28..3f063845 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt @@ -67,13 +67,13 @@ class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : Po private fun printHtml(element: Elements): String { return element.joinToString("\n\n") { - it.childNodes().joinToString("") { - if (it is Element && it.tagName() == "br") { + it.childNodes().joinToString("") { node -> + if (node is Element && node.tagName() == "br") { "\n" - } else if (it is Element) { - it.text() - } else if (it is TextNode) { - it.text() + } else if (node is Element) { + node.text() + } else if (node is TextNode) { + node.text() } else { "" } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt index 6e58fe8d..a6878729 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt @@ -5,7 +5,6 @@ import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteServi import dev.usbharu.hideout.core.domain.exception.UserNotFoundException import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.PostRepository @@ -38,7 +37,7 @@ class PostServiceImpl( logger.info("START Create Remote Post user: {}, remote url: {}", post.actorId, post.apId) val actor = actorRepository.findById(post.actorId) ?: throw UserNotFoundException("${post.actorId} was not found.") - val createdPost = internalCreate(post, false, actor) + val createdPost = internalCreate(post, false) logger.info("SUCCESS Create Remote Post url: {}", createdPost.url) return createdPost } @@ -79,11 +78,10 @@ class PostServiceImpl( actorRepository.save(actor.copy(postsCount = 0, lastPostDate = null)) } - private suspend fun internalCreate(post: Post, isLocal: Boolean, actor: Actor): Post { + private suspend fun internalCreate(post: Post, isLocal: Boolean): Post { return try { val save = postRepository.save(post) timelineService.publishTimeline(post, isLocal) -// actorRepository.save(actor.incrementPostsCount()) save } catch (_: DuplicateException) { postRepository.findByApId(post.apId) ?: throw PostNotFoundException.withApId(post.apId) @@ -105,7 +103,7 @@ class PostServiceImpl( replyId = post.repolyId, repostId = post.repostId, ) - return internalCreate(createPost, isLocal, user) + return internalCreate(createPost, isLocal) } companion object { diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt index 42d769da..8d903a56 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt @@ -4,6 +4,8 @@ interface MastodonNotificationRepository { suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification suspend fun deleteById(id: Long) suspend fun findById(id: Long): MastodonNotification? + + @Suppress("LongParameterList", "FunctionMaxLength") suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId( loginUser: Long, maxId: Long?, diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt index b4eb3a45..ff762881 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.mastodon.domain.model +@Suppress("EnumEntryName", "EnumNaming", "EnumEntryNameCase") enum class NotificationType { mention, status, diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt index 520d913a..14279e69 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt @@ -105,18 +105,16 @@ class ExposedMastodonNotificationRepository : MastodonNotificationRepository, Ab } } -fun ResultRow.toMastodonNotification(): MastodonNotification { - return MastodonNotification( - this[MastodonNotifications.id], - this[MastodonNotifications.userId], - NotificationType.valueOf(this[MastodonNotifications.type]), - this[MastodonNotifications.createdAt], - this[MastodonNotifications.accountId], - this[MastodonNotifications.statusId], - this[MastodonNotifications.reportId], - this[MastodonNotifications.relationshipServeranceEventId], - ) -} +fun ResultRow.toMastodonNotification(): MastodonNotification = MastodonNotification( + id = this[MastodonNotifications.id], + userId = this[MastodonNotifications.userId], + type = NotificationType.valueOf(this[MastodonNotifications.type]), + createdAt = this[MastodonNotifications.createdAt], + accountId = this[MastodonNotifications.accountId], + statusId = this[MastodonNotifications.statusId], + reportId = this[MastodonNotifications.reportId], + relationshipServeranceEvent = this[MastodonNotifications.relationshipServeranceEventId], +) object MastodonNotifications : Table("mastodon_notifications") { val id = long("id") diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt index 1016279f..96c7a278 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt @@ -18,17 +18,13 @@ class MongoMastodonNotificationRepositoryWrapper( private val mongoTemplate: MongoTemplate ) : MastodonNotificationRepository { - override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification { - return mongoMastodonNotificationRepository.save(mastodonNotification) - } + override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = + mongoMastodonNotificationRepository.save(mastodonNotification) - override suspend fun deleteById(id: Long) { - mongoMastodonNotificationRepository.deleteById(id) - } + override suspend fun deleteById(id: Long) = mongoMastodonNotificationRepository.deleteById(id) - override suspend fun findById(id: Long): MastodonNotification? { - return mongoMastodonNotificationRepository.findById(id).getOrNull() - } + override suspend fun findById(id: Long): MastodonNotification? = + mongoMastodonNotificationRepository.findById(id).getOrNull() override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId( loginUser: Long, diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt index c8ea7e78..ba83cb37 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt @@ -31,12 +31,12 @@ class MastodonNotificationApiController( accountId: List? ): ResponseEntity> = runBlocking { val notificationFlow = notificationApiService.notifications( - loginUserContextHolder.getLoginUserId(), - maxId?.toLong(), - minId?.toLong(), - sinceId?.toLong(), - limit ?: 20, - types.orEmpty().mapNotNull { NotificationType.parse(it) }, + loginUser = loginUserContextHolder.getLoginUserId(), + maxId = maxId?.toLong(), + minId = minId?.toLong(), + sinceId = sinceId?.toLong(), + limit = limit ?: 20, + types = types.orEmpty().mapNotNull { NotificationType.parse(it) }, excludeTypes = excludeTypes.orEmpty().mapNotNull { NotificationType.parse(it) }, accountId = accountId.orEmpty().mapNotNull { it.toLongOrNull() } ).asFlow() diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt index 80e66e14..68cc449c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt @@ -41,7 +41,7 @@ class MastodonNotificationStore(private val mastodonNotificationRepository: Mast val mastodonNotification = MastodonNotification( id = notification.id, - notification.userId, + userId = notification.userId, type = notificationType, createdAt = notification.createdAt, accountId = notification.sourceActorId, diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt index 152b6bab..0849d69a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.domain.mastodon.model.generated.Notification import dev.usbharu.hideout.mastodon.domain.model.NotificationType interface NotificationApiService { + @Suppress("LongParameterList") suspend fun notifications( loginUser: Long, maxId: Long?,