Skip to content

#136, 137 - 익명 프로필 생성 및 수정 API를 구현합니다. #148

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

Merged
merged 2 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/wespot/ApiApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.wespot

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableAsync

@SpringBootApplication(scanBasePackages = ["com.wespot"])
@EnableAsync
class ApiApplication

fun main(args: Array<String>) {
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/kotlin/com/wespot/user/AnonymousProfileController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.wespot.user

import com.wespot.user.dto.request.CreatedAnonymousProfileRequest
import com.wespot.user.dto.request.UpdatedAnonymousProfileRequest
import com.wespot.user.port.`in`.AnonymousProfileUseCase
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/v1/messages/profiles")
class AnonymousProfileController(
private val anonymousProfileUseCase: AnonymousProfileUseCase
) {

@PostMapping
fun createAnonymousProfile(@RequestBody createdAnonymousProfileRequest: CreatedAnonymousProfileRequest): ResponseEntity<Unit> {
anonymousProfileUseCase.createAnonymousProfile(createdAnonymousProfileRequest)

return ResponseEntity.status(HttpStatus.CREATED)
.build()
}

@PutMapping("/{id}")
fun unblockUser(
@PathVariable id: Long,
@RequestBody updatedAnonymousProfileRequest: UpdatedAnonymousProfileRequest
): ResponseEntity<Unit> {
anonymousProfileUseCase.updateAnonymousProfile(id, updatedAnonymousProfileRequest)

return ResponseEntity.noContent()
.build()
}

}
2 changes: 2 additions & 0 deletions core/src/main/kotlin/com/wespot/image/out/ImagePort.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ interface ImagePort {

fun deleteByUrl(url: String)

fun deleteById(imageId: Long)

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package com.wespot.image.service.listener

import com.wespot.image.event.CreatedImageWhenSignUpEvent
import com.wespot.image.event.DeletedImageEvent
import com.wespot.image.event.SavedImageEvent
import com.wespot.image.out.ImagePort
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.event.TransactionPhase
import org.springframework.transaction.event.TransactionalEventListener

@Component
class ImageEventListener(
Expand All @@ -15,4 +22,18 @@ class ImageEventListener(
imagePort.save(event.image)
}

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
fun saveImage(event: SavedImageEvent) {
imagePort.save(event.image)
}

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
fun deleteImage(event: DeletedImageEvent) {
imagePort.deleteByUrl(event.url)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wespot.user.dto.request

data class CreatedAnonymousProfileRequest(
val imageUrl: String,
val name: String,
val receiverId: Long,
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wespot.user.dto.request

data class UpdatedAnonymousProfileRequest(
val imageUrl: String,
val name: String,
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.wespot.user.port.`in`

import com.wespot.user.dto.request.CreatedAnonymousProfileRequest
import com.wespot.user.dto.request.UpdatedAnonymousProfileRequest


interface AnonymousProfileUseCase {

fun createAnonymousProfile(createdAnonymousProfileRequest: CreatedAnonymousProfileRequest)

fun updateAnonymousProfile(profileId: Long, updatedAnonymousProfileRequest: UpdatedAnonymousProfileRequest)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wespot.user.port.out

import com.wespot.user.message.AnonymousProfile

interface AnonymousProfilePort {

fun findByProfileId(profileId: Long): AnonymousProfile?

fun save(anonymousProfile: AnonymousProfile): AnonymousProfile

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.wespot.user.service

import com.wespot.EventUtils
import com.wespot.auth.service.SecurityUtils
import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import com.wespot.image.Image
import com.wespot.image.event.DeletedImageEvent
import com.wespot.image.event.SavedImageEvent
import com.wespot.user.dto.request.CreatedAnonymousProfileRequest
import com.wespot.user.dto.request.UpdatedAnonymousProfileRequest
import com.wespot.user.message.AnonymousProfile
import com.wespot.user.message.ProfileName
import com.wespot.user.port.`in`.AnonymousProfileUseCase
import com.wespot.user.port.out.AnonymousProfilePort
import com.wespot.user.port.out.UserPort
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class AnonymousProfileService(
private val anonymousProfilePort: AnonymousProfilePort,
private val userPort: UserPort,
@Value("\${aws.cloud-front.url}")
private val cloudFrontUrl: String,
) : AnonymousProfileUseCase {

@Transactional
override fun createAnonymousProfile(createdAnonymousProfileRequest: CreatedAnonymousProfileRequest) {
val loginUser = SecurityUtils.getLoginUser(userPort)
val names = userPort.findAll()
.map { it.name }
.toSet()
val image = Image.createImage(createdAnonymousProfileRequest.imageUrl, cloudFrontUrl)
val profileName = ProfileName.of(createdAnonymousProfileRequest.name, names)

val anonymousProfile = AnonymousProfile.of(
image = image,
profileName = profileName,
owner = loginUser,
receiverId = createdAnonymousProfileRequest.receiverId
)

anonymousProfilePort.save(anonymousProfile)
EventUtils.publish(SavedImageEvent(image))
}

@Transactional
override fun updateAnonymousProfile(
profileId: Long,
updatedAnonymousProfileRequest: UpdatedAnonymousProfileRequest
) {
val loginUser = SecurityUtils.getLoginUser(userPort)
val names = userPort.findAll()
.map { it.name }
.toSet()
val image = Image.createImage(updatedAnonymousProfileRequest.imageUrl, cloudFrontUrl)
val profileName = ProfileName.of(updatedAnonymousProfileRequest.name, names)

val savedAnonymousProfile = anonymousProfilePort.findByProfileId(profileId) ?: throw CustomException(
HttpStatus.BAD_REQUEST,
ExceptionView.TOAST,
"실명 프로필로를 찾을 수 없습니다."
)

val updatedAnonymousProfile = savedAnonymousProfile.update(
image, profileName, loginUser.id
)
anonymousProfilePort.save(updatedAnonymousProfile)

EventUtils.publish(SavedImageEvent(image))
EventUtils.publish(DeletedImageEvent(savedAnonymousProfile.imageUrl))
}
}
3 changes: 1 addition & 2 deletions domain/src/main/kotlin/com/wespot/image/Image.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.wespot.EventUtils
import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import com.wespot.image.event.UpdateProfileImageEvent
import com.wespot.user.UserIntroduction
import org.springframework.http.HttpStatus
import java.time.LocalDateTime
import java.util.*
Expand Down Expand Up @@ -44,7 +43,7 @@ data class Image(
return savedImage(image)
}

private fun createImage(url: String, cloudFrontUrl: String): Image {
fun createImage(url: String, cloudFrontUrl: String): Image {
require(url.isNotBlank()) {
throw CustomException(
HttpStatus.BAD_REQUEST,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wespot.image.event

data class DeletedImageEvent(
val url: String
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wespot.image.event

import com.wespot.image.Image

data class SavedImageEvent(
val image: Image
) {
}
6 changes: 3 additions & 3 deletions domain/src/main/kotlin/com/wespot/message/MessageV2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ data class MessageV2(
val isReceiverDeleted: Boolean,
val receiverDeletedAt: LocalDateTime?,

val messageRoomId: Long,
val messageRoomId: Long?,
val anonymousProfileId: Long?,
val messageRoomOwnerId: Long,
val isBookmarked: Boolean
val messageRoomOwnerId: Long?,
val isBookmarked: Boolean?
) {
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
package com.wespot.user.message

import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import com.wespot.image.Image
import com.wespot.user.User
import org.springframework.http.HttpStatus
import java.time.LocalDateTime

class AnonymousProfile(
val id: Long,
val imageUrl: String,
val name: String,
val ownerId: User,
val owner: User,
val receiverId: Long,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime,
) {
companion object {

fun of(
image: Image,
profileName: ProfileName,
owner: User,
receiverId: Long,
): AnonymousProfile {
return AnonymousProfile(
id = 0,
imageUrl = image.url,
name = profileName.name,
owner = owner,
receiverId = receiverId,
createdAt = LocalDateTime.now(),
updatedAt = LocalDateTime.now()
)
}
}

fun update(image: Image, profileName: ProfileName, accessUserId: Long): AnonymousProfile {
if (accessUserId != owner.id) {
throw CustomException(
HttpStatus.FORBIDDEN,
ExceptionView.TOAST,
"해당 익명 프로필의 소유주가 아니어 변경할 수 없습니다."
)
}
return AnonymousProfile(
id = id,
imageUrl = image.url,
name = profileName.name,
owner = owner,
receiverId = receiverId,
createdAt = createdAt,
updatedAt = LocalDateTime.now()
)
}

}
31 changes: 31 additions & 0 deletions domain/src/main/kotlin/com/wespot/user/message/ProfileName.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.wespot.user.message

import com.wespot.common.ProfanityChecker
import com.wespot.exception.CustomException
import com.wespot.exception.ExceptionView
import io.micrometer.common.util.StringUtils
import org.springframework.http.HttpStatus

class ProfileName(
val name: String
) {

companion object {
fun of(name: String, names: Set<String>): ProfileName {
if (StringUtils.isBlank(name)) {
throw CustomException(HttpStatus.BAD_REQUEST, ExceptionView.TOAST, "익명 프로필로 설정할 이름은 존재해야합니다.")
}

if (ProfanityChecker.checkProfanity(name)) {
throw CustomException(HttpStatus.BAD_REQUEST, ExceptionView.TOAST, "익명 프로필로에 비속어가 포함되어 있습니다.")
}

if (names.contains(name)) {
throw CustomException(HttpStatus.BAD_REQUEST, ExceptionView.TOAST, "실명은 익명 프로필로 활용할 수 없습니다.")
}

return ProfileName(name)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ class ImagePersistentAdapter(
imageJpaRepository.deleteByUrl(url)
}

override fun deleteById(imageId: Long) {
imageJpaRepository.deleteById(imageId)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ class MessageJpaEntityV2(

val receiverDeletedAt: LocalDateTime?,

val messageRoomId: Long,
val messageRoomId: Long? = 0,
val anonymousProfileId: Long?,
val messageRoomOwnerId: Long,
val isBookmarked: Boolean
val messageRoomOwnerId: Long? = 0,
val isBookmarked: Boolean? = false
) {

}
Loading
Loading