목표
📢 자신이 작성한 포스트를 볼 수 있는 마이 피드 기능 구현
접근방법
- User엔티티와 Post엔티티를 1대다 양방향 매핑을 한다.
- User엔티티에서 양방향 매핑된 myPosts필드를 Service에서 Page 처리, DTO변환 시킨다.
- Page 처리가 된 myPostsDTO를 컨트롤러에서 반환하여 응답한다.
핵심키워드
- 양방향 매핑
- User는 여러개의 포스트를 작성할 수 있다
- Post는 1명의 User에 의해 작성된다.
- List를 Page 처리
- User의 myPosts는 List 타입이므로 Page로 변환해야 한다.
- Security Filter 권한 (해결방안이 없다.)
TO-DO
마이피드 컨트롤러 테스트 구현
<@Test
@DisplayName("마이피드 목록 성공")
@WithMockUser
void post_my_SUCCESS() throws Exception {
when(postService.getMyPosts(any(),any())).thenReturn(Page.empty());
mockMvc.perform(get("/api/v1/posts/my")
.with(csrf()))
.andExpect(status().isOk());
}
@Test
@DisplayName("마이피드 목록 실패_로그인 실패")
@WithAnonymousUser
void post_my_FAILED_login() throws Exception {
when(postService.getMyPosts(any(),any()))
.thenThrow(new AppException(ErrorCode.INVALID_PERMISSION, ErrorCode.INVALID_PERMISSION.getMessage()));
mockMvc.perform(get("/api/v1/posts/my")
.with(csrf())
.param("page", "0")
.param("size", "20")
.param("sort", "createdAt,desc"))
.andExpect(status().isUnauthorized());
}
마이피드 컨트롤러 구현
@GetMapping("/my")
public ResponseEntity<Response> getMyPosts(@PageableDefault(size = 20) @SortDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, Authentication authentication) {
String userName = authentication.getName();
Page<PostGetResponse> postGetResponses = postService.getMyPosts(pageable, userName);
return ResponseEntity.ok().body(Response.of("SUCCESS", postGetResponses));
}
마이피드 서비스 구현
PostService 메서드 추가
public Page<PostGetResponse> getMyPosts(Pageable pageable, String userName) {
User findUser = AppUtil.findUser(userRepository, userName);
Page<Post> myPosts = postRepository.findByUser(pageable, findUser);
return PostGetResponse.listOf(myPosts);
}
PostRepository 메서드 추가
public interface PostRepository extends JpaRepository<Post,Long> {
Page<Post> findByUser(Pageable pageable, User user);
}
User 엔티티 필드 추가
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String userName;
private String password;
@OneToMany(mappedBy = "user")
private List<Post> myPosts;
public static User of(String userName, String password) {
return User.builder()
.userName(userName)
.password(password)
.build();
}
}
SecurityConfig 수정
@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()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(authenticationManager)
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
📖 회고
- 한 유저가 작성한 여러개의 포스트를 찾는 방법으로 처음에는 포스트의 userName을 비교하여 찾는 방식으로 접근했었다.
- jpa 양방향 매핑을 사용하면 위의 작업을 생략할 수 있다는 점을 알아내어 효율적으로 코드를 작성할 수 있었다.
- Security 권한에서 /api/v1/posts/my는 GET 메소드이지만 권한이 있는 사람만 접근할 수 있는 API이다. 하지만 SecurityConfig에서 GET메소드는 다 Permit으로 설정하여 제대로 예외처리가 되지 않는 문제가 발생했다. (아직 해결하지 못했다.)
- 알고보니 시큐리티 필터체인 쪽에서 일어난 문제가 아닌 jwt 필터에서 일어난 문제였다.