-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #96 from 9uttery/feat/achievement-api-#76
[Feature] 소확행 플레이리스트 관련 API 구현
- Loading branch information
Showing
19 changed files
with
524 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
...main/java/com/guttery/madii/domain/achievement/application/dto/AddAchievementRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotNull; | ||
|
||
@Schema(description = "소확행 플레이리스트에 추가 요청") | ||
public record AddAchievementRequest( | ||
@NotNull | ||
@Schema(description = "소확행 ID", example = "1") | ||
Long joyId | ||
) { | ||
} |
12 changes: 12 additions & 0 deletions
12
...n/java/com/guttery/madii/domain/achievement/application/dto/CancelAchievementRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotNull; | ||
|
||
@Schema(description = "소확행 취소 요청") | ||
public record CancelAchievementRequest( | ||
@NotNull | ||
@Schema(description = "소확행 ID", example = "1") | ||
Long achievementId | ||
) { | ||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/guttery/madii/domain/achievement/application/dto/DailyJoyPlaylist.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
import java.time.LocalDate; | ||
import java.util.List; | ||
|
||
@Schema(description = "일일 소확행 플레이리스트") | ||
public record DailyJoyPlaylist( | ||
@Schema(description = "소확행 플레이리스트 날짜", example = "2021-08-01") | ||
LocalDate date, | ||
@Schema(description = "소확행 목록") | ||
List<JoyAchievementInfo> joyAchievementInfos | ||
) { | ||
} |
16 changes: 16 additions & 0 deletions
16
...n/java/com/guttery/madii/domain/achievement/application/dto/FinishAchievementRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import com.guttery.madii.domain.achievement.domain.model.Satisfaction; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotNull; | ||
|
||
@Schema(description = "실천 완료 요청") | ||
public record FinishAchievementRequest( | ||
@NotNull | ||
@Schema(description = "실천 ID", example = "1") | ||
Long achievementId, | ||
@NotNull | ||
@Schema(description = "만족도", example = "VERY_SATISFIED") | ||
Satisfaction satisfaction | ||
) { | ||
} |
21 changes: 21 additions & 0 deletions
21
src/main/java/com/guttery/madii/domain/achievement/application/dto/JoyAchievementInfo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import com.guttery.madii.domain.achievement.domain.model.Satisfaction; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@Schema(description = "소확행 및 실천 정보") | ||
public record JoyAchievementInfo( | ||
@Schema(description = "소확행 아이디", example = "11") | ||
Long joyId, | ||
@Schema(description = "소확행 썸네일 아이콘 번호", example = "3") | ||
Long achievementId, | ||
@Schema(description = "소확행 썸네일 아이콘 번호", example = "3") | ||
Integer joyIconNum, | ||
@Schema(description = "소확행 내용", example = "낮잠자기") | ||
String contents, | ||
@Schema(description = "소확행 실천 여부", example = "true") | ||
Boolean isAchieved, | ||
@Schema(description = "만족도 평가", example = "SO_SO") | ||
Satisfaction satisfaction | ||
) { | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/com/guttery/madii/domain/achievement/application/dto/JoyPlaylistResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@Schema(description = "어제와 오늘의 소확행 플레이리스트 응답") | ||
public record JoyPlaylistResponse( | ||
@Schema(description = "오늘의 소확행 플레이리스트") | ||
DailyJoyPlaylist todayJoyPlayList, | ||
@Schema(description = "어제의 소확행 플레이리스트") | ||
DailyJoyPlaylist yesterdayJoyPlayList | ||
) { | ||
} |
10 changes: 10 additions & 0 deletions
10
...a/com/guttery/madii/domain/achievement/application/dto/MoveAchievementToTodayRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@Schema(description = "실천을 오늘로 이동 요청") | ||
public record MoveAchievementToTodayRequest( | ||
@Schema(description = "실천 ID", example = "1") | ||
Long achievementId | ||
) { | ||
} |
16 changes: 16 additions & 0 deletions
16
...ain/java/com/guttery/madii/domain/achievement/application/dto/RateAchievementRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.guttery.madii.domain.achievement.application.dto; | ||
|
||
import com.guttery.madii.domain.achievement.domain.model.Satisfaction; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotNull; | ||
|
||
@Schema(description = "실천 만족도 수정 요청") | ||
public record RateAchievementRequest( | ||
@NotNull | ||
@Schema(description = "실천 ID", example = "1") | ||
Long achievementId, | ||
@NotNull | ||
@Schema(description = "만족도", example = "SO_SO") | ||
Satisfaction satisfaction | ||
) { | ||
} |
30 changes: 30 additions & 0 deletions
30
...va/com/guttery/madii/domain/achievement/application/service/AchievementServiceHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.guttery.madii.domain.achievement.application.service; | ||
|
||
import com.guttery.madii.common.exception.CustomException; | ||
import com.guttery.madii.common.exception.ErrorDetails; | ||
import com.guttery.madii.domain.achievement.domain.model.Achievement; | ||
import com.guttery.madii.domain.achievement.domain.repository.AchievementRepository; | ||
import com.guttery.madii.domain.user.domain.model.UserPrincipal; | ||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class AchievementServiceHelper { | ||
public static Achievement findExistingAchievement(final AchievementRepository achievementRepository, final Long achievementId) { | ||
return achievementRepository.findById(achievementId) | ||
.orElseThrow(() -> CustomException.of(ErrorDetails.ACHIEVEMENT_NOT_FOUND)); | ||
} | ||
|
||
public static void validateAchievementAchiever(final Achievement achievement, final Long userId) { | ||
if (!achievement.isAchievedBy(userId)) { | ||
throw CustomException.of(ErrorDetails.ACHIEVEMENT_ACHIEVER_NOT_MATCH); | ||
} | ||
} | ||
|
||
public static Achievement findValidAchievement(final AchievementRepository achievementRepository, final Long achievementId, final UserPrincipal userPrincipal) { | ||
final Achievement foundAchievement = findExistingAchievement(achievementRepository, achievementId); | ||
validateAchievementAchiever(foundAchievement, userPrincipal.id()); | ||
|
||
return foundAchievement; | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
...ain/java/com/guttery/madii/domain/achievement/application/service/JoyPlaylistService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package com.guttery.madii.domain.achievement.application.service; | ||
|
||
import com.guttery.madii.domain.achievement.application.dto.AddAchievementRequest; | ||
import com.guttery.madii.domain.achievement.application.dto.JoyPlaylistResponse; | ||
import com.guttery.madii.domain.achievement.application.dto.MoveAchievementToTodayRequest; | ||
import com.guttery.madii.domain.achievement.domain.model.Achievement; | ||
import com.guttery.madii.domain.achievement.domain.model.FinishInfo; | ||
import com.guttery.madii.domain.achievement.domain.repository.AchievementRepository; | ||
import com.guttery.madii.domain.joy.application.service.JoyServiceHelper; | ||
import com.guttery.madii.domain.joy.domain.model.Joy; | ||
import com.guttery.madii.domain.joy.domain.repository.JoyRepository; | ||
import com.guttery.madii.domain.user.application.service.UserServiceHelper; | ||
import com.guttery.madii.domain.user.domain.model.User; | ||
import com.guttery.madii.domain.user.domain.model.UserPrincipal; | ||
import com.guttery.madii.domain.user.domain.repository.UserRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.time.LocalDate; | ||
|
||
@RequiredArgsConstructor | ||
@Slf4j | ||
@Service | ||
public class JoyPlaylistService { | ||
private final AchievementRepository achievementRepository; | ||
private final UserRepository userRepository; | ||
private final JoyRepository joyRepository; | ||
|
||
@Transactional | ||
public void addAchievementInPlaylist(final AddAchievementRequest addAchievementRequest, final UserPrincipal userPrincipal) { | ||
final User user = UserServiceHelper.findExistingUser(userRepository, userPrincipal); | ||
final Joy joy = JoyServiceHelper.findExistingJoy(joyRepository, addAchievementRequest.joyId()); | ||
final Achievement newAchievement = Achievement.create(user, joy, FinishInfo.createNotFinished()); | ||
// TODO: 중복 추가 방지를 위한 로직 필요 -> 테스트 이후에 구현 (복합 unique key 고려 중) | ||
|
||
achievementRepository.save(newAchievement); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public JoyPlaylistResponse getAchievementsInPlaylist(final UserPrincipal userPrincipal) { | ||
final User user = UserServiceHelper.findExistingUser(userRepository, userPrincipal); | ||
|
||
return achievementRepository.getAchievementsInPlaylist(user.getUserId(), LocalDate.now()); | ||
} | ||
|
||
@Transactional | ||
public void deleteAchievementInPlaylist(final Long achievementId, final UserPrincipal userPrincipal) { | ||
final Achievement foundAchievement = AchievementServiceHelper.findValidAchievement(achievementRepository, achievementId, userPrincipal); | ||
|
||
achievementRepository.delete(foundAchievement); | ||
} | ||
|
||
@Transactional | ||
public void moveAchievementInPlaylist(final MoveAchievementToTodayRequest moveAchievementToTodayRequest, final UserPrincipal userPrincipal) { | ||
final Achievement foundAchievement = AchievementServiceHelper.findValidAchievement(achievementRepository, moveAchievementToTodayRequest.achievementId(), userPrincipal); | ||
final Achievement newAchievement = foundAchievement.copyForMove(); | ||
|
||
achievementRepository.delete(foundAchievement); | ||
achievementRepository.save(newAchievement); | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
.../guttery/madii/domain/achievement/application/service/UpdateAchievementStatusService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.guttery.madii.domain.achievement.application.service; | ||
|
||
import com.guttery.madii.domain.achievement.application.dto.CancelAchievementRequest; | ||
import com.guttery.madii.domain.achievement.application.dto.FinishAchievementRequest; | ||
import com.guttery.madii.domain.achievement.application.dto.RateAchievementRequest; | ||
import com.guttery.madii.domain.achievement.domain.model.Achievement; | ||
import com.guttery.madii.domain.achievement.domain.repository.AchievementRepository; | ||
import com.guttery.madii.domain.user.domain.model.UserPrincipal; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@RequiredArgsConstructor | ||
@Slf4j | ||
@Service | ||
public class UpdateAchievementStatusService { | ||
private final AchievementRepository achievementRepository; | ||
|
||
@Transactional | ||
public void finishAchievement(final FinishAchievementRequest finishAchievementRequest, final UserPrincipal userPrincipal) { | ||
final Achievement foundAchievement = AchievementServiceHelper.findValidAchievement(achievementRepository, finishAchievementRequest.achievementId(), userPrincipal); | ||
foundAchievement.finish(finishAchievementRequest.satisfaction()); | ||
|
||
achievementRepository.save(foundAchievement); | ||
} | ||
|
||
@Transactional | ||
public void rateAchievement(final RateAchievementRequest rateAchievementRequest, final UserPrincipal userPrincipal) { | ||
final Achievement foundAchievement = AchievementServiceHelper.findValidAchievement(achievementRepository, rateAchievementRequest.achievementId(), userPrincipal); | ||
|
||
foundAchievement.rate(rateAchievementRequest.satisfaction()); | ||
|
||
achievementRepository.save(foundAchievement); | ||
} | ||
|
||
@Transactional | ||
public void cancelAchievement(final CancelAchievementRequest cancelAchievementRequest, final UserPrincipal userPrincipal) { | ||
final Achievement foundAchievement = AchievementServiceHelper.findValidAchievement(achievementRepository, cancelAchievementRequest.achievementId(), userPrincipal); | ||
foundAchievement.cancel(); | ||
|
||
achievementRepository.save(foundAchievement); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 21 additions & 1 deletion
22
src/main/java/com/guttery/madii/domain/achievement/domain/model/Satisfaction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,25 @@ | ||
package com.guttery.madii.domain.achievement.domain.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
import com.guttery.madii.common.exception.CustomException; | ||
import com.guttery.madii.common.exception.ErrorDetails; | ||
|
||
import java.util.stream.Stream; | ||
|
||
public enum Satisfaction { | ||
BAD, SO_SO, GOOD, GREAT, EXCELLENT | ||
BAD, SO_SO, GOOD, GREAT, EXCELLENT; | ||
|
||
@JsonCreator | ||
public static Satisfaction fromString(final String value) { | ||
return Stream.of(Satisfaction.values()) | ||
.filter(s -> s.name().equalsIgnoreCase(value)) | ||
.findFirst() | ||
.orElseThrow(() -> CustomException.of(ErrorDetails.INVALID_SATISFACTION_ENUM)); | ||
} | ||
|
||
@JsonValue | ||
public String toJson() { | ||
return name(); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...com/guttery/madii/domain/achievement/domain/repository/AchievementQueryDslRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,9 @@ | ||
package com.guttery.madii.domain.achievement.domain.repository; | ||
|
||
import com.guttery.madii.domain.achievement.application.dto.JoyPlaylistResponse; | ||
|
||
import java.time.LocalDate; | ||
|
||
public interface AchievementQueryDslRepository { | ||
JoyPlaylistResponse getAchievementsInPlaylist(Long userId, LocalDate date); | ||
} |
36 changes: 36 additions & 0 deletions
36
...n/java/com/guttery/madii/domain/achievement/infrastructure/AchievementRepositoryImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,50 @@ | ||
package com.guttery.madii.domain.achievement.infrastructure; | ||
|
||
import com.guttery.madii.common.domain.repository.BaseQueryDslRepository; | ||
import com.guttery.madii.domain.achievement.application.dto.DailyJoyPlaylist; | ||
import com.guttery.madii.domain.achievement.application.dto.JoyAchievementInfo; | ||
import com.guttery.madii.domain.achievement.application.dto.JoyPlaylistResponse; | ||
import com.guttery.madii.domain.achievement.domain.repository.AchievementQueryDslRepository; | ||
import com.guttery.madii.domain.achievement.domain.repository.AchievementRepository; | ||
import com.querydsl.jpa.impl.JPAQueryFactory; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
|
||
import static com.guttery.madii.domain.achievement.domain.model.QAchievement.achievement; | ||
import static com.guttery.madii.domain.joy.domain.model.QJoy.joy; | ||
import static com.guttery.madii.domain.user.domain.model.QUser.user; | ||
|
||
|
||
@Repository | ||
public class AchievementRepositoryImpl extends BaseQueryDslRepository<AchievementRepository> implements AchievementQueryDslRepository { | ||
public AchievementRepositoryImpl(JPAQueryFactory queryFactory) { | ||
super(queryFactory); | ||
} | ||
|
||
@Override | ||
public JoyPlaylistResponse getAchievementsInPlaylist(final Long userId, final LocalDate date) { | ||
final List<JoyAchievementInfo> yesterdayAchievementInfos = queryJoyAchievementInfos(userId, date.minusDays(1)); | ||
final List<JoyAchievementInfo> todayAchievementInfos = queryJoyAchievementInfos(userId, date); | ||
|
||
return new JoyPlaylistResponse( | ||
new DailyJoyPlaylist(date, todayAchievementInfos), | ||
new DailyJoyPlaylist(date.minusDays(1), yesterdayAchievementInfos) | ||
); | ||
} | ||
|
||
private List<JoyAchievementInfo> queryJoyAchievementInfos(final Long userId, final LocalDate date) { | ||
final LocalDateTime startOfDay = date.atStartOfDay(); | ||
final LocalDateTime endOfDay = date.atStartOfDay().plusDays(1).minusSeconds(1); | ||
|
||
return select(JoyAchievementInfo.class, joy.joyId, achievement.achievementId, joy.joyIconNum, joy.contents, achievement.finishInfo.isFinished, achievement.finishInfo.satisfaction) | ||
.from(achievement) | ||
.join(achievement.joy, joy) | ||
.join(achievement.achiever, user) | ||
.where(achievement.achiever.userId.eq(userId), achievement.createdAt.between(startOfDay, endOfDay)) | ||
.orderBy(achievement.finishInfo.isFinished.asc(), achievement.createdAt.asc()) | ||
.fetch(); | ||
} | ||
} |
Oops, something went wrong.