Skip to content

Commit

Permalink
feat: (#757) 신고 조치 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
tjdtls690 committed Oct 18, 2023
1 parent 88567a4 commit 4692375
Show file tree
Hide file tree
Showing 23 changed files with 843 additions and 262 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public enum MemberExceptionType implements ExceptionType {
INVALID_NICKNAME_LENGTH(800, "닉네임의 길이가 올바르지 않습니다."),
INVALID_NICKNAME_LETTER(801, "닉네임에 들어갈 수 없는 문자가 포함되어 있습니다."),
ALREADY_EXISTENT_NICKNAME(802, "이미 중복된 닉네임이 존재합니다."),
NONEXISTENT_MEMBER(803, "해당 회원이 존재하지 않습니다."),
NON_EXISTENT_MEMBER(803, "해당 회원이 존재하지 않습니다."),
INVALID_AGE(804, "존재할 수 없는 연령입니다."),
ALREADY_ASSIGNED_GENDER(805, "이미 성별이 할당되어 있습니다."),
ALREADY_ASSIGNED_BIRTH_YEAR(806, "이미 출생년도가 할당되어 있습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public Member register(final Member member) {
@Transactional(readOnly = true)
public Member findById(final Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException(MemberExceptionType.NONEXISTENT_MEMBER));
.orElseThrow(() -> new NotFoundException(MemberExceptionType.NON_EXISTENT_MEMBER));
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.votogether.domain.report.controller;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.report.dto.request.ReportActionRequest;
import com.votogether.domain.report.dto.request.ReportRequest;
import com.votogether.domain.report.service.ReportCommandService;
import com.votogether.global.jwt.Auth;
Expand All @@ -23,4 +24,10 @@ public ResponseEntity<Void> report(@Valid @RequestBody final ReportRequest reque
return ResponseEntity.ok().build();
}

@PostMapping("/reports/action/admin")
public ResponseEntity<Void> reportAction(@Valid @RequestBody final ReportActionRequest request) {
reportCommandService.reportAction(request);
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.votogether.domain.report.controller;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.report.dto.request.ReportActionRequest;
import com.votogether.domain.report.dto.request.ReportRequest;
import com.votogether.global.exception.ExceptionResponse;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -9,6 +10,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.Positive;
import org.springframework.http.ResponseEntity;

@Tag(name = "신고", description = "신고 API")
Expand All @@ -30,4 +32,25 @@ public interface ReportCommandControllerDocs {
})
ResponseEntity<Void> report(final ReportRequest request, final Member member);

@Operation(summary = "신고 조치", description = "신고를 조치한다.")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "신고 조치 성공"
),
@ApiResponse(
responseCode = "400",
description = """
1.신고 ID가 양의 정수가 아닌 경우
""",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 신고",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
)
})
ResponseEntity<Void> reportAction(final ReportActionRequest request);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.votogether.domain.report.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

@Schema(description = "신고 조치 요청")
public record ReportActionRequest(
@Schema(description = "신고 ID", example = "1")
@NotNull(message = "신고 ID는 빈 값일 수 없습니다.")
@Positive(message = "신고 ID는 양의 정수만 가능합니다.")
Long id,

@Schema(description = "신고 조치 여부", example = "true")
boolean hasAction
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum ReportExceptionType implements ExceptionType {
DUPLICATE_COMMENT_REPORT(1205, "하나의 댓글에 대해서 중복하여 신고할 수 없습니다."),
REPORT_MY_NICKNAME(1206, "자신의 닉네임은 신고할 수 없습니다."),
DUPLICATE_NICKNAME_REPORT(1207, "하나의 닉네임에 대해서 중복하여 신고할 수 없습니다."),
NOT_FOUND(1208, "신고가 존재하지 않습니다.")
;

private final int code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.votogether.domain.report.repository;

import com.votogether.domain.report.dto.ReportAggregateDto;
import com.votogether.domain.report.entity.vo.ReportType;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Pageable;

public interface ReportCustomRepository {

List<ReportAggregateDto> findReportsGroupedByReportTypeAndTargetId(final Pageable pageable);
List<ReportAggregateDto> findReportAggregateDtosByReportTypeAndTargetId(final Pageable pageable);

Optional<ReportAggregateDto> findReportAggregateDtoByReportTypeAndTargetId(ReportType reportType, Long targetId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.votogether.domain.report.dto.ReportAggregateDto;
import com.votogether.domain.report.entity.vo.ReportType;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
Expand All @@ -18,7 +20,7 @@ public class ReportCustomRepositoryImpl implements ReportCustomRepository {
private final JPAQueryFactory jpaQueryFactory;

@Override
public List<ReportAggregateDto> findReportsGroupedByReportTypeAndTargetId(final Pageable pageable) {
public List<ReportAggregateDto> findReportAggregateDtosByReportTypeAndTargetId(final Pageable pageable) {
return jpaQueryFactory.select(
Projections.constructor(
ReportAggregateDto.class,
Expand All @@ -37,4 +39,31 @@ public List<ReportAggregateDto> findReportsGroupedByReportTypeAndTargetId(final
.fetch();
}

@Override
public Optional<ReportAggregateDto> findReportAggregateDtoByReportTypeAndTargetId(
final ReportType reportType,
final Long targetId
) {
ReportAggregateDto result = jpaQueryFactory.select(
Projections.constructor(
ReportAggregateDto.class,
report.id.max(),
report.reportType,
report.targetId,
Expressions.stringTemplate("group_concat({0})", report.reason),
report.createdAt.max()
)
)
.from(report)
.where(
report.reportType.eq(reportType),
report.targetId.eq(targetId)
)
.groupBy(report.reportType, report.targetId)
.fetchOne();

return Optional.ofNullable(result);
}


}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.votogether.domain.report.service;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.report.dto.ReportAggregateDto;
import com.votogether.domain.report.dto.request.ReportActionRequest;
import com.votogether.domain.report.dto.request.ReportRequest;
import com.votogether.domain.report.entity.Report;
import com.votogether.domain.report.exception.ReportExceptionType;
import com.votogether.domain.report.repository.ReportRepository;
import com.votogether.domain.report.service.strategy.ReportActionProvider;
import com.votogether.domain.report.service.strategy.ReportStrategy;
import com.votogether.global.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -14,10 +20,29 @@
public class ReportCommandService {

private final ReportActionProvider reportActionProvider;
private final ReportRepository reportRepository;

public void report(final Member reporter, final ReportRequest request) {
final ReportStrategy reportStrategy = reportActionProvider.getStrategy(request.type());
reportStrategy.report(reporter, request);
}

public void reportAction(final ReportActionRequest request) {
final Report report = reportRepository.findById(request.id())
.orElseThrow(() -> new NotFoundException(ReportExceptionType.NOT_FOUND));

final ReportAggregateDto reportAggregateDto = reportRepository
.findReportAggregateDtoByReportTypeAndTargetId(report.getReportType(), report.getTargetId())
.orElseThrow(() -> new NotFoundException(ReportExceptionType.NOT_FOUND));

reportRepository.deleteAllWithReportTypeAndTargetIdInBatch(report.getReportType(), report.getTargetId());

if (!request.hasAction()) {
return;
}

final ReportStrategy strategy = reportActionProvider.getStrategy(reportAggregateDto.reportType());
strategy.reportAction(reportAggregateDto);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public ReportPageResponse getReports(final int page) {

final Pageable pageable = PageRequest.of(page, BASIC_PAGE_SIZE);
final List<ReportAggregateDto> reportAggregateDtos = reportRepository
.findReportsGroupedByReportTypeAndTargetId(pageable);
.findReportAggregateDtosByReportTypeAndTargetId(pageable);
final List<ReportResponse> reportResponses = parseReportResponses(reportAggregateDtos);

return ReportPageResponse.of(totalPageNumber, page, reportResponses);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.votogether.domain.report.service.strategy;

import com.votogether.domain.alarm.entity.ReportActionAlarm;
import com.votogether.domain.alarm.repository.ReportActionAlarmRepository;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.post.entity.comment.Comment;
import com.votogether.domain.post.exception.CommentExceptionType;
import com.votogether.domain.post.repository.CommentRepository;
import com.votogether.domain.report.dto.ReportAggregateDto;
import com.votogether.domain.report.dto.request.ReportRequest;
import com.votogether.domain.report.exception.ReportExceptionType;
import com.votogether.domain.report.repository.ReportRepository;
Expand All @@ -16,10 +19,9 @@
@Component
public class ReportCommentStrategy implements ReportStrategy {

private static final int NUMBER_OF_COMMENT_BLIND_BASED_REPORTS = 5;

private final CommentRepository commentRepository;
private final ReportRepository reportRepository;
private final ReportActionAlarmRepository reportActionAlarmRepository;

@Override
public void report(final Member reporter, final ReportRequest request) {
Expand All @@ -28,7 +30,6 @@ public void report(final Member reporter, final ReportRequest request) {
validateComment(reporter, request, reportedComment);

saveReport(reporter, request, reportRepository);
blindComment(request, reportedComment);
}

private void validateComment(
Expand All @@ -46,13 +47,6 @@ private void validateComment(
);
}

private void blindComment(final ReportRequest request, final Comment reportedComment) {
final int reportCount = reportRepository.countByReportTypeAndTargetId(request.type(), request.id());
if (reportCount >= NUMBER_OF_COMMENT_BLIND_BASED_REPORTS) {
reportedComment.blind();
}
}

private void validateHiddenComment(final Comment comment) {
if (comment.isHidden()) {
throw new BadRequestException(CommentExceptionType.IS_HIDDEN);
Expand All @@ -72,4 +66,21 @@ public String parseTarget(final Long targetId) {
return reportedComment.getContent();
}

@Override
public void reportAction(final ReportAggregateDto reportAggregateDto) {
final Comment comment = commentRepository.findById(reportAggregateDto.targetId())
.orElseThrow(() -> new NotFoundException(CommentExceptionType.NOT_FOUND));

final ReportActionAlarm reportActionAlarm = ReportActionAlarm.builder()
.member(comment.getWriter())
.reportType(reportAggregateDto.reportType())
.target(comment.getContent())
.reasons(reportAggregateDto.reasons())
.isChecked(false)
.build();

reportActionAlarmRepository.save(reportActionAlarm);
comment.blind();
}

}
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
package com.votogether.domain.report.service.strategy;

import com.votogether.domain.alarm.entity.ReportActionAlarm;
import com.votogether.domain.alarm.repository.ReportActionAlarmRepository;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.exception.MemberExceptionType;
import com.votogether.domain.member.repository.MemberRepository;
import com.votogether.domain.report.dto.ReportAggregateDto;
import com.votogether.domain.report.dto.request.ReportRequest;
import com.votogether.domain.report.entity.vo.ReportType;
import com.votogether.domain.report.exception.ReportExceptionType;
import com.votogether.domain.report.repository.ReportRepository;
import com.votogether.global.exception.BadRequestException;
import com.votogether.global.exception.NotFoundException;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class ReportNicknameStrategy implements ReportStrategy {

private static final int NUMBER_OF_NICKNAME_CHANGE_REPORTS = 3;

private final MemberRepository memberRepository;
private final ReportRepository reportRepository;
private final ReportActionAlarmRepository reportActionAlarmRepository;

@Override
public void report(final Member reporter, final ReportRequest request) {
final Member reportedMember = memberRepository.findById(request.id())
.orElseThrow(() -> new NotFoundException(MemberExceptionType.NONEXISTENT_MEMBER));
validateMemberExistence(request);
validateNickname(reporter, request);

saveReport(reporter, request, reportRepository);
changeNicknameByReport(reportedMember, request);
}

private void validateMemberExistence(final ReportRequest request) {
final Optional<Member> memberById = memberRepository.findById(request.id());
if (memberById.isEmpty()) {
throw new NotFoundException(MemberExceptionType.NON_EXISTENT_MEMBER);
}
}

private void validateNickname(final Member reporter, final ReportRequest request) {
Expand All @@ -48,19 +54,28 @@ private void validateMyNickname(final Member reporter, final ReportRequest reque
}
}

private void changeNicknameByReport(final Member reportedMember, final ReportRequest request) {
final int reportCount = reportRepository.countByReportTypeAndTargetId(request.type(), reportedMember.getId());
if (reportCount >= NUMBER_OF_NICKNAME_CHANGE_REPORTS) {
reportedMember.changeNicknameByReport();
reportRepository.deleteAllWithReportTypeAndTargetIdInBatch(ReportType.NICKNAME, request.id());
}
}

@Override
public String parseTarget(final Long targetId) {
final Member reportedMember = memberRepository.findById(targetId)
.orElseThrow(() -> new NotFoundException(MemberExceptionType.NONEXISTENT_MEMBER));
.orElseThrow(() -> new NotFoundException(MemberExceptionType.NON_EXISTENT_MEMBER));
return reportedMember.getNickname();
}

@Override
public void reportAction(final ReportAggregateDto reportAggregateDto) {
final Member reportedMember = memberRepository.findById(reportAggregateDto.targetId())
.orElseThrow(() -> new NotFoundException(MemberExceptionType.NON_EXISTENT_MEMBER));

final ReportActionAlarm reportActionAlarm = ReportActionAlarm.builder()
.member(reportedMember)
.reportType(reportAggregateDto.reportType())
.target(reportedMember.getNickname())
.reasons(reportAggregateDto.reasons())
.isChecked(false)
.build();

reportActionAlarmRepository.save(reportActionAlarm);
reportedMember.changeNicknameByReport();
}

}
Loading

0 comments on commit 4692375

Please sign in to comment.