Skip to content

Commit

Permalink
[FO-866] 프로필 검증 API (#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
DoodlesOnMyFood authored Jan 22, 2024
1 parent 586674a commit 8a47c6f
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.fone.profile.application

import com.fone.profile.domain.service.ValidateProfileService
import com.fone.profile.presentation.dto.ValidateProfileDto
import org.springframework.stereotype.Service

@Service
class ValidateProfileFacade(
private val validateProfileService: ValidateProfileService,
) {

suspend fun validateBasicPage(basicPageValidation: ValidateProfileDto.BasicPageValidation) =
validateProfileService.validateBasicPage(basicPageValidation)

suspend fun validateDetailPage(email: String, detailPageValidation: ValidateProfileDto.DetailPageValidation) =
validateProfileService.validateDetailPage(email, detailPageValidation)

suspend fun validateDescriptionPage(descriptionPageValidation: ValidateProfileDto.DescriptionPageValidation) =
validateProfileService.validateDescriptionPage(descriptionPageValidation)

suspend fun validateCareerPage(careerPageValidation: ValidateProfileDto.CareerPageValidation) =
validateProfileService.validateCareerPage(careerPageValidation)

suspend fun validateInterestPage(interestPageValidation: ValidateProfileDto.InterestPageValidation) =
validateProfileService.validateInterestPage(interestPageValidation)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.fone.profile.domain.service

import com.fone.common.exception.NotFoundUserException
import com.fone.common.exception.RequestValidationException
import com.fone.profile.presentation.dto.ValidateProfileDto
import com.fone.user.domain.enum.Job
import com.fone.user.domain.repository.UserRepository
import org.hibernate.validator.internal.constraintvalidators.hv.URLValidator
import org.springframework.stereotype.Service

@Service
class ValidateProfileService(
private val userRepository: UserRepository,
) {
private val urlValidator = URLValidator()

fun validateBasicPage(basicPageValidation: ValidateProfileDto.BasicPageValidation) {
if (basicPageValidation.name.isNullOrBlank()) {
throw RequestValidationException("항목 '이름'이 비었습니다.")
}
if (basicPageValidation.hookingComment.isNullOrEmpty()) {
throw RequestValidationException("항목 '후킹멘트'가 비었습니다.")
}
if (basicPageValidation.profileImages.isNullOrEmpty()) {
throw RequestValidationException("항목 '이미지 첨부'가 비었습니다.")
}
if (basicPageValidation.profileImages.any { urlValidator.isValid(it, null) }) {
throw RequestValidationException("항목 '이미지 첨부'에 유효하지 않은 값이 있습니다.")
}
}

suspend fun validateDetailPage(
email: String,
detailPageValidation: ValidateProfileDto.DetailPageValidation,
) {
val user = userRepository.findByEmail(email) ?: throw NotFoundUserException()
if (detailPageValidation.birthday == null) {
throw RequestValidationException("항목 '출생연도'가 비었습니다.")
}
if (detailPageValidation.gender == null) {
throw RequestValidationException("항목 '성별'이 비었습니다.")
}
if (user.job == Job.STAFF) {
if (detailPageValidation.domains.isNullOrEmpty()) {
throw RequestValidationException("항목 '도메인'이 비었습니다.")
}
} else {
if (detailPageValidation.height == null) {
throw RequestValidationException("항목 '신장'이 비었습니다.")
}
if (detailPageValidation.weight == null) {
throw RequestValidationException("항목 '체중'이 비었습니다.")
}
}
if (detailPageValidation.email == null) {
throw RequestValidationException("항목 '성별'이 비었습니다.")
}
}

fun validateDescriptionPage(descriptionPageValidation: ValidateProfileDto.DescriptionPageValidation) {
if (descriptionPageValidation.details.isNullOrBlank()) {
throw RequestValidationException("항목 '상세요강'이 비었습니다.")
}
if (descriptionPageValidation.details.length > 200) {
throw RequestValidationException("항목 '상세요강'이 200자 넘습니다.")
}
}

fun validateCareerPage(careerPageValidation: ValidateProfileDto.CareerPageValidation) {
if (careerPageValidation.career == null) {
throw RequestValidationException("항목 '경력'이 비었습니다.")
}
if (careerPageValidation.careerDetail.isNullOrBlank()) {
throw RequestValidationException("항목 '경력 상세사항'이 비었습니다.")
}
if (careerPageValidation.careerDetail.length > 500) {
throw RequestValidationException("항목 '경력 상세사항'이 500자 넘습니다.")
}
}

fun validateInterestPage(interestPageValidation: ValidateProfileDto.InterestPageValidation) {
if (interestPageValidation.categories.isNullOrEmpty()) {
throw RequestValidationException("항목 '관심사'가 비었습니다.")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.fone.profile.presentation.controller

import com.fone.common.response.CommonResponse
import com.fone.profile.application.ValidateProfileFacade
import com.fone.profile.presentation.dto.ValidateProfileDto
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.v3.oas.annotations.responses.ApiResponse
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.security.Principal

@Api(tags = ["04. Profile Info"], description = "프로필 서비스")
@RestController
@RequestMapping("/api/v1/profiles/validate")
class ValidateProfileController(
private val validateProfileFacade: ValidateProfileFacade,
) {
@PostMapping("/basic")
@PreAuthorize("hasRole('USER')")
@ApiOperation(value = "프로필 검증 API")
@ApiResponse(
responseCode = "200",
description = "성공"
)
suspend fun basicPageValidate(
@RequestBody
basicPageValidation: ValidateProfileDto.BasicPageValidation,
): CommonResponse<Unit> {
validateProfileFacade.validateBasicPage(basicPageValidation)
return CommonResponse.success()
}

@PostMapping("/details")
@PreAuthorize("hasRole('USER')")
@ApiOperation(value = "프로필 검증 API")
@ApiResponse(
responseCode = "200",
description = "성공"
)
suspend fun detailPageValidate(
principal: Principal,
@RequestBody
detailPageValidation: ValidateProfileDto.DetailPageValidation,
): CommonResponse<Unit> {
validateProfileFacade.validateDetailPage(principal.name, detailPageValidation)
return CommonResponse.success()
}

@PostMapping("/description")
@PreAuthorize("hasRole('USER')")
@ApiOperation(value = "프로필 검증 API")
@ApiResponse(
responseCode = "200",
description = "성공"
)
suspend fun descriptionValidate(
@RequestBody
descriptionPageValidation: ValidateProfileDto.DescriptionPageValidation,
): CommonResponse<Unit> {
validateProfileFacade.validateDescriptionPage(descriptionPageValidation)
return CommonResponse.success()
}

@PostMapping("/career")
@PreAuthorize("hasRole('USER')")
@ApiOperation(value = "프로필 검증 API")
@ApiResponse(
responseCode = "200",
description = "성공"
)
suspend fun careerValidate(
@RequestBody
careerPageValidation: ValidateProfileDto.CareerPageValidation,
): CommonResponse<Unit> {
validateProfileFacade.validateCareerPage(careerPageValidation)
return CommonResponse.success()
}

@PostMapping("/interest")
@PreAuthorize("hasRole('USER')")
@ApiOperation(value = "프로필 검증 API")
@ApiResponse(
responseCode = "200",
description = "성공"
)
suspend fun interestValidate(
@RequestBody
interestPageValidation: ValidateProfileDto.InterestPageValidation,
): CommonResponse<Unit> {
validateProfileFacade.validateInterestPage(interestPageValidation)
return CommonResponse.success()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.fone.profile.presentation.dto

import com.fone.common.entity.Career
import com.fone.common.entity.CategoryType
import com.fone.common.entity.DomainType
import com.fone.common.entity.Gender
import com.fone.profile.presentation.dto.common.ProfileSnsUrl
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDate

class ValidateProfileDto {
data class BasicPageValidation(
@Schema(description = "프로필 이름", required = true, example = "차이나는 클라스")
val name: String?,
@Schema(description = "후킹멘트", required = true, example = "제가 좋아하는 색은 노랑색이에요")
val hookingComment: String?,
@Schema(
description = "이미지 URL",
example = "['https://s3-ap-northeast-2.amazonaws.com/f-one-image/prod/user-profile/image.jpg']"
)
val profileImages: List<String>?,
)

data class DetailPageValidation(
@Schema(description = "생년월일", example = "2000-10-01")
val birthday: LocalDate?,
@Schema(description = "성별", example = "WOMAN")
val gender: Gender?,
@Schema(description = "", example = "188")
val height: Int?,
@Schema(description = "몸무게", example = "70")
val weight: Int?,
@Schema(description = "이메일", example = "example@something.com")
val email: String?,
@Schema(description = "SNS v2")
val snsUrls: List<ProfileSnsUrl>?,
@Schema(description = "특기", example = "매운 음식 먹기")
val specialty: String?,
@Schema(description = "분야", example = "PLANNING")
val domains: List<DomainType>?,
)

data class DescriptionPageValidation(
@Schema(description = "상세요강")
val details: String?,
)

data class CareerPageValidation(
@Schema(description = "경력", example = "LESS_THAN_3YEARS")
val career: Career?,
@Schema(description = "경력 상세 설명", example = "복숭아 요거트 제작 3년")
val careerDetail: String?,
)

data class InterestPageValidation(
@Schema(description = "관심사", example = "WEB_DRAMA")
val categories: List<CategoryType>?,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import org.springframework.test.web.reactive.server.WebTestClient
import java.time.LocalDate

@IntegrationTest
class ValidateControllerTest(client: WebTestClient) : CustomDescribeSpec() {
class ValidateJobOpeningControllerTest(client: WebTestClient) : CustomDescribeSpec() {
init {
val url = "/api/v1/job-openings/validate"
val (accessToken, _) = CommonUserCallApi.getAccessToken(client)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.fone.profile.presentation.controller

import com.fone.common.CommonUserCallApi
import com.fone.common.CustomDescribeSpec
import com.fone.common.IntegrationTest
import com.fone.common.doPost
import com.fone.common.entity.Gender
import com.fone.profile.domain.enum.SNS
import com.fone.profile.presentation.dto.ValidateProfileDto
import com.fone.profile.presentation.dto.common.ProfileSnsUrl
import org.springframework.test.web.reactive.server.WebTestClient
import java.time.LocalDate

@IntegrationTest
class ValidateProfileControllerTest(client: WebTestClient) : CustomDescribeSpec() {
init {
val url = "/api/v1/profiles/validate"
val (accessToken, _) = CommonUserCallApi.getAccessToken(client)
describe("#Profile 검증 API") {
context("basic 페이지") {
it("성공한다") {
val request =
ValidateProfileDto.BasicPageValidation(
"제목",
"후킹멘트",
listOf("https://s3-ap-northeast-2.amazonaws.com/f-one-image/prod/user-profile/image.jpg")
)
client.doPost("$url/basic", request, accessToken)
.expectStatus().isOk.expectBody()
.consumeWith { println(it) }.jsonPath("$.result").isEqualTo("SUCCESS")
}
it("실패한다") {
val request =
ValidateProfileDto.BasicPageValidation(
"제목",
"후킹멘트",
listOf("")
)
client.doPost("$url/basic", request, accessToken).expectStatus().isBadRequest.expectBody()
.consumeWith { println(it) }.jsonPath("$.result").isEqualTo("FAIL")
}
}
context("details 페이지") {
it("성공한다") {
val request =
ValidateProfileDto.DetailPageValidation(
LocalDate.now(),
Gender.MAN,
170,
300,
"mail@mail.com",
listOf(ProfileSnsUrl("123", SNS.YOUTUBE)),
"고추장 잘 먹음",
null
)
client.doPost("$url/details", request, accessToken)
.expectStatus().isOk.expectBody()
.consumeWith { println(it) }.jsonPath("$.result").isEqualTo("SUCCESS")
}
it("실패한다") {
val request =
ValidateProfileDto.DetailPageValidation(
LocalDate.now(),
Gender.MAN,
null,
300,
"mail@mail.com",
listOf(ProfileSnsUrl("123", SNS.YOUTUBE)),
"고추장 잘 먹음",
null
)
client.doPost("$url/details", request, accessToken).expectStatus().isBadRequest.expectBody()
.consumeWith { println(it) }.jsonPath("$.result").isEqualTo("FAIL")
}
}
}
}
}

0 comments on commit 8a47c6f

Please sign in to comment.