이번에는 어드민 페이지를 추가하였다.
우선 정지에 관련해서 처리할 수 있는 기능을 추가하였는데,
더 세부적인 기능은 추후 추가할 예정이다.
오늘은 유저, 게시판, 댓글에 대해 정지목록을 조회할 수 있고, 정지를 풀어주거나, 삭제할 수 있다.
각각 정지가 당하면, 유저는 권한이 바뀌어서 다른 곳에 접속을 못하게 되고, 게시판이나 댓글의 경우
조회가 안되도록 설정하였다.
그 외에는 3개다 비슷한 로직이기 때문에, 유저에 관해 설명하겠다.
Member
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
@Column(nullable = false, unique = true)
private String username;
@JsonIgnore
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String name;
@Enumerated(EnumType.STRING)
private Authority authority;
private boolean isReported;
@Builder
public Member(Long id, String username, String password, String name, Authority authority) {
this.id = id;
this.username = username;
this.password = password;
this.name = name;
this.authority = authority;
this.isReported = false;
}
public void modify(String password,String name) {
this.password = password;
this.name = name;
onPreUpdate();
}
public void isReportedStatus(){
isReported = true;
authority = Authority.ROLE_SUSPENSION;
}
public void unLockedReportedStatus(){
isReported = false;
authority = Authority.ROLE_USER;
}
}
신고가 누적될 경우에 권한이 사라지고, 운영자가 다시 풀어질 경우 다시 원래 권한을 갖게 된다.
AdminController
@Api(value = "Admin Controller", tags = "Admin")
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/admin/manages")
public class AdminController {
private final AdminService adminService;
@ApiOperation(value = "정지 유저 관리", notes = "정지된 유저를 관리합니다.")
@ResponseStatus(HttpStatus.OK)
@GetMapping("/members")
public Response findAllReportedMember(){
return Response.success(adminService.findAllReportedMember());
}
@ApiOperation(value = "신고된 유저 정지 해제", notes = "신고된 유저를 정지 해제시킵니다.")
@ResponseStatus(HttpStatus.OK)
@PostMapping("/members/{id}")
public Response unlockMember(@PathVariable Long id) {
return Response.success(adminService.unlockMember(id));
}
@ApiOperation(value = "신고된 유저 삭제", notes = "신고된 유저를 삭제 시킵니다.")
@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/members/{id}")
public Response deleteReportedMember(@PathVariable Long id) {
return Response.success(adminService.deleteReportedMember(id));
}
@ApiOperation(value = "게시판 관리", notes = "게시판을 관리합니다.")
@ResponseStatus(HttpStatus.OK)
@GetMapping("/boards")
public Response findAllReportedBoards() {
return Response.success(adminService.findAllReportedBoards());
}
@ApiOperation(value = "신고된 게시판 정지 해제", notes = "신고된 게시판을 정지 해제시킵니다.")
@ResponseStatus(HttpStatus.OK)
@PostMapping("/boards/{id}")
public Response unlockBoard(@PathVariable Long id) {
return Response.success(adminService.unlockBoard(id));
}
@ApiOperation(value = "신고된 게시글 삭제", notes = "신고된 게시글을 삭제 시킵니다.")
@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/boards/{id}")
public Response deleteReportedBoard(@PathVariable Long id) {
return Response.success(adminService.deleteReportedBoard(id));
}
@ApiOperation(value = "댓글 관리", notes = "댓글을 관리합니다.")
@ResponseStatus(HttpStatus.OK)
@GetMapping("/comments")
public Response findAllReportedComments() {
return Response.success(adminService.findAllReportedComments());
}
@ApiOperation(value = "신고된 댓글 정지 해제", notes = "신고된 댓글을 정지 해제시킵니다.")
@ResponseStatus(HttpStatus.OK)
@PostMapping("/comments/{id}")
public Response unlockComment(@PathVariable Long id) {
return Response.success(adminService.unlockComment(id));
}
@ApiOperation(value = "신고된 댓글 삭제", notes = "신고된 댓글을 삭제 시킵니다.")
@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/comments/{id}")
public Response deleteReportedComment(@PathVariable Long id) {
return Response.success(adminService.deleteReportedComment(id));
}
}
로직자체가 단순하다.
정지된 목록조회, 정지 풀기, 정지된 대상 삭제가 이루어져 있다.
AdminService
@RequiredArgsConstructor
@Service
public class AdminService {
private static final String UNLOCK ="신고가 해제되었습니다.";
private static final String DELETE ="삭제가 되었습니다.";
private final MemberRepository memberRepository;
private final MemberReportRepository memberReportRepository;
private final BoardRepository boardRepository;
private final BoardReportRepository boardReportRepository;
private final CommentRepository commentRepository;
private final CommentReportRepository commentReportRepository;
@Transactional(readOnly = true)
public List<ReportedMemberFindAllResponseDto> findAllReportedMember() {
List<Member> members = memberRepository.findAllByReportedIsTrue();
return members.stream()
.map(new ReportedMemberFindAllResponseDto()::toDto)
.collect(Collectors.toList());
}
@Transactional
public String unlockMember(Long id){
Member member = memberRepository.findById(id).orElseThrow(MemberNotFoundException::new);
validateUnlockMember(member);
unlockMember(member);
return UNLOCK;
}
private void validateUnlockMember(Member member) {
if (!member.isReported()) {
throw new NotReportedException();
}
}
private void unlockMember(Member member) {
member.unLockedReportedStatus();
memberReportRepository.deleteAllByReportedMember(member);
}
@Transactional
public String deleteReportedMember(Long id){
Member member = memberRepository.findById(id).orElseThrow(MemberNotFoundException::new);
validateUnlockMember(member);
memberRepository.delete(member);
return DELETE;
}
@Transactional(readOnly = true)
public List<ReportedBoardFindAllResponseDto> findAllReportedBoards() {
List<Board> boards = boardRepository.findAllByReportedIsTrue();
return boards.stream()
.map(new ReportedBoardFindAllResponseDto()::toDto)
.collect(Collectors.toList());
}
@Transactional
public String unlockBoard(Long id) {
Board board = boardRepository.findById(id).orElseThrow(BoardNotFoundException::new);
validateUnlockBoard(board);
unlockBoard(board);
return UNLOCK;
}
private void validateUnlockBoard(Board board) {
if (!board.isReported()) {
throw new NotReportedException();
}
}
private void unlockBoard(Board board) {
board.unlockReportedStatus();
boardReportRepository.deleteAllByReportedBoard(board);
}
@Transactional
public String deleteReportedBoard(Long id){
Board board = boardRepository.findById(id).orElseThrow(BoardNotFoundException::new);
validateUnlockBoard(board);
boardRepository.delete(board);
return DELETE;
}
@Transactional(readOnly = true)
public List<ReportedCommentFindAllResponseDto> findAllReportedComments() {
List<Comment> comments = commentRepository.findAllByReportedIsTrue();
return comments.stream()
.map(new ReportedCommentFindAllResponseDto()::toDto)
.collect(Collectors.toList());
}
@Transactional
public String unlockComment(Long id) {
Comment comment = commentRepository.findById(id).orElseThrow(CommentNotFoundException::new);
validateUnlockComment(comment);
unlockComment(comment);
return UNLOCK;
}
private void validateUnlockComment(Comment comment) {
if (!comment.isReported()) {
throw new NotReportedException();
}
}
private void unlockComment(Comment comment) {
comment.unlockReportedStatus();
commentReportRepository.deleteAllByReportedComment(comment);
}
@Transactional
public String deleteReportedComment(Long id){
Comment comment = commentRepository.findById(id).orElseThrow(CommentNotFoundException::new);
validateUnlockComment(comment);
commentRepository.delete(comment);
return DELETE;
}
}
유저, 게시판, 댓글 모두 비슷한 로직을 갖고 있다.
신고된 유저목록의 경우 비밀번호를 제외한 정보를 가지고 온다.
신고를 풀어줄 대상의 경우 현재 신고누적상태인지 확인 후 해제가 실행된다.
이후 신고기록을 모두 삭제해 준다.
신고된 유저 삭제의 경우에는
단순하게 삭제를 진행해 주었다.
신고기록을 삭제하지 않은 이유는 MemberReport에서 이미 OnDelete설정을 CASCADE로 해 두었기 때문에
유저 삭제 시 기록도 삭제되기 때문이다.
이제 테스트 코드를 작성해 보자.
AdminControllerTest
@ExtendWith(MockitoExtension.class)
public class AdminControllerTest {
@InjectMocks
AdminController adminController;
@Mock
AdminService adminService;
MockMvc mockMvc;
ObjectMapper objectMapper;
@BeforeEach
public void beforeEach() {
mockMvc = MockMvcBuilders.standaloneSetup(adminController).build();
}
@Test
public void 정지된_유저목록조회_테스트() throws Exception {
// given
// when, then
mockMvc.perform(
get("/api/admin/manages/members"))
.andExpect(status().isOk());
verify(adminService).findAllReportedMember();
}
@Test
public void 정지된_게시글조회_테스트() throws Exception {
// given
// when, then
mockMvc.perform(
get("/api/admin/manages/boards"))
.andExpect(status().isOk());
verify(adminService).findAllReportedBoards();
}
@Test
public void 정지된_댓글조회_테스트() throws Exception {
// given
// when, then
mockMvc.perform(
get("/api/admin/manages/comments"))
.andExpect(status().isOk());
verify(adminService).findAllReportedComments();
}
@Test
public void 신고된유저_정지해제_테스트() throws Exception {
// given
Long id = 1L;
// when, then
mockMvc.perform(
post("/api/admin/manages/members/{id}", id))
.andExpect(status().isOk());
verify(adminService).unlockMember(id);
}
@Test
public void 신고된게시글_정지해제_테스트() throws Exception {
// given
Long id = 1L;
// when, then
mockMvc.perform(
post("/api/admin/manages/boards/{id}", id))
.andExpect(status().isOk());
verify(adminService).unlockBoard(id);
}
@Test
public void 신고된댓글_정지해제_테스트() throws Exception {
// given
Long id = 1L;
// when, then
mockMvc.perform(
post("/api/admin/manages/comments/{id}", id))
.andExpect(status().isOk());
verify(adminService).unlockComment(id);
}
@Test
public void 신고된유저_삭제_테스트() throws Exception {
// given
Long id = 1L;
// when, then
mockMvc.perform(
delete("/api/admin/manages/members/{id}", id))
.andExpect(status().isOk());
verify(adminService).deleteReportedMember(id);
}
@Test
public void 신고된게시글_삭제_테스트() throws Exception {
// given
Long id = 1L;
// when, then
mockMvc.perform(
delete("/api/admin/manages/boards/{id}", id))
.andExpect(status().isOk());
verify(adminService).deleteReportedBoard(id);
}
@Test
public void 신고된댓글_삭제_테스트() throws Exception {
// given
Long id = 1L;
// when, then
mockMvc.perform(
delete("/api/admin/manages/comments/{id}", id))
.andExpect(status().isOk());
verify(adminService).deleteReportedComment(id);
}
}
AdminServiceTest
@ExtendWith(MockitoExtension.class)
public class AdminServiceTest {
@InjectMocks
AdminService adminService;
@Mock
MemberRepository memberRepository;
@Mock
BoardRepository boardRepository;
@Mock
MemberReportRepository memberReportRepository;
@Mock
BoardReportRepository boardReportRepository;
@Mock
CommentRepository commentRepository;
@Mock
CommentReportRepository commentReportRepository;
@Test
public void 신고된유저_목록조회_테스트() {
// given
List<Member> members = new ArrayList<>();
members.add(createMember());
given(memberRepository.findAllByReportedIsTrue()).willReturn(members);
// when
List<ReportedMemberFindAllResponseDto> result = adminService.findAllReportedMember();
// then
assertThat(result.size()).isEqualTo(1);
}
@Test
public void 신고된유저_정지해제_테스트() {
// given
Member member = createMember();
member.isReportedStatus();
given(memberRepository.findById(anyLong())).willReturn(Optional.of(member));
// when
String result = adminService.unlockMember(anyLong());
// then
assertThat(result).isEqualTo("신고가 해제되었습니다.");
assertThat(member.isReported()).isFalse();
verify(memberReportRepository).deleteAllByReportedMember(member);
}
@Test
public void 신고된유저_삭제_테스트(){
//given
Member member = createMember();
member.isReportedStatus();
given(memberRepository.findById(anyLong())).willReturn(Optional.of(member));
String result = adminService.deleteReportedMember(anyLong());
// then
assertThat(result).isEqualTo("삭제가 되었습니다.");
verify(memberRepository).delete(any());
}
@Test
void 신고된게시글_목록조회_테스트() {
// given
List<Board> boards = new ArrayList<>();
boards.add(createBoard());
given(boardRepository.findAllByReportedIsTrue()).willReturn(boards);
// when
List<ReportedBoardFindAllResponseDto> result = adminService.findAllReportedBoards();
// then
assertThat(result.size()).isEqualTo(1);
}
@Test
void 신고된게시글_정지해제_테스트() {
// given
Board board = createBoard();
board.isReportedStatus();
given(boardRepository.findById(anyLong())).willReturn(Optional.of(board));
// when
String result = adminService.unlockBoard(anyLong());
// then
assertThat(result).isEqualTo("신고가 해제되었습니다.");
assertThat(board.isReported()).isFalse();
verify(boardReportRepository).deleteAllByReportedBoard(board);
}
@Test
public void 신고된게시글_삭제_테스트(){
//given
Board board = createBoard();
board.isReportedStatus();
given(boardRepository.findById(anyLong())).willReturn(Optional.of(board));
String result = adminService.deleteReportedBoard(anyLong());
// then
assertThat(result).isEqualTo("삭제가 되었습니다.");
verify(boardRepository).delete(any());
}
@Test
void 신고된댓글_목록조회_테스트() {
// given
List<Comment> comments = new ArrayList<>();
comments.add(new Comment("content",createMember(),createBoard()));
given(commentRepository.findAllByReportedIsTrue()).willReturn(comments);
// when
List<ReportedCommentFindAllResponseDto> result = adminService.findAllReportedComments();
// then
assertThat(result.size()).isEqualTo(1);
}
@Test
void 신고된댓글_정지해제_테스트() {
// given
Comment comment = new Comment("content", createMember(), createBoard());
comment.isReportedStatus();
given(commentRepository.findById(anyLong())).willReturn(Optional.of(comment));
// when
String result = adminService.unlockComment(anyLong());
// then
assertThat(result).isEqualTo("신고가 해제되었습니다.");
assertThat(comment.isReported()).isFalse();
verify(commentReportRepository).deleteAllByReportedComment(comment);
}
@Test
public void 신고된댓글_삭제_테스트(){
//given
Comment comment = new Comment("content", createMember(), createBoard());
comment.isReportedStatus();
given(commentRepository.findById(anyLong())).willReturn(Optional.of(comment));
String result = adminService.deleteReportedComment(anyLong());
// then
assertThat(result).isEqualTo("삭제가 되었습니다.");
verify(commentRepository).delete(any());
}
}
테스트의 결과를 살펴보자.
잘 작동하는 것을 볼 수 있다.
이제 포스트맨을 사용하여 확인해 보자.
유저, 게시판, 댓글의 경우 모두 비슷하기 때문에 유저의 경우만 확인해 보자.
admin페이지의 경우 권한이 운영자만 확인할 수 있도록 설정하였기 때문에, DB에서 임의의 한 유저의 권한을 운영자로 바꾸고 테스트하였다.
1번 유저가 운영자로 바뀌었다.
현재 7번이 신고 누적상태이므로 신고누적멤버 조회 시 7번이 조회되어야 한다.
잘 나오는 것을 볼 수 있다.
이제 이 유저의 정지를 풀어보자.
신고 기록과 해당 유저의 권한 및 신고상태가 돌아왔다.
이제 다시 7번을 신고누적을 한 다음에 해당 유저를 삭제하고, 그때 기록도 삭제되는지 확인해 보자.
해당 유저가 다시 신고가 누적되었다.
이제 삭제를 진행해 보자.
우선 유저가 삭제되었다.
신고당한 유저가 삭제되니, OnDelete를 걸어둔 신고기록도 같이 삭제되었다.
원래 유저만 진행하려 했는데, 게시판까지 진행하였다.
8번 유저가 1번 게시글을 작성하고 조회한 결과이다.
전체조회, 단건조회에서 잘 나오는 것을 확인할 수 있다.
이제 신고를 누적시켜 보자.
신고가 누적된 모습이다.
이제 신고를 풀어보자.
신고 내역이 삭제되고, 신고상태가 바뀐 모습이다.
이제 다시 누적신고를 하고, 삭제해 보자.
다시 신고된 모습이다.
이제 삭제해 보자.
잘 삭제된 것을 볼 수 있다.
스웨거에 추가된 api를 확인해 보자.
스웨거 역시 잘 되는 것을 확인할 수 있다.
자세한 코드는 아래 깃허브를 통해 볼 수 있다.
https://github.com/kimtaesoo99/community
'프로젝트 > 커뮤니티' 카테고리의 다른 글
[프로젝트] 커뮤니티 REST API 서버만들기 #10 도메인 테스트 작성 (0) | 2023.02.14 |
---|---|
[프로젝트] 커뮤니티 REST API 서버만들기 #9 카테고리 API 만들기 (0) | 2023.01.29 |
[프로젝트] 커뮤니티 REST API 서버만들기 #7 Report API 만들기 (0) | 2023.01.27 |
[프로젝트] 커뮤니티 REST API 서버만들기 #6 Comment API만들기 (0) | 2023.01.25 |
[프로젝트] 커뮤니티 REST API 서버만들기 #5 게시판 부가기능 추가 (0) | 2023.01.24 |