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:
|
||||
debug:
|
||||
trace-query-exception: true
|
||||
trace-query-call: true
|
||||
url: "https://example.com"
|
||||
use-mongodb: true
|
||||
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,41 +180,51 @@ class APNoteServiceImpl(
|
|||
}.map { it.id }
|
||||
|
||||
val createPost =
|
||||
if (quote != null) {
|
||||
postBuilder.quoteRepostOf(
|
||||
id = postRepository.generateId(),
|
||||
actorId = person.second.id,
|
||||
content = note.content,
|
||||
createdAt = Instant.parse(note.published),
|
||||
visibility = visibility,
|
||||
url = note.id,
|
||||
replyId = reply?.id,
|
||||
sensitive = note.sensitive,
|
||||
apId = note.id,
|
||||
mediaIds = mediaList,
|
||||
emojiIds = emojis,
|
||||
repost = quote
|
||||
)
|
||||
} else {
|
||||
postBuilder.of(
|
||||
id = postRepository.generateId(),
|
||||
actorId = person.second.id,
|
||||
content = note.content,
|
||||
createdAt = Instant.parse(note.published).toEpochMilli(),
|
||||
visibility = visibility,
|
||||
url = note.id,
|
||||
replyId = reply?.id,
|
||||
sensitive = note.sensitive,
|
||||
apId = note.id,
|
||||
mediaIds = mediaList,
|
||||
emojiIds = emojis
|
||||
)
|
||||
}
|
||||
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(
|
||||
id = postRepository.generateId(),
|
||||
actorId = person.second.id,
|
||||
content = note.content,
|
||||
createdAt = Instant.parse(note.published),
|
||||
visibility = visibility,
|
||||
url = note.id,
|
||||
replyId = reply?.id,
|
||||
sensitive = note.sensitive,
|
||||
apId = note.id,
|
||||
mediaIds = mediaList,
|
||||
emojiIds = emojis,
|
||||
repost = quote
|
||||
)
|
||||
} else {
|
||||
postBuilder.of(
|
||||
id = postRepository.generateId(),
|
||||
actorId = person.second.id,
|
||||
content = note.content,
|
||||
createdAt = Instant.parse(note.published).toEpochMilli(),
|
||||
visibility = visibility,
|
||||
url = note.id,
|
||||
replyId = reply?.id,
|
||||
sensitive = note.sensitive,
|
||||
apId = note.id,
|
||||
mediaIds = mediaList,
|
||||
emojiIds = emojis
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun buildEmojis(note: Note) = note.tag
|
||||
.filterIsInstance<Emoji>()
|
||||
.map {
|
||||
|
|
|
@ -26,8 +26,7 @@ import org.springframework.context.annotation.Bean
|
|||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Primary
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.HttpMethod.GET
|
||||
import org.springframework.http.HttpMethod.POST
|
||||
import org.springframework.http.HttpMethod.*
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
|
||||
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/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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
id: Long,
|
||||
actorId: Long,
|
||||
|
@ -110,24 +111,25 @@ data class Post private constructor(
|
|||
require(actorId >= 0) { "actorId must be greater than or equal to 0." }
|
||||
|
||||
return Post(
|
||||
id,
|
||||
actorId,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
createdAt.toEpochMilli(),
|
||||
fixedVisibility,
|
||||
url,
|
||||
repost.id,
|
||||
null,
|
||||
false,
|
||||
apId,
|
||||
emptyList(),
|
||||
false,
|
||||
emptyList()
|
||||
id = id,
|
||||
actorId = actorId,
|
||||
overview = null,
|
||||
content = "",
|
||||
text = "",
|
||||
createdAt = createdAt.toEpochMilli(),
|
||||
visibility = fixedVisibility,
|
||||
url = url,
|
||||
repostId = repost.id,
|
||||
replyId = null,
|
||||
sensitive = false,
|
||||
apId = apId,
|
||||
mediaIds = emptyList(),
|
||||
delted = false,
|
||||
emojiIds = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun quoteRepostOf(
|
||||
id: 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 {
|
||||
modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
||||
} catch (ignore: Exception) {
|
||||
} catch (exception: Exception) {
|
||||
try {
|
||||
requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
|
||||
} 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")
|
||||
interface AccountApiService {
|
||||
|
||||
@Suppress("ongParameterList")
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun accountsStatuses(
|
||||
userid: Long,
|
||||
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
|
||||
- name: notification
|
||||
description: notification
|
||||
- name: filter
|
||||
description: filter
|
||||
|
||||
paths:
|
||||
/api/v2/instance:
|
||||
|
@ -922,6 +924,424 @@ paths:
|
|||
schema:
|
||||
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:
|
||||
schemas:
|
||||
V1MediaRequest:
|
||||
|
@ -1476,13 +1896,15 @@ components:
|
|||
title:
|
||||
type: string
|
||||
context:
|
||||
type: string
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
expires_at:
|
||||
type: string
|
||||
nullable: true
|
||||
|
@ -1518,6 +1940,191 @@ components:
|
|||
status_id:
|
||||
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:
|
||||
type: object
|
||||
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