📢 알람 기능을 구현하자.
/alarms
테이블
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",
}
]
}
}
<@Test
@DisplayName("알람 조회 성공")
@WithMockUser
void alarm_SUCCESS() throws Exception {
mockMvc.perform(get("/api/v1/alarms")
.with(csrf())
.param("page", "0")
.param("size", "10")
.param("sort", "createdAt,desc"))
.andExpect(status().isOk());
ArgumentCaptor<Pageable> pageableArgumentCaptor = ArgumentCaptor.forClass(Pageable.class);
verify(alarmService).getAlarms(pageableArgumentCaptor.capture(),any());
PageRequest pageRequest = (PageRequest) pageableArgumentCaptor.getValue();
assertEquals(0, pageRequest.getPageNumber());
assertEquals(10,pageRequest.getPageSize());
assertEquals(Sort.by(Sort.Direction.DESC,"createdAt"), pageRequest.getSort());
}
@Test
@DisplayName("알람 조회 실패 : 유저가 없는 경우")
@WithMockUser
void alarm_FAIL_user() throws Exception {
when(alarmService.getAlarms(any(), any()))
.thenThrow(new AppException(ErrorCode.USERNAME_NOT_FOUND, ErrorCode.USERNAME_NOT_FOUND.getMessage()));
mockMvc.perform(get("/api/v1/alarms")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isNotFound());
}
@RestController
@RequestMapping("/api/v1/alarms")
@RequiredArgsConstructor
public class AlarmController {
private final AlarmService alarmService;
@GetMapping
public ResponseEntity<Response> getAlarms(@PageableDefault(size = 20) @SortDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, Authentication authentication) {
String userName = authentication.getName();
Page<AlarmResponse> alarmResponses = alarmService.getAlarms(pageable, userName);
return ResponseEntity.ok().body(Response.of("SUCCESS", alarmResponses));
}
}
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class AlarmResponse {
private Long id;
private String alarmType;
private Long fromUserId;
private Long targetId;
private String text;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd' 'HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createdAt;
public static Page<AlarmResponse> listOf(Page<Alarm> alarms) {
Page<AlarmResponse> alarmResponses = alarms.map(m -> AlarmResponse.builder()
.id(m.getId())
.alarmType(m.getAlarmType())
.fromUserId(m.getFromUser().getUserId())
.targetId(m.getTargetPost().getId())
.text(m.getText())
.createdAt(m.getCreatedAt())
.build());
return alarmResponses;
}
}
@Service
@RequiredArgsConstructor
public class AlarmService {
private final UserRepository userRepository;
private final AlarmRepository alarmRepository;
public Page<AlarmResponse> getAlarms(Pageable pageable, String userName) {
//유저체크
User findUser = AppUtil.findUser(userRepository, userName);
//알람 가져오기
Page<Alarm> alarmPage = alarmRepository.findByUser(pageable, findUser);
//알람 dto 변환 후 리턴
Page<AlarmResponse> alarmResponses = AlarmResponse.listOf(alarmPage);
return alarmResponses;
}
}
public class AlarmUtil {
public static Alarm saveAlarm(AlarmRepository alarmRepository, AlarmType alarmType, User fromUser, Post post) {
Alarm alarm = Alarm.of(alarmType, fromUser, post);
return alarmRepository.save(alarm);
}
}
public interface AlarmRepository extends JpaRepository<Alarm, Long> {
Page<Alarm> findByUser(Pageable pageable, User user);
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@Entity
public class Alarm extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String alarmType;
@ManyToOne
@JoinColumn(name = "userId")
private User user;
@ManyToOne
@JoinColumn(name = "fromUserID")
private User fromUser;
@ManyToOne
@JoinColumn(name = "targetId")
private Post targetPost;
private String text;
public static Alarm of(AlarmType alarmType, User user, Post post) {
return Alarm.builder()
.alarmType(alarmType.name())
.user(post.getUser())
.fromUser(user)
.targetPost(post)
.text(alarmType.getMessage())
.build();
}
}
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final AuthenticationManager authenticationManager;
private final JwtFilter jwtFilter;
private final String[] PERMIT_URL = {
"/api/v1/hello",
"/api/v1/users/join",
"/api/v1/users/login"
};
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic().disable()
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers(PERMIT_URL).permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/**").authenticated()
.antMatchers(HttpMethod.PUT, "/api/v1/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/v1/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/v1/posts/my").authenticated()
.antMatchers(HttpMethod.GET,"/api/v1/alarms").authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(authenticationManager)
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}