Skip to content

Commit

Permalink
feat(damaba): #63 사진작가 등록 기능/API 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Wo-ogie committed Nov 15, 2024
1 parent 2e1bb68 commit d56a0d4
Show file tree
Hide file tree
Showing 33 changed files with 1,282 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.damaba.damaba.adapter.inbound.photographer

import com.damaba.damaba.adapter.inbound.photographer.dto.PhotographerResponse
import com.damaba.damaba.adapter.inbound.photographer.dto.RegisterPhotographerRequest
import com.damaba.damaba.application.port.inbound.photographer.RegisterPhotographerUseCase
import com.damaba.damaba.mapper.PhotographerMapper
import com.damaba.user.domain.user.User
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@Tag(name = "사진작가 관련 API")
@RestController
class PhotographerController(
private val registerPhotographerUseCase: RegisterPhotographerUseCase,
) {
@Operation(
summary = "사진작가 등록(회원가입)",
description = "<p>유저 회원가입 시에만 한 번 사용하며, 사진작가 등록 정보(서비스 이용에 필요한 기본 정보)를 받아 설정합니다." +
"<p>사진작가의 회원가입에만 사용해야 하며, 일반 유저라면 유저 등록 API(<code>PATCH /api/v*/users/me/registration</code>)를 사용해야 합니다.",
security = [SecurityRequirement(name = "access-token")],
)
@ApiResponses(
ApiResponse(responseCode = "200"),
ApiResponse(responseCode = "404", description = "유저 정보를 찾을 수 없는 경우", content = [Content()]),
ApiResponse(
responseCode = "409",
description = "<p>이미 등록(가입)된 회원인 경우, 즉 유저 등록 API를 이전헤 호출한 적이 있었던 경우" +
"<p>수정하고자 하는 닉네임이 이미 사용중인 경우",
content = [Content()],
),
)
@PutMapping("/api/v1/photographers/me/registration")
fun registerPhotographerV1(
@AuthenticationPrincipal requester: User,
@RequestBody request: RegisterPhotographerRequest,
): PhotographerResponse {
val photographer = registerPhotographerUseCase.register(request.toCommand(requester.id))
return PhotographerMapper.INSTANCE.toPhotographerResponse(photographer)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.damaba.damaba.adapter.inbound.photographer.dto

import io.swagger.v3.oas.annotations.media.Schema
import java.time.DayOfWeek
import java.time.LocalTime

data class BusinessScheduleResponse(
@Schema(description = "영업 요일")
val days: Set<DayOfWeek>,

@Schema(description = "영업 시작 시간")
val startTime: LocalTime,

@Schema(description = "영업 종료 시간")
val endTime: LocalTime,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.damaba.damaba.adapter.inbound.photographer.dto

import com.damaba.damaba.adapter.inbound.common.dto.AddressResponse
import com.damaba.damaba.adapter.inbound.common.dto.ImageResponse
import com.damaba.damaba.adapter.inbound.region.dto.RegionResponse
import com.damaba.damaba.domain.common.PhotographyType
import com.damaba.user.domain.user.constant.Gender
import com.damaba.user.domain.user.constant.LoginType
import com.damaba.user.domain.user.constant.UserType
import io.swagger.v3.oas.annotations.media.Schema

data class PhotographerResponse(
@Schema(description = "Id of user", example = "1")
val id: Long,

@Schema(description = "User type")
val type: UserType,

@Schema(description = "사용하는 로그인 종류")
val loginType: LoginType,

@Schema(description = "닉네임", example = "홍길동")
val nickname: String,

@Schema(description = "프로필 이미지")
val profileImage: ImageResponse,

@Schema(description = "성별")
val gender: Gender,

@Schema(description = "(Nullable) 인스타 아이디", example = "damaba.official")
val instagramId: String?,

@Schema(description = "주력 촬영 종류")
val mainPhotographyTypes: Set<PhotographyType>,

@Schema(description = "(Nullable) 대표 링크", example = "https://damaba-contact.com")
val contactLink: String?,

@Schema(description = "(Nullable) 상세 소개", example = "안녕하세요. 수원에서 주로...")
val description: String?,

@Schema(description = "(Nullable) 상세주소")
val address: AddressResponse?,

@Schema(description = "(Nullable) 영업시간")
val businessSchedule: BusinessScheduleResponse?,

@Schema(description = "포트폴리오")
val portfolio: List<ImageResponse>,

@Schema(description = "활동 지역")
val activeRegions: Set<RegionResponse>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.damaba.damaba.adapter.inbound.photographer.dto

import com.damaba.damaba.adapter.inbound.common.dto.ImageRequest
import com.damaba.damaba.adapter.inbound.region.dto.RegionRequest
import com.damaba.damaba.application.port.inbound.photographer.RegisterPhotographerUseCase
import com.damaba.damaba.domain.common.PhotographyType
import com.damaba.damaba.mapper.ImageMapper
import com.damaba.damaba.mapper.RegionMapper
import com.damaba.user.domain.user.constant.Gender

data class RegisterPhotographerRequest(
val nickname: String,
val gender: Gender,
val instagramId: String?,
val profileImage: ImageRequest,
val mainPhotographyTypes: Set<PhotographyType>,
val activeRegions: Set<RegionRequest>,
) {
fun toCommand(requesterId: Long) = RegisterPhotographerUseCase.Command(
userId = requesterId,
nickname = nickname,
gender = gender,
instagramId = instagramId,
profileImage = ImageMapper.INSTANCE.toImage(profileImage),
mainPhotographyTypes = mainPhotographyTypes,
activeRegions = activeRegions.map { regionRequest -> RegionMapper.INSTANCE.toRegion(regionRequest) }.toSet(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import java.time.DayOfWeek
import java.time.LocalTime

@Embeddable
data class BusinessScheduleEmbeddable(
data class BusinessScheduleJpaEmbeddable(
@Convert(converter = BusinessDaysConverter::class)
@Column(name = "business_days", nullable = true)
val days: Set<DayOfWeek>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.damaba.damaba.adapter.outbound.photographer

import jakarta.persistence.Column
import jakarta.persistence.Embeddable

@Embeddable
data class PhotographerAddressJpaEmbeddable(
@Column(name = "sido", nullable = true)
val sido: String,

@Column(name = "sigungu", nullable = true)
val sigungu: String,

@Column(name = "road_address", nullable = true)
val roadAddress: String,

@Column(name = "jibun_address", nullable = true)
val jibunAddress: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.damaba.damaba.adapter.outbound.photographer

import com.damaba.damaba.application.port.outbound.photographer.GetPhotographerPort
import com.damaba.damaba.application.port.outbound.photographer.SavePhotographerPort
import com.damaba.damaba.domain.photographer.Photographer
import com.damaba.damaba.domain.photographer.exception.PhotographerNotFoundException
import com.damaba.damaba.mapper.PhotographerMapper
import com.damaba.user.adapter.outbound.user.UserJpaRepository
import com.damaba.user.adapter.outbound.user.UserProfileImageJpaEntity
import com.damaba.user.adapter.outbound.user.UserProfileImageJpaRepository
import com.damaba.user.domain.user.exception.UserNotFoundException
import org.springframework.stereotype.Repository

@Repository
class PhotographerCoreRepository(
private val photographerJpaRepository: PhotographerJpaRepository,
private val userJpaRepository: UserJpaRepository,
private val userProfileImageJpaRepository: UserProfileImageJpaRepository,
) : GetPhotographerPort,
SavePhotographerPort {
override fun getById(id: Long): Photographer {
val userJpaEntity = userJpaRepository.findById(id)
.orElseThrow { PhotographerNotFoundException() }
val photographerJpaEntity = photographerJpaRepository.findById(id)
.orElseThrow { PhotographerNotFoundException() }
return PhotographerMapper.INSTANCE.toPhotographer(userJpaEntity, photographerJpaEntity)
}

override fun saveIfUserExists(photographer: Photographer): Photographer {
// Update user
val userJpaEntity = userJpaRepository
.findById(photographer.id)
.orElseThrow { UserNotFoundException() }

val originalProfileImage = userJpaEntity.profileImage
deleteProfileImageIfExists(originalProfileImage.url)

userJpaEntity.update(photographer)

// Save photographer
userProfileImageJpaRepository.save(
UserProfileImageJpaEntity(
userId = photographer.id,
name = photographer.profileImage.name,
url = photographer.profileImage.url,
),
)

val photographerJpaEntity = PhotographerMapper.INSTANCE.toPhotographerJpaEntity(photographer)
photographerJpaRepository.save(photographerJpaEntity)

return photographer
}

private fun deleteProfileImageIfExists(imageUrl: String) {
val profileImage = userProfileImageJpaRepository.findByUrl(imageUrl)
profileImage?.delete()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.damaba.damaba.adapter.outbound.photographer

import com.damaba.damaba.adapter.outbound.common.AddressJpaEmbeddable
import com.damaba.damaba.adapter.outbound.common.BaseJpaTimeEntity
import com.damaba.damaba.domain.common.PhotographyType
import jakarta.persistence.CascadeType
Expand All @@ -20,15 +19,15 @@ class PhotographerJpaEntity(
mainPhotographyTypes: Set<PhotographyType>,
contactLink: String?,
description: String?,
address: AddressJpaEmbeddable?,
businessSchedule: BusinessScheduleEmbeddable?,
address: PhotographerAddressJpaEmbeddable?,
businessSchedule: BusinessScheduleJpaEmbeddable?,
) : BaseJpaTimeEntity() {
@Convert(converter = MainPhotohraphyTypesConverter::class)
@Column(name = "main_photography_type", nullable = false)
var mainPhotographyTypes: Set<PhotographyType> = mainPhotographyTypes
private set

@Column(name = "contact_link", nullable = false)
@Column(name = "contact_link", nullable = true)
var contactLink: String? = contactLink
private set

Expand All @@ -37,18 +36,24 @@ class PhotographerJpaEntity(
private set

@Embedded
var address: AddressJpaEmbeddable? = address
var address: PhotographerAddressJpaEmbeddable? = address
private set

@Embedded
var businessSchedule: BusinessScheduleEmbeddable? = businessSchedule
var businessSchedule: BusinessScheduleJpaEmbeddable? = businessSchedule
private set

@OneToMany(mappedBy = "photographer", cascade = [CascadeType.PERSIST])
var portfolio: MutableList<PhotographerPortfolioImageJpaEntity> = mutableListOf()
private set
@OneToMany(mappedBy = "photographer", cascade = [CascadeType.ALL])
private var _portfolio: MutableList<PhotographerPortfolioImageJpaEntity> = mutableListOf()

val portfolio: List<PhotographerPortfolioImageJpaEntity>
get() = _portfolio.filter { it.deletedAt == null }

@OneToMany(mappedBy = "photographer", cascade = [CascadeType.ALL], orphanRemoval = true)
var activeRegions: MutableSet<PhotographerActiveRegionJpaEntity> = mutableSetOf()
private set

fun addPortfolioImages(images: List<PhotographerPortfolioImageJpaEntity>) {
this._portfolio.addAll(images)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.damaba.damaba.adapter.outbound.photographer

import org.springframework.data.jpa.repository.JpaRepository

interface PhotographerJpaRepository : JpaRepository<PhotographerJpaEntity, Long>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.damaba.damaba.adapter.outbound.photographer

import org.springframework.data.jpa.repository.JpaRepository

interface PhotographerPortfolioImageJpaRepository : JpaRepository<PhotographerPortfolioImageJpaEntity, Long>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.damaba.damaba.application.port.inbound.photographer

import com.damaba.common_file.domain.Image
import com.damaba.damaba.domain.common.PhotographyType
import com.damaba.damaba.domain.photographer.Photographer
import com.damaba.damaba.domain.photographer.PhotographerValidator
import com.damaba.damaba.domain.region.Region
import com.damaba.user.domain.user.UserValidator
import com.damaba.user.domain.user.constant.Gender
import com.damaba.user.domain.user.exception.NicknameAlreadyExistsException
import com.damaba.user.domain.user.exception.UserAlreadyRegisteredException
import com.damaba.user.domain.user.exception.UserNotFoundException

interface RegisterPhotographerUseCase {
/**
* 사진작가를 등록한다. 즉, 사진작가의 등록 정보를 수정한다.
* '등록 정보'란 회원가입 시 유저(사진작가)에게 입력받는 정보를 의미한다.
*
* @param command
* @return 등록된 사진작가
* @throws UserNotFoundException `userId`에 해당하는 유저를 찾을 수 없는 경우
* @throws UserAlreadyRegisteredException 이미 등록된 유저인 경우
* @throws NicknameAlreadyExistsException `nickname`을 다른 유저가 이미 사용중인 경우
*/
fun register(command: Command): Photographer

data class Command(
val userId: Long,
val nickname: String,
val gender: Gender,
val instagramId: String?,
val profileImage: Image,
val mainPhotographyTypes: Set<PhotographyType>,
val activeRegions: Set<Region>,
) {
init {
PhotographerValidator.validateNickname(nickname)
if (instagramId != null) UserValidator.validateInstagramId(instagramId)
PhotographerValidator.validateMainPhotographyTypes(mainPhotographyTypes)
PhotographerValidator.validateActiveRegions(activeRegions)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.damaba.damaba.application.port.outbound.photographer

import com.damaba.damaba.domain.photographer.Photographer
import com.damaba.damaba.domain.photographer.exception.PhotographerNotFoundException

interface GetPhotographerPort {
/**
* 사진작가를 단건 조회한다.
*
* @param id 조회할 사진작가의 id
* @return 조회된 사진작가
* @throws PhotographerNotFoundException id와 일치하는 사진작가가 없는 경우
*/
fun getById(id: Long): Photographer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.damaba.damaba.application.port.outbound.photographer

import com.damaba.damaba.domain.photographer.Photographer
import com.damaba.user.domain.user.exception.UserNotFoundException

interface SavePhotographerPort {
/**
* `Photographer`를 저장한다.
* `Photographer`는 `User`가 기존에 존재할 때만 생성/저장이 가능하다.
*
* @param photographer 저장할 `Photographer`
* @return 저장된 `Photographer`
* @throws UserNotFoundException 기존 유저 데이터가 존재하지 않는 경우.
*/
fun saveIfUserExists(photographer: Photographer): Photographer
}
Loading

0 comments on commit d56a0d4

Please sign in to comment.