Skip to content

Commit

Permalink
Merge pull request #257 from Team-Ampersand/feature/upload_profile_image
Browse files Browse the repository at this point in the history
🔀 :: 프로필 이미지 업로드 API
  • Loading branch information
KimGyeongsuuu committed Oct 5, 2023
2 parents 60209a3 + 3ae725f commit 478523f
Show file tree
Hide file tree
Showing 33 changed files with 162 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class CreateBoardServiceImpl(
private val boardSaveUtil: BoardSaveUtil
) : CreateBoardService {


@Value("\${cloud.aws.s3.url}")
private val S3_ADDRESS: String? = null

Expand All @@ -35,7 +34,7 @@ class CreateBoardServiceImpl(
.let { boardSaveUtil.saveBoard(board = it) }
}

val uploadFile: List<String> = s3Service.uploadFile(multipartFiles)
val uploadFile: List<String> = s3Service.uploadListFile(multipartFiles)
val board: Board = toEntity(createBoardDto, member)
.let { boardSaveUtil.saveBoard(board = it) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import com.dotori.v2.global.entity.BaseTimeEntity
import java.time.LocalDateTime
import javax.persistence.*


@Entity(name = "member")
@Entity
@Table(name = "member")
class Member(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down Expand Up @@ -36,14 +36,17 @@ class Member(
val roles: MutableList<Role>,

@OneToMany(mappedBy = "member")
val ruleViolation: MutableList<RuleViolation>
val ruleViolation: MutableList<RuleViolation>,

@Column(name = "profileImage")
var profileImage: String?

) : BaseTimeEntity() {
@Column(name = "member_refreshToken")
var refreshToken: String = ""
private set

@Column(name = "member_point", columnDefinition = "Long default 0")
@Column(name = "member_point")
val point: Long = 0

@Column(name = "self_study_check")
Expand Down Expand Up @@ -101,4 +104,18 @@ class Member(
fun updateSelfStudyExpiredDate(localDateTime: LocalDateTime?) {
this.selfStudyExpiredDate = localDateTime
}

fun updateProfileImage(profileImage: String?): Member {
return Member(
id = this.id,
memberName = this.memberName,
stuNum = this.stuNum,
email = this.email,
password = this.password,
gender = this.gender,
roles = this.roles,
ruleViolation = this.ruleViolation,
profileImage = profileImage
)
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package com.dotori.v2.domain.member.presentation

import com.dotori.v2.domain.member.presentation.data.req.NewPasswordReqDto
import com.dotori.v2.domain.member.presentation.data.req.WithdrawalReqDto
import com.dotori.v2.domain.member.service.ChangeAuthPasswordService
import com.dotori.v2.domain.member.service.ChangePasswordService
import com.dotori.v2.domain.member.service.LogoutService
import com.dotori.v2.domain.member.service.WithdrawalService
import com.dotori.v2.domain.member.service.*
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import javax.validation.Valid

@RestController
@RequestMapping("/v2/members")
class MemberController(
private val logoutService: LogoutService,
private val withdrawalService: WithdrawalService,
private val changeAuthPasswordService: ChangeAuthPasswordService
private val changeAuthPasswordService: ChangeAuthPasswordService,
private val uploadProfileImageService: UploadProfileImageService
) {
@DeleteMapping("/logout")
fun logout(): ResponseEntity<Void> =
Expand All @@ -32,4 +30,9 @@ class MemberController(
changeAuthPasswordService.execute(newPasswordReqDto)
.run { ResponseEntity.ok().build() }

@PostMapping("/profileImage")
fun uploadProfileImage(@RequestParam(value = "images") multipartFiles: MultipartFile?): ResponseEntity<Void> =
uploadProfileImageService.execute(multipartFiles)
.run { ResponseEntity.ok().build() }

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SignupReqDto(
email = email,
gender = gender,
roles = Collections.singletonList(Role.ROLE_MEMBER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dotori.v2.domain.member.service

import org.springframework.web.multipart.MultipartFile

interface UploadProfileImageService {
fun execute(multipartFiles: MultipartFile?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.dotori.v2.domain.member.service.impl

import com.dotori.v2.domain.member.domain.entity.Member
import com.dotori.v2.domain.member.domain.repository.MemberRepository
import com.dotori.v2.domain.member.service.UploadProfileImageService
import com.dotori.v2.global.thirdparty.aws.s3.S3Service
import com.dotori.v2.global.util.UserUtil
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile

@Service
@Transactional(rollbackFor = [Exception::class])
class UploadProfileImageServiceImpl(
private val memberRepository: MemberRepository,
private val userUtil: UserUtil,
private val s3Service: S3Service,
) : UploadProfileImageService {
override fun execute(multipartFiles: MultipartFile?) {
val member: Member = userUtil.fetchCurrentUser()
val uploadFile: String? = s3Service.uploadSingleFile(multipartFiles)

member.updateProfileImage(uploadFile)
.let { memberRepository.save(it) }

}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package com.dotori.v2.domain.member.service.impl

import com.dotori.v2.domain.member.domain.repository.MemberRepository
import com.dotori.v2.domain.member.exception.MemberNotFoundException
import com.dotori.v2.domain.member.exception.MemberNotSameException
import com.dotori.v2.domain.member.exception.PasswordMismatchException
import com.dotori.v2.domain.member.presentation.data.req.WithdrawalReqDto
import com.dotori.v2.domain.member.service.WithdrawalService
import com.dotori.v2.global.thirdparty.aws.s3.S3Service
import com.dotori.v2.global.util.UserUtil
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional(rollbackFor = [Exception::class])
class WithdrawalServiceImpl(
private val memberRepository: MemberRepository,
private val s3Service: S3Service,
private val userUtil: UserUtil,
) : WithdrawalService {
override fun execute() {
val currentUser = userUtil.fetchCurrentUser()

currentUser.profileImage?.let { s3Service.deleteFile(it) }
memberRepository.delete(currentUser)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class ModifyStudentInfoServiceImpl(
password = member.password,
gender = modifyStudentInfoRequest.gender,
roles = Collections.singletonList(modifyStudentInfoRequest.role),
ruleViolation = member.ruleViolation
ruleViolation = member.ruleViolation,
profileImage = member.profileImage
)
memberRepository.save(newMember)
}
Expand Down
16 changes: 10 additions & 6 deletions src/main/kotlin/com/dotori/v2/global/config/dev/DevMemberConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.dotori.v2.domain.member.domain.entity.Member
import com.dotori.v2.domain.member.domain.repository.MemberRepository
import com.dotori.v2.domain.member.enums.Gender
import com.dotori.v2.domain.member.enums.Role
import com.dotori.v2.global.security.jwt.TokenProvider
import org.springframework.context.annotation.Profile
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Component
Expand All @@ -25,7 +24,8 @@ class DevMemberConfig(
gender = Gender.PENDING,
password = passwordEncoder.encode("string1!"),
roles = mutableListOf(Role.ROLE_ADMIN),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
memberRepository.save(admin)

Expand All @@ -36,7 +36,8 @@ class DevMemberConfig(
gender = Gender.PENDING,
password = passwordEncoder.encode("string1!"),
roles = mutableListOf(Role.ROLE_DEVELOPER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
memberRepository.save(developer)

Expand All @@ -47,7 +48,8 @@ class DevMemberConfig(
gender = Gender.PENDING,
password = passwordEncoder.encode("string1!"),
roles = mutableListOf(Role.ROLE_COUNCILLOR),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
memberRepository.save(councillor)

Expand All @@ -58,7 +60,8 @@ class DevMemberConfig(
gender = Gender.MAN,
password = passwordEncoder.encode("string1!"),
roles = mutableListOf(Role.ROLE_MEMBER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
memberRepository.save(man)

Expand All @@ -69,7 +72,8 @@ class DevMemberConfig(
gender = Gender.WOMAN,
password = passwordEncoder.encode("string1!"),
roles = mutableListOf(Role.ROLE_MEMBER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
memberRepository.save(woman)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.dotori.v2.global.thirdparty.aws.s3
import org.springframework.web.multipart.MultipartFile

interface S3Service {
fun uploadFile(multipartFiles: List<MultipartFile>?): List<String>
fun uploadListFile(multipartFiles: List<MultipartFile>?): List<String>
fun uploadSingleFile(multipartFiles: MultipartFile?): String?
fun deleteFile(fileName: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class S3ServiceImpl(
@Value("\${cloud.aws.s3.bucket}")
private val bucket: String? = null

override fun uploadFile(multipartFiles: List<MultipartFile>?): List<String> {
override fun uploadListFile(multipartFiles: List<MultipartFile>?): List<String> {
val fileNameList = ArrayList<String>()

multipartFiles.orEmpty().forEach { file ->
Expand All @@ -42,6 +42,28 @@ class S3ServiceImpl(
return fileNameList
}

override fun uploadSingleFile(multipartFile: MultipartFile?): String? {
if (multipartFile == null || multipartFile.isEmpty) {
return null
}

val fileName = createFileName(multipartFile.originalFilename.orEmpty())
val objectMetadata = ObjectMetadata().apply {
contentLength = multipartFile.size
contentType = multipartFile.contentType
}

try {
amazonS3.putObject(
PutObjectRequest(bucket, fileName, multipartFile.inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead)
)
return fileName
} catch (e: IOException) {
throw IllegalStateException("파일 업로드에 실패했습니다.")
}
}

override fun deleteFile(fileName: String) {
amazonS3.deleteObject(DeleteObjectRequest(bucket, fileName))
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/com/dotori/v2/V2ApplicationTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ class V2ApplicationTests {
fun contextLoads() {
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ class CreateBoardControllerTest : BehaviorSpec({
password = "test",
gender = Gender.MAN,
roles = Collections.singletonList(Role.ROLE_MEMBER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
val board = Board(
title = "thisIsTitle",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class DeleteMultipleBoardServiceTest : BehaviorSpec({
val boardRepository = mockk<BoardRepository>()
val boardImageRepository = mockk<BoardImageRepository>()
val s3Service = mockk<S3Service>()

val deleteMultipleBoardServiceImpl = DeleteMultipleBoardServiceImpl(boardRepository, boardImageRepository, s3Service)

Given("공지사항 과 공지사항안에 이미자가 주어지면") {
Expand All @@ -32,7 +31,8 @@ class DeleteMultipleBoardServiceTest : BehaviorSpec({
password = "test",
gender = Gender.MAN,
roles = Collections.singletonList(Role.ROLE_MEMBER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
val board = Board(
id = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class BoardAlarmServiceTest : BehaviorSpec({
password = "test",
gender = Gender.MAN,
roles = Collections.singletonList(Role.ROLE_MEMBER),
ruleViolation = mutableListOf()
ruleViolation = mutableListOf(),
profileImage = null
)
val board = Board(1, testMember, "title", "content", boardImage = listOf())
every { boardRepository.findAllByOrderByCreatedDateDesc() } returns listOf(board)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package com.dotori.v2.domain.member.controller

import com.dotori.v2.domain.member.presentation.MemberController
import com.dotori.v2.domain.member.presentation.data.req.NewPasswordReqDto
import com.dotori.v2.domain.member.service.ChangeAuthPasswordService
import com.dotori.v2.domain.member.service.ChangePasswordService
import com.dotori.v2.domain.member.service.LogoutService
import com.dotori.v2.domain.member.service.WithdrawalService
import com.dotori.v2.domain.member.service.*
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
Expand All @@ -17,7 +14,8 @@ class ChangePasswordControllerTest : BehaviorSpec({
val logoutService = mockk<LogoutService>()
val withdrawalService = mockk<WithdrawalService>()
val changePasswordService = mockk<ChangeAuthPasswordService>()
val authController = MemberController(logoutService, withdrawalService, changePasswordService)
val uploadProfileImageService = mockk<UploadProfileImageService>()
val authController = MemberController(logoutService, withdrawalService, changePasswordService, uploadProfileImageService)

given("요청이 들어오면") {
`when`("is received") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package com.dotori.v2.domain.member.controller

import com.dotori.v2.domain.member.presentation.MemberController
import com.dotori.v2.domain.member.presentation.data.res.LogoutResDto
import com.dotori.v2.domain.member.service.ChangeAuthPasswordService
import com.dotori.v2.domain.member.service.ChangePasswordService
import com.dotori.v2.domain.member.service.LogoutService
import com.dotori.v2.domain.member.service.WithdrawalService
import com.dotori.v2.domain.member.service.*
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
Expand All @@ -17,7 +14,8 @@ class LogoutControllerTest : BehaviorSpec({
val logoutService = mockk<LogoutService>()
val withdrawalService = mockk<WithdrawalService>()
val changePasswordService = mockk<ChangeAuthPasswordService>()
val authController = MemberController(logoutService, withdrawalService, changePasswordService)
val uploadProfileImageService = mockk<UploadProfileImageService>()
val authController = MemberController(logoutService, withdrawalService, changePasswordService, uploadProfileImageService)

given("요청이 들어오면") {
`when`("is received") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class WithdrawalControllerTest : BehaviorSpec({
val logoutService = mockk<LogoutService>()
val withdrawalService = mockk<WithdrawalService>()
val changePasswordService = mockk<ChangeAuthPasswordService>()
val authController = MemberController(logoutService, withdrawalService, changePasswordService)
val uploadProfileImageService = mockk<UploadProfileImageService>()
val authController = MemberController(logoutService, withdrawalService, changePasswordService, uploadProfileImageService)

given("요청이 들어오면") {
`when`("is received") {
Expand Down
Loading

0 comments on commit 478523f

Please sign in to comment.