sodalive-backend-spring-boot/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt

230 lines
9.6 KiB
Kotlin

package kr.co.vividnext.sodalive.member
import com.amazonaws.services.s3.model.ObjectMetadata
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.jwt.TokenProvider
import kr.co.vividnext.sodalive.member.info.GetMemberInfoResponse
import kr.co.vividnext.sodalive.member.login.LoginRequest
import kr.co.vividnext.sodalive.member.login.LoginResponse
import kr.co.vividnext.sodalive.member.notification.MemberNotificationService
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
import kr.co.vividnext.sodalive.member.signUp.SignUpRequest
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository
import kr.co.vividnext.sodalive.member.stipulation.StipulationIds
import kr.co.vividnext.sodalive.member.stipulation.StipulationRepository
import kr.co.vividnext.sodalive.utils.generateFileName
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.repository.findByIdOrNull
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
@Service
@Transactional(readOnly = true)
class MemberService(
private val repository: MemberRepository,
private val stipulationRepository: StipulationRepository,
private val stipulationAgreeRepository: StipulationAgreeRepository,
private val memberNotificationService: MemberNotificationService,
private val s3Uploader: S3Uploader,
private val validator: SignUpValidator,
private val tokenProvider: TokenProvider,
private val passwordEncoder: PasswordEncoder,
private val authenticationManagerBuilder: AuthenticationManagerBuilder,
private val objectMapper: ObjectMapper,
@Value("\${cloud.aws.s3.bucket}")
private val s3Bucket: String
) : UserDetailsService {
@Transactional
fun signUp(
profileImage: MultipartFile?,
requestString: String
): ApiResponse<LoginResponse> {
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
val request = objectMapper.readValue(requestString, SignUpRequest::class.java)
if (!request.isAgreePrivacyPolicy || !request.isAgreeTermsOfService) {
throw SodaException("약관에 동의하셔야 회원가입이 가능합니다.")
}
validatePassword(request.password)
duplicateCheckEmail(request.email)
duplicateCheckNickname(request.nickname)
val member = createMember(request)
member.profileImage = uploadProfileImage(profileImage = profileImage, memberId = member.id!!)
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = login(request.email, request.password))
}
fun login(request: LoginRequest): ApiResponse<LoginResponse> {
return ApiResponse.ok(
message = "로그인 되었습니다.",
data = login(request.email, request.password, request.isAdmin, request.isCreator)
)
}
fun getMemberInfo(member: Member, container: String): GetMemberInfoResponse {
return GetMemberInfoResponse(
can = member.getChargeCan(container) + member.getRewardCan(container),
isAuth = member.auth != null,
role = member.role,
messageNotice = member.notification?.message,
followingChannelLiveNotice = member.notification?.live,
followingChannelUploadContentNotice = member.notification?.uploadContent
)
}
@Transactional
fun updateNotificationSettings(request: UpdateNotificationSettingRequest, member: Member) {
memberNotificationService.updateNotification(
live = request.live,
uploadContent = request.uploadContent,
message = request.message,
member = member
)
}
private fun login(
email: String,
password: String,
isAdmin: Boolean = false,
isCreator: Boolean = false
): LoginResponse {
val member = repository.findByEmail(email = email) ?: throw SodaException("로그인 정보를 확인해주세요.")
if (!member.isActive) {
throw SodaException("탈퇴한 계정입니다.\n고객센터로 문의해 주시기 바랍니다.")
}
if (isCreator && member.role != MemberRole.CREATOR) {
throw SodaException("로그인 정보를 확인해주세요.")
}
if (isAdmin && member.role != MemberRole.ADMIN) {
throw SodaException("로그인 정보를 확인해주세요.")
}
val authenticationToken = UsernamePasswordAuthenticationToken(email, password)
val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken)
SecurityContextHolder.getContext().authentication = authentication
val jwt = tokenProvider.createToken(
authentication = authentication,
memberId = member.id!!
)
return LoginResponse(
userId = member.id!!,
token = jwt,
nickname = member.nickname,
email = member.email,
profileImage = member.profileImage ?: ""
)
}
private fun uploadProfileImage(profileImage: MultipartFile?, memberId: Long): String {
return if (profileImage != null) {
val metadata = ObjectMetadata()
metadata.contentLength = profileImage.size
s3Uploader.upload(
inputStream = profileImage.inputStream,
bucket = s3Bucket,
filePath = "profile/$memberId/${generateFileName(prefix = "$memberId-profile")}",
metadata = metadata
)
} else {
"profile/default-profile.png"
}
}
private fun agreeTermsOfServiceAndPrivacyPolicy(
member: Member,
stipulationTermsOfService: Stipulation,
stipulationPrivacyPolicy: Stipulation
) {
val termsOfServiceAgree = StipulationAgree(true)
termsOfServiceAgree.member = member
termsOfServiceAgree.stipulation = stipulationTermsOfService
stipulationAgreeRepository.save(termsOfServiceAgree)
val privacyPolicyAgree = StipulationAgree(true)
privacyPolicyAgree.member = member
privacyPolicyAgree.stipulation = stipulationPrivacyPolicy
stipulationAgreeRepository.save(privacyPolicyAgree)
}
private fun createMember(request: SignUpRequest): Member {
val member = Member(
email = request.email,
password = passwordEncoder.encode(request.password),
nickname = request.nickname,
gender = request.gender,
container = request.container
)
return repository.save(member)
}
private fun validatePassword(password: String) {
val passwordValidationMessage = validator.passwordValidation(password)
if (passwordValidationMessage.trim().isNotEmpty()) {
throw SodaException(passwordValidationMessage)
}
}
fun duplicateCheckEmail(email: String): ApiResponse<Any> {
validateEmail(email)
repository.findByEmail(email)?.let { throw SodaException("이미 사용중인 이메일 입니다.", "email") }
return ApiResponse.ok(message = "사용 가능한 이메일 입니다.")
}
private fun validateEmail(email: String) {
val emailValidationMessage = validator.emailValidation(email)
if (emailValidationMessage.trim().isNotEmpty()) {
throw SodaException(emailValidationMessage, "email")
}
}
fun duplicateCheckNickname(nickname: String): ApiResponse<Any> {
validateNickname(nickname)
repository.findByNickname(nickname)?.let { throw SodaException("이미 사용중인 닉네임 입니다.", "nickname") }
return ApiResponse.ok(message = "사용 가능한 닉네임 입니다.")
}
private fun validateNickname(nickname: String) {
val nicknameValidationMessage = validator.nicknameValidation(nickname)
if (nicknameValidationMessage.trim().isNotEmpty()) {
throw SodaException(nicknameValidationMessage, "nickname")
}
}
override fun loadUserByUsername(username: String): UserDetails {
val member = repository.findByEmail(email = username)
?: throw UsernameNotFoundException(username)
return MemberAdapter(member)
}
}