Skip to content

Commit 2ef0a49

Browse files
authored
feat : 알림, 커뮤니티 프론트 구현 및 동작 테스트 (#157)
* feat : 커뮤니티 조회 부분 구현 * feat : 커뮤니티 토론글 및 댓글 작성 기능 구현 * feat : 토론글/댓글/대댓글 수정, 삭제, 추천 기능 구현 - 수정, 삭제 권한을 프론트 측에서 알기 위해서 isAuthor 필드 추가 * feat : 페이징 구현, 댓글 생성 버그 수정, 폰트 크기 수정 등 * feat : 알림 웹소켓 연결 및 모달 구현 * feat : 복구 완료 * feat : 정상 동작 확인 * feat : 정상 동작 확인 2
1 parent 113458d commit 2ef0a49

File tree

20 files changed

+1876
-316
lines changed

20 files changed

+1876
-316
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ public record DiscussionResponse(
3939
Long replyCount,
4040

4141
@Schema(description = "현재 사용자의 추천 상태 (UP, DOWN, NONE)", example = "UP")
42-
VoteType voteStatus
42+
VoteType voteStatus,
43+
44+
@Schema(description = "로그인한 유저의 해당 토론글 작성 여부", example = "true")
45+
boolean isAuthor
4346

4447
) {
4548

@@ -53,7 +56,8 @@ public static DiscussionResponse fromEntity(Discussion discussion) {
5356
null,
5457
null,
5558
null,
56-
null
59+
null,
60+
false
5761
);
5862
}
5963

@@ -67,7 +71,8 @@ public static DiscussionResponse from(DiscussionQueryResult result) {
6771
result.getUpvoteCount(),
6872
result.getDownvoteCount(),
6973
result.getReplyCount(),
70-
result.getVoteStatus()
74+
result.getVoteStatus(),
75+
result.isAuthor()
7176
);
7277
}
7378
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ public record ReplyResponse(
4242
Long childReplyCount,
4343

4444
@Schema(description = "현재 사용자의 추천 상태 (UP, DOWN, NONE)", example = "UP")
45-
VoteType voteStatus
45+
VoteType voteStatus,
4646

47+
@Schema(description = "로그인한 유저의 해당 토론글 작성 여부", example = "true")
48+
boolean isAuthor
4749
) {
4850

4951
public static ReplyResponse fromEntity(Reply reply) {
@@ -62,7 +64,8 @@ public static ReplyResponse fromEntity(Reply reply) {
6264
null,
6365
null,
6466
null,
65-
null
67+
null,
68+
false
6669
);
6770
}
6871

@@ -78,7 +81,8 @@ public static ReplyResponse from(ReplyQueryResult result) {
7881
result.getUpvoteCount(),
7982
result.getDownvoteCount(),
8083
result.getChildReplyCount(),
81-
result.getVoteStatus()
84+
result.getVoteStatus(),
85+
result.isAuthor()
8286
);
8387
}
8488
}

src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
import org.ezcode.codetest.common.security.util.ExceptionHandlingFilter;
99
import org.ezcode.codetest.common.security.util.JwtFilter;
1010
import org.ezcode.codetest.common.security.util.JwtUtil;
11-
import org.springframework.boot.web.servlet.FilterRegistrationBean;
1211
import org.springframework.context.annotation.Bean;
1312
import org.springframework.context.annotation.Configuration;
1413
import org.springframework.data.redis.core.RedisTemplate;
1514
import org.springframework.http.HttpMethod;
16-
import org.springframework.security.config.Customizer;
1715
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
1816
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1917
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -38,13 +36,12 @@
3836
@RequiredArgsConstructor
3937
@EnableMethodSecurity(prePostEnabled = true)
4038
public class SecurityConfig {
39+
4140
private final JwtUtil jwtUtil;
4241
private final RedisTemplate<String, String> redisTemplate;
4342
private final CustomOAuth2UserService customOAuth2UserService; //OAuth2.0 서비스
4443
private final CustomSuccessHandler customSuccessHandler;
4544

46-
47-
4845
@Bean
4946
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
5047
JwtFilter jwtFilter = new JwtFilter(jwtUtil, redisTemplate);
@@ -81,6 +78,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
8178
SecurityPath.PUBLIC_PATH).permitAll()
8279
.requestMatchers("/api/admin/**").hasRole("ADMIN") //어드민 권한 필요 (문제 생성, 관리 등)
8380
.requestMatchers(HttpMethod.GET,
81+
"/api/languages",
8482
"/api/problems",
8583
"/api/problems/{problemId}",
8684
"/api/problems/*/discussions",

src/main/java/org/ezcode/codetest/domain/community/dto/DiscussionQueryResult.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public class DiscussionQueryResult {
3030

3131
private final VoteType voteStatus;
3232

33+
private final boolean isAuthor;
34+
3335
@QueryProjection
3436
public DiscussionQueryResult(
3537
Long discussionId,
@@ -40,7 +42,7 @@ public DiscussionQueryResult(
4042
Long upvoteCount,
4143
Long downvoteCount,
4244
Long replyCount,
43-
VoteType voteType
45+
VoteType voteType, boolean isAuthor
4446
) {
4547

4648
this.discussionId = discussionId;
@@ -59,5 +61,7 @@ public DiscussionQueryResult(
5961
} else {
6062
this.voteStatus = VoteType.DOWN;
6163
}
64+
65+
this.isAuthor = isAuthor;
6266
}
6367
}

src/main/java/org/ezcode/codetest/domain/community/dto/ReplyQueryResult.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class ReplyQueryResult {
3232

3333
private final VoteType voteStatus;
3434

35+
private final boolean isAuthor;
36+
3537
@QueryProjection
3638
public ReplyQueryResult(
3739
Long replyId,
@@ -43,7 +45,8 @@ public ReplyQueryResult(
4345
Long upvoteCount,
4446
Long downvoteCount,
4547
Long childReplyCount,
46-
VoteType voteType
48+
VoteType voteType,
49+
boolean isAuthor
4750
) {
4851

4952
this.replyId = replyId;
@@ -63,5 +66,7 @@ public ReplyQueryResult(
6366
} else {
6467
this.voteStatus = VoteType.DOWN;
6568
}
69+
70+
this.isAuthor = isAuthor;
6671
}
6772
}

src/main/java/org/ezcode/codetest/domain/community/exception/CommunityExceptionCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public enum CommunityExceptionCode implements ResponseCode {
1515

1616
REPLY_NOT_FOUND(false, HttpStatus.NOT_FOUND, "해당 ID의 댓글이 존재하지 않습니다."),
1717
REPLY_DISCUSSION_MISMATCH(false, HttpStatus.BAD_REQUEST, "해당 댓글이 요청된 토론글에 속하지 않습니다."),
18-
18+
REPLY_PARENT_ALREADY_EXISTS(false, HttpStatus.BAD_REQUEST, "이미 부모 댓글이 있는 댓글에는 대댓글을 작성할 수 없습니다."),
19+
1920
USER_NOT_AUTHOR(false, HttpStatus.FORBIDDEN, "작성자만 수정/삭제할 수 있습니다."),
2021
;
2122

src/main/java/org/ezcode/codetest/domain/community/service/ReplyDomainService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public Reply createReply(Discussion discussion, User user, Long parentReplyId, S
2929
if (parentReplyId != null) {
3030
parentReply = getReplyById(parentReplyId);
3131
validateDiscussionMatches(parentReply, discussion);
32+
checkNoExistingParentForReply(parentReply);
3233
}
3334

3435
Reply reply = Reply.builder()
@@ -96,6 +97,7 @@ public void validateDiscussionMatches(Reply reply, Discussion discussion) {
9697
if (!reply.isDiscussionMatches(discussion.getId())) {
9798
throw new CommunityException(CommunityExceptionCode.REPLY_DISCUSSION_MISMATCH);
9899
}
100+
99101
}
100102

101103
public void validateIsAuthor(Reply reply, Long userId) {
@@ -105,6 +107,13 @@ public void validateIsAuthor(Reply reply, Long userId) {
105107
}
106108
}
107109

110+
public void checkNoExistingParentForReply(Reply parentReply) {
111+
112+
if (parentReply.getParent() != null) {
113+
throw new CommunityException(CommunityExceptionCode.REPLY_PARENT_ALREADY_EXISTS);
114+
}
115+
}
116+
108117
public NotificationCreateEvent createReplyNotification(User target, Reply reply) {
109118

110119
Long parentReplyId = reply.getParentReplyId();

src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.querydsl.core.types.Order;
2020
import com.querydsl.core.types.OrderSpecifier;
2121
import com.querydsl.core.types.Projections;
22+
import com.querydsl.core.types.dsl.BooleanExpression;
2223
import com.querydsl.core.types.dsl.CaseBuilder;
2324
import com.querydsl.core.types.dsl.Expressions;
2425
import com.querydsl.core.types.dsl.NumberExpression;
@@ -40,11 +41,15 @@ public List<Long> findDiscussionIdsByProblemId(Long problemId, String sortBy, Pa
4041

4142
NumberExpression<Long> bestScore = upvoteCount.subtract(downvoteCount);
4243

44+
long offset = pageable.getOffset();
45+
int pageNumber = pageable.getPageNumber();
46+
int pageSize = pageable.getPageSize();
47+
4348
return jpaQueryFactory
4449
.select(discussion.id)
4550
.from(discussion)
4651
.leftJoin(discussionVote).on(discussionVote.discussion.eq(discussion))
47-
.where(discussion.problem.id.eq(problemId))
52+
.where(discussion.problem.id.eq(problemId).and(discussion.isDeleted.isFalse()))
4853
.groupBy(discussion.id)
4954
.orderBy(getOrderSpecifier(sortBy, bestScore, upvoteCount))
5055
.offset(pageable.getOffset())
@@ -65,12 +70,15 @@ public List<DiscussionQueryResult> findDiscussionsByIds(List<Long> discussionIds
6570
Expression<Long> replyCount = reply.id.countDistinct();
6671

6772
Expression<VoteType> userVoteType;
73+
BooleanExpression isAuthor;
6874
if (currentUserId != null) {
6975
userVoteType = new CaseBuilder()
7076
.when(discussionVote.voter.id.eq(currentUserId)).then(discussionVote.voteType)
7177
.otherwise(Expressions.nullExpression(VoteType.class)).max();
78+
isAuthor = discussion.user.id.eq(currentUserId);
7279
} else {
7380
userVoteType = Expressions.nullExpression(VoteType.class);
81+
isAuthor = Expressions.FALSE;
7482
}
7583

7684
return jpaQueryFactory
@@ -88,7 +96,8 @@ public List<DiscussionQueryResult> findDiscussionsByIds(List<Long> discussionIds
8896
upvoteCount,
8997
downvoteCount,
9098
replyCount,
91-
userVoteType
99+
userVoteType,
100+
isAuthor
92101
))
93102
.from(discussion)
94103
.join(discussion.user, user)
@@ -114,7 +123,7 @@ public Long countByProblemId(Long problemId) {
114123
Long count = jpaQueryFactory
115124
.select(discussion.count())
116125
.from(discussion)
117-
.where(discussion.problem.id.eq(problemId))
126+
.where(discussion.problem.id.eq(problemId).and(discussion.isDeleted.isFalse()))
118127
.fetchOne();
119128

120129
return count != null ? count : 0L;

src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/reply/ReplyQueryRepositoryImpl.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,15 @@ private List<ReplyQueryResult> findRepliesByCondition(BooleanExpression conditio
6666
Expression<Long> childReplyCount = childReply.id.count();
6767

6868
Expression<VoteType> userVoteType;
69+
BooleanExpression isAuthor;
6970
if (currentUserId != null) {
7071
userVoteType = new CaseBuilder()
7172
.when(replyVote.voter.id.eq(currentUserId)).then(replyVote.voteType)
7273
.otherwise(Expressions.nullExpression(VoteType.class)).max();
74+
isAuthor = reply.user.id.eq(currentUserId);
7375
} else {
7476
userVoteType = Expressions.nullExpression(VoteType.class);
77+
isAuthor = Expressions.FALSE;
7578
}
7679

7780
return jpaQueryFactory
@@ -90,13 +93,14 @@ private List<ReplyQueryResult> findRepliesByCondition(BooleanExpression conditio
9093
upvoteCount,
9194
downvoteCount,
9295
childReplyCount,
93-
userVoteType
96+
userVoteType,
97+
isAuthor
9498
))
9599
.from(reply)
96100
.join(reply.user, user)
97101
.leftJoin(replyVote).on(replyVote.reply.eq(reply))
98102
.leftJoin(childReply).on(childReply.parent.eq(reply))
99-
.where(condition)
103+
.where(condition.and(reply.isDeleted.isFalse()))
100104
.groupBy(reply.id)
101105
.orderBy(reply.id.asc())
102106
.offset(pageable.getOffset())
@@ -109,7 +113,7 @@ public Long countByDiscussionId(Long discussionId) {
109113
return jpaQueryFactory
110114
.select(reply.count())
111115
.from(reply)
112-
.where(reply.discussion.id.eq(discussionId).and(reply.parent.isNull()))
116+
.where(reply.discussion.id.eq(discussionId).and(reply.parent.isNull()).and(reply.isDeleted.isFalse()))
113117
.fetchOne();
114118
}
115119

@@ -118,7 +122,7 @@ public Long countByParentId(Long parentId) {
118122
return jpaQueryFactory
119123
.select(reply.count())
120124
.from(reply)
121-
.where(reply.parent.id.eq(parentId))
125+
.where(reply.parent.id.eq(parentId).and(reply.isDeleted.isFalse()))
122126
.fetchOne();
123127
}
124128

src/main/java/org/ezcode/codetest/presentation/view/ViewController.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public String getGamePage() {
5050
return "game-page";
5151
}
5252

53+
@GetMapping("/test/notifications")
54+
public String getNotificationPage() {
55+
return "test-notifications";
56+
}
57+
5358
@GetMapping("/ezlogin")
5459
public String loginPage(){
5560
return "login-page";

0 commit comments

Comments
 (0)