좋아요 기능을 넣기 전에 번외로 관리자 페이지를 만들어 보고 싶어 졌다.
관리자 페이지를 만들어 보자.
우선 관리자 권한이 있어야한다.
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);
}
}
테스트 결과가 이상이 없으므로 검증이 완료 되었다.
검색을 해보면
댓글이나 회원 검색도 동일하게 진행하면 된다.