-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
스탬프 추가 기능 구현 #65
스탬프 추가 기능 구현 #65
Changes from 25 commits
8e5634c
4e81f5d
50e338b
0aba065
32caa71
3faacee
9b26fac
ab7ee97
873c22a
db80635
66f10f8
4ef124a
b7b9506
f8b5696
1251c78
8a16b11
21c33ff
4026c19
045fe05
2c14f2c
402d92b
d70aea1
b6a9a36
838a006
8d43251
7ea18cd
bde459f
8d5662a
3d0bf15
3bbc1fb
e136ca5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
=== 새로운 스탬프 추가 | ||
==== 요청 | ||
operation::stamp-controller-test/스탬프_생성을_요청하면_새로운_스탬프를_생성한다[snippets='http-request,request-headers,request-fields'] | ||
==== 응답 | ||
operation::stamp-controller-test/스탬프_생성을_요청하면_새로운_스탬프를_생성한다[snippets='http-response,response-fields'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.backend.blooming.stamp.application; | ||
|
||
import com.backend.blooming.goal.application.exception.NotFoundGoalException; | ||
import com.backend.blooming.goal.domain.Goal; | ||
import com.backend.blooming.goal.infrastructure.repository.GoalRepository; | ||
import com.backend.blooming.stamp.application.dto.CreateStampDto; | ||
import com.backend.blooming.stamp.application.dto.ReadStampDto; | ||
import com.backend.blooming.stamp.application.exception.CreateStampForbiddenException; | ||
import com.backend.blooming.stamp.domain.Stamp; | ||
import com.backend.blooming.stamp.domain.exception.InvalidStampException; | ||
import com.backend.blooming.stamp.infrastructure.repository.StampRepository; | ||
import com.backend.blooming.user.application.exception.NotFoundUserException; | ||
import com.backend.blooming.user.domain.User; | ||
import com.backend.blooming.user.infrastructure.repository.UserRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
|
||
@Service | ||
@Transactional | ||
@RequiredArgsConstructor | ||
public class StampService { | ||
|
||
private final GoalRepository goalRepository; | ||
private final UserRepository userRepository; | ||
private final StampRepository stampRepository; | ||
|
||
public ReadStampDto createStamp(final CreateStampDto createStampDto) { | ||
final Goal goal = getGoal(createStampDto.goalId()); | ||
final User user = getUser(createStampDto.userId()); | ||
validateUserInGoalTeams(goal, user.getId()); | ||
validateExistStamp(user.getId(), createStampDto.day()); | ||
final Stamp stamp = persistStamp(createStampDto, goal, user); | ||
|
||
return ReadStampDto.from(stamp); | ||
} | ||
|
||
private Goal getGoal(final Long goalId) { | ||
return goalRepository.findByIdWithUserAndDeletedIsFalse(goalId) | ||
.orElseThrow(NotFoundGoalException::new); | ||
} | ||
|
||
private User getUser(final Long userId) { | ||
return userRepository.findByIdAndDeletedIsFalse(userId) | ||
.orElseThrow(NotFoundUserException::new); | ||
} | ||
|
||
private void validateUserInGoalTeams(final Goal goal, final Long userId) { | ||
final List<Long> teamUserIds = goal.getTeams() | ||
.getGoalTeams() | ||
.stream() | ||
.map(goalTeam -> goalTeam.getUser().getId()) | ||
.toList(); | ||
if (!teamUserIds.contains(userId)) { | ||
throw new CreateStampForbiddenException(); | ||
} | ||
} | ||
|
||
private void validateExistStamp(final Long userId, final int day) { | ||
final boolean isExistsStamp = stampRepository.existsByUserIdAndDayAndDeletedIsFalse(userId, day); | ||
if (isExistsStamp) { | ||
throw new InvalidStampException.InvalidStampToCreate(); | ||
} | ||
} | ||
|
||
private Stamp persistStamp(final CreateStampDto createStampDto, final Goal goal, final User user) { | ||
final Stamp stamp = Stamp.builder() | ||
.goal(goal) | ||
.user(user) | ||
.day(createStampDto.day()) | ||
.message(createStampDto.message()) | ||
.build(); | ||
|
||
return stampRepository.save(stamp); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.backend.blooming.stamp.application.dto; | ||
|
||
import com.backend.blooming.stamp.presentation.dto.request.CreateStampRequest; | ||
|
||
public record CreateStampDto( | ||
Long goalId, | ||
Long userId, | ||
int day, | ||
String message | ||
) { | ||
|
||
public static CreateStampDto of(final CreateStampRequest request, final Long userId) { | ||
return new CreateStampDto( | ||
request.goalId(), | ||
userId, | ||
request.day(), | ||
request.message() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.backend.blooming.stamp.application.dto; | ||
|
||
import com.backend.blooming.stamp.domain.Stamp; | ||
|
||
public record ReadStampDto( | ||
Long goalId, | ||
int day, | ||
String message | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스탬프 아이디는 굳이 넘겨줄 필요가 없을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추가로 사용자 정보는 넘길 필요가 없는지 궁금합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자 이름은 제가 빼먹었네요...! 추가하도록 하겠습니다. |
||
|
||
public static ReadStampDto from(final Stamp stamp) { | ||
return new ReadStampDto( | ||
stamp.getGoal().getId(), | ||
stamp.getDay(), | ||
stamp.getMessage() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.backend.blooming.stamp.application.exception; | ||
|
||
import com.backend.blooming.exception.BloomingException; | ||
import com.backend.blooming.exception.ExceptionMessage; | ||
|
||
public class CreateStampForbiddenException extends BloomingException { | ||
|
||
public CreateStampForbiddenException() { | ||
super(ExceptionMessage.CREATE_STAMP_FORBIDDEN); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package com.backend.blooming.stamp.domain; | ||
|
||
import com.backend.blooming.common.entity.BaseTimeEntity; | ||
import com.backend.blooming.goal.domain.Goal; | ||
import com.backend.blooming.stamp.domain.exception.InvalidStampException; | ||
import com.backend.blooming.user.domain.User; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.ForeignKey; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.ToString; | ||
|
||
import java.time.LocalDate; | ||
import java.time.temporal.ChronoUnit; | ||
|
||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
@EqualsAndHashCode(of = "id", callSuper = false) | ||
@ToString(exclude = {"goal", "user"}) | ||
public class Stamp extends BaseTimeEntity { | ||
|
||
private static final int STAMP_DAY_MINIMUM = 1; | ||
private static final int STAMP_MESSAGE_MAXIMUM = 30; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 클래스에서는 더 이상 사용하지 않으니 없어져도 괜찮겠네요! |
||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "goal_id", foreignKey = @ForeignKey(name = "fk_stamp_goal"), nullable = false) | ||
private Goal goal; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_stamp_user"), nullable = false) | ||
private User user; | ||
|
||
@Column(name = "stamp_day", nullable = false) | ||
private int day; | ||
|
||
@Column(columnDefinition = "text", nullable = false, length = STAMP_MESSAGE_MAXIMUM) | ||
private String message; | ||
|
||
@Column(name = "is_deleted", nullable = false) | ||
private boolean deleted = false; | ||
|
||
@Builder | ||
private Stamp( | ||
final Goal goal, | ||
final User user, | ||
final int day, | ||
final String message | ||
) { | ||
this.goal = goal; | ||
this.user = user; | ||
this.day = validateDay(goal, day); | ||
this.message = validateMessage(message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋네요! day와 message 모두 vo 반영해보겠습니다! |
||
} | ||
|
||
private int validateDay(final Goal goal, final int day) { | ||
final long nowStampDay = ChronoUnit.DAYS.between(goal.getGoalTerm().getStartDate(), LocalDate.now()) + 1; | ||
|
||
if (day > nowStampDay) { | ||
throw new InvalidStampException.InvalidStampDayFuture(); | ||
} | ||
if (day > goal.getGoalTerm().getDays()) { | ||
throw new InvalidStampException.InvalidStampDay(); | ||
} | ||
if (goal.getGoalTerm().getStartDate().isAfter(LocalDate.now())) { | ||
throw new InvalidStampException.InvalidStampDay(); | ||
} | ||
|
||
return day; | ||
} | ||
|
||
private String validateMessage(final String message) { | ||
if (message == null || message.isEmpty()) { | ||
throw new InvalidStampException.InvalidStampMessage(); | ||
} | ||
if (message.length() > STAMP_MESSAGE_MAXIMUM) { | ||
throw new InvalidStampException.InvalidStampMessage(); | ||
} | ||
|
||
return message; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.backend.blooming.stamp.domain.exception; | ||
|
||
import com.backend.blooming.exception.BloomingException; | ||
import com.backend.blooming.exception.ExceptionMessage; | ||
|
||
public class InvalidStampException extends BloomingException { | ||
|
||
public InvalidStampException(final ExceptionMessage exceptionMessage) { | ||
super(exceptionMessage); | ||
} | ||
|
||
public static class InvalidStampDay extends InvalidStampException { | ||
|
||
public InvalidStampDay() { | ||
super(ExceptionMessage.INVALID_STAMP_DAY); | ||
} | ||
} | ||
|
||
public static class InvalidStampDayFuture extends InvalidStampException { | ||
|
||
public InvalidStampDayFuture() { | ||
super(ExceptionMessage.INVALID_STAMP_DAY_FUTURE); | ||
} | ||
} | ||
|
||
public static class InvalidStampToCreate extends InvalidStampException { | ||
|
||
public InvalidStampToCreate() { | ||
super(ExceptionMessage.INVALID_STAMP_TO_CREATE); | ||
} | ||
} | ||
|
||
public static class InvalidStampMessage extends InvalidStampException { | ||
|
||
public InvalidStampMessage() { | ||
super(ExceptionMessage.INVALID_STAMP_MESSAGE); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.backend.blooming.stamp.infrastructure.repository; | ||
|
||
import com.backend.blooming.stamp.domain.Stamp; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
|
||
public interface StampRepository extends JpaRepository<Stamp, Long> { | ||
|
||
@Query(""" | ||
SELECT EXISTS( | ||
SELECT 1 | ||
FROM Stamp s | ||
WHERE (s.user.id = :userId AND s.day = :day) AND s.deleted = FALSE | ||
) as exist | ||
""") | ||
boolean existsByUserIdAndDayAndDeletedIsFalse(final Long userId, final int day); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍