Skip to content

Commit 6cfda26

Browse files
committed
dev 반영
2 parents 449b4a4 + bcf2f24 commit 6cfda26

File tree

101 files changed

+2390
-608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+2390
-608
lines changed

build.gradle

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ dependencies {
7373
// Mac에서 빌드시 필요한 의존성
7474
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.121.Final:osx-aarch_64'
7575

76-
// actuator
76+
// actuator + 프로메테우스
7777
implementation 'org.springframework.boot:spring-boot-starter-actuator'
78+
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
7879

7980
// elasticsearch + elasticsearch 쿼리빌더 dsl
8081
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
@@ -99,7 +100,7 @@ dependencies {
99100
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
100101
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
101102
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'
102-
103+
103104
// mongodb
104105
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
105106

@@ -110,8 +111,12 @@ dependencies {
110111
//email-smtp
111112
implementation 'org.springframework.boot:spring-boot-starter-mail'
112113

114+
// S3 SDK
115+
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
116+
113117
//AES 암호화
114-
implementation 'javax.xml.bind:jaxb-api:2.3.1'}
118+
implementation 'javax.xml.bind:jaxb-api:2.3.1'
119+
}
115120

116121
tasks.named('test') {
117122
useJUnitPlatform()

src/main/java/org/ezcode/codetest/application/community/dto/response/DiscussionResponse.java

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22

33
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;
44

5+
import java.time.LocalDateTime;
6+
57
import org.ezcode.codetest.application.usermanagement.user.dto.response.SimpleUserInfoResponse;
8+
import org.ezcode.codetest.domain.community.dto.DiscussionQueryResult;
69
import org.ezcode.codetest.domain.community.model.entity.Discussion;
10+
import org.ezcode.codetest.domain.community.model.enums.VoteType;
711

812
import io.swagger.v3.oas.annotations.media.Schema;
913

10-
@Schema(name = "DiscussionResponse", description = "Discussion 조회 응답 DTO")
14+
@Schema(name = "DiscussionResponse", description = "Discussion 응답 DTO, 목록 조회 시에만 추천 수, 비추천 수 등의 데이터가 포함됨")
1115
public record DiscussionResponse(
16+
1217
@Schema(description = "Discussion 고유 ID", example = "123", requiredMode = REQUIRED)
1318
Long discussionId,
1419

@@ -18,20 +23,51 @@ public record DiscussionResponse(
1823
@Schema(description = "관련 문제 ID", example = "45", requiredMode = REQUIRED)
1924
Long problemId,
2025

21-
@Schema(description = "사용 언어명", example = "Java 17", requiredMode = REQUIRED)
22-
String languages,
23-
2426
@Schema(description = "토론 내용", example = "이 문제는 이렇게 풀 수 있습니다...", requiredMode = REQUIRED)
25-
String content
27+
String content,
28+
29+
@Schema(description = "생성 일시", example = "2025-06-25T14:30:00", requiredMode = REQUIRED)
30+
LocalDateTime createdAt,
31+
32+
@Schema(description = "총 추천 수 (upvote)", example = "10")
33+
Long upvoteCount,
34+
35+
@Schema(description = "총 비추천 수 (downvote)", example = "2")
36+
Long downvoteCount,
37+
38+
@Schema(description = "총 댓글 수", example = "5")
39+
Long replyCount,
40+
41+
@Schema(description = "현재 사용자의 추천 상태 (UP, DOWN, NONE)", example = "UP")
42+
VoteType voteStatus
43+
2644
) {
2745

2846
public static DiscussionResponse fromEntity(Discussion discussion) {
2947
return new DiscussionResponse(
3048
discussion.getId(),
3149
SimpleUserInfoResponse.fromEntity(discussion.getUser()),
32-
discussion.getProblem().getId(), // 문제 id가 굳이 필요한가?
33-
discussion.getLanguage().getName(), // TODO: 가공해줘야 할듯?
34-
discussion.getContent()
50+
discussion.getProblem().getId(),
51+
discussion.getContent(),
52+
discussion.getCreatedAt(),
53+
null,
54+
null,
55+
null,
56+
null
57+
);
58+
}
59+
60+
public static DiscussionResponse from(DiscussionQueryResult result) {
61+
return new DiscussionResponse(
62+
result.getDiscussionId(),
63+
result.getUserInfo(),
64+
result.getProblemId(),
65+
result.getContent(),
66+
result.getCreatedAt(),
67+
result.getUpvoteCount(),
68+
result.getDownvoteCount(),
69+
result.getReplyCount(),
70+
result.getVoteStatus()
3571
);
3672
}
3773
}

src/main/java/org/ezcode/codetest/application/community/dto/response/ReplyResponse.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;
44

5+
import java.time.LocalDateTime;
6+
57
import org.ezcode.codetest.application.usermanagement.user.dto.response.SimpleUserInfoResponse;
8+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
69
import org.ezcode.codetest.domain.community.model.entity.Reply;
10+
import org.ezcode.codetest.domain.community.model.enums.VoteType;
711

812
import io.swagger.v3.oas.annotations.media.Schema;
913

1014
@Schema(name = "ReplyResponse", description = "Reply 조회 응답 DTO")
1115
public record ReplyResponse(
16+
1217
@Schema(description = "Reply 고유 ID", example = "456", requiredMode = REQUIRED)
1318
Long replyId,
1419

1520
@Schema(description = "부모 Reply ID (없으면 null)", example = "123", nullable = true, requiredMode = NOT_REQUIRED)
16-
Long parentId,
21+
Long parentReplyId,
1722

1823
@Schema(description = "소속 Discussion ID", example = "123", requiredMode = REQUIRED)
1924
Long discussionId,
@@ -22,19 +27,58 @@ public record ReplyResponse(
2227
SimpleUserInfoResponse userInfo,
2328

2429
@Schema(description = "댓글 내용", example = "동의합니다!", requiredMode = REQUIRED)
25-
String content
30+
String content,
31+
32+
@Schema(description = "생성 일시", example = "2025-06-25T14:30:00", requiredMode = REQUIRED)
33+
LocalDateTime createdAt,
34+
35+
@Schema(description = "총 추천 수 (upvote)", example = "10")
36+
Long upvoteCount,
37+
38+
@Schema(description = "총 비추천 수 (downvote)", example = "2")
39+
Long downvoteCount,
40+
41+
@Schema(description = "총 댓글 수", example = "5")
42+
Long childReplyCount,
43+
44+
@Schema(description = "현재 사용자의 추천 상태 (UP, DOWN, NONE)", example = "UP")
45+
VoteType voteStatus
46+
2647
) {
2748

2849
public static ReplyResponse fromEntity(Reply reply) {
50+
2951
Long parentId = (reply.getParent() != null)
3052
? reply.getParent().getId()
3153
: null;
54+
3255
return new ReplyResponse(
3356
reply.getId(),
3457
parentId,
3558
reply.getDiscussion().getId(),
3659
SimpleUserInfoResponse.fromEntity(reply.getUser()),
37-
reply.getContent()
60+
reply.getContent(),
61+
reply.getCreatedAt(),
62+
null,
63+
null,
64+
null,
65+
null
66+
);
67+
}
68+
69+
public static ReplyResponse from(ReplyQueryResult result) {
70+
71+
return new ReplyResponse(
72+
result.getReplyId(),
73+
result.getParentReplyId(),
74+
result.getDiscussionId(),
75+
result.getUserInfo(),
76+
result.getContent(),
77+
result.getCreatedAt(),
78+
result.getUpvoteCount(),
79+
result.getDownvoteCount(),
80+
result.getChildReplyCount(),
81+
result.getVoteStatus()
3882
);
3983
}
4084
}

src/main/java/org/ezcode/codetest/application/community/dto/response/VoteResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;
44

5-
import org.ezcode.codetest.domain.community.model.VoteResult;
5+
import org.ezcode.codetest.domain.community.dto.VoteResult;
66
import org.ezcode.codetest.domain.community.model.enums.VoteType;
77

88
import io.swagger.v3.oas.annotations.media.Schema;

src/main/java/org/ezcode/codetest/application/community/service/BaseVoteService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.ezcode.codetest.application.community.service;
22

33
import org.ezcode.codetest.application.community.dto.response.VoteResponse;
4-
import org.ezcode.codetest.domain.community.model.VoteResult;
4+
import org.ezcode.codetest.domain.community.dto.VoteResult;
55
import org.ezcode.codetest.domain.community.model.entity.BaseVote;
66
import org.ezcode.codetest.domain.community.model.enums.VoteType;
77
import org.ezcode.codetest.domain.community.service.BaseVoteDomainService;

src/main/java/org/ezcode/codetest/application/community/service/DiscussionService.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.ezcode.codetest.application.community.dto.request.DiscussionCreateRequest;
44
import org.ezcode.codetest.application.community.dto.request.DiscussionModifyRequest;
55
import org.ezcode.codetest.application.community.dto.response.DiscussionResponse;
6+
import org.ezcode.codetest.domain.community.dto.DiscussionQueryResult;
67
import org.ezcode.codetest.domain.community.model.entity.Discussion;
78
import org.ezcode.codetest.domain.community.service.DiscussionDomainService;
89
import org.ezcode.codetest.domain.language.model.entity.Language;
@@ -40,10 +41,11 @@ public DiscussionResponse createDiscussion(Long problemId, DiscussionCreateReque
4041
}
4142

4243
@Transactional(readOnly = true)
43-
public Page<DiscussionResponse> getDiscussions(Long problemId, Pageable pageable) {
44+
public Page<DiscussionResponse> getDiscussions(Long problemId, String sortBy, Long userId, Pageable pageable) {
4445

45-
Page<Discussion> discussionResponsePage = discussionDomainService.getAllDiscussionsByProblemId(problemId, pageable);
46-
return discussionResponsePage.map(DiscussionResponse::fromEntity);
46+
Page<DiscussionQueryResult> result =
47+
discussionDomainService.getAllDiscussionsByProblemId(problemId, sortBy, userId, pageable);
48+
return result.map(DiscussionResponse::from);
4749
}
4850

4951
@Transactional

src/main/java/org/ezcode/codetest/application/community/service/ReplyService.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.ezcode.codetest.application.community.dto.response.ReplyResponse;
88
import org.ezcode.codetest.application.notification.event.NotificationCreateEvent;
99
import org.ezcode.codetest.application.notification.port.NotificationEventService;
10+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
1011
import org.ezcode.codetest.domain.community.model.entity.Discussion;
1112
import org.ezcode.codetest.domain.community.model.entity.Reply;
1213
import org.ezcode.codetest.domain.community.service.DiscussionDomainService;
@@ -58,28 +59,30 @@ public ReplyResponse createReply(
5859
}
5960

6061
@Transactional(readOnly = true)
61-
public Page<ReplyResponse> getReplies(Long problemId, Long discussionId, Pageable pageable) {
62+
public Page<ReplyResponse> getReplies(Long problemId, Long discussionId, Long currentUserId, Pageable pageable) {
6263

6364
Discussion discussion = discussionDomainService.getDiscussionForProblem(discussionId, problemId);
6465

65-
Page<Reply> replies = replyDomainService.getRepliesByDiscussionId(discussion, pageable);
66+
Page<ReplyQueryResult> replies = replyDomainService.getRepliesByDiscussionId(discussion, currentUserId, pageable);
6667

67-
return replies.map(ReplyResponse::fromEntity);
68+
return replies.map(ReplyResponse::from);
6869
}
6970

7071
@Transactional(readOnly = true)
7172
public Page<ReplyResponse> getChildReplies(
7273
Long problemId,
7374
Long discussionId,
7475
Long parentReplyId,
76+
Long currentUserId,
7577
Pageable pageable
7678
) {
7779

7880
Discussion discussion = discussionDomainService.getDiscussionForProblem(discussionId, problemId);
7981

80-
Page<Reply> replies = replyDomainService.getRepliesByParentReplyId(parentReplyId, discussion, pageable);
82+
Page<ReplyQueryResult> childReplies =
83+
replyDomainService.getRepliesByParentReplyId(parentReplyId, discussion, currentUserId, pageable);
8184

82-
return replies.map(ReplyResponse::fromEntity);
85+
return childReplies.map(ReplyResponse::from);
8386
}
8487

8588
@Transactional

src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,51 @@
11
package org.ezcode.codetest.application.problem.service;
22

33
import org.ezcode.codetest.application.problem.dto.request.ProblemCreateRequest;
4-
import org.ezcode.codetest.domain.problem.model.ProblemSearchCondition;
54
import org.ezcode.codetest.application.problem.dto.request.ProblemUpdateRequest;
65
import org.ezcode.codetest.application.problem.dto.response.ProblemDetailResponse;
76
import org.ezcode.codetest.application.problem.dto.response.ProblemResponse;
7+
import org.ezcode.codetest.domain.problem.model.ProblemSearchCondition;
88
import org.ezcode.codetest.domain.problem.model.entity.Problem;
99
import org.ezcode.codetest.domain.problem.service.ProblemDomainService;
1010
import org.ezcode.codetest.domain.user.model.entity.AuthUser;
1111
import org.ezcode.codetest.domain.user.model.entity.User;
1212
import org.ezcode.codetest.domain.user.service.UserDomainService;
13+
import org.ezcode.codetest.infrastructure.s3.S3Directory;
14+
import org.ezcode.codetest.infrastructure.s3.S3Uploader;
15+
import org.ezcode.codetest.infrastructure.s3.exception.S3Exception;
16+
import org.ezcode.codetest.infrastructure.s3.exception.code.S3ExceptionCode;
1317
import org.springframework.data.domain.Page;
1418
import org.springframework.data.domain.Pageable;
1519
import org.springframework.stereotype.Service;
1620
import org.springframework.transaction.annotation.Transactional;
21+
import org.springframework.web.multipart.MultipartFile;
1722

1823
import lombok.RequiredArgsConstructor;
24+
import lombok.extern.slf4j.Slf4j;
1925

2026
@Service
2127
@RequiredArgsConstructor
28+
@Slf4j
2229
public class ProblemService {
2330

2431
private final ProblemDomainService problemDomainService;
2532
private final UserDomainService userDomainService;
33+
private final S3Uploader s3Uploader;
2634

2735
// 문제 생성 ( 관리자 )
2836
@Transactional
29-
public ProblemDetailResponse createProblem(ProblemCreateRequest requestDto, AuthUser authUser) {
37+
public ProblemDetailResponse createProblem(ProblemCreateRequest requestDto, MultipartFile image, AuthUser authUser) {
3038

3139
User user = userDomainService.getUserById(authUser.getId());
3240

33-
Problem savedProblem = problemDomainService.createProblem(
34-
ProblemCreateRequest.toEntity(requestDto, user)
35-
);
41+
Problem problem = ProblemCreateRequest.toEntity(requestDto, user);
42+
Problem savedProblem = problemDomainService.createProblem(problem);
43+
44+
// 문제 이미지 있다면?
45+
if (image != null && !image.isEmpty()) {
46+
String imageUrl = uploadImageAfterTransaction(image, savedProblem.getId());
47+
updateProblemWithImage(savedProblem.getId(), imageUrl);
48+
}
3649

3750
return ProblemDetailResponse.from(savedProblem);
3851
}
@@ -82,5 +95,20 @@ public void removeProblem(Long problemId) {
8295

8396
problemDomainService.removeProblem(findProblem);
8497
}
98+
99+
@Transactional
100+
public void updateProblemWithImage(Long problemId, String imageUrl) {
101+
Problem problem = problemDomainService.getProblem(problemId);
102+
problem.addImage(imageUrl);
103+
}
104+
105+
private String uploadImageAfterTransaction(MultipartFile image, Long problemId) {
106+
try {
107+
return s3Uploader.upload(image, S3Directory.PROBLEM.getDir());
108+
} catch (Exception e) {
109+
log.error("Problem {} 이미지 업로드 실패", problemId, e);
110+
throw new S3Exception(S3ExceptionCode.S3_UPLOAD_FAILED);
111+
}
112+
}
85113
}
86114

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.ezcode.codetest.application.report.dto;
2+
3+
import org.ezcode.codetest.domain.report.model.Report;
4+
5+
public record ReportMyResponse(
6+
Long id,
7+
Long targetId,
8+
String targetType,
9+
String reportType,
10+
String reportStatus,
11+
String message,
12+
String imageUrl,
13+
String resultMessage,
14+
String createdAt
15+
) {
16+
public static ReportMyResponse from(Report r) {
17+
return new ReportMyResponse(
18+
r.getId(),
19+
r.getTargetId(),
20+
r.getTargetType().name(),
21+
r.getReportType().name(),
22+
r.getReportStatus().name(),
23+
r.getMessage(),
24+
r.getImageUrl(),
25+
r.getResultMessage(),
26+
r.getCreatedAt().toString()
27+
);
28+
}
29+
}

0 commit comments

Comments
 (0)