feat: ミュートのMastodon互換APIを実装

This commit is contained in:
usbharu 2024-02-12 12:23:54 +09:00
parent 01ae2268d5
commit 5cb014730e
6 changed files with 397 additions and 7 deletions

View File

@ -6,6 +6,9 @@ interface FilterRepository {
suspend fun save(filter: Filter): Filter suspend fun save(filter: Filter): Filter
suspend fun findById(id: Long): 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 findByUserIdAndType(userId: Long, types: List<FilterType>): List<Filter>
suspend fun deleteById(id: Long) suspend fun deleteById(id: Long)
suspend fun deleteByUserIdAndId(userId: Long, id: Long)
} }

View File

@ -4,4 +4,7 @@ import dev.usbharu.hideout.core.domain.model.filter.FilterType
interface FilterQueryService { interface FilterQueryService {
suspend fun findByUserIdAndType(userId: Long, types: List<FilterType>): List<FilterQueryModel> 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?
} }

View File

@ -1,6 +1,5 @@
package dev.usbharu.hideout.core.service.filter 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.FilterAction
import dev.usbharu.hideout.core.domain.model.filter.FilterType import dev.usbharu.hideout.core.domain.model.filter.FilterType
import dev.usbharu.hideout.core.query.model.FilterQueryModel import dev.usbharu.hideout.core.query.model.FilterQueryModel
@ -8,12 +7,11 @@ import dev.usbharu.hideout.core.query.model.FilterQueryModel
interface MuteService { interface MuteService {
suspend fun createFilter( suspend fun createFilter(
title: String, title: String,
name: String,
context: List<FilterType>, context: List<FilterType>,
action: FilterAction, action: FilterAction,
keywords: List<FilterKeyword>, keywords: List<FilterKeyword>,
loginUser: Long loginUser: Long
): Filter ): FilterQueryModel
suspend fun getFilters(userId: Long, types: List<FilterType> = emptyList()): List<FilterQueryModel> suspend fun getFilters(userId: Long, types: List<FilterType> = emptyList()): List<FilterQueryModel>

View File

@ -17,16 +17,15 @@ class MuteServiceImpl(
) : MuteService { ) : MuteService {
override suspend fun createFilter( override suspend fun createFilter(
title: String, title: String,
name: String,
context: List<FilterType>, context: List<FilterType>,
action: FilterAction, action: FilterAction,
keywords: List<FilterKeyword>, keywords: List<FilterKeyword>,
loginUser: Long loginUser: Long
): Filter { ): FilterQueryModel {
val filter = Filter( val filter = Filter(
filterRepository.generateId(), filterRepository.generateId(),
loginUser, loginUser,
name, title,
context, context,
action action
) )
@ -42,7 +41,8 @@ class MuteServiceImpl(
filterKeywordRepository.saveAll(filterKeywordList) filterKeywordRepository.saveAll(filterKeywordList)
return filterRepository.save(filter) val savedFilter = filterRepository.save(filter)
return FilterQueryModel.of(savedFilter, filterKeywordList)
} }
override suspend fun getFilters(userId: Long, types: List<FilterType>): List<FilterQueryModel> = override suspend fun getFilters(userId: Long, types: List<FilterType>): List<FilterQueryModel> =

View File

@ -0,0 +1,98 @@
package dev.usbharu.hideout.mastodon.interfaces.api.filter
import dev.usbharu.hideout.controller.mastodon.generated.FilterApi
import dev.usbharu.hideout.domain.mastodon.model.generated.*
import kotlinx.coroutines.flow.Flow
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
@Controller
class MastodonFilterApiController : FilterApi {
override suspend fun apiV1FiltersIdDelete(id: String): ResponseEntity<Any> {
return super.apiV1FiltersIdDelete(id)
}
override suspend fun apiV1FiltersIdGet(
id: String
): ResponseEntity<V1Filter> {
return super.apiV1FiltersIdGet(id)
}
override suspend fun apiV1FiltersIdPut(
id: String,
phrase: String?,
context: List<String>?,
irreversible: Boolean?,
wholeWord: Boolean?,
expiresIn: Int?
): ResponseEntity<V1Filter> {
return super.apiV1FiltersIdPut(id, phrase, context, irreversible, wholeWord, expiresIn)
}
override suspend fun apiV1FiltersPost(v1FilterPostRequest: V1FilterPostRequest): ResponseEntity<V1Filter> {
return super.apiV1FiltersPost(v1FilterPostRequest)
}
override suspend fun apiV2FiltersFilterIdKeywordsPost(
filterId: String,
filterKeywordsPostRequest: FilterKeywordsPostRequest
): ResponseEntity<FilterKeyword> {
return super.apiV2FiltersFilterIdKeywordsPost(filterId, filterKeywordsPostRequest)
}
override suspend fun apiV2FiltersFilterIdStatusesPost(
filterId: String,
filterStatusRequest: FilterStatusRequest
): ResponseEntity<FilterStatus> {
return super.apiV2FiltersFilterIdStatusesPost(filterId, filterStatusRequest)
}
override suspend fun apiV2FiltersIdDelete(id: String): ResponseEntity<Any> {
return super.apiV2FiltersIdDelete(id)
}
override suspend fun apiV2FiltersIdGet(id: String): ResponseEntity<Filter> {
return super.apiV2FiltersIdGet(id)
}
override suspend fun apiV2FiltersIdPut(
id: String,
title: String?,
context: List<String>?,
filterAction: String?,
expiresIn: Int?,
keywordsAttributes: List<FilterPubRequestKeyword>?
): ResponseEntity<Filter> {
return super.apiV2FiltersIdPut(id, title, context, filterAction, expiresIn, keywordsAttributes)
}
override suspend fun apiV2FiltersKeywordsIdDelete(id: String): ResponseEntity<Any> {
return super.apiV2FiltersKeywordsIdDelete(id)
}
override suspend fun apiV2FiltersKeywordsIdGet(id: String): ResponseEntity<FilterKeyword> {
return super.apiV2FiltersKeywordsIdGet(id)
}
override suspend fun apiV2FiltersKeywordsIdPut(
id: String,
keyword: String?,
wholeWord: Boolean?,
regex: Boolean?
): ResponseEntity<FilterKeyword> {
return super.apiV2FiltersKeywordsIdPut(id, keyword, wholeWord, regex)
}
override suspend fun apiV2FiltersPost(filterPostRequest: FilterPostRequest): ResponseEntity<Filter> {
return super.apiV2FiltersPost(filterPostRequest)
}
override suspend fun apiV2FiltersStatusesIdDelete(id: String): ResponseEntity<Any> {
return super.apiV2FiltersStatusesIdDelete(id)
}
override suspend fun apiV2FiltersStatusesIdGet(id: String): ResponseEntity<FilterStatus> {
return super.apiV2FiltersStatusesIdGet(id)
}
}

View File

@ -0,0 +1,288 @@
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
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)
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 {
when (it) {
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> {
return emptyFlow()
}
override suspend fun addFilterStatus(userId: Long, filterId: Long, filterStatusRequest: FilterStatusRequest) {
return
}
override fun filters(userId: Long): Flow<Filter> {
return 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) {
return
}
override suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus? {
return null
}
}