[Spring] 관리자 페이지 검색하기 - 블로그 제작(6)

merci·2023년 2월 16일
0

블로그 제작 V1

목록 보기
6/9
post-thumbnail
post-custom-banner

좋아요 기능을 넣기 전에 번외로 관리자 페이지를 만들어 보고 싶어 졌다.
관리자 페이지를 만들어 보자.

관리자 기능 추가

우선 관리자 권한이 있어야한다.

create table user_tb (
    id int auto_increment primary key,
    username varchar not null unique,
    password varchar not null,
    email varchar not null,
    profile varchar,
    role varchar,  // 권한 추가
    created_at timestamp not null
);

관리자 계정을 하나 추가했다

insert into user_tb (username, password, email, PROFILE, role, created_at) 
values ('admin','admin','admin@nate.com', '/images/default_profile.png', 'ADMIN', now());

간단하게 관리자 로그인폼을 만든다

관리자 계정으로만 로그인이 가능한 폼이므로 권한을 검사한다.
권한없이 관리자 페이지로 가려고 하는 경우 관리자 로그인창으로 보낸다.

    @GetMapping("/admin")
    public String admin() {
        User principal = (User) session.getAttribute("principal");
        if (principal == null) {
            return "redirect:/admin/loginForm";
        }
        if (!principal.getRole().equals("ADMIN")) {
            return "redirect:/admin/loginForm";
        }
        return "admin/user";
    }

관리자 로그인 정보를 얻기 위한 dto - AdminReqDto

    @Getter
    @Setter
    public static class AdminReqDto{
        private String username;
        private String password;
    }

로그인

    @PostMapping("/admin/login")
    public String loginAdmin(AdminReqDto adminReqDto, Model model) {
        if (adminReqDto.getUsername() == null || adminReqDto.getUsername().isEmpty()) {
            throw new CustomException("아이디를 입력해주세요");
        }
        if (adminReqDto.getPassword() == null || adminReqDto.getPassword().isEmpty()) {
            throw new CustomException("패스워드를 입력해주세요");
        }
        User admin = userRepository.findByUsernameAndPassword(
        		adminReqDto.getUsername(), adminReqDto.getPassword());
        if (admin == null) {
            throw new CustomException("아이디 또는 비밀번호가 다릅니다.");
        }
        if (!admin.getRole().equals("ADMIN")) {
            throw new CustomException("관리자 계정이 아닙니다.");
        }
        session.setAttribute("principal", admin);
        return "redirect:/admin/user";
    }

관리자로 로그인을 성공하면 기본페이지가 회원관리 페이지로 가게 했다.
/admin/user 에서 모델에 회원정보를 담아서 뷰로 전달한다.

관리자 페이지의 경우 왼쪽에 간단한 탭을 만들었다.

부트스트랩으로 만들었고 현재선택된 페이지는 active 속성으로 파랗게 표현한다.

    <div class="ms-4 mt-4">
        <ul class="nav flex-column nav-pills">
            <li class="nav-item">
                <a class="nav-link" aria-current="page" href="/admin/user">회원관리</a>
            </li>
            <li class="nav-item">
                <a class="nav-link active" href="/admin/board">게시글 관리</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="/admin/reply">댓글 관리</a>
            </li>
        </ul>
    </div>

회원관리 페이지

회원들의 간략한 정보들이 나온다.
일단 간단하게 정보들과 회원정보를 삭제할 수 있게 만들었다.
페이지 매니저 기능을 만들어서 회원의 권한을 승격시키는 버튼도 추가하면 좋을것 같다.

삭제 버튼 만들기

<script>
    function deleteUser(id) {
        $.ajax({
            type: "delete",
            url: "/admin/user/" + id + "/delete",
            dataType: "json"
        }).done((res) => {
            alert(res.msg);
            $('#user-' + id).remove();
        }).fail((err) => {
            alert(err.responseJSON.msg);
        });
    }
</script>

ajax로 간단하게 삭제한다.
삭제할시 화면에서도 remove() 를 이용해서 제거한다.

회원관리 페이지는 간단하게 하고 게시글 관리와 댓글 관리에 추가적인 기능을 넣었다.

게시글 페이지


게시글 관리 페이지에서는 검색 기능을 넣어서 조건에 맞는 게시글만 볼 수 있게 만들었다.

    <div class="d-flex">
        <div class="me-2 " style="width:150px">
            <select class="form-select" onchange="changeDropdown(this)">
                <option id="title" value="title">글 제목</option>
                <option id="content" value="content">글 내용</option>
                <option id="writer" value="username">작성자</option>
            </select>
        </div>

        <div class="input-group">
            <div class="form-outline">
                <input id="search-input" type="search" name="title" 
                       class="form-control" placeholder="검색"
                 onkeypress="if(event.keyCode=='13'){event.preventDefault(); searchEvt();}" />
            </div>
            <button id="search-button" class="btn btn-primary" onclick="searchBoard()">
                <i class="fas fa-search"></i>
            </button>
        </div>
    </div>


select box 를 이용해서 옵션을 선택하면 onchange="changeDropdown(this)" 가 변화를 감지해서 등록된 함수를 실행한다.

<input onkeypress="if(event.keyCode=='13'){event.preventDefault(); searchEvt();}" />

옵션을 이용하면 엔터를 눌렀을때 등록한 함수를 실행한다.

changeDropdown(this) 는 value를 input 태그의 name 속성에 넣는다.
만약 작성자를 선택하고 검색창에 입력을 하게 되면 username='검색내용' 이 된다.

    function changeDropdown(obj) {
        let DrondwonValue = $(obj).val();
        $('#search-input').attr('name', DrondwonValue);
    }

엔터를 누르면 검색아이콘을 클릭했을때의 함수가 실행된다.

    function searchEvt() {
        searchBoard();
    };



검색을 하는 함수는

    function searchBoard() {
        $('.remove-table').remove();
        let keyword;
        if ($('#search-input').attr('name') === 'title') {
            keyword = `title` + `=` + $('#search-input').val()
        }
        if ($('#search-input').attr('name') === 'content') {
            keyword = `content` + `=` + $('#search-input').val()
        }
        if ($('#search-input').attr('name') === 'username') {
            keyword = `username` + `=` + $('#search-input').val()
        }
        // console.log(keyword);
        $.ajax({
            type: "get",
            url: "/admin/board/search?" + keyword,
            dataType: "json"
        }).done((res) => {
            render(res.data);
            // console.log(res.data);
        }).fail((err) => {
            alert(err.reponseJSON.msg);
        });
        $('#search-input').val("");
    }

    function render(boards) {
        // console.log(boards[1].id);
        boards.forEach(board => {
            let id = board.id;
            let title = board.title;
            let content = board.content;
            let username = board.username;
            let createdAt = board.createdAtToString;
            let el = `
              <tr id="board-`+ id + `" class="remove-table">
                  <td>`+ id + `</td>
                  <td>`+ title + `</td>
                  <td class="my-title-ellipsis">`+ content + `</td>
                  <td>`+ username + `</td>
                  <td>`+ createdAt + `</td>
                  <td><button class="btn btn-danger" 
					  token template-punctuation string">`+ id + `)">삭제하기</button></td>
              </tr>
            `;
            $("#board-table").append(el);
        });
    }

기존 테이블의 로우를 먼저 제거한다.

<tr id="board-${board.id}" class="remove-table">

이후 검색창의 데이터를 ajax로 쿼리스트링에 넣어서 검색을 한다.

 url: "/admin/board/search?" + keyword,

검색을 완료하면 검색창을 다시 공백으로 만든다.

$('#search-input').val("");



검색의 결과를 다시 비동기통신으로 받아서 화면에 그려야 하는데
컨트롤러에서는 모델에 List 를 넣어서 응답하므로 boards로 받아서 foreach로 순환시킨다.

boards.forEach(board => {  }

이후 append() 를 이용해서 테이블 로우부터 다시 그린다.

remove-table는 재 검색시 다시 삭제하기 위해서 넣는다.

<tr id="board-`+ id + `" class="remove-table">


검색을 받을 컨트롤러를 만들어 보자.

쿼리스트링을 받을 dto 를 만든다. - AdminReqSearchAjaxDto

    @Getter
    @Setter
    public static class AdminReqSearchAjaxDto{
        private String title;
        private String content;
        private String username;
    }

컨트롤러

    @GetMapping("/admin/board/search")
    public ResponseEntity<?> searchBoard(AdminReqSearchAjaxDto aDto) {
        User admin = (User) session.getAttribute("principal");
        if (!admin.getRole().equals("ADMIN")) {
            throw new CustomApiException("관리자 계정이 아닙니다.");
        }
        if (aDto.getTitle() == null || aDto.getTitle().isEmpty()) {
            aDto.setTitle("");
        }
        if (aDto.getContent() == null || aDto.getContent().isEmpty()) {
            aDto.setContent("");
        }
        if (aDto.getUsername() == null || aDto.getUsername().isEmpty()) {
            aDto.setUsername("");
        } else {
            Integer num = userRepository.findByUsernameWithAdmin(aDto.getUsername());
            System.out.println("테스트 : "+num);
            if (num == null) {
                aDto.setUsername("결과없음");
            } else {
                aDto.setUsername(String.valueOf(num));
            }
        }
        List<AdminBoardSearchResqDto> boardSeartList = 
        			boardRepository.findBoardByAdminWithSearch(
                		aDto.getTitle(),
                		aDto.getContent(),
                		aDto.getUsername());
        // json 에 데이터 넣어줘야함
        return new ResponseEntity<>(new ResponseDto<>(1, "", boardSeartList),
                HttpStatus.OK);
    }

검색 결과를 조회하는 메소드 findBoardByAdminWithSearch()에 null 을 넣게 되면 DBMS 에서 익셉션이 발생하므로 컨트롤러에 입력된 값이 null 이라면 "" 공백을 넣는다.

findByUsernameWithAdmin()으로 유저의 이름으로 유저아이디를 얻어낸다.
처음부터 한번에 쿼리를 만들수도 있는데 이것도 간단해서 이렇게 만들어 봤다.

사용되는 쿼리는

<select id="findBoardByAdminWithSearch" resultType="shop.mtcoding.blog2.dto.admin.AdminResp$AdminBoardSearchResqDto">
   select 
      b.id, 
      b.title, 
      b.content, 
      (select username from user_tb where id = b.user_id ) username,
      b.created_at
      from board_tb b 
      where title regexp #{title} 
      and content regexp #{content} 
      and user_id regexp #{userId} 
</select>

이후 검색 결과를 받는 dto를 만든다. -AdminBoardSearchResqDto

    @Getter
    @Setter
    public static class {
        private Integer id;
        private String title;
        private String content;
        private String username;
        private Timestamp createdAt;

        public String getCreatedAtToString() {
            return DateUtil.format(createdAt);
        }
    }

만든 쿼리를 먼저 테스트한다.

@MybatisTest
public class BoardRepositoryTest {
    
    @Autowired
    private BoardRepository boardRepository;

    ObjectMapper om = new ObjectMapper();

    @Test
    public void findBoardByAdminWithSearch_test() throws Exception{
        List<AdminBoardSearchResqDto> boardList = 
        		boardRepository.findBoardByAdminWithSearch("","두번째","");
        String tes = om.writeValueAsString(boardList);
        System.out.println("테스트 : "+ tes);
    }
}

문제가 없으면 컨트롤러 테스트도 한다.

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class AdminController {
    @Autowired
    private MockMvc mvc;
    private MockHttpSession session;

    @BeforeEach  // 모든 테스트전 한번 실행 -> 세션 생성
    public void setUp(){
        User mockUser = new User();
        mockUser.setId(1);
        mockUser.setUsername("admin");
        mockUser.setPassword("admin");
        mockUser.setRole("ADMIN");
        mockUser.setEmail("admin@nate.com");

        session = new MockHttpSession();
        session.setAttribute("principal", mockUser);
    }
       @Test
    public void searchBoard_test() throws Exception{
        String test = "title=두번째";

        ResultActions rs = mvc.perform(get("/admin/board/search?"+test)
        .session(session));

        String a = rs.andReturn().getResponse().getContentAsString();
        System.out.println("테스트 : "+a);
    }
}

테스트 결과가 이상이 없으므로 검증이 완료 되었다.

검색을 해보면

댓글이나 회원 검색도 동일하게 진행하면 된다.

profile
작은것부터
post-custom-banner

0개의 댓글