mirror of https://github.com/usbharu/Hideout.git
feat: 絵文字リアクションAPIを修正
This commit is contained in:
parent
a5618e3307
commit
f06961b0bd
|
@ -1,7 +1,15 @@
|
||||||
package mastodon.status
|
package mastodon.status
|
||||||
|
|
||||||
import dev.usbharu.hideout.SpringApplication
|
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.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.AfterAll
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
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.context.support.WithAnonymousUser
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
|
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.csrf
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt
|
||||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers
|
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers
|
||||||
import org.springframework.test.context.jdbc.Sql
|
import org.springframework.test.context.jdbc.Sql
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.post
|
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.DefaultMockMvcBuilder
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import org.springframework.web.context.WebApplicationContext
|
import org.springframework.web.context.WebApplicationContext
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
@SpringBootTest(classes = [SpringApplication::class])
|
@SpringBootTest(classes = [SpringApplication::class])
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@Transactional
|
@Transactional
|
||||||
@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
|
@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 {
|
class StatusTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -124,7 +137,6 @@ class StatusTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Sql("/sql/test-post.sql")
|
|
||||||
fun in_reply_to_idを指定したら返信として処理される() {
|
fun in_reply_to_idを指定したら返信として処理される() {
|
||||||
mockMvc
|
mockMvc
|
||||||
.post("/api/v1/statuses") {
|
.post("/api/v1/statuses") {
|
||||||
|
@ -145,6 +157,64 @@ class StatusTest {
|
||||||
.andExpect { jsonPath("\$.in_reply_to_id") { value("1") } }
|
.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 {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@AfterAll
|
@AfterAll
|
||||||
|
|
|
@ -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');
|
|
@ -74,6 +74,18 @@ fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
|
||||||
this[CustomEmojis.createdAt]
|
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") {
|
object CustomEmojis : Table("emojis") {
|
||||||
val id = long("id")
|
val id = long("id")
|
||||||
val name = varchar("name", 1000)
|
val name = varchar("name", 1000)
|
||||||
|
|
|
@ -109,7 +109,22 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByPostId(id: Long): Status {
|
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> {
|
private fun resolveReplyAndRepost(pairs: List<Pair<Status, Long?>>): List<Status> {
|
||||||
|
|
|
@ -26,11 +26,17 @@ class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiSe
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> {
|
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> {
|
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> {
|
override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity<Status> {
|
||||||
|
|
Loading…
Reference in New Issue