diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserDailySolvedHistoryResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserDailySolvedHistoryResponse.java new file mode 100644 index 00000000..50cbabdf --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserDailySolvedHistoryResponse.java @@ -0,0 +1,10 @@ +package org.ezcode.codetest.application.usermanagement.user.dto.response; + +import java.util.List; + +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; + +public record UserDailySolvedHistoryResponse ( + Long userId, + List dailySolvedCounts +){} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserReviewTokenResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserReviewTokenResponse.java new file mode 100644 index 00000000..441a09c9 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserReviewTokenResponse.java @@ -0,0 +1,10 @@ +package org.ezcode.codetest.application.usermanagement.user.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UserReviewTokenResponse { + private final int reviewToken; +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java index f97311bb..c3bcb420 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java @@ -5,8 +5,11 @@ import java.util.List; import org.ezcode.codetest.application.usermanagement.user.dto.response.GrantAdminRoleResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.UserDailySolvedHistoryResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.UserProfileImageResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.UserReviewTokenResponse; import org.ezcode.codetest.application.usermanagement.user.model.UsersByWeek; +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; import org.ezcode.codetest.domain.submission.dto.WeeklySolveCount; import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest; import org.ezcode.codetest.application.usermanagement.user.dto.request.ModifyUserInfoRequest; @@ -176,4 +179,18 @@ public UserProfileImageResponse deleteUserProfileImage(AuthUser authUser) { return new UserProfileImageResponse(null); } + + @Transactional(readOnly = true) + public UserReviewTokenResponse getReviewToken(AuthUser authUser) { + User user = userDomainService.getUserById(authUser.getId()); + + return new UserReviewTokenResponse(user.getReviewToken()); + } + + @Transactional(readOnly = true) + public UserDailySolvedHistoryResponse getUserDailySolvedHistory(AuthUser authUser) { + Long userId = authUser.getId(); + List solvedHistory = submissionDomainService.getSolvedHistoryByDate(authUser.getId()); + return new UserDailySolvedHistoryResponse(userId, solvedHistory); + } } diff --git a/src/main/java/org/ezcode/codetest/domain/submission/dto/DailyCorrectCount.java b/src/main/java/org/ezcode/codetest/domain/submission/dto/DailyCorrectCount.java new file mode 100644 index 00000000..957c3c7c --- /dev/null +++ b/src/main/java/org/ezcode/codetest/domain/submission/dto/DailyCorrectCount.java @@ -0,0 +1,12 @@ +package org.ezcode.codetest.domain.submission.dto; + +import java.time.LocalDate; + +public record DailyCorrectCount( + LocalDate date, + int count +) { + public DailyCorrectCount(java.sql.Date date, int count) { + this(date.toLocalDate(), count); + } +} diff --git a/src/main/java/org/ezcode/codetest/domain/submission/repository/UserProblemResultRepository.java b/src/main/java/org/ezcode/codetest/domain/submission/repository/UserProblemResultRepository.java index 55433d77..ead89161 100644 --- a/src/main/java/org/ezcode/codetest/domain/submission/repository/UserProblemResultRepository.java +++ b/src/main/java/org/ezcode/codetest/domain/submission/repository/UserProblemResultRepository.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; import org.ezcode.codetest.domain.submission.model.entity.UserProblemResult; import org.springframework.data.repository.query.Param; @@ -18,4 +19,6 @@ public interface UserProblemResultRepository { List findScoresBetween(LocalDateTime start, LocalDateTime end); Optional sumPointByUserId(@Param("userId") Long userId); + + List countCorrectByUserGroupedByDate(Long userId); } diff --git a/src/main/java/org/ezcode/codetest/domain/submission/service/SubmissionDomainService.java b/src/main/java/org/ezcode/codetest/domain/submission/service/SubmissionDomainService.java index 6c635987..79ba07d2 100644 --- a/src/main/java/org/ezcode/codetest/domain/submission/service/SubmissionDomainService.java +++ b/src/main/java/org/ezcode/codetest/domain/submission/service/SubmissionDomainService.java @@ -5,6 +5,7 @@ import java.util.Optional; import org.ezcode.codetest.application.submission.model.SubmissionContext; +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; import org.ezcode.codetest.domain.submission.dto.WeeklySolveCount; import org.ezcode.codetest.domain.submission.model.SubmissionResult; import org.ezcode.codetest.domain.submission.model.TestcaseEvaluationInput; @@ -18,7 +19,9 @@ import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class SubmissionDomainService { @@ -85,6 +88,10 @@ public List getWeeklySolveCounts( return submissionRepository.fetchWeeklySolveCounts(startDateTime, endDateTime); } + public List getSolvedHistoryByDate(Long userId) { + return userProblemResultRepository.countCorrectByUserGroupedByDate(userId); + } + private boolean evaluate(TestcaseEvaluationInput input) { boolean isCorrect = input.isCorrect(); boolean timeEfficient = input.timeEfficient(); diff --git a/src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java b/src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java index 0745d20d..e19024f4 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java @@ -83,7 +83,7 @@ private String callChatApi(Map requestBody) { .bodyValue(requestBody) .retrieve() .bodyToMono(OpenAIResponse.class) - .timeout(Duration.ofSeconds(10)) + .timeout(Duration.ofSeconds(20)) .retryWhen( Retry.backoff(3, Duration.ofSeconds(1)) .maxBackoff(Duration.ofSeconds(5)) diff --git a/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/impl/UserProblemResultRepositoryImpl.java b/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/impl/UserProblemResultRepositoryImpl.java index bcfe468a..e53a5099 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/impl/UserProblemResultRepositoryImpl.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/impl/UserProblemResultRepositoryImpl.java @@ -4,9 +4,11 @@ import java.util.List; import java.util.Optional; +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; import org.ezcode.codetest.domain.submission.model.entity.UserProblemResult; import org.ezcode.codetest.domain.submission.repository.UserProblemResultRepository; import org.ezcode.codetest.infrastructure.persistence.repository.submission.jpa.UserProblemResultJpaRepository; +import org.ezcode.codetest.infrastructure.persistence.repository.submission.query.UserProblemResultQueryRepository; import org.springframework.stereotype.Repository; import lombok.RequiredArgsConstructor; @@ -16,6 +18,7 @@ public class UserProblemResultRepositoryImpl implements UserProblemResultRepository { private final UserProblemResultJpaRepository userProblemResultJpaRepository; + private final UserProblemResultQueryRepository userProblemResultQueryRepository; @Override public Optional findUserProblemResultByUserIdAndProblemId(Long userId, Long problemId) { @@ -42,4 +45,9 @@ public Optional sumPointByUserId(Long userId) { return userProblemResultJpaRepository.sumScoreByUserId(userId); } + @Override + public List countCorrectByUserGroupedByDate(Long userId) { + return userProblemResultQueryRepository.countCorrectByUserGroupedByDate(userId); + } + } diff --git a/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/query/UserProblemResultQueryRepository.java b/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/query/UserProblemResultQueryRepository.java new file mode 100644 index 00000000..18c0b71c --- /dev/null +++ b/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/query/UserProblemResultQueryRepository.java @@ -0,0 +1,9 @@ +package org.ezcode.codetest.infrastructure.persistence.repository.submission.query; + +import java.util.List; + +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; + +public interface UserProblemResultQueryRepository { + List countCorrectByUserGroupedByDate(Long userId); +} diff --git a/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/query/UserProblemResultQueryRepositoryImpl.java b/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/query/UserProblemResultQueryRepositoryImpl.java new file mode 100644 index 00000000..195c6f26 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/submission/query/UserProblemResultQueryRepositoryImpl.java @@ -0,0 +1,43 @@ +package org.ezcode.codetest.infrastructure.persistence.repository.submission.query; + + +import java.util.List; + +import org.ezcode.codetest.domain.submission.dto.DailyCorrectCount; +import org.ezcode.codetest.domain.submission.model.entity.QUserProblemResult; +import org.springframework.stereotype.Repository; + +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class UserProblemResultQueryRepositoryImpl implements UserProblemResultQueryRepository{ + + private final JPAQueryFactory queryFactory; + + @Override + public List countCorrectByUserGroupedByDate(Long userId) { + + QUserProblemResult upr = QUserProblemResult.userProblemResult; + + var date = Expressions.dateTemplate(java.sql.Date.class, "DATE({0})", upr.modifiedAt); + + return queryFactory + .select(Projections.constructor(DailyCorrectCount.class, + date, + upr.count().intValue() + )) + .from(upr) + .where( + upr.user.id.eq(userId), + upr.isCorrect.eq(true) + ) + .groupBy(date) + .orderBy(date.asc()) + .fetch(); + } +} diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java index 8b4e8128..96e58d96 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java @@ -4,8 +4,10 @@ import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest; import org.ezcode.codetest.application.usermanagement.user.dto.response.ChangeUserPasswordResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.GrantAdminRoleResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.UserDailySolvedHistoryResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.UserInfoResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.UserProfileImageResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.UserReviewTokenResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.WithdrawUserResponse; import org.ezcode.codetest.application.usermanagement.user.service.UserService; import org.ezcode.codetest.domain.user.model.entity.AuthUser; @@ -94,4 +96,19 @@ public ResponseEntity withdraw( return ResponseEntity.status(HttpStatus.OK).body(userService.withdrawUser(authUser)); } + @Operation(summary = "회원 AI 리뷰 토큰 조회", description = "회원의 남은 AI리뷰용 토큰의 개수를 조회합니다.") + @GetMapping("/users/review-token") + public ResponseEntity getReviewToken( + @AuthenticationPrincipal AuthUser authUser + ){ + return ResponseEntity.status(HttpStatus.OK).body(userService.getReviewToken(authUser)); + } + + @GetMapping("/users/daily-solved") + public ResponseEntity getUserDailySolvedHistory( + @AuthenticationPrincipal AuthUser authUser + ){ + return ResponseEntity.status(HttpStatus.OK).body(userService.getUserDailySolvedHistory(authUser)); + } + } diff --git a/src/main/resources/templates/submit-test.html b/src/main/resources/templates/submit-test.html index 580f829e..bbf42eb4 100644 --- a/src/main/resources/templates/submit-test.html +++ b/src/main/resources/templates/submit-test.html @@ -282,7 +282,7 @@ const d = document.getElementById(`slot-${data.seqId}`); if (!d) return; d.className = data.isPassed ? 'slot passed' : 'slot failed'; - d.textContent = `[${data.seqId}] ${data.message} (${data.executionTime}s, ${data.memoryUsage}KB)`; + d.textContent = `[${data.seqId}] ${data.message} (${data.executionTime}ms, ${data.memoryUsage}KB)`; } function handleFinal(res) { diff --git a/src/test/java/org/ezcode/codetest/domain/user/UserDomainServiceTest.java b/src/test/java/org/ezcode/codetest/domain/user/UserDomainServiceTest.java index 9b228299..2cc43a11 100644 --- a/src/test/java/org/ezcode/codetest/domain/user/UserDomainServiceTest.java +++ b/src/test/java/org/ezcode/codetest/domain/user/UserDomainServiceTest.java @@ -13,7 +13,6 @@ import org.ezcode.codetest.domain.user.repository.UserAuthTypeRepository; import org.ezcode.codetest.domain.user.repository.UserRepository; import org.ezcode.codetest.domain.user.service.UserDomainService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -58,7 +57,6 @@ public class UserDomainServiceTest { ) { public Long getId() { return 1L; } public int getReviewToken() { return 5; } - public int getZeroReviewToken() { return 0; } }; private final UserAuthType testAuthType = new UserAuthType(testUser, AuthType.EMAIL); @@ -173,4 +171,12 @@ void isDeletedUser_shouldPassWhenActive() { assertDoesNotThrow(() -> userDomainService.isDeletedUser(testUser)); } + // 9. 닉네임 자동 생성 테스트 + @Test + void generateUniqueNickname_shouldReturnNonExistingNickname() { + when(userRepository.existsByNickname(any())).thenReturn(false); + String nickname = userDomainService.generateUniqueNickname(); + assertNotNull(nickname); + } + }