SNS 제작 (마이피드)

개발연습생log·2023년 1월 9일
0

SNS 제작

목록 보기
11/15
post-thumbnail

목표

📢 자신이 작성한 포스트를 볼 수 있는 마이 피드 기능 구현

접근방법

  • User엔티티와 Post엔티티를 1대다 양방향 매핑을 한다.
  • User엔티티에서 양방향 매핑된 myPosts필드를 Service에서 Page 처리, DTO변환 시킨다.
  • Page 처리가 된 myPostsDTO를 컨트롤러에서 반환하여 응답한다.

핵심키워드

  • 양방향 매핑
    • User는 여러개의 포스트를 작성할 수 있다
    • Post는 1명의 User에 의해 작성된다.
  • List를 Page 처리
    • User의 myPosts는 List 타입이므로 Page로 변환해야 한다.
  • Security Filter 권한 (해결방안이 없다.)

TO-DO

  • 마이피드 컨트롤러 테스트 구현
  • 마이피드 컨트롤러 구현
  • 마이피드 서비스 구현
  • SecurityConfig 수정

마이피드 컨트롤러 테스트 구현

  • 테스트 메서드 추가
<@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);
        //PostGetResponseDto로 변환 후 리턴
        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 필터에서 일어난 문제였다.
profile
주니어 개발자를 향해서..

0개의 댓글