Skip to content

Commit

Permalink
프로필 이미지가 null일 경우 알림 전송에 실패하는 오류 해결 (#412)
Browse files Browse the repository at this point in the history
* feat: 로그인 시 프로필 이미지를 null이 아닌 기본이미지로 세팅하는 기능 추가

* feat: 사용자 엔티티의 프로필 이미지에 널 불가 제약조건 추가

* feat: 알림 dto에 `@NonNull` 제약 조건 추가

* refactor: 알림 전송 성공 및 예외 발생 시 로그 추가

* refactor: 기본 프로필 이미지 조회 시 이름으로 조회하도록 변경

* feat: 로그인 시 기본이미지를 찾을 수 없으면 예외가 발생하는 기능 추가

* refactor: 프로필 이미지 경로 생성을 유틸 클래스에서 하도록 변경

* refactor: 기본 프로필 이미지 이름을 엔티티에서 관리하도록 변경

* fix: flyway 스크립트 수정

* refactor: 알림 dto에서 사용하는 `@NonNull`을 롬복의 어노테이션을 사용하도록 변경
  • Loading branch information
kwonyj1022 authored and swonny committed Oct 6, 2023
1 parent 7189c23 commit 892e8c1
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import com.ddang.ddang.authentication.infrastructure.oauth2.Oauth2Type;
import com.ddang.ddang.device.application.DeviceTokenService;
import com.ddang.ddang.device.application.dto.PersistDeviceTokenDto;
import com.ddang.ddang.image.application.exception.ImageNotFoundException;
import com.ddang.ddang.image.domain.ProfileImage;
import com.ddang.ddang.image.infrastructure.persistence.JpaProfileImageRepository;
import com.ddang.ddang.user.domain.User;
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -23,6 +26,8 @@
import java.time.LocalDateTime;
import java.util.Map;

import static com.ddang.ddang.image.domain.ProfileImage.DEFAULT_PROFILE_IMAGE_STORE_NAME;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
Expand All @@ -33,6 +38,7 @@ public class AuthenticationService {
private final DeviceTokenService deviceTokenService;
private final Oauth2UserInformationProviderComposite providerComposite;
private final JpaUserRepository userRepository;
private final JpaProfileImageRepository profileImageRepository;
private final TokenEncoder tokenEncoder;
private final TokenDecoder tokenDecoder;
private final BlackListTokenService blackListTokenService;
Expand All @@ -58,7 +64,7 @@ private User findOrPersistUser(final Oauth2Type oauth2Type, final UserInformatio
.orElseGet(() -> {
final User user = User.builder()
.name(oauth2Type.calculateNickname(calculateRandomNumber()))
.profileImage(null)
.profileImage(findDefaultProfileImage())
.reliability(0.0d)
.oauthId(userInformationDto.findUserId())
.build();
Expand All @@ -67,6 +73,11 @@ private User findOrPersistUser(final Oauth2Type oauth2Type, final UserInformatio
});
}

private ProfileImage findDefaultProfileImage() {
return profileImageRepository.findByStoreName(DEFAULT_PROFILE_IMAGE_STORE_NAME)
.orElseThrow(() -> new ImageNotFoundException("기본 이미지를 찾을 수 없습니다."));
}

private String calculateRandomNumber() {
String name = RandomNameGenerator.generate();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
import com.ddang.ddang.chat.infrastructure.persistence.JpaChatRoomRepository;
import com.ddang.ddang.chat.infrastructure.persistence.JpaMessageRepository;
import com.ddang.ddang.chat.presentation.dto.request.ReadMessageRequest;
import com.ddang.ddang.image.application.util.ImageIdProcessor;
import com.ddang.ddang.image.domain.ProfileImage;
import com.ddang.ddang.image.presentation.util.ImageUrlCalculator;
import com.ddang.ddang.notification.application.NotificationService;
import com.ddang.ddang.notification.application.dto.CreateNotificationDto;
import com.ddang.ddang.notification.domain.NotificationType;
import com.ddang.ddang.user.application.exception.UserNotFoundException;
import com.ddang.ddang.user.domain.User;
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -27,6 +29,7 @@
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class MessageService {

private final NotificationService notificationService;
Expand Down Expand Up @@ -54,29 +57,29 @@ public Long create(final CreateMessageDto dto, final String baseUrl) {

final Message persistMessage = messageRepository.save(message);

sendNotification(persistMessage, baseUrl);
try {
final String sendNotificationMessage = sendNotification(persistMessage, baseUrl);
log.info(sendNotificationMessage);
} catch (Exception ex) {
log.error("exception type : {}, ", ex.getClass().getSimpleName(), ex);
}

return persistMessage.getId();
}

private void sendNotification(final Message message, final String baseUrl) {
final Long profileImageId = ImageIdProcessor.process(message.getWriter().getProfileImage());

// TODO: 2023/09/15 5차데모데이 이후 수정 예정
String profileImageUrl = null;
if (profileImageId != null) {
profileImageUrl = baseUrl.concat(String.valueOf(profileImageId));
}
private String sendNotification(final Message message, final String baseUrl) {
final ProfileImage writerProfileImage = message.getWriter().getProfileImage();

final CreateNotificationDto dto = new CreateNotificationDto(
NotificationType.MESSAGE,
message.getReceiver().getId(),
message.getWriter().getName(),
message.getContents(),
calculateRedirectUrl(message.getChatRoom().getId()),
profileImageUrl
ImageUrlCalculator.calculateProfileImageUrl(writerProfileImage, baseUrl)
);
notificationService.send(dto);

return notificationService.send(dto);
}

private String calculateRedirectUrl(final Long id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
@ToString(of = {"id", "image"})
public class ProfileImage {

public static final String DEFAULT_PROFILE_IMAGE_STORE_NAME = "default_profile_image.png";

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

import com.ddang.ddang.image.domain.ProfileImage;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

public interface JpaProfileImageRepository extends JpaRepository<ProfileImage, Long> {

@Query("select i from ProfileImage i where i.image.storeName = :storeName")
Optional<ProfileImage> findByStoreName(final String storeName);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.ddang.ddang.image.presentation.util;

import com.ddang.ddang.image.application.util.ImageIdProcessor;
import com.ddang.ddang.image.domain.ProfileImage;

public final class ImageUrlCalculator {

private ImageUrlCalculator() {
Expand All @@ -14,4 +17,9 @@ public static String calculate(final ImageBaseUrl imageBaseUrl, final Long id) {

return baseUrl.concat(String.valueOf(id));
}

public static String calculateProfileImageUrl(final ProfileImage profileImage, final String baseUrl) {
final Long profileImageId = ImageIdProcessor.process(profileImage);
return baseUrl.concat(String.valueOf(profileImageId));
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package com.ddang.ddang.notification.application.dto;

import com.ddang.ddang.notification.domain.NotificationType;
import lombok.NonNull;

public record CreateNotificationDto(
@NonNull
NotificationType notificationType,

@NonNull
Long targetUserId,

@NonNull
String title,

@NonNull
String body,

@NonNull
String redirectUrl,

@NonNull
String image
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class User extends BaseTimeEntity {
private String name;

@OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "profile_image_id", foreignKey = @ForeignKey(name = "fk_user_profile_image"))
@JoinColumn(name = "profile_image_id", foreignKey = @ForeignKey(name = "fk_user_profile_image"), nullable = false)
private ProfileImage profileImage;

private double reliability;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
UPDATE users SET profile_image_id = 1 WHERE profile_image_id is null;
ALTER TABLE users MODIFY profile_image_id bigint NOT NULL;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import com.ddang.ddang.device.application.DeviceTokenService;
import com.ddang.ddang.device.application.dto.PersistDeviceTokenDto;
import com.ddang.ddang.device.infrastructure.persistence.JpaDeviceTokenRepository;
import com.ddang.ddang.image.application.exception.ImageNotFoundException;
import com.ddang.ddang.image.domain.ProfileImage;
import com.ddang.ddang.image.infrastructure.persistence.JpaProfileImageRepository;
import com.ddang.ddang.user.domain.User;
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository;
import org.assertj.core.api.SoftAssertions;
Expand Down Expand Up @@ -61,6 +63,9 @@ class AuthenticationServiceTest {
@Autowired
JpaUserRepository userRepository;

@Autowired
JpaProfileImageRepository profileImageRepository;

@Autowired
JpaDeviceTokenRepository userDeviceTokenRepository;

Expand Down Expand Up @@ -92,6 +97,7 @@ void setUp(
deviceTokenService,
mockProviderComposite,
userRepository,
profileImageRepository,
tokenEncoder,
tokenDecoder,
mockBlackListTokenService
Expand Down Expand Up @@ -130,6 +136,8 @@ void setUp(
@Test
void 입한_회원이_소셜_로그인을__경우_accessToken_refreshToken_반환한다() {
// given
profileImageRepository.save(new ProfileImage("default_profile_image.png", "default_profile_image.png"));

final User user = User.builder()
.name("kakao12345")
.profileImage(new ProfileImage("upload.png", "store.png"))
Expand All @@ -154,9 +162,25 @@ void setUp(
});
}

@Test
void 입하지_않은_회원이_소셜_로그인을___기본_프로필_이미지를_찾을__없으면_예외가_발생한다() {
// given
final UserInformationDto userInformationDto = new UserInformationDto(12345L);

given(mockProviderComposite.findProvider(Oauth2Type.KAKAO)).willReturn(mockProvider);
given(mockProvider.findUserInformation(anyString())).willReturn(userInformationDto);

// when & then
assertThatThrownBy(() -> authenticationService.login(Oauth2Type.KAKAO, "accessToken", "deviceToken"))
.isInstanceOf(ImageNotFoundException.class)
.hasMessage("기본 이미지를 찾을 수 없습니다.");
}

@Test
void 입하지_않은_회원이_소셜_로그인을__경우_accessToken_refreshToken_반환한다() {
// given
profileImageRepository.save(new ProfileImage("default_profile_image.png", "default_profile_image.png"));

final UserInformationDto userInformationDto = new UserInformationDto(12345L);

given(mockProviderComposite.findProvider(Oauth2Type.KAKAO)).willReturn(mockProvider);
Expand All @@ -175,6 +199,8 @@ void setUp(
@Test
void 탈퇴한_회원이_소셜_로그인을__경우_accessToken_refreshToken_반환한다() {
// given
profileImageRepository.save(new ProfileImage("default_profile_image.png", "default_profile_image.png"));

final User user = User.builder()
.name("kakao12345")
.profileImage(new ProfileImage("upload.png", "store.png"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,22 @@ class JpaProfileImageRepositoryTest {
// then
assertThat(actual).isEmpty();
}

@Test
void 저장된_이름에_해당하는_이미지를_반환한다() {
// given
final String storeName = "storeName.png";
final ProfileImage expect = new ProfileImage("uploadName", storeName);

imageRepository.save(expect);

em.flush();
em.clear();

// when
final Optional<ProfileImage> actual = imageRepository.findByStoreName(storeName);

// then
assertThat(actual).contains(expect);
}
}

0 comments on commit 892e8c1

Please sign in to comment.