Skip to content

Commit bb611dc

Browse files
authored
Merge pull request #306 from prgrms-web-devcourse-final-project/chore#305
[chore] 환경변수 재설정 위한 브랜치 병합 테스트
2 parents 673b55e + fdea679 commit bb611dc

File tree

18 files changed

+811
-156
lines changed

18 files changed

+811
-156
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ terraform/secrets.tf
5555
### Claude AI ###
5656
CLAUDE.md
5757
.claude/
58+
59+
## dataSet ##
60+
cocktails_ver00.csv
61+
cocktails_ver01.csv

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies {
4040
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
4141
implementation("org.springframework.boot:spring-boot-starter-batch")
4242
testImplementation("org.springframework.batch:spring-batch-test")
43+
4344
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
4445
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")
4546
compileOnly("org.projectlombok:lombok")

src/main/java/com/back/domain/cocktail/comment/repository/CocktailCommentRepository.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
@Repository
1111
public interface CocktailCommentRepository extends JpaRepository<CocktailComment, Long> {
1212

13-
List<CocktailComment> findTop10ByCocktailIdOrderByIdDesc(Long cocktailId);
13+
List<CocktailComment> findTop10ByCocktailIdAndStatusInOrderByIdDesc(
14+
Long cocktailId, List<CommentStatus> statuses
15+
);
1416

15-
List<CocktailComment> findTop10ByCocktailIdAndIdLessThanOrderByIdDesc(Long cocktailId, Long lastId);
17+
List<CocktailComment> findTop10ByCocktailIdAndStatusInAndIdLessThanOrderByIdDesc(
18+
Long cocktailId, List<CommentStatus> statuses, Long lastId
19+
);
1620

1721
boolean existsByCocktailIdAndUserIdAndStatusNot(Long cocktailId, Long id, CommentStatus status);
1822
}

src/main/java/com/back/domain/cocktail/comment/service/CocktailCommentService.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.back.domain.cocktail.repository.CocktailRepository;
1010
import com.back.domain.post.comment.enums.CommentStatus;
1111
import com.back.domain.user.entity.User;
12+
import com.back.global.exception.UnauthorizedException;
1213
import com.back.global.rq.Rq;
1314
import lombok.RequiredArgsConstructor;
1415
import org.springframework.stereotype.Service;
@@ -46,6 +47,7 @@ public CocktailCommentResponseDto createCocktailComment(Long cocktailId, Cocktai
4647
.cocktail(cocktail)
4748
.user(user)
4849
.content(reqBody.content())
50+
.status(reqBody.status())
4951
.build();
5052

5153
return new CocktailCommentResponseDto(cocktailCommentRepository.save(cocktailComment));
@@ -54,17 +56,34 @@ public CocktailCommentResponseDto createCocktailComment(Long cocktailId, Cocktai
5456
// 칵테일 댓글 다건 조회 로직 (무한스크롤)
5557
@Transactional(readOnly = true)
5658
public List<CocktailCommentResponseDto> getCocktailComments(Long cocktailId, Long lastId) {
59+
User actor = rq.getActor(); // 서비스에서 호출 가능
60+
61+
if (actor == null) {
62+
throw new UnauthorizedException("로그인이 필요합니다.");
63+
}
64+
Long currentUserId = actor.getId();
65+
List<CocktailComment> comments;
66+
5767
if (lastId == null) {
58-
return cocktailCommentRepository.findTop10ByCocktailIdOrderByIdDesc(cocktailId)
59-
.stream()
60-
.map(CocktailCommentResponseDto::new)
61-
.toList();
68+
comments = cocktailCommentRepository
69+
.findTop10ByCocktailIdAndStatusInOrderByIdDesc(cocktailId, List.of(CommentStatus.PUBLIC, CommentStatus.PRIVATE)
70+
);
6271
} else {
63-
return cocktailCommentRepository.findTop10ByCocktailIdAndIdLessThanOrderByIdDesc(cocktailId, lastId)
64-
.stream()
65-
.map(CocktailCommentResponseDto::new)
66-
.toList();
72+
comments = cocktailCommentRepository
73+
.findTop10ByCocktailIdAndStatusInAndIdLessThanOrderByIdDesc(cocktailId, List.of(CommentStatus.PUBLIC, CommentStatus.PRIVATE),
74+
lastId);
6775
}
76+
77+
return comments.stream()
78+
.filter(comment ->{
79+
if(comment.getStatus() == CommentStatus.PUBLIC) return true;
80+
if(comment.getStatus() == CommentStatus.PRIVATE) {
81+
return comment.getUser().getId().equals(currentUserId);
82+
}
83+
return false;
84+
})
85+
.map(CocktailCommentResponseDto::new)
86+
.toList();
6887
}
6988

7089
// 칵테일 댓글 단건 조회 로직

src/main/java/com/back/domain/cocktail/service/CocktailService.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,19 @@ private String convertFractions(String ingredient) {
133133
if (ingredient == null) return null;
134134

135135
// 치환 테이블 생성
136-
Map<String, String> fractionMap = Map.of(
137-
"1/2", "½",
138-
"1/3", "⅓",
139-
"2/3", "⅔",
140-
"1/4", "¼",
141-
"3/4", "¾",
142-
"1/8", "⅛",
143-
"3/8", "⅜",
144-
"5/8", "⅝",
145-
"7/8", "⅞"
136+
Map<String, String> fractionMap = Map.ofEntries(
137+
Map.entry("1/2", "½"),
138+
Map.entry("1/3", "⅓"),
139+
Map.entry("2/3", "⅔"),
140+
Map.entry("1/4", "¼"),
141+
Map.entry("3/4", "¾"),
142+
Map.entry("1/8", "⅛"),
143+
Map.entry("3/8", "⅜"),
144+
Map.entry("5/8", "⅝"),
145+
Map.entry("7/8", "⅞"),
146+
Map.entry("1/5", "⅕"),
147+
Map.entry("2/5", "⅖"),
148+
Map.entry("1/6", "⅙")
146149
);
147150

148151
// 테이블 기반 치환
@@ -176,7 +179,7 @@ private List<IngredientDto> parseIngredients(String ingredientStr) {
176179

177180
// (숫자 + 선택적 분수) + (공백) + (단위)
178181
Pattern pattern = Pattern.compile(
179-
"^([0-9]*\\s*[½⅓⅔¼¾⅛⅜⅝⅞]?)\\s*(.*)$",
182+
"^([0-9]*\\s*[½⅓⅔¼¾⅛⅜⅝⅞⅕⅖⅙]?)\\s*(.*)$",
180183
Pattern.UNICODE_CHARACTER_CLASS
181184
);
182185
Matcher matcher = pattern.matcher(amountUnit);

src/main/java/com/back/domain/notification/controller/NotificationController.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import jakarta.validation.Valid;
1313
import jakarta.validation.constraints.Max;
1414
import jakarta.validation.constraints.Min;
15+
import java.time.LocalDateTime;
1516
import lombok.RequiredArgsConstructor;
1617
import org.springframework.format.annotation.DateTimeFormat;
1718
import org.springframework.security.access.prepost.PreAuthorize;
@@ -20,8 +21,6 @@
2021
import org.springframework.web.bind.annotation.*;
2122
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
2223

23-
import java.time.LocalDateTime;
24-
2524
@RestController
2625
@RequestMapping("/me")
2726
@RequiredArgsConstructor
@@ -120,7 +119,17 @@ public RsData<NotificationGoResponseDto> goPostLink(
120119
@PathVariable("id") Long notificationId
121120
) {
122121
Long userId = principal.getId();
123-
var body = notificationService.markAsReadAndGetPostLink(userId, notificationId);
122+
NotificationGoResponseDto body = notificationService.markAsReadAndGetPostLink(userId, notificationId);
124123
return RsData.successOf(body);
125124
}
125+
126+
@DeleteMapping("/notifications")
127+
@Operation(summary = "Delete all notifications", description = "Remove every notification belonging to the authenticated user")
128+
public RsData<Void> deleteNotifications(
129+
@AuthenticationPrincipal SecurityUser principal
130+
) {
131+
Long userId = principal.getId();
132+
notificationService.deleteAll(userId);
133+
return RsData.of(200, "cleared");
134+
}
126135
}

src/main/java/com/back/domain/notification/repository/NotificationRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.back.domain.notification.entity.Notification;
44
import org.springframework.data.domain.Pageable;
55
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Modifying;
67
import org.springframework.data.jpa.repository.Query;
78
import org.springframework.data.repository.query.Param;
89
import org.springframework.stereotype.Repository;
@@ -37,4 +38,8 @@ List<Notification> findMyNotificationsAfter(@Param("userId") Long userId,
3738
where n.id = :id and n.user.id = :userId
3839
""")
3940
Notification findByIdAndUserId(@Param("id") Long id, @Param("userId") Long userId);
41+
42+
@Modifying(clearAutomatically = true, flushAutomatically = true)
43+
long deleteByUser_Id(Long userId);
44+
4045
}

src/main/java/com/back/domain/notification/service/NotificationService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,9 @@ public void sendNotification(User user, Post post, NotificationType type, String
115115
}
116116
}
117117
}
118+
119+
@Transactional
120+
public void deleteAll(Long userId) {
121+
notificationRepository.deleteByUser_Id(userId);
122+
}
118123
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.back.global.appConfig;
2+
3+
import com.back.global.standard.util.DecimalToFractionConverter;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.batch.core.Job;
6+
import org.springframework.batch.core.Step;
7+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
8+
import org.springframework.batch.core.job.builder.JobBuilder;
9+
import org.springframework.batch.core.repository.JobRepository;
10+
import org.springframework.batch.core.step.builder.StepBuilder;
11+
import org.springframework.batch.item.ItemProcessor;
12+
import org.springframework.batch.item.file.FlatFileItemReader;
13+
import org.springframework.batch.item.file.FlatFileItemWriter;
14+
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
15+
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
16+
import org.springframework.batch.item.file.mapping.PassThroughLineMapper;
17+
import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
18+
import org.springframework.context.annotation.Bean;
19+
import org.springframework.context.annotation.Configuration;
20+
import org.springframework.core.io.FileSystemResource;
21+
import org.springframework.transaction.PlatformTransactionManager;
22+
23+
// DecimalToFractionConverter가 있다고 가정합니다.
24+
// import com.example.DecimalToFractionConverter;
25+
26+
@Configuration
27+
@EnableBatchProcessing
28+
@RequiredArgsConstructor
29+
public class CocktailBatchConfig {
30+
31+
private final JobRepository jobRepository;
32+
private final PlatformTransactionManager transactionManager;
33+
34+
private static final String INPUT_PATH = "src/main/resources/cocktails.csv";
35+
private static final String OUTPUT_PATH = "src/main/resources/cocktails_clean.csv";
36+
37+
// 1.Reader: CSV 파일 한 줄씩 읽기
38+
@Bean
39+
public FlatFileItemReader<String> reader() {
40+
return new FlatFileItemReaderBuilder<String>()
41+
.name("cocktailReader")
42+
.resource(new FileSystemResource(INPUT_PATH))
43+
.lineMapper(new PassThroughLineMapper())
44+
.build();
45+
}
46+
47+
// 2️⃣ Processor: ingredient 컬럼(6번째)만 DecimalToFractionConverter로 변환
48+
@Bean
49+
public ItemProcessor<String, String> processor() {
50+
return line -> {
51+
// CSV 한 줄을 안전하게 split
52+
String[] columns = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
53+
54+
// Header인지 확인: "ingredient"라는 컬럼 이름으로 간단 체크
55+
if (columns.length > 6 && !columns[6].equalsIgnoreCase("ingredient")) {
56+
// DecimalToFractionConverter는 별도로 구현되어 있어야 합니다.
57+
columns[6] = DecimalToFractionConverter.convert(columns[6]);
58+
}
59+
60+
// 다시 CSV 문자열로 합치기
61+
return String.join(",", columns);
62+
};
63+
}
64+
65+
// 3️⃣ Writer: 변환된 CSV 출력 (변화 없음)
66+
@Bean
67+
public FlatFileItemWriter<String> writer() {
68+
return new FlatFileItemWriterBuilder<String>()
69+
.name("cocktailWriter")
70+
.resource(new FileSystemResource(OUTPUT_PATH))
71+
.lineAggregator(new PassThroughLineAggregator<>())
72+
.build();
73+
}
74+
75+
// 4️⃣ Step: Chunk 단위 처리 (StepBuilderFactory 대체)
76+
@Bean
77+
public Step convertStep() {
78+
// StepBuilder를 직접 생성하고, jobRepository와 transactionManager를 주입합니다.
79+
return new StepBuilder("convertStep", jobRepository)
80+
// chunk 메서드에 transactionManager를 인수로 전달합니다.
81+
.<String, String>chunk(5, transactionManager)
82+
.reader(reader())
83+
.processor(processor())
84+
.writer(writer())
85+
.build();
86+
}
87+
88+
// 5️⃣ Job 정의 (JobBuilderFactory 대체)
89+
@Bean
90+
public Job convertJob() {
91+
// JobBuilder를 직접 생성하고, jobRepository를 주입합니다.
92+
return new JobBuilder("convertJob", jobRepository)
93+
.start(convertStep())
94+
.build();
95+
}
96+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.back.global.exception;
2+
3+
public class UnauthorizedException extends ServiceException {
4+
5+
private static final int UNAUTHORIZED_CODE = 401;
6+
private static final String UNAUTHORIZED_MSG = "로그인이 필요합니다.";
7+
8+
public UnauthorizedException() {
9+
super(UNAUTHORIZED_CODE, UNAUTHORIZED_MSG);
10+
}
11+
12+
public UnauthorizedException(String msg) {
13+
super(UNAUTHORIZED_CODE, msg);
14+
}
15+
}

0 commit comments

Comments
 (0)