Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ dependencies {

implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// kafka 추가
implementation 'org.springframework.kafka:spring-kafka'
}

tasks.named('test') {
Expand Down
29 changes: 29 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ services:
start_period: 40s
depends_on:
- redis
- kafka #추가

app-green:
#그린 배포
Expand All @@ -48,7 +49,35 @@ services:
start_period: 40s
depends_on:
- redis
- kafka #추가

#추가
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
restart: unless-stopped

kafka:
image: confluentinc/cp-kafka:7.5.0
container_name: kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

# 내부/외부 통신 분리 (중요)
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
restart: unless-stopped
volumes:
redis_data:
driver: local
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
package cc.backend.admin.amateurShow.service;

import cc.backend.admin.amateurShow.dto.AdminAmateurShowListResponseDTO;
import cc.backend.admin.amateurShow.dto.AdminAmateurShowRejectRequestDTO;
import cc.backend.admin.amateurShow.dto.AdminAmateurShowReviseRequestDTO;
import cc.backend.admin.amateurShow.dto.AdminAmateurShowSummaryResponseDTO;
import cc.backend.amateurShow.entity.AmateurShow;
import cc.backend.amateurShow.repository.AmateurShowRepository;
import cc.backend.apiPayLoad.ApiResponse;
import cc.backend.apiPayLoad.SliceResponse;
import cc.backend.apiPayLoad.code.status.ErrorStatus;
import cc.backend.apiPayLoad.exception.GeneralException;
import cc.backend.event.entity.ApproveShowEvent;
import cc.backend.event.entity.NewShowEvent;
import cc.backend.event.entity.RejectShowEvent;
import cc.backend.member.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

import static io.micrometer.common.util.StringUtils.isNotBlank;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import cc.backend.amateurShow.repository.AmateurShowRepository;
import cc.backend.apiPayLoad.code.status.ErrorStatus;
import cc.backend.apiPayLoad.exception.GeneralException;
import cc.backend.event.entity.ApproveShowEvent;
import cc.backend.event.entity.RejectShowEvent;
import cc.backend.notice.event.entity.ApproveShowEvent;
import cc.backend.notice.event.entity.RejectShowEvent;
import cc.backend.member.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,5 @@ public void reject(String rejectReason){
this.rejectReason = rejectReason;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,7 @@ List<AmateurShow> findHotShows(
@Query("UPDATE AmateurShow s SET s.status = 'ENDED' " +
"WHERE s.status = 'ONGOING' AND s.end < :today")
int updateShowsToEnded(@Param("today") LocalDate today);

@Query("SELECT s.hashtag FROM AmateurShow s WHERE s.member.id = :memberId")
List<String> findHashtagsByMemberId(@Param("memberId") Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,32 @@
import cc.backend.amateurShow.dto.AmateurShowResponseDTO;
import cc.backend.amateurShow.dto.AmateurUpdateRequestDTO;
import cc.backend.amateurShow.entity.*;
import cc.backend.amateurShow.entity.enums.ApprovalStatus;
import cc.backend.amateurShow.repository.*;
import cc.backend.amateurShow.converter.AmateurConverter;
import cc.backend.amateurShow.dto.AmateurEnrollRequestDTO;
import cc.backend.amateurShow.dto.AmateurEnrollResponseDTO;
import cc.backend.amateurShow.repository.specification.AmateurShowSpecification;
import cc.backend.apiPayLoad.code.status.ErrorStatus;
import cc.backend.apiPayLoad.exception.GeneralException;
import cc.backend.board.entity.enums.BoardType;
import cc.backend.event.entity.NewShowEvent;
import cc.backend.image.DTO.ImageRequestDTO;
import cc.backend.image.DTO.ImageResponseDTO;
import cc.backend.image.FilePath;
import cc.backend.image.entity.Image;
import cc.backend.image.repository.ImageRepository;
import cc.backend.image.service.ImageService;
import cc.backend.member.entity.Member;
import cc.backend.member.enumerate.Role;
import cc.backend.member.repository.MemberRepository;
import cc.backend.memberLike.entity.MemberLike;
import cc.backend.memberLike.repository.MemberLikeRepository;
import cc.backend.ticket.dto.response.ReserveListResponseDTO;
import cc.backend.notice.event.entity.NewShowEvent;
import cc.backend.notice.kafka.NewShowEvent.MemberRecommendationProducer;
import cc.backend.notice.service.NoticeService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.Collator;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

Expand All @@ -54,7 +47,8 @@ public class AmateurServiceImpl implements AmateurService {
private final MemberLikeRepository memberLikeRepository;
private final ImageService imageService;
private final ImageRepository imageRepository;
private final ApplicationEventPublisher eventPublisher; //이벤트 생성
private final NoticeService noticeService;
private final MemberRecommendationProducer memberRecommendationProducer;

// 소극장 공연 등록
@Transactional
Expand Down Expand Up @@ -93,16 +87,22 @@ public AmateurEnrollResponseDTO.AmateurEnrollResult enrollShow(Long memberId,
imageService.saveImageWithImageUrl(memberId, fullImageRequestDTO, Optional.ofNullable(dto.getImageUrl()));


// 좋아요한 멤버리스트
List<MemberLike> memberLikers = memberLikeRepository.findByPerformerId(memberId);
// 좋아요한 멤버가 한 명 이상일 때만
if(!memberLikers.isEmpty()) {
List<Member> likers = memberLikers.stream()
.map(MemberLike::getLiker)
.collect(Collectors.toList());
//좋아요한 멤버 추출
List<Long> likerIds = memberLikeRepository.findByPerformerId(memberId)
.stream()
.map(l -> l.getLiker().getId())
.toList();

eventPublisher.publishEvent(new NewShowEvent(newAmateurShow.getId(), memberId, likers)); //공연등록 이벤트 생성
// 좋아요 알림: 좋아요한 멤버들만
if (!likerIds.isEmpty()) {
noticeService.notifyNewShow(
new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
);
}
// 추천: 해시태그 기반 추천 → 모든 회원 대상
memberRecommendationProducer.recommendByHashtag(
new NewShowEvent(newAmateurShow.getId(), memberId, null)
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "AmateurServiceImpl.java" --exec wc -l {} \;

Repository: SeeATheater/CC_Backend

Length of output: 159


🏁 Script executed:

fd -t f "AmateurServiceImpl.java" -x cat -n {} | head -150

Repository: SeeATheater/CC_Backend

Length of output: 7794


🏁 Script executed:

# Search for the enrollShow method to verify the `@Transactional` annotation
rg -A 20 "def enrollShow|void enrollShow|AmateurShow enrollShow" src/main/java/cc/backend/amateurShow/

Repository: SeeATheater/CC_Backend

Length of output: 48


🏁 Script executed:

# Check for imports related to Kafka/async producers and transaction listeners
rg "@Transactional|@TransactionalEventListener|TransactionSynchronization|@KafkaListener|@TopicPartition" src/main/java/cc/backend/amateurShow/service/amateurShowService/

Repository: SeeATheater/CC_Backend

Length of output: 506


🏁 Script executed:

fd -t f "NoticeService.java" -x cat -n {} | head -100

Repository: SeeATheater/CC_Backend

Length of output: 4923


🏁 Script executed:

fd -t f "MemberRecommendationProducer.java" -x cat -n {} | head -150

Repository: SeeATheater/CC_Backend

Length of output: 6786


🏁 Script executed:

# Also search for NewShowEvent implementation
fd -t f "NewShowEvent.java" -x cat -n {} | head -100

Repository: SeeATheater/CC_Backend

Length of output: 587


🏁 Script executed:

rg -l "notifyNewShow" src/main/java/

Repository: SeeATheater/CC_Backend

Length of output: 275


🏁 Script executed:

rg -B5 -A15 "void notifyNewShow|notifyNewShow\(" src/main/java/cc/backend/notice/

Repository: SeeATheater/CC_Backend

Length of output: 3433


🏁 Script executed:

fd -t f "NoticeServiceImpl.java" -x cat -n {} | head -200

Repository: SeeATheater/CC_Backend

Length of output: 9890


Move Kafka publish to after-commit to prevent orphaned events.

enrollShow is transactional. Calling memberRecommendationProducer.recommendByHashtag() inside it emits Kafka events before the DB commit (via kafkaTemplate.send() at line 115). If the transaction rolls back, consumers see events referencing non‑existent records. Move this side effect to an after‑commit hook (e.g., @TransactionalEventListener(AFTER_COMMIT) or TransactionSynchronization) so Kafka stays consistent with the DB.

Note: notifyNewShow() performs only database operations (Notice/MemberNotice inserts), so it has no direct Kafka issue, but can remain in the same after-commit block if needed.

✅ Example after-commit hook using TransactionSynchronization
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
...
-        // 좋아요 알림: 좋아요한 멤버들만
-        if (!likerIds.isEmpty()) {
-            noticeService.notifyNewShow(
-                    new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
-            );
-        }
-        // 추천: 해시태그 기반 추천 → 모든 회원 대상
-        memberRecommendationProducer.recommendByHashtag(
-                new NewShowEvent(newAmateurShow.getId(), memberId, null)
-        );
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            `@Override`
+            public void afterCommit() {
+                if (!likerIds.isEmpty()) {
+                    noticeService.notifyNewShow(
+                            new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
+                    );
+                }
+                memberRecommendationProducer.recommendByHashtag(
+                        new NewShowEvent(newAmateurShow.getId(), memberId, null)
+                );
+            }
+        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//좋아요한 멤버 추출
List<Long> likerIds = memberLikeRepository.findByPerformerId(memberId)
.stream()
.map(l -> l.getLiker().getId())
.toList();
eventPublisher.publishEvent(new NewShowEvent(newAmateurShow.getId(), memberId, likers)); //공연등록 이벤트 생성
// 좋아요 알림: 좋아요한 멤버들만
if (!likerIds.isEmpty()) {
noticeService.notifyNewShow(
new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
);
}
// 추천: 해시태그 기반 추천 → 모든 회원 대상
memberRecommendationProducer.recommendByHashtag(
new NewShowEvent(newAmateurShow.getId(), memberId, null)
);
//좋아요한 멤버 추출
List<Long> likerIds = memberLikeRepository.findByPerformerId(memberId)
.stream()
.map(l -> l.getLiker().getId())
.toList();
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
`@Override`
public void afterCommit() {
if (!likerIds.isEmpty()) {
noticeService.notifyNewShow(
new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
);
}
memberRecommendationProducer.recommendByHashtag(
new NewShowEvent(newAmateurShow.getId(), memberId, null)
);
}
});
🤖 Prompt for AI Agents
In
`@src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java`
around lines 90 - 105, enrollShow is transactional so calling
memberRecommendationProducer.recommendByHashtag(...) emits Kafka events before
the DB commit; move that publish to an after‑commit hook to avoid orphaned
events. Replace the direct call to
memberRecommendationProducer.recommendByHashtag(new NewShowEvent(...)) inside
enrollShow with either (a) publish the NewShowEvent via a Spring application
event and handle it in a method annotated with `@TransactionalEventListener`(phase
= AFTER_COMMIT), or (b) register a TransactionSynchronization.afterCommit that
invokes memberRecommendationProducer.recommendByHashtag(...) with the same
NewShowEvent; keep notifyNewShow(...) as-is or also call it after-commit if you
prefer both side effects to run only after commit.


// response
return AmateurConverter.toAmateurEnrollDTO(newAmateurShow);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public enum ErrorStatus implements BaseErrorCode {
AMATEUR_TICKET_NOT_FOUND(HttpStatus.NOT_FOUND, "AMATEURTICKET4000", "존재하지 않는 소극장 공연 티켓입니다."),
AMATEUR_TICKET_STOCK(HttpStatus.BAD_REQUEST, "AMATEURTICKET4001", "주문 수량은 최소 1개 이상이어야 합니다."),
AMATEUR_SHOW_MISMATCH(HttpStatus.NOT_FOUND, "AMATEURTICKET4002", "회차와 티켓에 해당하는 공연이 일치하지 않습니다."),

// PHOTOALBUM ERROR
PHOTOALBUM_NOT_FOUND(HttpStatus.NOT_FOUND, "PHOTOALBUM4000", "존재하지 않는 사진첩입니다."),

Expand All @@ -108,6 +109,7 @@ public enum ErrorStatus implements BaseErrorCode {

//NOTICE ERROR
MEMBERNOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERNOTICE4001", "존재하지 않는 알림입니다."),
NOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTICE4001", "존재하지 않는 알림입니다."),
Comment on lines 110 to +112
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java | sed -n '100,120p'

Repository: SeeATheater/CC_Backend

Length of output: 1396


🏁 Script executed:

# Search for usages of these constants
rg "MEMBERNOTICE_NOT_FOUND|NOTICE_NOT_FOUND" -B 2 -A 2

Repository: SeeATheater/CC_Backend

Length of output: 1303


🏁 Script executed:

# Check for any comments or documentation about notice vs membernotice
rg -i "notice" src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java -B 1 -A 1 | head -50

Repository: SeeATheater/CC_Backend

Length of output: 270


🏁 Script executed:

# Search for all usages of NOTICE_NOT_FOUND more broadly
rg "NOTICE_NOT_FOUND" -r "Found in: $FILE"

Repository: SeeATheater/CC_Backend

Length of output: 498


🏁 Script executed:

# Check for Notice service/repository files
fd -t f "Notice" src/main/java --type f

Repository: SeeATheater/CC_Backend

Length of output: 953


🏁 Script executed:

# Look at the structure to find notice-related classes
fd -t d "notice" src/main/java

Repository: SeeATheater/CC_Backend

Length of output: 98


🏁 Script executed:

# Check NoticeService for how it handles not found cases
cat -n src/main/java/cc/backend/notice/service/NoticeService.java

Repository: SeeATheater/CC_Backend

Length of output: 933


🏁 Script executed:

# Check NoticeServiceImpl for how it handles not found cases
cat -n src/main/java/cc/backend/notice/service/NoticeServiceImpl.java

Repository: SeeATheater/CC_Backend

Length of output: 12662


🏁 Script executed:

# Check if there's a controller that might retrieve Notice directly
rg "noticeRepository\.findById|getNotice|Notice.*not.*found" -i -A 2 -B 2

Repository: SeeATheater/CC_Backend

Length of output: 10769


🏁 Script executed:

# Check MemberNoticeController to understand the API patterns
head -100 src/main/java/cc/backend/notice/controller/MemberNoticeController.java

Repository: SeeATheater/CC_Backend

Length of output: 2516


Remove unused NOTICE_NOT_FOUND error status.

NOTICE_NOT_FOUND is defined in ErrorStatus but never used anywhere in the codebase. Only MEMBERNOTICE_NOT_FOUND is actually used (in MemberNoticeService). The Notice entity is created through internal event handling in NoticeServiceImpl and is only accessed via MemberNotice relationships. Remove the unused constant or clarify if it should be used in a future feature.

🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java` around
lines 110 - 112, The enum constant NOTICE_NOT_FOUND in ErrorStatus is unused;
remove NOTICE_NOT_FOUND from ErrorStatus (leaving MEMBERNOTICE_NOT_FOUND intact)
unless you intend to use it later—if you do intend to keep it, update usages in
MemberNoticeService or NoticeServiceImpl to reference NOTICE_NOT_FOUND instead
of MEMBERNOTICE_NOT_FOUND; otherwise simply delete the NOTICE_NOT_FOUND enum
entry to eliminate dead code and redundant error codes.

// INQUIRY ERROR
INQUIRY_NOT_FOUND(HttpStatus.NOT_FOUND, "INQUIRY4000", "존재하지 않는 문의글입니다."),
FORBIDDEN_INQUIRY_ACCESS(HttpStatus.NOT_FOUND, "INQUIRY4001", "로그인한 멤버가 작성하지 않는 문의글입니다."),
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cc/backend/board/service/BoardService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import cc.backend.board.entity.enums.BoardType;
import cc.backend.board.repository.BoardLikeRepository;
import cc.backend.board.repository.HotBoardRepository;
import cc.backend.event.entity.PromoteHotEvent;
import cc.backend.notice.event.entity.PromoteHotEvent;
import cc.backend.image.DTO.ImageRequestDTO;
import cc.backend.image.DTO.ImageResponseDTO;
import cc.backend.image.FilePath;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/cc/backend/board/service/CommentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import cc.backend.board.repository.BoardRepository;
import cc.backend.board.repository.CommentLikeRepository;
import cc.backend.board.repository.CommentRepository;
import cc.backend.event.entity.CommentEvent;
import cc.backend.event.entity.ReplyEvent;
import cc.backend.notice.event.entity.CommentEvent;
import cc.backend.notice.event.entity.ReplyEvent;
import cc.backend.member.entity.Member;
import cc.backend.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
Expand Down
20 changes: 0 additions & 20 deletions src/main/java/cc/backend/event/entity/NewShowEvent.java

This file was deleted.

20 changes: 0 additions & 20 deletions src/main/java/cc/backend/event/service/NewShowEventListener.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ Slice<MemberLike> findLikedPerformersSortedBySoonestShow(
@Param("now") LocalDateTime now,
Pageable pageable
);

// memberId 기준으로 해당 멤버가 좋아요한 모든 공연자 조회
@Query("SELECT ml FROM MemberLike ml WHERE ml.liker.id = :memberId")
List<MemberLike> findByLikerId(@Param("memberId") Long memberId);

// 모든 회원을 대상으로, 추천 검사를 위해 distinct Member만 가져오기
@Query("SELECT DISTINCT ml.liker FROM MemberLike ml")
List<Member> findAllDistinctMembers();
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,6 @@ public Slice<MemberLikeResponseDTO> getLikedPerformers(Long memberId, Pageable p

}

private LocalDateTime findSoonestEndingShowDateForPerformer(Member performer) {
return amateurShowRepository.findAllByMemberWithRounds(performer).stream()
.flatMap(show -> show.getAmateurRounds().stream())
.map(AmateurRounds::getPerformanceDateTime)
.filter(date -> date.isAfter(LocalDateTime.now()))
.min(Comparator.naturalOrder())
.orElse(LocalDateTime.MAX);
}

private Member getMemberById(Long id) {
return memberRepository.findById(id)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND));
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/cc/backend/notice/entity/MemberNotice.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,29 @@ public class MemberNotice extends BaseEntity {
@Column(nullable = false, columnDefinition = "bigint")
private Long id;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "notice_id")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "notice_id", nullable = false)
private Notice notice;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "member_id")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
Comment on lines +21 to 27
Copy link

@coderabbitai coderabbitai bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find migrations touching member_notice schema
fd -t f \( -e sql -e yml -e yaml \) | xargs rg -l "member_notice|notice_id|member_id" 2>/dev/null | head -20

Repository: SeeATheater/CC_Backend

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Find migration files and schema management
echo "=== Searching for SQL migration files ==="
find . -type f \( -name "*.sql" -o -name "V*.sql" -o -name "U*.sql" \) 2>/dev/null | head -20

echo "=== Checking for Flyway/Liquibase directories ==="
find . -type d \( -name "db" -o -name "migration" -o -name "migrations" -o -name "liquibase" -o -name "flyway" \) 2>/dev/null

echo "=== Searching for application configuration files ==="
find . -type f \( -name "application*.yml" -o -name "application*.yaml" -o -name "application*.properties" \) 2>/dev/null | head -10

Repository: SeeATheater/CC_Backend

Length of output: 331


🏁 Script executed:

#!/bin/bash
# Check application configuration for DDL auto settings
echo "=== application.yml ==="
cat ./src/main/resources/application.yml

echo -e "\n=== application-dev.yml ==="
cat ./src/main/resources/application-dev.yml

echo -e "\n=== application-prod.yml ==="
cat ./src/main/resources/application-prod.yml

Repository: SeeATheater/CC_Backend

Length of output: 2606


🏁 Script executed:

#!/bin/bash
# Read the full MemberNotice.java entity
echo "=== MemberNotice.java entity ==="
cat -n ./src/main/java/cc/backend/notice/entity/MemberNotice.java

# Check if there are other entities with similar join columns to compare patterns
echo -e "\n=== Check similar entities for patterns ==="
rg -A 5 "@JoinColumn.*nullable" ./src/main/java/cc/backend --type java | head -40

Repository: SeeATheater/CC_Backend

Length of output: 4699


🏁 Script executed:

#!/bin/bash
# Check for data cleanup or migration utilities
echo "=== Searching for data migration/cleanup utilities ==="
find ./src -type f -name "*Migration*" -o -name "*Cleanup*" -o -name "*Backfill*" 2>/dev/null

echo -e "\n=== Searching for `@Sql` or schema initialization scripts ==="
rg -l "schema|INSERT|UPDATE.*member_notice|UPDATE.*notice_id" ./src --type java -i | head -10

echo -e "\n=== Checking MemberNotice repository/service for data handling ==="
find ./src -path "*notice*" -name "*Repository.java" -o -path "*notice*" -name "*Service.java" 2>/dev/null | head -10

Repository: SeeATheater/CC_Backend

Length of output: 1243


🏁 Script executed:

#!/bin/bash
# Check MemberNoticeService for data handling
echo "=== MemberNoticeService.java ==="
wc -l ./src/main/java/cc/backend/notice/service/MemberNoticeService.java
cat ./src/main/java/cc/backend/notice/service/MemberNoticeService.java

echo -e "\n=== MemberNoticeRepository.java ==="
cat ./src/main/java/cc/backend/notice/repository/MemberNoticeRepository.java

Repository: SeeATheater/CC_Backend

Length of output: 5433


Verify database has no NULL rows in notice_id or member_id columns before deploying.

With spring.jpa.hibernate.ddl-auto: update, Hibernate will automatically attempt to add NOT NULL constraints on lines 22 and 26. If the member_notice table contains any existing rows with NULL values in these columns, the schema update will fail and prevent application startup. Ensure all existing records have both notice_id and member_id populated, or add a data cleanup/backfill script before deployment to production.

🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/notice/entity/MemberNotice.java` around lines 21 -
27, The MemberNotice entity now marks associations Notice and Member as
non-nullable (JoinColumn name="notice_id" and name="member_id"); before
deploying with spring.jpa.hibernate.ddl-auto=update, ensure the member_notice
table contains no NULLs in notice_id or member_id by running a pre-deployment
data migration: identify rows where notice_id IS NULL or member_id IS NULL,
either delete or backfill them with valid foreign keys (or create placeholder
records in Notice/Member and set those FKs), and verify referential integrity;
alternatively, if immediate schema change is unsafe, temporarily keep the
JoinColumn nullable in MemberNotice and add a controlled migration that cleans
data then flips the constraint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice와 연결 안된 MemberNotice row는 존재하지 않으므로 문제 없음

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sweatbuckets, 확인해 주셔서 감사합니다! 데이터 무결성이 보장되어 있다면 스키마 마이그레이션이 안전하게 진행될 것입니다. 👍


private Boolean isRead = false;
private String personalMsg;

private boolean isRead = false;

@Builder
public MemberNotice( Notice notice, Member member) {
this.isRead = false ;
public MemberNotice(Notice notice, Member member, String personalMsg, boolean isRead) {
this.notice = notice;
this.member = member;
this.personalMsg = personalMsg;
this.isRead = isRead;
}

public MemberNotice updateIsRead(){
public void updateIsRead(){
this.isRead = true ;
return this;
}


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package cc.backend.notice.entity.enums;

public enum NoticeType {
AMATEURSHOW, HOT, COMMENT, REPLY, TICKET, REMIND, AD
AMATEURSHOW, HOT, COMMENT, REPLY, TICKET, REMIND, RECOMMEND
Copy link

@coderabbitai coderabbitai bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle backward compatibility for renamed enum value.

Renaming ADRECOMMEND will break any persisted values or payloads that still use "AD" (JPA EnumType.STRING, JSON, Kafka events). Please add a migration/backfill and/or a compatibility shim (e.g., custom converter/JSON creator) so legacy data and messages continue to deserialize.

🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/notice/entity/enums/NoticeType.java` at line 4,
NoticeType enum was renamed from AD to RECOMMEND and will break
deserialization/persistence; add a compatibility shim and optional backfill:
implement a static fromString factory on NoticeType (e.g.,
NoticeType.fromString(String)) that returns RECOMMEND when input equals "AD"
(case-insensitive) and use it for JSON deserialization via a `@JsonCreator` on
NoticeType and for JPA by adding an AttributeConverter<NoticeType,String> (or
update the existing converter) to map "AD" → RECOMMEND and otherwise delegate to
name(); additionally add a one-off DB migration/backfill script to update
persisted "AD" values to "RECOMMEND" to eliminate future compatibility handling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AD 쓰는 코드 없어서 backward compatibility 안챙겨도됨

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for any usage of NoticeType.AD in the codebase
rg -n 'NoticeType\.AD' --type=java

# Search for any string literal "AD" in notice-related contexts
rg -n '"AD"' --type=java -C2 -g '**/notice/**'

# Search for any references to AD in the enum context
rg -n '\bAD\b' --type=java -C2 -g '**/NoticeType.java'

# Check for any persisted AD values in test fixtures or migrations
fd -e sql -e yml -e yaml -e json | xargs rg -l '\bAD\b' 2>/dev/null || echo "No SQL/config files with AD found"

Length of output: 33


@sweatbuckets 확인했습니다! 코드베이스에서 NoticeType.AD의 사용이 전혀 없는 것을 확인했으니, backward compatibility 고려 없이 RECOMMEND로 변경하셔도 문제없습니다. 👍


✏️ Learnings added
Learnt from: sweatbuckets
Repo: SeeATheater/CC_Backend PR: 144
File: src/main/java/cc/backend/notice/entity/enums/NoticeType.java:4-4
Timestamp: 2026-01-16T20:03:01.596Z
Learning: The NoticeType enum value AD was never used in the codebase and was safely replaced with RECOMMEND without backward compatibility concerns.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cc.backend.event.entity;
package cc.backend.notice.event.entity;

import cc.backend.amateurShow.entity.AmateurShow;
import cc.backend.member.entity.Member;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cc.backend.event.entity;
package cc.backend.notice.event.entity;

import lombok.Getter;

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/cc/backend/notice/event/entity/NewShowEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cc.backend.notice.event.entity;

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

import java.io.Serializable;
import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class NewShowEvent {
private Long amateurShowId; // 공연 ID
private Long performerId; // 공연 등록자 ID
private List<Long> likerIds; // 좋아요한 유저 ID 리스트
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cc.backend.event.entity;
package cc.backend.notice.event.entity;

import cc.backend.member.entity.Member;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cc.backend.event.entity;
package cc.backend.notice.event.entity;

import cc.backend.amateurShow.entity.AmateurShow;
import cc.backend.member.entity.Member;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cc.backend.event.entity;
package cc.backend.notice.event.entity;

import lombok.Getter;

Expand Down
Loading