CommentWithAuthorsQueryModelImpl.kt

package com.example.realworldkotlinspringbootjdbc.infra

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.right
import com.example.realworldkotlinspringbootjdbc.domain.Comment
import com.example.realworldkotlinspringbootjdbc.domain.OtherUser
import com.example.realworldkotlinspringbootjdbc.domain.RegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.comment.Body
import com.example.realworldkotlinspringbootjdbc.domain.comment.CommentId
import com.example.realworldkotlinspringbootjdbc.domain.user.Bio
import com.example.realworldkotlinspringbootjdbc.domain.user.Image
import com.example.realworldkotlinspringbootjdbc.domain.user.UserId
import com.example.realworldkotlinspringbootjdbc.domain.user.Username
import com.example.realworldkotlinspringbootjdbc.usecase.comment.CommentWithAuthor
import com.example.realworldkotlinspringbootjdbc.usecase.comment.CommentWithAuthorsQueryModel
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.stereotype.Repository
import java.text.SimpleDateFormat

@Repository
class CommentWithAuthorsQueryModelImpl(val namedParameterJdbcTemplate: NamedParameterJdbcTemplate) :
    CommentWithAuthorsQueryModel {
    override fun fetchList(
        comments: List<Comment>,
        currentUser: Option<RegisteredUser>
    ): Either<CommentWithAuthorsQueryModel.FetchListError, List<CommentWithAuthor>> {
        /**
         * 作成済記事のコメントが 0 件だった場合、早期リターン
         */
        when (comments.isEmpty()) {
            true -> return listOf<CommentWithAuthor>().right()
            false -> {}
        }

        /**
         * ログイン状態によって、author の following の関係を取得するか分岐
         * - 未ログイン -> author.following = 0(未フォロー)固定
         * - ログイン済 -> author.following = 0(未フォロー)または 1(フォロー)
         */
        val commentWithAuthorsFromDb = when (currentUser) {
            /**
             * 未ログインのとき
             */
            is None -> {
                namedParameterJdbcTemplate.queryForList(
                    """
                        WITH other_users AS (
                            SELECT
                                users.id AS id
                                , users.username AS username
                                , profiles.bio AS bio
                                , profiles.image AS image
                                , 0 AS following_flg
                            FROM
                                users
                                JOIN
                                    profiles
                                ON
                                    profiles.user_id = users.id
                        )
                        SELECT
                            ac.id AS comment_id
                            , ac.body AS body
                            , ac.created_at AS created_at
                            , ac.updated_at AS updated_at
                            , ac.author_id AS author_id
                            , ou.id AS user_id
                            , ou.username AS username
                            , ou.bio AS bio
                            , ou.image AS image
                            , ou.following_flg AS following_flg
                        FROM
                            article_comments ac
                            JOIN
                                other_users ou
                            ON
                                ac.author_id = ou.id
                        WHERE
                            ac.id IN (:comment_ids)
                        ORDER BY 
                            ac.id
                        ;
                    """.trimIndent(),
                    MapSqlParameterSource().addValue("comment_ids", comments.map { it.id.value }.toSet())
                )
            }
            /**
             * ログイン済のとき
             */
            is Some -> {
                namedParameterJdbcTemplate.queryForList(
                    """
                        WITH other_users AS (
                            SELECT
                                users.id AS id
                                , users.username AS username
                                , profiles.bio AS bio
                                , profiles.image AS image
                                , CASE WHEN followings.id IS NOT NULL THEN 1 ELSE 0 END AS following_flg
                            FROM
                                users
                                JOIN
                                    profiles
                                ON
                                    profiles.user_id = users.id
                                LEFT OUTER JOIN
                                    followings
                                ON
                                    followings.following_id = users.id
                                    AND followings.follower_id = :curren_user_id
                        )
                        SELECT
                            ac.id AS comment_id
                            , ac.body AS body
                            , ac.created_at AS created_at
                            , ac.updated_at AS updated_at
                            , ac.author_id AS author_id
                            , ou.id AS user_id
                            , ou.username AS username
                            , ou.bio AS bio
                            , ou.image AS image
                            , ou.following_flg AS following_flg
                        FROM
                            article_comments ac
                            JOIN
                                other_users ou
                            ON
                                ac.author_id = ou.id
                        WHERE
                            ac.id IN (:comment_ids)
                        ORDER BY 
                            ac.id
                        ;
                    """.trimIndent(),
                    MapSqlParameterSource()
                        .addValue("comment_ids", comments.map { it.id.value }.toSet())
                        .addValue("curren_user_id", currentUser.value.userId.value)
                )
            }
        }

        val commentWithAuthors = commentWithAuthorsFromDb.map {
            CommentWithAuthor(
                Comment.newWithoutValidation(
                    id = CommentId.newWithoutValidation(it["comment_id"].toString().toInt()),
                    body = Body.newWithoutValidation(it["body"].toString()),
                    createdAt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(it["created_at"].toString()),
                    updatedAt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(it["updated_at"].toString()),
                    authorId = UserId(it["author_id"].toString().toInt()),
                ),
                OtherUser.newWithoutValidation(
                    userId = UserId(it["user_id"].toString().toInt()),
                    username = Username.newWithoutValidation(it["username"].toString()),
                    bio = Bio.newWithoutValidation(it["bio"].toString()),
                    image = Image.newWithoutValidation(it["image"].toString()),
                    following = it["following_flg"].toString() == "1"
                )
            )
        }

        return commentWithAuthors.right()
    }
}