ProfileRepositoryImpl.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.left
import arrow.core.right
import com.example.realworldkotlinspringbootjdbc.domain.OtherUser
import com.example.realworldkotlinspringbootjdbc.domain.ProfileRepository
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 org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.stereotype.Repository
@Repository
class ProfileRepositoryImpl(val namedParameterJdbcTemplate: NamedParameterJdbcTemplate) : ProfileRepository {
override fun show(
username: Username,
currentUserId: Option<UserId>
): Either<ProfileRepository.ShowError, OtherUser> {
val profileFromDb = when (currentUserId) {
/**
* 未ログインのとき
*/
is None -> {
namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
, 0 AS following_flg
FROM
users
JOIN
profiles
ON
users.id = profiles.user_id
AND users.username = :username
;
""".trimIndent(),
MapSqlParameterSource().addValue("username", username.value)
)
}
/**
* ログイン済のとき
*/
is Some -> {
namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
, CASE WHEN followings.id IS NOT NULL THEN 1 ELSE 0 END AS following_flg
FROM
users
JOIN
profiles
ON
users.id = profiles.user_id
AND users.username = :username
LEFT OUTER JOIN
followings
ON
followings.following_id = users.id
AND followings.follower_id = :current_user_id
;
""".trimIndent(),
MapSqlParameterSource()
.addValue("username", username.value)
.addValue("current_user_id", currentUserId.value.value)
)
}
}
/**
* user が存在しなかった時 NotFoundError
*/
if (profileFromDb.isEmpty()) {
return ProfileRepository.ShowError.NotFoundProfileByUsername(username, currentUserId).left()
}
val it = profileFromDb.first()
return OtherUser.newWithoutValidation(
UserId(it["id"].toString().toInt()),
Username.newWithoutValidation(it["username"].toString()),
Bio.newWithoutValidation(it["bio"].toString()),
Image.newWithoutValidation(it["image"].toString()),
it["following_flg"].toString() == "1"
).right()
}
override fun follow(username: Username, currentUserId: UserId): Either<ProfileRepository.FollowError, OtherUser> {
/**
* user を取得
*/
val selectUserSql = """
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
, CASE WHEN followings.id IS NOT NULL THEN 1 ELSE 0 END AS following_flg
FROM
users
JOIN
profiles
ON
users.id = profiles.user_id
AND users.username = :username
LEFT OUTER JOIN
followings
ON
followings.following_id = users.id
AND followings.follower_id = :current_user_id
;
""".trimIndent()
val sqlSelectUserSqlParams = MapSqlParameterSource()
.addValue("username", username.value)
.addValue("current_user_id", currentUserId.value)
val profileFromDb = namedParameterJdbcTemplate.queryForList(selectUserSql, sqlSelectUserSqlParams)
/**
* user が存在しなかった時 NotFoundError
*/
if (profileFromDb.isEmpty()) {
return ProfileRepository.FollowError.NotFoundProfileByUsername(username, currentUserId).left()
}
/**
* 未フォローのとき、フォロー
*/
val insertFollowingsSql = """
INSERT INTO followings
(
following_id
, follower_id
, created_at
)
SELECT
users.id
, :current_user_id
, NOW()
FROM
users
WHERE
users.username = :username
AND NOT EXISTS (
SELECT
1
FROM
followings
JOIN
users
ON
followings.following_id = users.id
WHERE
users.username = :username
AND followings.following_id = users.id
AND followings.follower_id = :current_user_id
)
;
""".trimIndent()
val insertFollowingsSqlParams = MapSqlParameterSource()
.addValue("username", username.value)
.addValue("current_user_id", currentUserId.value)
namedParameterJdbcTemplate.update(insertFollowingsSql, insertFollowingsSqlParams)
val it = profileFromDb.first()
return OtherUser.newWithoutValidation(
UserId(it["id"].toString().toInt()),
Username.newWithoutValidation(it["username"].toString()),
Bio.newWithoutValidation(it["bio"].toString()),
Image.newWithoutValidation(it["image"].toString()),
true
).right()
}
override fun unfollow(
username: Username,
currentUserId: UserId
): Either<ProfileRepository.UnfollowError, OtherUser> {
/**
* user を取得
*/
val selectUserSql = """
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
, CASE WHEN followings.id IS NOT NULL THEN 1 ELSE 0 END AS following_flg
FROM
users
JOIN
profiles
ON
users.id = profiles.user_id
AND users.username = :username
LEFT OUTER JOIN
followings
ON
followings.following_id = users.id
AND followings.follower_id = :current_user_id
;
""".trimIndent()
val sqlSelectUserSqlParams = MapSqlParameterSource()
.addValue("username", username.value)
.addValue("current_user_id", currentUserId.value)
val profileFromDb = namedParameterJdbcTemplate.queryForList(selectUserSql, sqlSelectUserSqlParams)
/**
* user が存在しなかった時 NotFoundError
*/
if (profileFromDb.isEmpty()) {
return ProfileRepository.UnfollowError.NotFoundProfileByUsername(username, currentUserId).left()
}
/**
* フォロー済のとき、アンフォロー
*/
val deleteFollowingsSql = """
DELETE FROM
followings
USING
users
WHERE
users.username = :username
AND users.id = followings.following_id
AND followings.follower_id = :current_user_id
;
""".trimIndent()
val deleteFollowingsSqlParams = MapSqlParameterSource()
.addValue("username", username.value)
.addValue("current_user_id", currentUserId.value)
namedParameterJdbcTemplate.update(deleteFollowingsSql, deleteFollowingsSqlParams)
val it = profileFromDb.first()
return OtherUser.newWithoutValidation(
UserId(it["id"].toString().toInt()),
Username.newWithoutValidation(it["username"].toString()),
Bio.newWithoutValidation(it["bio"].toString()),
Image.newWithoutValidation(it["image"].toString()),
false
).right()
}
override fun findByUsername(
username: Username,
viewpointUserId: Option<UserId>
): Either<ProfileRepository.FindByUsernameError, OtherUser> {
val sqlParams = MapSqlParameterSource().addValue("username", username.value)
val userList = when (viewpointUserId) {
/**
* 登録済みユーザー検索
* - followingを '0' で固定
*/
None -> namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.email
, users.username
, users.password
, profiles.bio
, profiles.image
, '0' AS following_flg
FROM
users
JOIN
profiles
ON
profiles.user_id = users.id
AND users.username = :username
;
""".trimIndent(),
sqlParams
)
/**
* 特定の登録済みユーザーから見た登録済みユーザー検索
* - followingが0 or 1
*/
is Some -> namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
, CASE WHEN followings.id IS NOT NULL THEN '1' ELSE '0' END AS following_flg
FROM
users
JOIN
profiles
ON
users.id = profiles.user_id
AND users.username = :username
LEFT OUTER JOIN
followings
ON
followings.following_id = users.id
AND followings.follower_id = :viewpoint_user_id
;
""".trimIndent(),
sqlParams.addValue("viewpoint_user_id", viewpointUserId.value.value)
)
}
return when {
/**
* 見つからなかった
*/
userList.isEmpty() -> ProfileRepository.FindByUsernameError.NotFound(username).left()
/**
* 見つかった
*/
else -> {
val user = userList.first()
OtherUser.newWithoutValidation(
userId = UserId(user["id"].toString().toInt()),
username = Username.newWithoutValidation(user["username"].toString()),
bio = Bio.newWithoutValidation(user["bio"].toString()),
image = Image.newWithoutValidation(user["image"].toString()),
following = user["following_flg"].toString() == "1"
).right()
}
}
}
override fun filterByUserIds(
userIds: Set<UserId>,
viewpointUserId: Option<UserId>
): Either<ProfileRepository.FilterByUserIdsError, Set<OtherUser>> {
if (userIds.isEmpty()) {
return emptySet<OtherUser>().right()
}
val sqlParams = MapSqlParameterSource().addValue("user_ids", userIds.map { it.value }.toSet())
return when (viewpointUserId) {
/**
* 登録済みユーザー検索
* - followingを '0' で固定
*/
None -> namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.email
, users.username
, users.password
, profiles.bio
, profiles.image
, '0' AS following_flg
FROM
users
JOIN
profiles
ON
profiles.user_id = users.id
WHERE
users.id IN (:user_ids)
;
""".trimIndent(),
sqlParams
)
/**
* 特定の登録済みユーザーから見た登録済みユーザー検索
* - followingが '0' or '1'
*/
is Some -> namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
, CASE WHEN followings.id IS NOT NULL THEN '1' ELSE '0' END AS following_flg
FROM
users
JOIN
profiles
ON
users.id = profiles.user_id
LEFT OUTER JOIN
followings
ON
followings.following_id = users.id
AND followings.follower_id = :viewpoint_user_id
WHERE
users.id IN (:user_ids)
;
""".trimIndent(),
sqlParams.addValue("viewpoint_user_id", viewpointUserId.value.value)
)
}.map { user ->
OtherUser.newWithoutValidation(
userId = UserId(user["id"].toString().toInt()),
username = Username.newWithoutValidation(user["username"].toString()),
bio = Bio.newWithoutValidation(user["bio"].toString()),
image = Image.newWithoutValidation(user["image"].toString()),
following = user["following_flg"].toString() == "1"
)
}.toSet().right()
}
override fun filterFollowedByUser(userId: UserId): Either<ProfileRepository.FilterFollowedByUserError, Set<OtherUser>> =
namedParameterJdbcTemplate.queryForList(
"""
SELECT
users.id
, users.username
, profiles.bio
, profiles.image
FROM
users
JOIN
profiles
ON
profiles.user_id = users.id
JOIN
followings
ON
followings.following_id = users.id
AND followings.follower_id = :user_id
;
""".trimIndent(),
MapSqlParameterSource().addValue("user_id", userId.value)
).map {
OtherUser.newWithoutValidation(
userId = UserId(it["id"].toString().toInt()),
username = Username.newWithoutValidation(it["username"].toString()),
bio = Bio.newWithoutValidation(it["bio"].toString()),
image = Image.newWithoutValidation(it["image"].toString()),
following = true
)
}.toSet().right()
}