본문 바로가기

Project

[Project] Lv_7 스케줄 프로젝트(심화)


 

요구사항

생성한 일정에 댓글을 남길 수 있습니다.

  • ✅ 댓글과 일정은 연관관계를 가집니다.

댓글을 저장, 조회, 수정, 삭제할 수 있습니다.

댓글은 아래와 같은 필드를 가집니다.

  • ✅ 댓글 내용, 작성일, 수정일, 유저 고유 식별자, 일정 고유 식별자 필드
  • ✅ 작성일, 수정일 필드는 JPA Auditing을 활용하여 적용합니다.

ERD와 schedule.sql에도 일정 엔티티에 대한 내용 추가합니다.

 


 

요구 구현

요구 구현에 앞서 추가된 Comment에 대한 API 명세서와 ERD 변경

더보기

 API 명세서




 

 

 

ERD

 

 

 

CommentService

더보기
@RestController
@RequestMapping("/api/comments")
@RequiredArgsConstructor
public class CommentController {

    private final CommentService commentService;

    @PostMapping
    public ResponseEntity<CreateCommentResponseDto> createComment(@RequestBody CreateCommentRequestDto requestDto) {
        CreateCommentResponseDto createCommentResponseDto = commentService.saveComment(requestDto);
        return new ResponseEntity<>(createCommentResponseDto, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<FindCommentResponseDto> findCommentById(@PathVariable Long id, HttpServletRequest request) {
        FindCommentResponseDto findCommentResponseDto = commentService.findCommentById(id, request);

        return new ResponseEntity<>(findCommentResponseDto, HttpStatus.OK);
    }

    @PatchMapping("/{id}")
    public ResponseEntity<UpdateCommentResponseDto> updateComment(
            @PathVariable Long id,
            @RequestBody UpdateCommentRequestDto requestDto,
            HttpServletRequest request
    ) {
        UpdateCommentResponseDto updateCommentResponseDto = commentService.updateComment(id, requestDto, request);

        return new ResponseEntity<>(updateCommentResponseDto, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteComment(
            @PathVariable Long id,
            HttpServletRequest request
    ) {
        commentService.deleteComment(id, request);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
  • HttpServletRequest : HTTP 요청에 대한 모든 정보를 담고 있는 객체입니다.
    • session 정보를 사용하기 위해 Serive 계층으로 넘겨주었습니다.

 

 

CommentService

더보기
@Service
@AllArgsConstructor
public class CommentService {

    private final CommentRepository commentRepository;
    private final UserRepository userRepository;
    private final ScheduleRepository scheduleRepository;

    public CreateCommentResponseDto saveComment(CreateCommentRequestDto requestDto) {
        Long userId = requestDto.getUserId();
        User user = userRepository.findByIdOrElseThrow(userId);

        Long scheduleId = requestDto.getScheduleId();
        Schedule schedule = scheduleRepository.findByIdOrElseThrow(scheduleId);

        Comment comment = new Comment(requestDto.getContent(), user, schedule);

        Comment savedComment = commentRepository.save(comment);

        return new CreateCommentResponseDto(savedComment);
    }

    public FindCommentResponseDto findCommentById(Long id, HttpServletRequest request) {
        Comment comment = getAuthorizedComment(id, request);

        return new FindCommentResponseDto(comment);
    }

    public UpdateCommentResponseDto updateComment (Long id, UpdateCommentRequestDto requestDto, HttpServletRequest request) {
        Comment comment = getAuthorizedComment(id, request);

        comment.updateContent(requestDto.getContent());

        Comment savedComment = commentRepository.save(comment);

        return new UpdateCommentResponseDto(savedComment);
    }

    public void deleteComment (Long id, HttpServletRequest request) {
        Comment comment = getAuthorizedComment(id, request);

        commentRepository.delete(comment);
    }

    private Comment getAuthorizedComment(Long id, HttpServletRequest request) {
        Comment comment = commentRepository.findByIdOrElseThrow(id);
        Long userId = comment.getUser().getId();

        HttpSession session = request.getSession();

        Long logInUserId = (Long) session.getAttribute("login-userId");

        if(!userId.equals(logInUserId)) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "This user is incorrect.");

        return comment;
    }
}
  • getAuthorizedComment() : 댓글에 저장된 유저 ID와 현재 로그인한 유저의 ID가 동일한지 검증 후, 맞다면 댓글 Entity를 반환해주는 메서드입니다.
    • 로직이 반복되어, 메서드화 시킨 것입니다.
  • Controller로부터 넘겨받은 HttpServletRequest 객체에 담긴 쿠키(JSESSIONID)를 통해, 서버는 그 세션 ID에 매핑된 세션 객체를 찾아서 반환합니다.
    • 세션에 저장되어 있던 `"login-userId"`라는 키의 값을 꺼냅니다.

 

 

CommentRepository

더보기
public interface CommentRepository extends JpaRepository<Comment, Long> {
    default Comment findByIdOrElseThrow(Long id) {
        return findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + id)
        );
    }
}

 

 

Comment

더보기
@Entity
@Getter
public class Comment extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String content;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne
    private User user;

    @JoinColumn(name = "schedule_id", nullable = false)
    @ManyToOne
    private Schedule schedule;

    public Comment(String content, User user, Schedule schedule) {
        this.content = content;
        this.user = user;
        this.schedule = schedule;
    }

    public Comment() {

    }

    public void updateContent(String content) {
        this.content = content;
    }
}

 

 

CreateCommentRequestDto

더보기
@Getter
@AllArgsConstructor
public class CreateCommentRequestDto {

    @NotNull
    private Long userId;

    @NotNull
    private Long scheduleId;

    @NotNull
    private String content;
}

 

 

CreateCommentResponseDto

더보기
@Getter
public class CreateCommentResponseDto {
    private final Long id;
    private final String content;
    private final Long userId;
    private final Long scheduleId;
    private final Date createdAt;
    private final Date updatedAt;

    public CreateCommentResponseDto(Comment comment) {
        this.id = comment.getId();
        this.content = comment.getContent();
        this.userId = comment.getUser().getId();
        this.scheduleId = comment.getSchedule().getId();
        this.createdAt = comment.getCreatedAt();
        this.updatedAt = comment.getUpdatedAt();
    }
}

 

 

FindCommentResponseDto

더보기
@Getter
@AllArgsConstructor
public class FindCommentResponseDto {

    private final Long id;
    private final String content;
    private final Long userId;
    private final Long scheduleId;
    private final Date createdAt;
    private final Date updateedAt;

    public FindCommentResponseDto(Comment comment) {
        this.id = comment.getId();
        this.userId = comment.getUser().getId();
        this.scheduleId = comment.getSchedule().getId();
        this.content = comment.getContent();
        this.createdAt = comment.getCreatedAt();
        this.updateedAt = comment.getUpdatedAt();
    }
}

 

 

 

UpdateCommentRequestDto

더보기
@Getter
@AllArgsConstructor
public class UpdateCommentRequestDto {

    private String content;
}

 

 

UpdateCommentResponseDto

더보기
@Getter
public class UpdateCommentResponseDto {
    private final Long id;
    private final Long userId;
    private final Long scheduleId;
    private final String content;
    private final Date updateAt;

    public UpdateCommentResponseDto(Comment comment) {
        this.id = comment.getId();
        this.userId = comment.getUser().getId();
        this.scheduleId = comment.getSchedule().getId();
        this.content = comment.getContent();
        this.updateAt = comment.getUpdatedAt();
    }
}

 

 


 

 

트러블 슈팅

(문제)

  • 다음은 댓글을 조회하는 API를 사용했을 때, 해당 댓글을 작성한 유저의 아이디로 로그인하여 조회를 하였지만, 유저가 맞지 않다는 오류가 발생하였습니다.
    • 로그인을 하여 Cookie 값에 `JSESIONID=40EB...`가 잘 저장되어있습니다.

 

 

(문제)

  • `request.getAttribute(..)` : HTTP 요청 내에서 서버 사이드에서 setAttribute로 설정한 값만 접근 가능한 상태입니다.
    • `request.getAttribute("xxx")`는 클라이언트가 보낸 데이터가 아니라, 같은 서버 코드 내(하나의 요청)에서 `request.setAttribute("xxx")`로 직접 넣은 값만 가져올 수 있다는 의미입니다.
  • HTTP 요청 처리 중에만 잠깐 사용하고 사라져 버리는 방식입니다. 즉, 새로운 요청이 들어오면 이전 값은 없어진다는 의미입니다.
    • 이전 요청에서 `request.getAttribute("xxx")`를 하고
    • 다음 요청에서 `request.setAttribute("xxx")`를 하면, `null`값이 나옵니다.
    • 즉, 올바른 값을 가져오려면 하나의 HTTP 요청이 끝나기 전에 값을 가져와야 합니다.
  • 저는 서로 다른 요청에서 값을 저장하고 불러왔기 때문에, null값이 떠서 생긴 오류입니다.

 

 

(해결)

  • `session.getAttribute(...)는 클라이언트가 보내온 JSESSIONID에 해당하는 서버 세션에서 값을 가져오는 방식입니다.
  • 이 세션은 이후 요청에서 값이 유지되기 때문에 정확한 값을 꺼내올 수 있습니다.

 

  • 정상적으로 실행된 모습