-
Notifications
You must be signed in to change notification settings - Fork 0
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
[feature/12] feat: 온보딩 선택을 등록하는 API 구현 #14
Changes from all commits
710faf4
b44a3d1
5931f8f
d580aaf
0aecbcf
de4651c
4159be5
55eddd5
8537fc8
e0994ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# | ||
|
||
# 문서 | ||
swagger: http://localhost:8080/swagger-ui | ||
|
||
# 설정 | ||
vm options: `-Duser.timezone=Asia/Seoul` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,18 @@ plugins { | |
kotlin("plugin.jpa") version "1.9.24" | ||
kotlin("jvm") version "1.9.24" | ||
kotlin("plugin.spring") version "1.9.24" | ||
kotlin("plugin.allopen") version "1.5.31" | ||
kotlin("plugin.noarg") version "1.5.31" | ||
} | ||
|
||
allOpen { | ||
annotation("javax.persistence.Entity") | ||
annotation("javax.persistence.Convert") | ||
} | ||
|
||
noArg { | ||
annotation("javax.persistence.Entity") | ||
annotation("javax.persistence.Convert") | ||
} | ||
|
||
group = "com.nexters" | ||
|
@@ -23,13 +35,21 @@ dependencies { | |
implementation("org.springframework.boot:spring-boot-starter-data-jdbc") | ||
implementation("org.springframework.boot:spring-boot-starter-data-jpa") | ||
implementation("org.springframework.boot:spring-boot-starter-web") | ||
|
||
implementation("io.springfox:springfox-boot-starter:3.0.0") | ||
implementation("io.springfox:springfox-swagger-ui:3.0.0") | ||
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. swagger 관련 설정입니다! |
||
|
||
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0") | ||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin") | ||
|
||
implementation("io.github.microutils:kotlin-logging:3.0.5") | ||
implementation("org.slf4j:slf4j-simple:2.0.7") | ||
Comment on lines
+45
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 코틀린 로그 설정이에요. 로그 남기고 싶은 클래스에서 private val log = KotlinLogging.logger { }
log.info { "로그입니다" } 이렇게 쓰시면 돼요 |
||
|
||
implementation("org.jetbrains.kotlin:kotlin-reflect") | ||
|
||
runtimeOnly("com.h2database:h2") | ||
runtimeOnly("mysql:mysql-connector-java") | ||
|
||
|
||
testImplementation("org.springframework.boot:spring-boot-starter-test") | ||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") | ||
testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.nexters.bottles.config | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature | ||
import com.fasterxml.jackson.module.kotlin.KotlinModule | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
|
||
@Configuration | ||
class JacksonConfig { | ||
|
||
companion object { | ||
val kotlinModule = KotlinModule.Builder() | ||
.withReflectionCacheSize(512) | ||
.configure(KotlinFeature.NullToEmptyCollection, false) | ||
.configure(KotlinFeature.NullToEmptyMap, false) | ||
.configure(KotlinFeature.NullIsSameAsDefault, false) | ||
.configure(KotlinFeature.StrictNullChecks, false) | ||
.build() | ||
} | ||
|
||
@Bean | ||
fun objectMapper(): ObjectMapper { | ||
return ObjectMapper().registerModule( | ||
kotlinModule | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.nexters.bottles.config | ||
|
||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
import springfox.documentation.builders.PathSelectors | ||
import springfox.documentation.builders.RequestHandlerSelectors | ||
import springfox.documentation.spi.DocumentationType | ||
import springfox.documentation.spring.web.plugins.Docket | ||
|
||
@Configuration | ||
class SwaggerConfig { | ||
|
||
@Bean | ||
fun api(): Docket { | ||
return Docket(DocumentationType.SWAGGER_2) | ||
.select() | ||
.apis(RequestHandlerSelectors.basePackage("com.nexters.bottles")) | ||
.paths(PathSelectors.any()) | ||
.build() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.nexters.bottles.user.controller | ||
|
||
import com.nexters.bottles.user.controller.dto.ProfileChoiceResponseDto | ||
import com.nexters.bottles.user.controller.dto.RegisterProfileRequestDto | ||
import com.nexters.bottles.user.facade.UserProfileFacade | ||
import org.springframework.web.bind.annotation.GetMapping | ||
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 | ||
|
||
@RestController | ||
@RequestMapping("/api/v1") | ||
class UserProfileController( | ||
private val profileFacade: UserProfileFacade, | ||
) { | ||
|
||
@PostMapping("/profile/choice") | ||
fun registerProfile(@RequestBody registerProfileRequestDto: RegisterProfileRequestDto) { | ||
profileFacade.saveProfile(registerProfileRequestDto) | ||
} | ||
|
||
@GetMapping("/profile/choice") | ||
fun getProfileChoiceList() : ProfileChoiceResponseDto { | ||
return profileFacade.getProfileChoice() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.nexters.bottles.user.controller.dto | ||
|
||
data class InterestDto( | ||
val culture: List<String> = arrayListOf(), | ||
val sports: List<String> = arrayListOf(), | ||
val entertainment: List<String> = arrayListOf(), | ||
val etc: List<String> = arrayListOf(), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.nexters.bottles.user.controller.dto | ||
|
||
data class ProfileChoiceResponseDto( | ||
val regions: List<Map<String, Any>> | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.nexters.bottles.user.controller.dto | ||
|
||
data class RegionDto( | ||
val city: String, | ||
val state: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.nexters.bottles.user.controller.dto | ||
|
||
data class RegisterProfileRequestDto( | ||
val mbti: String, | ||
val keyword: List<String>, | ||
val interest: InterestDto, | ||
val job: String, | ||
var smoking: String, | ||
var alcohol: String, | ||
val religion: String, | ||
val region: RegionDto | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.nexters.bottles.user.domain | ||
|
||
import org.hibernate.annotations.CreationTimestamp | ||
import org.hibernate.annotations.UpdateTimestamp | ||
import java.time.LocalDateTime | ||
import javax.persistence.MappedSuperclass | ||
|
||
@MappedSuperclass | ||
open class BaseEntity( | ||
@CreationTimestamp | ||
open val createdAt: LocalDateTime = LocalDateTime.now(), | ||
|
||
@UpdateTimestamp | ||
open var updatedAt: LocalDateTime = LocalDateTime.now() | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.nexters.bottles.user.domain | ||
|
||
import com.nexters.bottles.user.domain.enum.Gender | ||
import javax.persistence.* | ||
|
||
@Entity | ||
class User( | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val id: Long = 0, | ||
|
||
var name: String? = null, | ||
|
||
var kakaoId: String? = null, | ||
|
||
var phoneNumber: String? = null, | ||
|
||
@OneToOne(mappedBy = "user", cascade = [CascadeType.ALL]) | ||
var userProfile: UserProfile? = null, | ||
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 없는게 좋습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
요거 없앴더니 이렇게 에러가 나요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 음 혹시 어떤부분을 없앤건가요? User에서 24,25 line 삭제했는데 저렇게 나오나요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 제가 이해를 잘 못했나 일단 요것도 뒤에서 챙겨볼게요! |
||
|
||
@Enumerated(EnumType.STRING) | ||
var gender: Gender = Gender.MALE, | ||
|
||
) : BaseEntity() { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.nexters.bottles.user.domain | ||
|
||
import com.nexters.bottles.user.controller.dto.InterestDto | ||
import com.nexters.bottles.user.controller.dto.RegionDto | ||
import com.nexters.bottles.user.repository.converter.UserProfileSelectConverter | ||
import javax.persistence.* | ||
|
||
@Entity | ||
class UserProfile( | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val id: Long? = null, | ||
|
||
@OneToOne | ||
@JoinColumn(name = "user_id") | ||
var user: User? = null, | ||
|
||
@Convert(converter = UserProfileSelectConverter::class) | ||
var profileSelect: UserProfileSelect, | ||
) : BaseEntity() | ||
|
||
data class UserProfileSelect( | ||
val mbti: String, | ||
val keyword: List<String> = arrayListOf(), | ||
val interest: InterestDto, | ||
val job: String, | ||
val smoking: String, | ||
val alcohol: String, | ||
val religion: String, | ||
val region: RegionDto, | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.nexters.bottles.user.domain.enum | ||
|
||
enum class Gender( | ||
val displayName: String, | ||
) { | ||
MALE("남자"), | ||
FEMALE("여자"), | ||
; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.nexters.bottles.user.facade | ||
|
||
import com.nexters.bottles.user.controller.dto.ProfileChoiceResponseDto | ||
import com.nexters.bottles.user.controller.dto.RegisterProfileRequestDto | ||
import com.nexters.bottles.user.domain.UserProfile | ||
import com.nexters.bottles.user.domain.UserProfileSelect | ||
import com.nexters.bottles.user.service.UserProfileService | ||
import org.springframework.stereotype.Component | ||
import regions | ||
|
||
@Component | ||
class UserProfileFacade( | ||
private val profileService: UserProfileService, | ||
) { | ||
|
||
fun saveProfile(profileDto: RegisterProfileRequestDto) { | ||
validateProfile(profileDto) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Facade 클래스에서 하는 일은 dto를 검증하는 일이 되겠군요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞아요 그리고 여러 서비스에서 조회한 것들을 합치고, 로직이 들어갑니다. 또 외부 api를 호출하는 클래스를 호출하기도하고요 |
||
val convertedProfileDto = convertProfileDto(profileDto) | ||
|
||
profileService.saveProfile( | ||
UserProfile( | ||
profileSelect = UserProfileSelect( | ||
mbti = convertedProfileDto.mbti, | ||
keyword = convertedProfileDto.keyword, | ||
interest = convertedProfileDto.interest, | ||
job = convertedProfileDto.job, | ||
smoking = convertedProfileDto.smoking, | ||
alcohol = convertedProfileDto.alcohol, | ||
religion = convertedProfileDto.religion, | ||
region = convertedProfileDto.region, | ||
) | ||
) | ||
) | ||
} | ||
|
||
fun getProfileChoice(): ProfileChoiceResponseDto { | ||
return ProfileChoiceResponseDto( | ||
regions = regions | ||
) | ||
} | ||
|
||
private fun validateProfile(profileDto: RegisterProfileRequestDto) { | ||
require(profileDto.keyword.size <= 5) { | ||
"키워드는 5개 이하여야 해요" | ||
} | ||
val interestCount = profileDto.interest.culture.size + profileDto.interest.sports.size | ||
+profileDto.interest.entertainment.size + profileDto.interest.etc.size | ||
require(interestCount <= 5) { | ||
"취미는 5개 이하여야 해요" | ||
Comment on lines
+46
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
} | ||
|
||
private fun convertProfileDto(profileDto: RegisterProfileRequestDto): RegisterProfileRequestDto { | ||
when(profileDto.smoking) { | ||
"전혀 피우지 않아요" -> profileDto.smoking = "흡연 안해요" | ||
"가끔 피워요" -> profileDto.smoking = "흡연은 가끔" | ||
"자주 피워요" -> profileDto.smoking = "흡연해요" | ||
} | ||
when(profileDto.alcohol) { | ||
"한 방울도 마시지 않아요" -> profileDto.smoking = "술은 안해요" | ||
"때에 따라 적당히 즐겨요" -> profileDto.smoking = "술은 적당히" | ||
"자주 찾는 편이에요" -> profileDto.smoking = "술을 즐겨요" | ||
} | ||
return profileDto | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.nexters.bottles.user.repository | ||
|
||
import com.nexters.bottles.user.domain.UserProfile | ||
import org.springframework.data.jpa.repository.JpaRepository | ||
|
||
interface UserProfileRepository : JpaRepository<UserProfile, Long> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.nexters.bottles.user.repository | ||
|
||
import com.nexters.bottles.user.domain.User | ||
import org.springframework.data.jpa.repository.JpaRepository | ||
|
||
interface UserRepository : JpaRepository<User, Long> { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.nexters.bottles.user.repository.converter | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature | ||
import com.fasterxml.jackson.module.kotlin.KotlinModule | ||
import com.nexters.bottles.config.JacksonConfig.Companion.kotlinModule | ||
import com.nexters.bottles.user.domain.UserProfileSelect | ||
import javax.persistence.AttributeConverter | ||
import javax.persistence.Converter | ||
|
||
@Converter(autoApply = true) | ||
class UserProfileSelectConverter : AttributeConverter<UserProfileSelect, String> { | ||
|
||
private val objectMapper = ObjectMapper().registerModule(kotlinModule) | ||
|
||
override fun convertToDatabaseColumn(attribute: UserProfileSelect?): String { | ||
return try { | ||
objectMapper.writeValueAsString(attribute) | ||
} catch (e: Exception) { | ||
throw RuntimeException("Error converting JSON to String", e) | ||
} | ||
} | ||
|
||
override fun convertToEntityAttribute(dbData: String?): UserProfileSelect? { | ||
return try { | ||
dbData?.let { objectMapper.readValue(it, UserProfileSelect::class.java) } | ||
} catch (e: Exception) { | ||
throw RuntimeException("Error converting String to JSON", e) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.nexters.bottles.user.service | ||
|
||
import com.nexters.bottles.user.domain.UserProfile | ||
import com.nexters.bottles.user.repository.UserProfileRepository | ||
import com.nexters.bottles.user.repository.UserRepository | ||
import mu.KotlinLogging | ||
import org.springframework.data.repository.findByIdOrNull | ||
import org.springframework.stereotype.Service | ||
import javax.transaction.Transactional | ||
|
||
@Service | ||
class UserProfileService( | ||
private val profileRepository: UserProfileRepository, | ||
private val userRepository: UserRepository, | ||
) { | ||
|
||
private val log = KotlinLogging.logger { } | ||
|
||
@Transactional | ||
fun saveProfile(userProfile: UserProfile): UserProfile { | ||
val user = userRepository.findByIdOrNull(1L) // TODO User 회원 가입 기능 구현후 수정 | ||
|
||
userProfile.user = user | ||
return profileRepository.save(userProfile) | ||
} | ||
Comment on lines
+19
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오류가 저 api를 호출할 때 나는건가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네 api 호출때요! 아하 넵넵 그럼 문제가 없는걸로요! |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코틀린은 디폴트가 final인데, jpa는 프록시를 생성해야 하기 때문에 상속이 가능해야하잖아요. 그래서 open으로 상속이 가능하게 바꿔주는 설정입니다