Skip to content

Commit 5256d2f

Browse files
authored
Merge pull request #38 from CAPS-DGU/feature/#37
feat: #37 문의 신고 기능 추가
2 parents c8f645d + 3f49f59 commit 5256d2f

14 files changed

Lines changed: 380 additions & 1 deletion

File tree

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ dependencies {
4141

4242
// redis
4343
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
44-
4544
// aws s3
4645
implementation 'software.amazon.awssdk:s3:2.20.26'
4746
implementation 'software.amazon.awssdk:url-connection-client:2.20.26'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package kr.dgucaps.caps.domain.report.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import jakarta.validation.Valid;
9+
import kr.dgucaps.caps.domain.report.dto.request.CreateReportRequest;
10+
import kr.dgucaps.caps.domain.report.dto.response.ReportResponse;
11+
import kr.dgucaps.caps.global.annotation.Auth;
12+
import kr.dgucaps.caps.global.common.SuccessResponse;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.security.access.prepost.PreAuthorize;
15+
import org.springframework.web.bind.annotation.RequestBody;
16+
17+
@Tag(name = "Report", description = "문의 신고 API")
18+
public interface ReportApi {
19+
@Operation(
20+
summary = "문의 및 신고 작성",
21+
description = "문의 및 신고 내용을 서버에 제출합니다"
22+
)
23+
@ApiResponse(responseCode = "200", description = "문의 및 신고 성공",
24+
content = @Content(mediaType = "application/json",
25+
schema = @Schema(implementation = ReportResponse.class))
26+
)
27+
@PreAuthorize("hasAnyRole('MEMBER', 'GRADUATE', 'COUNCIL', 'PRESIDENT', 'ADMIN')")
28+
ResponseEntity<SuccessResponse<?>> createReport(
29+
@Auth Long memberId,
30+
@RequestBody @Valid CreateReportRequest request
31+
);
32+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package kr.dgucaps.caps.domain.report.controller;
2+
3+
import jakarta.validation.Valid;
4+
import kr.dgucaps.caps.domain.report.dto.request.CreateReportRequest;
5+
import kr.dgucaps.caps.domain.report.service.ReportService;
6+
import kr.dgucaps.caps.global.annotation.Auth;
7+
import kr.dgucaps.caps.global.common.SuccessResponse;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.security.access.prepost.PreAuthorize;
11+
import org.springframework.stereotype.Controller;
12+
import org.springframework.validation.annotation.Validated;
13+
import org.springframework.web.bind.annotation.PostMapping;
14+
import org.springframework.web.bind.annotation.RequestBody;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
17+
@Controller
18+
@Validated
19+
@RequiredArgsConstructor
20+
@RequestMapping("/api/v1/report")
21+
public class ReportController implements ReportApi {
22+
23+
private final ReportService reportService;
24+
25+
@PreAuthorize("hasAnyRole('MEMBER', 'GRADUATE', 'COUNCIL', 'PRESIDENT', 'ADMIN')")
26+
@Override
27+
@PostMapping
28+
public ResponseEntity<SuccessResponse<?>> createReport(
29+
@Auth Long memberId,
30+
@RequestBody @Valid CreateReportRequest request
31+
) {
32+
return SuccessResponse.ok(reportService.createReport(memberId, request));
33+
}
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package kr.dgucaps.caps.domain.report.dto.request;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import kr.dgucaps.caps.domain.member.entity.Member;
5+
import kr.dgucaps.caps.domain.report.entity.Category;
6+
import kr.dgucaps.caps.domain.report.entity.Report;
7+
8+
public record CreateReportRequest(
9+
@NotBlank String content,
10+
String[] fileUrls,
11+
Category category
12+
) {
13+
public Report toReportEntity(Member member) {
14+
return Report.builder()
15+
.member(member)
16+
.content(content)
17+
.category(category)
18+
.build();
19+
}
20+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package kr.dgucaps.caps.domain.report.dto.response;
2+
3+
import kr.dgucaps.caps.domain.report.entity.Category;
4+
import kr.dgucaps.caps.domain.report.entity.Report;
5+
import lombok.Builder;
6+
7+
import java.util.List;
8+
9+
@Builder
10+
public record ReportResponse(
11+
Integer id,
12+
String content,
13+
Category category,
14+
List<String> fileUrls
15+
) {
16+
public static ReportResponse from(Report report, List<String> fileUrls) {
17+
return ReportResponse.builder()
18+
.id(report.getId())
19+
.content(report.getContent())
20+
.category(report.getCategory())
21+
.fileUrls(fileUrls)
22+
.build();
23+
}
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package kr.dgucaps.caps.domain.report.entity;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
import lombok.Getter;
6+
import lombok.RequiredArgsConstructor;
7+
8+
@Getter
9+
@RequiredArgsConstructor
10+
public enum Category {
11+
INFO_ERROR("정보 오류"),
12+
ACCOUNT_MANAGEMENT("계정 관리"),
13+
SUGGESTION("건의사항"),
14+
USER_REPORT_AND_SECURITY_REPORT("유저 신고 및 보안 제보"),
15+
ETC("기타");
16+
private final String title;
17+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package kr.dgucaps.caps.domain.report.entity;
2+
3+
import jakarta.persistence.*;
4+
import kr.dgucaps.caps.domain.member.entity.Member;
5+
import lombok.AccessLevel;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
9+
10+
@Entity
11+
@Getter
12+
@Table(name = "report")
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
public class Report {
15+
16+
@Id
17+
@GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Integer id;
19+
20+
@ManyToOne(fetch = FetchType.LAZY)
21+
@JoinColumn(name = "member_id", nullable = false)
22+
private Member member;
23+
24+
@Column(nullable = false, columnDefinition = "TEXT")
25+
private String content;
26+
27+
@Enumerated(EnumType.STRING)
28+
@Column(nullable = false)
29+
private Category category;
30+
31+
@Builder
32+
public Report(Member member, String content, Category category) {
33+
this.member = member;
34+
this.content = content;
35+
this.category = category;
36+
}
37+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package kr.dgucaps.caps.domain.report.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.AccessLevel;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
@Entity
9+
@Getter
10+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
11+
@Table(name = "report_file")
12+
public class ReportFile {
13+
14+
@Id
15+
@GeneratedValue(strategy = GenerationType.IDENTITY)
16+
private Integer id;
17+
18+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
19+
@JoinColumn(name = "report_id", nullable = false)
20+
private Report report;
21+
22+
@Column(name = "file_url", nullable = false, length = 256)
23+
private String fileUrl;
24+
25+
public ReportFile(Report report, String fileUrl) {
26+
this.report = report;
27+
this.fileUrl = fileUrl;
28+
}
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package kr.dgucaps.caps.domain.report.integration;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.http.*;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.web.client.RestTemplate;
8+
9+
import java.util.Map;
10+
11+
@Component
12+
@RequiredArgsConstructor
13+
public class DiscordWebhookSender {
14+
15+
private final RestTemplate restTemplate = new RestTemplate();
16+
17+
@Value("${discord.webhook.uri}")
18+
private String webhookUri;
19+
20+
@Value("${discord.webhook.name:CAPS Report}")
21+
private String webhookName;
22+
23+
public void send(String message) {
24+
HttpHeaders headers = new HttpHeaders();
25+
headers.setContentType(MediaType.APPLICATION_JSON);
26+
27+
Map<String, Object> body = Map.of(
28+
"username", webhookName,
29+
"content", truncate(message, 1800)
30+
);
31+
32+
restTemplate.postForEntity(webhookUri, new HttpEntity<>(body, headers), String.class);
33+
}
34+
35+
private String truncate(String s, int max) {
36+
if (s == null) return "";
37+
if (s.length() <= max) return s;
38+
return s.substring(0, max - 3) + "...";
39+
}
40+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package kr.dgucaps.caps.domain.report.integration;
2+
3+
import kr.dgucaps.caps.domain.report.entity.Report;
4+
5+
import java.util.List;
6+
7+
public record ReportCreatedEvent(
8+
Report report,
9+
List<String> fileUrls
10+
) {}

0 commit comments

Comments
 (0)