72일차 (2) - 관리자 이거나 본인일 때만 댓글 수정 - 삭제 가능하게 수정

Yohan·2024년 6월 4일
0

코딩기록

목록 보기
111/156
post-custom-banner

서버에서는 인터셉터를 이용해서 403상태 코드로만 막고 나머지는 모두 UI(js)에서 처리

ApiAuthInterceptor

  • 비동기 통신시 인가 처리를 담당
    -> 동기 통신은 sendRedirect를 통해 팅기게 하면되지만 비동기는 팅기면 안되므로 이런식으로 처리
@Configuration
@Slf4j
public class ApiAuthInterceptor implements HandlerInterceptor {

    // 비동기 통신시 인가 처리

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession session = request.getSession();

        if (!LoginUtil.isLoggedIn(session)) {
            // 로그인하지 않은 경우 403상태코드를 전송
            log.info("인가되지 않은 접근입니다. : {}", request.getRequestURI());
            response.sendError(403);
            return false;
        }
        return true;
    }
}

InterceptorConfig

  • REST API 인가 처리 인터셉터 등록
// 만들어 놓은 인터셉터들을 스프링 컨텍스트에 등록하는 설정 파일
@Configuration // 설정파일을 의미
@RequiredArgsConstructor
public class InterceptorConfig implements WebMvcConfigurer {

    private final AfterLoginInterceptor afterLoginInterceptor;
    private final BoardInterceptor boardInterceptor;
    private final AutoLoginInterceptor autoLoginInterceptor;
    private final ApiAuthInterceptor apiAuthInterceptor;

    // 설정 메서드
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 로그인 인터셉터 등록
        registry
                .addInterceptor(afterLoginInterceptor)
                // 해당 인터셉터가 동작할 URL을 설정
                .addPathPatterns("/members/sign-up", "/members/sign-in")
                ;
        // 게시판 인터셉터 등록
        registry
                .addInterceptor(boardInterceptor)
                .addPathPatterns("/board/*")
                .excludePathPatterns("/board/list", "/board/detail"
                        , "/board/like", "/board/dislike")
                ;
        // 자동로그인 인터셉터 등록
        registry
                .addInterceptor(autoLoginInterceptor)
                .addPathPatterns("/**"); // 어디로 들어오든 자동로그인
        ↓ 추가
        // REST API 인가 처리 인터셉터 등록
        registry
                .addInterceptor(apiAuthInterceptor)
                .addPathPatterns("/api/v1/**")
                .excludePathPatterns("/api/v1/replies/*/page/*");
    }
}

api.js

  • 인터넷에서 정보를 가져오는 함수, 우리가 주소를 주면 그 주소에서 정보를 가져오고 만약 권한이 없다면 경고창을 띄우고 로그인 페이지로 보냄
// 서버 api에 호출을 수행하는 함수
export const callApi = async (url, method = 'GET', payload = null) => {

  const requestInfo = {
    method,
  };

if (payload) {
  requestInfo.headers = {'content-type': 'application/json'};
  requestInfo.body = JSON.stringify(payload);
}

  const res = await fetch(url, requestInfo);

if (res.status === 403) {
  alert('접근 권한이 없습니다.');
  window.location.href = '/members/sign-in';
  return null;
}

  return await res.json();
};

deleteReply, getReply, modifyReply, postReply.js 리팩토링

  • callApi를 이용하여 리팩토링

detail.jsp에서 비로그인시 댓글창 안보이게 막음

<c:if test="${login == null}">
                                    <a href="/members/sign-in">댓글은 로그인 후 작성해주세요!!</a>
                                </c:if>
                                <c:if test="${login != null}">
                                    <div class="row">
                                        <div class="col-md-9">
                                            <div class="form-group">
                                                <label for="newReplyText" hidden>댓글 내용</label>
                                                <textarea rows="3" id="newReplyText" name="replyText" class="form-control"
                                                    placeholder="댓글을 입력해주세요."></textarea>
                                            </div>
                                        </div>
                                        <div class="col-md-3">
                                            <div class="form-group">
                                                <label for="newReplyWriter" hidden>댓글 작성자</label>
                                                <input id="newReplyWriter" name="replyWriter" type="text" value="${login.nickname}"
                                                    class="form-control" placeholder="작성자 이름" style="margin-bottom: 6px;" readonly>
                                                <button id="replyAddBtn" type="button" class="btn btn-dark form-control">등록
                                                </button>
                                            </div>
                                        </div>
                                    </div>
                                </c:if>

1. 수정, 삭제시 DB에 적용

-> 계정이 필요

Reply

  • account 추가
// db와 1대1 매칭되는 객체
public class Reply {

    private long replyNo;
    @Setter
    private String replyText;
    private String replyWriter;
    private LocalDateTime replyDate;
    private long boardNo;
    private String account;

}

ReplyMapper.xml

  • save시 account 추가
    -> 계정이
    <insert id="save">
        INSERT INTO tbl_reply
        (reply_text, reply_writer, board_no, account)
        VALUES
        (#{replyText}, #{replyWriter}, #{boardNo}, #{account})
    </insert>

ReplyService

  • 쿼리에 추가하였기 때문에 댓글 입력시 현재 로그인한 계정명을 보내줘야됨
// 댓글 입력
    public boolean register(ReplyPostDto dto, HttpSession session) {
        Reply reply = Reply.builder()
                .replyText(dto.getText())
                .replyWriter(dto.getAuthor())
                .boardNo(dto.getBno())
                ↓ 추가
                .account(LoginUtil.getLoggedInUserAccount(session))
                .build();

        boolean flag = replyMapper.save(reply);
        if (flag) log.info("댓글 등록 성공 - {}", dto);
        else log.warn("댓글 등록 실패");
        return flag;
    }

2. 관리자이거나 내가 쓴 댓글일 경우만 조건부로 수정, 삭제 버튼 나오게함

  • 로그인한 회원 권한, 로그인한 회원 계정명, 해당 댓글의 계정명이 필요!

ReplyDetailDto

  • 화면에서 댓글 작성자 계정명이 필요하므로 화면으로 응답하는 dto에 accout 추가
public class ReplyDetailDto {

    private long rno;
    private String text;
    private String writer;

//    @JsonFormat(pattern = "yyyy년 MM월 dd일 HH:mm")
    private LocalDateTime createAt;
    private String account; // 댓글 작성자 계정명

    // 엔터티를 DTO로 변환하는 생성자
    public ReplyDetailDto(Reply r) {
        this.rno = r.getReplyNo();
        this.text = r.getReplyText();
        this.writer = r.getReplyWriter();
        this.createAt = r.getReplyDate();
        this.account = r.getAccount();
    }
}

ReplyListDto

  • 수정, 삭제 요청시 ReplyListDto로 감싸서 화면에 보내게 되는데 이 때 로그인한 회원 권한, 로그인한 회원 계정명을 얻을 수 있는 정보를 가지고 있는 LoginUserInfoDto를 ReplyListDto에 추가
public class ReplyListDto {

    // ReplyDetailDto의 형태
    // ReplyDetailDto를 필드로 만들면서 배열의 이름이 생김 (replies)
    /*
        [
            {}, {}, {}
        ]
     */
    @Setter
    private LoginUserInfoDto loginUser;

    private PageMaker pageInfo;
    private List<ReplyDetailDto> replies;
}

getReply.js

  • replies, loginUser는 ReplyListDto를 통해 받음
  • tag를 백틱으로 찢어서 조건부로 렌더링
function appendReplies({ replies, loginUser }) {
    // 댓글 목록 렌더링
    console.log(replies);
    let tag = '';
    if (replies && replies.length > 0) {
        replies.forEach(({ rno, writer, text, createAt, account: replyAccount }) => {
            tag += `
        <div id='replyContent' class='card-body' data-reply-id='${rno}'>
            <div class='row user-block'>
                <span class='col-md-3'>
                    <b>${writer}</b>
                </span>
                <span class='offset-md-6 col-md-3 text-right'><b>${getRelativeTime(
                createAt
            )}</b></span>
            </div><br>
            <div class='row'>
                <div class='col-md-9'>${text}</div>
                <div class='col-md-3 text-right'>
                `;

            // 관리자이거나 내가 쓴 댓글일 경우만 조건부 렌더링
            // 로그인한 회원 권한, 로그인한 회원 계정명, 해당 댓글의 계정명
            if (loginUser) { // 로그인 유저가 존재하면~
                const {auth, account: loginUserAccount} = loginUser;

                if (auth === 'ADMIN' || replyAccount === loginUserAccount) {
                    tag += `<a id='replyModBtn' class='btn btn-sm btn-outline-dark' data-bs-toggle='modal' data-bs-target='#replyModifyModal'>수정</a>&nbsp;
                            <a id='replyDelBtn' class='btn btn-sm btn-outline-dark' href='#'>삭제</a>
                `;
                }
            }


            tag += `</div>
            </div>
        </div>
        `;
        });
    } else {
        tag = `<div id='replyContent' class='card-body'>댓글이 아직 없습니다! ㅠㅠ</div>`;
    }
    document.getElementById('replyData').innerHTML += tag;
    console.log('append replies');

    // 로드된 댓글 수 업데이트
    loadedReplies += replies.length;
}
profile
백엔드 개발자
post-custom-banner

0개의 댓글