From 9334c44e74de7570616a21459b9553a705ec5b59 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:32:05 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20report=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/entity/ReportJpaEntity.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java new file mode 100644 index 0000000..7d359fa --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java @@ -0,0 +1,70 @@ +package com.leets.blog.report.adapter.out.persistence.entity; + +import com.leets.blog.common.BaseEntity; +import com.leets.blog.report.domain.Report; +import com.leets.blog.report.domain.enums.ReportStatus; +import com.leets.blog.report.domain.enums.ReportTargetType; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "report") +public class ReportJpaEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "reporter_id", nullable = false) + private Long reporterId; + + @Column(name = "target_type", nullable = false) + private ReportTargetType targetType; + + @Column(name = "target_id", nullable = false) + private Long targetId; + + @Column(name = "report_status", nullable = false) + private ReportStatus reportStatus; + + @Column(name = "reason", nullable = false, length = 250) + private String reason; + + @Builder + private ReportJpaEntity(Long reporterId, ReportTargetType targetType, Long targetId, ReportStatus reportStatus, String reason) { + this.reporterId = reporterId; + this.targetType = targetType; + this.targetId = targetId; + this.reportStatus = reportStatus; + this.reason = reason; + } + + // Domain -> JPA Entity + public static ReportJpaEntity from(Report report) { + return ReportJpaEntity.builder() + .reporterId(report.getReporterId()) + .targetType(report.getTargetType()) + .targetId(report.getTargetId()) + .reportStatus(report.getReportStatus()) + .reason(report.getReason()) + .build(); + } + + // JPA Entity -> Domain + public Report toDomain() { + return Report.reconstruct( + new Report.ReportId(this.id), + this.reporterId, + this.targetType, + this.targetId, + this.reportStatus, + this.reason + ); + } + +} From 67cac14fc355f312e701139eeb26ac4aed674113 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:32:58 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20command,=20usecase,=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/exception/constant/Domain.java | 3 +- .../port/in/command/ReportCommentUseCase.java | 7 + .../port/in/command/ReportPostUseCase.java | 7 + .../in/command/dto/ReportCommentCommand.java | 15 ++ .../in/command/dto/ReportPostCommand.java | 15 ++ .../com/leets/blog/report/domain/Report.java | 136 ++++++++++++++++++ 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportCommentUseCase.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportPostUseCase.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportCommentCommand.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportPostCommand.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/domain/Report.java diff --git a/hanharam/src/main/java/com/leets/blog/global/exception/constant/Domain.java b/hanharam/src/main/java/com/leets/blog/global/exception/constant/Domain.java index 67c803a..55351e2 100644 --- a/hanharam/src/main/java/com/leets/blog/global/exception/constant/Domain.java +++ b/hanharam/src/main/java/com/leets/blog/global/exception/constant/Domain.java @@ -9,5 +9,6 @@ public enum Domain { AUTHENTICATION, COMMENT, POST, - MEMBER + MEMBER, + REPORT } diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportCommentUseCase.java b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportCommentUseCase.java new file mode 100644 index 0000000..6a2d3bd --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportCommentUseCase.java @@ -0,0 +1,7 @@ +package com.leets.blog.report.application.port.in.command; + +import com.leets.blog.report.application.port.in.command.dto.ReportCommentCommand; + +public interface ReportCommentUseCase { + void report(ReportCommentCommand command); +} diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportPostUseCase.java b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportPostUseCase.java new file mode 100644 index 0000000..4af7acc --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReportPostUseCase.java @@ -0,0 +1,7 @@ +package com.leets.blog.report.application.port.in.command; + +import com.leets.blog.report.application.port.in.command.dto.ReportPostCommand; + +public interface ReportPostUseCase { + void report(ReportPostCommand command); +} diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportCommentCommand.java b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportCommentCommand.java new file mode 100644 index 0000000..485cadc --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportCommentCommand.java @@ -0,0 +1,15 @@ +package com.leets.blog.report.application.port.in.command.dto; + +import java.util.Objects; + +public record ReportCommentCommand( + Long commentId, + Long reporterId, + String reason +) { + public ReportCommentCommand{ + Objects.requireNonNull(commentId, "댓글 ID는 필수입니다."); + Objects.requireNonNull(reporterId, "신고자 ID는 필수입니다."); + Objects.requireNonNull(reason, "신고 사유는 필수입니다."); + } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportPostCommand.java b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportPostCommand.java new file mode 100644 index 0000000..cbac1ad --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReportPostCommand.java @@ -0,0 +1,15 @@ +package com.leets.blog.report.application.port.in.command.dto; + +import java.util.Objects; + +public record ReportPostCommand( + Long postId, + Long reporterId, + String reason +) { + public ReportPostCommand{ + Objects.requireNonNull(postId, "게시글 ID는 필수입니다."); + Objects.requireNonNull(reporterId, "신고자 ID는 필수입니다."); + Objects.requireNonNull(reason, "신고 사유는 필수입니다."); + } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/Report.java b/hanharam/src/main/java/com/leets/blog/report/domain/Report.java new file mode 100644 index 0000000..f2cf412 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/domain/Report.java @@ -0,0 +1,136 @@ +package com.leets.blog.report.domain; + +import com.leets.blog.report.domain.enums.ReportStatus; +import com.leets.blog.report.domain.enums.ReportTargetType; +import com.leets.blog.report.domain.exception.ReportDomainException; +import com.leets.blog.report.domain.exception.ReportErrorCode; +import lombok.*; + +import java.time.LocalDateTime; + + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +public class Report{ + @Getter + private final ReportId reportId; + + @Getter + private final Long reporterId; // 신고자 ID + + @Getter + private final ReportTargetType targetType; // COMMENT, POST + + @Getter + private final Long targetId; + + @Getter + private ReportStatus reportStatus; // PENDING, APPROVED, REJECTED + + @Getter + private final String reason; // 신고 사유 + + @Getter + private final LocalDateTime createdAt; + + // 신고 생성 + public static Report create(Long reporterId, ReportTargetType targetType, Long targetId, String reason) { + validateReporterId(reporterId); + validateTargetType(targetType); + validateTargetId(targetId); + validateReason(reason); + + // 생성 시점의 상태는 대기로 초기화 + ReportStatus initialStatus = ReportStatus.PENDING; + LocalDateTime now = LocalDateTime.now(); + + return Report.builder() + .reporterId(reporterId) + .targetType(targetType) + .targetId(targetId) + .reportStatus(initialStatus) + .reason(reason) + .createdAt(now) + .build(); + } + + // JPA Entity -> Domain + public static Report reconstruct( + ReportId reportId, + Long reporterId, + ReportTargetType targetType, + Long targetId, + ReportStatus reportStatus, + String reason) { + + validateReporterId(reporterId); + validateTargetType(targetType); + validateTargetId(targetId); + validateReason(reason); + + return Report.builder() + .reportId(reportId) + .reporterId(reporterId) + .targetType(targetType) + .targetId(targetId) + .reportStatus(reportStatus) + .reason(reason) + .build(); + } + + // 관리자가 신고 상태를 처리 + public void processReport(ReportStatus newStatus) { + validateReportStatus(newStatus); + + // 이미 처리된 신고를 다시 처리할 수 없게 막는 도메인 규칙 예시 + if (this.reportStatus == ReportStatus.APPROVED || this.reportStatus == ReportStatus.REJECTED) { + throw new ReportDomainException(ReportErrorCode.ALREADY_PROCESSED); + } + + this.reportStatus = newStatus; + } + + // 신고자 아이디 검증 + private static void validateReporterId(Long reporterId) { + if (reporterId == null || reporterId <= 0) { + throw new ReportDomainException(ReportErrorCode.INVALID_REPORTER_ID); + } + } + // 타겟 유형 검증 + private static void validateTargetType(ReportTargetType targetType) { + if (targetType == null) { + throw new ReportDomainException(ReportErrorCode.INVALID_TARGET_TYPE); + } + } + // 타겟 아이디 검증 + private static void validateTargetId(Long targetId) { + if (targetId == null || targetId <= 0) { + throw new ReportDomainException(ReportErrorCode.INVALID_TARGET_ID); + } + } + // 신고 사유 검증 + private static void validateReason(String reason) { + if (reason == null || reason.isBlank()) { + throw new ReportDomainException(ReportErrorCode.INVALID_REPORT_REASON); + } + // 신고 사유 250자로 제한 + if (reason.length() > 250) { + throw new ReportDomainException(ReportErrorCode.INVALID_REASON_SIZE); + } + } + // 신고 상태 검증 + private static void validateReportStatus(ReportStatus status) { + if (status == null) { + throw new ReportDomainException(ReportErrorCode.INVALID_REPORT_STATUS); + } + } + + + public record ReportId(Long id) { + public ReportId { + if (id <= 0) { + throw new ReportDomainException(ReportErrorCode.INVALID_ID); + } + } + } +} From ffbada6d7018c5bcd59d6b775c895f7fe62b3134 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:33:16 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20comment=20DB=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentPersistenceAdapter.java | 23 +++++++++++++++++++ .../out/persistence/CommentRepository.java | 7 ++++++ .../blog/comment/domain/CommentJpaEntity.java | 6 +++++ 3 files changed, 36 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java create mode 100644 hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentRepository.java diff --git a/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java b/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java new file mode 100644 index 0000000..ea21026 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java @@ -0,0 +1,23 @@ +package com.leets.blog.comment.adapter.out.persistence; + +import com.leets.blog.comment.application.port.out.LoadCommentPort; +import com.leets.blog.comment.domain.CommentJpaEntity; +import com.leets.blog.post.adapter.out.persistence.entity.PostJpaEntity; +import lombok.RequiredArgsConstructor; +import org.hibernate.annotations.Comment; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class CommentPersistenceAdapter implements LoadCommentPort { + + private final CommentRepository commentRepository; + + @Override + public Optional findById(Long commentId) { + return commentRepository.findById(commentId) + .map(CommentJpaEntity::toDomain); + } +} diff --git a/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentRepository.java b/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentRepository.java new file mode 100644 index 0000000..2013f3a --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentRepository.java @@ -0,0 +1,7 @@ +package com.leets.blog.comment.adapter.out.persistence; + +import com.leets.blog.comment.domain.CommentJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { +} diff --git a/hanharam/src/main/java/com/leets/blog/comment/domain/CommentJpaEntity.java b/hanharam/src/main/java/com/leets/blog/comment/domain/CommentJpaEntity.java index 777dfea..94fbf2a 100644 --- a/hanharam/src/main/java/com/leets/blog/comment/domain/CommentJpaEntity.java +++ b/hanharam/src/main/java/com/leets/blog/comment/domain/CommentJpaEntity.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; @Entity @Getter @@ -36,4 +37,9 @@ private CommentJpaEntity(Long postId, Long memberId, String content, Long parent this.content = content; this.parentId = parentId; } + + // todo: toDomain 임시 설정 -> Comment 도메인 생성 후 메서드 작성하기 + public Comment toDomain() { + return null; + } } From 968dd86e30b11085698e53cbb4a4036b2690cb7f Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:33:29 +0900 Subject: [PATCH 04/12] feat: comment port --- .../comment/application/port/out/LoadCommentPort.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java diff --git a/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java b/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java new file mode 100644 index 0000000..d69bf04 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java @@ -0,0 +1,9 @@ +package com.leets.blog.comment.application.port.out; + +import org.hibernate.annotations.Comment; + +import java.util.Optional; + +public interface LoadCommentPort { + Optional findById(Long commentId); +} From 1e4b082649af5f405bf6fb5f923f544d239e879f Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:33:47 +0900 Subject: [PATCH 05/12] feat: report port --- .../blog/report/application/port/out/LoadReportPort.java | 8 ++++++++ .../blog/report/application/port/out/SaveReportPort.java | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/out/SaveReportPort.java diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java b/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java new file mode 100644 index 0000000..d5b48ec --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java @@ -0,0 +1,8 @@ +package com.leets.blog.report.application.port.out; + +import com.leets.blog.report.domain.Report; +import com.leets.blog.report.domain.enums.ReportTargetType; + +public interface LoadReportPort { + boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, ReportTargetType targetType, Long targetId); +} diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/out/SaveReportPort.java b/hanharam/src/main/java/com/leets/blog/report/application/port/out/SaveReportPort.java new file mode 100644 index 0000000..8b43ac5 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/out/SaveReportPort.java @@ -0,0 +1,7 @@ +package com.leets.blog.report.application.port.out; + +import com.leets.blog.report.domain.Report; + +public interface SaveReportPort { + Report save(Report report); +} From 121a3017276c98de7b80adfd2700c30cf2bb3476 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:34:04 +0900 Subject: [PATCH 06/12] feat: report service --- .../service/ReportCommandService.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java diff --git a/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java b/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java new file mode 100644 index 0000000..92bfa1b --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java @@ -0,0 +1,68 @@ +package com.leets.blog.report.application.service; + +import com.leets.blog.comment.application.port.out.LoadCommentPort; +import com.leets.blog.post.application.port.out.LoadPostPort; +import com.leets.blog.report.application.port.in.command.ReportCommentUseCase; +import com.leets.blog.report.application.port.in.command.ReportPostUseCase; +import com.leets.blog.report.application.port.in.command.dto.ReportCommentCommand; +import com.leets.blog.report.application.port.in.command.dto.ReportPostCommand; +import com.leets.blog.report.application.port.out.LoadReportPort; +import com.leets.blog.report.application.port.out.SaveReportPort; +import com.leets.blog.report.domain.Report; +import com.leets.blog.report.domain.enums.ReportTargetType; +import com.leets.blog.report.domain.exception.ReportDomainException; +import com.leets.blog.report.domain.exception.ReportErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class ReportCommandService implements ReportPostUseCase, ReportCommentUseCase { + + private final SaveReportPort saveReportPort; + private final LoadReportPort loadReportPort; + private final LoadPostPort loadPostPort; + private final LoadCommentPort loadCommentPort; + + @Override + public void report(ReportCommentCommand command) { + // 댓글 존재 확인 + loadCommentPort.findById(command.commentId()) + .orElseThrow(() -> new ReportDomainException(ReportErrorCode.COMMENT_NOT_FOUND)); + + // 중복 신고 확인 및 저장 + checkDuplicateAndSaveReport(command.reporterId(), ReportTargetType.COMMENT, command.commentId(), command.reason()); + } + + @Override + public void report(ReportPostCommand command) { + // 게시글 존재 확인 + loadPostPort.findById(command.postId()) + .orElseThrow(() -> new ReportDomainException(ReportErrorCode.POST_NOT_FOUND)); + + // 중복 신고 확인 및 저장 + checkDuplicateAndSaveReport(command.reporterId(), ReportTargetType.POST, command.postId(), command.reason()); + } + + /** + * 중복 신고를 확인하고 신고를 생성합니다. + * + * @param reporterId 신고자 ID + * @param targetType 신고 타겟 유형 (Comment, Post) + * @param targetId 신고 대상 ID + * @throws ReportDomainException 이미 신고한 경우 + */ + + private void checkDuplicateAndSaveReport(Long reporterId, ReportTargetType targetType, Long targetId, String reason) { + // 중복 신고 확인 + if (loadReportPort.existsByReporterIdAndTargetTypeAndTargetId(reporterId, targetType, targetId)) { + throw new ReportDomainException(ReportErrorCode.REPORT_ALREADY_EXISTS); + } + + // 신고 생성 및 저장 + Report report = Report.create(reporterId, targetType, targetId, reason); + saveReportPort.save(report); + } +} From e85fc49c815b0dbf4a1dfe98bdeed55dfdcd90e7 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:34:32 +0900 Subject: [PATCH 07/12] feat: report adapter, enums --- .../authorization/domain/ResourceType.java | 43 +++++++++++++++++++ .../persistence/ReportPersistenceAdapter.java | 31 +++++++++++++ .../out/persistence/ReportRepository.java | 9 ++++ .../report/domain/enums/ReportStatus.java | 7 +++ .../report/domain/enums/ReportTargetType.java | 6 +++ 5 files changed, 96 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/authorization/domain/ResourceType.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportRepository.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportTargetType.java diff --git a/hanharam/src/main/java/com/leets/blog/authorization/domain/ResourceType.java b/hanharam/src/main/java/com/leets/blog/authorization/domain/ResourceType.java new file mode 100644 index 0000000..00ba4fe --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/authorization/domain/ResourceType.java @@ -0,0 +1,43 @@ +package com.leets.blog.authorization.domain; + +import lombok.Getter; + +import java.util.Set; + +/** + * 권한 체크 대상이 되는 리소스 타입 + */ +@Getter +public enum ResourceType { + + POST("post", "게시글", Set.of( + PermissionType.READ, PermissionType.WRITE, PermissionType.EDIT, PermissionType.DELETE + )), + COMMENT("comment", "댓글", Set.of( + PermissionType.READ, PermissionType.WRITE, PermissionType.EDIT, PermissionType.DELETE + )), + MEMBER("member", "회원", + Set.of(PermissionType.READ, PermissionType.EDIT, PermissionType.DELETE)); + + private final String code; + private final String description; + private final Set supportedPermissions; + + ResourceType(String code, String description, Set supportedPermissions) { + this.code = code; + this.description = description; + this.supportedPermissions = Set.copyOf(supportedPermissions); + } + + public boolean supports(PermissionType permission) { + return supportedPermissions.contains(permission); + } + + public void validatePermission(PermissionType permission) { + if (!supports(permission)) { + throw new IllegalArgumentException( + String.format("리소스 '%s'은(는) '%s' 권한을 지원하지 않습니다.", this.name(), permission) + ); + } + } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java new file mode 100644 index 0000000..8085eed --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java @@ -0,0 +1,31 @@ +package com.leets.blog.report.adapter.out.persistence; + +import com.leets.blog.report.adapter.out.persistence.entity.ReportJpaEntity; +import com.leets.blog.report.application.port.out.LoadReportPort; +import com.leets.blog.report.application.port.out.SaveReportPort; +import com.leets.blog.report.domain.Report; +import com.leets.blog.report.domain.enums.ReportTargetType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ReportPersistenceAdapter implements LoadReportPort, SaveReportPort { + + private final ReportRepository reportRepository; + + @Override + public boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, ReportTargetType targetType, Long targetId) { + + return reportRepository.existsByReporterIdAndTargetTypeAndTargetId(reporterId, targetType, targetId); + } + + @Override + public Report save(Report report) { + ReportJpaEntity entity = ReportJpaEntity.from(report); + + ReportJpaEntity saved = reportRepository.save(entity); + + return saved.toDomain(); + } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportRepository.java b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportRepository.java new file mode 100644 index 0000000..5e661b1 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportRepository.java @@ -0,0 +1,9 @@ +package com.leets.blog.report.adapter.out.persistence; + +import com.leets.blog.report.adapter.out.persistence.entity.ReportJpaEntity; +import com.leets.blog.report.domain.enums.ReportTargetType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportRepository extends JpaRepository { + boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, ReportTargetType targetType, Long targetId); +} diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java b/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java new file mode 100644 index 0000000..9d350f6 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java @@ -0,0 +1,7 @@ +package com.leets.blog.report.domain.enums; + +public enum ReportStatus { + PENDING, + APPROVED, + REJECTED +} diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportTargetType.java b/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportTargetType.java new file mode 100644 index 0000000..90f6b0b --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportTargetType.java @@ -0,0 +1,6 @@ +package com.leets.blog.report.domain.enums; + +public enum ReportTargetType { + POST, + COMMENT +} From c366f4f957cef775d8fe2f86d83ab1d67892b536 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:35:21 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=EC=9B=B9=20DTO,=20controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/ReportController.java | 40 +++++++++++++++++++ .../web/dto/request/CreateReportRequest.java | 21 ++++++++++ .../exception/ReportDomainException.java | 13 ++++++ .../domain/exception/ReportErrorCode.java | 29 ++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/adapter/in/web/dto/request/CreateReportRequest.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java b/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java new file mode 100644 index 0000000..8f18958 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java @@ -0,0 +1,40 @@ +package com.leets.blog.report.adapter.in.web; + +import com.leets.blog.report.adapter.in.web.dto.request.CreateReportRequest; +import com.leets.blog.report.application.port.in.command.ReportCommentUseCase; +import com.leets.blog.report.application.port.in.command.ReportPostUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/reports") +@RequiredArgsConstructor +@Tag(name = "Report | 신고 command", description = "신고 관련 API") +public class ReportController { + + private final ReportCommentUseCase reportCommentUseCase; + private final ReportPostUseCase reportPostUseCase; + + @PostMapping("/posts/{postId}") + @Operation(summary = "게시글 신고", description = "특정 게시글을 신고합니다.") + public void reportPost( + @PathVariable Long postId, + @Valid @RequestBody CreateReportRequest request, + Long reporterId + ) { + reportPostUseCase.report(request.toPostCommand(postId, reporterId)); + } + + @PostMapping("/comments/{commentId}") + @Operation(summary = "댓글 신고", description = "특정 댓글을 신고합니다.") + public void reportComment( + @PathVariable Long commentId, + @Valid @RequestBody CreateReportRequest request, + Long reporterId + ) { + reportCommentUseCase.report(request.toCommentCommand(commentId, reporterId)); + } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/dto/request/CreateReportRequest.java b/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/dto/request/CreateReportRequest.java new file mode 100644 index 0000000..aca639b --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/dto/request/CreateReportRequest.java @@ -0,0 +1,21 @@ +package com.leets.blog.report.adapter.in.web.dto.request; + +import com.leets.blog.report.application.port.in.command.dto.ReportCommentCommand; +import com.leets.blog.report.application.port.in.command.dto.ReportPostCommand; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "신고 사유 작성 요청") +public record CreateReportRequest( + @Schema(description = "신고 사유", example = "부적절한 언어 포함") + @NotBlank(message = "신고 사유는 필수입니다.") + String reason +) { + public ReportCommentCommand toCommentCommand(Long commentId, Long reporterId) { + return new ReportCommentCommand(commentId, reporterId, reason); + } + + public ReportPostCommand toPostCommand(Long postId, Long reporterId) { + return new ReportPostCommand(postId, reporterId, reason); + } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java new file mode 100644 index 0000000..b917fe5 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java @@ -0,0 +1,13 @@ +package com.leets.blog.report.domain.exception; + +import com.leets.blog.global.exception.BusinessException; +import com.leets.blog.global.exception.constant.Domain; +import com.leets.blog.report.domain.Report; + +public class ReportDomainException extends BusinessException { + public ReportDomainException(ReportErrorCode errorCode) { + super(Domain.REPORT, errorCode); + } + + public ReportDomainException(ReportErrorCode errorCode, String message) { super(Domain.REPORT, errorCode, message); } +} diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java new file mode 100644 index 0000000..2e5e797 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java @@ -0,0 +1,29 @@ +package com.leets.blog.report.domain.exception; + +import com.leets.blog.global.exception.constant.CommonErrorCode; +import com.leets.blog.global.response.code.BaseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ReportErrorCode implements BaseCode { + + INVALID_ID(HttpStatus.BAD_REQUEST, "REPORT-001", "ID는 양수입니다."), + INVALID_REPORTER_ID(HttpStatus.BAD_REQUEST, "REPORT-002", "신고자ID는 필수입니다."), + INVALID_TARGET_TYPE(HttpStatus.BAD_REQUEST, "REPORT-003", "타겟 타입은 필수입니다."), + INVALID_TARGET_ID(HttpStatus.BAD_REQUEST, "REPORT-004", "타겟ID는 필수입니다."), + INVALID_REPORT_REASON(HttpStatus.BAD_REQUEST, "REPORT-005", "신고 사유는 필수입니다."), + INVALID_REASON_SIZE(HttpStatus.BAD_REQUEST, "REPORT-006", "신고 사유가 250자 초과입니다."), + INVALID_REPORT_STATUS(HttpStatus.BAD_REQUEST, "REPORT-007", "신고 상태는 필수입니다."), + + ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, "REPORT-008", "이미 처리된 신고입니다."), + POST_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPORT-009", "신고한 게시글을 찾을 수 없습니다."), + REPORT_ALREADY_EXISTS(HttpStatus.CONFLICT, "REPORT-010", "이미 신고한 게시글/댓글입니다."), + COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPORT-011", "신고한 댓글을 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} From b360bb1dd88f80a750146e85fc9a1fb8fb7792ad Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:48:45 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B3=A0=20=EA=B2=80?= =?UTF-8?q?=ED=86=A0=EC=A4=91=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/ReportController.java | 9 +++++++++ .../persistence/ReportPersistenceAdapter.java | 19 ++++++++++++++++++- .../persistence/entity/ReportJpaEntity.java | 4 ++++ .../application/port/out/LoadReportPort.java | 2 ++ .../service/ReportCommandService.java | 11 ++++++++++- .../com/leets/blog/report/domain/Report.java | 8 ++++++++ .../report/domain/enums/ReportStatus.java | 1 + .../domain/exception/ReportErrorCode.java | 5 +++-- 8 files changed, 55 insertions(+), 4 deletions(-) diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java b/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java index 8f18958..8f9daf8 100644 --- a/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/in/web/ReportController.java @@ -3,6 +3,8 @@ import com.leets.blog.report.adapter.in.web.dto.request.CreateReportRequest; import com.leets.blog.report.application.port.in.command.ReportCommentUseCase; import com.leets.blog.report.application.port.in.command.ReportPostUseCase; +import com.leets.blog.report.application.port.in.command.ReviewReportUseCase; +import com.leets.blog.report.application.port.in.command.dto.ReviewReportCommand; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -17,6 +19,7 @@ public class ReportController { private final ReportCommentUseCase reportCommentUseCase; private final ReportPostUseCase reportPostUseCase; + private final ReviewReportUseCase reviewReportUseCase; @PostMapping("/posts/{postId}") @Operation(summary = "게시글 신고", description = "특정 게시글을 신고합니다.") @@ -37,4 +40,10 @@ public void reportComment( ) { reportCommentUseCase.report(request.toCommentCommand(commentId, reporterId)); } + + @PatchMapping("/{reportId}/reviewing") + @Operation(summary = "신고 검토중 처리", description = "특정 신고를 검토중 상태로 변경합니다.") + public void reviewReport(@PathVariable Long reportId) { + reviewReportUseCase.review(new ReviewReportCommand(reportId)); + } } diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java index 8085eed..0cd3530 100644 --- a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/ReportPersistenceAdapter.java @@ -5,6 +5,8 @@ import com.leets.blog.report.application.port.out.SaveReportPort; import com.leets.blog.report.domain.Report; import com.leets.blog.report.domain.enums.ReportTargetType; +import com.leets.blog.report.domain.exception.ReportDomainException; +import com.leets.blog.report.domain.exception.ReportErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -20,9 +22,24 @@ public boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, Repor return reportRepository.existsByReporterIdAndTargetTypeAndTargetId(reporterId, targetType, targetId); } + @Override + public Report findReport(Report.ReportId reportId) { + return reportRepository.findById(reportId.id()) + .map(ReportJpaEntity::toDomain) + .orElseThrow(() -> new ReportDomainException(ReportErrorCode.REPORT_NOT_FOUND)); + } + @Override public Report save(Report report) { - ReportJpaEntity entity = ReportJpaEntity.from(report); + ReportJpaEntity entity; + + if (report.getReportId() == null) { + entity = ReportJpaEntity.from(report); + } else { + entity = reportRepository.findById(report.getReportId().id()) + .orElseThrow(() -> new ReportDomainException(ReportErrorCode.REPORT_NOT_FOUND)); + entity.update(report); + } ReportJpaEntity saved = reportRepository.save(entity); diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java index 7d359fa..b8ae342 100644 --- a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java @@ -55,6 +55,10 @@ public static ReportJpaEntity from(Report report) { .build(); } + public void update(Report report) { + this.reportStatus = report.getReportStatus(); + } + // JPA Entity -> Domain public Report toDomain() { return Report.reconstruct( diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java b/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java index d5b48ec..a4c51f4 100644 --- a/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/out/LoadReportPort.java @@ -5,4 +5,6 @@ public interface LoadReportPort { boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, ReportTargetType targetType, Long targetId); + + Report findReport(Report.ReportId reportId); } diff --git a/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java b/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java index 92bfa1b..c9723e0 100644 --- a/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java +++ b/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java @@ -4,8 +4,10 @@ import com.leets.blog.post.application.port.out.LoadPostPort; import com.leets.blog.report.application.port.in.command.ReportCommentUseCase; import com.leets.blog.report.application.port.in.command.ReportPostUseCase; +import com.leets.blog.report.application.port.in.command.ReviewReportUseCase; import com.leets.blog.report.application.port.in.command.dto.ReportCommentCommand; import com.leets.blog.report.application.port.in.command.dto.ReportPostCommand; +import com.leets.blog.report.application.port.in.command.dto.ReviewReportCommand; import com.leets.blog.report.application.port.out.LoadReportPort; import com.leets.blog.report.application.port.out.SaveReportPort; import com.leets.blog.report.domain.Report; @@ -19,7 +21,7 @@ @Service @RequiredArgsConstructor @Transactional -public class ReportCommandService implements ReportPostUseCase, ReportCommentUseCase { +public class ReportCommandService implements ReportPostUseCase, ReportCommentUseCase, ReviewReportUseCase { private final SaveReportPort saveReportPort; private final LoadReportPort loadReportPort; @@ -46,6 +48,13 @@ public void report(ReportPostCommand command) { checkDuplicateAndSaveReport(command.reporterId(), ReportTargetType.POST, command.postId(), command.reason()); } + @Override + public void review(ReviewReportCommand command) { + Report report = loadReportPort.findReport(new Report.ReportId(command.reportId())); + report.markAsReviewing(); + saveReportPort.save(report); + } + /** * 중복 신고를 확인하고 신고를 생성합니다. * diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/Report.java b/hanharam/src/main/java/com/leets/blog/report/domain/Report.java index f2cf412..7531c25 100644 --- a/hanharam/src/main/java/com/leets/blog/report/domain/Report.java +++ b/hanharam/src/main/java/com/leets/blog/report/domain/Report.java @@ -90,6 +90,14 @@ public void processReport(ReportStatus newStatus) { this.reportStatus = newStatus; } + public void markAsReviewing() { + if (this.reportStatus != ReportStatus.PENDING) { + throw new ReportDomainException(ReportErrorCode.INVALID_STATUS_TRANSITION); + } + + this.reportStatus = ReportStatus.REVIEWING; + } + // 신고자 아이디 검증 private static void validateReporterId(Long reporterId) { if (reporterId == null || reporterId <= 0) { diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java b/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java index 9d350f6..04a866f 100644 --- a/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java +++ b/hanharam/src/main/java/com/leets/blog/report/domain/enums/ReportStatus.java @@ -2,6 +2,7 @@ public enum ReportStatus { PENDING, + REVIEWING, APPROVED, REJECTED } diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java index 2e5e797..5fe732f 100644 --- a/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java +++ b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportErrorCode.java @@ -1,6 +1,5 @@ package com.leets.blog.report.domain.exception; -import com.leets.blog.global.exception.constant.CommonErrorCode; import com.leets.blog.global.response.code.BaseCode; import lombok.AllArgsConstructor; import lombok.Getter; @@ -21,7 +20,9 @@ public enum ReportErrorCode implements BaseCode { ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, "REPORT-008", "이미 처리된 신고입니다."), POST_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPORT-009", "신고한 게시글을 찾을 수 없습니다."), REPORT_ALREADY_EXISTS(HttpStatus.CONFLICT, "REPORT-010", "이미 신고한 게시글/댓글입니다."), - COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPORT-011", "신고한 댓글을 찾을 수 없습니다."); + COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPORT-011", "신고한 댓글을 찾을 수 없습니다."), + REPORT_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPORT-012", "신고를 찾을 수 없습니다."), + INVALID_STATUS_TRANSITION(HttpStatus.BAD_REQUEST, "REPORT-013", "해당 상태로 변경할 수 없습니다."); private final HttpStatus httpStatus; private final String code; From aaa77592a3bd78ee78b26ddf79ac4c7ffecd7b56 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:50:47 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B3=A0=20=EA=B2=80?= =?UTF-8?q?=ED=86=A0=EC=A4=91=20API=20command,=20usecase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/command/ReviewReportUseCase.java | 7 +++++++ .../port/in/command/dto/ReviewReportCommand.java | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReviewReportUseCase.java create mode 100644 hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReviewReportCommand.java diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReviewReportUseCase.java b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReviewReportUseCase.java new file mode 100644 index 0000000..45720c5 --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/ReviewReportUseCase.java @@ -0,0 +1,7 @@ +package com.leets.blog.report.application.port.in.command; + +import com.leets.blog.report.application.port.in.command.dto.ReviewReportCommand; + +public interface ReviewReportUseCase { + void review(ReviewReportCommand command); +} diff --git a/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReviewReportCommand.java b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReviewReportCommand.java new file mode 100644 index 0000000..a5402be --- /dev/null +++ b/hanharam/src/main/java/com/leets/blog/report/application/port/in/command/dto/ReviewReportCommand.java @@ -0,0 +1,11 @@ +package com.leets.blog.report.application.port.in.command.dto; + +import java.util.Objects; + +public record ReviewReportCommand( + Long reportId +) { + public ReviewReportCommand { + Objects.requireNonNull(reportId, "신고 ID는 필수입니다."); + } +} From 344828bcbb1d279fb3431eecfd4f28ab8b7878f9 Mon Sep 17 00:00:00 2001 From: Hanharam Date: Tue, 28 Apr 2026 23:58:19 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20comment,=20report=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentPersistenceAdapter.java | 10 ++-------- .../application/port/out/LoadCommentPort.java | 7 ++----- .../service/ReportCommandService.java | 5 +++-- .../com/leets/blog/report/domain/Report.java | 20 ------------------- .../exception/ReportDomainException.java | 1 - 5 files changed, 7 insertions(+), 36 deletions(-) diff --git a/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java b/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java index ea21026..7c35b66 100644 --- a/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java +++ b/hanharam/src/main/java/com/leets/blog/comment/adapter/out/persistence/CommentPersistenceAdapter.java @@ -1,14 +1,9 @@ package com.leets.blog.comment.adapter.out.persistence; import com.leets.blog.comment.application.port.out.LoadCommentPort; -import com.leets.blog.comment.domain.CommentJpaEntity; -import com.leets.blog.post.adapter.out.persistence.entity.PostJpaEntity; import lombok.RequiredArgsConstructor; -import org.hibernate.annotations.Comment; import org.springframework.stereotype.Component; -import java.util.Optional; - @Component @RequiredArgsConstructor public class CommentPersistenceAdapter implements LoadCommentPort { @@ -16,8 +11,7 @@ public class CommentPersistenceAdapter implements LoadCommentPort { private final CommentRepository commentRepository; @Override - public Optional findById(Long commentId) { - return commentRepository.findById(commentId) - .map(CommentJpaEntity::toDomain); + public boolean existsById(Long commentId) { + return commentRepository.existsById(commentId); } } diff --git a/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java b/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java index d69bf04..f7a6863 100644 --- a/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java +++ b/hanharam/src/main/java/com/leets/blog/comment/application/port/out/LoadCommentPort.java @@ -1,9 +1,6 @@ package com.leets.blog.comment.application.port.out; -import org.hibernate.annotations.Comment; - -import java.util.Optional; - public interface LoadCommentPort { - Optional findById(Long commentId); + // TODO: Comment 도메인 생성 후 exists 기반 검증 대신 도메인 조회 책임으로 변경 + boolean existsById(Long commentId); } diff --git a/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java b/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java index c9723e0..c7959d4 100644 --- a/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java +++ b/hanharam/src/main/java/com/leets/blog/report/application/service/ReportCommandService.java @@ -31,8 +31,9 @@ public class ReportCommandService implements ReportPostUseCase, ReportCommentUse @Override public void report(ReportCommentCommand command) { // 댓글 존재 확인 - loadCommentPort.findById(command.commentId()) - .orElseThrow(() -> new ReportDomainException(ReportErrorCode.COMMENT_NOT_FOUND)); + if (!loadCommentPort.existsById(command.commentId())) { + throw new ReportDomainException(ReportErrorCode.COMMENT_NOT_FOUND); + } // 중복 신고 확인 및 저장 checkDuplicateAndSaveReport(command.reporterId(), ReportTargetType.COMMENT, command.commentId(), command.reason()); diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/Report.java b/hanharam/src/main/java/com/leets/blog/report/domain/Report.java index 7531c25..f98942b 100644 --- a/hanharam/src/main/java/com/leets/blog/report/domain/Report.java +++ b/hanharam/src/main/java/com/leets/blog/report/domain/Report.java @@ -78,18 +78,6 @@ public static Report reconstruct( .build(); } - // 관리자가 신고 상태를 처리 - public void processReport(ReportStatus newStatus) { - validateReportStatus(newStatus); - - // 이미 처리된 신고를 다시 처리할 수 없게 막는 도메인 규칙 예시 - if (this.reportStatus == ReportStatus.APPROVED || this.reportStatus == ReportStatus.REJECTED) { - throw new ReportDomainException(ReportErrorCode.ALREADY_PROCESSED); - } - - this.reportStatus = newStatus; - } - public void markAsReviewing() { if (this.reportStatus != ReportStatus.PENDING) { throw new ReportDomainException(ReportErrorCode.INVALID_STATUS_TRANSITION); @@ -126,14 +114,6 @@ private static void validateReason(String reason) { throw new ReportDomainException(ReportErrorCode.INVALID_REASON_SIZE); } } - // 신고 상태 검증 - private static void validateReportStatus(ReportStatus status) { - if (status == null) { - throw new ReportDomainException(ReportErrorCode.INVALID_REPORT_STATUS); - } - } - - public record ReportId(Long id) { public ReportId { if (id <= 0) { diff --git a/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java index b917fe5..bff4a42 100644 --- a/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java +++ b/hanharam/src/main/java/com/leets/blog/report/domain/exception/ReportDomainException.java @@ -2,7 +2,6 @@ import com.leets.blog.global.exception.BusinessException; import com.leets.blog.global.exception.constant.Domain; -import com.leets.blog.report.domain.Report; public class ReportDomainException extends BusinessException { public ReportDomainException(ReportErrorCode errorCode) { From 57e9e44b8b3f922d4975d010f2ec864a2bb33f0a Mon Sep 17 00:00:00 2001 From: Hanharam Date: Wed, 29 Apr 2026 00:30:43 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20report=20enum=20String=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/adapter/out/persistence/entity/ReportJpaEntity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java index b8ae342..c271d75 100644 --- a/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java +++ b/hanharam/src/main/java/com/leets/blog/report/adapter/out/persistence/entity/ReportJpaEntity.java @@ -23,12 +23,14 @@ public class ReportJpaEntity extends BaseEntity { @Column(name = "reporter_id", nullable = false) private Long reporterId; + @Enumerated(EnumType.STRING) @Column(name = "target_type", nullable = false) private ReportTargetType targetType; @Column(name = "target_id", nullable = false) private Long targetId; + @Enumerated(EnumType.STRING) @Column(name = "report_status", nullable = false) private ReportStatus reportStatus;