728x90

이전글

https://chordplaylist.tistory.com/235

Alarm API

  • 알람 리스트 조회
    • 알람 조회는 모든 회원이 권한을 가집니다.
    • 특정 User의 알람 목록을 받아옵니다.
    • 특정 포스트에 새 댓글이 달리고, 좋아요가 눌리면 알람이 등록됩니다.
    • 알람 목록은 Pagination으로 받아옵니다. (Pageable 사용)
      • 한 페이지당 default 피드 개수는 20개
      • 총 페이지 갯수가 표시
      • 작성날짜 기준으로 최신순으로 sortAlarm Entity
  • 앤드 포인트
    GET /alarms
  • 최신 순으로 20개씩 표시 (Pageable 사용)
  • 알람 리스트 조회 시 응답 필드
    • id : 알람 ID
    • alarmType :알람 타입 (NEW_COMMENT_ON_POST, NEW_LIKE_ON_POST)
    • fromUserId: fromUserId(알림을 발생시킨 user id)
    • targetId : targetId(알림이 발생된 post id)
    • text : alarmType 따라 string 필드에 담아 줄 수 있도록 필드를 선언합니다.
      • NEW_COMMENT_ON_POST 일 때는 alarmText new comment!
      • NEW_LIKE_ON_POST 일 때는 alarmText "new like!"
    • createdAt : 등록일시
  • 리턴
{
    "resultCode":"SUCCESS",
  "result": {
    "content":
    [
        {
          "id": 1,
          "alarmType": "NEW_LIKE_ON_POST",
        "fromUserId": 1,
        "targetId": 1,
          "text": "new like!",
          "createdAt": "2022-12-25T14:53:28.209+00:00",
      }
    ]
    }
}

Alarm Entity

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
public class Alarm extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @JoinColumn(name = "user_id")
    @ManyToOne(fetch = LAZY)
    private User user;

    @Enumerated(EnumType.STRING)
    private AlarmType alarmType; // like인지 comment인지

    private Integer fromUserId;

    private Integer targetId; // podst id

    private String text;

    public static Alarm toEntity(User user, Post post, AlarmType alarmType) {
        return Alarm.builder()
                .user(post.getUser())
                .alarmType(alarmType)
                .fromUserId(user.getId())
                .targetId(post.getId())
                .text(alarmType.getText())
                .build();
    }

    public AlarmResponse toResponse() {
        return AlarmResponse.builder()
                .id(this.id)
                .alarmType(this.alarmType)
                .fromUserId(this.fromUserId)
                .targetId(this.targetId)
                .text(this.alarmType.getText())
                .createdAt(this.getCreatedAt())
                .build();
    }

    @Override
    public void cancelDeletion() {
        super.cancelDeletion();
    }
}

테스트 코드

@WebMvcTest(AlarmRestController.class)
@WithMockUser
class AlarmRestControllerTest {
    @Autowired
    MockMvc mockMvc;

    @Autowired
    ObjectMapper objectMapper;

    @MockBean
    AlarmService alarmService;

    private String token;
    @Value("${jwt.secret}")
    private String secretKey;

    public final LocalDateTime time = LocalDateTime.now();

    @BeforeEach()
    public void getToken() {
        long expireTimeMs = 1000 * 60 * 60;
        token = JwtUtil.createJwt("chordpli", secretKey, System.currentTimeMillis() + expireTimeMs);
    }

    @Test
    @DisplayName("알람 목록 조회 성공")
    void success_get_alarm_list() throws Exception {
        User user = UserFixture.get("chordpli", "1234");
        Post post = PostFixture.get(user);

        //List<AlarmResponse> alarms = new ArrayList<>();
        PageRequest pageRequest = PageRequest.of(0, 20, Sort.by("createdAt").descending());

        Alarm alarm1 = Alarm.builder()
                .id(1)
                .alarmType(NEW_COMMENT_ON_POST)
                .user(user)
                .fromUserId(3)
                .targetId(post.getId())
                .text(NEW_COMMENT_ON_POST.getText())
                .build();

        Alarm alarm2 = Alarm.builder()
                .id(2)
                .alarmType(NEW_LIKE_ON_POST)
                .user(user)
                .fromUserId(3)
                .targetId(post.getId())
                .text(NEW_LIKE_ON_POST.getText())
                .build();

        List<AlarmResponse> alarms = Arrays.asList(alarm1.toResponse(), alarm2.toResponse());

        given(alarmService.getMyAlarms(any(), any())).willReturn(alarms);

        String url = "/api/v1/alarms";

        mockMvc.perform(get(url).with(csrf())
                        .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(pageRequest)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.content[0].id").value(alarm1.getId()))
                .andExpect(jsonPath("$.result.content[0].alarmType").value(alarm1.getAlarmType().name()))
                .andExpect(jsonPath("$.result.content[0].fromUserId").value(alarm1.getFromUserId()))
                .andExpect(jsonPath("$.result.content[0].targetId").value(alarm1.getTargetId()))
                .andExpect(jsonPath("$.result.content[0].text").value(alarm1.getAlarmType().getText()))
                .andDo(print());
    }
}

API 구현

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/alarms")
public class AlarmRestController {

    private final AlarmService alarmService;

    @GetMapping
    public Response<Page<AlarmResponse>> getAlarmsByUser(Authentication authentication) {
        String userName = authentication.getName();
        PageRequest pageable = PageRequest.of(0, 20, Sort.by("createdAt").descending());
        List<AlarmResponse> myAlarms = alarmService.getMyAlarms(userName, pageable);
        return Response.success(new PageImpl<>(myAlarms));
    }
}
  1. 알람을 확인하는 User의 정보를 받아옵니다.
  2. 해당 유저 정보와 페이징 세팅 정보를 service로 전달합니다.

Service

@Service
@RequiredArgsConstructor
public class AlarmService {

    private final AlarmRepository alarmRepository;
    private final ValidateService service;

    @Transactional
    public List<AlarmResponse> getMyAlarms(String userName, PageRequest pageable) {
        User user = service.validateGetUserByUserName(userName);

        Page<Alarm> alarms = alarmRepository.findAlarmsByUser(user, pageable);
        return alarms.stream()
                .map(Alarm::toResponse)
                .collect(Collectors.toList());
    }
}
  1. userName으로 유저의 정보를 받아옵니다.
  2. User Entity를 사용하여 모든 alarm 데이터를 받아옵니다.
  3. 받아온 alarm정보를 stream을 사용하여 List형태로 리턴합니다.

Repository

public interface AlarmRepository extends JpaRepository<Alarm, Integer> {
    Optional<Alarm> findAlarmByFromUserIdAndTargetIdAndAlarmType(Integer fromUserId, Integer targetId, AlarmType alarmType);
    Page<Alarm> findAlarmsByUser(User user, Pageable pageable);
}
  • Optiona<Alarm> findAlarmByFromUserIdAndTargetIdAndAlarmType: Userid, TargetId, AlarmType으로 Alarm 정보를 받아옵니다.
  • Page<Alarm> findAlarmsByUser(User user, Pageable pageable): User와 pageable을 사용하여 Alarm 정보를 받아옵니다.

Alarm 적용

  1. Like 추가시 알람 등록
  2. Comment Service

CommentService

@Service
@RequiredArgsConstructor
public class CommentService {

    private final CommentRepository commentRepository;
    private final AlarmRepository alarmRepository;
    private final ValidateService service;

    @Transactional
    public CommentWriteResponse writeComment(Integer postId, CommentRequest request, String userName) {
        ...
        commentRepository.save(savedComment);
        alarmRepository.save(Alarm.toEntity(user, post, NEW_COMMENT_ON_POST));
    return CommentWriteResponse.of(savedComment);
    }
}
  1. Comment를 작성하였다면(save), 해당 post, Comment를 작성한 user, alarmType을 이용하여 alarm을 저장합니다.

Like Service

@Service
@RequiredArgsConstructor
public class LikesService {

    private final LikesRepository likeRepository;
    private final AlarmRepository alarmRepository;
    private final ValidateService service;

    @Transactional
    public String increaseLike(Integer postId, String userName) {
        ...

        // 좋아요 기록이 있는지 확인합니다.
        if (optionalLike.isPresent()) {
            like = optionalLike.get();
        } else {
            ...
            // 좋아요 기록이 DB에 잘 저장되었다면, Alarm을 보냅니다.
            if (checkLike.isPresent()) {
                Alarm alarm = alarmRepository.findAlarmByFromUserIdAndTargetIdAndAlarmType(user.getId(), post.getId(), NEW_LIKE_ON_POST)
                        .orElse(alarmRepository.save(Alarm.toEntity(user, post, NEW_LIKE_ON_POST)));
            }
            return "좋아요를 눌렀습니다";
        }

        // 받아온 like 기록중 getDeletedAt의 정보를 확인합니다.
        if (like.getDeletedAt() == null) {
            ...
            // like를 soft delete 처리한 후 알람 기록도 삭제합니다.
            Optional<Alarm> alarm = alarmRepository.findAlarmByFromUserIdAndTargetIdAndAlarmType(user.getId(), post.getId(), NEW_LIKE_ON_POST);
            alarm.ifPresent(alarmRepository::delete);
            return "좋아요를 취소했습니다.";
        } else {
            ...
            // 다시 알람을 보냅니다.
            Alarm alarm = alarmRepository.findAlarmByFromUserIdAndTargetIdAndAlarmType(user.getId(), post.getId(), NEW_LIKE_ON_POST)
                    .orElse(alarmRepository.save(Alarm.toEntity(user, post, NEW_LIKE_ON_POST)));
            return "좋아요를 눌렀습니다";
        }
    }
}
  1. 좋아요 기록이 없는 상태라면 좋아요를 추가한 후, 해당 로직이 잘 실행되었는지 파악 한 다음 alamr 메서드를 진행합니다. 만약 alarm이 존재한다면 무시하고, 존재하지 않는다면 alarm 정보를 save 합니다.
  2. 좋아요 기록이 존재하는 상태에서 deleteAt의 정보가 null이라면 좋아요 기록을 삭제한 후, 알람 기록도 삭제합니다.
  3. 좋아요 기록이 존재하는 상태에서 deleteAt의 정보가 not null이라면 좋아요를 저장한 후, 다시 알람을 저장합니다.
반응형

'프로젝트 > Archive' 카테고리의 다른 글

[07] Like Api 개발  (0) 2023.01.05
[06] Comment Api 개발 - 2  (0) 2023.01.05
[06] Comment Api 개발 - 1  (1) 2023.01.04
[05] 리팩토링 - 2  (0) 2022.12.29
[05] 리팩토링 - 1  (0) 2022.12.29
코드플리