Skip to content

Commit

Permalink
[FEAT] add follow and unfollow API
Browse files Browse the repository at this point in the history
  • Loading branch information
jihoi-kang committed Oct 22, 2023
1 parent b00058c commit 3314b32
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 10 deletions.
47 changes: 47 additions & 0 deletions src/main/kotlin/healthiee/rest/api/member/MemberApiController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@ package healthiee.rest.api.member

import healthiee.rest.api.member.dto.MemberDto
import healthiee.rest.api.member.dto.response.CheckMemberResponse
import healthiee.rest.domain.member.Member
import healthiee.rest.lib.error.ApiException
import healthiee.rest.lib.error.ApplicationErrorCode.NOT_FOUND_MEMBER
import healthiee.rest.lib.response.BaseResponse
import healthiee.rest.repository.member.MemberRepository
import healthiee.rest.service.FollowService
import healthiee.rest.service.MemberService
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import java.util.*

@RestController
class MemberApiController(
private val memberService: MemberService,
private val followService: FollowService,
private val memberRepository: MemberRepository,
) {

Expand All @@ -34,4 +43,42 @@ class MemberApiController(
return ResponseEntity.ok(BaseResponse(code = HttpStatus.OK.value(), data = CheckMemberResponse(exist)))
}

@PostMapping("/v1/members/{memberId}/follow")
@PreAuthorize("hasRole('MEMBER')")
fun followMember(
@AuthenticationPrincipal member: Member,
@PathVariable("memberId") targetMemberId: UUID,
): ResponseEntity<BaseResponse<Any>> {
val findMember = memberRepository.findByIdOrNull(targetMemberId)
?: throw ApiException(NOT_FOUND_MEMBER)

followService.follow(member, findMember)

return ResponseEntity.ok(
BaseResponse(
code = HttpStatus.OK.value(),
message = "팔로우가 완료 되었습니다",
)
)
}

@DeleteMapping("/v1/members/{memberId}/follow")
@PreAuthorize("hasRole('MEMBER')")
fun unfollowMember(
@AuthenticationPrincipal member: Member,
@PathVariable("memberId") targetMemberId: UUID,
): ResponseEntity<BaseResponse<Any>> {
val findMember = memberRepository.findByIdOrNull(targetMemberId)
?: throw ApiException(NOT_FOUND_MEMBER)

followService.unfollow(member, findMember)

return ResponseEntity.ok(
BaseResponse(
code = HttpStatus.OK.value(),
message = "팔로우가 취소 되었습니다",
)
)
}

}
7 changes: 6 additions & 1 deletion src/main/kotlin/healthiee/rest/domain/base/BaseEntity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ abstract class BaseEntity {
protected set

@Column(nullable = false)
val deleted: Boolean = false
var deleted: Boolean = false
private set

fun delete() {
deleted = true
}

}
4 changes: 4 additions & 0 deletions src/main/kotlin/healthiee/rest/domain/follow/Follow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ class Follow(
@Column(name = "follow_id")
val id: Long = 0L

companion object {
fun createFollow(member: Member, targetMember: Member) = Follow(member, targetMember)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import org.springframework.http.HttpStatus
enum class ApplicationErrorCode(
private val _httpStatus: HttpStatus,
private val _message: String,
): ErrorCode {
) : ErrorCode {
// Not Found
NOT_FOUND_MEMBER(HttpStatus.NOT_FOUND, "멤버를 찾을 수 없습니다"),
NOT_FOUND_CODE(HttpStatus.NOT_FOUND, "코드를 찾을 수 없습니다"),
NOT_FOUND_FOLLOW(HttpStatus.NOT_FOUND, "팔로우를 찾을 수 없습니다"),

// Forbidden
FORBIDDEN_INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "유효하지 않는 리프레쉬 토큰입니다")
FORBIDDEN_INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "유효하지 않는 리프레쉬 토큰입니다"),

// Bad Request
BAD_REQUEST_ALREADY_EXIST_FOLLOW(HttpStatus.BAD_REQUEST, "이미 팔로우가 되어 있습니다"),
;

override val httpStatus: HttpStatus
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package healthiee.rest.repository.follow

import healthiee.rest.domain.follow.Follow
import healthiee.rest.domain.member.Member

interface FollowCustomRepository {

fun findByMember(member: Member, targetMember: Member): Follow?

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package healthiee.rest.repository.follow

import com.querydsl.jpa.impl.JPAQueryFactory
import healthiee.rest.domain.follow.Follow
import healthiee.rest.domain.follow.QFollow.follow
import healthiee.rest.domain.member.Member
import java.util.*

class FollowCustomRepositoryImpl(
private val queryFactory: JPAQueryFactory,
) : FollowCustomRepository {

override fun findByMember(member: Member, targetMember: Member): Follow? {
return queryFactory.selectFrom(follow)
.where(
follow.member.eq(member),
follow.targetMember.eq(targetMember),
follow.deleted.eq(false)
)
.fetchOne()

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface FollowRepository : JpaRepository<Follow, Long> {
interface FollowRepository : JpaRepository<Follow, Long>, FollowCustomRepository {
fun countByMember(member: Member): Int // 팔로우 수
fun countByTargetMember(member: Member): Int // 팔로워 수
}
38 changes: 38 additions & 0 deletions src/main/kotlin/healthiee/rest/service/FollowService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package healthiee.rest.service

import healthiee.rest.domain.follow.Follow
import healthiee.rest.domain.member.Member
import healthiee.rest.lib.error.ApiException
import healthiee.rest.lib.error.ApplicationErrorCode.BAD_REQUEST_ALREADY_EXIST_FOLLOW
import healthiee.rest.lib.error.ApplicationErrorCode.NOT_FOUND_FOLLOW
import healthiee.rest.repository.follow.FollowRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional(readOnly = true)
class FollowService(
val followRepository: FollowRepository,
) {

@Transactional
fun follow(member: Member, targetMember: Member) {
val findFollow = followRepository.findByMember(member, targetMember)
findFollow?.let { throw ApiException(BAD_REQUEST_ALREADY_EXIST_FOLLOW) }

followRepository.save(Follow.createFollow(member, targetMember))
}

@Transactional
fun unfollow(member: Member, targetMember: Member) {
val findFollow = followRepository.findByMember(member, targetMember)
?: throw ApiException(NOT_FOUND_FOLLOW)

findFollow.delete()
}

fun getFollowingCount(member: Member): Int = followRepository.countByMember(member)

fun getFollowerCount(member: Member): Int = followRepository.countByTargetMember(member)

}
14 changes: 8 additions & 6 deletions src/main/kotlin/healthiee/rest/service/MemberService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package healthiee.rest.service
import healthiee.rest.api.member.dto.MemberDto
import healthiee.rest.lib.error.ApiException
import healthiee.rest.lib.error.ApplicationErrorCode.NOT_FOUND_MEMBER
import healthiee.rest.repository.follow.FollowRepository
import healthiee.rest.repository.member.MemberRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
Expand All @@ -14,17 +13,20 @@ import java.util.*
@Transactional(readOnly = true)
class MemberService(
val memberRepository: MemberRepository,
val followRepository: FollowRepository,
val followService: FollowService,
) {

fun getMember(id: UUID): MemberDto {
val findMember =
memberRepository.findByIdOrNull(id) ?: throw ApiException(NOT_FOUND_MEMBER)

val followingCount = followRepository.countByMember(findMember)
val followerCount = followRepository.countByTargetMember(findMember)

return MemberDto.create(MemberDto.Params(findMember, followingCount, followerCount))
return MemberDto.create(
MemberDto.Params(
findMember,
followService.getFollowingCount(findMember),
followService.getFollowerCount(findMember),
)
)
}

}

0 comments on commit 3314b32

Please sign in to comment.