From 078547d5ffd61258640c8715530164d2b6e5db70 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:23:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E8=BF=BD=E5=8A=A0=E3=81=A8=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/application/filter/DeleteFilter.kt | 19 +++ .../hideout/core/application/filter/Filter.kt | 49 ++++++ .../core/application/filter/FilterKeyword.kt | 25 +++ .../core/application/filter/GetFilter.kt | 19 +++ .../core/application/filter/RegisterFilter.kt | 27 ++++ .../filter/RegisterFilterKeyword.kt | 24 +++ .../UserDeleteFilterApplicationService.kt | 40 +++++ .../filter/UserGetFilterApplicationService.kt | 41 +++++ .../UserRegisterFilterApplicationService.kt | 67 ++++++++ .../core/domain/model/filter/Filter.kt | 33 +++- .../domain/model/filter/FilterRepository.kt | 3 + .../exposed/FilterQueryMapper.kt | 50 ++++++ .../exposed/FilterResultRowMapper.kt | 35 +++++ .../ExposedFilterRepository.kt | 98 ++++++++++++ .../application/filter/DeleteFilterV1.kt | 19 +++ .../DeleteFilterV1ApplicationService.kt | 41 +++++ .../application/filter/GetFilterV1.kt | 19 +++ .../filter/GetFilterV1ApplicationService.kt | 61 ++++++++ .../interfaces/api/SpringFilterApi.kt | 144 ++++++++++++++++-- 19 files changed, 802 insertions(+), 12 deletions(-) create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt create mode 100644 hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt create mode 100644 hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt create mode 100644 hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt create mode 100644 hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt new file mode 100644 index 00000000..52a2bf0a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +data class DeleteFilter(val filterId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt new file mode 100644 index 00000000..0231663e --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.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.FilterContext + +data class Filter( + val filterId: Long, + val userDetailId: Long, + val name: String, + val filterContext: Set, + val filterAction: FilterAction, + val filterKeywords: Set, +) { + companion object { + fun of(filter: Filter): dev.usbharu.hideout.core.application.filter.Filter { + return Filter( + filterId = filter.id.id, + userDetailId = filter.userDetailId.id, + name = filter.name.name, + filterContext = filter.filterContext, + filterAction = filter.filterAction, + filterKeywords = filter.filterKeywords.map { + FilterKeyword( + it.id.id, + it.keyword.keyword, + it.mode + ) + }.toSet() + ) + } + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt new file mode 100644 index 00000000..bb58f6e6 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +import dev.usbharu.hideout.core.domain.model.filter.FilterMode + +data class FilterKeyword( + val id: Long, + val keyword: String, + val filterMode: FilterMode, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt new file mode 100644 index 00000000..b9089ab0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +data class GetFilter(val filterId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt new file mode 100644 index 00000000..3fd9a35f --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +import dev.usbharu.hideout.core.domain.model.filter.FilterAction +import dev.usbharu.hideout.core.domain.model.filter.FilterContext + +data class RegisterFilter( + val filterName: String, + val filterContext: Set, + val filterAction: FilterAction, + val filterKeywords: Set, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt new file mode 100644 index 00000000..b6e3ed31 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +import dev.usbharu.hideout.core.domain.model.filter.FilterMode + +data class RegisterFilterKeyword( + val keyword: String, + val filterMode: FilterMode, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt new file mode 100644 index 00000000..3b032fc4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.filter.FilterId +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserDeleteFilterApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, logger + ) { + companion object { + private val logger = LoggerFactory.getLogger(UserDeleteFilterApplicationService::class.java) + } + + override suspend fun internalExecute(command: DeleteFilter, executor: CommandExecutor) { + val filter = filterRepository.findByFilterId(FilterId(command.filterId)) ?: throw Exception("not found") + filterRepository.delete(filter) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt new file mode 100644 index 00000000..bf10cc6b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.filter.FilterId +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserGetFilterApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, logger + ) { + override suspend fun internalExecute(command: GetFilter, executor: CommandExecutor): Filter { + val filter = filterRepository.findByFilterId(FilterId(command.filterId)) ?: throw Exception("Not Found") + + return Filter.of(filter) + } + + companion object { + private val logger = LoggerFactory.getLogger(UserGetFilterApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt new file mode 100644 index 00000000..a1dc48a3 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.filter.FilterKeyword +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserRegisterFilterApplicationService( + private val idGenerateService: IdGenerateService, + private val filterRepository: FilterRepository, + transaction: Transaction, +) : + AbstractApplicationService( + transaction, logger + ) { + + companion object { + private val logger = LoggerFactory.getLogger(UserRegisterFilterApplicationService::class.java) + } + + override suspend fun internalExecute(command: RegisterFilter, executor: CommandExecutor): Filter { + require(executor is UserDetailGettableCommandExecutor) + + + val filter = dev.usbharu.hideout.core.domain.model.filter.Filter.create( + FilterId(idGenerateService.generateId()), + UserDetailId(executor.userDetailId), + FilterName(command.filterName), + command.filterContext, + command.filterAction, + command.filterKeywords + .map { + FilterKeyword( + FilterKeywordId(idGenerateService.generateId()), + FilterKeywordKeyword(it.keyword), + it.filterMode + ) + }.toSet() + ) + + filterRepository.save(filter) + return Filter.of(filter) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt index 9135e519..e174ec3b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt @@ -9,9 +9,9 @@ class Filter( val id: FilterId, val userDetailId: UserDetailId, var name: FilterName, - val filterContext: List, + val filterContext: Set, val filterAction: FilterAction, - filterKeywords: Set + filterKeywords: Set, ) { var filterKeywords = filterKeywords private set @@ -39,6 +39,17 @@ class Filter( .toRegex() } + fun reconstructWith(filterKeywords: Set): Filter { + return Filter( + this.id, + this.userDetailId, + this.name, + this.filterContext, + this.filterAction, + filterKeywords + ) + } + companion object { fun isAllow(user: UserDetail, action: Action, resource: Filter): Boolean { return when (action) { @@ -49,5 +60,23 @@ class Filter( enum class Action { SET_KEYWORDS } + + fun create( + id: FilterId, + userDetailId: UserDetailId, + name: FilterName, + filterContext: Set, + filterAction: FilterAction, + filterKeywords: Set, + ): Filter { + return Filter( + id, + userDetailId, + name, + filterContext, + filterAction, + filterKeywords + ) + } } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt index 990472f1..88ff3feb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt @@ -3,4 +3,7 @@ package dev.usbharu.hideout.core.domain.model.filter interface FilterRepository { suspend fun save(filter: Filter): Filter suspend fun delete(filter: Filter) + + suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? + suspend fun findByFilterId(filterId: FilterId): Filter? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt new file mode 100644 index 00000000..9fe7081d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.infrastructure.exposed + +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.infrastructure.exposedrepository.FilterKeywords +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Filters +import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.ResultRow +import org.springframework.stereotype.Component + +@Component +class FilterQueryMapper(private val filterResultRowMapper: ResultRowMapper) : QueryMapper { + override fun map(query: Query): List { + return query + .groupBy { it[Filters.id] } + .map { it.value } + .map { + it + .first() + .let(filterResultRowMapper::map) + .apply { + reconstructWith(it.mapNotNull { resultRow: ResultRow -> + FilterKeyword( + FilterKeywordId(resultRow.getOrNull(FilterKeywords.id) ?: return@mapNotNull null), + FilterKeywordKeyword( + resultRow.getOrNull(FilterKeywords.keyword) ?: return@mapNotNull null + ), + FilterMode.valueOf(resultRow.getOrNull(FilterKeywords.mode) ?: return@mapNotNull null) + ) + + }.toSet()) + } + } + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt new file mode 100644 index 00000000..13221bd7 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.infrastructure.exposed + +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Filters +import org.jetbrains.exposed.sql.ResultRow +import org.springframework.stereotype.Component + +@Component +class FilterResultRowMapper : ResultRowMapper { + override fun map(resultRow: ResultRow): Filter = Filter( + FilterId(resultRow[Filters.id]), + UserDetailId(resultRow[Filters.userId]), + FilterName(resultRow[Filters.name]), + resultRow[Filters.context].split(",").filter { it.isNotEmpty() }.map { FilterContext.valueOf(it) }.toSet(), + FilterAction.valueOf(resultRow[Filters.filterAction]), + emptySet() + ) +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt new file mode 100644 index 00000000..0bcbfc8e --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.filter.Filter +import dev.usbharu.hideout.core.domain.model.filter.FilterId +import dev.usbharu.hideout.core.domain.model.filter.FilterKeywordId +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import dev.usbharu.hideout.core.infrastructure.exposed.QueryMapper +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 filterQueryMapper: QueryMapper) : FilterRepository, + AbstractRepository() { + override suspend fun save(filter: Filter): Filter = query { + Filters.upsert { + it[id] = filter.id.id + it[userId] = filter.userDetailId.id + it[name] = filter.name.name + it[context] = filter.filterContext.joinToString(",") { it.name } + it[filterAction] = filter.filterAction.name + } + FilterKeywords.deleteWhere { + filterId eq filter.id.id + } + FilterKeywords.batchUpsert(filter.filterKeywords) { + this[FilterKeywords.id] = it.id.id + this[FilterKeywords.filterId] = filter.id.id + this[FilterKeywords.keyword] = it.keyword.keyword + this[FilterKeywords.mode] = it.mode.name + } + filter + } + + override suspend fun delete(filter: Filter): Unit = query { + FilterKeywords.deleteWhere { filterId eq filter.id.id } + Filters.deleteWhere { id eq filter.id.id } + } + + override suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? { + val filterId = FilterKeywords + .selectAll() + .where { FilterKeywords.id eq filterKeywordId.id } + .firstOrNull()?.get(FilterKeywords.filterId) ?: return null + val where = Filters.selectAll().where { Filters.id eq filterId } + return filterQueryMapper.map(where).firstOrNull() + } + + override suspend fun findByFilterId(filterId: FilterId): Filter? { + val where = Filters.selectAll().where { Filters.id eq filterId.id } + return filterQueryMapper.map(where).firstOrNull() + } + + override val logger: Logger + get() = Companion.logger + + companion object { + private val logger = LoggerFactory.getLogger(ExposedFilterRepository::class.java) + } +} + +object Filters : Table("filters") { + val id = long("id") + val userId = long("user_id").references(UserDetails.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + val name = varchar("name", 255) + val context = varchar("context", 500) + val filterAction = varchar("action", 255) + + override val primaryKey: PrimaryKey = PrimaryKey(id) +} + +object FilterKeywords : Table("filter_keywords") { + val id = long("id") + val filterId = + long("filter_id").references(Filters.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) + val keyword = varchar("keyword", 1000) + val mode = varchar("mode", 100) + + override val primaryKey: PrimaryKey = PrimaryKey(id) +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt new file mode 100644 index 00000000..6baf4976 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.mastodon.application.filter + +data class DeleteFilterV1(val filterKeywordId: Long) diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt new file mode 100644 index 00000000..88134308 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.mastodon.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.filter.FilterKeywordId +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class DeleteFilterV1ApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, logger + ) { + companion object { + private val logger = LoggerFactory.getLogger(DeleteFilterV1ApplicationService::class.java) + } + + override suspend fun internalExecute(command: DeleteFilterV1, executor: CommandExecutor) { + val filter = filterRepository.findByFilterKeywordId(FilterKeywordId(command.filterKeywordId)) + ?: throw Exception("Not Found") + filterRepository.delete(filter) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt new file mode 100644 index 00000000..e6d1c26c --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.mastodon.application.filter + +data class GetFilterV1(val filterKeywordId: Long) \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt new file mode 100644 index 00000000..22411fb9 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.mastodon.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.filter.FilterContext.* +import dev.usbharu.hideout.core.domain.model.filter.FilterKeywordId +import dev.usbharu.hideout.core.domain.model.filter.FilterMode +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.V1Filter +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class GetFilterV1ApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, logger + ) { + override suspend fun internalExecute(command: GetFilterV1, executor: CommandExecutor): V1Filter { + val filter = filterRepository.findByFilterKeywordId(FilterKeywordId(command.filterKeywordId)) + ?: throw Exception("Not Found") + + val filterKeyword = filter.filterKeywords.find { it.id.id == command.filterKeywordId } + return V1Filter( + id = filter.id.id.toString(), + phrase = filterKeyword?.keyword?.keyword, + context = filter.filterContext.map { + when (it) { + home -> V1Filter.Context.home + notifications -> V1Filter.Context.notifications + public -> V1Filter.Context.public + thread -> V1Filter.Context.thread + account -> V1Filter.Context.account + } + }, + expiresAt = null, + irreversible = false, + wholeWord = filterKeyword?.mode == FilterMode.WHOLE_WORD + ) + } + + companion object { + private val logger = LoggerFactory.getLogger(GetFilterV1ApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt index 9a4e7a05..4bfdbea5 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt @@ -16,21 +16,50 @@ package dev.usbharu.hideout.mastodon.interfaces.api +import dev.usbharu.hideout.core.application.filter.* +import dev.usbharu.hideout.core.domain.model.filter.FilterAction +import dev.usbharu.hideout.core.domain.model.filter.FilterContext +import dev.usbharu.hideout.core.domain.model.filter.FilterMode +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory +import dev.usbharu.hideout.mastodon.application.filter.DeleteFilterV1 +import dev.usbharu.hideout.mastodon.application.filter.DeleteFilterV1ApplicationService +import dev.usbharu.hideout.mastodon.application.filter.GetFilterV1 +import dev.usbharu.hideout.mastodon.application.filter.GetFilterV1ApplicationService import dev.usbharu.hideout.mastodon.interfaces.api.generated.FilterApi import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.* -import kotlinx.coroutines.flow.Flow +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Filter +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterKeyword +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterPostRequest.Context +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.V1FilterPostRequest.Context.* import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller @Controller -class SpringFilterApi : FilterApi { +class SpringFilterApi( + private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, + private val userRegisterFilterApplicationService: UserRegisterFilterApplicationService, + private val getFilterV1ApplicationService: GetFilterV1ApplicationService, + private val deleteFilterV1ApplicationService: DeleteFilterV1ApplicationService, + private val userDeleteFilterApplicationService: UserDeleteFilterApplicationService, + private val userGetFilterApplicationService: UserGetFilterApplicationService, +) : FilterApi { override suspend fun apiV1FiltersIdDelete(id: String): ResponseEntity { - return super.apiV1FiltersIdDelete(id) + return ResponseEntity.ok( + deleteFilterV1ApplicationService.execute( + DeleteFilterV1(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + ) } override suspend fun apiV1FiltersIdGet(id: String): ResponseEntity { - return super.apiV1FiltersIdGet(id) + return ResponseEntity.ok( + getFilterV1ApplicationService.execute( + GetFilterV1(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + ) } override suspend fun apiV1FiltersIdPut( @@ -45,7 +74,33 @@ class SpringFilterApi : FilterApi { } override suspend fun apiV1FiltersPost(v1FilterPostRequest: V1FilterPostRequest): ResponseEntity { - return super.apiV1FiltersPost(v1FilterPostRequest) + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + val filterMode = if (v1FilterPostRequest.wholeWord == true) { + FilterMode.WHOLE_WORD + } else { + FilterMode.NONE + } + val filterContext = v1FilterPostRequest.context.map { + when (it) { + home -> FilterContext.home + notifications -> FilterContext.notifications + public -> FilterContext.public + thread -> FilterContext.thread + account -> FilterContext.account + } + }.toSet() + val filter = userRegisterFilterApplicationService.execute( + RegisterFilter( + v1FilterPostRequest.phrase, filterContext, FilterAction.warn, + setOf(RegisterFilterKeyword(v1FilterPostRequest.phrase, filterMode)) + ), executor + ) + return ResponseEntity.ok( + getFilterV1ApplicationService.execute( + GetFilterV1(filter.filterKeywords.first().id), + executor + ) + ) } override suspend fun apiV2FiltersFilterIdKeywordsPost( @@ -63,13 +118,50 @@ class SpringFilterApi : FilterApi { } override suspend fun apiV2FiltersIdDelete(id: String): ResponseEntity { - return super.apiV2FiltersIdDelete(id) + userDeleteFilterApplicationService.execute( + DeleteFilter(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + return ResponseEntity.ok(Unit) } override suspend fun apiV2FiltersIdGet(id: String): ResponseEntity { - return super.apiV2FiltersIdGet(id) + val filter = userGetFilterApplicationService.execute( + GetFilter(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + return ResponseEntity.ok( + filter(filter) + ) } + private fun filter(filter: dev.usbharu.hideout.core.application.filter.Filter) = Filter( + id = filter.filterId.toString(), + title = filter.name, + context = filter.filterContext.map { + when (it) { + FilterContext.home -> Filter.Context.home + FilterContext.notifications -> Filter.Context.notifications + FilterContext.public -> Filter.Context.public + FilterContext.thread -> Filter.Context.thread + FilterContext.account -> Filter.Context.account + } + }, + expiresAt = null, + filterAction = when (filter.filterAction) { + FilterAction.warn -> Filter.FilterAction.warn + FilterAction.hide -> Filter.FilterAction.hide + + }, + keywords = filter.filterKeywords.map { + FilterKeyword( + it.id.toString(), + it.keyword, + it.filterMode == FilterMode.WHOLE_WORD + ) + }, statuses = null + ) + override suspend fun apiV2FiltersIdPut( id: String, title: String?, @@ -99,14 +191,46 @@ class SpringFilterApi : FilterApi { } override suspend fun apiV2FiltersPost(filterPostRequest: FilterPostRequest): ResponseEntity { - return super.apiV2FiltersPost(filterPostRequest) + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + val filter = userRegisterFilterApplicationService.execute( + RegisterFilter( + filterName = filterPostRequest.title, + filterContext = filterPostRequest.context.map { + when (it) { + Context.home -> FilterContext.home + Context.notifications -> FilterContext.notifications + Context.public -> FilterContext.public + Context.thread -> FilterContext.thread + Context.account -> FilterContext.account + } + }.toSet(), + filterAction = when (filterPostRequest.filterAction) { + FilterPostRequest.FilterAction.warn -> FilterAction.warn + FilterPostRequest.FilterAction.hide -> FilterAction.hide + null -> FilterAction.warn + }, + filterKeywords = filterPostRequest.keywordsAttributes.orEmpty().map { + RegisterFilterKeyword( + it.keyword, + if (it.regex == true) { + FilterMode.REGEX + } else if (it.wholeWord == true) { + FilterMode.WHOLE_WORD + } else { + FilterMode.NONE + } + ) + }.toSet() + ), executor + ) + return ResponseEntity.ok(filter(filter)) } override suspend fun apiV2FiltersStatusesIdDelete(id: String): ResponseEntity { - return super.apiV2FiltersStatusesIdDelete(id) + return ResponseEntity.notFound().build() } override suspend fun apiV2FiltersStatusesIdGet(id: String): ResponseEntity { - return super.apiV2FiltersStatusesIdGet(id) + return ResponseEntity.notFound().build() } } \ No newline at end of file