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 @@ -84,7 +84,8 @@ public BaseResponse<Void> checkAuthNumber(
return BaseResponse.onSuccess(SuccessStatus.VERIFY_AUTH_NUMBER_SUCCESS, null);
}

@Operation(summary = "전화번호 중복 체크 API", description = "# [v1.0 (2025-01-15)]\n" +
@Operation(summary = "전화번호 중복 체크 API",
description = "# [v1.0 (2025-09-18)](https://clumsy-seeder-416.notion.site/2551197c19ed808a9757f7f0fc4cf09b?source=copy_link)\n" +
"- 입력한 전화번호가 이미 가입된 사용자가 있는지 확인합니다.\n" +
"- 중복된 전화번호가 있으면 에러를 반환합니다.\n" +
"\n**Request Body:**\n" +
Expand All @@ -99,7 +100,8 @@ public BaseResponse<Void> checkPhoneNumberAvailability(
return BaseResponse.onSuccess(SuccessStatus._OK, null);
}

@Operation(summary = "이메일 중복 체크 API", description = "# [v1.0 (2025-01-15)]\n" +
@Operation(summary = "이메일 중복 체크 API",
description = "# [v1.0 (2025-09-18)](https://clumsy-seeder-416.notion.site/2551197c19ed802d8f6dd373dd045f3a?source=copy_link)\n" +
"- 입력한 이메일이 이미 가입된 사용자가 있는지 확인합니다.\n" +
"- 중복된 이메일이 있으면 에러를 반환합니다.\n" +
"\n**Request Body:**\n" +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.assu.server.domain.common.entity.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ReportedStatus {
NORMAL("정상"),
REPORTED("신고됨"),
DELETED("삭제됨");

private final String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.assu.server.domain.store.entity.Store;
import com.assu.server.domain.store.repository.StoreRepository;
import com.assu.server.domain.user.entity.Student;
import com.assu.server.domain.user.entity.enums.Major;
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.global.exception.GeneralException;

Expand All @@ -41,7 +40,7 @@ public PaperResponseDTO.partnershipContent getStorePaperContent(Long storeId, Me

// 역할이 학생이 아닌 경우 : 이미 type별로 ui를 분기 시켜놔서 그럴일 없을 것 같긴 하지만 혹시 몰라서 처리함
if(member.getRole() != UserRole.STUDENT)
throw new GeneralException(ErrorStatus.NO_STUENT_TYPE);
throw new GeneralException(ErrorStatus.NO_STUDENT_TYPE);

Student student = member.getStudentProfile();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.assu.server.domain.report.controller;

import com.assu.server.domain.report.dto.ReportRequestDTO;
import com.assu.server.domain.report.dto.ReportResponseDTO;
import com.assu.server.domain.report.entity.enums.ReportTargetType;
import com.assu.server.domain.report.service.ReportService;
import com.assu.server.global.apiPayload.BaseResponse;
import com.assu.server.global.apiPayload.code.status.SuccessStatus;
import com.assu.server.global.util.PrincipalDetails;
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.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Report", description = "신고 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/reports")
public class ReportController {

private final ReportService reportService;

@Operation(summary = "콘텐츠 신고 API",
description = "# [v1.0 (2025-09-24)](https://clumsy-seeder-416.notion.site/API-2771197c19ed80b79afbf3d8d8d82c15?source=copy_link)\n" +
"- 신고자는 본인 Member ID로 자동 설정됩니다.\n" +
"- 자기 자신의 콘텐츠를 신고할 수 없습니다.\n" +
"- 동일한 대상을 중복 신고할 수 없습니다.\n\n" +
"**Request Body:**\n" +
"- `targetType` (String, required): 신고 대상 타입 (REVIEW, SUGGESTION)\n" +
"- `targetId` (Long, required): 리뷰 ID 또는 건의글 ID\n" +
"- `reportType` (String, required): 신고 유형\n" +
" - 리뷰 신고: REVIEW_INAPPROPRIATE_CONTENT, REVIEW_FALSE_INFORMATION, REVIEW_SPAM\n" +
" - 건의글 신고: SUGGESTION_INAPPROPRIATE_CONTENT, SUGGESTION_FALSE_INFORMATION, SUGGESTION_SPAM\n\n" +
"**Response:**\n" +
"- 성공 시 201(CREATED)과 신고 ID 반환")
@PostMapping
public BaseResponse<ReportResponseDTO.CreateReportResponse> reportContent(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestBody @Valid ReportRequestDTO.CreateContentReportRequest request
) {
Long reporterId = principalDetails.getMember().getId();
ReportResponseDTO.CreateReportResponse response = reportService.reportContent(reporterId, request);
return BaseResponse.onSuccess(SuccessStatus.REPORT_SUCCESS, response);
}

@Operation(summary = "작성자 신고 API",
description = "# [v1.0 (2025-09-24)](https://clumsy-seeder-416.notion.site/API-2771197c19ed80f8ab45e70772fcfc58?source=copy_link)\n" +
"- 신고자는 본인 Member ID로 자동 설정됩니다.\n" +
"- 자기 자신을 신고할 수 없습니다.\n" +
"- 동일한 작성자를 중복 신고할 수 없습니다.\n\n" +
"**Request Body:**\n" +
"- `targetType` (String, required): 신고 대상 타입 (REVIEW, SUGGESTION)\n" +
"- `targetId` (Long, required): 리뷰 ID 또는 건의글 ID\n" +
"- `reportType` (String, required): 신고 유형\n" +
" - 사용자 신고: USER_SPAM, USER_INAPPROPRIATE_CONTENT, USER_HARASSMENT, USER_FRAUD, USER_PRIVACY_VIOLATION, USER_OTHER\n\n"
+
"**Response:**\n" +
"- 성공 시 201(CREATED)과 신고 ID 반환")
@PostMapping("/students")
public BaseResponse<ReportResponseDTO.CreateReportResponse> reportStudent(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestBody @Valid ReportRequestDTO.CreateStudentReportRequest request
) {
Long reporterId = principalDetails.getMember().getId();
ReportResponseDTO.CreateReportResponse response = reportService.reportStudent(reporterId, request);
return BaseResponse.onSuccess(SuccessStatus.REPORT_SUCCESS, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.assu.server.domain.report.dto;

import com.assu.server.domain.report.entity.enums.ReportTargetType;
import com.assu.server.domain.report.entity.enums.ReportType;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class ReportRequestDTO {

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CreateContentReportRequest {
@NotNull(message = "신고 대상 타입은 필수입니다.")
private ReportTargetType targetType; // REVIEW, SUGGESTION

@NotNull(message = "신고 대상 ID는 필수입니다.")
private Long targetId; // 리뷰 ID 또는 건의글 ID

@NotNull(message = "신고 유형은 필수입니다.")
private ReportType reportType; // REVIEW_*, SUGGESTION_*
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CreateStudentReportRequest {
@NotNull(message = "신고 대상의 작성 컨텐츠의 타입은 필수입니다.")
private ReportTargetType targetType; // REVIEW, SUGGESTION

@NotNull(message = "신고 대상의 작성 컨텐츠 ID는 필수입니다.")
private Long targetId; // 리뷰 ID 또는 건의글 ID

@NotNull(message = "유저 신고 유형은 필수입니다.")
private ReportType reportType; // USER_*
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.assu.server.domain.report.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class ReportResponseDTO {

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CreateReportResponse {
private Long reportId;

public static CreateReportResponse of(Long reportId) {
return CreateReportResponse.builder()
.reportId(reportId)
.build();
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/com/assu/server/domain/report/entity/Report.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.assu.server.domain.report.entity;

import com.assu.server.domain.common.entity.BaseEntity;
import com.assu.server.domain.member.entity.Member;
import com.assu.server.domain.report.entity.enums.ReportStatus;
import com.assu.server.domain.report.entity.enums.ReportTargetType;
import com.assu.server.domain.report.entity.enums.ReportType;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Report extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reporter_id", nullable = false)
private Member reporter; // 신고자

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ReportTargetType targetType; // 신고 대상 타입 (STUDENT_USER, REVIEW, SUGGESTION)

@Column(nullable = false)
private Long targetId; // 신고 대상 ID (사용자 ID, 리뷰 ID, 건의 ID 등)

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reported_id")
private Member reported; // 피신고자 (사용자 신고인 경우에만)

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ReportType reportType; // 신고 유형

@Enumerated(EnumType.STRING)
@Builder.Default
private ReportStatus status = ReportStatus.PENDING; // 신고 상태

// Todo 관리자용 업데이트 로직 추가
// 신고 상태 업데이트 메서드
public void updateStatus(ReportStatus status) {
this.status = status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.assu.server.domain.report.entity.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ReportStatus {
PENDING("대기중"),
PROCESSED("처리완료"),
REJECTED("기각"),
UNDER_REVIEW("검토중");

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.assu.server.domain.report.entity.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ReportTargetType {
STUDENT_USER("학생 사용자"),
REVIEW("리뷰"),
SUGGESTION("건의글");

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.assu.server.domain.report.entity.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ReportType {
// 사용자 신고용
STUDENT_USER_SPAM("스팸/홍보"),
STUDENT_USER_INAPPROPRIATE_CONTENT("부적절한 내용"),
STUDENT_USER_HARASSMENT("괴롭힘/욕설"),
STUDENT_USER_FRAUD("사기/부정행위"),
STUDENT_USER_PRIVACY_VIOLATION("개인정보 침해"),
STUDENT_USER_OTHER("기타"),

// 리뷰 신고용
REVIEW_INAPPROPRIATE_CONTENT("부적절한 내용 및 욕설이 포함된 리뷰에요"),
REVIEW_FALSE_INFORMATION("허위사실 / 거짓이 포함된 리뷰에요"),
REVIEW_SPAM("홍보 / 광고를 위한 리뷰에요"),

// 건의글 신고용
SUGGESTION_INAPPROPRIATE_CONTENT("부적절한 내용 및 욕설이 포함된 건의글이에요"),
SUGGESTION_FALSE_INFORMATION("허위사실 / 거짓이 포함된 건의글에요"),
SUGGESTION_SPAM("홍보/광고를 위한 건의글이에요 ");

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.assu.server.domain.report.event;

import com.assu.server.domain.report.entity.enums.ReportTargetType;
import com.assu.server.domain.report.entity.enums.ReportStatus;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class ReportProcessedEvent {
private final Long reportId;
private final ReportTargetType targetType;
private final Long targetId;
private final ReportStatus status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.assu.server.domain.report.exception;

import com.assu.server.global.apiPayload.code.BaseErrorCode;
import com.assu.server.global.exception.GeneralException;

public class ReportException extends GeneralException {
public ReportException(BaseErrorCode errorStatus) { super(errorStatus); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.assu.server.domain.report.repository;

import com.assu.server.domain.report.entity.Report;
import com.assu.server.domain.report.entity.enums.ReportTargetType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ReportRepository extends JpaRepository<Report, Long> {
// 특정 사용자가 특정 대상을 신고했는지 확인
boolean existsByReporterIdAndTargetTypeAndTargetId(Long reporterId, ReportTargetType targetType, Long targetId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.assu.server.domain.report.service;

import com.assu.server.domain.report.dto.ReportRequestDTO;
import com.assu.server.domain.report.dto.ReportResponseDTO;

public interface ReportService {

// 콘텐츠 신고 생성 (리뷰, 건의글)
ReportResponseDTO.CreateReportResponse reportContent(Long reporterId, ReportRequestDTO.CreateContentReportRequest request);

// 작성자 신고 생성 (리뷰/건의글 작성자)
ReportResponseDTO.CreateReportResponse reportStudent(Long reporterId, ReportRequestDTO.CreateStudentReportRequest request);
}
Loading