Skip to content

Commit

Permalink
✨feature : 친구관리 기능 작업
Browse files Browse the repository at this point in the history
 - 친구관리 기능 작업
  • Loading branch information
ParkYunHo committed Feb 21, 2023
1 parent ea83cf6 commit 74ed48a
Show file tree
Hide file tree
Showing 17 changed files with 503 additions and 2 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
kotlin("jvm") version "1.7.22"
kotlin("plugin.spring") version "1.7.22"
kotlin("plugin.jpa") version "1.7.22"
kotlin("kapt") version "1.7.22"
}

group = "com.nexters"
Expand Down Expand Up @@ -52,6 +53,9 @@ dependencies {
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")

implementation("com.infobip:infobip-spring-data-r2dbc-querydsl-boot-starter:6.2.0")
kapt("com.infobip:infobip-spring-data-jdbc-annotation-processor-common:6.2.0")
}

tasks.withType<KotlinCompile> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class AccountHandler(
"profile" -> saveUserUseCase.updateProfileImgUrl(
request.userId(),
request.queryParam("profile").orElseThrow { BadRequestException("필수 입력값이 누락되었습니다.") })
else -> Mono.error(BadRequestException("유효한 경로가 아닙니다."))
else -> throw BadRequestException("유효한 경로가 아닙니다.")
}
.flatMap { BaseResponse().success(it) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import com.nexters.pimo.common.exception.BadRequestException
*/
class CommCode {
companion object {
const val DEFAULT_PAGING_START: String = "0"
const val DEFAULT_PAGING_SIZE: String = "10"
const val DEFAULT_SORT_OPTION: String = "0"

fun findPrefix(type: String): String {
for(item in Social.values()) {
if(item.code == type) {
return item.prefix
}
}

throw BadRequestException("지원하지 않는 소셜로그인입니다.")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.nexters.pimo.follow.adapter.`in`.web

import com.nexters.pimo.common.constants.CommCode
import com.nexters.pimo.common.dto.BaseResponse
import com.nexters.pimo.common.exception.BadRequestException
import com.nexters.pimo.common.filter.userId
import com.nexters.pimo.follow.adapter.`in`.web.dto.RegisterInput
import com.nexters.pimo.follow.application.dto.FollowAccountDto
import com.nexters.pimo.follow.application.dto.FollowCntDto
import com.nexters.pimo.follow.application.port.`in`.DeleteUseCase
import com.nexters.pimo.follow.application.port.`in`.FindUseCase
import com.nexters.pimo.follow.application.port.`in`.RegisterUseCase
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

/**
* @author yoonho
* @since 2023.02.16
*/
@Component
class FollowHandler(
private val registerUseCase: RegisterUseCase,
private val findUseCase: FindUseCase,
private val deleteUseCase: DeleteUseCase
) {
/**
* 친구 등록
*
* @param userId [String]
* @return boolean [Boolean]
* @author yoonho
* @since 2023.02.18
*/
fun register(request: ServerRequest): Mono<ServerResponse> =
request.bodyToMono(RegisterInput::class.java)
.flatMap { registerUseCase.register(it.userId, request.userId()) }
.flatMap { BaseResponse().success(it) }

/**
* 친구 취소
*
* @param userId [String]
* @return boolean [Boolean]
* @author yoonho
* @since 2023.02.18
*/
fun delete(request: ServerRequest): Mono<ServerResponse> =
request.bodyToMono(RegisterInput::class.java)
.flatMap { deleteUseCase.delete(it.userId, request.userId()) }
.flatMap { BaseResponse().success(it) }

/**
* 친구 목록수 조회
*
* @return List [FollowCntDto]
* @author yoonho
* @since 2023.02.18
*/
fun count(request: ServerRequest): Mono<ServerResponse> =
findUseCase.count(request.userId())
.flatMap { BaseResponse().success(it) }

/**
* 친구목록 계정정보 조회
* <pre>
* 1. path Variable
* - follower: 나만 친구추가한 친구목록 조회
* - followee: 상대방만 나를 친구추가한 친구목록 조회
* - followforfollow: 맞팔한 친구목록 조회
* 2. sort
* - 0: 최근 친구추가한 순서
* - 1: 닉네임 가나다순
* 3. paging
* - start: 시작Index
* - size: 해당 페이지에서 조회할 갯수
*
* @param sort [String]
* @param start [String]
* @param size [String]
* @return List [FollowAccountDto]
* @author yoonho
* @since 2023.02.18
*/
fun list(request: ServerRequest): Mono<ServerResponse> =
when(request.pathVariable("target")) {
"follower" -> {
findUseCase.follower(
request.userId(),
request.queryParam("sort").orElse(CommCode.DEFAULT_SORT_OPTION),
request.queryParam("start").orElse(CommCode.DEFAULT_PAGING_START).toInt(),
request.queryParam("size").orElse(CommCode.DEFAULT_PAGING_SIZE).toInt()
)
}
"followee" -> {
findUseCase.followee(
request.userId(),
request.queryParam("sort").orElse(CommCode.DEFAULT_SORT_OPTION),
request.queryParam("start").orElse(CommCode.DEFAULT_PAGING_START).toInt(),
request.queryParam("size").orElse(CommCode.DEFAULT_PAGING_SIZE).toInt()
)
}
"followforfollow" -> {
findUseCase.followForFollow(
request.userId(),
request.queryParam("sort").orElse(CommCode.DEFAULT_SORT_OPTION),
request.queryParam("start").orElse(CommCode.DEFAULT_PAGING_START).toInt(),
request.queryParam("size").orElse(CommCode.DEFAULT_PAGING_SIZE).toInt()
)
}
else -> throw BadRequestException("유효한 경로가 아닙니다.")
}
.collectList()
.flatMap { BaseResponse().success(it) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.nexters.pimo.follow.adapter.`in`.web

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.router

/**
* @author yoonho
* @since 2023.02.15
*/
@Configuration
class FollowRouter(
private val followHandler: FollowHandler
) {

@Bean
fun followRouterFunction(): RouterFunction<ServerResponse> =
router {
accept(MediaType.APPLICATION_JSON).nest {
POST("/follows", followHandler::register)
DELETE("/follows", followHandler::delete)
GET("/follows/count", followHandler::count)
GET("/follows/{target}", followHandler::list)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.nexters.pimo.follow.adapter.`in`.web.dto

import java.io.Serializable

/**
* @author yoonho
* @since 2023.02.16
*/
data class RegisterInput(
val userId: String
): Serializable
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.nexters.pimo.follow.adapter.out.persistence

import com.fasterxml.jackson.databind.ObjectMapper
import com.nexters.pimo.account.adapter.out.persistence.AccountRepository
import com.nexters.pimo.common.exception.BadRequestException
import com.nexters.pimo.follow.application.dto.FollowAccountDto
import com.nexters.pimo.follow.application.dto.FollowCntDto
import com.nexters.pimo.follow.application.port.out.FindPort
import com.nexters.pimo.follow.application.port.out.SavePort
import com.nexters.pimo.follow.domain.Follow
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Repository
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.switchIfEmpty
import java.util.*
import java.util.stream.Collectors

/**
* @author yoonho
* @since 2023.02.15
*/
@Repository
class FollowPersistenceAdapter(
private val followRepository: FollowRepository,
private val accountRepository: AccountRepository,
private val objectMapper: ObjectMapper,
): SavePort, FindPort {

override fun register(followeeUserId: String, followerUserId: String): Mono<Boolean> =
Mono.zip(
accountRepository.findByUserId(followeeUserId)
.switchIfEmpty { throw BadRequestException("등록되지 않은 사용자입니다.") },
accountRepository.findByUserId(followerUserId)
.switchIfEmpty { throw BadRequestException("등록되지 않은 사용자입니다.") }
)
.flatMap { followRepository.save(
Follow(
followeeUserId = followeeUserId,
followeeNickName = it.t1.nickName,
followerUserId = followerUserId,
followerNickName = it.t2.nickName
)
)
.flatMap { Mono.just(true) } }

override fun delete(followeeUserId: String, followerUserId: String): Mono<Boolean> =
followRepository.deleteByFollowerUserIdAndFolloweeUserId(followerUserId, followeeUserId)

override fun count(userId: String): Mono<FollowCntDto> =
Mono.zip(
followRepository.findAllByFollowerUserId(userId)
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(it.followeeUserId, userId)
.map { !it } // not exists 조건
}
.count(),
followRepository.findAllByFolloweeUserId(userId)
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(userId, it.followerUserId)
.map { !it } // not exists 조건
}
.count(),
followRepository.findAllByFollowerUserId(userId)
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(it.followeeUserId, userId)
}
.count()
)
.map {
FollowCntDto(
followerCnt = it.t1,
followeeCnt = it.t2,
followForFollowCnt = it.t3
)
}

override fun follower(userId: String, start: Int, size: Int): Flux<FollowAccountDto> =
followRepository.findAllByFollowerUserIdOrderByCreatedAt(userId, PageRequest.of(start, size))
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(it.followeeUserId, userId)
.map { !it } // not exists 조건
}
.flatMap { findFollowAccountDto(it.followeeUserId) }

override fun followee(userId: String, start: Int, size: Int): Flux<FollowAccountDto> =
followRepository.findAllByFolloweeUserIdOrderByCreatedAt(userId, PageRequest.of(start, size))
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(userId, it.followerUserId)
.map { !it } // not exists 조건
}
.flatMap { findFollowAccountDto(it.followerUserId) }

override fun followForFollow(userId: String, start: Int, size: Int): Flux<FollowAccountDto> =
followRepository.findAllByFollowerUserIdOrderByCreatedAt(userId, PageRequest.of(start, size))
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(it.followeeUserId, userId)
}
.flatMap { findFollowAccountDto(it.followeeUserId) }

override fun followerByNickname(userId: String, start: Int, size: Int): Flux<FollowAccountDto> =
followRepository.findAllByFollowerUserIdOrderByFollowerNickName(userId, PageRequest.of(start, size))
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(it.followeeUserId, userId)
.map { !it } // not exists 조건
}
.flatMap { findFollowAccountDto(it.followeeUserId) }

override fun followeeByNickname(userId: String, start: Int, size: Int): Flux<FollowAccountDto> =
followRepository.findAllByFolloweeUserIdOrderByFolloweeNickName(userId, PageRequest.of(start, size))
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(userId, it.followerUserId)
.map { !it } // not exists 조건
}
.flatMap { findFollowAccountDto(it.followerUserId) }

override fun followForFollowByNickname(userId: String, start: Int, size: Int): Flux<FollowAccountDto> =
followRepository.findAllByFollowerUserIdOrderByFollowerNickName(userId, PageRequest.of(start, size))
.filterWhen {
followRepository.existsByFollowerUserIdAndFolloweeUserId(it.followeeUserId, userId)
}
.flatMap { findFollowAccountDto(it.followeeUserId) }


private fun findFollowAccountDto(userId: String): Mono<FollowAccountDto> =
accountRepository.findByUserId(userId)
.switchIfEmpty { throw BadRequestException("등록되지 않은 사용자입니다.") }
.map { it.toDto() }
.map { objectMapper.convertValue(it, FollowAccountDto::class.java) }


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.nexters.pimo.follow.adapter.out.persistence

import com.nexters.pimo.follow.domain.Follow
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

/**
* @author yoonho
* @since 2023.02.16
*/
interface FollowRepository: ReactiveCrudRepository<Follow, Long> {
fun deleteByFollowerUserIdAndFolloweeUserId(followerUserId: String, followeeUserId: String): Mono<Boolean>

fun findAllByFollowerUserId(followerUserId: String): Flux<Follow>
fun findAllByFolloweeUserId(followeeUserId: String): Flux<Follow>
fun existsByFollowerUserIdAndFolloweeUserId(followerUserId: String, followeeUserId: String): Mono<Boolean>

fun findAllByFollowerUserIdOrderByCreatedAt(followerUserId: String, pageable: Pageable): Flux<Follow>
fun findAllByFolloweeUserIdOrderByCreatedAt(followeeUserId: String, pageable: Pageable): Flux<Follow>
fun findAllByFollowerUserIdOrderByFollowerNickName(followerUserId: String, pageable: Pageable): Flux<Follow>
fun findAllByFolloweeUserIdOrderByFolloweeNickName(followeeUserId: String, pageable: Pageable): Flux<Follow>
}
Loading

0 comments on commit 74ed48a

Please sign in to comment.