mirror of https://github.com/usbharu/Hideout.git
commit
31075240a0
|
@ -0,0 +1,694 @@
|
||||||
|
package mastodon.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.SpringApplication
|
||||||
|
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.FilterKeywordsPostRequest
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequest
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequestKeyword
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.V1FilterPostRequest
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.flywaydb.core.Flyway
|
||||||
|
import org.junit.jupiter.api.AfterAll
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
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.delete
|
||||||
|
import org.springframework.test.web.servlet.get
|
||||||
|
import org.springframework.test.web.servlet.post
|
||||||
|
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
|
||||||
|
|
||||||
|
@SpringBootTest(classes = [SpringApplication::class])
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@Transactional
|
||||||
|
@Sql("/sql/test-user.sql", "/sql/filter/test-filter.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
|
||||||
|
class FilterTest {
|
||||||
|
@Autowired
|
||||||
|
private lateinit var context: WebApplicationContext
|
||||||
|
|
||||||
|
private lateinit var mockMvc: MockMvc
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
mockMvc = MockMvcBuilders.webAppContextSetup(context)
|
||||||
|
.apply<DefaultMockMvcBuilder>(SecurityMockMvcConfigurers.springSecurity())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersPost write権限で追加できる`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v2/filters") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
FilterPostRequest(
|
||||||
|
title = "mute test",
|
||||||
|
context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public),
|
||||||
|
filterAction = FilterPostRequest.FilterAction.warn,
|
||||||
|
expiresIn = null,
|
||||||
|
keywordsAttributes = listOf(
|
||||||
|
FilterPostRequestKeyword(
|
||||||
|
keyword = "hoge",
|
||||||
|
wholeWord = false,
|
||||||
|
regex = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
.andExpect {
|
||||||
|
content {
|
||||||
|
jsonPath("$.keywords[0].keyword") {
|
||||||
|
value("hoge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersPost write_filters権限で追加できる`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v2/filters") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
FilterPostRequest(
|
||||||
|
title = "mute test",
|
||||||
|
context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public),
|
||||||
|
filterAction = FilterPostRequest.FilterAction.warn,
|
||||||
|
expiresIn = null,
|
||||||
|
keywordsAttributes = listOf(
|
||||||
|
FilterPostRequestKeyword(
|
||||||
|
keyword = "fuga",
|
||||||
|
wholeWord = true,
|
||||||
|
regex = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
.andExpect {
|
||||||
|
content {
|
||||||
|
jsonPath("$.keywords[0].keyword") {
|
||||||
|
value("fuga")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersPost read権限で401`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v2/filters") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
FilterPostRequest(
|
||||||
|
title = "mute test",
|
||||||
|
context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public),
|
||||||
|
filterAction = FilterPostRequest.FilterAction.warn,
|
||||||
|
expiresIn = null,
|
||||||
|
keywordsAttributes = listOf(
|
||||||
|
FilterPostRequestKeyword(
|
||||||
|
keyword = "fuga",
|
||||||
|
wholeWord = true,
|
||||||
|
regex = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersGet read権限で取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersGet read_filters権限で取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersGet write権限で401`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersIdGet read権限で取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersIdGet read_filters権限で取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersIdGet write権限で401`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdKeywordsGet read権限で取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1/keywords") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdKeywordsGet read_filters権限で取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1/keywords") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdKeywordsGet writeで403`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1/keywords") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdKeywordsPost writeで追加できる`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v2/filters/1/keywords") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
FilterKeywordsPostRequest(
|
||||||
|
"hage", false, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdKeywordsPost write_filtersで追加できる`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v2/filters/1/keywords") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
FilterKeywordsPostRequest(
|
||||||
|
"hage", false, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdKeywordsPost readで403`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v2/filters/1/keywords") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
FilterKeywordsPostRequest(
|
||||||
|
"hage", false, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersKeywordsIdGet readで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/keywords/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersKeywordsIdGet read_filtersで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/keywords/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersKeywordsIdGet writeだと403`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/keywords/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersKeyowrdsIdDelete writeで削除できる`() = runTest {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v2/filters/keywords/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersKeyowrdsIdDelete write_filtersで削除できる`() = runTest {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v2/filters/keywords/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersKeyowrdsIdDelete readで403`() = runTest {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v2/filters/keywords/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdStatuses readで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1/statuses") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdStatuses read_filtersで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1/statuses") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersFilterIdStatuses writeで403`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/1/statuses") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersStatusesIdGet readで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/statuses/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersStatusesIdGet read_filtersで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/statuses/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersStatusesIdGet writeで403`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v2/filters/statuses/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersStatusesIdDelete writeで削除できる`() {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v2/filters/statuses/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersStatusesIdDelete write_filtersで削除できる`() {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v2/filters/statuses/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV2FiltersStatusesIdDelete readで403`() {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v2/filters/statuses/1") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersGet readで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v1/filters") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersGet read_filtersで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v1/filters") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersGet writeで403`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v1/filters") {
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersPost writeで新規作成`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v1/filters") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
V1FilterPostRequest(
|
||||||
|
phrase = "hoge",
|
||||||
|
context = listOf(V1FilterPostRequest.Context.home),
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = false,
|
||||||
|
expiresIn = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersPost write_filtersで新規作成`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v1/filters") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
V1FilterPostRequest(
|
||||||
|
phrase = "hoge",
|
||||||
|
context = listOf(V1FilterPostRequest.Context.home),
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = false,
|
||||||
|
expiresIn = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersPost readで403`() {
|
||||||
|
mockMvc
|
||||||
|
.post("/api/v1/filters") {
|
||||||
|
contentType = MediaType.APPLICATION_JSON
|
||||||
|
content = ActivityPubConfig().objectMapper().writeValueAsString(
|
||||||
|
V1FilterPostRequest(
|
||||||
|
phrase = "hoge",
|
||||||
|
context = listOf(V1FilterPostRequest.Context.home),
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = false,
|
||||||
|
expiresIn = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with(
|
||||||
|
jwt()
|
||||||
|
.jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersIdGet readで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v1/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersIdGet read_filtersで取得できる`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v1/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersIdGet writeで403`() {
|
||||||
|
mockMvc
|
||||||
|
.get("/api/v1/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.andExpect { status { isForbidden() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersIdDelete writeで削除できる`() {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v1/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersIdDelete write_filtersで削除できる`() {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v1/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apiV1FiltersIdDelete readで403`() {
|
||||||
|
mockMvc
|
||||||
|
.delete("/api/v1/filters/1") {
|
||||||
|
with(
|
||||||
|
jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asyncDispatch()
|
||||||
|
.andExpect { status { isOk() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@AfterAll
|
||||||
|
fun dropDatabase(@Autowired flyway: Flyway) {
|
||||||
|
flyway.clean()
|
||||||
|
flyway.migrate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
hideout:
|
hideout:
|
||||||
|
debug:
|
||||||
|
trace-query-exception: true
|
||||||
|
trace-query-call: true
|
||||||
url: "https://example.com"
|
url: "https://example.com"
|
||||||
use-mongodb: true
|
use-mongodb: true
|
||||||
security:
|
security:
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
insert into filters (id, user_id, name, context, action)
|
||||||
|
VALUES (1, 1, 'test filter', 'home', 'warn');
|
||||||
|
insert into filter_keywords(id, filter_id, keyword, mode)
|
||||||
|
VALUES (1, 1, 'hoge', 'NONE')
|
|
@ -180,7 +180,21 @@ class APNoteServiceImpl(
|
||||||
}.map { it.id }
|
}.map { it.id }
|
||||||
|
|
||||||
val createPost =
|
val createPost =
|
||||||
if (quote != null) {
|
post(quote, person, note, visibility, reply, mediaList, emojis)
|
||||||
|
|
||||||
|
val createRemote = postService.createRemote(createPost)
|
||||||
|
return note to createRemote
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun post(
|
||||||
|
quote: Post?,
|
||||||
|
person: Pair<Person, Actor>,
|
||||||
|
note: Note,
|
||||||
|
visibility: Visibility,
|
||||||
|
reply: Post?,
|
||||||
|
mediaList: List<Long>,
|
||||||
|
emojis: List<Long>
|
||||||
|
) = if (quote != null) {
|
||||||
postBuilder.quoteRepostOf(
|
postBuilder.quoteRepostOf(
|
||||||
id = postRepository.generateId(),
|
id = postRepository.generateId(),
|
||||||
actorId = person.second.id,
|
actorId = person.second.id,
|
||||||
|
@ -211,10 +225,6 @@ class APNoteServiceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val createRemote = postService.createRemote(createPost)
|
|
||||||
return note to createRemote
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun buildEmojis(note: Note) = note.tag
|
private suspend fun buildEmojis(note: Note) = note.tag
|
||||||
.filterIsInstance<Emoji>()
|
.filterIsInstance<Emoji>()
|
||||||
.map {
|
.map {
|
||||||
|
|
|
@ -26,8 +26,7 @@ import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Primary
|
import org.springframework.context.annotation.Primary
|
||||||
import org.springframework.core.annotation.Order
|
import org.springframework.core.annotation.Order
|
||||||
import org.springframework.http.HttpMethod.GET
|
import org.springframework.http.HttpMethod.*
|
||||||
import org.springframework.http.HttpMethod.POST
|
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
||||||
|
@ -210,6 +209,33 @@ class SecurityConfig {
|
||||||
authorize(GET, "/api/v1/timelines/public", permitAll)
|
authorize(GET, "/api/v1/timelines/public", permitAll)
|
||||||
authorize(GET, "/api/v1/timelines/home", hasAnyScope("read", "read:statuses"))
|
authorize(GET, "/api/v1/timelines/home", hasAnyScope("read", "read:statuses"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v2/filters", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(POST, "/api/v2/filters", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v2/filters/*", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(PUT, "/api/v2/filters/*", hasAnyScope("write", "write:filters"))
|
||||||
|
authorize(DELETE, "/api/v2/filters/*", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v2/filters/*/keywords", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(POST, "/api/v2/filters/*/keywords", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v2/filters/keywords/*", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(PUT, "/api/v2/filters/keywords/*", hasAnyScope("write", "write:filters"))
|
||||||
|
authorize(DELETE, "/api/v2/filters/keywords/*", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v2/filters/*/statuses", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(POST, "/api/v2/filters/*/statuses", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v2/filters/statuses/*", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(DELETE, "/api/v2/filters/statuses/*", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v1/filters", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(POST, "/api/v1/filters", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
|
authorize(GET, "/api/v1/filters/*", hasAnyScope("read", "read:filters"))
|
||||||
|
authorize(POST, "/api/v1/filters/*", hasAnyScope("write", "write:filters"))
|
||||||
|
authorize(DELETE, "/api/v1/filters/*", hasAnyScope("write", "write:filters"))
|
||||||
|
|
||||||
authorize(anyRequest, authenticated)
|
authorize(anyRequest, authenticated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
|
data class Filter(
|
||||||
|
val id: Long,
|
||||||
|
val userId: Long,
|
||||||
|
val name: String,
|
||||||
|
val context: List<FilterType>,
|
||||||
|
val filterAction: FilterAction,
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
|
@Suppress("EnumEntryNameCase", "EnumNaming")
|
||||||
|
enum class FilterAction {
|
||||||
|
warn,
|
||||||
|
hide
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
|
enum class FilterMode {
|
||||||
|
WHOLE_WORD,
|
||||||
|
REGEX,
|
||||||
|
NONE
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
|
interface FilterRepository {
|
||||||
|
|
||||||
|
suspend fun generateId(): Long
|
||||||
|
suspend fun save(filter: Filter): Filter
|
||||||
|
suspend fun findById(id: Long): Filter?
|
||||||
|
|
||||||
|
suspend fun findByUserIdAndId(userId: Long, id: Long): Filter?
|
||||||
|
suspend fun findByUserIdAndType(userId: Long, types: List<FilterType>): List<Filter>
|
||||||
|
suspend fun deleteById(id: Long)
|
||||||
|
|
||||||
|
suspend fun deleteByUserIdAndId(userId: Long, id: Long)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
|
@Suppress("EnumEntryNameCase", "EnumNaming")
|
||||||
|
enum class FilterType {
|
||||||
|
home,
|
||||||
|
notifications,
|
||||||
|
public,
|
||||||
|
thread,
|
||||||
|
account
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filterkeyword
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
|
||||||
|
|
||||||
|
data class FilterKeyword(
|
||||||
|
val id: Long,
|
||||||
|
val filterId: Long,
|
||||||
|
val keyword: String,
|
||||||
|
val mode: FilterMode
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.filterkeyword
|
||||||
|
|
||||||
|
interface FilterKeywordRepository {
|
||||||
|
suspend fun generateId(): Long
|
||||||
|
suspend fun save(filterKeyword: FilterKeyword): FilterKeyword
|
||||||
|
suspend fun saveAll(filterKeywordList: List<FilterKeyword>)
|
||||||
|
suspend fun findById(id: Long): FilterKeyword?
|
||||||
|
suspend fun deleteById(id: Long)
|
||||||
|
suspend fun deleteByFilterId(filterId: Long)
|
||||||
|
}
|
|
@ -89,6 +89,7 @@ data class Post private constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
fun pureRepostOf(
|
fun pureRepostOf(
|
||||||
id: Long,
|
id: Long,
|
||||||
actorId: Long,
|
actorId: Long,
|
||||||
|
@ -110,24 +111,25 @@ data class Post private constructor(
|
||||||
require(actorId >= 0) { "actorId must be greater than or equal to 0." }
|
require(actorId >= 0) { "actorId must be greater than or equal to 0." }
|
||||||
|
|
||||||
return Post(
|
return Post(
|
||||||
id,
|
id = id,
|
||||||
actorId,
|
actorId = actorId,
|
||||||
null,
|
overview = null,
|
||||||
"",
|
content = "",
|
||||||
"",
|
text = "",
|
||||||
createdAt.toEpochMilli(),
|
createdAt = createdAt.toEpochMilli(),
|
||||||
fixedVisibility,
|
visibility = fixedVisibility,
|
||||||
url,
|
url = url,
|
||||||
repost.id,
|
repostId = repost.id,
|
||||||
null,
|
replyId = null,
|
||||||
false,
|
sensitive = false,
|
||||||
apId,
|
apId = apId,
|
||||||
emptyList(),
|
mediaIds = emptyList(),
|
||||||
false,
|
delted = false,
|
||||||
emptyList()
|
emojiIds = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
fun quoteRepostOf(
|
fun quoteRepostOf(
|
||||||
id: Long,
|
id: Long,
|
||||||
actorId: Long,
|
actorId: Long,
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class ExposedFilterKeywordRepository(private val idGenerateService: IdGenerateService) : FilterKeywordRepository,
|
||||||
|
AbstractRepository() {
|
||||||
|
override val logger: Logger
|
||||||
|
get() = Companion.logger
|
||||||
|
|
||||||
|
override suspend fun generateId(): Long = idGenerateService.generateId()
|
||||||
|
|
||||||
|
override suspend fun save(filterKeyword: FilterKeyword): FilterKeyword = query {
|
||||||
|
val empty = FilterKeywords.selectAll().where { FilterKeywords.id eq filterKeyword.id }.empty()
|
||||||
|
if (empty) {
|
||||||
|
FilterKeywords.insert {
|
||||||
|
it[id] = filterKeyword.id
|
||||||
|
it[filterId] = filterKeyword.filterId
|
||||||
|
it[keyword] = filterKeyword.keyword
|
||||||
|
it[mode] = filterKeyword.mode.name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FilterKeywords.update({ FilterKeywords.id eq filterKeyword.id }) {
|
||||||
|
it[filterId] = filterKeyword.filterId
|
||||||
|
it[keyword] = filterKeyword.keyword
|
||||||
|
it[mode] = filterKeyword.mode.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterKeyword
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveAll(filterKeywordList: List<FilterKeyword>): Unit = query {
|
||||||
|
FilterKeywords.batchInsert(filterKeywordList, ignore = true) {
|
||||||
|
this[FilterKeywords.id] = it.id
|
||||||
|
this[FilterKeywords.filterId] = it.filterId
|
||||||
|
this[FilterKeywords.keyword] = it.keyword
|
||||||
|
this[FilterKeywords.mode] = it.mode.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findById(id: Long): FilterKeyword? = query {
|
||||||
|
return@query FilterKeywords.selectAll().where { FilterKeywords.id eq id }.singleOrNull()?.toFilterKeyword()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteById(id: Long): Unit = query {
|
||||||
|
FilterKeywords.deleteWhere { FilterKeywords.id eq id }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteByFilterId(filterId: Long): Unit = query {
|
||||||
|
FilterKeywords.deleteWhere { FilterKeywords.filterId eq filterId }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(ExposedFilterKeywordRepository::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResultRow.toFilterKeyword(): FilterKeyword {
|
||||||
|
return FilterKeyword(
|
||||||
|
this[FilterKeywords.id],
|
||||||
|
this[FilterKeywords.filterId],
|
||||||
|
this[FilterKeywords.keyword],
|
||||||
|
this[FilterKeywords.mode].let { FilterMode.valueOf(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object FilterKeywords : Table("filter_keywords") {
|
||||||
|
val id = long("id")
|
||||||
|
val filterId = long("filter_id").references(Filters.id)
|
||||||
|
val keyword = varchar("keyword", 1000)
|
||||||
|
val mode = varchar("mode", 100)
|
||||||
|
|
||||||
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.Filter
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class ExposedFilterRepository(private val idGenerateService: IdGenerateService) : FilterRepository,
|
||||||
|
AbstractRepository() {
|
||||||
|
override val logger: Logger
|
||||||
|
get() = Companion.logger
|
||||||
|
|
||||||
|
override suspend fun generateId(): Long = idGenerateService.generateId()
|
||||||
|
|
||||||
|
override suspend fun save(filter: Filter): Filter = query {
|
||||||
|
val empty = Filters.selectAll().where {
|
||||||
|
Filters.id eq filter.id
|
||||||
|
}.forUpdate().empty()
|
||||||
|
if (empty) {
|
||||||
|
Filters.insert {
|
||||||
|
it[id] = filter.id
|
||||||
|
it[userId] = filter.userId
|
||||||
|
it[name] = filter.name
|
||||||
|
it[context] = filter.context.joinToString(",") { filterType -> filterType.name }
|
||||||
|
it[filterAction] = filter.filterAction.name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Filters.update({ Filters.id eq filter.id }) {
|
||||||
|
it[userId] = filter.userId
|
||||||
|
it[name] = filter.name
|
||||||
|
it[context] = filter.context.joinToString(",") { filterType -> filterType.name }
|
||||||
|
it[filterAction] = filter.filterAction.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filter
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findById(id: Long): Filter? = query {
|
||||||
|
return@query Filters.selectAll().where { Filters.id eq id }.singleOrNull()?.toFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserIdAndId(userId: Long, id: Long): Filter? = query {
|
||||||
|
return@query Filters.selectAll().where { Filters.userId eq userId and (Filters.id eq id) }.singleOrNull()
|
||||||
|
?.toFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserIdAndType(userId: Long, types: List<FilterType>): List<Filter> = query {
|
||||||
|
return@query Filters.selectAll().where { Filters.userId eq userId }.map { it.toFilter() }
|
||||||
|
.filter { it.context.containsAll(types) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteById(id: Long): Unit = query {
|
||||||
|
Filters.deleteWhere { Filters.id eq id }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteByUserIdAndId(userId: Long, id: Long) {
|
||||||
|
Filters.deleteWhere { Filters.userId eq userId and (Filters.id eq id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(ExposedFilterRepository::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResultRow.toFilter(): Filter = Filter(
|
||||||
|
this[Filters.id],
|
||||||
|
this[Filters.userId],
|
||||||
|
this[Filters.name],
|
||||||
|
this[Filters.context].split(",").filterNot(String::isEmpty).map { FilterType.valueOf(it) },
|
||||||
|
this[Filters.filterAction].let { FilterAction.valueOf(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
object Filters : Table() {
|
||||||
|
val id = long("id")
|
||||||
|
val userId = long("user_id").references(Actors.id)
|
||||||
|
val name = varchar("name", 255)
|
||||||
|
val context = varchar("context", 500)
|
||||||
|
val filterAction = varchar("action", 255)
|
||||||
|
|
||||||
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package dev.usbharu.hideout.core.query.model
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.FilterKeywords
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Filters
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toFilter
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toFilterKeyword
|
||||||
|
import org.jetbrains.exposed.sql.Query
|
||||||
|
import org.jetbrains.exposed.sql.and
|
||||||
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class ExposedFilterQueryService : FilterQueryService {
|
||||||
|
override suspend fun findByUserIdAndType(userId: Long, types: List<FilterType>): List<FilterQueryModel> {
|
||||||
|
return Filters
|
||||||
|
.rightJoin(FilterKeywords)
|
||||||
|
.selectAll()
|
||||||
|
.where { Filters.userId eq userId }
|
||||||
|
.toFilterQueryModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserId(userId: Long): List<FilterQueryModel> {
|
||||||
|
return Filters
|
||||||
|
.rightJoin(FilterKeywords)
|
||||||
|
.selectAll()
|
||||||
|
.where { Filters.userId eq userId }
|
||||||
|
.toFilterQueryModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserIdAndId(userId: Long, id: Long): FilterQueryModel? {
|
||||||
|
return Filters
|
||||||
|
.leftJoin(FilterKeywords)
|
||||||
|
.selectAll()
|
||||||
|
.where { Filters.userId eq userId and (Filters.id eq id) }
|
||||||
|
.toFilterQueryModel()
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserIdAndKeywordId(userId: Long, keywordId: Long): FilterQueryModel? {
|
||||||
|
return Filters
|
||||||
|
.leftJoin(FilterKeywords)
|
||||||
|
.selectAll()
|
||||||
|
.where { Filters.userId eq userId and (FilterKeywords.id eq keywordId) }
|
||||||
|
.toFilterQueryModel()
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Query.toFilterQueryModel(): List<FilterQueryModel> {
|
||||||
|
return this
|
||||||
|
.groupBy { it[Filters.id] }
|
||||||
|
.map { it.value }
|
||||||
|
.map {
|
||||||
|
FilterQueryModel.of(
|
||||||
|
it.first().toFilter(),
|
||||||
|
it.map { resultRow -> resultRow.toFilterKeyword() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package dev.usbharu.hideout.core.query.model
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.Filter
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword
|
||||||
|
|
||||||
|
data class FilterQueryModel(
|
||||||
|
val id: Long,
|
||||||
|
val userId: Long,
|
||||||
|
val name: String,
|
||||||
|
val context: List<FilterType>,
|
||||||
|
val filterAction: FilterAction,
|
||||||
|
val keywords: List<FilterKeyword>
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("FunctionMinLength")
|
||||||
|
fun of(filter: Filter, keywords: List<FilterKeyword>): FilterQueryModel = FilterQueryModel(
|
||||||
|
id = filter.id,
|
||||||
|
userId = filter.userId,
|
||||||
|
name = filter.name,
|
||||||
|
context = filter.context,
|
||||||
|
filterAction = filter.filterAction,
|
||||||
|
keywords = keywords
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.usbharu.hideout.core.query.model
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
|
||||||
|
interface FilterQueryService {
|
||||||
|
suspend fun findByUserIdAndType(userId: Long, types: List<FilterType>): List<FilterQueryModel>
|
||||||
|
suspend fun findByUserId(userId: Long): List<FilterQueryModel>
|
||||||
|
suspend fun findByUserIdAndId(userId: Long, id: Long): FilterQueryModel?
|
||||||
|
suspend fun findByUserIdAndKeywordId(userId: Long, keywordId: Long): FilterQueryModel?
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
|
||||||
|
|
||||||
|
data class FilterKeyword(
|
||||||
|
val keyword: String,
|
||||||
|
val mode: FilterMode
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
|
||||||
|
data class FilterResult(
|
||||||
|
val filter: FilterQueryModel,
|
||||||
|
val keyword: String,
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
|
||||||
|
interface MuteProcessService {
|
||||||
|
suspend fun processMute(post: Post, context: List<FilterType>, filters: List<FilterQueryModel>): FilterResult?
|
||||||
|
suspend fun processMutes(
|
||||||
|
posts: List<Post>,
|
||||||
|
context: List<FilterType>,
|
||||||
|
filters: List<FilterQueryModel>
|
||||||
|
): Map<Post, FilterResult>
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterMode.*
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MuteProcessServiceImpl : MuteProcessService {
|
||||||
|
override suspend fun processMute(
|
||||||
|
post: Post,
|
||||||
|
context: List<FilterType>,
|
||||||
|
filters: List<FilterQueryModel>
|
||||||
|
): FilterResult? {
|
||||||
|
val preprocess = preprocess(context, filters)
|
||||||
|
|
||||||
|
return processMute(post, preprocess)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun processMute(
|
||||||
|
post: Post,
|
||||||
|
preprocess: List<PreProcessedFilter>
|
||||||
|
): FilterResult? {
|
||||||
|
logger.trace("process mute post: {}", post)
|
||||||
|
if (post.overview != null) {
|
||||||
|
val processMute = processMute(post.overview, preprocess)
|
||||||
|
|
||||||
|
if (processMute != null) {
|
||||||
|
return processMute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val processMute = processMute(post.text, preprocess)
|
||||||
|
|
||||||
|
if (processMute != null) {
|
||||||
|
return processMute
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun processMutes(
|
||||||
|
posts: List<Post>,
|
||||||
|
context: List<FilterType>,
|
||||||
|
filters: List<FilterQueryModel>
|
||||||
|
): Map<Post, FilterResult> {
|
||||||
|
val preprocess = preprocess(context, filters)
|
||||||
|
|
||||||
|
return posts.mapNotNull { it to (processMute(it, preprocess) ?: return@mapNotNull null) }.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun processMute(string: String, filters: List<PreProcessedFilter>): FilterResult? {
|
||||||
|
for (filter in filters) {
|
||||||
|
val matchEntire = filter.regex.find(string)
|
||||||
|
|
||||||
|
if (matchEntire != null) {
|
||||||
|
return FilterResult(filter.filter, matchEntire.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun preprocess(context: List<FilterType>, filters: List<FilterQueryModel>): List<PreProcessedFilter> {
|
||||||
|
val filterQueryModelList = filters
|
||||||
|
.filter { it.context.any(context::contains) }
|
||||||
|
.map {
|
||||||
|
PreProcessedFilter(
|
||||||
|
it,
|
||||||
|
precompileRegex(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterQueryModelList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun precompileRegex(filter: FilterQueryModel): Regex {
|
||||||
|
logger.trace("precompile regex. filter: {}", filter)
|
||||||
|
|
||||||
|
val regexList = mutableListOf<Regex>()
|
||||||
|
|
||||||
|
val noneRegexStrings = mutableListOf<String>()
|
||||||
|
val wholeRegexStrings = mutableListOf<String>()
|
||||||
|
|
||||||
|
for (keyword in filter.keywords) {
|
||||||
|
when (keyword.mode) {
|
||||||
|
WHOLE_WORD -> wholeRegexStrings.add(keyword.keyword)
|
||||||
|
REGEX -> regexList.add(Regex(keyword.keyword))
|
||||||
|
NONE -> noneRegexStrings.add(keyword.keyword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val noneRegex = noneRegexStrings.joinToString("|", "(", ")")
|
||||||
|
val wholeRegex = wholeRegexStrings.joinToString("|", "\\b(", ")\\b")
|
||||||
|
|
||||||
|
val regex = if (noneRegexStrings.isNotEmpty() && wholeRegexStrings.isNotEmpty()) {
|
||||||
|
Regex("$noneRegex|$wholeRegex")
|
||||||
|
} else if (noneRegexStrings.isNotEmpty()) {
|
||||||
|
noneRegex.toRegex()
|
||||||
|
} else if (wholeRegexStrings.isNotEmpty()) {
|
||||||
|
wholeRegex.toRegex()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regex != null) {
|
||||||
|
regexList.add(regex)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pattern = regexList.joinToString(")|(", "(", ")")
|
||||||
|
logger.trace("precompiled regex {}", pattern)
|
||||||
|
|
||||||
|
return Regex(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PreProcessedFilter(val filter: FilterQueryModel, val regex: Regex)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(MuteProcessServiceImpl::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
|
||||||
|
interface MuteService {
|
||||||
|
suspend fun createFilter(
|
||||||
|
title: String,
|
||||||
|
context: List<FilterType>,
|
||||||
|
action: FilterAction,
|
||||||
|
keywords: List<FilterKeyword>,
|
||||||
|
loginUser: Long
|
||||||
|
): FilterQueryModel
|
||||||
|
|
||||||
|
suspend fun getFilters(userId: Long, types: List<FilterType> = emptyList()): List<FilterQueryModel>
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.Filter
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MuteServiceImpl(
|
||||||
|
private val filterRepository: FilterRepository,
|
||||||
|
private val filterKeywordRepository: FilterKeywordRepository,
|
||||||
|
private val filterQueryService: FilterQueryService
|
||||||
|
) : MuteService {
|
||||||
|
override suspend fun createFilter(
|
||||||
|
title: String,
|
||||||
|
context: List<FilterType>,
|
||||||
|
action: FilterAction,
|
||||||
|
keywords: List<FilterKeyword>,
|
||||||
|
loginUser: Long
|
||||||
|
): FilterQueryModel {
|
||||||
|
val filter = Filter(
|
||||||
|
filterRepository.generateId(),
|
||||||
|
loginUser,
|
||||||
|
title,
|
||||||
|
context,
|
||||||
|
action
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterKeywordList = keywords.map {
|
||||||
|
dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword(
|
||||||
|
filterKeywordRepository.generateId(),
|
||||||
|
filter.id,
|
||||||
|
it.keyword,
|
||||||
|
it.mode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val savedFilter = filterRepository.save(filter)
|
||||||
|
|
||||||
|
filterKeywordRepository.saveAll(filterKeywordList)
|
||||||
|
return FilterQueryModel.of(savedFilter, filterKeywordList)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getFilters(userId: Long, types: List<FilterType>): List<FilterQueryModel> =
|
||||||
|
filterQueryService.findByUserIdAndType(userId, types)
|
||||||
|
}
|
|
@ -40,11 +40,12 @@ class JsonOrFormModelMethodProcessor(
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
||||||
} catch (ignore: Exception) {
|
} catch (exception: Exception) {
|
||||||
try {
|
try {
|
||||||
requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn("Failed to bind request", e)
|
logger.warn("Failed to bind request (1)", exception)
|
||||||
|
logger.warn("Failed to bind request (2)", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
package dev.usbharu.hideout.mastodon.interfaces.api.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.controller.mastodon.generated.FilterApi
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.*
|
||||||
|
import dev.usbharu.hideout.mastodon.service.filter.MastodonFilterApiService
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.stereotype.Controller
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
class MastodonFilterApiController(
|
||||||
|
private val mastodonFilterApiService: MastodonFilterApiService,
|
||||||
|
private val loginUserContextHolder: LoginUserContextHolder
|
||||||
|
) : FilterApi {
|
||||||
|
|
||||||
|
override suspend fun apiV1FiltersIdDelete(id: String): ResponseEntity<Any> {
|
||||||
|
mastodonFilterApiService.deleteV1FilterById(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||||
|
return ResponseEntity.ok().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1FiltersIdGet(
|
||||||
|
id: String
|
||||||
|
): ResponseEntity<V1Filter> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.getV1FilterById(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
id.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1FiltersIdPut(
|
||||||
|
id: String,
|
||||||
|
phrase: String?,
|
||||||
|
context: List<String>?,
|
||||||
|
irreversible: Boolean?,
|
||||||
|
wholeWord: Boolean?,
|
||||||
|
expiresIn: Int?
|
||||||
|
): ResponseEntity<V1Filter> = super.apiV1FiltersIdPut(id, phrase, context, irreversible, wholeWord, expiresIn)
|
||||||
|
|
||||||
|
override suspend fun apiV1FiltersPost(v1FilterPostRequest: V1FilterPostRequest): ResponseEntity<V1Filter> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.createByV1Filter(loginUserContextHolder.getLoginUserId(), v1FilterPostRequest)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersFilterIdKeywordsPost(
|
||||||
|
filterId: String,
|
||||||
|
filterKeywordsPostRequest: FilterKeywordsPostRequest
|
||||||
|
): ResponseEntity<FilterKeyword> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.addKeyword(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
filterId.toLong(),
|
||||||
|
filterKeywordsPostRequest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersFilterIdStatusesPost(
|
||||||
|
filterId: String,
|
||||||
|
filterStatusRequest: FilterStatusRequest
|
||||||
|
): ResponseEntity<FilterStatus> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.addFilterStatus(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
filterId.toLong(),
|
||||||
|
filterStatusRequest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apiV1FiltersGet(): ResponseEntity<Flow<V1Filter>> =
|
||||||
|
ResponseEntity.ok(mastodonFilterApiService.v1Filters(loginUserContextHolder.getLoginUserId()))
|
||||||
|
|
||||||
|
override fun apiV2FiltersFilterIdKeywordsGet(filterId: String): ResponseEntity<Flow<FilterKeyword>> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.filterKeywords(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
filterId.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apiV2FiltersFilterIdStatusesGet(filterId: String): ResponseEntity<Flow<FilterStatus>> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.filterStatuses(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
filterId.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apiV2FiltersGet(): ResponseEntity<Flow<Filter>> {
|
||||||
|
return ResponseEntity.ok(mastodonFilterApiService.filters(loginUserContextHolder.getLoginUserId()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersIdDelete(id: String): ResponseEntity<Any> {
|
||||||
|
mastodonFilterApiService.deleteById(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||||
|
return ResponseEntity.ok().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersIdGet(id: String): ResponseEntity<Filter> =
|
||||||
|
ResponseEntity.ok(mastodonFilterApiService.getById(loginUserContextHolder.getLoginUserId(), id.toLong()))
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersIdPut(
|
||||||
|
id: String,
|
||||||
|
title: String?,
|
||||||
|
context: List<String>?,
|
||||||
|
filterAction: String?,
|
||||||
|
expiresIn: Int?,
|
||||||
|
keywordsAttributes: List<FilterPubRequestKeyword>?
|
||||||
|
): ResponseEntity<Filter> =
|
||||||
|
super.apiV2FiltersIdPut(id, title, context, filterAction, expiresIn, keywordsAttributes)
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersKeywordsIdDelete(id: String): ResponseEntity<Any> {
|
||||||
|
mastodonFilterApiService.deleteKeyword(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||||
|
return ResponseEntity.ok().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersKeywordsIdGet(id: String): ResponseEntity<FilterKeyword> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.getKeywordById(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
id.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersKeywordsIdPut(
|
||||||
|
id: String,
|
||||||
|
keyword: String?,
|
||||||
|
wholeWord: Boolean?,
|
||||||
|
regex: Boolean?
|
||||||
|
): ResponseEntity<FilterKeyword> = super.apiV2FiltersKeywordsIdPut(id, keyword, wholeWord, regex)
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersPost(filterPostRequest: FilterPostRequest): ResponseEntity<Filter> =
|
||||||
|
ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.createFilter(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
filterPostRequest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersStatusesIdDelete(id: String): ResponseEntity<Any> {
|
||||||
|
mastodonFilterApiService.deleteFilterStatusById(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||||
|
return ResponseEntity.ok().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV2FiltersStatusesIdGet(id: String): ResponseEntity<FilterStatus> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
mastodonFilterApiService.getFilterStatusById(
|
||||||
|
loginUserContextHolder.getLoginUserId(),
|
||||||
|
id.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ import kotlin.math.min
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
interface AccountApiService {
|
interface AccountApiService {
|
||||||
|
|
||||||
@Suppress("ongParameterList")
|
@Suppress("LongParameterList")
|
||||||
suspend fun accountsStatuses(
|
suspend fun accountsStatuses(
|
||||||
userid: Long,
|
userid: Long,
|
||||||
onlyMedia: Boolean,
|
onlyMedia: Boolean,
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
package dev.usbharu.hideout.mastodon.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction.hide
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction.warn
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType.*
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.filter.MuteService
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.*
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequest.FilterAction
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.V1Filter.Context
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
|
interface MastodonFilterApiService {
|
||||||
|
fun v1Filters(userId: Long): Flow<V1Filter>
|
||||||
|
|
||||||
|
suspend fun deleteV1FilterById(userId: Long, id: Long)
|
||||||
|
|
||||||
|
suspend fun getV1FilterById(userId: Long, id: Long): V1Filter?
|
||||||
|
|
||||||
|
suspend fun createByV1Filter(userId: Long, v1FilterRequest: V1FilterPostRequest): V1Filter
|
||||||
|
|
||||||
|
fun filterKeywords(userId: Long, filterId: Long): Flow<FilterKeyword>
|
||||||
|
|
||||||
|
suspend fun addKeyword(userId: Long, filterId: Long, keyword: FilterKeywordsPostRequest): FilterKeyword
|
||||||
|
|
||||||
|
fun filterStatuses(userId: Long, filterId: Long): Flow<FilterStatus>
|
||||||
|
|
||||||
|
suspend fun addFilterStatus(userId: Long, filterId: Long, filterStatusRequest: FilterStatusRequest): FilterStatus
|
||||||
|
|
||||||
|
fun filters(userId: Long): Flow<Filter>
|
||||||
|
|
||||||
|
suspend fun deleteById(userId: Long, filterId: Long)
|
||||||
|
|
||||||
|
suspend fun getById(userId: Long, filterId: Long): Filter?
|
||||||
|
|
||||||
|
suspend fun deleteKeyword(userId: Long, keywordId: Long)
|
||||||
|
|
||||||
|
suspend fun getKeywordById(userId: Long, keywordId: Long): FilterKeyword?
|
||||||
|
|
||||||
|
suspend fun createFilter(userId: Long, filterPostRequest: FilterPostRequest): Filter
|
||||||
|
|
||||||
|
suspend fun deleteFilterStatusById(userId: Long, filterPostsId: Long)
|
||||||
|
|
||||||
|
suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus?
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MastodonFilterApiServiceImpl(
|
||||||
|
private val muteService: MuteService,
|
||||||
|
private val filterQueryService: FilterQueryService,
|
||||||
|
private val filterRepository: FilterRepository,
|
||||||
|
private val filterKeywordRepository: FilterKeywordRepository
|
||||||
|
) : MastodonFilterApiService {
|
||||||
|
override fun v1Filters(userId: Long): Flow<V1Filter> {
|
||||||
|
return runBlocking { filterQueryService.findByUserId(userId) }.flatMap { filterQueryModel ->
|
||||||
|
filterQueryModel.keywords.map {
|
||||||
|
V1Filter(
|
||||||
|
id = it.id.toString(),
|
||||||
|
phrase = it.keyword,
|
||||||
|
context = filterQueryModel.context.map { filterType ->
|
||||||
|
when (filterType) {
|
||||||
|
home -> Context.home
|
||||||
|
notifications -> Context.notifications
|
||||||
|
public -> Context.public
|
||||||
|
thread -> Context.thread
|
||||||
|
account -> Context.account
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiresAt = null,
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = (it.mode != FilterMode.WHOLE_WORD).not()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.asFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteV1FilterById(userId: Long, id: Long) {
|
||||||
|
val keywordId = filterQueryService.findByUserIdAndKeywordId(userId, id)?.keywords?.singleOrNull()?.id ?: return
|
||||||
|
|
||||||
|
filterKeywordRepository.deleteById(keywordId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getV1FilterById(userId: Long, id: Long): V1Filter? {
|
||||||
|
val filterQueryModel = filterQueryService.findByUserIdAndKeywordId(userId, id) ?: return null
|
||||||
|
|
||||||
|
val filterKeyword = filterQueryModel.keywords.firstOrNull() ?: return null
|
||||||
|
|
||||||
|
return v1Filter(filterQueryModel, filterKeyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun v1Filter(
|
||||||
|
filterQueryModel: FilterQueryModel,
|
||||||
|
filterKeyword: dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword
|
||||||
|
) = V1Filter(
|
||||||
|
id = filterQueryModel.id.toString(),
|
||||||
|
phrase = filterKeyword.keyword,
|
||||||
|
context = filterQueryModel.context.map {
|
||||||
|
when (it) {
|
||||||
|
home -> Context.home
|
||||||
|
notifications -> Context.notifications
|
||||||
|
public -> Context.public
|
||||||
|
thread -> Context.thread
|
||||||
|
account -> Context.account
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiresAt = null,
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = filterKeyword.mode == FilterMode.WHOLE_WORD
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun createByV1Filter(userId: Long, v1FilterRequest: V1FilterPostRequest): V1Filter {
|
||||||
|
val createFilter = muteService.createFilter(
|
||||||
|
title = v1FilterRequest.phrase,
|
||||||
|
context = v1FilterRequest.context.map {
|
||||||
|
when (it) {
|
||||||
|
V1FilterPostRequest.Context.home -> home
|
||||||
|
V1FilterPostRequest.Context.notifications -> notifications
|
||||||
|
V1FilterPostRequest.Context.public -> public
|
||||||
|
V1FilterPostRequest.Context.thread -> thread
|
||||||
|
V1FilterPostRequest.Context.account -> account
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action = warn,
|
||||||
|
keywords = listOf(
|
||||||
|
dev.usbharu.hideout.core.service.filter.FilterKeyword(
|
||||||
|
v1FilterRequest.phrase,
|
||||||
|
if (v1FilterRequest.wholeWord == true) {
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
} else {
|
||||||
|
FilterMode.NONE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
loginUser = userId
|
||||||
|
)
|
||||||
|
|
||||||
|
return v1Filter(createFilter, createFilter.keywords.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun filterKeywords(userId: Long, filterId: Long): Flow<FilterKeyword> =
|
||||||
|
runBlocking { filterQueryService.findByUserIdAndId(userId, filterId) }
|
||||||
|
?.keywords
|
||||||
|
?.map {
|
||||||
|
toFilterKeyword(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.orEmpty()
|
||||||
|
.asFlow()
|
||||||
|
|
||||||
|
override suspend fun addKeyword(userId: Long, filterId: Long, keyword: FilterKeywordsPostRequest): FilterKeyword {
|
||||||
|
val id = filterQueryService.findByUserIdAndId(userId, filterId)?.id
|
||||||
|
?: throw IllegalArgumentException("filter not found.")
|
||||||
|
|
||||||
|
val filterKeyword = filterKeywordRepository.save(
|
||||||
|
dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword(
|
||||||
|
id = filterKeywordRepository.generateId(),
|
||||||
|
filterId = id,
|
||||||
|
keyword = keyword.keyword,
|
||||||
|
mode = if (keyword.regex == true) {
|
||||||
|
FilterMode.REGEX
|
||||||
|
} else if (keyword.wholeWord == true) {
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
} else {
|
||||||
|
FilterMode.NONE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return toFilterKeyword(filterKeyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun filterStatuses(userId: Long, filterId: Long): Flow<FilterStatus> = emptyFlow()
|
||||||
|
|
||||||
|
override suspend fun addFilterStatus(
|
||||||
|
userId: Long,
|
||||||
|
filterId: Long,
|
||||||
|
filterStatusRequest: FilterStatusRequest
|
||||||
|
): FilterStatus {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun filters(userId: Long): Flow<Filter> =
|
||||||
|
runBlocking { filterQueryService.findByUserId(userId) }.map { filterQueryModel ->
|
||||||
|
toFilter(filterQueryModel)
|
||||||
|
}.asFlow()
|
||||||
|
|
||||||
|
private fun toFilter(filterQueryModel: FilterQueryModel) = Filter(
|
||||||
|
id = filterQueryModel.id.toString(),
|
||||||
|
title = filterQueryModel.name,
|
||||||
|
context = filterQueryModel.context.map {
|
||||||
|
when (it) {
|
||||||
|
home -> Filter.Context.home
|
||||||
|
notifications -> Filter.Context.notifications
|
||||||
|
public -> Filter.Context.public
|
||||||
|
thread -> Filter.Context.thread
|
||||||
|
account -> Filter.Context.account
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiresAt = null,
|
||||||
|
filterAction = when (filterQueryModel.filterAction) {
|
||||||
|
warn -> Filter.FilterAction.warn
|
||||||
|
hide -> Filter.FilterAction.hide
|
||||||
|
},
|
||||||
|
keywords = filterQueryModel.keywords.map {
|
||||||
|
toFilterKeyword(it)
|
||||||
|
},
|
||||||
|
statuses = null
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun toFilterKeyword(it: dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword) = FilterKeyword(
|
||||||
|
it.id.toString(),
|
||||||
|
it.keyword,
|
||||||
|
it.mode == FilterMode.WHOLE_WORD
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun deleteById(userId: Long, filterId: Long) =
|
||||||
|
filterRepository.deleteByUserIdAndId(userId, filterId)
|
||||||
|
|
||||||
|
override suspend fun getById(userId: Long, filterId: Long): Filter? =
|
||||||
|
filterQueryService.findByUserIdAndId(userId, filterId)?.let { toFilter(it) }
|
||||||
|
|
||||||
|
override suspend fun deleteKeyword(userId: Long, keywordId: Long) {
|
||||||
|
val id = filterQueryService.findByUserIdAndKeywordId(userId, keywordId)?.keywords?.singleOrNull()?.id ?: return
|
||||||
|
|
||||||
|
filterKeywordRepository.deleteById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getKeywordById(userId: Long, keywordId: Long): FilterKeyword? {
|
||||||
|
return filterQueryService
|
||||||
|
.findByUserIdAndKeywordId(userId, keywordId)
|
||||||
|
?.keywords
|
||||||
|
?.firstOrNull()
|
||||||
|
?.let { toFilterKeyword(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createFilter(userId: Long, filterPostRequest: FilterPostRequest): Filter {
|
||||||
|
val keywords = filterPostRequest.keywordsAttributes.orEmpty().map {
|
||||||
|
dev.usbharu.hideout.core.service.filter.FilterKeyword(
|
||||||
|
it.keyword,
|
||||||
|
if (it.regex == true) {
|
||||||
|
FilterMode.REGEX
|
||||||
|
} else if (it.wholeWord == true) {
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
} else {
|
||||||
|
FilterMode.NONE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return toFilter(
|
||||||
|
muteService.createFilter(
|
||||||
|
title = filterPostRequest.title,
|
||||||
|
context = filterPostRequest.context.map {
|
||||||
|
when (it) {
|
||||||
|
FilterPostRequest.Context.home -> home
|
||||||
|
FilterPostRequest.Context.notifications -> notifications
|
||||||
|
FilterPostRequest.Context.public -> public
|
||||||
|
FilterPostRequest.Context.thread -> thread
|
||||||
|
FilterPostRequest.Context.account -> account
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action = when (filterPostRequest.filterAction) {
|
||||||
|
FilterAction.warn -> warn
|
||||||
|
FilterAction.hide -> warn
|
||||||
|
null -> warn
|
||||||
|
},
|
||||||
|
keywords = keywords,
|
||||||
|
loginUser = userId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteFilterStatusById(userId: Long, filterPostsId: Long) = Unit
|
||||||
|
|
||||||
|
override suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus? = null
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
create table if not exists filters
|
||||||
|
(
|
||||||
|
id bigint primary key not null,
|
||||||
|
user_id bigint not null,
|
||||||
|
name varchar(255) not null,
|
||||||
|
context varchar(500) not null,
|
||||||
|
action varchar(255) not null,
|
||||||
|
constraint fk_filters_user_id__id foreign key (user_id) references actors (id) on delete cascade on update cascade
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists filter_keywords
|
||||||
|
(
|
||||||
|
id bigint primary key not null,
|
||||||
|
filter_id bigint not null,
|
||||||
|
keyword varchar(1000) 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
|
||||||
|
);
|
|
@ -21,6 +21,8 @@ tags:
|
||||||
description: media
|
description: media
|
||||||
- name: notification
|
- name: notification
|
||||||
description: notification
|
description: notification
|
||||||
|
- name: filter
|
||||||
|
description: filter
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/api/v2/instance:
|
/api/v2/instance:
|
||||||
|
@ -922,6 +924,424 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
|
|
||||||
|
/api/v2/filters:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Filter"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterPostRequest"
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterPostRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Filter"
|
||||||
|
|
||||||
|
/api/v2/filters/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Filter"
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterPutRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Filter"
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
/api/v2/filters/{filter_id}/keywords:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: filter_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/FilterKeyword"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: filter_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterKeywordsPostRequest"
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterKeywordsPostRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterKeyword"
|
||||||
|
|
||||||
|
/api/v2/filters/keywords/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterKeyword"
|
||||||
|
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterKeywordsPutRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterKeyword"
|
||||||
|
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
/api/v2/filters/{filter_id}/statuses:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: filter_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/FilterStatus"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: filter_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterStatusRequest"
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterStatusRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterStatus"
|
||||||
|
|
||||||
|
/api/v2/filters/statuses/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FilterStatus"
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
|
||||||
|
/api/v1/filters:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/V1Filter"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/V1FilterPostRequest"
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/V1FilterPostRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/V1Filter"
|
||||||
|
|
||||||
|
/api/v1/filters/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "read:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/V1Filter"
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/V1FilterPutRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/V1Filter"
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- filter
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:filters"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
V1MediaRequest:
|
V1MediaRequest:
|
||||||
|
@ -1476,6 +1896,8 @@ components:
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
context:
|
context:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- home
|
- home
|
||||||
|
@ -1518,6 +1940,191 @@ components:
|
||||||
status_id:
|
status_id:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
V1Filter:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
phrase:
|
||||||
|
type: string
|
||||||
|
context:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
enum:
|
||||||
|
- home
|
||||||
|
- notifications
|
||||||
|
- thread
|
||||||
|
- public
|
||||||
|
- account
|
||||||
|
expires_at:
|
||||||
|
type: string
|
||||||
|
irreversible:
|
||||||
|
type: boolean
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
V1FilterPostRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
phrase:
|
||||||
|
type: string
|
||||||
|
context:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- home
|
||||||
|
- notifications
|
||||||
|
- public
|
||||||
|
- thread
|
||||||
|
- account
|
||||||
|
irreversible:
|
||||||
|
type: boolean
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- phrase
|
||||||
|
- context
|
||||||
|
|
||||||
|
V1FilterPutRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
phrase:
|
||||||
|
type: string
|
||||||
|
context:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- home
|
||||||
|
- notifications
|
||||||
|
- public
|
||||||
|
- thread
|
||||||
|
- account
|
||||||
|
irreversible:
|
||||||
|
type: boolean
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
|
||||||
|
FilterPostRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
context:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- home
|
||||||
|
- notifications
|
||||||
|
- public
|
||||||
|
- thread
|
||||||
|
- account
|
||||||
|
filter_action:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- warn
|
||||||
|
- hide
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
keywords_attributes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/FilterPostRequestKeyword"
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- context
|
||||||
|
|
||||||
|
FilterPostRequestKeyword:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
regex:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
required:
|
||||||
|
- keyword
|
||||||
|
|
||||||
|
FilterKeywordsPostRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
regex:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
required:
|
||||||
|
- keyword
|
||||||
|
|
||||||
|
FilterKeywordsPutRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
regex:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
FilterPutRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
context:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- homa
|
||||||
|
- notifications
|
||||||
|
- public
|
||||||
|
- thread
|
||||||
|
- account
|
||||||
|
filter_action:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- warn
|
||||||
|
- hide
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
keywords_attributes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/FilterPubRequestKeyword"
|
||||||
|
|
||||||
|
FilterPubRequestKeyword:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
whole_word:
|
||||||
|
type: boolean
|
||||||
|
regex:
|
||||||
|
type: boolean
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
_destroy:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
FilterStatusRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status_id:
|
||||||
|
type: string
|
||||||
|
|
||||||
Instance:
|
Instance:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -0,0 +1,388 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.FilterType
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import utils.PostBuilder
|
||||||
|
|
||||||
|
class MuteProcessServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun 単純な文字列にマッチする() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 複数の文字列でマッチする() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mate",
|
||||||
|
FilterMode.NONE
|
||||||
|
),
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mata",
|
||||||
|
FilterMode.NONE
|
||||||
|
),
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 単語にマッチする() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 単語以外にはマッチしない() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mutetest")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 複数の単語にマッチする() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mate",
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
),
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mata",
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
),
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 正規表現も使える() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"e\\st",
|
||||||
|
FilterMode.REGEX
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "e t"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cw文字にマッチする() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(overview = "mute test", text = "hello")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"e\\st",
|
||||||
|
FilterMode.REGEX
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "e t"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 文字列と単語と正規表現を同時に使える() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"e\\st",
|
||||||
|
FilterMode.REGEX
|
||||||
|
),
|
||||||
|
FilterKeyword(
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.NONE
|
||||||
|
),
|
||||||
|
FilterKeyword(
|
||||||
|
3,
|
||||||
|
1,
|
||||||
|
"test",
|
||||||
|
FilterMode.WHOLE_WORD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 複数の投稿を処理できる() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"mute",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val posts = listOf(
|
||||||
|
PostBuilder.of(text = "mute"), PostBuilder.of(text = "mutes"), PostBuilder.of(text = "hoge")
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMutes(
|
||||||
|
posts,
|
||||||
|
FilterType.entries.toList(),
|
||||||
|
listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual)
|
||||||
|
.hasSize(2)
|
||||||
|
.containsEntry(posts[0], FilterResult(filterQueryModel, "mute"))
|
||||||
|
.containsEntry(posts[1], FilterResult(filterQueryModel, "mute"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 何もマッチしないとnullが返ってくる() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"fuga",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun Cwで何もマッチしないと本文を確認する() = runTest {
|
||||||
|
val muteProcessServiceImpl = MuteProcessServiceImpl()
|
||||||
|
|
||||||
|
val post = PostBuilder.of(overview = "hage", text = "mute test")
|
||||||
|
|
||||||
|
val filterQueryModel = FilterQueryModel(
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
"mute test",
|
||||||
|
FilterType.entries,
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"fuga",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val actual = muteProcessServiceImpl.processMute(
|
||||||
|
post, FilterType.entries.toList(), listOf(
|
||||||
|
filterQueryModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(actual).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package dev.usbharu.hideout.core.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.*
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryModel
|
||||||
|
import dev.usbharu.hideout.core.query.model.FilterQueryService
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class MuteServiceImplTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var filterRepository: FilterRepository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var filterKeywordRepository: FilterKeywordRepository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var filterQueryService: FilterQueryService
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private lateinit var muteServiceImpl: MuteServiceImpl
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun createFilter() = runTest {
|
||||||
|
whenever(filterRepository.generateId()).doReturn(1)
|
||||||
|
whenever(filterKeywordRepository.generateId()).doReturn(1)
|
||||||
|
|
||||||
|
whenever(filterRepository.save(any())).doAnswer { it.arguments[0]!! as Filter }
|
||||||
|
|
||||||
|
val createFilter = muteServiceImpl.createFilter(
|
||||||
|
title = "hoge",
|
||||||
|
context = listOf(FilterType.home, FilterType.public),
|
||||||
|
action = FilterAction.warn,
|
||||||
|
keywords = listOf(
|
||||||
|
FilterKeyword(
|
||||||
|
"fuga",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
loginUser = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(createFilter).isEqualTo(
|
||||||
|
FilterQueryModel(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"hoge",
|
||||||
|
listOf(FilterType.home, FilterType.public),
|
||||||
|
FilterAction.warn,
|
||||||
|
keywords = listOf(
|
||||||
|
dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword(1, 1, "fuga", FilterMode.NONE)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getFilters() = runTest {
|
||||||
|
whenever(filterQueryService.findByUserIdAndType(any(), any())).doReturn(
|
||||||
|
listOf(
|
||||||
|
FilterQueryModel(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"hoge",
|
||||||
|
listOf(FilterType.home),
|
||||||
|
FilterAction.warn,
|
||||||
|
listOf(
|
||||||
|
dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"fuga",
|
||||||
|
FilterMode.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
muteServiceImpl.getFilters(1, listOf(FilterType.home))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getFilters 何も指定しない`() = runTest {
|
||||||
|
whenever(filterQueryService.findByUserIdAndType(any(), eq(emptyList()))).doReturn(emptyList())
|
||||||
|
|
||||||
|
muteServiceImpl.getFilters(1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue