-
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
✨ 프로필 위젯 추가 API #33
✨ 프로필 위젯 추가 API #33
Conversation
Walkthrough이 변경 사항은 Changes
Possibly related PRs
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (14)
domain/src/main/kotlin/com/threedays/domain/user/entity/ProfileWidget.kt (2)
Line range hint
1-21
: 문서화 개선이 필요합니다.클래스와 enum에 대한 KDoc 문서화를 추가하여 각 위젯 타입의 용도와 content 필드에 들어갈 수 있는 값의 형식을 명확히 설명하면 좋을 것 같습니다.
예시:
/** * 사용자 프로필에 표시되는 위젯을 나타내는 데이터 클래스 * * @property type 위젯의 종류 * @property content 위젯에 표시될 내용 */ data class ProfileWidget( val type: Type, val content: String, ) { /** * 프로필 위젯의 가능한 종류들을 정의 */ enum class Type { /** 취미 정보 */ HOBBY, // ... 다른 타입들에 대한 설명 } }
5-5
: content 필드에 대한 유효성 검증이 필요합니다.content 필드의 길이 제한이나 필수 값 여부 등의 제약 조건을 추가하는 것이 좋을 것 같습니다.
예시:
data class ProfileWidget( val type: Type, val content: String, ) { init { require(content.isNotBlank()) { "content는 비어있을 수 없습니다" } require(content.length <= MAX_CONTENT_LENGTH) { "content는 ${MAX_CONTENT_LENGTH}자를 초과할 수 없습니다" } } companion object { const val MAX_CONTENT_LENGTH = 100 } }application/src/main/kotlin/com/threedays/application/user/port/inbound/PutProfileWidget.kt (3)
6-7
: 인터페이스에 KDoc 문서화를 추가해주세요.이 인터페이스의 목적과 책임을 명확히 설명하는 문서화가 필요합니다.
다음과 같은 문서화를 제안드립니다:
+/** + * 사용자의 프로필 위젯을 추가하거나 업데이트하는 인바운드 포트 + * + * @see ProfileWidget + * @see User + */ interface PutProfileWidget {
8-8
: 메서드 문서화와 예외 처리 명세가 필요합니다.메서드의 동작, 파라미터, 발생 가능한 예외 상황에 대한 명세가 필요합니다.
다음과 같은 개선을 제안드립니다:
+ /** + * 사용자의 프로필 위젯을 추가하거나 업데이트합니다. + * + * @param command 프로필 위젯 업데이트 명령 + * @throws UserNotFoundException 사용자를 찾을 수 없는 경우 + * @throws InvalidWidgetDataException 위젯 데이터가 유효하지 않은 경우 + */ fun invoke(command: Command)
10-13
: Command 클래스의 속성 문서화와 유효성 검증이 필요합니다.데이터 클래스의 각 속성에 대한 설명과 제약 조건을 명시하면 좋겠습니다.
다음과 같은 개선을 제안드립니다:
data class Command( + /** + * 프로필 위젯을 업데이트할 사용자의 ID + */ val userId: User.Id, + /** + * 업데이트할 프로필 위젯 정보 + */ val profileWidget: ProfileWidget - ) + ) { + init { + require(profileWidget.isValid()) { "프로필 위젯 데이터가 유효하지 않습니다" } + } + }domain/src/main/kotlin/com/threedays/domain/user/repository/UserRepository.kt (1)
10-11
: KDoc 문서화 추가를 제안드립니다.메서드의 목적과 발생 가능한 예외에 대한 문서화를 추가하면 좋을 것 같습니다.
다음과 같은 문서화를 추가해보세요:
+ /** + * 주어진 ID로 사용자를 조회합니다. + * + * @param id 조회할 사용자의 ID + * @return 조회된 사용자 객체 + * @throws NotFoundException 해당 ID의 사용자가 존재하지 않는 경우 + */ fun get(id: User.Id): User = find(id)infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/ProfileWidgetJpaEmbeddable.kt (2)
8-13
: 클래스 문서화가 필요합니다.데이터 클래스의 구조는 잘 되어있지만, 다음 사항들에 대한 KDoc 문서화를 추가하면 좋을 것 같습니다:
- 클래스의 목적과 사용 사례
- 각 프로퍼티의 의미와 제약 조건
@Embeddable
을 사용한 이유예시:
/** * 사용자 프로필 위젯의 영속성을 위한 임베디드 클래스입니다. * * @property type 위젯의 종류 * @property content 위젯의 내용 */ @Embeddable data class ProfileWidgetJpaEmbeddable(
15-22
: null 안전성 개선이 필요합니다.도메인 객체를 JPA 엔티티로 변환하는 과정에서 null 안전성을 고려해야 합니다.
다음과 같이 개선을 제안합니다:
companion object { - fun ProfileWidget.toJpaEmbeddable() = ProfileWidgetJpaEmbeddable( + fun ProfileWidget?.toJpaEmbeddable(): ProfileWidgetJpaEmbeddable? = this?.let { + ProfileWidgetJpaEmbeddable( type = type, content = content, - ) + ) + } }domain/src/main/kotlin/com/threedays/domain/user/entity/UserProfile.kt (1)
18-27
: 구현이 깔끔하고 적절합니다!불변성을 유지하면서 위젯을 추가하거나 업데이트하는 로직이 잘 구현되어 있습니다.
다만, 다음 사항들을 고려해보시면 좋을 것 같습니다:
- KDoc을 추가하여 메서드의 동작을 문서화
- profileWidget 파라미터에 대한 유효성 검사 추가
- 프로필 위젯 최대 개수 제한 고려
다음과 같이 개선해 보는 것은 어떨까요?
+ /** + * 프로필 위젯을 추가하거나 업데이트합니다. + * 동일한 타입의 위젯이 이미 존재하는 경우 새로운 위젯으로 교체됩니다. + * + * @param profileWidget 추가하거나 업데이트할 프로필 위젯 + * @return 업데이트된 UserProfile 인스턴스 + * @throws IllegalArgumentException 위젯이 null이거나 최대 개수를 초과하는 경우 + */ fun putProfileWidget(profileWidget: ProfileWidget): UserProfile { + require(profileWidget.isValid()) { "프로필 위젯이 유효하지 않습니다" } + require(profileWidgets.size < MAX_WIDGETS || profileWidgets.any { it.type == profileWidget.type }) { + "프로필 위젯은 최대 ${MAX_WIDGETS}개까지만 추가할 수 있습니다" + } + val existingProfileWidget: ProfileWidget? = profileWidgets.find { it.type == profileWidget.type } return if (existingProfileWidget == null) { copy(profileWidgets = profileWidgets + profileWidget) } else { copy(profileWidgets = profileWidgets.map { if (it.type == profileWidget.type) profileWidget else it }) } } + + companion object { + const val MAX_WIDGETS = 5 + }infrastructure/persistence/src/main/resources/db/migration/V1__init_user.sql (1)
63-70
: 기본 구조는 적절하나 몇 가지 개선사항이 있습니다.테이블 구조가 전반적으로 잘 설계되어 있습니다. 복합 기본키와 외래키 제약조건이 적절히 구현되어 있습니다.
다음과 같은 개선사항을 고려해보시기 바랍니다:
CREATE TABLE user_profile_widgets ( user_profile_id BINARY(16) NOT NULL, type VARCHAR(255) NOT NULL, content VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (user_profile_id, type), + INDEX idx_user_profile_widgets_type (type), FOREIGN KEY (user_profile_id) REFERENCES user_profiles (id) ON DELETE CASCADE );
- 위젯 타입별 조회를 위한 인덱스 추가
- 생성/수정 시간 컬럼 추가로 감사 추적 지원
- 필요한 경우 content 컬럼의 크기를 TEXT로 변경하는 것을 고려해보세요 (위젯 콘텐츠의 예상 크기에 따라)
domain/src/main/kotlin/com/threedays/domain/user/entity/User.kt (1)
89-91
: 구현이 깔끔하나 유효성 검증이 필요할 수 있습니다.메서드 구현이 불변성을 잘 유지하고 있으며 책임을 적절히 위임하고 있습니다. 하지만 widget 파라미터에 대한 유효성 검증을 고려해보시면 좋을 것 같습니다.
다음과 같은 방식으로 검증 로직을 추가하는 것을 고려해보세요:
fun putProfileWidget(widget: ProfileWidget): User { + require(widget.userId == id) { "위젯의 사용자 ID가 일치하지 않습니다." } return copy(profile = profile.putProfileWidget(widget)) }
infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/UserProfileJpaEntity.kt (1)
68-74
: JPA 어노테이션 설정을 검토해주세요.현재 설정된 FetchType.EAGER는 성능에 영향을 미칠 수 있습니다:
- 프로필을 조회할 때마다 위젯 데이터도 함께 로드됩니다
- 위젯 데이터가 많아질 경우 성능 저하가 발생할 수 있습니다
다음과 같은 개선을 고려해보세요:
- @ElementCollection(fetch = FetchType.EAGER) + @ElementCollection(fetch = FetchType.LAZY)bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/user/UserController.kt (1)
109-114
: enum 매핑 방식 개선 제안현재 구현된 enum 매핑 방식은 도메인 모델과 API 모델 간의 결합도가 높습니다. 향후 유지보수성을 높이기 위해 다음과 같은 개선을 고려해보세요.
- type = com.threedays.oas.model.ProfileWidgetType.valueOf(it.type.name), + type = it.type.toApiModel(), // Extension 함수로 분리도메인 모델에 다음과 같은 확장 함수를 추가하는 것을 추천드립니다:
fun ProfileWidget.Type.toApiModel(): com.threedays.oas.model.ProfileWidgetType { return when (this) { Type.SOME_TYPE -> ProfileWidgetType.SOME_TYPE // ... 다른 케이스들 } }domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt (1)
168-184
: 테스트 케이스 보완이 필요합니다.기본 기능에 대한 테스트는 잘 작성되어 있으나, 다음과 같은 엣지 케이스들도 검증이 필요해 보입니다:
- 최대 허용 위젯 개수를 초과하는 경우
- 유효하지 않은 위젯 데이터가 입력된 경우
- 동일한 위젯을 중복 추가하는 경우
추가 테스트 케이스 작성을 도와드릴까요?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (12)
application/src/main/kotlin/com/threedays/application/user/port/inbound/PutProfileWidget.kt
(1 hunks)application/src/main/kotlin/com/threedays/application/user/service/UserService.kt
(3 hunks)bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/user/UserController.kt
(4 hunks)domain/src/main/kotlin/com/threedays/domain/user/entity/ProfileWidget.kt
(1 hunks)domain/src/main/kotlin/com/threedays/domain/user/entity/User.kt
(1 hunks)domain/src/main/kotlin/com/threedays/domain/user/entity/UserProfile.kt
(1 hunks)domain/src/main/kotlin/com/threedays/domain/user/repository/UserRepository.kt
(1 hunks)domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt
(2 hunks)infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/ProfileWidgetJpaEmbeddable.kt
(1 hunks)infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/UserProfileJpaEntity.kt
(5 hunks)infrastructure/persistence/src/main/resources/db/migration/V1__init_user.sql
(1 hunks)openapi
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- openapi
🔇 Additional comments (14)
domain/src/main/kotlin/com/threedays/domain/user/entity/ProfileWidget.kt (1)
4-6
: 속성 이름 변경이 적절해 보입니다.
'comment'에서 'content'로의 변경은 위젯의 내용을 더 잘 표현하는 이름입니다.
application/src/main/kotlin/com/threedays/application/user/port/inbound/PutProfileWidget.kt (1)
1-5
: 패키지 구조와 임포트가 적절합니다!
헥사고날 아키텍처의 포트 계층에 맞게 잘 구성되어 있으며, 필요한 도메인 엔티티만 임포트하고 있습니다.
domain/src/main/kotlin/com/threedays/domain/user/repository/UserRepository.kt (1)
10-11
: 구현이 깔끔하고 적절합니다!
기존의 getByPhoneNumber
메서드와 일관된 방식으로 구현되었으며, 명확한 예외 처리가 포함되어 있습니다.
infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/ProfileWidgetJpaEmbeddable.kt (2)
1-7
: 패키지 구조와 임포트가 적절합니다!
영속성 계층에 맞는 패키지 구조와 필요한 임포트들이 잘 정의되어 있습니다.
24-28
: 양방향 변환 패턴 검증이 필요합니다.
도메인 객체로의 변환도 null 안전성이 필요하며, 양방향 변환 패턴이 일관되게 사용되는지 확인이 필요합니다.
다음과 같은 개선을 제안합니다:
- fun toValueObject() = ProfileWidget(
+ fun toValueObject(): ProfileWidget = ProfileWidget(
type = type,
content = content,
)
✅ Verification successful
Let me gather more information about the conversion patterns and null safety.
Let me check the ProfileWidget domain class to understand its structure better.
양방향 변환 패턴이 적절하게 구현되어 있습니다.
검증 결과:
- 도메인 객체(
ProfileWidget
)와 JPA 엔티티(ProfileWidgetJpaEmbeddable
) 모두 non-null 프로퍼티만 사용 - 양방향 변환 메서드가 일관되게 구현되어 있음
- 타입 안전성이 보장됨
리턴 타입 명시는 선택적이며, 현재 구현에서는 불필요합니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 양방향 변환 패턴의 일관성 검증
# 다른 JPA 엔티티에서 유사한 변환 패턴 검색
rg -t kotlin "fun.*toValueObject|fun.*toJpaEmbeddable" --glob "infrastructure/persistence/**/*.kt"
Length of output: 437
Script:
#!/bin/bash
# Check the implementation details of both conversion methods
rg -A 5 -B 5 "fun.*toValueObject|fun.*toJpaEmbeddable" --glob "infrastructure/persistence/**/*.kt"
# Check if there are any nullable properties in the ProfileWidget class
ast-grep --pattern 'class ProfileWidget($$$)'
Length of output: 2446
Script:
#!/bin/bash
# Find and check the ProfileWidget domain class implementation
rg -A 10 "class ProfileWidget" --glob "**/domain/**/*.kt"
# Check if there are any nullable properties in ProfileWidgetJpaEmbeddable
rg -A 5 "class ProfileWidgetJpaEmbeddable" --glob "infrastructure/persistence/**/*.kt"
Length of output: 1936
application/src/main/kotlin/com/threedays/application/user/service/UserService.kt (3)
5-5
: LGTM - 필요한 import 추가됨
PutProfileWidget
인터페이스 구현을 위한 import가 적절히 추가되었습니다.
23-23
: LGTM - 인터페이스 구현이 명확히 선언됨
PutProfileWidget
인터페이스가 기존 RegisterUser
인터페이스와 함께 적절히 구현 선언되었습니다.
57-63
: 🛠️ Refactor suggestion
트랜잭션 범위와 에러 처리 개선 필요
현재 구현에서 몇 가지 개선이 필요한 부분이 있습니다:
- 트랜잭션 전파 속성이 명시되어 있지 않습니다.
- 사용자를 찾지 못했을 때의 예외 처리가 명시적이지 않습니다.
- 프로필 위젯 업데이트 실패에 대한 예외 처리가 없습니다.
다음과 같이 개선을 제안드립니다:
- @Transactional
+ @Transactional(rollbackFor = [Exception::class])
override fun invoke(command: PutProfileWidget.Command) {
- userRepository
- .get(command.userId)
- .putProfileWidget(command.profileWidget)
- .also { userRepository.save(it) }
+ try {
+ val user = userRepository.get(command.userId)
+ ?: throw UserNotFoundException("사용자를 찾을 수 없습니다: ${command.userId}")
+
+ user.putProfileWidget(command.profileWidget)
+ .also { userRepository.save(it) }
+ } catch (e: Exception) {
+ throw ProfileWidgetUpdateException("프로필 위젯 업데이트 실패", e)
+ }
}
프로필 위젯 업데이트 관련 예외 클래스들이 정의되어 있는지 확인이 필요합니다:
domain/src/main/kotlin/com/threedays/domain/user/entity/User.kt (1)
89-91
: UserProfile 클래스와의 통합 검증이 필요합니다.
profile.putProfileWidget()
메서드가 UserProfile 클래스에 올바르게 구현되어 있는지 확인이 필요합니다.
✅ Verification successful
UserProfile 클래스의 putProfileWidget 메서드가 올바르게 구현되어 있음을 확인했습니다.
UserProfile 클래스에서 putProfileWidget 메서드는 다음과 같이 적절하게 구현되어 있습니다:
- 동일한 타입의 위젯이 있는 경우 업데이트
- 새로운 타입의 위젯인 경우 목록에 추가
- 불변성을 유지하며 새로운 UserProfile 인스턴스를 반환
따라서 User 클래스의 putProfileWidget 메서드에서 이를 호출하는 것이 안전합니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# UserProfile 클래스에서 putProfileWidget 메서드 구현 확인
ast-grep --pattern 'class UserProfile {
$$$
fun putProfileWidget($_) {
$$$
}
$$$
}'
Length of output: 97
Script:
#!/bin/bash
# UserProfile 관련 파일 찾기
fd "UserProfile.kt"
# UserProfile 클래스 내용 확인
rg -A 5 "class UserProfile"
# putProfileWidget 메서드 검색
rg "putProfileWidget"
Length of output: 2979
Script:
#!/bin/bash
# UserProfile 클래스의 putProfileWidget 메서드 구현 확인
cat domain/src/main/kotlin/com/threedays/domain/user/entity/UserProfile.kt
Length of output: 1066
infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/UserProfileJpaEntity.kt (3)
9-9
: 새로운 임포트 구문이 적절히 추가되었습니다.
ProfileWidget 관련 기능을 위한 필수 임포트들이 잘 추가되었습니다.
Also applies to: 12-12
34-34
: 프로필 위젯 속성이 적절히 추가되었습니다.
생성자에 profileWidgets 매개변수가 잘 추가되었습니다.
85-85
: 도메인 변환 메서드가 적절히 수정되었습니다.
toJpaEntity()와 toDomainEntity() 메서드에 profileWidgets 변환 로직이 올바르게 추가되었습니다.
하지만 다음 사항을 확인해주세요:
Also applies to: 97-97
✅ Verification successful
ProfileWidget 변환 메서드가 모두 적절히 구현되어 있음을 확인했습니다.
ProfileWidgetJpaEmbeddable에 도메인 객체와 JPA 엔티티 간의 변환을 위한 toJpaEmbeddable()과 toValueObject() 메서드가 올바르게 구현되어 있습니다. 변환 로직이 완전하게 구현되어 있어 추가 검증이 필요하지 않습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# ProfileWidget 관련 변환 메서드들이 구현되어 있는지 확인
rg -l "toValueObject|toJpaEmbeddable" --type kotlin
Length of output: 269
Script:
#!/bin/bash
# ProfileWidgetJpaEmbeddable.kt 파일의 변환 메서드 구현 확인
rg "toValueObject|toJpaEmbeddable" -A 5 -B 5 infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/entity/ProfileWidgetJpaEmbeddable.kt
Length of output: 457
bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/user/UserController.kt (1)
3-5
: 의존성 주입이 올바르게 구현되었습니다!
Spring의 생성자 주입 패턴을 잘 따르고 있으며, 필요한 의존성들이 명확하게 정의되어 있습니다.
Also applies to: 30-30
domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt (1)
6-6
: LGTM!
FixtureMonkey의 set
함수를 import하여 테스트 데이터 설정에 적절히 활용하고 있습니다.
bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/user/UserController.kt
Show resolved
Hide resolved
domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt
Outdated
Show resolved
Hide resolved
Quality Gate passedIssues Measures |
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.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (1)
domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt (1)
192-225
: 테스트 케이스 보완이 필요합니다.현재 구현에 다음과 같은 테스트 케이스를 추가하면 좋을 것 같습니다:
실패 케이스 테스트:
- 유효하지 않은 위젯 데이터 처리
- null 값 처리
검증 로직 강화:
- 이전 위젯이 실제로 제거되었는지 확인
- 위젯 개수가 정확한지 검증
- 다른 타입의 위젯들은 영향받지 않았는지 확인
예시 구현:
context("위젯 데이터가 유효하지 않은 경우") { it("예외가 발생한다") { // arrange val invalidWidget: ProfileWidget = fixtureMonkey .giveMeBuilder<ProfileWidget>() .set(ProfileWidget::content, "") // 빈 컨텐츠 .sample() // act & assert shouldThrow<IllegalArgumentException> { user.putProfileWidget(invalidWidget) } } } context("동일한 타입의 위젯을 업데이트하는 경우") { it("이전 위젯은 제거되고 새 위젯만 존재한다") { // arrange val oldWidget = // ... 기존 코드와 동일 val newWidget = // ... 기존 코드와 동일 // act val result = user.putProfileWidget(newWidget) // assert result.profile.profileWidgets.count { it.type == widgetType } shouldBe 1 result.profile.profileWidgets.find { it == oldWidget } shouldBe null result.profile.profileWidgets.find { it == newWidget } shouldBe newWidget } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt
(2 hunks)
🔇 Additional comments (2)
domain/src/test/kotlin/com/threedays/domain/user/entity/UserTest.kt (2)
6-6
: LGTM!
FixtureMonkey의 set
함수 import가 새로운 테스트 케이스에 적절하게 추가되었습니다.
167-190
: 테스트 구현이 잘 되었습니다!
다음과 같은 장점들이 있습니다:
- Arrange/Act/Assert 패턴을 명확하게 따르고 있습니다
- FixtureMonkey를 활용한 테스트 데이터 생성이 잘 구현되었습니다
- 테스트 의도가 명확합니다
Summary by CodeRabbit
새로운 기능
버그 수정
테스트
데이터베이스 변경
user_profile_widgets
생성.