Skip to content

Commit 93f3763

Browse files
committed
feat: DataLoader,AutoCreateSeason
1 parent 8fb81da commit 93f3763

File tree

8 files changed

+212
-35
lines changed

8 files changed

+212
-35
lines changed

β€Žsrc/main/java/cmf/commitField/domain/season/controller/ApiV1SeasonController.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,4 @@ public Season createSeason() {
3030
public Season getActiveSeason() {
3131
return seasonService.getActiveSeason();
3232
}
33-
34-
//Season scheduler ν…ŒμŠ€νŠΈ μ½”λ“œ
35-
@GetMapping("/scheduler")
36-
public void testResetSeason() {
37-
seasonScheduler.resetSeason();
38-
}
3933
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cmf.commitField.domain.season.controller;
2+
3+
import cmf.commitField.domain.season.dto.UserSeasonDto;
4+
import cmf.commitField.domain.season.entity.UserSeason;
5+
import cmf.commitField.domain.season.service.UserSeasonService;
6+
import cmf.commitField.global.globalDto.GlobalResponse;
7+
import cmf.commitField.global.globalDto.GlobalResponseCode;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
import java.util.List;
16+
import java.util.stream.Collectors;
17+
18+
@RestController
19+
@RequestMapping("/user-season")
20+
@RequiredArgsConstructor
21+
public class ApiV1UserSeasonController {
22+
private final UserSeasonService userSeasonService;
23+
24+
// ν˜„μž¬ μ‹œμ¦Œμ— μœ μ € 랭크 μΆ”κ°€ν•˜κΈ° (SEED λ“±κΈ‰)
25+
@GetMapping("/{userId}")
26+
public GlobalResponse<UserSeasonDto> addUserRank(@PathVariable Long userId) {
27+
UserSeason userSeason = userSeasonService.addUserRank(userId);
28+
UserSeasonDto userSeasonDto = new UserSeasonDto(userSeason);
29+
return GlobalResponse.success(userSeasonDto);
30+
}
31+
32+
// μœ μ €μ˜ λͺ¨λ“  μ‹œμ¦Œ 랭크 쑰회
33+
@GetMapping("/{userId}/ranks")
34+
public GlobalResponse<List<UserSeasonDto>> getUserRanks(@PathVariable Long userId) {
35+
List<UserSeason> userSeasons = userSeasonService.getUserRanks(userId);
36+
37+
// UserSeason -> UserSeasonDto λ³€ν™˜
38+
List<UserSeasonDto> userSeasonDtos = userSeasons.stream()
39+
.map(UserSeasonDto::new)
40+
.collect(Collectors.toList());
41+
42+
return GlobalResponse.success(userSeasonDtos);
43+
}
44+
45+
// νŠΉμ • μ‹œμ¦Œμ˜ μœ μ € 랭크 쑰회
46+
@GetMapping("/{userId}/season/{seasonId}")
47+
public GlobalResponse<UserSeasonDto> getUserRankBySeason(
48+
@PathVariable Long userId,
49+
@PathVariable Long seasonId
50+
) {
51+
UserSeason userSeason = userSeasonService.getUserRankBySeason(userId, seasonId);
52+
UserSeasonDto userSeasonDto = new UserSeasonDto(userSeason);
53+
return GlobalResponse.success(userSeasonDto);
54+
}
55+
}
56+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cmf.commitField.domain.season.dto;
2+
3+
import cmf.commitField.domain.season.entity.Rank;
4+
import cmf.commitField.domain.season.entity.UserSeason;
5+
import lombok.Getter;
6+
7+
@Getter
8+
public class UserSeasonDto {
9+
private Long userId;
10+
private String username;
11+
private String seasonName;
12+
private Rank rank;
13+
14+
public UserSeasonDto(UserSeason userSeason) {
15+
this.userId = userSeason.getUser().getId();
16+
this.username = userSeason.getUser().getUsername();
17+
this.seasonName = userSeason.getSeason().getName();
18+
this.rank = userSeason.getRank();
19+
}
20+
}

β€Žsrc/main/java/cmf/commitField/domain/season/entity/UserSeason.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
public class UserSeason extends BaseEntity {
1919

2020
@ManyToOne
21-
@JoinColumn(name = "user_id")
21+
@JoinColumn(name = "user_id", nullable = false)
2222
private User user;
2323

2424
@ManyToOne
25-
@JoinColumn(name = "season_id")
25+
@JoinColumn(name = "season_id", nullable = false)
2626
private Season season;
2727

2828
@Enumerated(EnumType.STRING)
29-
@Column(name = "`rank`") // λ°±ν‹±(`)을 μ‚¬μš©ν•˜μ—¬ μ˜ˆμ•½μ–΄ 문제 ν•΄κ²°
29+
@Column(name = "`rank`", nullable = false) // λ°±ν‹±(`)을 μ‚¬μš©ν•˜μ—¬ μ˜ˆμ•½μ–΄ 문제 ν•΄κ²°
3030
private Rank rank; // 예: SEED, TREE λ“±λ“±
3131
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package cmf.commitField.domain.season.repository;
22

3+
import cmf.commitField.domain.season.entity.Season;
34
import cmf.commitField.domain.season.entity.UserSeason;
45
import cmf.commitField.domain.user.entity.User;
56
import org.springframework.data.jpa.repository.JpaRepository;
67
import org.springframework.stereotype.Repository;
78

89
import java.util.List;
10+
import java.util.Optional;
911

1012
@Repository
1113
public interface UserSeasonRepository extends JpaRepository<UserSeason, Long> {
1214
List<UserSeason> findByUser(User user);
15+
Optional<UserSeason> findByUserAndSeason(User user, Season season);
1316
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package cmf.commitField.domain.season.service;
2+
3+
import cmf.commitField.domain.season.entity.Rank;
4+
import cmf.commitField.domain.season.entity.Season;
5+
import cmf.commitField.domain.season.entity.UserSeason;
6+
import cmf.commitField.domain.season.repository.SeasonRepository;
7+
import cmf.commitField.domain.season.repository.UserSeasonRepository;
8+
import cmf.commitField.domain.user.entity.User;
9+
import cmf.commitField.domain.user.repository.UserRepository;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Service;
12+
13+
import java.util.List;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
public class UserSeasonService {
18+
private final UserSeasonRepository userSeasonRepository;
19+
private final UserRepository userRepository;
20+
private final SeasonRepository seasonRepository;
21+
private final SeasonService seasonService;
22+
23+
// ν˜„μž¬ μ‹œμ¦Œμ— μœ μ € 랭크 μΆ”κ°€ν•˜κΈ° (SEED λ“±κΈ‰)
24+
public UserSeason addUserRank(Long userId) {
25+
User user = userRepository.findById(userId)
26+
.orElseThrow(() -> new RuntimeException("User not found"));
27+
28+
Season season = seasonService.getActiveSeason();
29+
30+
UserSeason userSeason = new UserSeason();
31+
userSeason.setUser(user);
32+
userSeason.setSeason(season);
33+
userSeason.setRank(Rank.SEED);
34+
35+
return userSeasonRepository.save(userSeason);
36+
}
37+
38+
// μœ μ €μ˜ λͺ¨λ“  μ‹œμ¦Œ 랭크 쑰회
39+
public List<UserSeason> getUserRanks(Long userId) {
40+
User user = userRepository.findById(userId)
41+
.orElseThrow(() -> new RuntimeException("User not found"));
42+
return userSeasonRepository.findByUser(user);
43+
}
44+
45+
// νŠΉμ • μ‹œμ¦Œμ˜ μœ μ € 랭크 쑰회
46+
public UserSeason getUserRankBySeason(Long userId, Long seasonId) {
47+
User user = userRepository.findById(userId)
48+
.orElseThrow(() -> new RuntimeException("User not found"));
49+
Season season = seasonRepository.findById(seasonId)
50+
.orElseThrow(() -> new RuntimeException("Season not found"));
51+
return userSeasonRepository.findByUserAndSeason(user, season)
52+
.orElseThrow(() -> new RuntimeException("Rank not found for this season"));
53+
}
54+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cmf.commitField.global.jpa;
2+
3+
import cmf.commitField.global.scheduler.SeasonScheduler;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.boot.ApplicationRunner;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.stereotype.Component;
8+
9+
@RequiredArgsConstructor
10+
@Component
11+
public class DataLoader {
12+
13+
private final SeasonScheduler seasonScheduler;
14+
15+
// μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹œμž‘ μ‹œ μžλ™ μ‹€ν–‰λ˜λŠ” λ©”μ„œλ“œ
16+
@Bean
17+
public ApplicationRunner loadInitialData() {
18+
return args -> {
19+
// DataLoaderμ—μ„œ λ°”λ‘œ SeasonScheduler의 λ©”μ„œλ“œλ₯Ό 호좜
20+
seasonScheduler.checkAndCreateNewSeason();
21+
};
22+
}
23+
}

β€Žsrc/main/java/cmf/commitField/global/scheduler/SeasonScheduler.java

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
import cmf.commitField.domain.season.entity.UserSeason;
77
import cmf.commitField.domain.season.repository.SeasonRepository;
88
import cmf.commitField.domain.season.repository.UserSeasonRepository;
9+
import cmf.commitField.domain.season.service.SeasonService;
910
import cmf.commitField.domain.user.entity.User;
1011
import cmf.commitField.domain.user.repository.UserRepository;
11-
import jakarta.transaction.Transactional;
1212
import lombok.RequiredArgsConstructor;
1313
import lombok.extern.slf4j.Slf4j;
1414
import org.springframework.scheduling.annotation.Scheduled;
1515
import org.springframework.stereotype.Component;
1616

17+
import java.time.LocalDate;
1718
import java.time.LocalDateTime;
19+
import java.time.Month;
1820
import java.util.List;
1921

2022
@Component
@@ -25,37 +27,38 @@ public class SeasonScheduler {
2527
private final SeasonRepository seasonRepository;
2628
private final UserSeasonRepository userSeasonRepository;
2729
private final UserRepository userRepository;
30+
private final SeasonService seasonService;
2831

29-
@Scheduled(cron = "0 0 0 1 1,4,7,10 *") // 1μ›”, 4μ›”, 7μ›”, 10μ›” 1일 00:00:00에 μ‹€ν–‰
30-
@Transactional
31-
public void resetSeason() {
32-
log.info("πŸ•’ μ‹œμ¦Œ μ΄ˆκΈ°ν™” μŠ€μΌ€μ€„λŸ¬ μ‹€ν–‰");
32+
// 맀일 μžμ •λ§ˆλ‹€ μ‹œμ¦Œ 확인 및 생성
33+
@Scheduled(cron = "0 0 0 * * *")
34+
public void checkAndCreateNewSeason() {
35+
LocalDate today = LocalDate.now();
36+
String seasonName = today.getYear() + " " + getSeasonName(today.getMonthValue());
3337

34-
// ν˜„μž¬ ν™œμ„±ν™”λœ μ‹œμ¦Œ μ’…λ£Œ
35-
Season currentSeason = seasonRepository.findByStatus(SeasonStatus.ACTIVE);
36-
if (currentSeason != null) {
37-
currentSeason.setStatus(SeasonStatus.INACTIVE);
38-
seasonRepository.save(currentSeason);
39-
log.info("βœ… κΈ°μ‘΄ μ‹œμ¦Œ '{}' μ’…λ£Œλ¨", currentSeason.getName());
40-
}
38+
// ν˜„μž¬ ν™œμ„± μ‹œμ¦Œ 쑰회
39+
Season activeSeason = seasonService.getActiveSeason();
40+
41+
// ν˜„μž¬ ν™œμ„± μ‹œμ¦Œμ΄ μ—†κ±°λ‚˜, ν˜„μž¬ μ‹œμ¦Œκ³Ό λ‹€λ₯΄λ©΄ μƒˆλ‘œμš΄ μ‹œμ¦Œ 생성
42+
if (activeSeason == null || !activeSeason.getName().equals(seasonName)) {
43+
LocalDateTime startDate = getSeasonStartDate(today.getYear(), today.getMonth());
44+
LocalDateTime endDate = startDate.plusMonths(3).minusSeconds(1);
4145

42-
// μƒˆλ‘œμš΄ μ‹œμ¦Œ 생성
43-
LocalDateTime now = LocalDateTime.now();
44-
LocalDateTime start = now.withDayOfMonth(1);
45-
LocalDateTime end = start.plusMonths(3).minusSeconds(1);
46+
//ν˜„μž¬ ν™œμ„± μ‹œμ¦Œ μ’…λ£Œ
47+
if (activeSeason != null) {
48+
activeSeason.setStatus(SeasonStatus.INACTIVE);
49+
seasonRepository.save(activeSeason);
50+
log.info("βœ… κΈ°μ‘΄ μ‹œμ¦Œ '{}' μ’…λ£Œλ¨", activeSeason.getName());
51+
}
4652

47-
Season newSeason = Season.builder()
48-
.name("Season " + start.getYear() + " Q" + ((start.getMonthValue() - 1) / 3 + 1))
49-
.startDate(start)
50-
.endDate(end)
51-
.status(SeasonStatus.ACTIVE)
52-
.build();
53+
Season newSeason = seasonService.createNewSeason(seasonName, startDate, endDate);
5354

54-
seasonRepository.save(newSeason);
55-
log.info("🌟 μƒˆλ‘œμš΄ μ‹œμ¦Œ '{}' 생성됨 (κΈ°κ°„: {} ~ {})", newSeason.getName(), start, end);
55+
// λͺ¨λ“  μœ μ €μ˜ 랭크 μ΄ˆκΈ°ν™”
56+
resetUserRanks(newSeason);
5657

57-
// λͺ¨λ“  μœ μ €μ˜ 랭크 μ΄ˆκΈ°ν™”
58-
resetUserRanks(newSeason);
58+
System.out.println("μƒˆ μ‹œμ¦Œ 생성: " + newSeason.getName());
59+
} else {
60+
System.out.println("이미 ν™œμ„±ν™”λœ μ‹œμ¦Œ: " + activeSeason.getName());
61+
}
5962
}
6063

6164
private void resetUserRanks(Season newSeason) {
@@ -71,4 +74,28 @@ private void resetUserRanks(Season newSeason) {
7174
log.info("πŸ”„ μœ μ € '{}'의 μ‹œμ¦Œ 랭크 μ΄ˆκΈ°ν™” ({} -> 씨앗)", user.getNickname(), newSeason.getName());
7275
}
7376
}
77+
78+
// 월을 κΈ°μ€€μœΌλ‘œ μ‹œμ¦Œ 이름 μ •ν•˜κΈ° (예: 2025 Spring, 2025 Summer)
79+
private String getSeasonName(int month) {
80+
if (month >= 3 && month <= 5) {
81+
return "Spring"; // λ΄„
82+
} else if (month >= 6 && month <= 8) {
83+
return "Summer"; // 여름
84+
} else if (month >= 9 && month <= 11) {
85+
return "Fall"; // 가을
86+
} else {
87+
return "Winter"; // 겨울
88+
}
89+
}
90+
91+
// ν•΄λ‹Ή μ‹œμ¦Œμ˜ μ‹œμž‘ λ‚ μ§œ λ°˜ν™˜
92+
private LocalDateTime getSeasonStartDate(int year, Month month) {
93+
int startMonth = switch (month) {
94+
case MARCH, APRIL, MAY -> 3; // λ΄„
95+
case JUNE, JULY, AUGUST -> 6; // 여름
96+
case SEPTEMBER, OCTOBER, NOVEMBER -> 9; // 가을
97+
default -> 12; // 겨울 (12μ›”λΆ€ν„° μ‹œμž‘)
98+
};
99+
return LocalDateTime.of(year, startMonth, 1, 0, 0);
100+
}
74101
}

0 commit comments

Comments
Β (0)