Skip to content

Commit e26c94f

Browse files
Merge branch 'dev' into refactor/delivery-schedule-individual-transaction(WR9-142)
# Conflicts: # src/main/java/io/crops/warmletter/global/config/AsyncConfig.java
2 parents 27f7336 + ae35759 commit e26c94f

File tree

15 files changed

+150
-60
lines changed

15 files changed

+150
-60
lines changed

src/main/java/io/crops/warmletter/domain/badword/controller/BadWordController.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.crops.warmletter.domain.badword.dto.request.CreateBadWordRequest;
55
import io.crops.warmletter.domain.badword.dto.request.UpdateBadWordRequest;
66
import io.crops.warmletter.domain.badword.dto.request.UpdateBadWordStatusRequest;
7+
import io.crops.warmletter.domain.badword.dto.response.BadWordResponse;
78
import io.crops.warmletter.domain.badword.dto.response.UpdateBadWordResponse;
89
import io.crops.warmletter.domain.badword.service.BadWordService;
910
import io.crops.warmletter.global.response.BaseResponse;
@@ -28,19 +29,18 @@ public class BadWordController {
2829

2930
@PostMapping
3031
@Operation(summary = "금칙어 등록", description = "금칙어 등록하는 API입니다.")
31-
public ResponseEntity<BaseResponse<Void>> createBadWord(@RequestBody @Valid CreateBadWordRequest request) {
32-
badWordService.createBadWord(request);
33-
return ResponseEntity.ok(BaseResponse.of(null, "금칙어 등록완료"));
32+
public ResponseEntity<BaseResponse<BadWordResponse>> createBadWord(@RequestBody @Valid CreateBadWordRequest request) {
33+
BadWordResponse response = badWordService.createBadWord(request);
34+
return ResponseEntity.ok(BaseResponse.of(response, "금칙어 등록완료"));
3435
}
3536

3637
@PatchMapping("/{badWordId}/status")
3738
@Operation(summary = "금칙어 상태변경", description = "금칙어 상태변경 활성여부 API입니다.")
38-
public ResponseEntity<BaseResponse<Void>> updateBadWordStatus(
39+
public ResponseEntity<BaseResponse<BadWordResponse>> updateBadWordStatus(
3940
@PathVariable Long badWordId,
4041
@RequestBody @Valid UpdateBadWordStatusRequest request) {
41-
42-
badWordService.updateBadWordStatus(badWordId, request);
43-
return ResponseEntity.ok(BaseResponse.of(null, "금칙어 상태 변경 완료"));
42+
BadWordResponse response = badWordService.updateBadWordStatus(badWordId, request);
43+
return ResponseEntity.ok(BaseResponse.of(response, "금칙어 상태 변경 완료"));
4444
}
4545

4646
@GetMapping

src/main/java/io/crops/warmletter/domain/badword/dto/response/BadWordResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.crops.warmletter.domain.badword.dto.response;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import lombok.AllArgsConstructor;
56
import lombok.Getter;
@@ -11,4 +12,11 @@ public class BadWordResponse {
1112
private Long id;
1213
@Schema(description = "금지어 단어", example = "비속어")
1314
private String word;
15+
16+
private boolean isUsed;
17+
18+
@JsonProperty("isUsed")
19+
public boolean isUsed() {
20+
return isUsed;
21+
}
1422
}

src/main/java/io/crops/warmletter/domain/badword/repository/BadWordRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
public interface BadWordRepository extends JpaRepository<BadWord, Long> {
1313
boolean existsByWord(String word);
1414

15-
@Query("SELECT new io.crops.warmletter.domain.badword.dto.response.BadWordResponse(b.id, b.word) FROM BadWord b WHERE b.isUsed = true")
15+
@Query("SELECT new io.crops.warmletter.domain.badword.dto.response.BadWordResponse(b.id, b.word, b.isUsed) FROM BadWord b ")
1616
List<BadWordResponse> findAllBadWords();
1717

1818
}

src/main/java/io/crops/warmletter/domain/badword/service/BadWordService.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.crops.warmletter.domain.badword.dto.request.CreateBadWordRequest;
55
import io.crops.warmletter.domain.badword.dto.request.UpdateBadWordRequest;
66
import io.crops.warmletter.domain.badword.dto.request.UpdateBadWordStatusRequest;
7+
import io.crops.warmletter.domain.badword.dto.response.BadWordResponse;
78
import io.crops.warmletter.domain.badword.dto.response.UpdateBadWordResponse;
89
import io.crops.warmletter.domain.badword.entity.BadWord;
910
import io.crops.warmletter.domain.badword.exception.BadWordContainsException;
@@ -33,7 +34,8 @@ public class BadWordService {
3334
private static final String BAD_WORD_PATTERN = "[^가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9\\s]";
3435

3536

36-
public void createBadWord(CreateBadWordRequest request) {
37+
@Transactional
38+
public BadWordResponse createBadWord(CreateBadWordRequest request) {
3739
String word = request.getWord();
3840

3941
boolean exists = badWordRepository.existsByWord(word);
@@ -46,38 +48,43 @@ public void createBadWord(CreateBadWordRequest request) {
4648
.isUsed(true)
4749
.build();
4850

49-
50-
badWordRepository.save(badWord);
51+
BadWord savedBadWord = badWordRepository.save(badWord);
5152

5253
redisTemplate.opsForHash().put(BAD_WORD_KEY, badWord.getId().toString(), word);
54+
return new BadWordResponse(savedBadWord.getId(), savedBadWord.getWord(), savedBadWord.isUsed());
5355
}
5456

5557

5658
@Transactional
57-
public void updateBadWordStatus(Long badWordId, UpdateBadWordStatusRequest request) {
59+
public BadWordResponse updateBadWordStatus(Long badWordId, UpdateBadWordStatusRequest request) {
5860
BadWord badWord = badWordRepository.findById(badWordId)
5961
.orElseThrow(BadWordNotFoundException::new);
6062
badWord.updateStatus(request.isUsed());
63+
BadWord savedBadWord = badWordRepository.save(badWord);
64+
redisTemplate.opsForHash().delete(BAD_WORD_KEY,badWordId.toString(), badWord.getWord());
65+
redisTemplate.opsForHash().put(BAD_WORD_KEY,badWordId.toString(), badWord.getWord());
66+
return new BadWordResponse(savedBadWord.getId(), savedBadWord.getWord(), savedBadWord.isUsed());
6167

62-
if (request.isUsed()) {
63-
redisTemplate.opsForHash().put(BAD_WORD_KEY,badWordId.toString(), badWord.getWord());
64-
} else {
65-
redisTemplate.opsForHash().delete(BAD_WORD_KEY,badWordId.toString(), badWord.getWord());
66-
}
6768
}
6869

6970
public List<Map<String, String>> getBadWords() {
7071
Map<Object, Object> entries = redisTemplate.opsForHash().entries(BAD_WORD_KEY);
7172
return entries.entrySet().stream()
7273
.map(e -> {
7374
Map<String, String> map = new HashMap<>();
74-
map.put("id", e.getKey().toString());
75+
// 여기서 id 변수를 선언합니다.
76+
String id = e.getKey().toString();
77+
map.put("id", id);
7578
map.put("word", e.getValue().toString());
79+
// DB에서 조회한 isUsed 값을 포함 (없으면 기본값 false)
80+
Optional<BadWord> optional = badWordRepository.findById(Long.valueOf(id));
81+
map.put("isUsed", optional.map(bw -> Boolean.toString(bw.isUsed())).orElse("false"));
7682
return map;
7783
})
78-
.toList();
84+
.collect(Collectors.toList());
7985
}
8086

87+
8188
@Transactional
8289
public UpdateBadWordResponse updateBadWord(Long id, UpdateBadWordRequest request) {
8390
BadWord badWord = badWordRepository.findById(id)

src/main/java/io/crops/warmletter/domain/report/service/ReportService.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.crops.warmletter.domain.report.dto.response.ReportsResponse;
1818
import io.crops.warmletter.domain.report.dto.response.UpdateReportResponse;
1919
import io.crops.warmletter.domain.report.entity.Report;
20+
import io.crops.warmletter.domain.report.enums.ReasonType;
2021
import io.crops.warmletter.domain.report.enums.ReportStatus;
2122
import io.crops.warmletter.domain.report.enums.ReportType;
2223
import io.crops.warmletter.domain.report.exception.DuplicateReportException;
@@ -30,20 +31,19 @@
3031
import io.crops.warmletter.domain.share.repository.ShareProposalRepository;
3132
import io.crops.warmletter.domain.timeline.dto.request.NotificationRequest;
3233
import io.crops.warmletter.domain.timeline.enums.AlarmType;
33-
import io.crops.warmletter.domain.timeline.facade.NotificationFacade;
3434
import jakarta.transaction.Transactional;
3535
import lombok.RequiredArgsConstructor;
3636
import org.springframework.context.ApplicationEventPublisher;
3737
import org.springframework.data.domain.Page;
3838
import org.springframework.data.domain.Pageable;
39+
import org.springframework.scheduling.annotation.Async;
3940
import org.springframework.stereotype.Service;
4041

4142
import java.time.LocalDateTime;
4243
import java.util.HashMap;
4344
import java.util.List;
4445
import java.util.Map;
4546
import java.util.Optional;
46-
import java.util.concurrent.CompletableFuture;
4747

4848
@Service
4949
@RequiredArgsConstructor
@@ -101,6 +101,7 @@ public Page<ReportsResponse> getAllReports(String reportType, String status, Pag
101101
@Transactional
102102
public ReportResponse createReport(CreateReportRequest request) {
103103
Long memberId = authFacde.getCurrentUserId();
104+
//Long memberId = 1L;
104105
Map<String, String> reportedContentMap = new HashMap<>();
105106
validateRequest(request, memberId, reportedContentMap);
106107
String reportedContent = reportedContentMap.get("content");
@@ -124,13 +125,19 @@ public ReportResponse createReport(CreateReportRequest request) {
124125
}
125126
Report report = builder.build();
126127
Report savedReport = reportRepository.save(report);
127-
CompletableFuture.runAsync(() -> {
128-
Map<String, String> moderationResult = reportModerationService.moderateText(reportedContent, request.getReasonType(), request.getReason());
129-
updateReportWithAIResult(savedReport.getId(), moderationResult);
130-
});
128+
129+
processReportInBackground(savedReport.getId(), reportedContent, request.getReasonType(), request.getReason());
130+
131131
return new ReportResponse(savedReport);
132132
}
133133

134+
// 신고 AI 판별 비동기 처리
135+
@Async
136+
public void processReportInBackground(Long reportId, String reportedContent, ReasonType reasonType, String reason) {
137+
Map<String, String> moderationResult = reportModerationService.moderateText(reportedContent, reasonType, reason);
138+
updateReportWithAIResult(reportId, moderationResult);
139+
}
140+
134141
@Transactional
135142
public void updateReportWithAIResult(Long reportId, Map<String, String> moderationResult) {
136143
Report report = reportRepository.findById(reportId)

src/main/java/io/crops/warmletter/domain/share/repository/ShareProposalRepository.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ public interface ShareProposalRepository extends JpaRepository<ShareProposal,Lon
99
@Query("SELECT m.zipCode " +
1010
"FROM ShareProposal proposal " +
1111
"JOIN Member m ON proposal.requesterId = m.id " +
12-
"WHERE proposal.requesterId = :requesterId")
13-
String findZipCodeByRequesterId(Long requesterId);
12+
"WHERE proposal.id = :id " +
13+
"AND proposal.requesterId = :requesterId ")
14+
String findZipCodeByRequesterId(Long id, Long requesterId);
1415

1516
@Query("SELECT m.zipCode " +
1617
"FROM ShareProposal proposal " +
1718
"JOIN Member m ON proposal.recipientId = m.id " +
18-
"WHERE proposal.recipientId = :recipientId")
19-
String findZipCodeByRecipientId(Long recipientId);
19+
"WHERE proposal.id = :id " +
20+
"AND proposal.recipientId = :recipientId ")
21+
String findZipCodeByRecipientId(Long id, Long recipientId);
2022
}

src/main/java/io/crops/warmletter/domain/share/service/SharePostLikeService.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ public SharePostLikeResponse getLikeCountAndStatus(Long sharePostId) {
2727
if (sharePostId == null)
2828
throw new ShareInvalidInputValue();
2929

30-
return sharePostLikeRepository.getLikeCountAndStatus(sharePostId,memberId);
30+
boolean isLikedInRedis = postLikeRedisManager.isLiked(sharePostId,memberId);
31+
32+
SharePostLikeResponse response = sharePostLikeRepository.getLikeCountAndStatus(sharePostId,memberId);
33+
34+
if (isLikedInRedis != response.isLiked()) {
35+
return new SharePostLikeResponse(response.getLikeCount(), isLikedInRedis);
36+
}
37+
38+
return response;
3139
}
3240
}

src/main/java/io/crops/warmletter/domain/share/service/ShareProposalService.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.crops.warmletter.domain.share.repository.*;
1515
import io.crops.warmletter.domain.timeline.dto.request.NotificationRequest;
1616
import io.crops.warmletter.domain.timeline.enums.AlarmType;
17-
import io.crops.warmletter.domain.timeline.facade.NotificationFacade;
1817
import lombok.RequiredArgsConstructor;
1918
import org.springframework.context.ApplicationEventPublisher;
2019
import org.springframework.stereotype.Service;
@@ -79,8 +78,8 @@ public ShareProposalStatusResponse approveShareProposal(Long shareProposalId) {
7978
.build();
8079
sharePost = sharePostRepository.save(sharePost);
8180
// 알림 전송(양쪽 다)
82-
String requestZipCode = shareProposalRepository.findZipCodeByRequesterId(shareProposal.getRequesterId());
83-
String recipientZipCode = shareProposalRepository.findZipCodeByRecipientId(shareProposal.getRecipientId());
81+
String requestZipCode = shareProposalRepository.findZipCodeByRequesterId(shareProposalId, shareProposal.getRequesterId());
82+
String recipientZipCode = shareProposalRepository.findZipCodeByRecipientId(shareProposalId, shareProposal.getRecipientId());
8483
notificationPublisher.publishEvent(NotificationRequest.builder()
8584
.senderZipCode(recipientZipCode)
8685
.receiverId(shareProposal.getRequesterId())

src/main/java/io/crops/warmletter/domain/timeline/facade/NotificationFacade.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.crops.warmletter.domain.timeline.facade;
22

33
import io.crops.warmletter.domain.timeline.dto.request.NotificationRequest;
4-
import io.crops.warmletter.domain.timeline.enums.AlarmType;
54
import io.crops.warmletter.domain.timeline.service.NotificationService;
65
import lombok.RequiredArgsConstructor;
76
import org.springframework.scheduling.annotation.Async;

src/main/java/io/crops/warmletter/domain/timeline/service/NotificationService.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.crops.warmletter.domain.timeline.service;
22

3-
import io.crops.warmletter.domain.auth.exception.UnauthorizedException;
43
import io.crops.warmletter.domain.auth.facade.AuthFacade;
54
import io.crops.warmletter.domain.timeline.dto.response.NotificationResponse;
65
import io.crops.warmletter.domain.timeline.entity.Timeline;
@@ -9,6 +8,7 @@
98
import lombok.RequiredArgsConstructor;
109
import lombok.extern.slf4j.Slf4j;
1110
import org.springframework.http.MediaType;
11+
import org.springframework.scheduling.annotation.Async;
1212
import org.springframework.scheduling.annotation.Scheduled;
1313
import org.springframework.stereotype.Service;
1414
import org.springframework.transaction.annotation.Transactional;
@@ -57,7 +57,9 @@ protected void handleTimeout(Long memberId, SseEmitter emitter) {
5757
}
5858

5959
// 편지 수신, 신고 조치, 공유 요청, 공유 게시글 등록 시 호출 필요
60-
public void createNotification(String senderZipCode, Long receiverId, AlarmType alarmType, String data){
60+
@Transactional
61+
public void createNotification(String senderZipCode, Long receiverId, AlarmType alarmType, String data) {
62+
6163
Timeline.TimelineBuilder builder = Timeline.builder()
6264
.memberId(receiverId)
6365
// data = LETTER: letterId / REPORT: adminMemo, 경고횟수 / SHARE: shareProposalId / POSTED: sharePostId
@@ -111,6 +113,7 @@ protected void sendEventToClient(Long receiverId, NotificationResponse notificat
111113
}
112114

113115
// 연결을 확인하기 위한 Heartbeat를 30초마다 실행
116+
@Async
114117
@Scheduled(fixedRate = 30000)
115118
public void sendHeartbeat() {
116119
NotificationResponse notificationResponse = NotificationResponse.builder()

0 commit comments

Comments
 (0)