ProfileController.kt

package com.example.realworldkotlinspringbootjdbc.presentation

import arrow.core.Some
import arrow.core.none
import arrow.core.toOption
import com.example.realworldkotlinspringbootjdbc.openapi.generated.controller.ProfileApi
import com.example.realworldkotlinspringbootjdbc.openapi.generated.model.GenericErrorModel
import com.example.realworldkotlinspringbootjdbc.openapi.generated.model.GenericErrorModelErrors
import com.example.realworldkotlinspringbootjdbc.openapi.generated.model.Profile
import com.example.realworldkotlinspringbootjdbc.openapi.generated.model.ProfileResponse
import com.example.realworldkotlinspringbootjdbc.presentation.shared.RealworldAuthenticationUseCaseUnauthorizedException
import com.example.realworldkotlinspringbootjdbc.usecase.profile.FollowProfileUseCase
import com.example.realworldkotlinspringbootjdbc.usecase.profile.ShowProfileUseCase
import com.example.realworldkotlinspringbootjdbc.usecase.profile.UnfollowProfileUseCase
import com.example.realworldkotlinspringbootjdbc.usecase.shared.RealworldAuthenticationUseCase
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestController

@RestController
class ProfileController(
    val realworldAuthenticationUseCase: RealworldAuthenticationUseCase,
    val showProfileUseCase: ShowProfileUseCase,
    val followProfileUseCase: FollowProfileUseCase,
    val unfollowProfileUseCase: UnfollowProfileUseCase,
) : ProfileApi {
    override fun getProfileByUsername(username: String, authorization: String?): ResponseEntity<ProfileResponse> {
        /**
         * JWT 認証
         * - Authorization ヘッダーなし -> none
         * - Authorization ヘッダーあり、JWT 認証失敗 -> none
         * - Authorization ヘッダーあり、JWT 認証成功 -> RegisteredUser
         */
        val currentUser = authorization.toOption().fold(
            { none() },
            {
                realworldAuthenticationUseCase.execute(it).fold(
                    { none() },
                    { result -> Some(result) }
                )
            }
        )

        val showProfileUseCaseResult = showProfileUseCase.execute(username, currentUser).fold(
            { throw ShowProfileUseCaseErrorException(it) },
            { it }
        )

        return ResponseEntity(
            ProfileResponse(
                Profile(
                    username = showProfileUseCaseResult.username.value,
                    bio = showProfileUseCaseResult.bio.value,
                    image = showProfileUseCaseResult.image.value,
                    following = showProfileUseCaseResult.following
                ),
            ),
            HttpStatus.OK
        )
    }

    data class ShowProfileUseCaseErrorException(val error: ShowProfileUseCase.Error) : Exception()

    @ExceptionHandler(value = [ShowProfileUseCaseErrorException::class])
    fun onShowProfileUseCaseErrorException(e: ShowProfileUseCaseErrorException): ResponseEntity<GenericErrorModel> =
        when (val error = e.error) {
            /**
             * Username が不正だった場合
             */
            is ShowProfileUseCase.Error.InvalidUsername -> ResponseEntity(
                GenericErrorModel(GenericErrorModelErrors(body = error.errors.map { it.message })),
                HttpStatus.UNPROCESSABLE_ENTITY
            )
            /**
             * Username に該当する登録済ユーザーが見つからなかった場合
             */
            is ShowProfileUseCase.Error.NotFound -> ResponseEntity(
                GenericErrorModel(GenericErrorModelErrors(body = listOf("プロフィールが見つかりませんでした"))),
                HttpStatus.NOT_FOUND
            )
        }

    override fun followUserByUsername(authorization: String, username: String): ResponseEntity<ProfileResponse> {
        /**
         * JWT 認証
         */
        val currentUser = realworldAuthenticationUseCase.execute(authorization).fold(
            { throw RealworldAuthenticationUseCaseUnauthorizedException(it) },
            { it }
        )

        val followProfileResult = followProfileUseCase.execute(username, currentUser).fold(
            { throw FollowProfileUseCaseErrorException(it) },
            { it }
        )
        return ResponseEntity(
            ProfileResponse(
                Profile(
                    username = followProfileResult.username.value,
                    bio = followProfileResult.bio.value,
                    image = followProfileResult.image.value,
                    following = followProfileResult.following
                ),
            ),
            HttpStatus.OK
        )
    }

    data class FollowProfileUseCaseErrorException(val error: FollowProfileUseCase.Error) : Exception()

    @ExceptionHandler(value = [FollowProfileUseCaseErrorException::class])
    fun onFollowProfileUseCaseErrorException(e: FollowProfileUseCaseErrorException): ResponseEntity<GenericErrorModel> =
        when (val error = e.error) {
            /**
             * Username が不正だった場合
             */
            is FollowProfileUseCase.Error.InvalidUsername -> ResponseEntity(
                GenericErrorModel(GenericErrorModelErrors(body = error.errors.map { it.message })),
                HttpStatus.UNPROCESSABLE_ENTITY
            )

            /**
             * Username に該当する登録済ユーザーが見つからなかった場合
             */
            is FollowProfileUseCase.Error.NotFound -> ResponseEntity(
                GenericErrorModel(GenericErrorModelErrors(body = listOf("プロフィールが見つかりませんでした"))),
                HttpStatus.NOT_FOUND
            )
        }

    override fun unfollowUserByUsername(authorization: String, username: String): ResponseEntity<ProfileResponse> {
        /**
         * JWT 認証
         */
        val currentUser = realworldAuthenticationUseCase.execute(authorization).fold(
            { throw RealworldAuthenticationUseCaseUnauthorizedException(it) },
            { it }
        )

        val unfollowProfileResult = unfollowProfileUseCase.execute(username, currentUser).fold(
            { throw UnfollowProfileUseCaseErrorException(it) },
            { it }
        )
        return ResponseEntity(
            ProfileResponse(
                Profile(
                    username = unfollowProfileResult.username.value,
                    bio = unfollowProfileResult.bio.value,
                    image = unfollowProfileResult.image.value,
                    following = unfollowProfileResult.following
                ),
            ),
            HttpStatus.OK
        )
    }

    data class UnfollowProfileUseCaseErrorException(val error: UnfollowProfileUseCase.Error) : Exception()

    @ExceptionHandler(value = [UnfollowProfileUseCaseErrorException::class])
    fun onUnfollowProfileUseCaseErrorException(e: UnfollowProfileUseCaseErrorException): ResponseEntity<GenericErrorModel> =
        when (val error = e.error) {
            /**
             * Username が不正だった場合
             */
            is UnfollowProfileUseCase.Error.InvalidUsername -> ResponseEntity(
                GenericErrorModel(GenericErrorModelErrors(body = error.errors.map { it.message })),
                HttpStatus.UNPROCESSABLE_ENTITY
            )

            is UnfollowProfileUseCase.Error.NotFound -> ResponseEntity(
                GenericErrorModel(GenericErrorModelErrors(body = listOf("プロフィールが見つかりませんでした"))),
                HttpStatus.NOT_FOUND
            )
        }
}