mirror of https://github.com/usbharu/Hideout.git
Merge pull request #259 from usbharu/feature/notification
Feature/notification
This commit is contained in:
commit
fe3082db59
|
@ -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})"
|
||||
}
|
||||
|
|
|
@ -272,7 +272,9 @@ class SecurityConfig {
|
|||
fun jwtTokenCustomizer(): OAuth2TokenCustomizer<JwtEncodingContext> {
|
||||
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<Authentication>().principal as UserDetailsImpl
|
||||
context.claims.claim("uid", userDetailsImpl.id.toString())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
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[type] = notification.type
|
||||
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[type] = notification.type
|
||||
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(
|
||||
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") {
|
||||
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()
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package dev.usbharu.hideout.core.domain.model.notification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class Notification(
|
||||
val id: Long,
|
||||
val type: String,
|
||||
val userId: Long,
|
||||
val sourceActorId: Long?,
|
||||
val postId: Long?,
|
||||
val text: String?,
|
||||
val reactionId: Long?,
|
||||
val createdAt: Instant
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
@ -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<Reaction>
|
||||
suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction?
|
||||
suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean
|
||||
|
|
|
@ -43,6 +43,7 @@ interface RelationshipRepository {
|
|||
ignoreFollowRequest: Boolean
|
||||
): List<Relationship>
|
||||
|
||||
@Suppress("FunctionMaxLength")
|
||||
suspend fun findByActorIdAntMutingAndMaxIdAndSinceId(
|
||||
actorId: Long,
|
||||
muting: Boolean,
|
||||
|
|
|
@ -101,6 +101,10 @@ class ReactionRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Long): Reaction? = query {
|
||||
return@query Reactions.leftJoin(CustomEmojis).select { Reactions.id eq id }.singleOrNull()?.toReaction()
|
||||
}
|
||||
|
||||
override suspend fun findByPostId(postId: Long): List<Reaction> = query {
|
||||
return@query Reactions.leftJoin(CustomEmojis).select { Reactions.postId eq postId }.map { it.toReaction() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
package dev.usbharu.hideout.core.service.notification
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
data class MentionNotificationRequest(
|
||||
override val userId: Long,
|
||||
override val sourceActorId: Long,
|
||||
override val postId: Long
|
||||
) : NotificationRequest(
|
||||
userId,
|
||||
sourceActorId,
|
||||
"mention"
|
||||
),
|
||||
PostId {
|
||||
override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification(
|
||||
id = id,
|
||||
type = type,
|
||||
userId = userId,
|
||||
sourceActorId = sourceActorId,
|
||||
postId = postId,
|
||||
text = null,
|
||||
reactionId = null,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
||||
|
||||
data class PostNotificationRequest(
|
||||
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(
|
||||
id = id,
|
||||
type = type,
|
||||
userId = userId,
|
||||
sourceActorId = sourceActorId,
|
||||
postId = postId,
|
||||
text = null,
|
||||
reactionId = null,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
||||
|
||||
data class RepostNotificationRequest(
|
||||
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 = id,
|
||||
type = type,
|
||||
userId = userId,
|
||||
sourceActorId = sourceActorId,
|
||||
postId = postId,
|
||||
text = null,
|
||||
reactionId = null,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
||||
|
||||
data class FollowNotificationRequest(
|
||||
override val userId: Long,
|
||||
override val sourceActorId: Long
|
||||
) : NotificationRequest(userId, sourceActorId, "follow") {
|
||||
override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification(
|
||||
id = id,
|
||||
type = type,
|
||||
userId = userId,
|
||||
sourceActorId = sourceActorId,
|
||||
postId = null,
|
||||
text = null,
|
||||
reactionId = null,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
||||
|
||||
data class FollowRequestNotificationRequest(
|
||||
override val userId: Long,
|
||||
override val sourceActorId: Long
|
||||
) : NotificationRequest(userId, sourceActorId, "follow-request") {
|
||||
override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification(
|
||||
id = id,
|
||||
type = type,
|
||||
userId = userId,
|
||||
sourceActorId = sourceActorId,
|
||||
postId = null,
|
||||
text = null,
|
||||
reactionId = null,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
||||
|
||||
data class ReactionNotificationRequest(
|
||||
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(
|
||||
id = id,
|
||||
type = type,
|
||||
userId = userId,
|
||||
sourceActorId = sourceActorId,
|
||||
postId = postId,
|
||||
text = null,
|
||||
reactionId = reactionId,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
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
|
||||
|
||||
@Service
|
||||
class NotificationServiceImpl(
|
||||
private val relationshipNotificationManagementService: RelationshipNotificationManagementService,
|
||||
private val relationshipRepository: RelationshipRepository,
|
||||
private val notificationStoreList: List<NotificationStore>,
|
||||
private val notificationRepository: NotificationRepository,
|
||||
private val actorRepository: ActorRepository,
|
||||
private val postRepository: PostRepository,
|
||||
private val reactionRepository: ReactionRepository,
|
||||
private val applicationConfig: ApplicationConfig
|
||||
) : NotificationService {
|
||||
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
|
||||
}
|
||||
|
||||
val id = notificationRepository.generateId()
|
||||
val createdAt = Instant.now()
|
||||
|
||||
val notification = notificationRequest.buildNotification(id, createdAt)
|
||||
|
||||
val savedNotification = notificationRepository.save(notification)
|
||||
|
||||
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) {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(NotificationServiceImpl::class.java)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.core.service.notification
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||
|
||||
interface RelationshipNotificationManagementService {
|
||||
fun sendNotification(relationship: Relationship, notificationRequest: NotificationRequest): Boolean
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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 {
|
||||
""
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
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 userId: Long,
|
||||
val type: NotificationType,
|
||||
val createdAt: Instant,
|
||||
val accountId: Long,
|
||||
val statusId: Long?,
|
||||
val reportId: Long?,
|
||||
val relationshipServeranceEvent: Long?
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
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?
|
||||
|
||||
@Suppress("LongParameterList", "FunctionMaxLength")
|
||||
suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
typesTmp: MutableList<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<MastodonNotification>
|
||||
|
||||
suspend fun deleteByUserId(userId: Long)
|
||||
suspend fun deleteByUserIdAndId(userId: Long, id: Long)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package dev.usbharu.hideout.mastodon.domain.model
|
||||
|
||||
@Suppress("EnumEntryName", "EnumNaming", "EnumEntryNameCase")
|
||||
enum class NotificationType {
|
||||
mention,
|
||||
status,
|
||||
reblog,
|
||||
follow,
|
||||
follow_request,
|
||||
favourite,
|
||||
poll,
|
||||
update,
|
||||
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.sign_up" -> admin_sign_up
|
||||
"admin.report" -> admin_report
|
||||
"servered_relationships" -> severed_relationships
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
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
|
||||
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()
|
||||
}
|
||||
|
||||
override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
typesTmp: MutableList<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<MastodonNotification> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
val userId = long("user_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()
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
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<MastodonNotification, Long> {
|
||||
|
||||
fun deleteByUserId(userId: Long): Long
|
||||
|
||||
fun deleteByIdAndUserId(id: Long, userId: Long): Long
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
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,
|
||||
private val mongoTemplate: MongoTemplate
|
||||
) :
|
||||
MastodonNotificationRepository {
|
||||
override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification =
|
||||
mongoMastodonNotificationRepository.save(mastodonNotification)
|
||||
|
||||
override suspend fun deleteById(id: Long) = mongoMastodonNotificationRepository.deleteById(id)
|
||||
|
||||
override suspend fun findById(id: Long): MastodonNotification? =
|
||||
mongoMastodonNotificationRepository.findById(id).getOrNull()
|
||||
|
||||
override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
typesTmp: MutableList<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<MastodonNotification> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<Any> {
|
||||
notificationApiService.clearAll(loginUserContextHolder.getLoginUserId())
|
||||
return ResponseEntity.ok(null)
|
||||
}
|
||||
|
||||
override fun apiV1NotificationsGet(
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
minId: String?,
|
||||
limit: Int?,
|
||||
types: List<String>?,
|
||||
excludeTypes: List<String>?,
|
||||
accountId: List<String>?
|
||||
): ResponseEntity<Flow<Notification>> = runBlocking {
|
||||
val notificationFlow = notificationApiService.notifications(
|
||||
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()
|
||||
ResponseEntity.ok(notificationFlow)
|
||||
}
|
||||
|
||||
override suspend fun apiV1NotificationsIdDismissPost(id: String): ResponseEntity<Any> {
|
||||
notificationApiService.dismiss(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||
return ResponseEntity.ok(null)
|
||||
}
|
||||
|
||||
override suspend fun apiV1NotificationsIdGet(id: String): ResponseEntity<Notification> {
|
||||
val notification = notificationApiService.fingById(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||
|
||||
return ResponseEntity.ok(notification)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
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,
|
||||
userId = notification.userId,
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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 {
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun notifications(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
types: List<NotificationType>,
|
||||
excludeTypes: List<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<Notification>
|
||||
|
||||
suspend fun fingById(loginUser: Long, notificationId: Long): Notification?
|
||||
|
||||
suspend fun clearAll(loginUser: Long)
|
||||
|
||||
suspend fun dismiss(loginUser: Long, notificationId: Long)
|
||||
}
|
|
@ -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<NotificationType>,
|
||||
excludeTypes: List<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<Notification> = transaction.transaction {
|
||||
val typesTmp = mutableListOf<NotificationType>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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<NotificationStore> = 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"))
|
||||
|
||||
|
|
|
@ -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<Arguments> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue