Skip to content
Original file line number Diff line number Diff line change
@@ -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<PermissionType> supportedPermissions;

ResourceType(String code, String description, Set<PermissionType> 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)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.leets.blog.comment.adapter.out.persistence;

import com.leets.blog.comment.application.port.out.LoadCommentPort;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class CommentPersistenceAdapter implements LoadCommentPort {

private final CommentRepository commentRepository;

@Override
public boolean existsById(Long commentId) {
return commentRepository.existsById(commentId);
}
}
Original file line number Diff line number Diff line change
@@ -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<CommentJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.leets.blog.comment.application.port.out;

public interface LoadCommentPort {
// TODO: Comment 도메인 생성 후 exists 기반 검증 대신 도메인 조회 책임으로 변경
boolean existsById(Long commentId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

@Entity
@Getter
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public enum Domain {
AUTHENTICATION,
COMMENT,
POST,
MEMBER
MEMBER,
REPORT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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 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;
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;
private final ReviewReportUseCase reviewReportUseCase;

@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));
}

@PatchMapping("/{reportId}/reviewing")
@Operation(summary = "신고 검토중 처리", description = "특정 신고를 검토중 상태로 변경합니다.")
public void reviewReport(@PathVariable Long reportId) {
reviewReportUseCase.review(new ReviewReportCommand(reportId));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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 com.leets.blog.report.domain.exception.ReportDomainException;
import com.leets.blog.report.domain.exception.ReportErrorCode;
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 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;

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);

return saved.toDomain();
}
}
Original file line number Diff line number Diff line change
@@ -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<ReportJpaEntity, Long> {
boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, ReportTargetType targetType, Long targetId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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;

@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;

@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();
}

public void update(Report report) {
this.reportStatus = report.getReportStatus();
}

// JPA Entity -> Domain
public Report toDomain() {
return Report.reconstruct(
new Report.ReportId(this.id),
this.reporterId,
this.targetType,
this.targetId,
this.reportStatus,
this.reason
);
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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, "신고 사유는 필수입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -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, "신고 사유는 필수입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -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는 필수입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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);

Report findReport(Report.ReportId reportId);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Loading