728x90
이전글
https://chordplaylist.tistory.com/235
Alarm API
- 알람 리스트 조회
- 알람 조회는 모든 회원이 권한을 가집니다.
- 특정 User의 알람 목록을 받아옵니다.
- 특정 포스트에 새 댓글이 달리고, 좋아요가 눌리면 알람이 등록됩니다.
- 알람 목록은 Pagination으로 받아옵니다. (Pageable 사용)
- 한 페이지당 default 피드 개수는 20개
- 총 페이지 갯수가 표시
- 작성날짜 기준으로 최신순으로 sortAlarm Entity
- 앤드 포인트
GET/alarms
- 최신 순으로 20개씩 표시 (Pageable 사용)
- 알람 리스트 조회 시 응답 필드
id
: 알람 IDalarmType
:알람 타입 (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));
}
}
- 알람을 확인하는 User의 정보를 받아옵니다.
- 해당 유저 정보와 페이징 세팅 정보를 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());
}
}
- userName으로 유저의 정보를 받아옵니다.
- User Entity를 사용하여 모든 alarm 데이터를 받아옵니다.
- 받아온 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 적용
- Like 추가시 알람 등록
- 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);
}
}
- 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 "좋아요를 눌렀습니다";
}
}
}
- 좋아요 기록이 없는 상태라면 좋아요를 추가한 후, 해당 로직이 잘 실행되었는지 파악 한 다음 alamr 메서드를 진행합니다. 만약 alarm이 존재한다면 무시하고, 존재하지 않는다면 alarm 정보를 save 합니다.
- 좋아요 기록이 존재하는 상태에서 deleteAt의 정보가 null이라면 좋아요 기록을 삭제한 후, 알람 기록도 삭제합니다.
- 좋아요 기록이 존재하는 상태에서 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 |