Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ public CreateDailyReportResponse generateDailyReport(Long userId, DailyReportReq
throw new BadRequestException(ErrorCode.DAILY_QUESTION_MISMATCH);
}

PrepareDailyResultDto prep = dailyReportTxService.prepareDaily(user, question, request.answer(), isDayPassed, request.objectKey());
PrepareDailyResultDto prep = dailyReportTxService.prepareDaily(
user,
question,
udq,
request.answer(),
isDayPassed,
request.objectKey()
);

AnswerEntry answerEntry = prep.entry();
ModelCandidate modelCandidate = dailyReportModelSelector.select();
Expand All @@ -104,7 +111,7 @@ public CreateDailyReportResponse generateDailyReport(Long userId, DailyReportReq
AiDailyReportResultDto dto;
try {
LlmGenerationResult<AiDailyReportResultDto> generationResult =
dailyReportLlmClient.generate(question.getQuestionText(), answerEntry, modelCandidate);
dailyReportLlmClient.generate(prep.questionText(), answerEntry, modelCandidate);
dto = generationResult.content();
LlmTokenUsage tokenUsage = generationResult.tokenUsage();
reportGenerationLogRecorder.recordTokenUsage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import com.devkor.ifive.nadab.domain.dailyreport.core.service.AnswerEntryService;
import com.devkor.ifive.nadab.domain.dailyreport.core.service.PendingDailyReportService;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestionRevision;
import com.devkor.ifive.nadab.domain.question.core.entity.UserDailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.service.DailyQuestionExposureService;
import com.devkor.ifive.nadab.domain.user.core.entity.Interest;
import com.devkor.ifive.nadab.domain.user.core.entity.InterestCode;
import com.devkor.ifive.nadab.domain.user.core.entity.User;
Expand Down Expand Up @@ -45,20 +48,42 @@ public class DailyReportTxService {

private final AnswerEntryService answerEntryService;
private final PendingDailyReportService pendingDailyReportService;
private final DailyQuestionExposureService dailyQuestionExposureService;


private static final long DAILY_REPORT_REWARD = 10L;

protected PrepareDailyResultDto prepareDaily(User user, DailyQuestion dq, String answerText, boolean isDayPassed,
@Nullable String imageKey) {
protected PrepareDailyResultDto prepareDaily(
User user,
DailyQuestion dq,
UserDailyQuestion assignment,
String answerText,
boolean isDayPassed,
@Nullable String imageKey
) {

DailyQuestionRevision questionRevision = dailyQuestionExposureService
.recordAnswer(assignment)
.orElse(null);

// AnswerEntry 생성 또는 조회 (별도의 트랜잭션)
AnswerEntry entry = answerEntryService.getOrCreateTodayAnswerEntry(user, dq, answerText, isDayPassed, imageKey);
AnswerEntry entry = answerEntryService.getOrCreateTodayAnswerEntry(
user,
dq,
questionRevision,
answerText,
isDayPassed,
imageKey
);

// DailyReport PENDING 생성 또는 조회 (별도의 트랜잭션)
DailyReport report = pendingDailyReportService.getOrCreatePendingDailyReport(entry, isDayPassed);

return new PrepareDailyResultDto(entry, report.getId(), user.getId());
String questionText = entry.getQuestionRevision() != null
? entry.getQuestionRevision().getQuestionText()
: dq.getQuestionText();

return new PrepareDailyResultDto(entry, report.getId(), user.getId(), questionText);
}

protected ConfirmDailyAndRewardDto confirmDailyAndReward(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public record PrepareDailyResultDto(
AnswerEntry entry,
Long reportId,
Long userId
Long userId,
String questionText
) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devkor.ifive.nadab.domain.dailyreport.core.entity;

import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestionRevision;
import com.devkor.ifive.nadab.domain.user.core.entity.User;
import com.devkor.ifive.nadab.global.shared.entity.AuditableEntity;
import jakarta.persistence.*;
Expand Down Expand Up @@ -34,6 +35,10 @@ public class AnswerEntry extends AuditableEntity {
@JoinColumn(name = "question_id", nullable = false)
private DailyQuestion question;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "question_revision_id")
private DailyQuestionRevision questionRevision;

@Column(name = "content", length = 500, nullable = false)
private String content;

Expand All @@ -45,9 +50,15 @@ public class AnswerEntry extends AuditableEntity {

public static AnswerEntry create(User user, DailyQuestion question, String content, LocalDate date,
@Nullable String imageKey) {
return create(user, question, null, content, date, imageKey);
}

public static AnswerEntry create(User user, DailyQuestion question, @Nullable DailyQuestionRevision questionRevision,
String content, LocalDate date, @Nullable String imageKey) {
AnswerEntry e = new AnswerEntry();
e.user = user;
e.question = question;
e.questionRevision = questionRevision;
e.content = content;
e.date = date;
e.imageKey = imageKey;
Expand All @@ -64,4 +75,4 @@ public void updateImageKey(String imageKey) {
this.imageKey = imageKey;
onUpdate();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.devkor.ifive.nadab.domain.dailyreport.core.entity.AnswerEntry;
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.question.core.entity.DailyQuestionRevision;
import com.devkor.ifive.nadab.domain.user.core.entity.User;
import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider;
import lombok.RequiredArgsConstructor;
Expand All @@ -22,8 +23,14 @@ public class AnswerEntryService {


@Transactional
public AnswerEntry getOrCreateTodayAnswerEntry(User user, DailyQuestion dq, String answerText, boolean isDayPassed,
@Nullable String imageKey) {
public AnswerEntry getOrCreateTodayAnswerEntry(
User user,
DailyQuestion dq,
@Nullable DailyQuestionRevision questionRevision,
String answerText,
boolean isDayPassed,
@Nullable String imageKey
) {

LocalDate targetDate =
isDayPassed ? TodayDateTimeProvider.getTodayDate().minusDays(1) : TodayDateTimeProvider.getTodayDate();
Expand All @@ -35,7 +42,14 @@ public AnswerEntry getOrCreateTodayAnswerEntry(User user, DailyQuestion dq, Stri
})
.orElseGet(() -> {
try {
return answerEntryRepository.save(AnswerEntry.create(user, dq, answerText, targetDate, imageKey));
return answerEntryRepository.save(AnswerEntry.create(
user,
dq,
questionRevision,
answerText,
targetDate,
imageKey
));
} catch (DataIntegrityViolationException e) {
// 동시 요청에서 이미 누가 만들었을 수 있음 -> 재조회로 멱등 처리
return answerEntryRepository.findByUserAndDate(user, targetDate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.devkor.ifive.nadab.domain.question.application.helper.DailyQuestionSelector;
import com.devkor.ifive.nadab.domain.question.application.helper.QuestionLevelPolicy;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestionRevision;
import com.devkor.ifive.nadab.domain.question.core.entity.UserDailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.repository.UserDailyQuestionRepository;
import com.devkor.ifive.nadab.domain.question.core.service.DailyQuestionExposureService;
import com.devkor.ifive.nadab.domain.dailyreport.core.repository.AnswerEntryRepository;
import com.devkor.ifive.nadab.domain.user.core.entity.User;
import com.devkor.ifive.nadab.domain.user.core.repository.UserInterestRepository;
Expand Down Expand Up @@ -33,6 +35,7 @@ public class QuestionCommandService {

private final QuestionLevelPolicy questionLevelPolicy;
private final DailyQuestionSelector dailyQuestionSelector;
private final DailyQuestionExposureService dailyQuestionExposureService;

public DailyQuestionResponse getOrCreateTodayQuestion(Long userId) {

Expand All @@ -44,16 +47,21 @@ public DailyQuestionResponse getOrCreateTodayQuestion(Long userId) {
.orElseGet(() -> this.createTodayQuestion(userId, today));

DailyQuestion question = udq.getDailyQuestion();
DailyQuestionRevision revision = dailyQuestionExposureService
.findLatestRevision(udq)
.orElse(null);

boolean answered = answerEntryRepository.existsActiveAnswer(userId, question.getId());

return new DailyQuestionResponse(
question.getId(),
question.getInterest().getCode().toString(),
question.getQuestionText(),
question.getEmpathyGuide(),
question.getHintGuide(),
question.getLeadingQuestionGuide(),
revision != null
? revision.getInterest().getCode().toString()
: question.getInterest().getCode().toString(),
revision != null ? revision.getQuestionText() : question.getQuestionText(),
revision != null ? revision.getEmpathyGuide() : question.getEmpathyGuide(),
revision != null ? revision.getHintGuide() : question.getHintGuide(),
revision != null ? revision.getLeadingQuestionGuide() : question.getLeadingQuestionGuide(),
answered,
udq.isRerollUsed()
);
Expand All @@ -68,25 +76,28 @@ public DailyQuestionResponse getOrCreateTodayQuestion(Long userId) {
public UserDailyQuestion createTodayQuestion(Long userId, LocalDate todayKst) {
// 동시성: 여러 요청이 동시에 들어오면 UNIQUE(user_id, date)로 한 번만 성공해야 함
// -> insert 시도 후 unique 위반이면 다시 조회해서 반환
try {
User user = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
User user = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));

Long userInterestId = userInterestRepository.findInterestIdByUserId(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_INTEREST_NOT_FOUND));
Long userInterestId = userInterestRepository.findInterestIdByUserId(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_INTEREST_NOT_FOUND));

Integer levelOnly = questionLevelPolicy.levelOnlyFor(user, OffsetDateTime.now());
Integer levelOnly = questionLevelPolicy.levelOnlyFor(user, OffsetDateTime.now());

DailyQuestion picked = dailyQuestionSelector.pickFirst(user.getId(), userInterestId, levelOnly);
DailyQuestion picked = dailyQuestionSelector.pickFirst(user.getId(), userInterestId, levelOnly);

UserDailyQuestion saved;
try {
UserDailyQuestion udq = UserDailyQuestion.create(user, todayKst, picked);
return userDailyQuestionRepository.save(udq);

saved = userDailyQuestionRepository.save(udq);
} catch (DataIntegrityViolationException e) {
// 이미 생성됨(경합 상황)
return userDailyQuestionRepository.findByUserIdAndDate(userId, todayKst)
.orElseThrow(() -> e);
}

dailyQuestionExposureService.recordInitialAssignment(saved);
return saved;
}

/**
Expand Down Expand Up @@ -126,6 +137,7 @@ public DailyQuestionResponse rerollTodayQuestion(Long userId) {
);

udq.rerollTo(newQ);
dailyQuestionExposureService.recordReroll(udq, newQ);

return new DailyQuestionResponse(
newQ.getId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import com.devkor.ifive.nadab.domain.question.application.helper.DailyQuestionSelector;
import com.devkor.ifive.nadab.domain.question.application.helper.QuestionLevelPolicy;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.entity.DailyQuestionRevision;
import com.devkor.ifive.nadab.domain.question.core.entity.UserDailyQuestion;
import com.devkor.ifive.nadab.domain.question.core.repository.UserDailyQuestionRepository;
import com.devkor.ifive.nadab.domain.question.core.service.DailyQuestionExposureService;
import com.devkor.ifive.nadab.domain.user.core.entity.User;
import com.devkor.ifive.nadab.domain.user.core.repository.UserInterestRepository;
import com.devkor.ifive.nadab.domain.user.core.repository.UserRepository;
Expand All @@ -33,6 +35,7 @@ public class QuestionCommandServiceV2 {

private final QuestionLevelPolicy questionLevelPolicy;
private final DailyQuestionSelector dailyQuestionSelector;
private final DailyQuestionExposureService dailyQuestionExposureService;

public DailyQuestionResponseV2 getOrCreateTodayQuestion(Long userId) {

Expand All @@ -44,16 +47,21 @@ public DailyQuestionResponseV2 getOrCreateTodayQuestion(Long userId) {
.orElseGet(() -> this.createTodayQuestion(userId, today));

DailyQuestion question = udq.getDailyQuestion();
DailyQuestionRevision revision = dailyQuestionExposureService
.findLatestRevision(udq)
.orElse(null);

boolean answered = answerEntryRepository.existsActiveAnswer(userId, question.getId());

return new DailyQuestionResponseV2(
question.getId(),
question.getInterest().getCode().toString(),
question.getQuestionText(),
question.getEmpathyGuide(),
question.getHintGuide(),
question.getLeadingQuestionGuide(),
revision != null
? revision.getInterest().getCode().toString()
: question.getInterest().getCode().toString(),
revision != null ? revision.getQuestionText() : question.getQuestionText(),
revision != null ? revision.getEmpathyGuide() : question.getEmpathyGuide(),
revision != null ? revision.getHintGuide() : question.getHintGuide(),
revision != null ? revision.getLeadingQuestionGuide() : question.getLeadingQuestionGuide(),
answered,
udq.getRerollLeft()
);
Expand All @@ -62,27 +70,30 @@ public DailyQuestionResponseV2 getOrCreateTodayQuestion(Long userId) {
public UserDailyQuestion createTodayQuestion(Long userId, LocalDate todayKst) {
// 동시성: 여러 요청이 동시에 들어오면 UNIQUE(user_id, date)로 한 번만 성공해야 함
// -> insert 시도 후 unique 위반이면 다시 조회해서 반환
try {
User user = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
User user = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));

Long userInterestId = userInterestRepository.findInterestIdByUserId(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_INTEREST_NOT_FOUND));
Long userInterestId = userInterestRepository.findInterestIdByUserId(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_INTEREST_NOT_FOUND));

boolean isFirstQuestion = !(userDailyQuestionRepository.existsByUserId(userId));
boolean isFirstQuestion = !(userDailyQuestionRepository.existsByUserId(userId));

Integer levelOnly = questionLevelPolicy.levelOnlyForFirstTime(isFirstQuestion);
Integer levelOnly = questionLevelPolicy.levelOnlyForFirstTime(isFirstQuestion);

DailyQuestion picked = dailyQuestionSelector.pickFirst(user.getId(), userInterestId, levelOnly);
DailyQuestion picked = dailyQuestionSelector.pickFirst(user.getId(), userInterestId, levelOnly);

UserDailyQuestion saved;
try {
UserDailyQuestion udq = UserDailyQuestion.create(user, todayKst, picked);
return userDailyQuestionRepository.save(udq);

saved = userDailyQuestionRepository.save(udq);
} catch (DataIntegrityViolationException e) {
// 이미 생성됨(경합 상황)
return userDailyQuestionRepository.findByUserIdAndDate(userId, todayKst)
.orElseThrow(() -> e);
}

dailyQuestionExposureService.recordInitialAssignment(saved);
return saved;
}

public DailyQuestionResponseV2 rerollTodayQuestion(Long userId) {
Expand Down Expand Up @@ -113,6 +124,7 @@ public DailyQuestionResponseV2 rerollTodayQuestion(Long userId) {
);

udq.rerollTo(newQ);
dailyQuestionExposureService.recordReroll(udq, newQ);

return new DailyQuestionResponseV2(
newQ.getId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ public class DailyQuestion extends SoftDeletableEntity {

@Column(name = "leading_question_guide", length = 100)
private String leadingQuestionGuide;

@Column(name = "current_revision_no", nullable = false)
private int currentRevisionNo;
}
Loading
Loading