Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.clokey.code.GlobalBaseSuccessCode;
import org.clokey.domain.report.dto.request.ReportCreateRequest;
import org.clokey.domain.report.dto.response.ReportCreateResponse;
import org.clokey.domain.report.dto.response.ReportedCheckResponse;
import org.clokey.domain.report.service.ReportService;
import org.clokey.response.BaseResponse;
import org.springframework.web.bind.annotation.*;
Expand All @@ -28,4 +29,15 @@ public BaseResponse<ReportCreateResponse> createNewReport(
ReportCreateResponse response = reportService.createReport(reportCreatRequest);
return BaseResponse.onSuccess(GlobalBaseSuccessCode.CREATED, response);
}

@GetMapping("/received")
@Operation(
operationId = "Report_checkReportReceived",
summary = "사용자에 접수된 신고 확인",
description = "사용자에 접수된 신고(미확인 상태)가 있는지 확인합니다.")
public BaseResponse<ReportedCheckResponse> checkReportReceived() {
ReportedCheckResponse response = reportService.checkReportReceived();

return BaseResponse.onSuccess(GlobalBaseSuccessCode.OK, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.clokey.domain.report.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import org.clokey.report.enums.TargetType;

public record ReportedCheckResponse(
@Schema(description = "UNCHECKED 상태의 신고가 존재하는지", example = "true") boolean isReported,
@Schema(description = "신고의 타입", example = "TargetType.HISTORY") TargetType targetType) {

public static ReportedCheckResponse of(boolean isReported, TargetType targetType) {
return new ReportedCheckResponse(isReported, targetType);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.clokey.domain.report.repository;

import java.util.Optional;
import org.clokey.report.entity.Report;
import org.clokey.report.enums.ReportStatus;
import org.clokey.report.enums.TargetType;
Expand All @@ -8,4 +9,7 @@
public interface ReportRepository extends JpaRepository<Report, Long> {
boolean existsByTargetTypeAndTargetIdAndReportStatusIsNot(
TargetType targetType, Long TargetId, ReportStatus reportStatus);

Optional<Report> findTopByReported_IdAndReportStatusOrderByCreatedAtDesc(
Long memberId, ReportStatus reportStatus);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import org.clokey.domain.report.dto.request.ReportCreateRequest;
import org.clokey.domain.report.dto.response.ReportCreateResponse;
import org.clokey.domain.report.dto.response.ReportedCheckResponse;

public interface ReportService {

ReportCreateResponse createReport(ReportCreateRequest request);

ReportedCheckResponse checkReportReceived();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.clokey.domain.history.repository.HistoryRepository;
import org.clokey.domain.report.dto.request.ReportCreateRequest;
import org.clokey.domain.report.dto.response.ReportCreateResponse;
import org.clokey.domain.report.dto.response.ReportedCheckResponse;
import org.clokey.domain.report.exception.ReportErrorCode;
import org.clokey.domain.report.repository.ReportRepository;
import org.clokey.exception.BaseCustomException;
Expand All @@ -32,14 +33,15 @@ public class ReportServiceImpl implements ReportService {
@Override
@Transactional
public ReportCreateResponse createReport(ReportCreateRequest request) {
final Member reporter = memberUtil.getCurrentMember();
validateTargetExists(request.targetType(), request.targetId());
Member reporter = memberUtil.getCurrentMember();
Member reported = getReportedMember(request.targetType(), request.targetId());
validateDuplicateReport(request);

Report report =
Report.createReport(
request.targetId(),
reporter,
reported,
request.targetType(),
request.reportReason(),
request.content());
Expand All @@ -49,6 +51,37 @@ public ReportCreateResponse createReport(ReportCreateRequest request) {
return ReportCreateResponse.from(report);
}

@Override
public ReportedCheckResponse checkReportReceived() {
Member member = memberUtil.getCurrentMember();

Report report =
reportRepository
.findTopByReported_IdAndReportStatusOrderByCreatedAtDesc(
member.getId(), ReportStatus.UNCHECKED)
.orElse(null);

if (report != null) {
return ReportedCheckResponse.of(true, report.getTargetType());
}

return ReportedCheckResponse.of(false, null);
}

private Member getReportedMember(TargetType targetType, Long targetId) {
if (targetType.equals(TargetType.COMMENT)) {
return commentRepository
.findById(targetId)
.orElseThrow(() -> new BaseCustomException(CommentErrorCode.COMMENT_NOT_FOUND))
.getMember();
} else {
return historyRepository
.findById(targetId)
.orElseThrow(() -> new BaseCustomException(HistoryErrorCode.HISTORY_NOT_FOUND))
.getMember();
}
}

private void validateDuplicateReport(ReportCreateRequest request) {
boolean exists =
reportRepository.existsByTargetTypeAndTargetIdAndReportStatusIsNot(
Expand All @@ -58,16 +91,4 @@ private void validateDuplicateReport(ReportCreateRequest request) {
throw new BaseCustomException(ReportErrorCode.REPORT_DUPLICATED);
}
}

private void validateTargetExists(TargetType targetType, Long targetId) {
if (targetType.equals(TargetType.COMMENT)) {
if (!commentRepository.existsById(targetId)) {
throw new BaseCustomException(CommentErrorCode.COMMENT_NOT_FOUND);
}
} else {
if (!historyRepository.existsById(targetId)) {
throw new BaseCustomException(HistoryErrorCode.HISTORY_NOT_FOUND);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.clokey.domain.report.controller;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.clokey.domain.report.dto.request.ReportCreateRequest;
import org.clokey.domain.report.dto.response.ReportCreateResponse;
import org.clokey.domain.report.dto.response.ReportedCheckResponse;
import org.clokey.domain.report.service.ReportService;
import org.clokey.report.enums.ReportReason;
import org.clokey.report.enums.TargetType;
Expand Down Expand Up @@ -149,4 +151,27 @@ class 신고_생성_요청_시 {
.value("신고 사유는 비워둘 수 없습니다."));
}
}

@Nested
class 접수된_미확인_신고_확인_요청_시 {

@Test
void 유효한_요청이면_최신_UNCHECKED_상태의_신고_존재_여부를_반환한다() throws Exception {
// given
ReportedCheckResponse response = new ReportedCheckResponse(true, TargetType.COMMENT);
given(reportService.checkReportReceived()).willReturn(response);

// when
ResultActions perform = mockMvc.perform(get("/reports/received"));

// then
perform.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.isSuccess").value(true))
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value("COMMON200"))
.andExpect(MockMvcResultMatchers.jsonPath("$.result.isReported").value(true))
.andExpect(
MockMvcResultMatchers.jsonPath("$.result.targetType")
.value(TargetType.COMMENT.name()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.mockito.BDDMockito.given;

import java.time.LocalDate;
import java.util.List;
import org.clokey.IntegrationTest;
import org.clokey.comment.entitiy.Comment;
import org.clokey.domain.comment.exception.CommentErrorCode;
Expand All @@ -14,6 +15,7 @@
import org.clokey.domain.history.repository.SituationRepository;
import org.clokey.domain.member.repository.MemberRepository;
import org.clokey.domain.report.dto.request.ReportCreateRequest;
import org.clokey.domain.report.dto.response.ReportedCheckResponse;
import org.clokey.domain.report.exception.ReportErrorCode;
import org.clokey.domain.report.repository.ReportRepository;
import org.clokey.exception.BaseCustomException;
Expand Down Expand Up @@ -87,11 +89,13 @@ void setUp() {
.extracting(
"targetId",
"reporter.id",
"reported.id",
"targetType",
"reportReason",
"reportStatus",
"content")
.containsExactly(
1L,
1L,
1L,
TargetType.HISTORY,
Expand Down Expand Up @@ -182,4 +186,66 @@ void setUp() {
.hasMessage(ReportErrorCode.REPORT_DUPLICATED.getMessage());
}
}

@Nested
class 접수된_미확인_신고_조회할_때 {

@BeforeEach
void setUp() {
Member member1 =
Member.createMember(
"testEmail1",
"testClokeyId1",
"testNickName1",
OauthInfo.createOauthInfo("testOauthId1", OauthProvider.KAKAO));
Member member2 =
Member.createMember(
"testEmail2",
"testClokeyId2",
"testNickName2",
OauthInfo.createOauthInfo("testOauthId2", OauthProvider.KAKAO));
memberRepository.saveAll(List.of(member1, member2));
given(memberUtil.getCurrentMember()).willReturn(member2);

Situation situation = Situation.createSituation("testSituation");
situationRepository.save(situation);

History history1 =
History.createHistory(
LocalDate.of(2026, 1, 1), "testContent1", member2, situation);
historyRepository.save(history1);

Report report =
Report.createReport(
1L,
member1,
member2,
TargetType.HISTORY,
ReportReason.VIOLENT,
"Test Report");
reportRepository.save(report);
}

@Test
void 유효한_요청이면_미확인_신고_여부를_반환한다() {
// when & then
ReportedCheckResponse response = reportService.checkReportReceived();

assertThat(response.isReported()).isTrue();
assertThat(response.targetType()).isEqualTo(TargetType.HISTORY);
}

@Test
void 접수된_신고가_없으면_false를_반환한다() {
// given
Member member1 = memberRepository.findById(1L).orElse(null);
given(memberUtil.getCurrentMember()).willReturn(member1);

// when & then
ReportedCheckResponse response = reportService.checkReportReceived();

assertThat(response.isReported()).isFalse();
assertThat(response.targetType()).isEqualTo(null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ public class Report extends BaseEntity {
@NotNull private Long targetId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
@JoinColumn(name = "reporter_member_id")
@NotNull
private Member reporter;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reported_member_id")
@NotNull
private Member reported;

@Enumerated(EnumType.STRING)
@NotNull
private TargetType targetType;
Expand All @@ -46,12 +51,14 @@ public class Report extends BaseEntity {
private Report(
Long targetId,
Member reporter,
Member reported,
TargetType targetType,
ReportReason reportReason,
String content,
ReportStatus reportStatus) {
this.targetId = targetId;
this.reporter = reporter;
this.reported = reported;
this.targetType = targetType;
this.reportReason = reportReason;
this.content = content;
Expand All @@ -61,13 +68,15 @@ private Report(
public static Report createReport(
Long targetId,
Member reporter,
Member reported,
TargetType targetType,
ReportReason reportReason,
String content) {
Report report =
Report.builder()
.targetId(targetId)
.reporter(reporter)
.reported(reported)
.targetType(targetType)
.reportReason(reportReason)
.content(content)
Expand Down
7 changes: 5 additions & 2 deletions clokey-domain/src/main/resources/db/migration/V1__init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ CREATE TABLE report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,

target_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,

reporter_member_id BIGINT NOT NULL,
reported_member_id BIGINT NOT NULL,

report_reason VARCHAR(255) NOT NULL CHECK (
report_reason IN (
Expand All @@ -297,7 +299,8 @@ CREATE TABLE report (
created_at DATETIME(6) NOT NULL,
updated_at DATETIME(6) NOT NULL,

CONSTRAINT fk_report_member FOREIGN KEY (member_id) REFERENCES member(id)
CONSTRAINT fk_report_reporter FOREIGN KEY (reporter_member_id) REFERENCES member(id),
CONSTRAINT fk_report_reported FOREIGN KEY (reported_member_id) REFERENCES member(id)
);

CREATE TABLE member_term (
Expand Down