Skip to content

Commit

Permalink
✨ 프로필 위젯 추가 API (#33)
Browse files Browse the repository at this point in the history
* ✨ 프로필 위젯 추가 API

* 🩹 리뷰 반영
  • Loading branch information
waterfogSW authored Nov 11, 2024
1 parent 0f14154 commit 373f965
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.threedays.application.user.port.inbound

import com.threedays.domain.user.entity.ProfileWidget
import com.threedays.domain.user.entity.User

interface PutProfileWidget {

fun invoke(command: Command)

data class Command(
val userId: User.Id,
val profileWidget: ProfileWidget
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.threedays.application.user.service

import com.threedays.application.auth.config.AuthProperties
import com.threedays.application.auth.port.inbound.IssueLoginTokens
import com.threedays.application.user.port.inbound.PutProfileWidget
import com.threedays.application.user.port.inbound.RegisterUser
import com.threedays.domain.user.entity.Company
import com.threedays.domain.user.entity.Location
Expand All @@ -19,7 +20,7 @@ class UserService(
private val companyQueryRepository: CompanyQueryRepository,
private val issueLoginTokens: IssueLoginTokens,
private val authProperties: AuthProperties,
) : RegisterUser {
) : RegisterUser, PutProfileWidget {

@Transactional
override fun invoke(command: RegisterUser.Command): RegisterUser.Result {
Expand Down Expand Up @@ -53,4 +54,12 @@ class UserService(
)
}

@Transactional
override fun invoke(command: PutProfileWidget.Command) {
userRepository
.get(command.userId)
.putProfileWidget(command.profileWidget)
.also { userRepository.save(it) }
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.threedays.bootstrap.api.user

import com.threedays.application.user.port.inbound.PutProfileWidget
import com.threedays.application.user.port.inbound.RegisterUser
import com.threedays.bootstrap.api.support.security.UserAuthentication
import com.threedays.bootstrap.api.support.security.withUserAuthentication
import com.threedays.domain.auth.vo.PhoneNumber
import com.threedays.domain.user.entity.User
Expand All @@ -11,6 +13,7 @@ import com.threedays.domain.user.vo.Gender
import com.threedays.domain.user.vo.JobOccupation
import com.threedays.oas.api.UsersApi
import com.threedays.oas.model.GetMyUserInfoResponse
import com.threedays.oas.model.ProfileWidget
import com.threedays.oas.model.RegisterUserRequest
import com.threedays.oas.model.TokenResponse
import com.threedays.oas.model.UserProfile
Expand All @@ -24,6 +27,7 @@ import java.time.Year
@RestController
class UserController(
private val registerUser: RegisterUser,
private val putProfileWidget: PutProfileWidget,
private val userRepository: UserRepository,
) : UsersApi {

Expand Down Expand Up @@ -101,7 +105,28 @@ class UserController(
)
},
preferDistance = com.threedays.oas.model.PreferDistance.valueOf(user.desiredPartner.preferDistance.name),
)
),
profileWidgets = user.profile.profileWidgets.map {
ProfileWidget(
type = com.threedays.oas.model.ProfileWidgetType.valueOf(it.type.name),
content = it.content,
)
}
).let { ResponseEntity.ok(it) }
}

override fun putProfileWidget(body: ProfileWidget): ResponseEntity<ProfileWidget> =
withUserAuthentication { userAuthentication: UserAuthentication ->
val command = PutProfileWidget.Command(
userId = userAuthentication.userId,
profileWidget = com.threedays.domain.user.entity.ProfileWidget(
type = com.threedays.domain.user.entity.ProfileWidget.Type.valueOf(body.type.name),
content = body.content
)
)

putProfileWidget
.invoke(command)
.let { ResponseEntity.ok(body) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.threedays.domain.user.entity

data class ProfileWidget(
val type: Type,
val comment: String,
val content: String,
) {

enum class Type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,8 @@ data class User(
}
}

fun putProfileWidget(widget: ProfileWidget): User {
return copy(profile = profile.putProfileWidget(widget))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,17 @@ data class UserProfile(
val jobOccupation: JobOccupation,
val locations: List<Location>,
val profileWidgets: List<ProfileWidget> = emptyList(),
) : DomainEntity<UserProfile, User.Id>()
) : DomainEntity<UserProfile, User.Id>() {

fun putProfileWidget(profileWidget: ProfileWidget): UserProfile {
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 })
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import com.threedays.support.common.exception.NotFoundException

interface UserRepository : Repository<User, User.Id> {

fun get(id: User.Id): User = find(id)
?: throw NotFoundException("User not found by id: $id")

fun findByPhoneNumber(phoneNumber: PhoneNumber): User?
fun getByPhoneNumber(phoneNumber: PhoneNumber): User = findByPhoneNumber(phoneNumber)
?: throw NotFoundException("User not found by phone number: $phoneNumber")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.threedays.domain.user.entity
import com.navercorp.fixturemonkey.FixtureMonkey
import com.navercorp.fixturemonkey.kotlin.giveMeBuilder
import com.navercorp.fixturemonkey.kotlin.introspector.PrimaryConstructorArbitraryIntrospector
import com.navercorp.fixturemonkey.kotlin.set
import com.threedays.domain.auth.vo.PhoneNumber
import com.threedays.domain.user.vo.BirthYearRange
import com.threedays.domain.user.vo.Gender
Expand Down Expand Up @@ -163,5 +164,65 @@ class UserTest : DescribeSpec({
}
}

describe("putProfileWidget - 프로필 위젯 추가") {
it("프로필 위젯을 추가한다") {
// arrange
val profileWidget: ProfileWidget = fixtureMonkey
.giveMeBuilder<ProfileWidget>()
.sample()

val userDesiredPartner: UserDesiredPartner = fixtureMonkey
.giveMeBuilder<UserDesiredPartner>()
.set(UserDesiredPartner::allowSameCompany, null)
.sample()

val user: User = fixtureMonkey
.giveMeBuilder<User>()
.set(User::phoneNumber, PhoneNumber("01012345678"))
.set(User::desiredPartner, userDesiredPartner)
.sample()

// act
val result: User = user.putProfileWidget(profileWidget)

// assert
result.profile.profileWidgets.find { it == profileWidget } shouldBe profileWidget
}

ProfileWidget.Type.entries.forEach { widgetType ->
context("동일한 타입($widgetType)의 위젯이 이미 있는 경우") {
it("$widgetType 타입의 위젯 내용을 수정한다") {
// arrange
val profileWidget: ProfileWidget = fixtureMonkey
.giveMeBuilder<ProfileWidget>()
.set(ProfileWidget::type, widgetType)
.sample()

val userDesiredPartner: UserDesiredPartner = fixtureMonkey
.giveMeBuilder<UserDesiredPartner>()
.set(UserDesiredPartner::allowSameCompany, null)
.sample()

val user: User = fixtureMonkey
.giveMeBuilder<User>()
.set(User::phoneNumber, PhoneNumber("01012345678"))
.set(User::desiredPartner, userDesiredPartner)
.sample()
.putProfileWidget(profileWidget)

val updatedProfileWidget: ProfileWidget = fixtureMonkey
.giveMeBuilder<ProfileWidget>()
.set(ProfileWidget::type, widgetType)
.sample()

// act
val result: User = user.putProfileWidget(updatedProfileWidget)

// assert
result.profile.profileWidgets.find { it == updatedProfileWidget } shouldBe updatedProfileWidget
}
}
}
}

})
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.threedays.persistence.user.entity

import com.threedays.domain.user.entity.ProfileWidget
import jakarta.persistence.Embeddable
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated

@Embeddable
data class ProfileWidgetJpaEmbeddable(
@Enumerated(EnumType.STRING)
val type: ProfileWidget.Type,
val content: String,
) {

companion object {

fun ProfileWidget.toJpaEmbeddable() = ProfileWidgetJpaEmbeddable(
type = type,
content = content,
)

}

fun toValueObject() = ProfileWidget(
type = type,
content = content,
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import com.threedays.domain.user.entity.UserProfile
import com.threedays.domain.user.vo.Gender
import com.threedays.persistence.user.entity.CompanyJpaEntity.Companion.toJpaEntity
import com.threedays.persistence.user.entity.LocationJpaEntity.Companion.toJpaEntity
import com.threedays.persistence.user.entity.ProfileWidgetJpaEmbeddable.Companion.toJpaEmbeddable
import jakarta.persistence.CollectionTable
import jakarta.persistence.Column
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
Expand All @@ -29,6 +31,7 @@ class UserProfileJpaEntity(
company: CompanyJpaEntity?,
jobOccupation: JobOccupation,
locations: List<LocationJpaEntity>,
profileWidgets: List<ProfileWidgetJpaEmbeddable>,
) {

@Id
Expand Down Expand Up @@ -62,6 +65,14 @@ class UserProfileJpaEntity(
var locations: List<LocationJpaEntity> = locations
private set

@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(
name = "user_profile_widgets",
joinColumns = [JoinColumn(name = "user_profile_id")],
)
var profileWidgets: List<ProfileWidgetJpaEmbeddable> = profileWidgets
private set

companion object {

fun UserProfile.toJpaEntity() = UserProfileJpaEntity(
Expand All @@ -71,6 +82,7 @@ class UserProfileJpaEntity(
company = company?.toJpaEntity(),
jobOccupation = jobOccupation,
locations = locations.map { it.toJpaEntity() },
profileWidgets = profileWidgets.map { it.toJpaEmbeddable() }
)

}
Expand All @@ -82,6 +94,7 @@ class UserProfileJpaEntity(
company = company?.toDomainEntity(),
jobOccupation = jobOccupation,
locations = locations.map { it.toDomainEntity() },
profileWidgets = profileWidgets.map { it.toValueObject() }
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@ CREATE TABLE users
CONSTRAINT pk_users PRIMARY KEY (id),
CONSTRAINT uq_users_phone_number UNIQUE (phone_number)
);

CREATE TABLE user_profile_widgets
(
user_profile_id BINARY(16) NOT NULL,
type VARCHAR(255) NOT NULL,
content VARCHAR(255) NOT NULL,
PRIMARY KEY (user_profile_id, type),
FOREIGN KEY (user_profile_id) REFERENCES user_profiles (id) ON DELETE CASCADE
);
2 changes: 1 addition & 1 deletion openapi
Submodule openapi updated 1 files
+147 −0 openapi.yaml

0 comments on commit 373f965

Please sign in to comment.