Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package org.runimo.runimo.user.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.runimo.runimo.common.response.SuccessResponse;
import org.runimo.runimo.user.controller.request.UpdateNotificationAllowedRequst;
import org.runimo.runimo.user.enums.UserHttpResponseCode;
import org.runimo.runimo.user.service.dto.command.UpdateNotificationAllowedCommand;
import org.runimo.runimo.user.service.dto.response.MyPageViewResponse;
import org.runimo.runimo.user.service.dto.response.NotificationAllowedResponse;
import org.runimo.runimo.user.service.usecases.UpdateUserDetailUsecase;
import org.runimo.runimo.user.service.usecases.query.MyPageQueryUsecase;
import org.runimo.runimo.user.service.usecases.query.UserInfoQueryUsecase;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -16,6 +26,8 @@
public class MyPageController {

private final MyPageQueryUsecase myPageQueryUsecase;
private final UserInfoQueryUsecase userInfoQueryUsecase;
private final UpdateUserDetailUsecase updateUserDetailUsecase;

@GetMapping
public ResponseEntity<SuccessResponse<MyPageViewResponse>> queryMyPageView(
Expand All @@ -25,4 +37,34 @@ public ResponseEntity<SuccessResponse<MyPageViewResponse>> queryMyPageView(
SuccessResponse.of(UserHttpResponseCode.MY_PAGE_DATA_FETCHED, response));
}

@Operation(summary = "회원 알림 수신 허용 여부 수정", description = "알림을 수신 여부 변경")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "수신 여부 수정 성공"),
@ApiResponse(responseCode = "404", description = "없는 사용자")
})
@PatchMapping("/notifications/permission")
public ResponseEntity<SuccessResponse<Void>> updateNotificationPermission(
@UserId Long userId,
@RequestBody UpdateNotificationAllowedRequst request
) {
updateUserDetailUsecase.updateUserNotificationAllowed(
UpdateNotificationAllowedCommand.of(userId, request));
return ResponseEntity.ok().body(
SuccessResponse.messageOnly(UserHttpResponseCode.NOTIFICATION_ALLOW_UPDATED));
}

@Operation(summary = "회원 알림 수신 허용 여부 확인", description = "알림을 수신하는지 확인")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "수신 여부 확인"),
@ApiResponse(responseCode = "404", description = "없는 사용자 또는 토큰 미등록")
})
@GetMapping("/notifications/permission")
public ResponseEntity<SuccessResponse<NotificationAllowedResponse>> queryNotificationPermission(
@UserId Long userId
) {
return ResponseEntity.ok(
SuccessResponse.of(UserHttpResponseCode.NOTIFICATION_ALLOW_FETCHED,
userInfoQueryUsecase.getUserNotificationAllowed(userId)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.runimo.runimo.user.controller.request;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "알림 허용 여부 업데이트 요청")
public record UpdateNotificationAllowedRequst(
@Schema(description = "알림 허용 여부", example = "true")
boolean allowed,
String deviceToken,
String platform
) {

}
16 changes: 16 additions & 0 deletions src/main/java/org/runimo/runimo/user/domain/UserDeviceToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,20 @@ public static UserDeviceToken from(String deviceToken, DevicePlatform platform,
.build();
}

public void updateNotificationAllowed(Boolean allowed) {
this.notificationAllowed = allowed;
}

public void updateDeviceToken(String deviceToken) {
validateDeviceToken(deviceToken, this.platform);
this.deviceToken = deviceToken;
this.lastUsedAt = LocalDateTime.now();
}

private void validateDeviceToken(String deviceToken, DevicePlatform platform) {
if (deviceToken == null || deviceToken.trim().isEmpty()) {
throw new IllegalArgumentException("디바이스 토큰은 필수입니다");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public enum UserHttpResponseCode implements CustomResponseCode {
REGISTER_EGG_SUCCESS(HttpStatus.CREATED, "부화기 등록 성공", "부화기 등록 성공"),
USE_LOVE_POINT_SUCCESS(HttpStatus.OK, "애정 사용 성공", "애정 사용 성공"),
MY_INCUBATING_EGG_FETCHED(HttpStatus.OK, "부화기중인 알 조회 성공", "부화중인 알 조회 성공"),
NOTIFICATION_ALLOW_UPDATED(HttpStatus.OK, "알림 허용 업데이트 성공", "알림 허용 업데이트 성공"),
NOTIFICATION_ALLOW_FETCHED(HttpStatus.OK, "알림 허용 조회 성공", "알림 허용 조회 성공"),

LOGIN_FAIL_NOT_SIGN_IN(HttpStatus.NOT_FOUND
, "로그인 실패 - 회원가입하지 않은 사용자", "로그인 실패 - 회원가입하지 않은 사용자"),
Expand All @@ -27,7 +29,8 @@ public enum UserHttpResponseCode implements CustomResponseCode {
"사용자가 유효하지 않습니다. Refresh 토큰 삭제에 실패했습니다"),
LOG_OUT_SUCCESS(HttpStatus.OK, "로그아웃 성공", "로그아웃 성공"),
ALREADY_LOG_OUT_SUCCESS(HttpStatus.OK, "로그아웃 성공 (이미 로그아웃된 사용자)", "로그아웃 성공 (이미 로그아웃된 사용자)"),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없음", "사용자를 찾을 수 없음");
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없음", "사용자를 찾을 수 없음"),
DEVICE_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "디바이스 토큰이 없음", "디바이스 토큰이 없음");

private final HttpStatus code;
private final String clientMessage;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.runimo.runimo.user.repository;

import java.util.Optional;
import org.runimo.runimo.user.domain.UserDeviceToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserDeviceTokenRepository extends JpaRepository<UserDeviceToken, Long> {

Optional<UserDeviceToken> findByUserId(Long userId);
}
8 changes: 8 additions & 0 deletions src/main/java/org/runimo/runimo/user/service/UserFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import lombok.RequiredArgsConstructor;
import org.runimo.runimo.user.domain.LovePoint;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.domain.UserDeviceToken;
import org.runimo.runimo.user.repository.LovePointRepository;
import org.runimo.runimo.user.repository.UserDeviceTokenRepository;
import org.runimo.runimo.user.repository.UserRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -15,6 +17,7 @@ public class UserFinder {

private final UserRepository userRepository;
private final LovePointRepository lovePointRepository;
private final UserDeviceTokenRepository userDeviceTokenRepository;

@Transactional(readOnly = true)
public Optional<User> findUserByPublicId(final String publicId) {
Expand All @@ -30,4 +33,9 @@ public Optional<User> findUserById(final Long userId) {
public Optional<LovePoint> findLovePointByUserId(Long userId) {
return lovePointRepository.findLovePointByUserId(userId);
}

@Transactional(readOnly = true)
public Optional<UserDeviceToken> findUserDeviceTokenByUserId(Long userId) {
return userDeviceTokenRepository.findByUserId(userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.runimo.runimo.user.service.dto.command;

import org.runimo.runimo.user.controller.request.UpdateNotificationAllowedRequst;
import org.runimo.runimo.user.domain.DevicePlatform;

public record UpdateNotificationAllowedCommand(
Long userId,
Boolean allowed,
String deviceToken,
DevicePlatform devicePlatform
) {

public static UpdateNotificationAllowedCommand of(Long userId,
UpdateNotificationAllowedRequst request) {
return new UpdateNotificationAllowedCommand(userId, request.allowed(),
request.deviceToken(), DevicePlatform.fromString(request.platform()));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.runimo.runimo.user.service.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "알림 허용 여부 응답")
public record NotificationAllowedResponse(
@Schema(description = "사용자 ID", example = "1")
Long userId,
@Schema(description = "알림 허용 여부", example = "true")
boolean allowed
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.runimo.runimo.user.service.usecases;

import org.runimo.runimo.user.service.dto.command.UpdateNotificationAllowedCommand;

public interface UpdateUserDetailUsecase {

void updateUserNotificationAllowed(UpdateNotificationAllowedCommand command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.runimo.runimo.user.service.usecases;

import lombok.RequiredArgsConstructor;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.domain.UserDeviceToken;
import org.runimo.runimo.user.enums.UserHttpResponseCode;
import org.runimo.runimo.user.exception.UserException;
import org.runimo.runimo.user.repository.UserDeviceTokenRepository;
import org.runimo.runimo.user.service.UserFinder;
import org.runimo.runimo.user.service.dto.command.UpdateNotificationAllowedCommand;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UpdateUserDetailUsecaseImpl implements UpdateUserDetailUsecase {

private final UserFinder userFinder;
private final UserDeviceTokenRepository userDeviceTokenRepository;

@Override
@Transactional
public void updateUserNotificationAllowed(UpdateNotificationAllowedCommand command) {
UserDeviceToken token = userDeviceTokenRepository
.findByUserId(command.userId())
.orElseGet(() -> createUserDeviceTokenIfNotExist(command));
token.updateDeviceToken(command.deviceToken());
token.updateNotificationAllowed(command.allowed());
}

private UserDeviceToken createUserDeviceTokenIfNotExist(
UpdateNotificationAllowedCommand command) {
User user = userFinder.findUserById(command.userId())
.orElseThrow(() -> UserException.of(UserHttpResponseCode.USER_NOT_FOUND));

UserDeviceToken token = UserDeviceToken.builder()
.user(user)
.deviceToken(command.deviceToken())
.platform(command.devicePlatform())
.notificationAllowed(command.allowed())
.build();
return userDeviceTokenRepository.save(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.runimo.runimo.user.service.usecases.query;

import org.runimo.runimo.user.service.dto.response.NotificationAllowedResponse;

public interface UserInfoQueryUsecase {

NotificationAllowedResponse getUserNotificationAllowed(Long userId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.runimo.runimo.user.service.usecases.query;

import lombok.RequiredArgsConstructor;
import org.runimo.runimo.user.enums.UserHttpResponseCode;
import org.runimo.runimo.user.exception.UserException;
import org.runimo.runimo.user.service.UserFinder;
import org.runimo.runimo.user.service.dto.response.NotificationAllowedResponse;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserInfoQueryUsecaseImpl implements UserInfoQueryUsecase {

private final UserFinder userFinder;

@Override
public NotificationAllowedResponse getUserNotificationAllowed(Long userId) {
if (userFinder.findUserById(userId).isEmpty()) {
throw UserException.of(UserHttpResponseCode.USER_NOT_FOUND);
}
var userDeviceToken = userFinder.findUserDeviceTokenByUserId(userId)
.orElseThrow(() -> UserException.of(UserHttpResponseCode.DEVICE_TOKEN_NOT_FOUND));
return new NotificationAllowedResponse(userId, userDeviceToken.getNotificationAllowed());
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ spring:
dialect: org.hibernate.dialect.MySQLDialect
show-sql: true

flyway:
enabled: true
locations: classpath:db/migration/mysql
baseline-on-migrate: true
clean-disabled: false
jwt:
expiration: ${JWT_EXPIRATION:180000}
refresh:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE user_token
ADD CONSTRAINT uq_user_token_device_token UNIQUE (device_token);
1 change: 1 addition & 0 deletions src/test/java/org/runimo/runimo/CleanUpUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class CleanUpUtil {
"user_love_point",
"incubating_egg",
"runimo",
"user_token"
};

@Autowired
Expand Down
Loading