feat: 絵文字リアクションAPIを修正

This commit is contained in:
usbharu 2024-01-08 17:06:09 +09:00
parent a5618e3307
commit f06961b0bd
5 changed files with 110 additions and 4 deletions

View File

@ -1,7 +1,15 @@
package mastodon.status
import dev.usbharu.hideout.SpringApplication
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
import dev.usbharu.hideout.core.infrastructure.exposedrepository.CustomEmojis
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.Flyway
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -13,19 +21,24 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.test.context.support.WithAnonymousUser
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers
import org.springframework.test.context.jdbc.Sql
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.post
import org.springframework.test.web.servlet.put
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.context.WebApplicationContext
import java.time.Instant
@SpringBootTest(classes = [SpringApplication::class])
@AutoConfigureMockMvc
@Transactional
@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql("/sql/test-post.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql("/sql/test-custom-emoji.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
class StatusTest {
@Autowired
@ -124,7 +137,6 @@ class StatusTest {
}
@Test
@Sql("/sql/test-post.sql")
fun in_reply_to_idを指定したら返信として処理される() {
mockMvc
.post("/api/v1/statuses") {
@ -145,6 +157,64 @@ class StatusTest {
.andExpect { jsonPath("\$.in_reply_to_id") { value("1") } }
}
@Test
fun ユニコード絵文字をリアクションできる() {
mockMvc
.put("/api/v1/statuses/1/emoji_reactions/😭") {
with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")))
}
.andDo { print() }
.asyncDispatch()
.andExpect { status { isOk() } }
val reaction = Reactions.select { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction()
assertThat(reaction.emoji).isEqualTo(UnicodeEmoji("😭"))
assertThat(reaction.postId).isEqualTo(1)
assertThat(reaction.actorId).isEqualTo(1)
}
@Test
fun 存在しない絵文字はフォールバックされる() {
mockMvc
.put("/api/v1/statuses/1/emoji_reactions/hoge") {
with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")))
}
.andDo { print() }
.asyncDispatch()
.andExpect { status { isOk() } }
val reaction = Reactions.select { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction()
assertThat(reaction.emoji).isEqualTo(UnicodeEmoji(""))
assertThat(reaction.postId).isEqualTo(1)
assertThat(reaction.actorId).isEqualTo(1)
}
@Test
fun カスタム絵文字をリアクションできる() {
mockMvc
.put("/api/v1/statuses/1/emoji_reactions/kotlin") {
with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")))
}
.andDo { print() }
.asyncDispatch()
.andExpect { status { isOk() } }
val reaction =
Reactions.leftJoin(CustomEmojis).select { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single()
.toReaction()
assertThat(reaction.emoji).isEqualTo(
CustomEmoji(
1,
"kotlin",
"example.com",
null,
"https://example.com/emojis/kotlin",
null,
Instant.ofEpochMilli(1704700290036)
)
)
}
companion object {
@JvmStatic
@AfterAll

View File

@ -0,0 +1,3 @@
insert into emojis(id, name, domain, instance_id, url, category, created_at)
VALUES (1, 'kotlin', 'example.com', null, 'https://example.com/emojis/kotlin', null,
TIMESTAMP '2024-01-08 16:51:30.036');

View File

@ -74,6 +74,18 @@ fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
this[CustomEmojis.createdAt]
)
fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? {
return CustomEmoji(
id = this.getOrNull(CustomEmojis.id) ?: return null,
name = this.getOrNull(CustomEmojis.name) ?: return null,
domain = this.getOrNull(CustomEmojis.domain) ?: return null,
instanceId = this[CustomEmojis.instanceId],
url = this.getOrNull(CustomEmojis.url) ?: return null,
category = this[CustomEmojis.category],
createdAt = this.getOrNull(CustomEmojis.createdAt) ?: return null
)
}
object CustomEmojis : Table("emojis") {
val id = long("id")
val name = varchar("name", 1000)

View File

@ -109,7 +109,22 @@ class StatusQueryServiceImpl : StatusQueryService {
}
override suspend fun findByPostId(id: Long): Status {
TODO("Not yet implemented")
val map = Posts
.leftJoin(PostsMedia)
.leftJoin(Actors)
.leftJoin(Media)
.select { Posts.id eq id }
.groupBy { it[Posts.id] }
.map { it.value }
.map {
toStatus(it.first()).copy(
mediaAttachments = it.mapNotNull { resultRow ->
resultRow.toMediaOrNull()?.toMediaAttachments()
},
emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() }
) to it.first()[Posts.repostId]
}
return resolveReplyAndRepost(map).single()
}
private fun resolveReplyAndRepost(pairs: List<Pair<Status, Long?>>): List<Status> {

View File

@ -26,11 +26,17 @@ class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiSe
}
override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> {
return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji)
val uid =
(SecurityContextHolder.getContext().authentication.principal as Jwt).getClaim<String>("uid").toLong()
return ResponseEntity.ok(statusesApiService.removeEmojiReactions(id.toLong(), uid, emoji))
}
override suspend fun apiV1StatusesIdEmojiReactionsEmojiPut(id: String, emoji: String): ResponseEntity<Status> {
return super.apiV1StatusesIdEmojiReactionsEmojiPut(id, emoji)
val uid =
(SecurityContextHolder.getContext().authentication.principal as Jwt).getClaim<String>("uid").toLong()
return ResponseEntity.ok(statusesApiService.emojiReactions(id.toLong(), uid, emoji))
}
override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity<Status> {