FilterCreatedArticleUseCase.kt
package com.example.realworldkotlinspringbootjdbc.usecase.article
import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.Either.Right
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.Validated.Invalid
import arrow.core.Validated.Valid
import arrow.core.left
import arrow.core.none
import arrow.core.right
import arrow.core.toOption
import com.example.realworldkotlinspringbootjdbc.domain.ArticleRepository
import com.example.realworldkotlinspringbootjdbc.domain.ProfileRepository
import com.example.realworldkotlinspringbootjdbc.domain.RegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.article.FilterParameters
import com.example.realworldkotlinspringbootjdbc.domain.article.Tag
import com.example.realworldkotlinspringbootjdbc.domain.user.Username
import com.example.realworldkotlinspringbootjdbc.usecase.shared_model.CreatedArticleWithAuthor
import com.example.realworldkotlinspringbootjdbc.util.MyError
import org.springframework.stereotype.Service
interface FilterCreatedArticleUseCase {
/**
* フィルタされた作成済み記事一覧
*
* @property articles フィルタされた作成済み記事の一覧(最大数: limit)
* @property articlesCount フィルタで引っかかった作成済み記事の数(メタデータ)
*/
data class FilteredCreatedArticleList(
val articles: List<CreatedArticleWithAuthor>,
val articlesCount: Int,
)
/**
* 作成済み記事をフィルタ(フィルタがなければただの一覧取得)
*
* @param tag タグでフィルタ(1つだけ、複数ではできない)
* @param author 作成済み記事の著者でフィルタ(ユーザー名, 1人分だけ)
* @param favoritedByUsernamea 特定の登録済みユーザーがお気に入りしているかどうかでフィルタ(ユーザー名, 1人分だけ)
* @param limit 1度に表示する表示数
* @param offset 作成済み記事N個のうち、Offset個分だけskipしてそこから表示(0の場合、0個分skip)
* @return エラー or 作成済み記事一覧
*/
fun execute(
tag: String? = null,
author: String? = null,
favoritedByUsername: String? = null,
limit: Int,
offset: Int,
currentUser: Option<RegisteredUser> = none()
): Either<Error, FilteredCreatedArticleList> = throw NotImplementedError()
/**
* - フィルタ用パラメータのバリデーションエラー
* - Offset値がフィルタ済み作成済み記事の数を超過エラー
*/
sealed interface Error : MyError {
data class FilterParametersValidationErrors(override val errors: List<MyError.ValidationError>) :
Error,
MyError.ValidationErrors
data class OffsetOverCreatedArticlesCountError(
val filterParameters: FilterParameters,
val articlesCount: Int,
) : Error, MyError.Basic
// TODO: いらないかも:要検討
data class NotFoundUser(
val user: RegisteredUser,
override val cause: MyError
) : Error, MyError.MyErrorWithMyError
}
}
@Service
class FilterCreatedArticleUseCaseImpl(
val articleRepository: ArticleRepository,
val profileRepository: ProfileRepository,
) : FilterCreatedArticleUseCase {
/**
* 1. フィルタパラメータ
* 2. 誰かのお気に入りの作成済み記事フィルタ or 全作成済み記事
* 3. 作成済み記事にAuthor情報を付与
* 4. 著者名やタグでフィルタ
* 5. LimitとOffsetで返すListを調整
*/
override fun execute(
tag: String?,
author: String?,
favoritedByUsername: String?,
limit: Int,
offset: Int,
currentUser: Option<RegisteredUser>
): Either<FilterCreatedArticleUseCase.Error, FilterCreatedArticleUseCase.FilteredCreatedArticleList> {
/**
* 1. フィルタパラメータ
*/
val filterParameters = when (
val filterParameters = FilterParameters.new(
tag = tag,
author = author,
favoritedByUsername = favoritedByUsername,
limit = limit,
offset = offset,
)
) {
is Invalid -> return FilterCreatedArticleUseCase.Error.FilterParametersValidationErrors(filterParameters.value).left()
is Valid -> filterParameters.value
}
/**
* 2. 誰かのお気に入りの作成済み記事フィルタ or 全作成済み記事
*/
val filterTargetBase = when (val optionalUserName = filterParameters.favoritedByUsername) {
/**
* 特定の他ユーザーがお気に入りしているフィルタ 無し
* - 全ての作成済み記事
*/
None -> when (val list = articleRepository.all(currentUser.map { it.userId })) {
is Left -> TODO("allの具体的なエラーが無いため、現在この分岐に入らない想定")
is Right -> list.value.toSet()
}
/**
* 特定の他ユーザーがお気に入りしているフィルタ 有り
*/
is Some -> when (
val otherUser =
profileRepository.findByUsername(Username.newWithoutValidation(optionalUserName.value))
) {
/**
* ユーザ名に該当する他ユーザーが見つからなかった
* - フィルタしても該当する記事は1つも見つからない
*/
is Left -> emptySet()
/**
* ユーザ名に該当する他ユーザーが見つかった
* - フィルタ済み 作成済み記事一覧
*/
is Right -> articleRepository.filterFavoritedByOtherUserId(
otherUser.value.userId,
currentUser.map { it.userId }
).fold(
{ TODO("filterFavoritedByOtherUserIdのエラーが無いため、現在この分岐に入らない想定") },
{ it.toSet() }
)
}
}
/**
* 3. 作成済み記事にAuthor情報を付与
* 4. 著者名やタグでフィルタ
*/
val allFilteredCreatedArticleList = profileRepository.filterByUserIds(
userIds = filterTargetBase.map { it.authorId }.toSet(),
viewpointUserId = currentUser.map { it.userId }
).fold(
{ TODO("filterByUserIdsのエラーが無いため、この分岐に入ることは想定さていない") },
/**
* 3. 作成済み記事にAuthor情報を付与
*/
{ authors ->
filterTargetBase.map { createdArticle ->
authors.find { author -> createdArticle.authorId == author.userId }.toOption().fold(
{ TODO("必ず見つかる想定なため、この分岐に入ることはない") },
{ CreatedArticleWithAuthor(article = createdArticle, author = it) }
)
}.toSet()
}
).filter { createdArticleWithAuthor ->
filterParameters.author.fold(
/**
* 著者名によるフィルタ 無し
*/
{ true },
/**
* 著者名によるフィルタ 有り
*/
{ createdArticleWithAuthor.author.username == Username.newWithoutValidation(it) }
)
}.filter { createdArticleWithAuthor ->
filterParameters.tag.fold(
/**
* タグによるフィルタ 無し
*/
{ true },
/**
* タグによるフィルタ 無し
*/
{ createdArticleWithAuthor.article.hasTag(Tag.newWithoutValidation(it)) }
)
}.toList().sortedBy { it.article.id.value }
/**
* 5. LimitとOffsetで返すListを調整
* - フィルタ後のListのサイズを指定したoffsetが超えていたらエラー
*/
return if (allFilteredCreatedArticleList.size < filterParameters.offset) {
FilterCreatedArticleUseCase.Error.OffsetOverCreatedArticlesCountError(
filterParameters = filterParameters,
articlesCount = allFilteredCreatedArticleList.size
).left()
} else {
FilterCreatedArticleUseCase.FilteredCreatedArticleList(
articles = allFilteredCreatedArticleList
.slice(filterParameters.offset until allFilteredCreatedArticleList.size)
.take(filterParameters.limit),
articlesCount = allFilteredCreatedArticleList.size
).right()
}
}
}