Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 회원 관련 기능 추가 #55

Merged
merged 18 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions data/src/main/kotlin/com/kw/data/domain/member/Member.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class Member(email: String) : Base() {

var memberRoles : MutableList<MemberRoleType> = mutableListOf(MemberRoleType.ROLE_USER)

@OneToMany(mappedBy = "member", cascade = [CascadeType.ALL], orphanRemoval = true)
var snsList: MutableList<Sns> = mutableListOf()
protected set

@OneToMany(mappedBy = "member", cascade = [CascadeType.ALL], orphanRemoval = true)
var memberTags: MutableList<MemberTag> = mutableListOf()
protected set

fun updateMemberNickname(nickname: String) {
this.nickname = nickname
}
Expand All @@ -50,6 +58,22 @@ class Member(email: String) : Base() {
this.deletedAt = LocalDateTime.now()
}

fun updateMemberSns(snsList: List<Sns>) {
if (snsList.size > 3) {
throw IllegalArgumentException("Sns는 최대 3개까지 지정 가능합니다.")
}
this.snsList.clear()
this.snsList.addAll(snsList)
}

fun updateMemberTags(memberTags: List<MemberTag>) {
if (memberTags.size > 3) {
throw IllegalArgumentException("관심 태그는 최대 3개까지 지정 가능합니다.")
}
this.memberTags.clear()
this.memberTags.addAll(memberTags)
}

enum class MemberRoleType {
ROLE_USER,
ROLE_ADMIN
Expand Down
21 changes: 21 additions & 0 deletions data/src/main/kotlin/com/kw/data/domain/member/MemberTag.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.kw.data.domain.member

import com.kw.data.domain.Base
import com.kw.data.domain.tag.Tag
import jakarta.persistence.*

@Entity
class MemberTag(member: Member, tag: Tag): Base() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
val id: Long? = null

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
val member: Member = member

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tag_id", nullable = false)
val tag: Tag = tag
}
6 changes: 5 additions & 1 deletion data/src/main/kotlin/com/kw/data/domain/member/Sns.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.kw.data.domain.Base
import jakarta.persistence.*

@Entity
class Sns(name: String, url: String) : Base() {
class Sns(name: String, url: String, member: Member) : Base() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
Expand All @@ -17,4 +17,8 @@ class Sns(name: String, url: String) : Base() {
@Column(name = "url", nullable = false)
var url: String = url
protected set

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
val member: Member = member
}
3 changes: 3 additions & 0 deletions infra/infra-security/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ dependencies {

// redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")

// swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0")
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import org.springframework.transaction.annotation.Transactional
@Transactional
class OAuth2SuccessHandler(private val jwtTokenProvider: JwtTokenProvider,
private val memberRepository: MemberRepository,
private val redisRefreshTokenRepository: RedisRefreshTokenRepository) : AuthenticationSuccessHandler {
private val redisRefreshTokenRepository: RedisRefreshTokenRepository,
private val httpResponseUtil: HttpResponseUtil) : AuthenticationSuccessHandler {

override fun onAuthenticationSuccess(
request: HttpServletRequest?,
Expand All @@ -29,11 +30,13 @@ class OAuth2SuccessHandler(private val jwtTokenProvider: JwtTokenProvider,
val email = principal.getAttribute<String>("email")

var member = Member(email = email!!)
var isSignUp = false
if(isMember(email)){
member = getMember(email)
}
else {
member = createMember(member)
isSignUp = true
}
val authorities = member.memberRoles.map { memberRole ->
SimpleGrantedAuthority(memberRole.toString())
Expand All @@ -50,7 +53,7 @@ class OAuth2SuccessHandler(private val jwtTokenProvider: JwtTokenProvider,

redisRefreshTokenRepository.save(refreshToken = refreshToken, memberId = member.id!!)

HttpResponseUtil.writeResponse(response!!, accessToken, refreshToken)
httpResponseUtil.writeResponse(response!!, accessToken, refreshToken, isSignUp)
}

private fun getMember(email : String) : Member {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.kw.infrasecurity.resolver

import io.swagger.v3.oas.annotations.Parameter

@Parameter(hidden = true)
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class AuthToMember {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.kw.infrasecurity.resolver

import com.kw.data.domain.member.Member
import com.kw.data.domain.member.repository.MemberRepository
import com.kw.infrasecurity.oauth.OAuth2UserDetails
import org.springframework.core.MethodParameter
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.support.WebDataBinderFactory
Expand All @@ -27,11 +27,9 @@ class AuthToMemberArgumentResolver(val memberRepository: MemberRepository) : Han
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?
): Any? {
val authentication = SecurityContextHolder.getContext().authentication
?: throw IllegalArgumentException("접근이 거부되었습니다.")
val userDetails = authentication.principal as OAuth2UserDetails
val member = memberRepository.findMemberByEmail(userDetails.email)
?: throw IllegalArgumentException("존재하지 않는 회원입니다.")
val authentication = SecurityContextHolder.getContext().authentication ?: throw IllegalArgumentException("접근이 거부되었습니다.")
val userDetails = authentication.principal as DefaultOAuth2User
val member = memberRepository.findMemberByEmail(userDetails.getAttribute<String>("email").toString()) ?: throw IllegalArgumentException("존재하지 않는 회원입니다.")

isMemberWithdraw(member)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package com.kw.infrasecurity.util

import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

class HttpResponseUtil {
data class tokenRespone(val accessToken: String, val refreshToken: String)
@Component
class HttpResponseUtil(@Value("\${client-redirect-url}") val REDIRECT_URL: String) {

companion object {
fun writeResponse(response : HttpServletResponse, accessToken : String, refreshToken : String) {
val objectMapper = ObjectMapper()
val responseBody: String = objectMapper.writeValueAsString(tokenRespone(accessToken, refreshToken))
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.status = HttpStatus.OK.value()
response.characterEncoding = "UTF-8"
response.writer.write(responseBody)
fun writeResponse(response : HttpServletResponse, accessToken : String, refreshToken : String, isSignUp : Boolean) {
var redirectUrl = REDIRECT_URL
if(isSignUp) {
redirectUrl += "/welcome"
}
val sb = StringBuffer(redirectUrl)
sb.append("?").append("access-token=").append(accessToken)
sb.append("&").append("refresh-token=").append(refreshToken)

response.sendRedirect(sb.toString())
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.kw.api.domain.member.controller

import com.kw.api.domain.member.dto.request.MemberSnsUpdateRequest
import com.kw.api.domain.member.dto.response.MemberInfoResponse
import com.kw.api.domain.member.service.MemberService
import com.kw.data.domain.member.Member
Expand All @@ -23,13 +24,29 @@ class MemberController(private val memberService: MemberService) {

@Operation(summary = "사용자의 닉네임을 변경합니다.")
@PatchMapping("/me/nickname")
fun updateMemberNickname(
@AuthToMember member: Member,
@RequestParam nickname: String
): MemberInfoResponse {
return memberService.updateMemberNickname(member, nickname)
fun updateMemberNickname(@AuthToMember member: Member,
@RequestParam nickname: String): MemberInfoResponse {
val response = memberService.updateMemberNickname(member, nickname)
return response
}

@Operation(summary = "사용자의 소셜 링크를 변경합니다.")
@PatchMapping("/me/sns")
fun updateMemberSns(@AuthToMember member: Member,
@RequestBody memberSnsUpdateRequest: MemberSnsUpdateRequest): MemberInfoResponse {
val response = memberService.updateMemberSns(member, memberSnsUpdateRequest)
return response
}

@Operation(summary = "사용자의 관심 태그를 변경합니다.")
@PatchMapping("/me/tags")
fun updateMemberTags(@AuthToMember member: Member,
@RequestParam tagIds: List<Long>): MemberInfoResponse {
val response = memberService.updateMemberTags(member, tagIds)
return response
}


@Operation(summary = "회원 프로필 사진을 저장합니다.")
@PatchMapping(value = ["/me/profile-image"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
fun updateMemberProfileImage(
Expand All @@ -38,4 +55,11 @@ class MemberController(private val memberService: MemberService) {
): MemberInfoResponse {
return memberService.updateMemberProfileImage(member, file)
}

@Operation(summary = "회원 아이디로 회원 정보를 가져옵니다.")
@GetMapping("/{id}")
fun updateMemberProfileImage(@PathVariable id: Long): MemberInfoResponse {
val response = memberService.getMemberInfoById(id)
return response
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.kw.api.domain.member.dto.request

data class MemberSnsUpdateRequest(val snsRequests: List<snsRequest>) {
data class snsRequest(val name: String,
val url: String)
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
package com.kw.api.domain.member.dto.response

import com.kw.api.domain.tag.dto.response.TagResponse
import com.kw.data.domain.member.Member

data class MemberInfoResponse(val id: Long?,
val nickname: String?,
val email: String,
val provider: Member.Provider,
val profileImage: String?
val profileImage: String?,
val snsList: List<SnsResponse>,
val memberTags: List<TagResponse>
) {
companion object {
fun from(member: Member) : MemberInfoResponse {
val snsResponses = member.snsList.map { sns ->
SnsResponse(
name = sns.name,
url = sns.url
)
}.toList()

val tagResponses = member.memberTags.map { memberTag ->
TagResponse(
id = memberTag.tag.id,
content = memberTag.tag.name
)
}.toList()

return MemberInfoResponse(
id = member.id,
nickname = member.nickname,
email = member.email,
provider = member.provider,
profileImage = member.profileImage
profileImage = member.profileImage,
snsList = snsResponses,
memberTags = tagResponses
)
}
}

data class SnsResponse(val name: String,
val url: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ package com.kw.api.domain.member.service

import com.kw.api.common.exception.ApiErrorCode
import com.kw.api.common.exception.ApiException
import com.kw.api.domain.member.dto.request.MemberSnsUpdateRequest
import com.kw.api.domain.member.dto.response.MemberInfoResponse
import com.kw.data.domain.member.Member
import com.kw.data.domain.member.MemberTag
import com.kw.data.domain.member.Sns
import com.kw.data.domain.member.repository.MemberRepository
import com.kw.data.domain.tag.repository.TagRepository
import com.kw.infras3.service.S3Service
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile

@Service
@Transactional
class MemberService(private val memberRepository: MemberRepository,
private val s3Service: S3Service) {
private val s3Service: S3Service,
private val tagRepository: TagRepository) {

@Transactional(readOnly = true)
fun getMemberInfo(member: Member): MemberInfoResponse{
Expand All @@ -32,6 +38,34 @@ class MemberService(private val memberRepository: MemberRepository,
return MemberInfoResponse.from(member)
}

fun updateMemberSns(member: Member, memberSnsUpdateRequest: MemberSnsUpdateRequest): MemberInfoResponse {
val snsList = memberSnsUpdateRequest.snsRequests.map { snsRequest ->
Sns(
name = snsRequest.name,
url = snsRequest.url,
member = member
)
}.toList()

member.updateMemberSns(snsList)
return MemberInfoResponse.from(member)
}

fun updateMemberTags(member: Member, tagIds: List<Long>): MemberInfoResponse {
val memberTags = tagIds.map { tagId ->
val tag = tagRepository.findByIdOrNull(tagId) ?: throw ApiException(ApiErrorCode.INCLUDE_NOT_FOUND_TAG)
MemberTag(member, tag)
}.toList()

member.updateMemberTags(memberTags)
return MemberInfoResponse.from(member)
}

fun getMemberInfoById(id: Long): MemberInfoResponse {
val member = memberRepository.findByIdOrNull(id) ?: throw ApiException(ApiErrorCode.NOT_FOUND_MEMBER)
return MemberInfoResponse.from(member)
}

private fun isNicknameUnique(nickname: String) {
if(memberRepository.existsByNickname(nickname)){
throw ApiException(ApiErrorCode.NICKNAME_ALREADY_EXISTS)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kw.api.domain.question.controller

import com.kw.api.domain.question.dto.request.QuestionCreateRequest
import com.kw.api.domain.question.dto.request.QuestionReportRequest
import com.kw.api.domain.question.dto.request.QuestionSearchRequest
import com.kw.api.domain.question.dto.request.QuestionUpdateRequest
import com.kw.api.domain.question.dto.response.QuestionListResponse
Expand Down Expand Up @@ -54,7 +55,7 @@ class QuestionController(val questionService: QuestionService) {
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/questions/{id}/report")
fun reportQuestion(
@RequestParam reason: String,
@RequestBody reason: QuestionReportRequest,
@PathVariable id: Long
): QuestionReportResponse {
return questionService.reportQuestion(reason, id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.kw.api.domain.question.dto.request

data class QuestionReportRequest(val reason: String)
Loading
Loading