mirror of https://github.com/usbharu/Hideout.git
feat: リアクションしたかを確認できるように
This commit is contained in:
parent
e42919ce3c
commit
d4aaad3fb6
|
@ -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<GetPostDetail, PostDetail>(
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ data class PostDetail(
|
|||
val deleted: Boolean,
|
||||
val mediaDetailList: List<MediaDetail>,
|
||||
val moveTo: PostDetail?,
|
||||
val reactionsList: List<Reactions>
|
||||
val reactionsList: List<Reactions>,
|
||||
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<Reactions>
|
||||
reactionsList: List<Reactions>,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Reaction>
|
||||
suspend fun existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
|
||||
postId: PostId,
|
||||
actorId: ActorId,
|
||||
customEmojiId: CustomEmojiId?,
|
||||
unicodeEmoji: String
|
||||
): Boolean
|
||||
|
||||
suspend fun delete(reaction: Reaction)
|
||||
}
|
|
@ -31,7 +31,8 @@ data class TimelineObjectDetail(
|
|||
val lastUpdateAt: Instant,
|
||||
val hasMediaInRepost: Boolean,
|
||||
val warnFilter: List<TimelineObjectWarnFilter>,
|
||||
val reactionsList: List<Reactions>
|
||||
val reactionsList: List<Reactions>,
|
||||
val favourited: Boolean
|
||||
) {
|
||||
companion object {
|
||||
@Suppress("LongParameterList")
|
||||
|
@ -51,7 +52,8 @@ data class TimelineObjectDetail(
|
|||
repostPostActor: Actor?,
|
||||
repostPostActorIconMedia: Media?,
|
||||
warnFilter: List<TimelineObjectWarnFilter>,
|
||||
reactionsList: List<Reactions>
|
||||
reactionsList: List<Reactions>,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ class TimelineObject(
|
|||
hasMediaInRepost: Boolean,
|
||||
lastUpdatedAt: Instant,
|
||||
var warnFilters: List<TimelineObjectWarnFilter>,
|
||||
|
||||
) {
|
||||
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<FilterResult>
|
||||
filterResults: List<FilterResult>,
|
||||
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<FilterResult>
|
||||
filterResults: List<FilterResult>,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ class ExposedReactionsQueryService : ReactionsQueryService, AbstractRepository()
|
|||
ExposedrepositoryReactions.postId,
|
||||
ExposedrepositoryReactions.postId.count(),
|
||||
ExposedrepositoryReactions.customEmojiId.max(),
|
||||
ExposedrepositoryReactions.unicodeEmoji.max(),
|
||||
ExposedrepositoryReactions.unicodeEmoji.max<String, String>(),
|
||||
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<String, String>()]!!,
|
||||
it.getOrNull(CustomEmojis.domain) ?: UnicodeEmoji.domain.domain,
|
||||
it.getOrNull(CustomEmojis.url)?.let { it1 -> URI.create(it1) },
|
||||
it[actorIdsQuery].split(",").mapNotNull { it.toLongOrNull() }
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -133,7 +133,8 @@ data class SpringDataMongoTimelineObject(
|
|||
val visibleActors: List<Long>,
|
||||
val hasMediaInRepost: Boolean,
|
||||
val lastUpdatedAt: Long,
|
||||
val warnFilters: List<SpringDataMongoTimelineObjectWarnFilter>
|
||||
val warnFilters: List<SpringDataMongoTimelineObjectWarnFilter>,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,10 +44,14 @@
|
|||
|
||||
<div class="post-controller" th:fragment="single-post-controller(post)">
|
||||
<!--/*@thymesVar id="post" type="dev.usbharu.hideout.core.application.post.PostDetail"*/-->
|
||||
<a th:href="${'/publish?reply_to=' + post.id}">Reply</a>
|
||||
<a th:href="${post.apId}">
|
||||
<time th:datetime="${post.createdAt}" th:text="${#temporals.format(post.createdAt, 'yyyy-MM-dd HH:mm')}"></time>
|
||||
</a>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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" }
|
||||
|
||||
|
|
Loading…
Reference in New Issue