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
26 changes: 26 additions & 0 deletions src/docs/asciidoc/grooming-test-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,29 @@ include::{snippetsDir}/submitGroomingTests/1/http-response.adoc[]
include::{snippetsDir}/submitGroomingTests/1/response-fields.adoc[]

---

=== **3. 그루밍 테스트 가능 여부 조회**

그루밍 테스트가 가능한지 여부를 체크하는 api 입니다. (인증된 회원)

==== Request
include::{snippetsDir}/checkGroomingTestAvailability/1/http-request.adoc[]

==== 성공 Response
성공 1. 마지막 테스트일로부터 7일이 지나지 않아, 그루밍 테스트가 불가능한 경우 (테스트가 가능하기까지 남은 일수, 마지막으로 테스트한 날짜, 테스트가 가능한 날짜 정보 응답)

include::{snippetsDir}/checkGroomingTestAvailability/1/http-response.adoc[]

성공 2. 마지막 테스트일로부터 7일 이상 경과해 그루밍 테스트가 가능한 경우 (마지막으로 테스트한 날짜 정보 응답)

include::{snippetsDir}/checkGroomingTestAvailability/2/http-response.adoc[]

성공 3. 이전 테스트 기록이 없고 즉시 그루밍 테스트가 가능한 경우

include::{snippetsDir}/checkGroomingTestAvailability/3/http-response.adoc[]

==== Response Body Fields
include::{snippetsDir}/checkGroomingTestAvailability/1/response-fields.adoc[]

==== 실패 Response
include::{snippetsDir}/checkGroomingTestAvailability/4/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.ftm.server.adapter.in.web.grooming.controller;

import com.ftm.server.adapter.in.web.grooming.dto.response.CheckGroomingTestAvailabilityResponse;
import com.ftm.server.application.port.in.grooming.CheckGroomingTestAvailabilityUseCase;
import com.ftm.server.application.query.FindByUserIdQuery;
import com.ftm.server.application.vo.grooming.GroomingTestAvailabilityVo;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import com.ftm.server.infrastructure.security.UserPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class CheckGroomingTestAvailabilityController {

private final CheckGroomingTestAvailabilityUseCase checkGroomingTestAvailabilityUseCase;

@GetMapping("/api/grooming/tests/availability")
public ResponseEntity<ApiResponse<CheckGroomingTestAvailabilityResponse>> checkAvailability(
@AuthenticationPrincipal UserPrincipal userPrincipal) {
GroomingTestAvailabilityVo result =
checkGroomingTestAvailabilityUseCase.execute(
FindByUserIdQuery.of(userPrincipal.getId()));
return ResponseEntity.status(HttpStatus.OK)
.body(
ApiResponse.success(
SuccessResponseCode.OK,
CheckGroomingTestAvailabilityResponse.from(result)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.ftm.server.adapter.in.web.grooming.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.ftm.server.application.vo.grooming.GroomingTestAvailabilityVo;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;

@Getter
public class CheckGroomingTestAvailabilityResponse {

private final boolean available;
private final Long remainingDays;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm", shape = JsonFormat.Shape.STRING)
private final LocalDateTime lastTestedAt;

@JsonFormat(pattern = "yyyy-MM-dd", shape = JsonFormat.Shape.STRING)
private final LocalDate nextAvailableAt;

private CheckGroomingTestAvailabilityResponse(
GroomingTestAvailabilityVo groomingTestAvailabilityVo) {
this.available = groomingTestAvailabilityVo.isAvailable();
this.remainingDays = groomingTestAvailabilityVo.getRemainingDays();
this.lastTestedAt = groomingTestAvailabilityVo.getLastTestedAt();
this.nextAvailableAt = groomingTestAvailabilityVo.getNextAvailableAt();
}

public static CheckGroomingTestAvailabilityResponse from(
GroomingTestAvailabilityVo groomingTestAvailabilityVo) {
return new CheckGroomingTestAvailabilityResponse(groomingTestAvailabilityVo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import com.ftm.server.application.port.out.persistence.grooming.*;
import com.ftm.server.application.query.FIndGroomingLevelByScoreQuery;
import com.ftm.server.application.query.FindByIdQuery;
import com.ftm.server.application.query.FindByUserIdQuery;
import com.ftm.server.common.annotation.Adapter;
import com.ftm.server.common.exception.CustomException;
import com.ftm.server.common.response.enums.ErrorResponseCode;
import com.ftm.server.domain.entity.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand All @@ -25,7 +27,8 @@ public class GroomingDomainPersistenceAdapter
LoadUserForGroomingPort,
SaveGroomingTestResultPort,
LoadGroomingLevelPort,
UpdateUserForGroomingPort {
UpdateUserForGroomingPort,
LoadGroomingTestResultPort {

// Repository
private final GroomingTestQuestionRepository groomingTestQuestionRepository;
Expand Down Expand Up @@ -113,4 +116,9 @@ public void updateUserGroomingStatus(User user) {
// 유저 그루밍 레벨 업데이트
userJpaEntity.updateGroomingLevel(groomingLevelJpaEntity);
}

@Override
public LocalDateTime loadLatestTestedAtByUserId(FindByUserIdQuery query) {
return groomingTestResultRepository.fetchLatestTestedAtByUserId(query);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ftm.server.adapter.out.persistence.repository;

import com.ftm.server.application.query.FindByUserIdQuery;
import java.time.LocalDateTime;

public interface GroomingTestResultCustomRepository {

LocalDateTime fetchLatestTestedAtByUserId(FindByUserIdQuery query);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.ftm.server.adapter.out.persistence.repository;

import static com.ftm.server.adapter.out.persistence.model.QGroomingTestResultJpaEntity.groomingTestResultJpaEntity;

import com.ftm.server.application.query.FindByUserIdQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class GroomingTestResultCustomRepositoryImpl implements GroomingTestResultCustomRepository {

private final JPAQueryFactory queryFactory;

@Override
public LocalDateTime fetchLatestTestedAtByUserId(FindByUserIdQuery query) {
return queryFactory
.select(groomingTestResultJpaEntity.testedAt.max())
.from(groomingTestResultJpaEntity)
.where(groomingTestResultJpaEntity.user.id.eq(query.getUserId()))
.fetchOne();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface GroomingTestResultRepository
extends JpaRepository<GroomingTestResultJpaEntity, Long> {}
extends JpaRepository<GroomingTestResultJpaEntity, Long>,
GroomingTestResultCustomRepository {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ftm.server.application.port.in.grooming;

import com.ftm.server.application.query.FindByUserIdQuery;
import com.ftm.server.application.vo.grooming.GroomingTestAvailabilityVo;
import com.ftm.server.common.annotation.UseCase;

@UseCase
public interface CheckGroomingTestAvailabilityUseCase {

GroomingTestAvailabilityVo execute(FindByUserIdQuery query);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ftm.server.application.port.out.persistence.grooming;

import com.ftm.server.application.query.FindByUserIdQuery;
import com.ftm.server.common.annotation.Port;
import java.time.LocalDateTime;

@Port
public interface LoadGroomingTestResultPort {

LocalDateTime loadLatestTestedAtByUserId(FindByUserIdQuery query);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ftm.server.application.service.grooming;

import static com.ftm.server.common.consts.StaticConsts.MINIMUM_DAYS_BETWEEN_TESTS;

import com.ftm.server.application.port.in.grooming.CheckGroomingTestAvailabilityUseCase;
import com.ftm.server.application.port.out.persistence.grooming.LoadGroomingTestResultPort;
import com.ftm.server.application.query.FindByUserIdQuery;
import com.ftm.server.application.vo.grooming.GroomingTestAvailabilityVo;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CheckGroomingTestAvailabilityService implements CheckGroomingTestAvailabilityUseCase {

private final LoadGroomingTestResultPort loadGroomingTestResultPort;

@Override
public GroomingTestAvailabilityVo execute(FindByUserIdQuery query) {
// 가장 최근 테스트 날짜 조회
LocalDateTime lastTestDateTime =
loadGroomingTestResultPort.loadLatestTestedAtByUserId(query);
if (lastTestDateTime == null) { // 테스트 기록이 없는 경우
return GroomingTestAvailabilityVo.available(null);
}

LocalDate now = LocalDate.now(ZoneId.of("Asia/Seoul"));
LocalDate lastTestDate = lastTestDateTime.toLocalDate();

// 가장 마지막에 수행한 테스트 날짜와 현재 날짜를 비교해 테스트 가능 여부 판단
long daysSinceLastTest = ChronoUnit.DAYS.between(lastTestDate, now);
if (daysSinceLastTest < MINIMUM_DAYS_BETWEEN_TESTS) { // 마지막 테스트일로부터 7일이 지나지 않은 경우
long remainingDays = MINIMUM_DAYS_BETWEEN_TESTS - daysSinceLastTest;
return GroomingTestAvailabilityVo.unavailable(
remainingDays,
lastTestDateTime,
lastTestDate.plusDays(MINIMUM_DAYS_BETWEEN_TESTS));
}

return GroomingTestAvailabilityVo.available(lastTestDateTime); // 마지막 테스트일로부터 7일이 지난 경우
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.ftm.server.application.vo.grooming;

import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;

@Getter
public class GroomingTestAvailabilityVo {

private final boolean available;
private final Long remainingDays;
private final LocalDateTime lastTestedAt;
private final LocalDate nextAvailableAt;

private GroomingTestAvailabilityVo(
boolean available,
Long remainingDays,
LocalDateTime lastTestedAt,
LocalDate nextAvailableAt) {
this.available = available;
this.remainingDays = remainingDays;
this.lastTestedAt = lastTestedAt;
this.nextAvailableAt = nextAvailableAt;
}

public static GroomingTestAvailabilityVo available(LocalDateTime lastTestedAt) {
return new GroomingTestAvailabilityVo(true, null, lastTestedAt, null);
}

public static GroomingTestAvailabilityVo unavailable(
Long remainingDays, LocalDateTime lastTestedAt, LocalDate nextAvailableAt) {
return new GroomingTestAvailabilityVo(false, remainingDays, lastTestedAt, nextAvailableAt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class StaticConsts {
public static final int HAIR_CATEGORY_MAX_SCORE = 11;
public static final int WORKOUT_CATEGORY_MAX_SCORE = 40;
public static final int FASHION_CATEGORY_MAX_SCORE = 14;

public static final int MINIMUM_DAYS_BETWEEN_TESTS = 7;
public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer ";
public static final String AUTHORIZATION_GRANT_TYPE = "authorization_code";
public static final String PENDING_SOCIAL_USER_SESSION_KEY = "PENDING_SOCIAL_USER_INFO";
Expand Down
Loading