diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportQueryService.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportQueryService.java index c6c7db3..b1bc226 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportQueryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportQueryService.java @@ -8,13 +8,12 @@ import com.devkor.ifive.nadab.domain.user.core.entity.User; import com.devkor.ifive.nadab.domain.user.core.repository.UserRepository; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; +import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneId; @Service @RequiredArgsConstructor @@ -29,21 +28,12 @@ public DailyReportResponse getDailyReport(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다. id: " + id)); - LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); + TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange(); - OffsetDateTime startOfToday = - today.atStartOfDay(ZoneId.of("Asia/Seoul")) - .toOffsetDateTime(); - - OffsetDateTime startOfTomorrow = - today.plusDays(1) - .atStartOfDay(ZoneId.of("Asia/Seoul")) - .toOffsetDateTime(); - - AnswerEntry entry = answerEntryRepository.findByUserAndCreatedAtBetween(user, startOfToday, startOfTomorrow) + AnswerEntry entry = answerEntryRepository.findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow()) .orElseThrow(() -> new NotFoundException("오늘의 답변 항목을 찾을 수 없습니다. userId: " + id)); - DailyReport report = dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, startOfToday, startOfTomorrow) + DailyReport report = dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow()) .orElseThrow(() -> new NotFoundException("오늘의 리포트를 찾을 수 없습니다. userId: " + id)); return new DailyReportResponse( diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportService.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportService.java index bd20f63..caa3a70 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/application/DailyReportService.java @@ -8,25 +8,33 @@ import com.devkor.ifive.nadab.domain.dailyreport.core.entity.AnswerEntry; import com.devkor.ifive.nadab.domain.dailyreport.infra.DailyReportLlmClient; import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion; +import com.devkor.ifive.nadab.domain.question.core.entity.UserDailyQuestion; import com.devkor.ifive.nadab.domain.question.core.repository.DailyQuestionRepository; +import com.devkor.ifive.nadab.domain.question.core.repository.UserDailyQuestionRepository; import com.devkor.ifive.nadab.domain.user.core.entity.User; import com.devkor.ifive.nadab.domain.user.core.repository.UserRepository; +import com.devkor.ifive.nadab.global.exception.BadRequestException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.LocalDate; + @Service @RequiredArgsConstructor public class DailyReportService { private final UserRepository userRepository; private final DailyQuestionRepository dailyQuestionRepository; + private final UserDailyQuestionRepository userDailyQuestionRepository; private final DailyReportTxService dailyReportTxService; private final DailyReportLlmClient dailyReportLlmClient; + public CreateDailyReportResponse generateDailyReport(Long userId, DailyReportRequest request) { User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다. id: " + userId)); @@ -34,6 +42,13 @@ public CreateDailyReportResponse generateDailyReport(Long userId, DailyReportReq DailyQuestion question = dailyQuestionRepository.findById(request.questionId()) .orElseThrow(() -> new NotFoundException("질문을 찾을 수 없습니다. id: " + request.questionId())); + LocalDate today = TodayDateTimeProvider.getTodayDate(); + UserDailyQuestion udq = userDailyQuestionRepository.findByUserIdAndDate(userId, today) + .orElseThrow(() -> new BadRequestException("오늘의 질문이 사용자에게 할당되지 않았습니다. date: " + today)); + if (!udq.getDailyQuestion().getId().equals(request.questionId())) { + throw new BadRequestException("요청의 질문이 사용자에게 할당된 오늘의 질문과 일치하지 않습니다. 할당된 questionId: " + udq.getDailyQuestion().getId()); + } + PrepareDailyResultDto prep = dailyReportTxService.prepareDaily(user, question, request.answer()); AnswerEntry answerEntry = prep.entry(); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/AnswerEntry.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/AnswerEntry.java index 34843e0..d408ad4 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/AnswerEntry.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/AnswerEntry.java @@ -2,17 +2,24 @@ import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion; import com.devkor.ifive.nadab.domain.user.core.entity.User; -import com.devkor.ifive.nadab.global.shared.entity.SoftDeletableEntity; +import com.devkor.ifive.nadab.global.shared.entity.AuditableEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; + @Entity -@Table(name = "answer_entries") +@Table( + name = "answer_entries", + uniqueConstraints = { + @UniqueConstraint(name = "uq_answer_entries_user_id_date", columnNames = {"user_id", "date"}) + } +) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AnswerEntry extends SoftDeletableEntity { +public class AnswerEntry extends AuditableEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,15 +36,20 @@ public class AnswerEntry extends SoftDeletableEntity { @Column(name = "content", length = 500, nullable = false) private String content; - public static AnswerEntry create(User user, DailyQuestion question, String content) { + @Column(name = "date", nullable = false) + private LocalDate date; + + public static AnswerEntry create(User user, DailyQuestion question, String content, LocalDate date) { AnswerEntry e = new AnswerEntry(); e.user = user; e.question = question; e.content = content; + e.date = date; return e; } public void updateContent(String content) { this.content = content; + onUpdate(); } } \ No newline at end of file diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/DailyReport.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/DailyReport.java index f912b69..4a60b8d 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/DailyReport.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/entity/DailyReport.java @@ -6,10 +6,16 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.time.OffsetDateTime; @Entity -@Table(name = "daily_reports") +@Table( + name = "daily_reports", + uniqueConstraints = { + @UniqueConstraint(name = "uq_daily_reports_answer_entry_id_date", columnNames = {"answer_entry_id", "date"}) + } +) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class DailyReport extends CreatableEntity { @@ -36,16 +42,20 @@ public class DailyReport extends CreatableEntity { @Column(name = "analyzed_at") private OffsetDateTime analyzedAt; - public static DailyReport create(AnswerEntry answerEntry, Emotion emotion, String content, DailyReportStatus status) { + @Column(name = "date", nullable = false) + private LocalDate date; + + public static DailyReport create(AnswerEntry answerEntry, Emotion emotion, String content, LocalDate date,DailyReportStatus status) { DailyReport dr = new DailyReport(); dr.answerEntry = answerEntry; dr.emotion = emotion; dr.content = content; + dr.date = date; dr.status = status; return dr; } - public static DailyReport createPending(AnswerEntry answerEntry) { - return create(answerEntry, null, null, DailyReportStatus.PENDING); + public static DailyReport createPending(AnswerEntry answerEntry, LocalDate date) { + return create(answerEntry, null, null, date,DailyReportStatus.PENDING); } } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/AnswerEntryRepository.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/AnswerEntryRepository.java index 4a44b9a..f29123c 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/AnswerEntryRepository.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/AnswerEntryRepository.java @@ -16,7 +16,6 @@ public interface AnswerEntryRepository extends JpaRepository from AnswerEntry a where a.user.id = :userId and a.question.id = :questionId - and a.deletedAt is null """) boolean existsActiveAnswer( @Param("userId") Long userId, @@ -24,4 +23,6 @@ boolean existsActiveAnswer( ); Optional findByUserAndCreatedAtBetween(User user, OffsetDateTime start, OffsetDateTime end); + + boolean existsByUserAndCreatedAtBetween(User user, OffsetDateTime start, OffsetDateTime end); } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java index f608093..27d8675 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java @@ -4,14 +4,15 @@ import com.devkor.ifive.nadab.domain.dailyreport.core.repository.AnswerEntryRepository; import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion; import com.devkor.ifive.nadab.domain.user.core.entity.User; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; +import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneId; + @Service @RequiredArgsConstructor @@ -19,22 +20,16 @@ public class AnswerEntryService { private final AnswerEntryRepository answerEntryRepository; + @Transactional(propagation = Propagation.REQUIRES_NEW) public AnswerEntry getOrCreateTodayAnswerEntry(User user, DailyQuestion dq, String answerText) { - LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); - - OffsetDateTime startOfToday = - today.atStartOfDay(ZoneId.of("Asia/Seoul")) - .toOffsetDateTime(); + TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange(); - OffsetDateTime startOfTomorrow = - today.plusDays(1) - .atStartOfDay(ZoneId.of("Asia/Seoul")) - .toOffsetDateTime(); + LocalDate today = TodayDateTimeProvider.getTodayDate(); - return answerEntryRepository.findByUserAndCreatedAtBetween(user, startOfToday, startOfTomorrow) - .orElseGet(() -> answerEntryRepository.save(AnswerEntry.create(user, dq, answerText))); + return answerEntryRepository.findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow()) + .orElseGet(() -> answerEntryRepository.save(AnswerEntry.create(user, dq, answerText, today))); } } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java index bd3c876..7ae6478 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java @@ -5,14 +5,15 @@ import com.devkor.ifive.nadab.domain.dailyreport.core.entity.DailyReportStatus; import com.devkor.ifive.nadab.domain.dailyreport.core.repository.DailyReportRepository; import com.devkor.ifive.nadab.global.exception.ConflictException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; +import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneId; + @Service @RequiredArgsConstructor @@ -20,22 +21,16 @@ public class PendingDailyReportService { private final DailyReportRepository dailyReportRepository; + @Transactional(propagation = Propagation.REQUIRES_NEW) public DailyReport getOrCreatePendingDailyReport(AnswerEntry entry) { - LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); - - OffsetDateTime startOfToday = - today.atStartOfDay(ZoneId.of("Asia/Seoul")) - .toOffsetDateTime(); + TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange(); - OffsetDateTime startOfTomorrow = - today.plusDays(1) - .atStartOfDay(ZoneId.of("Asia/Seoul")) - .toOffsetDateTime(); + LocalDate today = TodayDateTimeProvider.getTodayDate(); - DailyReport report = dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, startOfToday, startOfTomorrow) - .orElseGet(() -> dailyReportRepository.save(DailyReport.createPending(entry))); + DailyReport report = dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow()) + .orElseGet(() -> dailyReportRepository.save(DailyReport.createPending(entry, today))); if (report.getStatus() == DailyReportStatus.COMPLETED) { throw new ConflictException("이미 작성된 일간 리포트가 존재합니다. reportId: " + report.getId()); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/question/application/QuestionCommandService.java b/src/main/java/com/devkor/ifive/nadab/domain/question/application/QuestionCommandService.java index e692015..cad6041 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/question/application/QuestionCommandService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/question/application/QuestionCommandService.java @@ -15,6 +15,8 @@ import com.devkor.ifive.nadab.global.exception.BadRequestException; import com.devkor.ifive.nadab.global.exception.ConflictException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; +import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -28,8 +30,6 @@ @Transactional public class QuestionCommandService { - private static final ZoneId KST = ZoneId.of("Asia/Seoul"); - private final UserRepository userRepository; private final UserDailyQuestionRepository userDailyQuestionRepository; private final UserInterestRepository userInterestRepository; @@ -41,11 +41,11 @@ public class QuestionCommandService { private final DailyQuestionSelector dailyQuestionSelector; public DailyQuestionResponse getOrCreateTodayQuestion(Long userId) { - LocalDate today = LocalDate.now(KST); User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다. id: " + userId)); + LocalDate today = TodayDateTimeProvider.getTodayDate(); UserDailyQuestion udq = userDailyQuestionRepository.findByUserIdAndDate(userId, today) .orElseGet(() -> this.createTodayQuestion(userId, today)); @@ -104,7 +104,9 @@ public UserDailyQuestion createTodayQuestion(Long userId, LocalDate todayKst) { * - 이미 답변한 질문은 제외 */ public DailyQuestionResponse rerollTodayQuestion(Long userId) { - LocalDate today = LocalDate.now(KST); + LocalDate today = TodayDateTimeProvider.getTodayDate(); + + TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange(); UserDailyQuestion udq = userDailyQuestionRepository.findByUserIdAndDate(userId, today) .orElseThrow(() -> new ConflictException("오늘의 첫 질문이 아직 생성되지 않았습니다.")); @@ -115,6 +117,11 @@ public DailyQuestionResponse rerollTodayQuestion(Long userId) { User user = udq.getUser(); + boolean alreadyAnswered = answerEntryRepository.existsByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow()); + if (alreadyAnswered) { + throw new BadRequestException("오늘의 질문에 이미 답변을 작성한 후에는 질문을 새로 받을 수 없습니다."); + } + Long userInterestId = userInterestRepository.findInterestIdByUserId(userId) .orElseThrow(() -> new NotFoundException("유저 관심 주제가 없습니다. id: " + userId)); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/question/core/repository/DailyQuestionRepository.java b/src/main/java/com/devkor/ifive/nadab/domain/question/core/repository/DailyQuestionRepository.java index 0cb8a71..a27ab96 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/question/core/repository/DailyQuestionRepository.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/question/core/repository/DailyQuestionRepository.java @@ -21,12 +21,12 @@ public interface DailyQuestionRepository extends JpaRepository findRandomByInterestExcludingAnswered( where q.interest.id = :interestId and q.id <> :excludeId and (:levelOnly is null or q.questionLevel = :levelOnly) + and q.deletedAt is null and not exists ( select 1 from AnswerEntry a where a.user.id = :userId and a.question.id = q.id - and a.deletedAt is null ) order by function('random') """) diff --git a/src/main/java/com/devkor/ifive/nadab/global/shared/entity/AuditableEntity.java b/src/main/java/com/devkor/ifive/nadab/global/shared/entity/AuditableEntity.java index f99ac27..7c996dc 100644 --- a/src/main/java/com/devkor/ifive/nadab/global/shared/entity/AuditableEntity.java +++ b/src/main/java/com/devkor/ifive/nadab/global/shared/entity/AuditableEntity.java @@ -14,7 +14,7 @@ public abstract class AuditableEntity extends CreatableEntity { @Column(name = "updated_at") protected OffsetDateTime updatedAt; - // Timestamped의 onCreate를 오버라이드 + // CreatableEntity의 onCreate를 오버라이드 @Override protected void onCreate() { super.onCreate(); diff --git a/src/main/java/com/devkor/ifive/nadab/global/shared/util/DateTimeConverter.java b/src/main/java/com/devkor/ifive/nadab/global/shared/util/DateTimeConverter.java index c51ae77..6429f64 100644 --- a/src/main/java/com/devkor/ifive/nadab/global/shared/util/DateTimeConverter.java +++ b/src/main/java/com/devkor/ifive/nadab/global/shared/util/DateTimeConverter.java @@ -5,6 +5,9 @@ import java.time.OffsetDateTime; import java.time.ZoneId; +/** + * OffsetDateTime을 서울 시간대의 LocalDate 또는 LocalDateTime으로 변환하는 유틸리티 클래스 + */ public class DateTimeConverter { private static final ZoneId SEOUL = ZoneId.of("Asia/Seoul"); diff --git a/src/main/java/com/devkor/ifive/nadab/global/shared/util/TodayDateTimeProvider.java b/src/main/java/com/devkor/ifive/nadab/global/shared/util/TodayDateTimeProvider.java new file mode 100644 index 0000000..ad8ac7d --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/global/shared/util/TodayDateTimeProvider.java @@ -0,0 +1,37 @@ +package com.devkor.ifive.nadab.global.shared.util; + +import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +/** + * 오늘 날짜, 오늘 날짜의 시작과 내일 날짜의 시작 등을 제공하는 유틸리티 클래스 + * repository 등에서 오늘 날짜 범위 조회 시 사용 + */ +public class TodayDateTimeProvider { + + private static final ZoneId SEOUL = ZoneId.of("Asia/Seoul"); + + private TodayDateTimeProvider() { + } + + public static TodayDateTimeRangeDto getRange() { + LocalDate today = LocalDate.now(SEOUL); + + OffsetDateTime startOfToday = + today.atStartOfDay(SEOUL).toOffsetDateTime(); + + OffsetDateTime startOfTomorrow = + today.plusDays(1) + .atStartOfDay(SEOUL) + .toOffsetDateTime(); + + return new TodayDateTimeRangeDto(startOfToday, startOfTomorrow); + } + + public static LocalDate getTodayDate() { + return LocalDate.now(SEOUL); + } +} diff --git a/src/main/java/com/devkor/ifive/nadab/global/shared/util/dto/TodayDateTimeRangeDto.java b/src/main/java/com/devkor/ifive/nadab/global/shared/util/dto/TodayDateTimeRangeDto.java new file mode 100644 index 0000000..4fd1291 --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/global/shared/util/dto/TodayDateTimeRangeDto.java @@ -0,0 +1,9 @@ +package com.devkor.ifive.nadab.global.shared.util.dto; + +import java.time.OffsetDateTime; + +public record TodayDateTimeRangeDto( + OffsetDateTime startOfToday, + OffsetDateTime startOfTomorrow +) { +} diff --git a/src/main/resources/db/migration/V20251229_2018__IS_drop_deleted_at_on_answer_entries.sql b/src/main/resources/db/migration/V20251229_2018__IS_drop_deleted_at_on_answer_entries.sql new file mode 100644 index 0000000..f8b0701 --- /dev/null +++ b/src/main/resources/db/migration/V20251229_2018__IS_drop_deleted_at_on_answer_entries.sql @@ -0,0 +1,5 @@ +-- 1. 이미 soft delete 된 데이터 영구 삭제 +DELETE FROM answer_entries WHERE deleted_at IS NOT NULL; + +-- 2. 컬럼 삭제 +ALTER TABLE answer_entries DROP COLUMN deleted_at; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20251230_1409__IS_add_date_column_and_unique_constraint_to_daily_reports.sql b/src/main/resources/db/migration/V20251230_1409__IS_add_date_column_and_unique_constraint_to_daily_reports.sql new file mode 100644 index 0000000..0528de9 --- /dev/null +++ b/src/main/resources/db/migration/V20251230_1409__IS_add_date_column_and_unique_constraint_to_daily_reports.sql @@ -0,0 +1,14 @@ +-- 1. date 컬럼을 먼저 Nullable 상태로 추가 +ALTER TABLE daily_reports ADD COLUMN date DATE; + +-- 2. 기존 created_at 값을 기준으로 date 데이터 채우기 (Backfill) +UPDATE daily_reports +SET date = created_at::DATE; + +-- 3. 데이터가 채워졌으므로 NOT NULL 제약 조건 적용 +ALTER TABLE daily_reports + ALTER COLUMN date SET NOT NULL; + +-- 4. (answer_entry_id, date) 유니크 제약 조건 추가 +ALTER TABLE daily_reports + ADD CONSTRAINT uq_daily_reports_answer_entry_id_date UNIQUE (answer_entry_id, date); \ No newline at end of file diff --git a/src/main/resources/db/migration/V20251230_1439__IS_add_date_column_and_unique_constraint_to_answer_entries.sql b/src/main/resources/db/migration/V20251230_1439__IS_add_date_column_and_unique_constraint_to_answer_entries.sql new file mode 100644 index 0000000..a195bea --- /dev/null +++ b/src/main/resources/db/migration/V20251230_1439__IS_add_date_column_and_unique_constraint_to_answer_entries.sql @@ -0,0 +1,15 @@ +-- 1. date 컬럼 추가 (먼저 Nullable로 생성) +ALTER TABLE answer_entries ADD COLUMN date DATE; + +-- 2. 기존 created_at 값을 기준으로 date 데이터 채우기 (Backfill) +UPDATE answer_entries +SET date = created_at::DATE; + +-- 3. 데이터가 채워졌으므로 NOT NULL 제약 조건 적용 +ALTER TABLE answer_entries + ALTER COLUMN date SET NOT NULL; + +-- 4. (user_id, date) 유니크 제약 조건 추가 +-- 한 유저는 하루에 하나의 답변만 작성할 수 있다는 의미 +ALTER TABLE answer_entries + ADD CONSTRAINT uq_answer_entries_user_id_date UNIQUE (user_id, date); \ No newline at end of file