-
Notifications
You must be signed in to change notification settings - Fork 8
feat: 어드민 유저 차단 기능 추가 #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.example.solidconnection.admin.controller; | ||
|
|
||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PatchMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import com.example.solidconnection.admin.dto.UserBanRequest; | ||
| import com.example.solidconnection.admin.service.AdminUserBanService; | ||
| import com.example.solidconnection.common.resolver.AuthorizedUser; | ||
|
|
||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @RequestMapping("/admin/users") | ||
| @RestController | ||
| public class AdminUserBanController { | ||
| private final AdminUserBanService adminUserBanService; | ||
|
|
||
| @PostMapping("/{user-id}/ban") | ||
| public ResponseEntity<Void> banUser( | ||
| @AuthorizedUser long adminId, | ||
| @PathVariable(name = "user-id") long userId, | ||
| @Valid @RequestBody UserBanRequest request | ||
| ) { | ||
| adminUserBanService.banUser(userId, adminId, request); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @PatchMapping("/{user-id}/unban") | ||
| public ResponseEntity<Void> unbanUser( | ||
| @AuthorizedUser long adminId, | ||
| @PathVariable(name = "user-id") long userId | ||
| ) { | ||
| adminUserBanService.unbanUser(userId, adminId); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.domain.UserBanDuration; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record UserBanRequest( | ||
| @NotNull(message = "차단 기간을 입력해주세요.") | ||
| UserBanDuration duration | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| package com.example.solidconnection.admin.service; | ||
|
|
||
| import static java.time.ZoneOffset.UTC; | ||
|
|
||
| import com.example.solidconnection.admin.dto.UserBanRequest; | ||
| import com.example.solidconnection.chat.repository.ChatMessageRepository; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.common.exception.ErrorCode; | ||
| import com.example.solidconnection.community.post.repository.PostRepository; | ||
| import com.example.solidconnection.report.repository.ReportRepository; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import com.example.solidconnection.siteuser.domain.UserBan; | ||
| import com.example.solidconnection.siteuser.domain.UserStatus; | ||
| import com.example.solidconnection.siteuser.repository.SiteUserRepository; | ||
| import com.example.solidconnection.siteuser.repository.UserBanRepository; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.scheduling.annotation.Scheduled; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Slf4j | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 마찬가지입니다 ! |
||
| @RequiredArgsConstructor | ||
| @Service | ||
| public class AdminUserBanService { | ||
|
|
||
| private final UserBanRepository userBanRepository; | ||
| private final ReportRepository reportRepository; | ||
| private final SiteUserRepository siteUserRepository; | ||
| private final PostRepository postRepository; | ||
| private final ChatMessageRepository chatMessageRepository; | ||
|
|
||
| @Transactional | ||
| public void banUser(long userId, long adminId, UserBanRequest request) { | ||
| SiteUser user = siteUserRepository.findById(userId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); | ||
| validateNotAlreadyBanned(userId); | ||
| validateReportExists(userId); | ||
|
|
||
| user.updateUserStatus(UserStatus.BANNED); | ||
| updateReportedContentIsDeleted(userId, true); | ||
| createUserBan(userId, adminId, request); | ||
| } | ||
|
Comment on lines
+35
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 차단 처리 순서/원자성(동시성) 보강이 필요해요.
|
||
|
|
||
| private void validateNotAlreadyBanned(long userId) { | ||
| if (userBanRepository.existsByBannedUserIdAndIsExpiredFalseAndExpiredAtAfter(userId, ZonedDateTime.now(UTC))) { | ||
| throw new CustomException(ErrorCode.ALREADY_BANNED_USER); | ||
| } | ||
| } | ||
|
Comment on lines
+47
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "AdminUserBanService.java" -type fRepository: solid-connection/solid-connect-server Length of output: 163 🏁 Script executed: cat -n ./src/main/java/com/example/solidconnection/admin/service/AdminUserBanService.javaRepository: solid-connection/solid-connect-server Length of output: 5608 🏁 Script executed: find . -name "UserBan.java" -type f | head -5Repository: solid-connection/solid-connect-server Length of output: 153 🏁 Script executed: cat -n ./src/main/java/com/example/solidconnection/siteuser/domain/UserBan.javaRepository: solid-connection/solid-connect-server Length of output: 2262 🏁 Script executed: find . -name "UserBanRepository.java" -type fRepository: solid-connection/solid-connect-server Length of output: 167 🏁 Script executed: cat -n ./src/main/java/com/example/solidconnection/siteuser/repository/UserBanRepository.javaRepository: solid-connection/solid-connect-server Length of output: 1365 시간 기준 만료와 배치 실행 사이의 시간차로 활성 차단 검증이 누락될 수 있습니다. 현재 로직의 동작 방식:
발생 가능한 문제:
개선 방향:
|
||
|
|
||
| private void validateReportExists(long userId) { | ||
| if (!reportRepository.existsByReportedId(userId)) { | ||
| throw new CustomException(ErrorCode.REPORT_NOT_FOUND); | ||
| } | ||
| } | ||
|
|
||
| private void updateReportedContentIsDeleted(long userId, boolean isDeleted) { | ||
| postRepository.updateReportedPostsIsDeleted(userId, isDeleted); | ||
| chatMessageRepository.updateReportedChatMessagesIsDeleted(userId, isDeleted); | ||
| } | ||
|
Comment on lines
+59
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # AdminUserBanService 파일 내용 확인
wc -l src/main/java/com/example/solidconnection/admin/service/AdminUserBanService.javaRepository: solid-connection/solid-connect-server Length of output: 218 🏁 Script executed: # 업데이트 쿼리 메서드 위치 찾기
rg -n "updateReportedPostsIsDeleted|updateReportedChatMessagesIsDeleted" --type javaRepository: solid-connection/solid-connect-server Length of output: 824 🏁 Script executed: # Post 및 ChatMessage 저장소의 실제 구현 확인
find src -name "*Repository.java" -type f | grep -E "(Post|ChatMessage)" | head -5Repository: solid-connection/solid-connect-server Length of output: 438 🏁 Script executed: # MyBatis mapper XML 파일 찾기
find src -name "*.xml" -type f | head -20Repository: solid-connection/solid-connect-server Length of output: 118 🏁 Script executed: # PostRepository 및 ChatMessageRepository의 SQL 매퍼 찾기
find src/main/resources -name "*Mapper.xml" -o -name "*mapper.xml" | grep -i -E "(post|chat)"Repository: solid-connection/solid-connect-server Length of output: 63 🏁 Script executed: # 전체 resources 디렉토리 구조 확인
find src/main/resources -type f | head -30Repository: solid-connection/solid-connect-server Length of output: 2117 🏁 Script executed: # PostRepository 전체 내용 확인
cat -n src/main/java/com/example/solidconnection/community/post/repository/PostRepository.javaRepository: solid-connection/solid-connect-server Length of output: 3956 🏁 Script executed: # ChatMessageRepository 전체 내용 확인
cat -n src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.javaRepository: solid-connection/solid-connect-server Length of output: 3701 🏁 Script executed: # Post 엔티티 확인 (deletion reason 관련 필드 확인)
find src -name "Post.java" -type f | grep -v TestRepository: solid-connection/solid-connect-server Length of output: 154 🏁 Script executed: # Post 엔티티 확인 - 삭제 이유 필드 존재 여부
cat -n src/main/java/com/example/solidconnection/community/post/domain/Post.javaRepository: solid-connection/solid-connect-server Length of output: 3461 🏁 Script executed: # ChatMessage 엔티티 확인
find src -name "ChatMessage.java" -path "*/domain/*" | grep -v TestRepository: solid-connection/solid-connect-server Length of output: 151 🏁 Script executed: # Report 테이블/엔티티 확인
find src -name "Report.java" -path "*/domain/*" | grep -v TestRepository: solid-connection/solid-connect-server Length of output: 148 🏁 Script executed: # AdminUserBanService의 ban/unban 로직 확인
cat -n src/main/java/com/example/solidconnection/admin/service/AdminUserBanService.java | head -150Repository: solid-connection/solid-connect-server Length of output: 5608 🏁 Script executed: # Report 엔티티 확인
cat -n src/main/java/com/example/solidconnection/report/domain/Report.javaRepository: solid-connection/solid-connect-server Length of output: 2162 🏁 Script executed: # ChatMessage 엔티티 확인
cat -n src/main/java/com/example/solidconnection/chat/domain/ChatMessage.javaRepository: solid-connection/solid-connect-server Length of output: 2262 🏁 Script executed: # 사용자가 자신의 콘텐츠를 삭제하는 다른 경로 확인
rg -n "isDeleted.*true" src/main/java/com/example/solidconnection/community/post/ --type java -A2Repository: solid-connection/solid-connect-server Length of output: 63 🏁 Script executed: # 사용자가 자신의 채팅을 삭제하는 다른 경로 확인
rg -n "isDeleted.*true" src/main/java/com/example/solidconnection/chat/ --type java -A2Repository: solid-connection/solid-connect-server Length of output: 63 🏁 Script executed: # Post 삭제 메서드 찾기
rg -n "setDeleted|delete\(" src/main/java/com/example/solidconnection/community/post/ --type java -A3Repository: solid-connection/solid-connect-server Length of output: 63 🏁 Script executed: # ChatMessage 삭제 메서드 찾기
rg -n "setDeleted|delete\(" src/main/java/com/example/solidconnection/chat/ --type java -A3Repository: solid-connection/solid-connect-server Length of output: 63 🏁 Script executed: # Post를 삭제하는 모든 서비스 로직 확인
find src/main/java/com/example/solidconnection -name "*Service.java" -exec grep -l "PostRepository\|updateReportedPostsIsDeleted" {} \;Repository: solid-connection/solid-connect-server Length of output: 680 🏁 Script executed: # 삭제 관련 메서드와 호출 흐름 확인
rg -n "updateReportedPostsIsDeleted|updateReportedChatMessagesIsDeleted|bulkUpdateReportedPostsIsDeleted|bulkUpdateReportedChatMessagesIsDeleted" src/main/java --type java -B5 -A5Repository: solid-connection/solid-connect-server Length of output: 7857 신고 콘텐츠의 삭제 이유를 추적하지 않아, 차단 해제 시 다른 사유로 삭제된 콘텐츠까지 복구될 위험이 있습니다. 현재 코드 분석 결과:
🤖 Prompt for AI Agents |
||
|
|
||
| private void createUserBan(long userId, long adminId, UserBanRequest request) { | ||
| ZonedDateTime now = ZonedDateTime.now(UTC); | ||
| ZonedDateTime expiredAt = now.plusDays(request.duration().getDays()); | ||
| UserBan userBan = new UserBan(userId, adminId, request.duration(), expiredAt); | ||
| userBanRepository.save(userBan); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void unbanUser(long userId, long adminId) { | ||
| SiteUser user = siteUserRepository.findById(userId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); | ||
| UserBan userBan = findActiveBan(userId); | ||
| userBan.manuallyUnban(adminId); | ||
|
|
||
| user.updateUserStatus(UserStatus.REPORTED); | ||
| updateReportedContentIsDeleted(userId, false); | ||
| } | ||
|
|
||
| private UserBan findActiveBan(long userId) { | ||
| return userBanRepository | ||
| .findByBannedUserIdAndIsExpiredFalseAndExpiredAtAfter(userId, ZonedDateTime.now(UTC)) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.NOT_BANNED_USER)); | ||
| } | ||
|
|
||
| @Transactional | ||
| @Scheduled(cron = "0 0 0 * * *") | ||
| public void expireUserBans() { | ||
| try { | ||
| ZonedDateTime now = ZonedDateTime.now(UTC); | ||
| List<Long> expiredUserIds = userBanRepository.findExpiredBannedUserIds(now); | ||
|
|
||
| if (expiredUserIds.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| userBanRepository.bulkExpireUserBans(now); | ||
| siteUserRepository.bulkUpdateUserStatus(expiredUserIds, UserStatus.REPORTED); | ||
| bulkUpdateReportedContentIsDeleted(expiredUserIds); | ||
| log.info("Finished processing expired blocks:: userIds={}", expiredUserIds); | ||
| } catch (Exception e) { | ||
| log.error("Failed to process expired blocks", e); | ||
| } | ||
| } | ||
|
Comment on lines
+88
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
변경 예시(예외 삼키지 않기) @Transactional
@Scheduled(cron = "0 0 0 * * *")
public void expireUserBans() {
- try {
- ZonedDateTime now = ZonedDateTime.now(UTC);
- List<Long> expiredUserIds = userBanRepository.findExpiredBannedUserIds(now);
+ ZonedDateTime now = ZonedDateTime.now(UTC);
+ List<Long> expiredUserIds = userBanRepository.findExpiredBannedUserIds(now);
- if (expiredUserIds.isEmpty()) {
- return;
- }
+ if (expiredUserIds.isEmpty()) {
+ return;
+ }
- userBanRepository.bulkExpireUserBans(now);
- siteUserRepository.bulkUpdateUserStatus(expiredUserIds, UserStatus.REPORTED);
- bulkUpdateReportedContentIsDeleted(expiredUserIds);
- log.info("Finished processing expired blocks:: userIds={}", expiredUserIds);
- } catch (Exception e) {
- log.error("Failed to process expired blocks", e);
- }
+ userBanRepository.bulkExpireUserBans(now);
+ siteUserRepository.bulkUpdateUserStatus(expiredUserIds, UserStatus.REPORTED);
+ bulkUpdateReportedContentIsDeleted(expiredUserIds);
+ log.info("Finished processing expired bans:: userIds={}", expiredUserIds);
}🤖 Prompt for AI Agents |
||
|
|
||
| private void bulkUpdateReportedContentIsDeleted(List<Long> expiredUserIds) { | ||
| postRepository.bulkUpdateReportedPostsIsDeleted(expiredUserIds, false); | ||
| chatMessageRepository.bulkUpdateReportedChatMessagesIsDeleted(expiredUserIds, false); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,10 +15,12 @@ | |
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.hibernate.annotations.Where; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Where(clause = "is_deleted = false") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이후
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런 기능은 처음 알았네요!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| public class ChatMessage extends BaseEntity { | ||
|
|
||
| @Id | ||
|
|
@@ -33,6 +35,9 @@ public class ChatMessage extends BaseEntity { | |
| @ManyToOne(fetch = FetchType.LAZY) | ||
| private ChatRoom chatRoom; | ||
|
|
||
| @Column(name = "is_deleted", columnDefinition = "boolean default false", nullable = false) | ||
| private boolean isDeleted = false; | ||
|
|
||
| @OneToMany(mappedBy = "chatMessage", cascade = CascadeType.ALL, orphanRemoval = true) | ||
| private final List<ChatAttachment> chatAttachments = new ArrayList<>(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.domain.Slice; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Modifying; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
|
|
@@ -48,4 +49,20 @@ SELECT MAX(cm2.id) | |
| GROUP BY cm.chatRoom.id | ||
| """) | ||
| List<UnreadCountDto> countUnreadMessagesBatch(@Param("chatRoomIds") List<Long> chatRoomIds, @Param("userId") long userId); | ||
|
|
||
| @Modifying(clearAutomatically = true, flushAutomatically = true) | ||
| @Query(value = """ | ||
| UPDATE chat_message cm SET cm.is_deleted = :isDeleted | ||
| WHERE cm.id IN (SELECT r.target_id FROM report r WHERE r.target_type = 'CHAT') | ||
| AND cm.sender_id IN (SELECT cp.id FROM chat_participant cp WHERE cp.site_user_id = :siteUserId) | ||
| """, nativeQuery = true) | ||
| void updateReportedChatMessagesIsDeleted(@Param("siteUserId") long siteUserId, @Param("isDeleted") boolean isDeleted); | ||
|
Comment on lines
+53
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # 마이그레이션 파일 디렉토리 위치 확인
find . -type d -name "migration" -o -name "db" | head -20Repository: solid-connection/solid-connect-server Length of output: 138 🏁 Script executed: # 마이그레이션 파일 목록 확인
ls -la ./src/main/resources/db/migration/
echo -e "\n=== 전체 파일 목록 ==="
find ./src/main/resources/db/migration -type f -name "*.sql" | sortRepository: solid-connection/solid-connect-server Length of output: 6853 🏁 Script executed: # chat 및 report 관련 테이블 생성 파일 확인
echo "=== V24__add_chat_related_tables.sql ==="
cat ./src/main/resources/db/migration/V24__add_chat_related_tables.sql
echo -e "\n=== V25__create_report_table.sql ==="
cat ./src/main/resources/db/migration/V25__create_report_table.sql
echo -e "\n=== V41__add_is_deleted_to_post_and_chat_message.sql ==="
cat ./src/main/resources/db/migration/V41__add_is_deleted_to_post_and_chat_message.sqlRepository: solid-connection/solid-connect-server Length of output: 3030 🏁 Script executed: # report 테이블의 target_type ENUM 변경 이력 확인
echo "=== Checking for report table modifications ==="
rg -n "ALTER TABLE report" ./src/main/resources/db/migration/ -A 5
echo -e "\n=== Checking for CHAT in target_type ==="
rg -n "CHAT" ./src/main/resources/db/migration/ -B 2 -A 2
echo -e "\n=== Checking for index creations ==="
rg -n "CREATE.*INDEX|CREATE INDEX" ./src/main/resources/db/migration/ -A 3Repository: solid-connection/solid-connect-server Length of output: 14024 🏁 Script executed: # ENUM 수정 쿼리 찾기
echo "=== Searching for ALTER ENUM or MODIFY for target_type ==="
rg -n "target_type|MODIFY.*enum|ALTER.*enum" ./src/main/resources/db/migration/ -i -B 1 -A 3
# V25 이후의 모든 파일에서 report 관련 수정 확인
echo -e "\n=== All ALTER for report table ==="
rg -n "report" ./src/main/resources/db/migration/ -B 2 -A 2 | head -100
# 'CHAT' 또는 'Chat' 문자열 전체 검색
echo -e "\n=== Searching for Chat keyword in migrations ==="
rg -n "'CHAT'|\"CHAT\"|Chat" ./src/main/resources/db/migration/Repository: solid-connection/solid-connect-server Length of output: 8049 🏁 Script executed: # Java 코드에서 실제 target_type 값 확인
echo "=== Checking Java code for target_type usage ==="
rg -n "target_type.*CHAT|'CHAT'|\"CHAT\"" src/main/java/ -B 2 -A 2 | head -50
# ReportTargetType enum 확인
echo -e "\n=== Searching for ReportTargetType enum ==="
fd -t f "ReportTargetType.java" src/
# Report entity 확인
echo -e "\n=== Searching for Report entity ==="
fd -t f "Report.java" src/ | grep -v "Repository"Repository: solid-connection/solid-connect-server Length of output: 1730 🏁 Script executed: # Report entity 상세 확인
echo "=== Report.java content ==="
cat src/main/java/com/example/solidconnection/report/domain/Report.java
# ChatMessageRepository 메서드 전체 확인
echo -e "\n=== ChatMessageRepository class ==="
cat src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java | head -80Repository: solid-connection/solid-connect-server Length of output: 4326 🏁 Script executed: # TargetType enum 검색
echo "=== Searching for TargetType enum ==="
fd -t f "TargetType.java" src/
# 또는 Report.java와 같은 디렉토리에서 찾기
echo -e "\n=== Files in report domain directory ==="
ls -la src/main/java/com/example/solidconnection/report/domain/
# enum 정의 검색
echo -e "\n=== Searching for enum TargetType definition ==="
rg -n "enum TargetType|class TargetType" src/main/java/ -A 5Repository: solid-connection/solid-connect-server Length of output: 1082 🏁 Script executed: # TargetType.java 전체 확인
echo "=== TargetType.java ==="
cat src/main/java/com/example/solidconnection/report/domain/TargetType.java
# 모든 마이그레이션 파일이 있는지 확인
echo -e "\n=== Total migration files ==="
ls -1 ./src/main/resources/db/migration/ | wc -l
# 가장 최신 마이그레이션 파일들 확인
echo -e "\n=== Latest 5 migration files ==="
ls -1 ./src/main/resources/db/migration/ | sort -V | tail -5
# application.properties에서 JPA 설정 확인
echo -e "\n=== Checking application.properties for JPA DDL settings ==="
find ./src/main/resources -name "application*.properties" -o -name "application*.yml" | xargs cat 2>/dev/null | grep -i "jpa\|hibernate\|ddl" -A 2Repository: solid-connection/solid-connect-server Length of output: 560 데이터베이스 스키마 수정이 필수적으로 필요합니다. 신고된 채팅 메시지 일괄 처리 메서드의 구현 로직은 정확하나, 데이터베이스와 코드 간 중요한 불일치가 있습니다.
🤖 Prompt for AI Agents |
||
|
|
||
| @Modifying(clearAutomatically = true, flushAutomatically = true) | ||
| @Query(value = """ | ||
| UPDATE chat_message cm SET cm.is_deleted = :isDeleted | ||
| WHERE cm.id IN (SELECT r.target_id FROM report r WHERE r.target_type = 'CHAT') | ||
| AND cm.sender_id IN (SELECT cp.id FROM chat_participant cp WHERE cp.site_user_id IN :siteUserIds) | ||
| """, nativeQuery = true) | ||
| void bulkUpdateReportedChatMessagesIsDeleted(@Param("siteUserIds") List<Long> siteUserIds, @Param("isDeleted") boolean isDeleted); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.example.solidconnection.common.interceptor; | ||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.BANNED_USER_ACCESS_DENIED; | ||
|
|
||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.security.userdetails.SiteUserDetails; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import com.example.solidconnection.siteuser.domain.UserStatus; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.servlet.HandlerInterceptor; | ||
|
|
||
| @Component | ||
| public class BannedUserInterceptor implements HandlerInterceptor { | ||
|
|
||
| @Override | ||
| public boolean preHandle( | ||
| HttpServletRequest request, | ||
| HttpServletResponse response, | ||
| Object handler | ||
| ) { | ||
| Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||
|
|
||
| if (authentication != null && authentication.getPrincipal() instanceof SiteUserDetails) { | ||
| SiteUserDetails userDetails = (SiteUserDetails) authentication.getPrincipal(); | ||
| SiteUser siteUser = userDetails.getSiteUser(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JWT 토큰에서 사용자를 조회하고 있어, 차단 시점과의 불일치가 생길 거 같은데, DB 조회와 차단 시 토큰 무효화 중 뭐가 좋을지는 생각해봐야겠네요 .. 불일치가 큰 영향이 있을 거 같진 않다는 생각이 들기도 하고요 .... |
||
|
|
||
| if (siteUser.getUserStatus() == UserStatus.BANNED) { | ||
| throw new CustomException(BANNED_USER_ACCESS_DENIED); | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,11 +18,13 @@ | |
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.hibernate.annotations.BatchSize; | ||
| import org.hibernate.annotations.Where; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Comment 엔티티 파일 찾기
find . -type f -name "Comment.java" | grep -E "community|comment"Repository: solid-connection/solid-connect-server Length of output: 162 🏁 Script executed: # Post 엔티티에서 isDeleted 관련 메서드 확인
rg -n "isDeleted" --type java -A 3 -B 3 | grep -E "(Post\.java|public|private|protected|void|boolean)" | head -50Repository: solid-connection/solid-connect-server Length of output: 3229 🏁 Script executed: # Comment 엔티티의 @Where 어노테이션 확인
rg -n "@Where|@Entity|class Comment" --type java -B 2 -A 10 | grep -E "Comment|@Where"Repository: solid-connection/solid-connect-server Length of output: 10260 🏁 Script executed: # soft delete 관련 메서드명 패턴 검색
rg -n "delete|Delete|soft" --type java src/main/java/com/example/solidconnection/community/ | grep -i "method\|void\|class" | head -30Repository: solid-connection/solid-connect-server Length of output: 413 🏁 Script executed: cat -n src/main/java/com/example/solidconnection/community/comment/domain/Comment.java | head -50Repository: solid-connection/solid-connect-server Length of output: 1797 🏁 Script executed: # Comment.java의 @Where 어노테이션 확인
rg -n "@Where|@Entity" src/main/java/com/example/solidconnection/community/comment/domain/Comment.javaRepository: solid-connection/solid-connect-server Length of output: 91 🏁 Script executed: # Comment의 deprecateComment() 메서드 확인
rg -n "deprecateComment" src/main/java/com/example/solidconnection/community/comment/domain/Comment.java -A 5Repository: solid-connection/solid-connect-server Length of output: 172 🏁 Script executed: # CommentRepository에서 soft delete 관련 쿼리 확인
rg -n "isDeleted|deleteComment|deprecate" src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java -B 2 -A 5Repository: solid-connection/solid-connect-server Length of output: 63 🏁 Script executed: # CommentRepository 파일 찾기 및 내용 확인
find . -type f -name "CommentRepository.java"Repository: solid-connection/solid-connect-server Length of output: 176 🏁 Script executed: # CommentRepository 전체 내용 확인
cat -n src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java | head -80Repository: solid-connection/solid-connect-server Length of output: 2647 🏁 Script executed: # Comment 조회 쿼리들 확인 - isDeleted 필터링 여부
rg -n "SELECT|select|FROM|from" src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java -B 1 -A 8Repository: solid-connection/solid-connect-server Length of output: 1831 🏁 Script executed: # Post 엔티티에서 isDeleted 관련 public 메서드 확인
rg -n "isDeleted|deprecated|deprecate" src/main/java/com/example/solidconnection/community/post/domain/Post.java -B 2 -A 3Repository: solid-connection/solid-connect-server Length of output: 343 🏁 Script executed: # Post.java 전체 메서드 확인
cat -n src/main/java/com/example/solidconnection/community/post/domain/Post.java | grep -E "public|private|void|boolean" | head -30Repository: solid-connection/solid-connect-server Length of output: 1079 🏁 Script executed: # Post.java 전체 내용 확인 - 특히 메서드들
cat -n src/main/java/com/example/solidconnection/community/post/domain/Post.javaRepository: solid-connection/solid-connect-server Length of output: 3461 🏁 Script executed: # Comment와 Post의 soft delete 메서드 비교
echo "=== Post의 soft delete 관련 메서드 ===" && \
rg -n "public.*void|public.*delete|public.*deprecate" src/main/java/com/example/solidconnection/community/post/domain/Post.java && \
echo -e "\n=== Comment의 soft delete 관련 메서드 ===" && \
rg -n "public.*void|public.*delete|public.*deprecate" src/main/java/com/example/solidconnection/community/comment/domain/Comment.javaRepository: solid-connection/solid-connect-server Length of output: 614 🏁 Script executed: # soft delete 호출되는 곳 확인
rg -n "deprecateComment|\.isDeleted\s*=\s*true" --type java src/main/java/com/example/solidconnection/community/comment/Repository: solid-connection/solid-connect-server Length of output: 452 🏁 Script executed: # CommentService의 findCommentsByPostId 메서드 전체 확인
rg -n "findCommentsByPostId" src/main/java/com/example/solidconnection/community/comment/service/CommentService.java -B 2 -A 15Repository: solid-connection/solid-connect-server Length of output: 1041 🏁 Script executed: # soft delete 처리 관련 전체 흐름 확인 - 어디서 soft delete가 호출되나
rg -n "deprecateComment|updateReportedPostsIsDeleted|updateReportedChatMessagesIsDeleted|updateReportedContentIsDeleted" --type java -B 3 -A 3Repository: solid-connection/solid-connect-server Length of output: 9558 🏁 Script executed: # Verify filterCommentsByDeletionRules implementation
rg -n "filterCommentsByDeletionRules" src/main/java/com/example/solidconnection/community/comment/service/CommentService.java -A 20Repository: solid-connection/solid-connect-server Length of output: 1916 Comment 엔티티의 @where 어노테이션 누락과 soft delete 일관성 문제를 해결하세요. 검증 결과, Post와 ChatMessage 엔티티와의 일관성 문제가 확실합니다. 추가로 soft delete 처리에서 더 심각한 문제점도 발견되었습니다:
해결 방법:
🤖 Prompt for AI Agents |
||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor | ||
| @EqualsAndHashCode(of = "id") | ||
| @Where(clause = "is_deleted = false") | ||
| public class Post extends BaseEntity { | ||
|
|
||
| @Id | ||
|
|
@@ -50,6 +52,8 @@ public class Post extends BaseEntity { | |
| @Column | ||
| private long siteUserId; | ||
|
|
||
| @Column(name = "is_deleted", columnDefinition = "boolean default false", nullable = false) | ||
| private boolean isDeleted = false; | ||
|
|
||
| @BatchSize(size = 20) | ||
| @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.