mirror of https://github.com/usbharu/Hideout.git
commit
ecab768a6e
|
|
@ -0,0 +1,28 @@
|
||||||
|
package dev.usbharu.hideout.core.application.model
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
data class Reactions(
|
||||||
|
val postId: Long,
|
||||||
|
val count: Int,
|
||||||
|
val name: String,
|
||||||
|
val domain: String,
|
||||||
|
val url: URI?,
|
||||||
|
val actorIds: List<Long>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun of(reactionList: List<Reaction>, customEmoji: CustomEmoji?): Reactions {
|
||||||
|
val first = reactionList.first()
|
||||||
|
return Reactions(
|
||||||
|
first.id.value,
|
||||||
|
reactionList.size,
|
||||||
|
customEmoji?.name ?: first.unicodeEmoji.name,
|
||||||
|
customEmoji?.domain?.domain ?: first.unicodeEmoji.domain.domain,
|
||||||
|
customEmoji?.url,
|
||||||
|
reactionList.map { it.actorId.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,10 @@ import dev.usbharu.hideout.core.domain.model.media.Media
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
|
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
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.support.principal.Principal
|
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||||
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
||||||
|
import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
|
@ -20,7 +22,9 @@ class GetPostDetailApplicationService(
|
||||||
private val postRepository: PostRepository,
|
private val postRepository: PostRepository,
|
||||||
private val actorRepository: ActorRepository,
|
private val actorRepository: ActorRepository,
|
||||||
private val mediaRepository: MediaRepository,
|
private val mediaRepository: MediaRepository,
|
||||||
private val iPostReadAccessControl: IPostReadAccessControl
|
private val iPostReadAccessControl: IPostReadAccessControl,
|
||||||
|
private val reactionsQueryService: ReactionsQueryService,
|
||||||
|
private val reactionRepository: ReactionRepository,
|
||||||
) : AbstractApplicationService<GetPostDetail, PostDetail>(
|
) : AbstractApplicationService<GetPostDetail, PostDetail>(
|
||||||
transaction,
|
transaction,
|
||||||
logger
|
logger
|
||||||
|
|
@ -38,6 +42,15 @@ class GetPostDetailApplicationService(
|
||||||
|
|
||||||
val mediaList = mediaRepository.findByIds(post.mediaIds)
|
val mediaList = mediaRepository.findByIds(post.mediaIds)
|
||||||
|
|
||||||
|
val reactions = reactionsQueryService.findAllByPostId(post.id)
|
||||||
|
|
||||||
|
val favourited = reactionRepository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||||
|
post.id,
|
||||||
|
principal.actorId,
|
||||||
|
null,
|
||||||
|
"❤"
|
||||||
|
)
|
||||||
|
|
||||||
return PostDetail.of(
|
return PostDetail.of(
|
||||||
post = post,
|
post = post,
|
||||||
actor = actor,
|
actor = actor,
|
||||||
|
|
@ -46,6 +59,8 @@ class GetPostDetailApplicationService(
|
||||||
reply = post.replyId?.let { fetchChild(it, actor, iconMedia, principal) },
|
reply = post.replyId?.let { fetchChild(it, actor, iconMedia, principal) },
|
||||||
repost = post.repostId?.let { fetchChild(it, actor, iconMedia, principal) },
|
repost = post.repostId?.let { fetchChild(it, actor, iconMedia, principal) },
|
||||||
moveTo = post.moveTo?.let { fetchChild(it, actor, iconMedia, principal) },
|
moveTo = post.moveTo?.let { fetchChild(it, actor, iconMedia, principal) },
|
||||||
|
reactionsList = reactions,
|
||||||
|
favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,10 +84,12 @@ class GetPostDetailApplicationService(
|
||||||
|
|
||||||
val mediaList = mediaRepository.findByIds(post.mediaIds)
|
val mediaList = mediaRepository.findByIds(post.mediaIds)
|
||||||
return PostDetail.of(
|
return PostDetail.of(
|
||||||
post,
|
post = post,
|
||||||
first,
|
actor = first,
|
||||||
third,
|
iconMedia = third,
|
||||||
mediaList
|
mediaList = mediaList,
|
||||||
|
reactionsList = emptyList(),
|
||||||
|
favourited = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.application.post
|
package dev.usbharu.hideout.core.application.post
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.model.Reactions
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.media.Media
|
import dev.usbharu.hideout.core.domain.model.media.Media
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
|
|
@ -23,7 +24,9 @@ data class PostDetail(
|
||||||
val sensitive: Boolean,
|
val sensitive: Boolean,
|
||||||
val deleted: Boolean,
|
val deleted: Boolean,
|
||||||
val mediaDetailList: List<MediaDetail>,
|
val mediaDetailList: List<MediaDetail>,
|
||||||
val moveTo: PostDetail?
|
val moveTo: PostDetail?,
|
||||||
|
val reactionsList: List<Reactions>,
|
||||||
|
val favourited: Boolean
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
@ -35,6 +38,8 @@ data class PostDetail(
|
||||||
reply: PostDetail? = null,
|
reply: PostDetail? = null,
|
||||||
repost: PostDetail? = null,
|
repost: PostDetail? = null,
|
||||||
moveTo: PostDetail? = null,
|
moveTo: PostDetail? = null,
|
||||||
|
reactionsList: List<Reactions>,
|
||||||
|
favourited: Boolean
|
||||||
): PostDetail {
|
): PostDetail {
|
||||||
return PostDetail(
|
return PostDetail(
|
||||||
id = post.id.id,
|
id = post.id.id,
|
||||||
|
|
@ -52,7 +57,9 @@ data class PostDetail(
|
||||||
sensitive = post.sensitive,
|
sensitive = post.sensitive,
|
||||||
deleted = false,
|
deleted = false,
|
||||||
mediaDetailList = mediaList.map { MediaDetail.of(it) },
|
mediaDetailList = mediaList.map { MediaDetail.of(it) },
|
||||||
moveTo = moveTo
|
moveTo = moveTo,
|
||||||
|
reactionsList = reactionsList,
|
||||||
|
favourited = favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package dev.usbharu.hideout.core.application.reaction
|
||||||
|
|
||||||
|
data class CreateReaction(val postId: Long, val customEmojiId: Long?, val unicodeEmoji: String)
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.core.application.reaction
|
||||||
|
|
||||||
|
data class RemoveReaction(
|
||||||
|
val postId: Long,
|
||||||
|
val customEmojiId: Long?,
|
||||||
|
val unicodeEmoji: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package dev.usbharu.hideout.core.application.reaction
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||||
|
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
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.ReactionId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
|
||||||
|
import dev.usbharu.hideout.core.domain.service.emoji.UnicodeEmojiService
|
||||||
|
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class UserCreateReactionApplicationService(
|
||||||
|
transaction: Transaction,
|
||||||
|
private val idGenerateService: IdGenerateService,
|
||||||
|
private val reactionRepository: ReactionRepository,
|
||||||
|
private val postReadAccessControl: IPostReadAccessControl,
|
||||||
|
private val postRepository: PostRepository,
|
||||||
|
private val customEmojiRepository: CustomEmojiRepository,
|
||||||
|
private val unicodeEmojiService: UnicodeEmojiService
|
||||||
|
) :
|
||||||
|
LocalUserAbstractApplicationService<CreateReaction, Unit>(
|
||||||
|
transaction,
|
||||||
|
logger
|
||||||
|
) {
|
||||||
|
override suspend fun internalExecute(command: CreateReaction, principal: LocalUser) {
|
||||||
|
val postId = PostId(command.postId)
|
||||||
|
val post = postRepository.findById(postId) ?: throw IllegalArgumentException("Post $postId not found.")
|
||||||
|
if (postReadAccessControl.isAllow(post, principal).not()) {
|
||||||
|
throw PermissionDeniedException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val customEmoji = command.customEmojiId?.let { customEmojiRepository.findById(it) }
|
||||||
|
|
||||||
|
val unicodeEmoji = if (unicodeEmojiService.isUnicodeEmoji(command.unicodeEmoji)) {
|
||||||
|
command.unicodeEmoji
|
||||||
|
} else {
|
||||||
|
"❤"
|
||||||
|
}
|
||||||
|
|
||||||
|
val reaction = Reaction.create(
|
||||||
|
id = ReactionId(idGenerateService.generateId()),
|
||||||
|
postId = postId,
|
||||||
|
actorId = principal.actorId,
|
||||||
|
customEmojiId = customEmoji?.id,
|
||||||
|
unicodeEmoji = UnicodeEmoji(unicodeEmoji),
|
||||||
|
createdAt = Instant.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
reactionRepository.save(reaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(UserCreateReactionApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package dev.usbharu.hideout.core.application.reaction
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
|
||||||
|
import dev.usbharu.hideout.core.domain.service.emoji.UnicodeEmojiService
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class UserRemoveReactionApplicationService(
|
||||||
|
transaction: Transaction,
|
||||||
|
private val customEmojiRepository: CustomEmojiRepository,
|
||||||
|
private val reactionRepository: ReactionRepository,
|
||||||
|
private val unicodeEmojiService: UnicodeEmojiService
|
||||||
|
) :
|
||||||
|
LocalUserAbstractApplicationService<RemoveReaction, Unit>(
|
||||||
|
transaction,
|
||||||
|
logger
|
||||||
|
) {
|
||||||
|
override suspend fun internalExecute(command: RemoveReaction, principal: LocalUser) {
|
||||||
|
val postId = PostId(command.postId)
|
||||||
|
|
||||||
|
val customEmoji = command.customEmojiId?.let { customEmojiRepository.findById(it) }
|
||||||
|
|
||||||
|
val unicodeEmoji = if (unicodeEmojiService.isUnicodeEmoji(command.unicodeEmoji)) {
|
||||||
|
command.unicodeEmoji
|
||||||
|
} else {
|
||||||
|
"❤"
|
||||||
|
}
|
||||||
|
val reaction =
|
||||||
|
reactionRepository.findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||||
|
postId,
|
||||||
|
principal.actorId,
|
||||||
|
customEmoji?.id,
|
||||||
|
unicodeEmoji
|
||||||
|
)
|
||||||
|
?: throw IllegalArgumentException("Reaction $postId ${principal.actorId} ${customEmoji?.id} $unicodeEmoji not found.")
|
||||||
|
|
||||||
|
reaction.delete()
|
||||||
|
|
||||||
|
reactionRepository.delete(reaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(UserRemoveReactionApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,7 +47,9 @@ class ReadTimelineApplicationService(
|
||||||
it.replyPost,
|
it.replyPost,
|
||||||
it.replyPostActor!!,
|
it.replyPostActor!!,
|
||||||
it.replyPostActorIconMedia,
|
it.replyPostActorIconMedia,
|
||||||
it.replyPostMedias.orEmpty()
|
it.replyPostMedias.orEmpty(),
|
||||||
|
reactionsList = emptyList(),
|
||||||
|
favourited = false,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
@ -56,10 +58,12 @@ class ReadTimelineApplicationService(
|
||||||
val repost = if (it.repostPost != null) {
|
val repost = if (it.repostPost != null) {
|
||||||
@Suppress("UnsafeCallOnNullableType")
|
@Suppress("UnsafeCallOnNullableType")
|
||||||
PostDetail.of(
|
PostDetail.of(
|
||||||
it.repostPost,
|
post = it.repostPost,
|
||||||
it.repostPostActor!!,
|
actor = it.repostPostActor!!,
|
||||||
it.repostPostActorIconMedia,
|
iconMedia = it.repostPostActorIconMedia,
|
||||||
it.repostPostMedias.orEmpty()
|
mediaList = it.repostPostMedias.orEmpty(),
|
||||||
|
reactionsList = emptyList(),
|
||||||
|
favourited = false
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
@ -71,7 +75,9 @@ class ReadTimelineApplicationService(
|
||||||
iconMedia = it.postActorIconMedia,
|
iconMedia = it.postActorIconMedia,
|
||||||
mediaList = it.postMedias,
|
mediaList = it.postMedias,
|
||||||
reply = reply,
|
reply = reply,
|
||||||
repost = repost
|
repost = repost,
|
||||||
|
reactionsList = emptyList(),
|
||||||
|
favourited = it.favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.event.reaction
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.ReactionId
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||||
|
|
||||||
|
class ReactionEventFactory(private val reaction: Reaction) {
|
||||||
|
fun createEvent(reactionEvent: ReactionEvent): DomainEvent<ReactionEventBody> =
|
||||||
|
DomainEvent.create(reactionEvent.eventName, ReactionEventBody(reaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReactionEventBody(
|
||||||
|
reaction: Reaction
|
||||||
|
) : DomainEventBody(mapOf("reactionId" to reaction.id)) {
|
||||||
|
fun getReactionId(): ReactionId = toMap()["reactionId"] as ReactionId
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ReactionEvent(val eventName: String) {
|
||||||
|
CREATE("ReactionCreate"),
|
||||||
|
DELETE("ReactionDelete"),
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ package dev.usbharu.hideout.core.domain.model.actor
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory
|
import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory
|
||||||
import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.*
|
import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.*
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||||
|
|
@ -52,7 +52,7 @@ class Actor(
|
||||||
var lastUpdateAt: Instant = createdAt,
|
var lastUpdateAt: Instant = createdAt,
|
||||||
alsoKnownAs: Set<ActorId> = emptySet(),
|
alsoKnownAs: Set<ActorId> = emptySet(),
|
||||||
moveTo: ActorId? = null,
|
moveTo: ActorId? = null,
|
||||||
emojiIds: Set<EmojiId>,
|
emojiIds: Set<CustomEmojiId>,
|
||||||
deleted: Boolean,
|
deleted: Boolean,
|
||||||
icon: MediaId?,
|
icon: MediaId?,
|
||||||
banner: MediaId?,
|
banner: MediaId?,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ sealed class Emoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CustomEmoji(
|
data class CustomEmoji(
|
||||||
val id: EmojiId,
|
val id: CustomEmojiId,
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val domain: Domain,
|
override val domain: Domain,
|
||||||
val instanceId: InstanceId,
|
val instanceId: InstanceId,
|
||||||
|
|
@ -50,6 +50,10 @@ data class CustomEmoji(
|
||||||
data class UnicodeEmoji(
|
data class UnicodeEmoji(
|
||||||
override val name: String
|
override val name: String
|
||||||
) : Emoji() {
|
) : Emoji() {
|
||||||
override val domain: Domain = Domain("unicode.org")
|
override val domain: Domain = Companion.domain
|
||||||
override fun id(): String = name
|
override fun id(): String = name
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val domain = Domain("unicode.org")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.emoji
|
package dev.usbharu.hideout.core.domain.model.emoji
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class EmojiId(val emojiId: Long) {
|
value class CustomEmojiId(val emojiId: Long) {
|
||||||
init {
|
init {
|
||||||
require(0 <= emojiId)
|
require(0 <= emojiId)
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ import dev.usbharu.hideout.core.domain.event.post.PostDomainEventFactory
|
||||||
import dev.usbharu.hideout.core.domain.event.post.PostEvent
|
import dev.usbharu.hideout.core.domain.event.post.PostEvent
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
|
||||||
|
|
@ -138,7 +138,7 @@ class Post(
|
||||||
return content.text
|
return content.text
|
||||||
}
|
}
|
||||||
|
|
||||||
val emojiIds: List<EmojiId>
|
val emojiIds: List<CustomEmojiId>
|
||||||
get() {
|
get() {
|
||||||
if (hide) {
|
if (hide) {
|
||||||
return PostContent.empty.emojiIds
|
return PostContent.empty.emojiIds
|
||||||
|
|
@ -217,7 +217,7 @@ class Post(
|
||||||
|
|
||||||
override fun hashCode(): Int = id.hashCode()
|
override fun hashCode(): Int = id.hashCode()
|
||||||
|
|
||||||
fun reconstructWith(mediaIds: List<MediaId>, emojis: List<EmojiId>, visibleActors: Set<ActorId>): Post {
|
fun reconstructWith(mediaIds: List<MediaId>, emojis: List<CustomEmojiId>, visibleActors: Set<ActorId>): Post {
|
||||||
return Post(
|
return Post(
|
||||||
id = id,
|
id = id,
|
||||||
actorId = actorId,
|
actorId = actorId,
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.domain.model.post
|
package dev.usbharu.hideout.core.domain.model.post
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
|
|
||||||
data class PostContent(val text: String, val content: String, val emojiIds: List<EmojiId>) {
|
data class PostContent(val text: String, val content: String, val emojiIds: List<CustomEmojiId>) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val empty = PostContent("", "", emptyList())
|
val empty = PostContent("", "", emptyList())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.reaction
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.event.reaction.ReactionEvent
|
||||||
|
import dev.usbharu.hideout.core.domain.event.reaction.ReactionEventFactory
|
||||||
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class Reaction(
|
||||||
|
val id: ReactionId,
|
||||||
|
val postId: PostId,
|
||||||
|
val actorId: ActorId,
|
||||||
|
val customEmojiId: CustomEmojiId?,
|
||||||
|
val unicodeEmoji: UnicodeEmoji,
|
||||||
|
val createdAt: Instant
|
||||||
|
) : DomainEventStorable() {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as Reaction
|
||||||
|
|
||||||
|
return id == other.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return id.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete() {
|
||||||
|
addDomainEvent(ReactionEventFactory(this).createEvent(ReactionEvent.DELETE))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
id: ReactionId,
|
||||||
|
postId: PostId,
|
||||||
|
actorId: ActorId,
|
||||||
|
customEmojiId: CustomEmojiId?,
|
||||||
|
unicodeEmoji: UnicodeEmoji,
|
||||||
|
createdAt: Instant
|
||||||
|
): Reaction {
|
||||||
|
return Reaction(
|
||||||
|
id,
|
||||||
|
postId,
|
||||||
|
actorId,
|
||||||
|
customEmojiId,
|
||||||
|
unicodeEmoji,
|
||||||
|
createdAt
|
||||||
|
).apply { addDomainEvent(ReactionEventFactory(this).createEvent(ReactionEvent.CREATE)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.reaction
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class ReactionId(val value: Long)
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.reaction
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
|
||||||
|
interface ReactionRepository {
|
||||||
|
suspend fun save(reaction: Reaction): Reaction
|
||||||
|
suspend fun findById(reactionId: ReactionId): Reaction?
|
||||||
|
suspend fun findByPostId(postId: PostId): List<Reaction>
|
||||||
|
suspend fun existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||||
|
postId: PostId,
|
||||||
|
actorId: ActorId,
|
||||||
|
customEmojiId: CustomEmojiId?,
|
||||||
|
unicodeEmoji: String
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
suspend fun findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||||
|
postId: PostId,
|
||||||
|
actorId: ActorId,
|
||||||
|
customEmojiId: CustomEmojiId?,
|
||||||
|
unicodeEmoji: String
|
||||||
|
): Reaction?
|
||||||
|
|
||||||
|
suspend fun delete(reaction: Reaction)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail
|
package dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.model.Reactions
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.media.Media
|
import dev.usbharu.hideout.core.domain.model.media.Media
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
|
|
@ -29,7 +30,9 @@ data class TimelineObjectDetail(
|
||||||
val isPureRepost: Boolean,
|
val isPureRepost: Boolean,
|
||||||
val lastUpdateAt: Instant,
|
val lastUpdateAt: Instant,
|
||||||
val hasMediaInRepost: Boolean,
|
val hasMediaInRepost: Boolean,
|
||||||
val warnFilter: List<TimelineObjectWarnFilter>
|
val warnFilter: List<TimelineObjectWarnFilter>,
|
||||||
|
val reactionsList: List<Reactions>,
|
||||||
|
val favourited: Boolean
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
@ -48,7 +51,9 @@ data class TimelineObjectDetail(
|
||||||
repostPostMedias: List<Media>?,
|
repostPostMedias: List<Media>?,
|
||||||
repostPostActor: Actor?,
|
repostPostActor: Actor?,
|
||||||
repostPostActorIconMedia: Media?,
|
repostPostActorIconMedia: Media?,
|
||||||
warnFilter: List<TimelineObjectWarnFilter>
|
warnFilter: List<TimelineObjectWarnFilter>,
|
||||||
|
reactionsList: List<Reactions>,
|
||||||
|
favourited: Boolean
|
||||||
): TimelineObjectDetail {
|
): TimelineObjectDetail {
|
||||||
return TimelineObjectDetail(
|
return TimelineObjectDetail(
|
||||||
id = timelineObject.id,
|
id = timelineObject.id,
|
||||||
|
|
@ -69,7 +74,9 @@ data class TimelineObjectDetail(
|
||||||
isPureRepost = timelineObject.isPureRepost,
|
isPureRepost = timelineObject.isPureRepost,
|
||||||
lastUpdateAt = timelineObject.lastUpdatedAt,
|
lastUpdateAt = timelineObject.lastUpdatedAt,
|
||||||
hasMediaInRepost = timelineObject.hasMediaInRepost,
|
hasMediaInRepost = timelineObject.hasMediaInRepost,
|
||||||
warnFilter = warnFilter
|
warnFilter = warnFilter,
|
||||||
|
reactionsList = reactionsList,
|
||||||
|
favourited = favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.timelineobject
|
package dev.usbharu.hideout.core.domain.model.timelineobject
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.filter.FilterResult
|
import dev.usbharu.hideout.core.domain.model.filter.FilterResult
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
|
|
@ -28,12 +28,12 @@ class TimelineObject(
|
||||||
visibility: Visibility,
|
visibility: Visibility,
|
||||||
isPureRepost: Boolean,
|
isPureRepost: Boolean,
|
||||||
mediaIds: List<MediaId>,
|
mediaIds: List<MediaId>,
|
||||||
emojiIds: List<EmojiId>,
|
emojiIds: List<CustomEmojiId>,
|
||||||
visibleActors: List<ActorId>,
|
visibleActors: List<ActorId>,
|
||||||
hasMediaInRepost: Boolean,
|
hasMediaInRepost: Boolean,
|
||||||
lastUpdatedAt: Instant,
|
lastUpdatedAt: Instant,
|
||||||
var warnFilters: List<TimelineObjectWarnFilter>,
|
var warnFilters: List<TimelineObjectWarnFilter>,
|
||||||
|
var favourited: Boolean
|
||||||
) {
|
) {
|
||||||
var isPureRepost = isPureRepost
|
var isPureRepost = isPureRepost
|
||||||
private set
|
private set
|
||||||
|
|
@ -82,7 +82,8 @@ class TimelineObject(
|
||||||
timeline: Timeline,
|
timeline: Timeline,
|
||||||
post: Post,
|
post: Post,
|
||||||
replyActorId: ActorId?,
|
replyActorId: ActorId?,
|
||||||
filterResults: List<FilterResult>
|
filterResults: List<FilterResult>,
|
||||||
|
favourited: Boolean
|
||||||
): TimelineObject {
|
): TimelineObject {
|
||||||
return TimelineObject(
|
return TimelineObject(
|
||||||
id = timelineObjectId,
|
id = timelineObjectId,
|
||||||
|
|
@ -102,7 +103,8 @@ class TimelineObject(
|
||||||
visibleActors = post.visibleActors.toList(),
|
visibleActors = post.visibleActors.toList(),
|
||||||
hasMediaInRepost = false,
|
hasMediaInRepost = false,
|
||||||
lastUpdatedAt = Instant.now(),
|
lastUpdatedAt = Instant.now(),
|
||||||
warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) }
|
warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) },
|
||||||
|
favourited = favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +115,8 @@ class TimelineObject(
|
||||||
post: Post,
|
post: Post,
|
||||||
replyActorId: ActorId?,
|
replyActorId: ActorId?,
|
||||||
repost: Post,
|
repost: Post,
|
||||||
filterResults: List<FilterResult>
|
filterResults: List<FilterResult>,
|
||||||
|
favourited: Boolean
|
||||||
): TimelineObject {
|
): TimelineObject {
|
||||||
require(post.repostId == repost.id)
|
require(post.repostId == repost.id)
|
||||||
|
|
||||||
|
|
@ -138,7 +141,8 @@ class TimelineObject(
|
||||||
visibleActors = post.visibleActors.toList(),
|
visibleActors = post.visibleActors.toList(),
|
||||||
hasMediaInRepost = repost.mediaIds.isNotEmpty(),
|
hasMediaInRepost = repost.mediaIds.isNotEmpty(),
|
||||||
lastUpdatedAt = Instant.now(),
|
lastUpdatedAt = Instant.now(),
|
||||||
warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) }
|
warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) },
|
||||||
|
favourited = favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.service.emoji
|
||||||
|
|
||||||
|
interface UnicodeEmojiService {
|
||||||
|
fun isUnicodeEmoji(emoji: String): Boolean
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.emojikt
|
||||||
|
|
||||||
|
import Emojis
|
||||||
|
import dev.usbharu.hideout.core.domain.service.emoji.UnicodeEmojiService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class EmojiKtUnicodeEmojiService : UnicodeEmojiService {
|
||||||
|
override fun isUnicodeEmoji(emoji: String): Boolean {
|
||||||
|
return Emojis.allEmojis.singleOrNull { it.char == emoji } != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.*
|
import dev.usbharu.hideout.core.domain.model.actor.*
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||||
|
|
@ -57,7 +57,7 @@ class ActorResultRowMapper : ResultRowMapper<Actor> {
|
||||||
emojiIds = resultRow[Actors.emojis]
|
emojiIds = resultRow[Actors.emojis]
|
||||||
.split(",")
|
.split(",")
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.map { EmojiId(it.toLong()) }
|
.map { CustomEmojiId(it.toLong()) }
|
||||||
.toSet(),
|
.toSet(),
|
||||||
deleted = resultRow[Actors.deleted],
|
deleted = resultRow[Actors.deleted],
|
||||||
icon = resultRow[Actors.icon]?.let { MediaId(it) },
|
icon = resultRow[Actors.icon]?.let { MediaId(it) },
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposed
|
package dev.usbharu.hideout.core.infrastructure.exposed
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts
|
||||||
|
|
@ -55,7 +55,7 @@ class PostQueryMapper(private val postResultRowMapper: ResultRowMapper<Post>) :
|
||||||
.mapNotNull { resultRow: ResultRow ->
|
.mapNotNull { resultRow: ResultRow ->
|
||||||
resultRow
|
resultRow
|
||||||
.getOrNull(PostsEmojis.emojiId)
|
.getOrNull(PostsEmojis.emojiId)
|
||||||
?.let { emojiId -> EmojiId(emojiId) }
|
?.let { emojiId -> CustomEmojiId(emojiId) }
|
||||||
},
|
},
|
||||||
visibleActors = it.mapNotNull { resultRow: ResultRow ->
|
visibleActors = it.mapNotNull { resultRow: ResultRow ->
|
||||||
resultRow
|
resultRow
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.model.Reactions
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.CustomEmojis
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toCustomEmojiOrNull
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction
|
||||||
|
import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.net.URI
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions as ExposedrepositoryReactions
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class ExposedReactionsQueryService : ReactionsQueryService, AbstractRepository() {
|
||||||
|
override suspend fun findAllByPostId(postId: PostId): List<Reactions> {
|
||||||
|
return query {
|
||||||
|
ExposedrepositoryReactions.leftJoin(CustomEmojis).selectAll()
|
||||||
|
.where { ExposedrepositoryReactions.postId eq postId.id }
|
||||||
|
.groupBy {
|
||||||
|
it[ExposedrepositoryReactions.customEmojiId]?.toString()
|
||||||
|
?: it[ExposedrepositoryReactions.unicodeEmoji]
|
||||||
|
}
|
||||||
|
.map { it.value }
|
||||||
|
.map {
|
||||||
|
Reactions.of(
|
||||||
|
it.map { resultRow -> resultRow.toReaction() },
|
||||||
|
it.first().toCustomEmojiOrNull()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findAllByPostIdIn(postIds: List<PostId>): List<Reactions> {
|
||||||
|
return query {
|
||||||
|
val actorIdsQuery =
|
||||||
|
ExposedrepositoryReactions.actorId.castTo<String>(VarCharColumnType()).groupConcat(",", true)
|
||||||
|
|
||||||
|
ExposedrepositoryReactions.leftJoin(CustomEmojis)
|
||||||
|
.select(
|
||||||
|
ExposedrepositoryReactions.postId,
|
||||||
|
ExposedrepositoryReactions.postId.count(),
|
||||||
|
ExposedrepositoryReactions.customEmojiId.max(),
|
||||||
|
ExposedrepositoryReactions.unicodeEmoji.max<String, String>(),
|
||||||
|
actorIdsQuery
|
||||||
|
)
|
||||||
|
.where { ExposedrepositoryReactions.postId inList postIds.map { it.id } }
|
||||||
|
.groupBy(ExposedrepositoryReactions.postId)
|
||||||
|
.map {
|
||||||
|
Reactions(
|
||||||
|
it[ExposedrepositoryReactions.postId],
|
||||||
|
it[ExposedrepositoryReactions.postId.count()].toInt(),
|
||||||
|
it.getOrNull(CustomEmojis.name)
|
||||||
|
?: it[ExposedrepositoryReactions.unicodeEmoji.max<String, String>()]!!,
|
||||||
|
it.getOrNull(CustomEmojis.domain) ?: UnicodeEmoji.domain.domain,
|
||||||
|
it.getOrNull(CustomEmojis.url)?.let { it1 -> URI.create(it1) },
|
||||||
|
it[actorIdsQuery].split(",").mapNotNull { it.toLongOrNull() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val logger: Logger
|
||||||
|
get() = Companion.logger
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(ExposedReactionsQueryService::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,12 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi
|
||||||
.leftJoin(iconMedia, { Actors.icon }, { iconMedia[Media.id] })
|
.leftJoin(iconMedia, { Actors.icon }, { iconMedia[Media.id] })
|
||||||
.leftJoin(PostsMedia, { authorizedQuery[Posts.id] }, { PostsMedia.postId })
|
.leftJoin(PostsMedia, { authorizedQuery[Posts.id] }, { PostsMedia.postId })
|
||||||
.leftJoin(Media, { PostsMedia.mediaId }, { Media.id })
|
.leftJoin(Media, { PostsMedia.mediaId }, { Media.id })
|
||||||
|
.leftJoin(
|
||||||
|
Reactions,
|
||||||
|
{ authorizedQuery[Posts.id] },
|
||||||
|
{ Reactions.postId },
|
||||||
|
{ Reactions.id isDistinctFrom principal.actorId.id }
|
||||||
|
)
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { authorizedQuery[Posts.id] inList idList.map { it.id } }
|
.where { authorizedQuery[Posts.id] inList idList.map { it.id } }
|
||||||
.groupBy { it[authorizedQuery[Posts.id]] }
|
.groupBy { it[authorizedQuery[Posts.id]] }
|
||||||
|
|
@ -69,7 +75,8 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi
|
||||||
toPostDetail(it.first(), authorizedQuery, iconMedia).copy(
|
toPostDetail(it.first(), authorizedQuery, iconMedia).copy(
|
||||||
mediaDetailList = it.mapNotNull { resultRow ->
|
mediaDetailList = it.mapNotNull { resultRow ->
|
||||||
resultRow.toMediaOrNull()?.let { it1 -> MediaDetail.of(it1) }
|
resultRow.toMediaOrNull()?.let { it1 -> MediaDetail.of(it1) }
|
||||||
}
|
},
|
||||||
|
favourited = it.any { it.getOrNull(Reactions.actorId) != null }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +107,9 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi
|
||||||
sensitive = it[authorizedQuery[Posts.sensitive]],
|
sensitive = it[authorizedQuery[Posts.sensitive]],
|
||||||
deleted = it[authorizedQuery[Posts.deleted]],
|
deleted = it[authorizedQuery[Posts.deleted]],
|
||||||
mediaDetailList = emptyList(),
|
mediaDetailList = emptyList(),
|
||||||
moveTo = null
|
moveTo = null,
|
||||||
|
emptyList(),
|
||||||
|
favourited = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
@ -81,7 +81,7 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
|
fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
|
||||||
id = EmojiId(this[CustomEmojis.id]),
|
id = CustomEmojiId(this[CustomEmojis.id]),
|
||||||
name = this[CustomEmojis.name],
|
name = this[CustomEmojis.name],
|
||||||
domain = Domain(this[CustomEmojis.domain]),
|
domain = Domain(this[CustomEmojis.domain]),
|
||||||
instanceId = InstanceId(this[CustomEmojis.instanceId]),
|
instanceId = InstanceId(this[CustomEmojis.instanceId]),
|
||||||
|
|
@ -92,7 +92,7 @@ fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
|
||||||
|
|
||||||
fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? {
|
fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? {
|
||||||
return CustomEmoji(
|
return CustomEmoji(
|
||||||
id = EmojiId(this.getOrNull(CustomEmojis.id) ?: return null),
|
id = CustomEmojiId(this.getOrNull(CustomEmojis.id) ?: return null),
|
||||||
name = this.getOrNull(CustomEmojis.name) ?: return null,
|
name = this.getOrNull(CustomEmojis.name) ?: return null,
|
||||||
domain = Domain(this.getOrNull(CustomEmojis.domain) ?: return null),
|
domain = Domain(this.getOrNull(CustomEmojis.domain) ?: return null),
|
||||||
instanceId = InstanceId(this.getOrNull(CustomEmojis.instanceId) ?: return null),
|
instanceId = InstanceId(this.getOrNull(CustomEmojis.instanceId) ?: return null),
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ class ExposedPostRepository(
|
||||||
Posts.id eq id.id
|
Posts.id eq id.id
|
||||||
}
|
}
|
||||||
.let(postQueryMapper::map)
|
.let(postQueryMapper::map)
|
||||||
.first()
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findAllById(ids: List<PostId>): List<Post> {
|
override suspend fun findAllById(ids: List<PostId>): List<Post> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.ReactionId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository
|
||||||
|
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 ExposedReactionRepository(override val domainEventPublisher: DomainEventPublisher) :
|
||||||
|
ReactionRepository,
|
||||||
|
AbstractRepository(),
|
||||||
|
DomainEventPublishableRepository<Reaction> {
|
||||||
|
|
||||||
|
override val logger: Logger
|
||||||
|
get() = Companion.logger
|
||||||
|
|
||||||
|
override suspend fun save(reaction: Reaction): Reaction {
|
||||||
|
return query {
|
||||||
|
Reactions.upsert {
|
||||||
|
it[Reactions.id] = reaction.id.value
|
||||||
|
it[Reactions.postId] = reaction.postId.id
|
||||||
|
it[Reactions.actorId] = reaction.actorId.id
|
||||||
|
it[Reactions.customEmojiId] = reaction.customEmojiId?.emojiId
|
||||||
|
it[Reactions.unicodeEmoji] = reaction.unicodeEmoji.name
|
||||||
|
it[Reactions.createdAt] = reaction.createdAt
|
||||||
|
}
|
||||||
|
onComplete {
|
||||||
|
update(reaction)
|
||||||
|
}
|
||||||
|
reaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findById(reactionId: ReactionId): Reaction? {
|
||||||
|
return query {
|
||||||
|
Reactions.selectAll().where {
|
||||||
|
Reactions.id eq reactionId.value
|
||||||
|
}.singleOrNull()?.toReaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByPostId(postId: PostId): List<Reaction> {
|
||||||
|
return query {
|
||||||
|
Reactions.selectAll().where {
|
||||||
|
Reactions.postId eq postId.id
|
||||||
|
}.map { it.toReaction() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||||
|
postId: PostId,
|
||||||
|
actorId: ActorId,
|
||||||
|
customEmojiId: CustomEmojiId?,
|
||||||
|
unicodeEmoji: String
|
||||||
|
): Boolean {
|
||||||
|
return query {
|
||||||
|
Reactions.selectAll().where {
|
||||||
|
Reactions.postId.eq(postId.id).and(Reactions.actorId eq actorId.id)
|
||||||
|
.and(
|
||||||
|
(Reactions.customEmojiId eq customEmojiId?.emojiId or (Reactions.unicodeEmoji eq unicodeEmoji))
|
||||||
|
)
|
||||||
|
}.empty().not()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(reaction: Reaction) {
|
||||||
|
return query {
|
||||||
|
Reactions.deleteWhere {
|
||||||
|
Reactions.id eq reaction.id.value
|
||||||
|
}
|
||||||
|
onComplete {
|
||||||
|
update(reaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||||
|
postId: PostId,
|
||||||
|
actorId: ActorId,
|
||||||
|
customEmojiId: CustomEmojiId?,
|
||||||
|
unicodeEmoji: String
|
||||||
|
): Reaction? {
|
||||||
|
return query {
|
||||||
|
Reactions.selectAll().where {
|
||||||
|
Reactions.postId.eq(postId.id).and(Reactions.actorId eq actorId.id)
|
||||||
|
.and(
|
||||||
|
(Reactions.customEmojiId eq customEmojiId?.emojiId or (Reactions.unicodeEmoji eq unicodeEmoji))
|
||||||
|
)
|
||||||
|
}.limit(1).singleOrNull()?.toReaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(ExposedReactionRepository::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResultRow.toReaction(): Reaction {
|
||||||
|
return Reaction(
|
||||||
|
ReactionId(this[Reactions.id]),
|
||||||
|
PostId(this[Reactions.postId]),
|
||||||
|
ActorId(this[Reactions.actorId]),
|
||||||
|
this[Reactions.customEmojiId]?.let { CustomEmojiId(it) },
|
||||||
|
UnicodeEmoji(this[Reactions.unicodeEmoji]),
|
||||||
|
this[Reactions.createdAt]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Reactions : Table("reactions") {
|
||||||
|
val id = long("id")
|
||||||
|
val postId = long("post_id").references(Posts.id)
|
||||||
|
val actorId = long("actor_id").references(Actors.id)
|
||||||
|
val customEmojiId = long("custom_emoji_id").references(CustomEmojis.id).nullable()
|
||||||
|
val unicodeEmoji = varchar("unicode_emoji", 100)
|
||||||
|
val createdAt = timestamp("created_at")
|
||||||
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.mongorepository
|
package dev.usbharu.hideout.core.infrastructure.mongorepository
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.filter.FilterId
|
import dev.usbharu.hideout.core.domain.model.filter.FilterId
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
|
@ -133,7 +133,8 @@ data class SpringDataMongoTimelineObject(
|
||||||
val visibleActors: List<Long>,
|
val visibleActors: List<Long>,
|
||||||
val hasMediaInRepost: Boolean,
|
val hasMediaInRepost: Boolean,
|
||||||
val lastUpdatedAt: Long,
|
val lastUpdatedAt: Long,
|
||||||
val warnFilters: List<SpringDataMongoTimelineObjectWarnFilter>
|
val warnFilters: List<SpringDataMongoTimelineObjectWarnFilter>,
|
||||||
|
val favourited: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toTimelineObject(): TimelineObject {
|
fun toTimelineObject(): TimelineObject {
|
||||||
|
|
@ -151,11 +152,12 @@ data class SpringDataMongoTimelineObject(
|
||||||
visibility = visibility,
|
visibility = visibility,
|
||||||
isPureRepost = isPureRepost,
|
isPureRepost = isPureRepost,
|
||||||
mediaIds = mediaIds.map { MediaId(it) },
|
mediaIds = mediaIds.map { MediaId(it) },
|
||||||
emojiIds = emojiIds.map { EmojiId(it) },
|
emojiIds = emojiIds.map { CustomEmojiId(it) },
|
||||||
visibleActors = visibleActors.map { ActorId(it) },
|
visibleActors = visibleActors.map { ActorId(it) },
|
||||||
hasMediaInRepost = hasMediaInRepost,
|
hasMediaInRepost = hasMediaInRepost,
|
||||||
lastUpdatedAt = Instant.ofEpochSecond(lastUpdatedAt),
|
lastUpdatedAt = Instant.ofEpochSecond(lastUpdatedAt),
|
||||||
warnFilters = warnFilters.map { it.toTimelineObjectWarnFilter() }
|
warnFilters = warnFilters.map { it.toTimelineObjectWarnFilter() },
|
||||||
|
favourited = favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +181,8 @@ data class SpringDataMongoTimelineObject(
|
||||||
visibleActors = timelineObject.visibleActors.map { it.id },
|
visibleActors = timelineObject.visibleActors.map { it.id },
|
||||||
hasMediaInRepost = timelineObject.hasMediaInRepost,
|
hasMediaInRepost = timelineObject.hasMediaInRepost,
|
||||||
lastUpdatedAt = timelineObject.lastUpdatedAt.epochSecond,
|
lastUpdatedAt = timelineObject.lastUpdatedAt.epochSecond,
|
||||||
warnFilters = timelineObject.warnFilters.map { SpringDataMongoTimelineObjectWarnFilter.of(it) }
|
warnFilters = timelineObject.warnFilters.map { SpringDataMongoTimelineObjectWarnFilter.of(it) },
|
||||||
|
favourited = timelineObject.favourited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class SPAInterceptor : HandlerInterceptor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.session.getAttribute("s") == "f") {
|
if (request.getSession(false)?.getAttribute("s") == "f") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.timeline
|
package dev.usbharu.hideout.core.infrastructure.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.model.Reactions
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.filter.Filter
|
import dev.usbharu.hideout.core.domain.model.filter.Filter
|
||||||
|
|
@ -75,7 +76,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
post = post,
|
post = post,
|
||||||
replyActorId = replyActorId,
|
replyActorId = replyActorId,
|
||||||
repost = repost,
|
repost = repost,
|
||||||
filterResults = applyFilters.filterResults
|
filterResults = applyFilters.filterResults,
|
||||||
|
favourited = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +86,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
timeline = timeline,
|
timeline = timeline,
|
||||||
post = post,
|
post = post,
|
||||||
replyActorId = replyActorId,
|
replyActorId = replyActorId,
|
||||||
filterResults = applyFilters.filterResults
|
filterResults = applyFilters.filterResults,
|
||||||
|
favourited = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,6 +259,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
actors.mapNotNull { it.value.icon }
|
actors.mapNotNull { it.value.icon }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val reactions = getReactions(posts.map { it.id })
|
||||||
|
|
||||||
return PaginationList(
|
return PaginationList(
|
||||||
timelineObjectList.mapNotNull<TimelineObject, TimelineObjectDetail> {
|
timelineObjectList.mapNotNull<TimelineObject, TimelineObjectDetail> {
|
||||||
val timelineUserDetail = userDetails[it.userDetailId] ?: return@mapNotNull null
|
val timelineUserDetail = userDetails[it.userDetailId] ?: return@mapNotNull null
|
||||||
|
|
@ -268,6 +273,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
val repost = postMap[it.repostId]
|
val repost = postMap[it.repostId]
|
||||||
val repostMedias = repost?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
|
val repostMedias = repost?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
|
||||||
val repostActor = actors[it.repostActorId]
|
val repostActor = actors[it.repostActorId]
|
||||||
|
val reactionsList = reactions[it.postId].orEmpty()
|
||||||
TimelineObjectDetail.of(
|
TimelineObjectDetail.of(
|
||||||
timelineObject = it,
|
timelineObject = it,
|
||||||
timelineUserDetail = timelineUserDetail,
|
timelineUserDetail = timelineUserDetail,
|
||||||
|
|
@ -288,7 +294,9 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
filterResult.filter.id,
|
filterResult.filter.id,
|
||||||
filterResult.matchedKeyword
|
filterResult.matchedKeyword
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
reactionsList = reactionsList,
|
||||||
|
favourited = it.favourited
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
timelineObjectList.lastOrNull()?.postId,
|
timelineObjectList.lastOrNull()?.postId,
|
||||||
|
|
@ -300,5 +308,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
|
|
||||||
protected abstract suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media>
|
protected abstract suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media>
|
||||||
|
|
||||||
|
protected abstract suspend fun getReactions(postIds: List<PostId>): Map<PostId, List<Reactions>>
|
||||||
|
|
||||||
protected abstract suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail>
|
protected abstract suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.timeline
|
package dev.usbharu.hideout.core.infrastructure.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.model.Reactions
|
||||||
import dev.usbharu.hideout.core.config.DefaultTimelineStoreConfig
|
import dev.usbharu.hideout.core.config.DefaultTimelineStoreConfig
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
|
@ -32,6 +33,7 @@ import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService
|
||||||
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
||||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
|
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
|
||||||
|
import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
@ -49,7 +51,8 @@ open class DefaultTimelineStore(
|
||||||
private val userDetailRepository: UserDetailRepository,
|
private val userDetailRepository: UserDetailRepository,
|
||||||
private val actorRepository: ActorRepository,
|
private val actorRepository: ActorRepository,
|
||||||
private val mediaRepository: MediaRepository,
|
private val mediaRepository: MediaRepository,
|
||||||
private val postIPostReadAccessControl: IPostReadAccessControl
|
private val postIPostReadAccessControl: IPostReadAccessControl,
|
||||||
|
private val reactionsQueryService: ReactionsQueryService,
|
||||||
) : AbstractTimelineStore(idGenerateService) {
|
) : AbstractTimelineStore(idGenerateService) {
|
||||||
override suspend fun getTimelines(actorId: ActorId): List<Timeline> {
|
override suspend fun getTimelines(actorId: ActorId): List<Timeline> {
|
||||||
return timelineRepository.findByIds(
|
return timelineRepository.findByIds(
|
||||||
|
|
@ -157,6 +160,10 @@ open class DefaultTimelineStore(
|
||||||
override suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media> =
|
override suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media> =
|
||||||
mediaRepository.findByIds(mediaIds).associateBy { it.id }
|
mediaRepository.findByIds(mediaIds).associateBy { it.id }
|
||||||
|
|
||||||
|
override suspend fun getReactions(postIds: List<PostId>): Map<PostId, List<Reactions>> {
|
||||||
|
return reactionsQueryService.findAllByPostIdIn(postIds).groupBy { PostId(it.postId) }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail> =
|
override suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail> =
|
||||||
userDetailRepository.findAllById(userDetailIdList).associateBy { it.id }
|
userDetailRepository.findAllById(userDetailIdList).associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,25 @@ import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||||
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
||||||
import dev.usbharu.hideout.core.application.post.GetPostDetail
|
import dev.usbharu.hideout.core.application.post.GetPostDetail
|
||||||
import dev.usbharu.hideout.core.application.post.GetPostDetailApplicationService
|
import dev.usbharu.hideout.core.application.post.GetPostDetailApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.CreateReaction
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.RemoveReaction
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.UserCreateReactionApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.UserRemoveReactionApplicationService
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
||||||
import org.springframework.security.access.AccessDeniedException
|
import org.springframework.security.access.AccessDeniedException
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
import org.springframework.ui.Model
|
import org.springframework.ui.Model
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class PostsController(
|
class PostsController(
|
||||||
private val getPostDetailApplicationService: GetPostDetailApplicationService,
|
private val getPostDetailApplicationService: GetPostDetailApplicationService,
|
||||||
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
||||||
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService
|
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService,
|
||||||
|
private val userCreateReactionApplicationService: UserCreateReactionApplicationService,
|
||||||
|
private val userRemoveReactionApplicationService: UserRemoveReactionApplicationService
|
||||||
) {
|
) {
|
||||||
@GetMapping("/users/{name}/posts/{id}")
|
@GetMapping("/users/{name}/posts/{id}")
|
||||||
suspend fun postById(@PathVariable id: Long, model: Model): String {
|
suspend fun postById(@PathVariable id: Long, model: Model): String {
|
||||||
|
|
@ -31,4 +38,32 @@ class PostsController(
|
||||||
|
|
||||||
return "postById"
|
return "postById"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/{name}/posts/{id}/favourite")
|
||||||
|
suspend fun favourite(@PathVariable id: Long, @PathVariable name: String): String {
|
||||||
|
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||||
|
userCreateReactionApplicationService.execute(
|
||||||
|
CreateReaction(
|
||||||
|
id,
|
||||||
|
null,
|
||||||
|
"❤"
|
||||||
|
),
|
||||||
|
principal
|
||||||
|
)
|
||||||
|
return "redirect:/users/$name/posts/$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/{name}/posts/{id}/unfavourite")
|
||||||
|
suspend fun unfavourite(@PathVariable id: Long, @PathVariable name: String): String {
|
||||||
|
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||||
|
userRemoveReactionApplicationService.execute(
|
||||||
|
RemoveReaction(
|
||||||
|
id,
|
||||||
|
null,
|
||||||
|
"❤"
|
||||||
|
),
|
||||||
|
principal
|
||||||
|
)
|
||||||
|
return "redirect:/users/$name/posts/$id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package dev.usbharu.hideout.core.query.reactions
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.model.Reactions
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
|
||||||
|
interface ReactionsQueryService {
|
||||||
|
suspend fun findAllByPostId(postId: PostId): List<Reactions>
|
||||||
|
suspend fun findAllByPostIdIn(postIds: List<PostId>): List<Reactions>
|
||||||
|
}
|
||||||
|
|
@ -318,4 +318,17 @@ create table if not exists filter_keywords
|
||||||
keyword varchar(1000) not null,
|
keyword varchar(1000) not null,
|
||||||
mode varchar(100) not null,
|
mode varchar(100) not null,
|
||||||
constraint fk_filter_keywords_filter_id__id foreign key (filter_id) references filters (id) on delete cascade on update cascade
|
constraint fk_filter_keywords_filter_id__id foreign key (filter_id) references filters (id) on delete cascade on update cascade
|
||||||
)
|
);
|
||||||
|
|
||||||
|
create table if not exists reactions
|
||||||
|
(
|
||||||
|
id bigint primary key,
|
||||||
|
post_id bigint not null,
|
||||||
|
actor_id bigint not null,
|
||||||
|
custom_emoji_id bigint null,
|
||||||
|
unicode_emoji varchar(100) not null,
|
||||||
|
created_at timestamp not null,
|
||||||
|
constraint fk_reactions_post_id__id foreign key (post_id) references posts (id) on delete cascade on update cascade,
|
||||||
|
constraint fk_reactions_actor_id__id foreign key (actor_id) references actors (id) on delete cascade on update cascade,
|
||||||
|
unique (post_id, actor_id, created_at, unicode_emoji)
|
||||||
|
);
|
||||||
|
|
@ -44,10 +44,27 @@
|
||||||
|
|
||||||
<div class="post-controller" th:fragment="single-post-controller(post)">
|
<div class="post-controller" th:fragment="single-post-controller(post)">
|
||||||
<!--/*@thymesVar id="post" type="dev.usbharu.hideout.core.application.post.PostDetail"*/-->
|
<!--/*@thymesVar id="post" type="dev.usbharu.hideout.core.application.post.PostDetail"*/-->
|
||||||
<a th:href="${'/publish?reply_to=' + post.id}">Reply</a>
|
<th:block th:if="${post.favourited}">
|
||||||
<a th:href="${post.apId}">
|
<form method="post" th:action="@{/users/a/posts/{id}/unfavourite(id=${post.id})}">
|
||||||
<time th:datetime="${post.createdAt}" th:text="${#temporals.format(post.createdAt, 'yyyy-MM-dd HH:mm')}"></time>
|
<a th:href="${'/publish?reply_to=' + post.id}">Reply</a>
|
||||||
</a>
|
<input type="submit" value="[❤]">
|
||||||
|
<a th:href="${post.apId}">
|
||||||
|
<time th:datetime="${post.createdAt}"
|
||||||
|
th:text="${#temporals.format(post.createdAt, 'yyyy-MM-dd HH:mm')}"></time>
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</th:block>
|
||||||
|
<th:block th:unless="${post.favourited}">
|
||||||
|
<form method="post" th:action="@{/users/a/posts/{id}/favourite(id=${post.id})}">
|
||||||
|
<a th:href="${'/publish?reply_to=' + post.id}">Reply</a>
|
||||||
|
<input type="submit" value="❤">
|
||||||
|
<a th:href="${post.apId}">
|
||||||
|
<time th:datetime="${post.createdAt}"
|
||||||
|
th:text="${#temporals.format(post.createdAt, 'yyyy-MM-dd HH:mm')}"></time>
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.actor
|
package dev.usbharu.hideout.core.domain.model.actor
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||||
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
|
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
|
||||||
|
|
@ -35,7 +35,7 @@ object TestActorFactory {
|
||||||
suspend: Boolean = false,
|
suspend: Boolean = false,
|
||||||
alsoKnownAs: Set<ActorId> = emptySet(),
|
alsoKnownAs: Set<ActorId> = emptySet(),
|
||||||
moveTo: Long? = null,
|
moveTo: Long? = null,
|
||||||
emojiIds: Set<EmojiId> = emptySet(),
|
emojiIds: Set<CustomEmojiId> = emptySet(),
|
||||||
deleted: Boolean = false,
|
deleted: Boolean = false,
|
||||||
roles: Set<Role> = emptySet(),
|
roles: Set<Role> = emptySet(),
|
||||||
): Actor {
|
): Actor {
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,18 @@ package dev.usbharu.hideout.core.domain.model.emoji
|
||||||
import org.junit.jupiter.api.Assertions.assertDoesNotThrow
|
import org.junit.jupiter.api.Assertions.assertDoesNotThrow
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class EmojiIdTest {
|
class CustomEmojiIdTest {
|
||||||
@Test
|
@Test
|
||||||
fun emojiIdは0以上である必要がある() {
|
fun emojiIdは0以上である必要がある() {
|
||||||
org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
|
org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
|
||||||
EmojiId(-1)
|
CustomEmojiId(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun emojiIdは0以上なら設定できる() {
|
fun emojiIdは0以上なら設定できる() {
|
||||||
assertDoesNotThrow {
|
assertDoesNotThrow {
|
||||||
EmojiId(1)
|
CustomEmojiId(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import dev.usbharu.hideout.core.domain.event.post.PostEvent
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
|
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory
|
import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory
|
||||||
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
|
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
@ -447,7 +447,7 @@ class PostTest {
|
||||||
@Test
|
@Test
|
||||||
fun `emojiIds hideがtrueの時empty`() {
|
fun `emojiIds hideがtrueの時empty`() {
|
||||||
val actor = TestActorFactory.create()
|
val actor = TestActorFactory.create()
|
||||||
val emojiIds = listOf(EmojiId(1), EmojiId(2))
|
val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
|
||||||
val post = Post.create(
|
val post = Post.create(
|
||||||
id = PostId(1),
|
id = PostId(1),
|
||||||
actorId = actor.id,
|
actorId = actor.id,
|
||||||
|
|
@ -473,7 +473,7 @@ class PostTest {
|
||||||
@Test
|
@Test
|
||||||
fun `emojiIds hideがfalseの時中身が返される`() {
|
fun `emojiIds hideがfalseの時中身が返される`() {
|
||||||
val actor = TestActorFactory.create()
|
val actor = TestActorFactory.create()
|
||||||
val emojiIds = listOf(EmojiId(1), EmojiId(2))
|
val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
|
||||||
val post = Post.create(
|
val post = Post.create(
|
||||||
id = PostId(1),
|
id = PostId(1),
|
||||||
actorId = actor.id,
|
actorId = actor.id,
|
||||||
|
|
@ -500,7 +500,7 @@ class PostTest {
|
||||||
val post = TestPostFactory.create()
|
val post = TestPostFactory.create()
|
||||||
val mediaIds = listOf<MediaId>(MediaId(1))
|
val mediaIds = listOf<MediaId>(MediaId(1))
|
||||||
val visibleActors = setOf<ActorId>((ActorId(2)))
|
val visibleActors = setOf<ActorId>((ActorId(2)))
|
||||||
val emojis = listOf<EmojiId>(EmojiId(3))
|
val emojis = listOf<CustomEmojiId>(CustomEmojiId(3))
|
||||||
val reconstructWith = post.reconstructWith(mediaIds, emojis, visibleActors)
|
val reconstructWith = post.reconstructWith(mediaIds, emojis, visibleActors)
|
||||||
|
|
||||||
assertEquals(mediaIds, reconstructWith.mediaIds)
|
assertEquals(mediaIds, reconstructWith.mediaIds)
|
||||||
|
|
@ -511,7 +511,7 @@ class PostTest {
|
||||||
@Test
|
@Test
|
||||||
fun `mediaIds hideがtrueの時emptyが返される`() {
|
fun `mediaIds hideがtrueの時emptyが返される`() {
|
||||||
val actor = TestActorFactory.create()
|
val actor = TestActorFactory.create()
|
||||||
val emojiIds = listOf(EmojiId(1), EmojiId(2))
|
val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
|
||||||
val mediaIds = listOf(MediaId(1))
|
val mediaIds = listOf(MediaId(1))
|
||||||
val post = Post.create(
|
val post = Post.create(
|
||||||
id = PostId(1),
|
id = PostId(1),
|
||||||
|
|
@ -538,7 +538,7 @@ class PostTest {
|
||||||
@Test
|
@Test
|
||||||
fun `mediaIds hideがfalseの時中身が返される`() {
|
fun `mediaIds hideがfalseの時中身が返される`() {
|
||||||
val actor = TestActorFactory.create()
|
val actor = TestActorFactory.create()
|
||||||
val emojiIds = listOf(EmojiId(1), EmojiId(2))
|
val emojiIds = listOf(CustomEmojiId(1), CustomEmojiId(2))
|
||||||
val mediaIds = listOf(MediaId(2))
|
val mediaIds = listOf(MediaId(2))
|
||||||
val post = Post.create(
|
val post = Post.create(
|
||||||
id = PostId(1),
|
id = PostId(1),
|
||||||
|
|
@ -603,7 +603,7 @@ class PostTest {
|
||||||
fun `restore 指定された引数で再構成されCHECKUPDATEイベントが発生する`() {
|
fun `restore 指定された引数で再構成されCHECKUPDATEイベントが発生する`() {
|
||||||
val post = TestPostFactory.create(deleted = true)
|
val post = TestPostFactory.create(deleted = true)
|
||||||
|
|
||||||
val postContent = PostContent("aiueo", "aiueo", listOf(EmojiId(1)))
|
val postContent = PostContent("aiueo", "aiueo", listOf(CustomEmojiId(1)))
|
||||||
val overview = PostOverview("overview")
|
val overview = PostOverview("overview")
|
||||||
val mediaIds = listOf(MediaId(1))
|
val mediaIds = listOf(MediaId(1))
|
||||||
post.restore(
|
post.restore(
|
||||||
|
|
@ -622,7 +622,7 @@ class PostTest {
|
||||||
fun deletedがfalseの時失敗する() {
|
fun deletedがfalseの時失敗する() {
|
||||||
val post = TestPostFactory.create(deleted = false)
|
val post = TestPostFactory.create(deleted = false)
|
||||||
|
|
||||||
val postContent = PostContent("aiueo", "aiueo", listOf(EmojiId(1)))
|
val postContent = PostContent("aiueo", "aiueo", listOf(CustomEmojiId(1)))
|
||||||
val overview = PostOverview("overview")
|
val overview = PostOverview("overview")
|
||||||
val mediaIds = listOf(MediaId(1))
|
val mediaIds = listOf(MediaId(1))
|
||||||
assertThrows<IllegalArgumentException> {
|
assertThrows<IllegalArgumentException> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.emojikt
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource
|
||||||
|
|
||||||
|
class EmojiKtUnicodeEmojiServiceTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = ["❤", "👱", "☠️", "⁉️"])
|
||||||
|
fun 絵文字の判定ができる(s: String) {
|
||||||
|
assertTrue(EmojiKtUnicodeEmojiService().isUnicodeEmoji(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,10 @@ package dev.usbharu.hideout.mastodon.interfaces.api
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.application.post.RegisterLocalPost
|
import dev.usbharu.hideout.core.application.post.RegisterLocalPost
|
||||||
import dev.usbharu.hideout.core.application.post.RegisterLocalPostApplicationService
|
import dev.usbharu.hideout.core.application.post.RegisterLocalPostApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.CreateReaction
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.RemoveReaction
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.UserCreateReactionApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.reaction.UserRemoveReactionApplicationService
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SpringSecurityOauth2PrincipalContextHolder
|
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SpringSecurityOauth2PrincipalContextHolder
|
||||||
import dev.usbharu.hideout.mastodon.application.status.GetStatus
|
import dev.usbharu.hideout.mastodon.application.status.GetStatus
|
||||||
|
|
@ -32,7 +36,9 @@ import org.springframework.stereotype.Controller
|
||||||
class SpringStatusApi(
|
class SpringStatusApi(
|
||||||
private val registerLocalPostApplicationService: RegisterLocalPostApplicationService,
|
private val registerLocalPostApplicationService: RegisterLocalPostApplicationService,
|
||||||
private val getStatusApplicationService: GetStatusApplicationService,
|
private val getStatusApplicationService: GetStatusApplicationService,
|
||||||
private val principalContextHolder: SpringSecurityOauth2PrincipalContextHolder
|
private val principalContextHolder: SpringSecurityOauth2PrincipalContextHolder,
|
||||||
|
private val userCreateReactionApplicationService: UserCreateReactionApplicationService,
|
||||||
|
private val userRemoveReactionApplicationService: UserRemoveReactionApplicationService
|
||||||
) : StatusApi {
|
) : StatusApi {
|
||||||
override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> =
|
override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> =
|
||||||
super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji)
|
super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji)
|
||||||
|
|
@ -49,6 +55,20 @@ class SpringStatusApi(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1StatusesIdFavouritePost(id: String): ResponseEntity<Status> {
|
||||||
|
val principal = principalContextHolder.getPrincipal()
|
||||||
|
|
||||||
|
userCreateReactionApplicationService.execute(CreateReaction(postId = id.toLong(), null, "❤"), principal)
|
||||||
|
return ResponseEntity.ok(getStatusApplicationService.execute(GetStatus(id), principal))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1StatusesIdUnfavouritePost(id: String): ResponseEntity<Status> {
|
||||||
|
val principal = principalContextHolder.getPrincipal()
|
||||||
|
|
||||||
|
userRemoveReactionApplicationService.execute(RemoveReaction(postId = id.toLong(), null, "❤"), principal)
|
||||||
|
return ResponseEntity.ok(getStatusApplicationService.execute(GetStatus(id), principal))
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
|
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
|
||||||
val principal = principalContextHolder.getPrincipal()
|
val principal = principalContextHolder.getPrincipal()
|
||||||
val execute = registerLocalPostApplicationService.execute(
|
val execute = registerLocalPostApplicationService.execute(
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,48 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Status"
|
$ref: "#/components/schemas/Status"
|
||||||
|
|
||||||
|
/api/v1/statuses/{id}/favourite:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- status
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:favourites"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Status"
|
||||||
|
|
||||||
|
/api/v1/statuses/{id}/unfavourite:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- status
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:favourites"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Status"
|
||||||
|
|
||||||
/api/v1/statuses/{id}/emoji_reactions/{emoji}:
|
/api/v1/statuses/{id}/emoji_reactions/{emoji}:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue