diff --git a/src/main/kotlin/com/routebox/routebox/application/user_mobile/UpdateUserMobileUseCase.kt b/src/main/kotlin/com/routebox/routebox/application/user_mobile/UpdateUserMobileUseCase.kt new file mode 100644 index 0000000..d304fe5 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/user_mobile/UpdateUserMobileUseCase.kt @@ -0,0 +1,35 @@ +package com.routebox.routebox.application.user_mobile + +import com.routebox.routebox.application.user_mobile.dto.UpdateUserMobileCommand +import com.routebox.routebox.domain.user_mobile.UserMobile +import com.routebox.routebox.domain.user_mobile.UserMobileService +import jakarta.transaction.Transactional +import org.springframework.stereotype.Component + +@Component +class UpdateUserMobileUseCase( + private val userMobileService: UserMobileService, +) { + @Transactional + operator fun invoke(command: UpdateUserMobileCommand): Long { + // user 푸시 이력 조회 + val userMobile = userMobileService.get(command.userId) + + // 이미 있으면 수정 + if (userMobile !== null) { + userMobile.update(command.pushToken, command.os) + userMobileService.create(userMobile) + return userMobile.id + } else { + // 없으면 새로 추가 + val newMobile = UserMobile( + userId = command.userId, + pushToken = command.pushToken, + osType = command.os, + ) + + val userMobileId = userMobileService.create(newMobile) + return userMobileId + } + } +} diff --git a/src/main/kotlin/com/routebox/routebox/application/user_mobile/dto/UpdateUserMobileCommand.kt b/src/main/kotlin/com/routebox/routebox/application/user_mobile/dto/UpdateUserMobileCommand.kt new file mode 100644 index 0000000..35544c9 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/application/user_mobile/dto/UpdateUserMobileCommand.kt @@ -0,0 +1,7 @@ +package com.routebox.routebox.application.user_mobile.dto + +data class UpdateUserMobileCommand( + val userId: Long, + val os: String, + val pushToken: String?, +) diff --git a/src/main/kotlin/com/routebox/routebox/controller/user/UserController.kt b/src/main/kotlin/com/routebox/routebox/controller/user/UserController.kt index 24650b8..48daeaf 100644 --- a/src/main/kotlin/com/routebox/routebox/controller/user/UserController.kt +++ b/src/main/kotlin/com/routebox/routebox/controller/user/UserController.kt @@ -4,8 +4,11 @@ import com.routebox.routebox.application.user.CheckNicknameAvailabilityUseCase import com.routebox.routebox.application.user.GetUserProfileUseCase import com.routebox.routebox.application.user.GetUserUseCase import com.routebox.routebox.application.user.UpdateUserInfoUseCase +import com.routebox.routebox.application.user_mobile.UpdateUserMobileUseCase import com.routebox.routebox.controller.user.dto.CheckNicknameAvailabilityResponse import com.routebox.routebox.controller.user.dto.UpdateUserInfoRequest +import com.routebox.routebox.controller.user.dto.UpdateUserMobileRequest +import com.routebox.routebox.controller.user.dto.UpdateUserMobileResponse import com.routebox.routebox.controller.user.dto.UserProfileResponse import com.routebox.routebox.controller.user.dto.UserResponse import com.routebox.routebox.domain.validation.Nickname @@ -25,6 +28,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.ModelAttribute import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -37,6 +41,7 @@ class UserController( private val getUserProfileUseCase: GetUserProfileUseCase, private val checkNicknameAvailabilityUseCase: CheckNicknameAvailabilityUseCase, private val updateUserInfoUseCase: UpdateUserInfoUseCase, + private val updateUserMobileUseCase: UpdateUserMobileUseCase, ) { @Operation( summary = "내 유저 정보 조회", @@ -106,4 +111,20 @@ class UserController( val updateUserInfo = updateUserInfoUseCase(request.toCommand(userPrincipal.userId)) return UserResponse.from(updateUserInfo) } + + @Operation( + summary = "유저 푸시 정보 입력", + security = [SecurityRequirement(name = "access-token")], + ) + @ApiResponses( + ApiResponse(responseCode = "200"), + ) + @PutMapping("/v1/users/mobile") + fun updateUserMobile( + @AuthenticationPrincipal userPrincipal: UserPrincipal, + @ModelAttribute @Valid request: UpdateUserMobileRequest, + ): UpdateUserMobileResponse { + val id = updateUserMobileUseCase(request.toCommand(userPrincipal.userId)) + return UpdateUserMobileResponse(id) + } } diff --git a/src/main/kotlin/com/routebox/routebox/controller/user/dto/UpdateUserMobileRequest.kt b/src/main/kotlin/com/routebox/routebox/controller/user/dto/UpdateUserMobileRequest.kt new file mode 100644 index 0000000..edf89c9 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/controller/user/dto/UpdateUserMobileRequest.kt @@ -0,0 +1,19 @@ +package com.routebox.routebox.controller.user.dto + +import com.routebox.routebox.application.user_mobile.dto.UpdateUserMobileCommand +import io.swagger.v3.oas.annotations.media.Schema + +data class UpdateUserMobileRequest( + @Schema(description = "OS (iOS, Android)") + var os: String, + + @Schema(description = "FCM 토큰") + var pushToken: String, +) { + fun toCommand(userId: Long): UpdateUserMobileCommand = + UpdateUserMobileCommand( + userId = userId, + os = os, + pushToken = pushToken, + ) +} diff --git a/src/main/kotlin/com/routebox/routebox/controller/user/dto/UpdateUserMobileResponse.kt b/src/main/kotlin/com/routebox/routebox/controller/user/dto/UpdateUserMobileResponse.kt new file mode 100644 index 0000000..568d631 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/controller/user/dto/UpdateUserMobileResponse.kt @@ -0,0 +1,8 @@ +package com.routebox.routebox.controller.user.dto + +import io.swagger.v3.oas.annotations.media.Schema + +data class UpdateUserMobileResponse( + @Schema(description = "Id(PK) of user mobile", example = "1") + val id: Long, +) diff --git a/src/main/kotlin/com/routebox/routebox/domain/user_mobile/UserMobile.kt b/src/main/kotlin/com/routebox/routebox/domain/user_mobile/UserMobile.kt new file mode 100644 index 0000000..cee0e98 --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/domain/user_mobile/UserMobile.kt @@ -0,0 +1,35 @@ +package com.routebox.routebox.domain.user_mobile + +import com.routebox.routebox.domain.common.TimeTrackedBaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id + +@Entity +class UserMobile( + userId: Long, + pushToken: String?, + osType: String, + id: Long = 0, +) : TimeTrackedBaseEntity() { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_mobile_id") + val id: Long = id + + @Column(name = "user_id", nullable = false) + val userId: Long = userId + + @Column + var pushToken: String? = pushToken + + @Column + var osType: String = osType + + fun update(pushToken: String?, osType: String) { + this.pushToken = pushToken + this.osType = osType + } +} diff --git a/src/main/kotlin/com/routebox/routebox/domain/user_mobile/UserMobileService.kt b/src/main/kotlin/com/routebox/routebox/domain/user_mobile/UserMobileService.kt new file mode 100644 index 0000000..59d170c --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/domain/user_mobile/UserMobileService.kt @@ -0,0 +1,15 @@ +package com.routebox.routebox.domain.user_mobile + +import com.routebox.routebox.infrastructure.user_mobile.UserMobileRepository +import org.springframework.stereotype.Service + +@Service +class UserMobileService(private val userMobileRepository: UserMobileRepository) { + fun get(userId: Long): UserMobile? { + return userMobileRepository.findByUserId(userId) + } + + fun create(userMobile: UserMobile): Long { + return userMobileRepository.save(userMobile).id + } +} diff --git a/src/main/kotlin/com/routebox/routebox/infrastructure/user_mobile/UserMobileRepository.kt b/src/main/kotlin/com/routebox/routebox/infrastructure/user_mobile/UserMobileRepository.kt new file mode 100644 index 0000000..406db2d --- /dev/null +++ b/src/main/kotlin/com/routebox/routebox/infrastructure/user_mobile/UserMobileRepository.kt @@ -0,0 +1,8 @@ +package com.routebox.routebox.infrastructure.user_mobile + +import com.routebox.routebox.domain.user_mobile.UserMobile +import org.springframework.data.jpa.repository.JpaRepository + +interface UserMobileRepository : JpaRepository { + fun findByUserId(userId: Long): UserMobile? +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ffd041c..6dfc6b9 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -298,3 +298,12 @@ CREATE TABLE comment_report ( ); -- CREATE INDEX idx__comment_report__reporter_id ON comment_report (reporter_id); -- CREATE INDEX idx__comment_report__reported_comment_id ON comment_report (reported_comment_id); + +CREATE TABLE user_mobile ( + user_mobile_id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + push_token TEXT, + os_type VARCHAR(10) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 시간', + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트 시간' +); diff --git a/src/test/kotlin/com/routebox/routebox/controller/user/UserControllerTest.kt b/src/test/kotlin/com/routebox/routebox/controller/user/UserControllerTest.kt index 4600335..4442e6c 100644 --- a/src/test/kotlin/com/routebox/routebox/controller/user/UserControllerTest.kt +++ b/src/test/kotlin/com/routebox/routebox/controller/user/UserControllerTest.kt @@ -6,6 +6,7 @@ import com.routebox.routebox.application.user.GetUserUseCase import com.routebox.routebox.application.user.UpdateUserInfoUseCase import com.routebox.routebox.application.user.dto.GetUserProfileResult import com.routebox.routebox.application.user.dto.UpdateUserInfoResult +import com.routebox.routebox.application.user_mobile.UpdateUserMobileUseCase import com.routebox.routebox.config.ControllerTestConfig import com.routebox.routebox.controller.user.dto.UpdateUserInfoRequest import com.routebox.routebox.domain.user.constant.Gender @@ -46,6 +47,9 @@ class UserControllerTest @Autowired constructor(private val mvc: MockMvc) { @MockBean lateinit var checkNicknameAvailabilityUseCase: CheckNicknameAvailabilityUseCase + @MockBean + lateinit var updateUserMobileUseCase: UpdateUserMobileUseCase + private fun verifyEveryMocksShouldHaveNoMoreInteractions() { then(getUserUseCase).shouldHaveNoMoreInteractions() then(getUserProfileUseCase).shouldHaveNoMoreInteractions()