diff --git a/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java b/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java index 11d70fee..48717e37 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java @@ -9,6 +9,7 @@ import javax.persistence.*; import java.time.LocalDate; +import java.time.Period; import static javax.persistence.FetchType.LAZY; import static lombok.AccessLevel.PROTECTED; @@ -72,4 +73,13 @@ private void initEndDate() { LocalDate createdDate = getCreatedAt().toLocalDate(); this.endDate = createdDate.minusDays(1).plusWeeks(duration); } + + // 현재가 몇 주차인지 + public long getCurrentWeek() { + LocalDate createdAt = getCreatedAt().toLocalDate(); + LocalDate today = LocalDate.now(); + Period between = Period.between(today, createdAt); + + return between.getDays() / 7 + 1; // 1주차부터 시작 + } } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/insight/Insight.java b/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/insight/Insight.java index af43b88c..46c53dba 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/insight/Insight.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/insight/Insight.java @@ -40,6 +40,9 @@ public class Insight extends BaseTimeEntity { @Embedded private Link link; + @Column(name = "valid", nullable = false) + private boolean valid = false; + @Column(name = "deleted", nullable = false) private boolean deleted = false; @@ -59,13 +62,14 @@ public class Insight extends BaseTimeEntity { @Column(name = "view") private Long view = 0L; - public static Insight of(User writer, ChallengeParticipation challengeParticipation, Drawer drawer, String contents, Link link) { + public static Insight of(User writer, ChallengeParticipation participation, Drawer drawer, String contents, Link link, boolean valid) { Insight insight = new Insight(); insight.writer = writer; - insight.challengeParticipation = challengeParticipation; + insight.challengeParticipation = participation; insight.drawer = drawer; insight.contents = contents; insight.link = link; + insight.valid = valid; return insight; } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/challenge/ChallengeParticipationRepository.java b/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/challenge/ChallengeParticipationRepository.java index ef7ffaa7..52cd8922 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/challenge/ChallengeParticipationRepository.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/challenge/ChallengeParticipationRepository.java @@ -2,6 +2,7 @@ import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; import ccc.keewedomain.persistence.domain.challenge.enums.ChallengeParticipationStatus; +import ccc.keewedomain.persistence.domain.user.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -9,5 +10,6 @@ public interface ChallengeParticipationRepository extends JpaRepository, ChallengeParticipationQueryRepository { - Optional findByChallengerIdAndStatus(Long userId, ChallengeParticipationStatus status); + + Optional findByChallengerAndStatusAndDeletedFalse(User challenger, ChallengeParticipationStatus status); } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/DrawerRepository.java b/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/DrawerRepository.java index 85c01358..cbe526bb 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/DrawerRepository.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/DrawerRepository.java @@ -13,6 +13,6 @@ public interface DrawerRepository extends JpaRepository, DrawerQue List findAllByUserIdAndDeletedFalse(Long userId); default Drawer findByIdOrElseThrow(Long id) { - return findByIdAndDeletedFalse(id).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR442)); + return findByIdAndDeletedFalse(id).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR440)); } } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/InsightQueryRepository.java b/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/InsightQueryRepository.java index 7eb2f4b9..6172643d 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/InsightQueryRepository.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/persistence/repository/insight/InsightQueryRepository.java @@ -1,5 +1,6 @@ package ccc.keewedomain.persistence.repository.insight; +import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; import ccc.keewedomain.persistence.domain.insight.Insight; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -25,4 +26,13 @@ public Insight findByIdWithWriter(Long insightId) { .fetchOne(); } + + public Long countValidForParticipation(ChallengeParticipation participation) { + return queryFactory.select(insight.id.count()) + .from(insight) + .where(insight.challengeParticipation.id.eq(participation.getId()) + .and(insight.deleted.isFalse()) + .and(insight.valid.isTrue())) + .fetchFirst(); + } } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/ChallengeDomainService.java b/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/ChallengeDomainService.java index f6ea7169..c0925b5e 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/ChallengeDomainService.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/ChallengeDomainService.java @@ -32,7 +32,7 @@ public Challenge save(ChallengeCreateDto dto) { public ChallengeParticipation participate(ChallengeParticipateDto dto) { User challenger = userDomainService.getUserByIdOrElseThrow(dto.getChallengerId()); - exitCurrentChallengeIfExist(dto.getChallengerId()); + exitCurrentChallengeIfExist(challenger); Challenge challenge = getByIdOrElseThrow(dto.getChallengeId()); return challenge.participate(challenger, dto.getMyTopic(), dto.getInsightPerWeek(), dto.getDuration()); } @@ -45,15 +45,15 @@ public boolean checkParticipation(Long userId) { return challengeParticipationRepository.existsByChallengerIdAndStatus(userId, CHALLENGING); } - public ChallengeParticipation getCurrentChallengeParticipation(Long userId) { - return findCurrentChallengeParticipation(userId).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432)); + public ChallengeParticipation getCurrentChallengeParticipation(User challenger) { + return findCurrentChallengeParticipation(challenger).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432)); } - private void exitCurrentChallengeIfExist(Long userId) { - findCurrentChallengeParticipation(userId).ifPresent(ChallengeParticipation::cancel); + private void exitCurrentChallengeIfExist(User challenger) { + findCurrentChallengeParticipation(challenger).ifPresent(ChallengeParticipation::cancel); } - public Optional findCurrentChallengeParticipation(Long userId) { - return challengeParticipationRepository.findByChallengerIdAndStatus(userId, CHALLENGING); + public Optional findCurrentChallengeParticipation(User challenger) { + return challengeParticipationRepository.findByChallengerAndStatusAndDeletedFalse(challenger, CHALLENGING); } } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/service/insight/CommentDomainService.java b/keewe-domain/src/main/java/ccc/keewedomain/service/insight/CommentDomainService.java index 61e36289..631b1bcf 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/service/insight/CommentDomainService.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/service/insight/CommentDomainService.java @@ -28,7 +28,7 @@ public Comment create(CommentCreateDto dto) { Optional optParent = findByIdAndInsightId(dto.getParentId(), dto.getInsightId()); optParent.ifPresent(this::validateHasNoParent); - Comment parent = optParent.get(); + Comment parent = optParent.orElse(null); Comment comment = Comment.of(insight, writer, parent, dto.getContent()); return commentRepository.save(comment); diff --git a/keewe-domain/src/main/java/ccc/keewedomain/service/insight/InsightDomainService.java b/keewe-domain/src/main/java/ccc/keewedomain/service/insight/InsightDomainService.java index b3c7431f..44234df4 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/service/insight/InsightDomainService.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/service/insight/InsightDomainService.java @@ -1,11 +1,11 @@ package ccc.keewedomain.service.insight; import ccc.keewecore.consts.KeeweConsts; +import ccc.keewecore.consts.KeeweRtnConsts; +import ccc.keewecore.exception.KeeweException; import ccc.keewedomain.cache.domain.insight.CInsightView; import ccc.keewedomain.cache.repository.insight.CInsightViewRepository; import ccc.keewedomain.domain.insight.ReactionAggregation; -import ccc.keewecore.consts.KeeweRtnConsts; -import ccc.keewecore.exception.KeeweException; import ccc.keewedomain.dto.insight.InsightCreateDto; import ccc.keewedomain.dto.insight.InsightViewIncrementDto; import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; @@ -45,10 +45,14 @@ public class InsightDomainService { public Insight create(InsightCreateDto dto) { User writer = userDomainService.getUserByIdOrElseThrow(dto.getWriterId()); Drawer drawer = drawerDomainService.getDrawerIfOwner(dto.getDrawerId(), writer); - ChallengeParticipation participation = challengeDomainService.findCurrentChallengeParticipation(dto.getWriterId()) - .orElse(null); - - Insight insight = Insight.of(writer, participation, drawer, dto.getContents(), Link.of(dto.getLink())); + ChallengeParticipation participation = null; + boolean valid = false; + if (dto.isParticipate()) { + participation = challengeDomainService.getCurrentChallengeParticipation(writer); + valid = isRecordable(participation); + } + + Insight insight = Insight.of(writer, participation, drawer, dto.getContents(), Link.of(dto.getLink()), valid); insightRepository.save(insight); createReactionAggregations(insight); return insight; @@ -110,4 +114,11 @@ public Insight getById(Long id) { return insightRepository.findById(id).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR445)); } + private boolean isRecordable(ChallengeParticipation participation) { + Long count = insightQueryRepository.countValidForParticipation(participation); + long weeks = participation.getCurrentWeek(); + log.info("[IDS:isRecordable] count={} weeks={}", count, weeks); + return count < weeks * participation.getInsightPerWeek(); + } + } diff --git a/keewe-domain/src/main/resources/ddl/ddl.sql b/keewe-domain/src/main/resources/ddl/ddl.sql index 89491c6b..07db7420 100644 --- a/keewe-domain/src/main/resources/ddl/ddl.sql +++ b/keewe-domain/src/main/resources/ddl/ddl.sql @@ -121,6 +121,7 @@ CREATE TABLE IF NOT EXISTS `insight` contents VARCHAR(300) NOT NULL, url VARCHAR(2000) NOT NULL, deleted BIT NOT NULL, + valid BIT NOT NULL, view BIGINT NOT NULL DEFAULT 0, created_at DATETIME(6) NOT NULL, updated_at DATETIME(6) NOT NULL, @@ -141,7 +142,7 @@ CREATE TABLE IF NOT EXISTS `reaction` created_at DATETIME(6) NOT NULL, updated_at DATETIME(6) NOT NULL, - PRIMARY KEY (reacition_id) + PRIMARY KEY (reaction_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `reaction_aggregation` diff --git a/keewe-domain/src/main/resources/dml/issue-126_dml.sql b/keewe-domain/src/main/resources/dml/issue-126_dml.sql new file mode 100644 index 00000000..8531cd8a --- /dev/null +++ b/keewe-domain/src/main/resources/dml/issue-126_dml.sql @@ -0,0 +1,2 @@ +ALTER TABLE insight + ADD COLUMN valid BIT NOT NULL \ No newline at end of file diff --git a/keewe-domain/src/test/java/ccc/keewedomain/service/insight/CommentDomainServiceTest.java b/keewe-domain/src/test/java/ccc/keewedomain/service/insight/CommentDomainServiceTest.java index af75da1e..4171f8f6 100644 --- a/keewe-domain/src/test/java/ccc/keewedomain/service/insight/CommentDomainServiceTest.java +++ b/keewe-domain/src/test/java/ccc/keewedomain/service/insight/CommentDomainServiceTest.java @@ -49,8 +49,8 @@ public class CommentDomainServiceTest { DatabaseCleaner databaseCleaner; User user = User.from(UserSignUpDto.of("vendorId", VendorType.NAVER, "boseong844@naver.com", null, null));; - Insight insight = Insight.of(user, null, null, "인사이트 내용", Link.of("https://naver.com"));; - Comment comment = Comment.of(insight, user, null, "댓글 내용");; + Insight insight = Insight.of(user, null, null, "인사이트 내용", Link.of("https://naver.com"), false);; + Comment comment = Comment.of(insight, user, null, "댓글 내용"); @BeforeEach void setup() { diff --git a/keewe-domain/src/test/java/ccc/keewedomain/service/insight/InsightDomainServiceTest.java b/keewe-domain/src/test/java/ccc/keewedomain/service/insight/InsightDomainServiceTest.java new file mode 100644 index 00000000..27101a82 --- /dev/null +++ b/keewe-domain/src/test/java/ccc/keewedomain/service/insight/InsightDomainServiceTest.java @@ -0,0 +1,176 @@ +package ccc.keewedomain.service.insight; + +import ccc.keewecore.consts.KeeweRtnConsts; +import ccc.keewecore.exception.KeeweException; +import ccc.keewedomain.KeeweDomainApplication; +import ccc.keewedomain.dto.insight.InsightCreateDto; +import ccc.keewedomain.dto.user.UserSignUpDto; +import ccc.keewedomain.persistence.domain.challenge.Challenge; +import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; +import ccc.keewedomain.persistence.domain.common.Link; +import ccc.keewedomain.persistence.domain.insight.Drawer; +import ccc.keewedomain.persistence.domain.insight.Insight; +import ccc.keewedomain.persistence.domain.user.User; +import ccc.keewedomain.persistence.domain.user.enums.VendorType; +import ccc.keewedomain.persistence.repository.challenge.ChallengeParticipationRepository; +import ccc.keewedomain.persistence.repository.challenge.ChallengeRepository; +import ccc.keewedomain.persistence.repository.insight.DrawerRepository; +import ccc.keewedomain.persistence.repository.insight.InsightQueryRepository; +import ccc.keewedomain.persistence.repository.insight.InsightRepository; +import ccc.keewedomain.persistence.repository.insight.ReactionAggregationRepository; +import ccc.keewedomain.persistence.repository.user.UserRepository; +import ccc.keewedomain.utils.DatabaseCleaner; +import ccc.keeweinfra.KeeweInfraApplication; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +@SpringBootTest(classes = {KeeweDomainApplication.class, KeeweInfraApplication.class}) +@TestPropertySource(properties = {"spring.config.location = classpath:application-domain.yml"}) +@ActiveProfiles("test") +public class InsightDomainServiceTest { + + @Autowired + InsightDomainService insightDomainService; + + @Autowired + UserRepository userRepository; + + @Autowired + InsightRepository insightRepository; + + @Autowired + InsightQueryRepository insightQueryRepository; + + @Autowired + ChallengeRepository challengeRepository; + + @Autowired + ChallengeParticipationRepository challengeParticipationRepository; + + @Autowired + ReactionAggregationRepository reactionAggregationRepository; + + @Autowired + DrawerRepository drawerRepository; + + @Autowired + DatabaseCleaner databaseCleaner; + + User user = User.from(UserSignUpDto.of("vendorId", VendorType.NAVER, "boseong844@naver.com", null, null)); + Insight insight = Insight.of(user, null, null, "인사이트 내용", Link.of("https://naver.com"), false); + Challenge challenge = Challenge.of(user, "챌린지", "개발", "챌린지의 소개입니다."); + ChallengeParticipation challengeParticipation = challenge.participate(user, "참가자 토픽", 2, 3); + + @BeforeEach + void setup() { + userRepository.save(user); + insightRepository.save(insight); + challengeRepository.save(challenge); + } + + @AfterEach + void clean() { + databaseCleaner.execute(); + } + + @Test + @DisplayName("챌린지 기록 없이 인사이트 생성") + void create_without_participate() { + //given + InsightCreateDto dto = InsightCreateDto.of(user.getId(), "인사이트 내용", "https://comic.naver.com", false, null); + //when + Insight insight = insightDomainService.create(dto); + + //then + assertAll( + () -> assertThat(insight.isValid()).isFalse(), + () -> assertThat(insightRepository.findById(insight.getId())).isPresent() + ); + } + + @Test + @DisplayName("챌린지 기록하면서 인사이트 생성") + void create_with_participate() { + //given + InsightCreateDto dto = InsightCreateDto.of(user.getId(), "인사이트 내용", "https://comic.naver.com", true, null); + //when + Insight newInsight = insightDomainService.create(dto); + + //then + assertAll( + () -> assertThat(newInsight.isValid()).isTrue(), + () -> assertThat(insightRepository.findById(newInsight.getId())).isPresent(), + () -> assertThat(insightQueryRepository.countValidForParticipation(newInsight.getChallengeParticipation())) + .isEqualTo(1L) + ); + } + + @Test + @DisplayName("이번 주 목표 기록 완료 시 기록 불가능") + void valid_false_if_completed() { + //given + List dtos = List.of( + InsightCreateDto.of(user.getId(), "인사이트 내용1", "https://comic.naver.com", true, null), + InsightCreateDto.of(user.getId(), "인사이트 내용2", "https://comic.naver.com", true, null), + InsightCreateDto.of(user.getId(), "인사이트 내용3", "https://comic.naver.com", true, null) + ); + + //when + List insights = dtos.stream() + .map(dto -> insightDomainService.create(dto)) + .collect(Collectors.toList()); + + Insight insight1 = insights.get(0); + Insight insight2 = insights.get(1); + Insight insight3 = insights.get(2); + + //then + assertAll( + () -> assertThat(insight1.isValid()).isTrue(), + () -> assertThat(insight2.isValid()).isTrue(), + () -> assertThat(insight3.isValid()).isFalse(), + () -> assertThat(insightQueryRepository.countValidForParticipation(insight3.getChallengeParticipation())) + .isEqualTo(2L) + ); + } + + @Test + @DisplayName("서랍이 존재하지 않으면 실패") + void throw_if_drawer_not_exist() { + //given + InsightCreateDto dto = InsightCreateDto.of(user.getId(), "인사이트 내용", "https://comic.naver.com", true, 9999L); + + //when, then + assertThatThrownBy(() -> insightDomainService.create(dto)) + .isExactlyInstanceOf(KeeweException.class) + .hasMessage(KeeweRtnConsts.ERR440.getDescription()); + } + + @Test + @DisplayName("자신의 서랍이 아니면 실패") + void throw_if_not_owner_of_drawer() { + //given + User other = User.from(UserSignUpDto.of("vendorId222", VendorType.NAVER, "boseong844@naver.com", null, null)); + userRepository.save(other); + Drawer drawer = drawerRepository.save(Drawer.of(other, "서랍")); + InsightCreateDto dto = InsightCreateDto.of(user.getId(), "인사이트 내용", "https://comic.naver.com", true, drawer.getId()); + + //when, then + assertThatThrownBy(() -> insightDomainService.create(dto)) + .isExactlyInstanceOf(KeeweException.class) + .hasMessage(KeeweRtnConsts.ERR444.getDescription()); + } +}