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 @@ -31,7 +31,8 @@ public class SecurityPath {
"/api/posts/*",
"/api/search/history",
"/api/search/history/*",
"/api/mongo/services/matched"
"/api/mongo/services/matched",
"/api/users/reports"
};

// hasRole("ADMIN")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ public enum ErrorCode {
INVALID_CREDENTIALS(HttpStatus.UNAUTHORIZED, "AUTH-006", "์œ ํšจํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค."),
INVALID_SECRET_KEY(HttpStatus.UNAUTHORIZED, "AUTH-007", "์œ ํšจํ•˜์ง€ ์•Š์€ ๋น„๋ฐ€ ํ‚ค์ž…๋‹ˆ๋‹ค."),
DELETE_USER_DENIED(HttpStatus.FORBIDDEN, "AUTH-008", "ํšŒ์› ํƒˆํ‡ด๊ฐ€ ๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."),
ROLE_NOT_FOUND(HttpStatus.FORBIDDEN, "AUTH-009", "๊ถŒํ•œ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."),
BLACKLIST_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH-010", "์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ์•ก์„ธ์Šค ํ† ํฐ์ž…๋‹ˆ๋‹ค."),
CANNOT_REPORT_SELF(HttpStatus.BAD_REQUEST, "AUTH-009","์ž๊ธฐ ์ž์‹ ์„ ์‹ ๊ณ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),
ROLE_NOT_FOUND(HttpStatus.FORBIDDEN, "AUTH-010", "๊ถŒํ•œ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."),
BLACKLIST_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH-011", "์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ์•ก์„ธ์Šค ํ† ํฐ์ž…๋‹ˆ๋‹ค."),
REPORT_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH-012", "ํ•ด๋‹น ์‹ ๊ณ  ๋‚ด์—ญ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),
REPORT_ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, "AUTH-013", "์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์‹ ๊ณ ์ž…๋‹ˆ๋‹ค."),
INVALID_REPORT_REQUEST(HttpStatus.BAD_REQUEST, "AUTH-014","์ž˜๋ชป๋œ ์‹ ๊ณ  ์š”์ฒญ์ž…๋‹ˆ๋‹ค."),

// ๊ณ„์ • ๊ด€๋ จ
DUPLICATED_REAL_ID(HttpStatus.CONFLICT, "ACCOUNT-001", "์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์•„์ด๋””์ž…๋‹ˆ๋‹ค."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.hyetaekon.hyetaekon.user.controller;

import com.hyetaekon.hyetaekon.common.exception.ErrorCode;
import com.hyetaekon.hyetaekon.common.exception.GlobalException;
import com.hyetaekon.hyetaekon.user.dto.admin.UserAdminResponseDto;
import com.hyetaekon.hyetaekon.user.dto.admin.UserReportResponseDto;
import com.hyetaekon.hyetaekon.user.dto.admin.UserSuspendRequestDto;
import com.hyetaekon.hyetaekon.user.entity.ReportStatus;
import com.hyetaekon.hyetaekon.user.service.UserAdminService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -32,7 +35,7 @@ public ResponseEntity<Page<UserAdminResponseDto>> getAllUsers(
*/
@PostMapping("/users/{userId}/suspend")
public ResponseEntity<Void> suspendUser(
@PathVariable Long userId,
@PathVariable("userId") Long userId,
@RequestBody UserSuspendRequestDto requestDto) {
userAdminService.suspendUser(userId, requestDto);
return ResponseEntity.ok().build();
Expand All @@ -42,7 +45,7 @@ public ResponseEntity<Void> suspendUser(
* ์ •์ง€ ํ•ด์ œ
*/
@PutMapping("/users/{userId}/unsuspend")
public ResponseEntity<Void> unsuspendUser(@PathVariable Long userId) {
public ResponseEntity<Void> unsuspendUser(@PathVariable("userId") Long userId) {
userAdminService.unsuspendUser(userId);
return ResponseEntity.ok().build();
}
Expand Down Expand Up @@ -77,4 +80,42 @@ public ResponseEntity<Page<UserReportResponseDto>> getUserReports(
return ResponseEntity.ok(userAdminService.getUserReports(page, size));
}

/**
* ์ƒํƒœ๋ณ„ ์‹ ๊ณ  ๋‚ด์—ญ ์กฐํšŒ
*/
@GetMapping("/users/reports/status/{status}")
public ResponseEntity<Page<UserReportResponseDto>> getReportsByStatus(
@PathVariable("status") ReportStatus status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return ResponseEntity.ok(userAdminService.getReportsByStatus(status, page, size));
}

/**
* ์‹ ๊ณ  ์Šน์ธ ์ฒ˜๋ฆฌ
*/
@PostMapping("/users/reports/{reportId}/resolve")
public ResponseEntity<Void> resolveReport(
@PathVariable("reportId") Long reportId,
@RequestParam(defaultValue = "false") boolean suspendUser,
@RequestBody(required = false) UserSuspendRequestDto suspendRequestDto) {

// ์‚ฌ์šฉ์ž ์ •์ง€ ์š”์ฒญ์ด ์žˆ์ง€๋งŒ ์ •์ง€ ์ •๋ณด๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
if (suspendUser && suspendRequestDto == null) {
throw new GlobalException(ErrorCode.INVALID_REPORT_REQUEST);
}

userAdminService.resolveReport(reportId, suspendUser, suspendRequestDto);
return ResponseEntity.ok().build();
}

/**
* ์‹ ๊ณ  ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ
*/
@PostMapping("/users/reports/{reportId}/reject")
public ResponseEntity<Void> rejectReport(@PathVariable("reportId") Long reportId) {
userAdminService.rejectReport(reportId);
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ public ResponseEntity<Void> updateMyPassword(
@DeleteMapping("/users/me")
public ResponseEntity<Void> deleteUser(
@AuthenticationPrincipal CustomUserDetails customUserDetails,
@RequestBody String deleteReason,
@RequestBody UserDeleteRequestDto deleteRequestDto,
@CookieValue(name = "refreshToken", required = false) String refreshToken,
@RequestHeader("Authorization") String authHeader
) {
String accessToken = authHeader.replace("Bearer ", "");
userService.deleteUser(customUserDetails.getId(), deleteReason, accessToken, refreshToken);
userService.deleteUser(customUserDetails.getId(), deleteRequestDto.getDeleteReason(), accessToken, refreshToken);

return ResponseEntity.noContent().build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.hyetaekon.hyetaekon.user.controller;

import com.hyetaekon.hyetaekon.common.jwt.CustomUserDetails;
import com.hyetaekon.hyetaekon.user.dto.UserReportRequestDto;
import com.hyetaekon.hyetaekon.user.service.UserReportService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/api/users/reports")
@RequiredArgsConstructor
public class UserReportController {
private final UserReportService userReportService;

@PostMapping
public ResponseEntity<Void> reportUser(
@AuthenticationPrincipal CustomUserDetails userDetails,
@Valid @RequestBody UserReportRequestDto reportRequestDto
) {
Long reporterId = userDetails.getId();
userReportService.reportUser(reporterId, reportRequestDto);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hyetaekon.hyetaekon.user.dto;

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

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDeleteRequestDto {
private String deleteReason;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.hyetaekon.hyetaekon.user.dto;

import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserReportRequestDto {
@NotNull(message = "์‹ ๊ณ  ๋Œ€์ƒ ์‚ฌ์šฉ์ž ID๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private Long reportedUserId;

@NotNull(message = "์‹ ๊ณ  ์‚ฌ์œ ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private String reason;

private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.hyetaekon.hyetaekon.user.dto.admin;

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

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserReportProcessDto {
private boolean suspendUser; // ์‹ ๊ณ  ์ฒ˜๋ฆฌ ์‹œ ์‚ฌ์šฉ์ž ์ •์ง€ ์—ฌ๋ถ€

// ์‚ฌ์šฉ์ž ์ •์ง€์‹œ ํ•„์š”ํ•œ ์ •๋ณด (suspendUser๊ฐ€ true์ผ ๋•Œ)
private LocalDateTime suspendStartAt;
private LocalDateTime suspendEndAt;
private String suspendReason;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.hyetaekon.hyetaekon.user.entity;

import lombok.Getter;

@Getter
public enum ReportStatus {
PENDING("์ฒ˜๋ฆฌ ๋Œ€๊ธฐ์ค‘"),
RESOLVED("์ฒ˜๋ฆฌ ์™„๋ฃŒ"),
REJECTED("๊ฑฐ๋ถ€๋จ");

private final String description;

ReportStatus(String description) {
this.description = description;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,22 @@ public class UserReport {
private String content;

@Column(name = "status", length = 20)
private String status;
@Enumerated(EnumType.STRING)
private ReportStatus status;

@Column(name = "created_at")
private LocalDateTime createdAt;

@Column(name = "processed_at")
private LocalDateTime processedAt;

public void resolve() {
this.status = ReportStatus.RESOLVED;
this.processedAt = LocalDateTime.now();
}

public void reject() {
this.status = ReportStatus.REJECTED;
this.processedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ public interface UserAdminMapper {
// UserReport Entity -> ์‹ ๊ณ  ๋‚ด์—ญ DTO ๋ณ€ํ™˜
@Mapping(source = "reporter.nickname", target = "reporterNickname")
@Mapping(source = "reported.nickname", target = "reportedNickname")
@Mapping(source = "status.description", target = "status")
UserReportResponseDto toReportResponseDto(UserReport userReport);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.hyetaekon.hyetaekon.user.repository;


import com.hyetaekon.hyetaekon.user.entity.ReportStatus;
import com.hyetaekon.hyetaekon.user.entity.UserReport;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserReportRepository extends JpaRepository<UserReport, Long> {
// ๊ธฐ๋ณธ CRUD ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
// ์ƒํƒœ๋ณ„ ์‹ ๊ณ  ๋ชฉ๋ก ์กฐํšŒ
Page<UserReport> findByStatus(ReportStatus status, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.hyetaekon.hyetaekon.common.exception.ErrorCode;
import com.hyetaekon.hyetaekon.common.exception.GlobalException;
import com.hyetaekon.hyetaekon.user.dto.admin.UserAdminResponseDto;
import com.hyetaekon.hyetaekon.user.dto.admin.UserReportProcessDto;
import com.hyetaekon.hyetaekon.user.dto.admin.UserReportResponseDto;
import com.hyetaekon.hyetaekon.user.dto.admin.UserSuspendRequestDto;
import com.hyetaekon.hyetaekon.user.entity.ReportStatus;
import com.hyetaekon.hyetaekon.user.entity.User;
import com.hyetaekon.hyetaekon.user.entity.UserReport;
import com.hyetaekon.hyetaekon.user.mapper.UserAdminMapper;
Expand Down Expand Up @@ -107,7 +109,7 @@ public Page<UserAdminResponseDto> getWithdrawnUsers(int page, int size) {
}

/**
* ์‹ ๊ณ  ๋‚ด์—ญ ์กฐํšŒ
* ์‹ ๊ณ  ๋‚ด์—ญ ์กฐํšŒ (์ „์ฒด)
*/
@Transactional(readOnly = true)
public Page<UserReportResponseDto> getUserReports(int page, int size) {
Expand All @@ -116,5 +118,60 @@ public Page<UserReportResponseDto> getUserReports(int page, int size) {
return reportPage.map(userAdminMapper::toReportResponseDto);
}

/**
* ์ƒํƒœ๋ณ„ ์‹ ๊ณ  ๋‚ด์—ญ ์กฐํšŒ
*/
@Transactional(readOnly = true)
public Page<UserReportResponseDto> getReportsByStatus(ReportStatus status, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Page<UserReport> reportPage = userReportRepository.findByStatus(status, pageable);
return reportPage.map(userAdminMapper::toReportResponseDto);
}

/**
* ์‹ ๊ณ  ์Šน์ธ ์ฒ˜๋ฆฌ
*/
@Transactional
public void resolveReport(Long reportId, boolean suspendUser, UserSuspendRequestDto suspendRequestDto) {
UserReport report = userReportRepository.findById(reportId)
.orElseThrow(() -> new GlobalException(ErrorCode.REPORT_NOT_FOUND));

// ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์‹ ๊ณ ์ธ์ง€ ํ™•์ธ
if (report.getStatus() != ReportStatus.PENDING) {
throw new GlobalException(ErrorCode.REPORT_ALREADY_PROCESSED);
}

// ์‹ ๊ณ  ์Šน์ธ ์ฒ˜๋ฆฌ
report.resolve();

// ์‹ ๊ณ ๋‹นํ•œ ์‚ฌ์šฉ์ž ์ •์ง€ ์ฒ˜๋ฆฌ ์—ฌ๋ถ€ ํ™•์ธ
if (suspendUser && suspendRequestDto != null) {
User reportedUser = report.getReported();
suspendUser(reportedUser.getId(), suspendRequestDto);
log.info("์‹ ๊ณ ์— ๋”ฐ๋ฅธ ์‚ฌ์šฉ์ž {} ์ •์ง€ ์ฒ˜๋ฆฌ ์™„๋ฃŒ", reportedUser.getId());
}

userReportRepository.save(report);
log.info("์‹ ๊ณ  {} ์Šน์ธ ์ฒ˜๋ฆฌ ์™„๋ฃŒ", reportId);
}

/**
* ์‹ ๊ณ  ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ
*/
@Transactional
public void rejectReport(Long reportId) {
UserReport report = userReportRepository.findById(reportId)
.orElseThrow(() -> new GlobalException(ErrorCode.REPORT_NOT_FOUND));

// ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์‹ ๊ณ ์ธ์ง€ ํ™•์ธ
if (report.getStatus() != ReportStatus.PENDING) {
throw new GlobalException(ErrorCode.REPORT_ALREADY_PROCESSED);
}

report.reject();
userReportRepository.save(report);
log.info("์‹ ๊ณ  {} ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ ์™„๋ฃŒ", reportId);
}


}
Loading