-
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 3 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,6 +35,8 @@ 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.module:jackson-module-kotlin") | ||
implementation("org.jetbrains.kotlin:kotlin-reflect") | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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 { | ||
|
||
@Bean | ||
fun objectMapper(): ObjectMapper { | ||
return ObjectMapper().registerModule( | ||
KotlinModule.Builder() | ||
.withReflectionCacheSize(512) | ||
.configure(KotlinFeature.NullToEmptyCollection, false) | ||
.configure(KotlinFeature.NullToEmptyMap, false) | ||
.configure(KotlinFeature.NullIsSameAsDefault, false) | ||
.configure(KotlinFeature.SingletonSupport, DISABLED) | ||
.configure(KotlinFeature.StrictNullChecks, false) | ||
.build() | ||
) | ||
} | ||
} |
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,20 @@ | ||
package com.nexters.bottles.profile.controller | ||
|
||
import com.nexters.bottles.profile.controller.dto.RegisterProfileRequestDto | ||
import com.nexters.bottles.profile.facade.ProfileFacade | ||
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") | ||
class ProfileController( | ||
private val profileFacade: ProfileFacade, | ||
) { | ||
|
||
@PostMapping("/profile/choice") | ||
fun registerProfile(@RequestBody registerProfileRequestDto: RegisterProfileRequestDto) { | ||
profileFacade.saveProfile(registerProfileRequestDto) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.nexters.bottles.profile.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,6 @@ | ||
package com.nexters.bottles.profile.controller.dto | ||
|
||
data class RegionDto( | ||
val city: String, | ||
val state: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.nexters.bottles.profile.controller.dto | ||
|
||
data class RegisterProfileRequestDto( | ||
val mbti: String, | ||
val keyword: List<String>, | ||
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,15 @@ | ||
package com.nexters.bottles.profile.domain | ||
|
||
import com.nexters.bottles.profile.controller.dto.InterestDto | ||
import com.nexters.bottles.profile.controller.dto.RegionDto | ||
|
||
data class ProfileSelect( | ||
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,29 @@ | ||
package com.nexters.bottles.profile.domain | ||
|
||
import com.nexters.bottles.profile.repository.converter.ProfileSelectConverter | ||
import org.hibernate.annotations.CreationTimestamp | ||
import org.hibernate.annotations.UpdateTimestamp | ||
import java.time.LocalDateTime | ||
import javax.persistence.* | ||
|
||
@Entity | ||
class UserProfile( | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val id: Long? = null, | ||
|
||
@Column(name = "user_id") | ||
var userId: Long, | ||
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. 요거 생각이 안나서 이렇게 했는데 낼 수정해놓을게요! |
||
|
||
@Column(name = "profile_select") | ||
@Convert(converter = ProfileSelectConverter::class) | ||
var profileSelect: ProfileSelect, | ||
|
||
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. 이 저장되는 데이터를 별도로 검색하는 요구 조건이 없고, 통째로 저장하고 통째로 꺼내쓸 것 같아서 json으로 db에 저장하게 했어요 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. mysql 5.8이후 부터인가 json 안에 특정 값의 index도 가능하고, 값에 대해 쿼리도 가능하더라고요! 추후 마이그레이션도 좋습니다! |
||
@Column(name = "created_at", updatable = false) | ||
@CreationTimestamp | ||
val createdAt: LocalDateTime = LocalDateTime.now(), | ||
|
||
@Column(name = "updated_at") | ||
@UpdateTimestamp | ||
var updatedAt: LocalDateTime = LocalDateTime.now() | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.nexters.bottles.profile.facade | ||
|
||
import com.nexters.bottles.profile.controller.dto.RegisterProfileRequestDto | ||
import com.nexters.bottles.profile.domain.ProfileSelect | ||
import com.nexters.bottles.profile.domain.UserProfile | ||
import com.nexters.bottles.profile.service.ProfileService | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class ProfileFacade( | ||
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. 파사드 클래스 써보셨나요? 이렇게 레이어가 생기게 했어요. 이유는 서비스 레이어 (service, component)간에 참조가 생기지 않게하기 위해서 많이 쓰는데 어떻게 쓰시나요? 단순 mvc를 할때는 사실 불필요할 수 있어서 의견 내주세요! 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. 레이어드 아키텍처 때문인데요, 예를들어 컨트롤러가 컨트롤러를 참조하면 이상하듯 서비스가 서비스를 참조해도 괜찮냐? 하시는 분들이 있더라고요. 서비스가 서비스를 호출하면 잠재적으로 재귀호출이 문제가 될 수 있어서요. 또 추후 문제이기도 하지만 서비스 레이어만 있으면 aop를 적용할때도 self invocation 문제가 생겨 결국 서비스가 서비스 호출하는 경우도 생기고요! dto를 facade에 넣는건 아주 좋네요! 다만 지금 12 <- 15 <- 17을 바라보고 있어서, 그럼 17번에서 위치이동 빼먹지 않고 시킬게요! 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 profileService: ProfileService, | ||
) { | ||
|
||
fun saveProfile(profileDto: RegisterProfileRequestDto) { | ||
validateProfile(profileDto) | ||
|
||
profileService.saveProfile( | ||
UserProfile( | ||
userId = 1L, | ||
profileSelect = ProfileSelect( | ||
mbti = profileDto.mbti, | ||
keyword = profileDto.keyword, | ||
interest = profileDto.interest, | ||
job = profileDto.job, | ||
smoking = profileDto.smoking, | ||
alcohol = profileDto.alcohol, | ||
religion = profileDto.religion, | ||
region = profileDto.region, | ||
) | ||
) | ||
) | ||
} | ||
|
||
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개 이하여야 해요" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.nexters.bottles.profile.repository | ||
|
||
import com.nexters.bottles.profile.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,39 @@ | ||
package com.nexters.bottles.profile.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.profile.domain.ProfileSelect | ||
import javax.persistence.AttributeConverter | ||
import javax.persistence.Converter | ||
|
||
@Converter(autoApply = true) | ||
class ProfileSelectConverter : AttributeConverter<ProfileSelect, String> { | ||
|
||
private val objectMapper = ObjectMapper().registerModule( | ||
KotlinModule.Builder() | ||
.withReflectionCacheSize(512) | ||
.configure(KotlinFeature.NullToEmptyCollection, false) | ||
.configure(KotlinFeature.NullToEmptyMap, false) | ||
.configure(KotlinFeature.NullIsSameAsDefault, false) | ||
.configure(KotlinFeature.SingletonSupport, DISABLED) | ||
.configure(KotlinFeature.StrictNullChecks, false) | ||
.build() | ||
) | ||
|
||
override fun convertToDatabaseColumn(attribute: ProfileSelect?): String { | ||
return try { | ||
objectMapper.writeValueAsString(attribute) | ||
} catch (e: Exception) { | ||
throw RuntimeException("Error converting JSON to String", e) | ||
} | ||
} | ||
|
||
override fun convertToEntityAttribute(dbData: String?): ProfileSelect? { | ||
return try { | ||
dbData?.let { objectMapper.readValue(it, ProfileSelect::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,17 @@ | ||
package com.nexters.bottles.profile.service | ||
|
||
import com.nexters.bottles.profile.domain.UserProfile | ||
import com.nexters.bottles.profile.repository.UserProfileRepository | ||
import org.springframework.stereotype.Service | ||
import javax.transaction.Transactional | ||
|
||
@Service | ||
class ProfileService( | ||
private val profileRepository: UserProfileRepository | ||
) { | ||
|
||
@Transactional | ||
fun saveProfile(userProfile: UserProfile): UserProfile { | ||
return profileRepository.save(userProfile) | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
server: | ||
port: 8080 | ||
|
||
spring: | ||
profiles: | ||
active: local | ||
jpa: | ||
properties: | ||
hibernate: | ||
jdbc: | ||
time_zone: UTC | ||
mvc: | ||
pathmatch: | ||
matching-strategy: ant_path_matcher | ||
|
||
--- | ||
spring: | ||
profiles: local | ||
datasource: | ||
url: jdbc:mysql://localhost:3306/bottles?useSSL=false | ||
username: root | ||
password: root | ||
|
||
logging: | ||
level: | ||
org: | ||
springframework: INFO |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CREATE TABLE user_profile ( | ||
id BIGINT AUTO_INCREMENT PRIMARY KEY, | ||
user_id BIGINT NOT NULL, | ||
profile_select JSON, | ||
Comment on lines
+11
to
+14
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. ddl 쿼리들을 원래 어떻게 관리하셨나요? 일단 여기에 저장해뒀습니다! 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. 그냥 따로 기록해놨었어요! 여기에 저장해두는거 좋아요! |
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, | ||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL | ||
); |
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으로 상속이 가능하게 바꿔주는 설정입니다