Skip to content

Commit 86a6c69

Browse files
authored
[feat] 개발 환경 초기 데이터 구성(DevInitData) #158 (#162)
* chore: initData용 이미지 추가 * feat: 개발 환경 초기 데이터 구성(DevInitData) - `DevInitData`에 사용자, 게시판(게시글/댓글/좋아요), 알림, 마이바 데이터 초기화 로직 추가 - cocktailInt: 기본 칵테일 4종 데이터 생성 - userInit: 테스트 사용자(A, B, C) 생성 - boardInit: 게시글, 댓글, 좋아요 샘플 데이터 및 탈퇴한 사용자 B의 데이터 포함 - notificationInit: 사용자별 알림 샘플 데이터 생성 - myBarInit: 사용자의 칵테일 킵(Keep) 기록 샘플 데이터 생성 - 전체 초기화 메서드를 `ApplicationRunner`에 등록하여 애플리케이션 시작 시 데이터 자동 삽입 * feat: SecurityConfig에 회원 탈퇴 엔드포인트 인증 규칙 추가 - `DELETE /me/account` 엔드포인트에 인증된 사용자만 접근 가능하도록 권한 설정 (`.authenticated()`)
1 parent f6732fb commit 86a6c69

File tree

6 files changed

+280
-6
lines changed

6 files changed

+280
-6
lines changed

src/main/java/com/back/global/init/DevInitData.java

Lines changed: 277 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
package com.back.global.init;
22

3+
import com.back.domain.notification.entity.Notification;
4+
import com.back.domain.notification.enums.NotificationType;
5+
import com.back.domain.notification.repository.NotificationRepository;
6+
import com.back.domain.cocktail.entity.Cocktail;
7+
import com.back.domain.cocktail.enums.AlcoholBaseType;
8+
import com.back.domain.cocktail.enums.AlcoholStrength;
9+
import com.back.domain.cocktail.enums.CocktailType;
310
import com.back.domain.cocktail.repository.CocktailRepository;
4-
import com.back.domain.cocktail.service.CocktailService;
11+
import com.back.domain.post.category.entity.Category;
12+
import com.back.domain.post.category.repository.CategoryRepository;
13+
import com.back.domain.post.comment.entity.Comment;
14+
import com.back.domain.post.comment.repository.CommentRepository;
15+
import com.back.domain.post.post.entity.Post;
16+
import com.back.domain.post.post.entity.PostLike;
17+
import com.back.domain.post.post.enums.PostLikeStatus;
18+
import com.back.domain.post.post.repository.PostLikeRepository;
19+
import com.back.domain.post.post.repository.PostRepository;
20+
import com.back.domain.user.entity.User;
21+
import com.back.domain.user.repository.UserRepository;
22+
import org.springframework.transaction.annotation.Transactional;
523
import lombok.RequiredArgsConstructor;
624
import org.springframework.beans.factory.annotation.Autowired;
725
import org.springframework.boot.ApplicationRunner;
@@ -15,19 +33,273 @@
1533
@RequiredArgsConstructor
1634
public class DevInitData {
1735

36+
private final UserRepository userRepository;
37+
private final CategoryRepository categoryRepository;
38+
private final PostRepository postRepository;
39+
private final CommentRepository commentRepository;
40+
private final PostLikeRepository postLikeRepository;
41+
private final NotificationRepository notificationRepository;
1842
private final CocktailRepository cocktailRepository;
19-
private final CocktailService cocktailService;
43+
private final com.back.domain.mybar.service.MyBarService myBarService;
44+
private final com.back.domain.mybar.repository.MyBarRepository myBarRepository;
2045

2146
@Autowired
2247
@Lazy
2348
private DevInitData self;
2449

25-
2650
@Bean
2751
ApplicationRunner devInitDataApplicationRunner() {
2852
return args -> {
29-
// self.cocktailInit(); // 테스트용 데이터 삽입
53+
self.cocktailInit();
54+
self.userInit();
55+
self.boardInit();
56+
self.myBarInit();
57+
self.notificationInit();
3058
};
3159
}
32-
}
3360

61+
@Transactional
62+
public void cocktailInit() {
63+
if (cocktailRepository.count() > 0) return;
64+
65+
// 1) 하이볼
66+
cocktailRepository.save(Cocktail.builder()
67+
.cocktailName("Highball")
68+
.cocktailNameKo("하이볼")
69+
.alcoholStrength(AlcoholStrength.LIGHT)
70+
.cocktailType(CocktailType.LONG)
71+
.alcoholBaseType(AlcoholBaseType.WHISKY)
72+
.ingredient("위스키, 탄산수, 얼음, 레몬피")
73+
.recipe("잔에 얼음 → 위스키 → 탄산수 → 가볍게 스터")
74+
.cocktailImgUrl("/img/cocktail/1.jpg")
75+
.build());
76+
77+
// 2) 진토닉
78+
cocktailRepository.save(Cocktail.builder()
79+
.cocktailName("Gin and Tonic")
80+
.cocktailNameKo("진토닉")
81+
.alcoholStrength(AlcoholStrength.WEAK)
82+
.cocktailType(CocktailType.LONG)
83+
.alcoholBaseType(AlcoholBaseType.GIN)
84+
.ingredient("진, 토닉워터, 얼음, 라임")
85+
.recipe("잔에 얼음 → 진 → 토닉워터 → 라임")
86+
.cocktailImgUrl("/img/cocktail/2.jpg")
87+
.build());
88+
89+
// 3) 올드패션드
90+
cocktailRepository.save(Cocktail.builder()
91+
.cocktailName("Old Fashioned")
92+
.cocktailNameKo("올드패션드")
93+
.alcoholStrength(AlcoholStrength.STRONG)
94+
.cocktailType(CocktailType.SHORT)
95+
.alcoholBaseType(AlcoholBaseType.WHISKY)
96+
.ingredient("버번 위스키, 설탕/시럽, 앙고스투라 비터스, 오렌지 필")
97+
.recipe("시럽+비터스 → 위스키 → 얼음 → 스터 → 오렌지 필")
98+
.cocktailImgUrl("/img/cocktail/3.jpg")
99+
.build());
100+
101+
// 4) 모히또
102+
cocktailRepository.save(Cocktail.builder()
103+
.cocktailName("Mojito")
104+
.cocktailNameKo("모히또")
105+
.alcoholStrength(AlcoholStrength.LIGHT)
106+
.cocktailType(CocktailType.LONG)
107+
.alcoholBaseType(AlcoholBaseType.RUM)
108+
.ingredient("라임, 민트, 설탕/시럽, 화이트 럼, 탄산수, 얼음")
109+
.recipe("라임+민트+시럽 머들 → 럼 → 얼음 → 탄산수")
110+
.cocktailImgUrl("/img/cocktail/4.jpg")
111+
.build());
112+
}
113+
114+
@Transactional
115+
public void userInit() {
116+
userRepository.findByNickname("사용자A").orElseGet(() ->
117+
userRepository.save(User.builder().nickname("사용자A").role("USER").build())
118+
);
119+
userRepository.findByNickname("사용자B").orElseGet(() ->
120+
userRepository.save(User.builder().nickname("사용자B").role("USER").build())
121+
);
122+
userRepository.findByNickname("사용자C").orElseGet(() ->
123+
userRepository.save(User.builder().nickname("사용자C").role("USER").build())
124+
);
125+
}
126+
127+
@Transactional
128+
public void boardInit() {
129+
if (postRepository.count() > 0) return;
130+
131+
User userA = userRepository.findByNickname("사용자A").orElseThrow();
132+
User userB = userRepository.findByNickname("사용자B").orElseThrow();
133+
User userC = userRepository.findByNickname("사용자C").orElseThrow();
134+
135+
Category free = categoryRepository.findAll().stream()
136+
.filter(c -> "자유".equals(c.getName()))
137+
.findFirst()
138+
.orElseGet(() -> categoryRepository.save(Category.builder()
139+
.name("자유")
140+
.description("자유 게시판")
141+
.build()));
142+
143+
Post postA = postRepository.save(Post.builder()
144+
.category(free)
145+
.user(userA)
146+
.title("A의 게시글")
147+
.content("내용A")
148+
.imageUrl("/img/cocktail/1.jpg")
149+
.build());
150+
151+
Post postB = postRepository.save(Post.builder()
152+
.category(free)
153+
.user(userB)
154+
.title("B의 게시글")
155+
.content("내용B")
156+
.imageUrl("/img/cocktail/2.jpg")
157+
.build());
158+
159+
// 댓글: C가 A/B 게시글에 작성
160+
commentRepository.save(Comment.builder()
161+
.post(postA)
162+
.user(userC)
163+
.content("C의 댓글 on A")
164+
.build());
165+
166+
commentRepository.save(Comment.builder()
167+
.post(postB)
168+
.user(userC)
169+
.content("C의 댓글 on B")
170+
.build());
171+
172+
// 좋아요: A→B, B→A, C→A, C→B
173+
postLikeRepository.save(PostLike.builder()
174+
.post(postB)
175+
.user(userA)
176+
.status(PostLikeStatus.LIKE)
177+
.build());
178+
postB.increaseLikeCount();
179+
180+
postLikeRepository.save(PostLike.builder()
181+
.post(postA)
182+
.user(userB)
183+
.status(PostLikeStatus.LIKE)
184+
.build());
185+
postA.increaseLikeCount();
186+
187+
postLikeRepository.save(PostLike.builder()
188+
.post(postA)
189+
.user(userC)
190+
.status(PostLikeStatus.LIKE)
191+
.build());
192+
postA.increaseLikeCount();
193+
194+
postLikeRepository.save(PostLike.builder()
195+
.post(postB)
196+
.user(userC)
197+
.status(PostLikeStatus.LIKE)
198+
.build());
199+
postB.increaseLikeCount();
200+
201+
postRepository.save(postA);
202+
postRepository.save(postB);
203+
204+
// B 사용자 soft delete (게시글/좋아요/댓글 생성 후)
205+
User b = userRepository.findByNickname("사용자B").orElseThrow();
206+
b.markDeleted("탈퇴한 사용자");
207+
userRepository.save(b);
208+
}
209+
210+
@Transactional
211+
public void notificationInit() {
212+
if (notificationRepository.count() > 0) return;
213+
214+
// 게시글 소유자에게 COMMENT/LIKE 알림 생성
215+
Post postA = postRepository.findAll().stream()
216+
.filter(p -> "A의 게시글".equals(p.getTitle()))
217+
.findFirst().orElse(null);
218+
Post postB = postRepository.findAll().stream()
219+
.filter(p -> "B의 게시글".equals(p.getTitle()))
220+
.findFirst().orElse(null);
221+
if (postA == null || postB == null) return;
222+
223+
User ownerA = postA.getUser();
224+
User ownerB = postB.getUser();
225+
226+
notificationRepository.save(Notification.builder()
227+
.user(ownerA)
228+
.post(postA)
229+
.type(NotificationType.COMMENT)
230+
.message("새 댓글이 달렸습니다.")
231+
.build());
232+
233+
notificationRepository.save(Notification.builder()
234+
.user(ownerA)
235+
.post(postA)
236+
.type(NotificationType.LIKE)
237+
.message("게시글에 좋아요가 추가되었습니다.")
238+
.build());
239+
240+
notificationRepository.save(Notification.builder()
241+
.user(ownerA)
242+
.post(postA)
243+
.type(NotificationType.LIKE)
244+
.message("게시글에 좋아요가 추가되었습니다.")
245+
.build());
246+
247+
notificationRepository.save(Notification.builder()
248+
.user(ownerB)
249+
.post(postB)
250+
.type(NotificationType.COMMENT)
251+
.message("새 댓글이 달렸습니다.")
252+
.build());
253+
254+
notificationRepository.save(Notification.builder()
255+
.user(ownerB)
256+
.post(postB)
257+
.type(NotificationType.LIKE)
258+
.message("게시글에 좋아요가 추가되었습니다.")
259+
.build());
260+
261+
notificationRepository.save(Notification.builder()
262+
.user(ownerB)
263+
.post(postB)
264+
.type(NotificationType.LIKE)
265+
.message("게시글에 좋아요가 추가되었습니다.")
266+
.build());
267+
}
268+
269+
@Transactional
270+
public void myBarInit() {
271+
if (myBarRepository.count() > 0) return;
272+
273+
User userA = userRepository.findByNickname("사용자A").orElseThrow();
274+
User userB = userRepository.findByNickname("사용자B").orElseThrow();
275+
User userC = userRepository.findByNickname("사용자C").orElseThrow();
276+
277+
// 칵테일 참조 준비
278+
var cocktails = cocktailRepository.findAll();
279+
Cocktail c1 = cocktails.stream().filter(c -> "하이볼".equals(c.getCocktailNameKo())).findFirst().orElse(null);
280+
Cocktail c2 = cocktails.stream().filter(c -> "진토닉".equals(c.getCocktailNameKo())).findFirst().orElse(null);
281+
Cocktail c3 = cocktails.stream().filter(c -> "올드패션드".equals(c.getCocktailNameKo())).findFirst().orElse(null);
282+
Cocktail c4 = cocktails.stream().filter(c -> "모히또".equals(c.getCocktailNameKo())).findFirst().orElse(null);
283+
284+
// 방어: 칵테일 누락 시 스킵
285+
if (c1 == null || c2 == null || c3 == null || c4 == null) return;
286+
287+
// A: c1(now-2d), c2(now-1d)
288+
myBarService.keep(userA.getId(), c1.getId());
289+
myBarService.keep(userA.getId(), c2.getId());
290+
myBarRepository.findByUser_IdAndCocktail_Id(userA.getId(), c1.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(2)));
291+
myBarRepository.findByUser_IdAndCocktail_Id(userA.getId(), c2.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(1)));
292+
293+
// B: c3 keep 후 unkeep -> DELETED
294+
myBarService.keep(userB.getId(), c3.getId());
295+
myBarService.unkeep(userB.getId(), c3.getId());
296+
297+
// C: c2(now-3d), c3(now-2d), c4(now-1h)
298+
myBarService.keep(userC.getId(), c2.getId());
299+
myBarService.keep(userC.getId(), c3.getId());
300+
myBarService.keep(userC.getId(), c4.getId());
301+
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c2.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(3)));
302+
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c3.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusDays(2)));
303+
myBarRepository.findByUser_IdAndCocktail_Id(userC.getId(), c4.getId()).ifPresent(m -> m.setKeptAt(java.time.LocalDateTime.now().minusHours(1)));
304+
}
305+
}

src/main/java/com/back/global/security/SecurityConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1515

1616
import java.util.Arrays;
17+
import static org.springframework.http.HttpMethod.DELETE;
1718

1819
@Configuration
1920
@EnableWebSecurity
@@ -51,6 +52,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5152
) // OAuth 인증시 필요할때만 세션 사용
5253

5354
.authorizeHttpRequests(auth -> auth
55+
.requestMatchers(DELETE, "/me/account").authenticated()
5456
// 개발 편의성을 위해 모든 요청 허용
5557
.anyRequest().permitAll()
5658

@@ -123,4 +125,4 @@ public CorsConfigurationSource corsConfigurationSource() {
123125
source.registerCorsConfiguration("/**", configuration);
124126
return source;
125127
}
126-
}
128+
}
10.4 KB
Loading
5.39 KB
Loading
6 KB
Loading
5.41 KB
Loading

0 commit comments

Comments
 (0)