diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt index a9abd30a..278c0afe 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt @@ -9,6 +9,7 @@ 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.post.PostId 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.service.post.IPostReadAccessControl import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService @@ -23,6 +24,7 @@ class GetPostDetailApplicationService( private val mediaRepository: MediaRepository, private val iPostReadAccessControl: IPostReadAccessControl, private val reactionsQueryService: ReactionsQueryService, + private val reactionRepository: ReactionRepository, ) : AbstractApplicationService( transaction, logger @@ -42,6 +44,13 @@ class GetPostDetailApplicationService( val reactions = reactionsQueryService.findAllByPostId(post.id) + val favourited = reactionRepository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + post.id, + principal.actorId, + null, + "❤" + ) + return PostDetail.of( post = post, actor = actor, @@ -50,7 +59,8 @@ class GetPostDetailApplicationService( reply = post.replyId?.let { fetchChild(it, actor, iconMedia, principal) }, repost = post.repostId?.let { fetchChild(it, actor, iconMedia, principal) }, moveTo = post.moveTo?.let { fetchChild(it, actor, iconMedia, principal) }, - reactionsList = reactions + reactionsList = reactions, + favourited ) } @@ -78,7 +88,8 @@ class GetPostDetailApplicationService( actor = first, iconMedia = third, mediaList = mediaList, - reactionsList = emptyList() + reactionsList = emptyList(), + favourited = false ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/PostDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/PostDetail.kt index 3cee2e7c..67dd9d96 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/PostDetail.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/PostDetail.kt @@ -25,7 +25,8 @@ data class PostDetail( val deleted: Boolean, val mediaDetailList: List, val moveTo: PostDetail?, - val reactionsList: List + val reactionsList: List, + val favourited: Boolean ) { companion object { @Suppress("LongParameterList") @@ -37,7 +38,8 @@ data class PostDetail( reply: PostDetail? = null, repost: PostDetail? = null, moveTo: PostDetail? = null, - reactionsList: List + reactionsList: List, + favourited: Boolean ): PostDetail { return PostDetail( id = post.id.id, @@ -56,7 +58,8 @@ data class PostDetail( deleted = false, mediaDetailList = mediaList.map { MediaDetail.of(it) }, moveTo = moveTo, - reactionsList = reactionsList + reactionsList = reactionsList, + favourited = favourited ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/reaction/CreateReaction.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/reaction/CreateReaction.kt new file mode 100644 index 00000000..7d6f171b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/reaction/CreateReaction.kt @@ -0,0 +1,3 @@ +package dev.usbharu.hideout.core.application.reaction + +data class CreateReaction(val postId: Long, val customEmojiId: Long?, val unicodeEmoji: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/reaction/UserCreateReactionApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/reaction/UserCreateReactionApplicationService.kt new file mode 100644 index 00000000..1ca68aef --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/reaction/UserCreateReactionApplicationService.kt @@ -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( + 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) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/ReadTimelineApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/ReadTimelineApplicationService.kt index 129aae7a..8b045657 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/ReadTimelineApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/ReadTimelineApplicationService.kt @@ -49,6 +49,7 @@ class ReadTimelineApplicationService( it.replyPostActorIconMedia, it.replyPostMedias.orEmpty(), reactionsList = emptyList(), + favourited = false, ) } else { null @@ -61,7 +62,8 @@ class ReadTimelineApplicationService( actor = it.repostPostActor!!, iconMedia = it.repostPostActorIconMedia, mediaList = it.repostPostMedias.orEmpty(), - reactionsList = emptyList() + reactionsList = emptyList(), + favourited = false ) } else { null @@ -74,7 +76,8 @@ class ReadTimelineApplicationService( mediaList = it.postMedias, reply = reply, repost = repost, - reactionsList = emptyList() + reactionsList = emptyList(), + favourited = it.favourited ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt index 29acd189..f2ed4779 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt @@ -1,10 +1,19 @@ 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 + suspend fun existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + postId: PostId, + actorId: ActorId, + customEmojiId: CustomEmojiId?, + unicodeEmoji: String + ): Boolean + suspend fun delete(reaction: Reaction) } \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/timelineobjectdetail/TimelineObjectDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/timelineobjectdetail/TimelineObjectDetail.kt index a0727f4f..43b0fa9f 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/timelineobjectdetail/TimelineObjectDetail.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/timelineobjectdetail/TimelineObjectDetail.kt @@ -31,7 +31,8 @@ data class TimelineObjectDetail( val lastUpdateAt: Instant, val hasMediaInRepost: Boolean, val warnFilter: List, - val reactionsList: List + val reactionsList: List, + val favourited: Boolean ) { companion object { @Suppress("LongParameterList") @@ -51,7 +52,8 @@ data class TimelineObjectDetail( repostPostActor: Actor?, repostPostActorIconMedia: Media?, warnFilter: List, - reactionsList: List + reactionsList: List, + favourited: Boolean ): TimelineObjectDetail { return TimelineObjectDetail( id = timelineObject.id, @@ -73,7 +75,8 @@ data class TimelineObjectDetail( lastUpdateAt = timelineObject.lastUpdatedAt, hasMediaInRepost = timelineObject.hasMediaInRepost, warnFilter = warnFilter, - reactionsList = reactionsList + reactionsList = reactionsList, + favourited = favourited ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObject.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObject.kt index 73d3e87d..a6fbe671 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObject.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObject.kt @@ -33,8 +33,8 @@ class TimelineObject( hasMediaInRepost: Boolean, lastUpdatedAt: Instant, var warnFilters: List, - - ) { + var favourited: Boolean +) { var isPureRepost = isPureRepost private set var visibleActors = visibleActors @@ -61,9 +61,9 @@ class TimelineObject( lastUpdatedAt = Instant.now() isPureRepost = post.repostId != null && - post.replyId == null && - post.text.isEmpty() && - post.overview?.overview.isNullOrEmpty() + post.replyId == null && + post.text.isEmpty() && + post.overview?.overview.isNullOrEmpty() warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) } } @@ -82,7 +82,8 @@ class TimelineObject( timeline: Timeline, post: Post, replyActorId: ActorId?, - filterResults: List + filterResults: List, + favourited: Boolean ): TimelineObject { return TimelineObject( id = timelineObjectId, @@ -102,7 +103,8 @@ class TimelineObject( visibleActors = post.visibleActors.toList(), hasMediaInRepost = false, 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, replyActorId: ActorId?, repost: Post, - filterResults: List + filterResults: List, + favourited: Boolean ): TimelineObject { require(post.repostId == repost.id) @@ -130,15 +133,16 @@ class TimelineObject( repostActorId = repost.actorId, visibility = post.visibility, isPureRepost = repost.mediaIds.isEmpty() && - repost.overview == null && - repost.content == PostContent.empty && - repost.replyId == null, + repost.overview == null && + repost.content == PostContent.empty && + repost.replyId == null, mediaIds = post.mediaIds, emojiIds = post.emojiIds, visibleActors = post.visibleActors.toList(), hasMediaInRepost = repost.mediaIds.isNotEmpty(), lastUpdatedAt = Instant.now(), - warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) } + warnFilters = filterResults.map { TimelineObjectWarnFilter(it.filter.id, it.matchedKeyword) }, + favourited = favourited ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/emoji/UnicodeEmojiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/emoji/UnicodeEmojiService.kt new file mode 100644 index 00000000..add7bdbe --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/emoji/UnicodeEmojiService.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.core.domain.service.emoji + +interface UnicodeEmojiService { + fun isUnicodeEmoji(emoji: String): Boolean +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/emojikt/EmojiKtUnicodeEmojiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/emojikt/EmojiKtUnicodeEmojiService.kt new file mode 100644 index 00000000..3eef34aa --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/emojikt/EmojiKtUnicodeEmojiService.kt @@ -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 + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedReactionsQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedReactionsQueryService.kt index 4a779905..b4d7705f 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedReactionsQueryService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedReactionsQueryService.kt @@ -46,7 +46,7 @@ class ExposedReactionsQueryService : ReactionsQueryService, AbstractRepository() ExposedrepositoryReactions.postId, ExposedrepositoryReactions.postId.count(), ExposedrepositoryReactions.customEmojiId.max(), - ExposedrepositoryReactions.unicodeEmoji.max(), + ExposedrepositoryReactions.unicodeEmoji.max(), actorIdsQuery ) .where { ExposedrepositoryReactions.postId inList postIds.map { it.id } } @@ -55,7 +55,8 @@ class ExposedReactionsQueryService : ReactionsQueryService, AbstractRepository() Reactions( it[ExposedrepositoryReactions.postId], it[ExposedrepositoryReactions.postId.count()].toInt(), - it.getOrNull(CustomEmojis.name) ?: it[ExposedrepositoryReactions.unicodeEmoji], + it.getOrNull(CustomEmojis.name) + ?: it[ExposedrepositoryReactions.unicodeEmoji.max()]!!, it.getOrNull(CustomEmojis.domain) ?: UnicodeEmoji.domain.domain, it.getOrNull(CustomEmojis.url)?.let { it1 -> URI.create(it1) }, it[actorIdsQuery].split(",").mapNotNull { it.toLongOrNull() } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt index 99c5cee4..6b4338fb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt @@ -42,10 +42,10 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi .select(Posts.columns) .where { Posts.visibility eq Visibility.PUBLIC.name or - (Posts.visibility eq Visibility.UNLISTED.name) or - (Posts.visibility eq Visibility.DIRECT.name and (PostsVisibleActors.actorId eq principal.actorId.id)) or - (Posts.visibility eq Visibility.FOLLOWERS.name and (Relationships.blocking eq false and (relationshipsAlias[Relationships.following] eq true))) or - (Posts.actorId eq principal.actorId.id) + (Posts.visibility eq Visibility.UNLISTED.name) or + (Posts.visibility eq Visibility.DIRECT.name and (PostsVisibleActors.actorId eq principal.actorId.id)) or + (Posts.visibility eq Visibility.FOLLOWERS.name and (Relationships.blocking eq false and (relationshipsAlias[Relationships.following] eq true))) or + (Posts.actorId eq principal.actorId.id) } .alias("authorized_table") } @@ -61,6 +61,10 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi .leftJoin(iconMedia, { Actors.icon }, { iconMedia[Media.id] }) .leftJoin(PostsMedia, { authorizedQuery[Posts.id] }, { PostsMedia.postId }) .leftJoin(Media, { PostsMedia.mediaId }, { Media.id }) + .leftJoin(Reactions, + { authorizedQuery[Posts.id] }, + { Reactions.postId }, + { Reactions.id isDistinctFrom principal.actorId.id }) .selectAll() .where { authorizedQuery[Posts.id] inList idList.map { it.id } } .groupBy { it[authorizedQuery[Posts.id]] } @@ -69,7 +73,7 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi toPostDetail(it.first(), authorizedQuery, iconMedia).copy( mediaDetailList = it.mapNotNull { resultRow -> resultRow.toMediaOrNull()?.let { it1 -> MediaDetail.of(it1) } - } + }, favourited = it.any { it.getOrNull(Reactions.actorId) != null } ) } } @@ -101,7 +105,8 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi deleted = it[authorizedQuery[Posts.deleted]], mediaDetailList = emptyList(), moveTo = null, - emptyList() + emptyList(), + favourited = false ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepository.kt index 0b376bd0..f3ac7b96 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepository.kt @@ -32,6 +32,7 @@ class ExposedReactionRepository(override val domainEventPublisher: DomainEventPu 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) @@ -56,6 +57,20 @@ class ExposedReactionRepository(override val domainEventPublisher: DomainEventPu } } + 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 { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt index c0cf2472..2e66bbbd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt @@ -133,7 +133,8 @@ data class SpringDataMongoTimelineObject( val visibleActors: List, val hasMediaInRepost: Boolean, val lastUpdatedAt: Long, - val warnFilters: List + val warnFilters: List, + val favourited: Boolean ) { fun toTimelineObject(): TimelineObject { @@ -155,7 +156,8 @@ data class SpringDataMongoTimelineObject( visibleActors = visibleActors.map { ActorId(it) }, hasMediaInRepost = hasMediaInRepost, 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 }, hasMediaInRepost = timelineObject.hasMediaInRepost, lastUpdatedAt = timelineObject.lastUpdatedAt.epochSecond, - warnFilters = timelineObject.warnFilters.map { SpringDataMongoTimelineObjectWarnFilter.of(it) } + warnFilters = timelineObject.warnFilters.map { SpringDataMongoTimelineObjectWarnFilter.of(it) }, + favourited = timelineObject.favourited ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt index 9032a2ac..9ddcc131 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt @@ -76,7 +76,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe post = post, replyActorId = replyActorId, repost = repost, - filterResults = applyFilters.filterResults + filterResults = applyFilters.filterResults, + favourited = false ) } @@ -85,7 +86,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe timeline = timeline, post = post, replyActorId = replyActorId, - filterResults = applyFilters.filterResults + filterResults = applyFilters.filterResults, + favourited = false ) } @@ -293,7 +295,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe filterResult.matchedKeyword ) }, - reactionsList = reactionsList + reactionsList = reactionsList, + favourited = it.favourited ) }, timelineObjectList.lastOrNull()?.postId, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PostsController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PostsController.kt index 4dda4759..0f6558c6 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PostsController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PostsController.kt @@ -4,18 +4,22 @@ import dev.usbharu.hideout.core.application.exception.PermissionDeniedException import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService import dev.usbharu.hideout.core.application.post.GetPostDetail import dev.usbharu.hideout.core.application.post.GetPostDetailApplicationService +import dev.usbharu.hideout.core.application.reaction.CreateReaction +import dev.usbharu.hideout.core.application.reaction.UserCreateReactionApplicationService import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder import org.springframework.security.access.AccessDeniedException import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping @Controller class PostsController( private val getPostDetailApplicationService: GetPostDetailApplicationService, private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder, - private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService + private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService, + private val userCreateReactionApplicationService: UserCreateReactionApplicationService ) { @GetMapping("/users/{name}/posts/{id}") suspend fun postById(@PathVariable id: Long, model: Model): String { @@ -31,4 +35,17 @@ class PostsController( 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" + } } diff --git a/hideout-core/src/main/resources/templates/fragments-post.html b/hideout-core/src/main/resources/templates/fragments-post.html index e55f55be..671cec83 100644 --- a/hideout-core/src/main/resources/templates/fragments-post.html +++ b/hideout-core/src/main/resources/templates/fragments-post.html @@ -44,10 +44,14 @@
- Reply - - - +
+ Reply + + + + +
diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/emojikt/EmojiKtUnicodeEmojiServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/emojikt/EmojiKtUnicodeEmojiServiceTest.kt new file mode 100644 index 00000000..f589be2c --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/emojikt/EmojiKtUnicodeEmojiServiceTest.kt @@ -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)) + } + + +} \ No newline at end of file diff --git a/libs.versions.toml b/libs.versions.toml index 88c0adf6..e698a5e4 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -103,7 +103,7 @@ mongodb-kotlin-coroutine = { module = "org.mongodb:mongodb-driver-kotlin-corouti mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.4.0" } http-signature = { module = "dev.usbharu:http-signature", version = "1.0.0" } -emoji-kt = { module = "dev.usbharu:emoji-kt", version = "2.0.0" } +emoji-kt = { module = "dev.usbharu:emoji-kt", version = "2.0.1" } logback-ecs-encoder = { module = "co.elastic.logging:logback-ecs-encoder", version = "1.6.0" }