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
11 changes: 11 additions & 0 deletions src/docs/asciidoc/grooming-test-api.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
=== **1. 그루밍 테스트 목록 조회**

그루밍 테스트 목록 조회 api 입니다.

==== 성공 Response
include::{snippetsDir}/loadGroomingTests/1/http-response.adoc[]

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

---
6 changes: 5 additions & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,8 @@ include::user-api.adoc[]

= **인증/인가**

include::auth-api.adoc[]
include::auth-api.adoc[]

= **그루밍 테스트**

include::grooming-test-api.adoc[]
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ftm.server.adapter.in.web.grooming.controller;

import com.ftm.server.adapter.in.web.grooming.dto.response.GroomingTestsInfoResponse;
import com.ftm.server.application.port.in.grooming.LoadGroomingTestsUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class LoadGroomingTestsController {

private final LoadGroomingTestsUseCase loadGroomingTestsUseCase;

@GetMapping("/api/grooming/tests")
public ResponseEntity<ApiResponse<GroomingTestsInfoResponse>> loadGroomingTests() {
return ResponseEntity.status(HttpStatus.OK)
.body(
ApiResponse.success(
SuccessResponseCode.OK,
GroomingTestsInfoResponse.from(
loadGroomingTestsUseCase.execute())));
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ftm.server.adapter.in.web.grooming.dto.response;

import com.ftm.server.application.vo.grooming.GroomingTestQuestionWithAnswersVo;
import java.util.Set;
import lombok.Getter;

@Getter
public class GroomingTestsInfoResponse {

private final Integer totalCount;
private final Set<GroomingTestQuestionWithAnswersVo> groomingTests;

GroomingTestsInfoResponse(Set<GroomingTestQuestionWithAnswersVo> groomingTests) {
this.totalCount = groomingTests.size();
this.groomingTests = groomingTests;
}

public static GroomingTestsInfoResponse from(
Set<GroomingTestQuestionWithAnswersVo> groomingTests) {
return new GroomingTestsInfoResponse(groomingTests);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.ftm.server.adapter.out.persistence.adapter.grooming;

import com.ftm.server.adapter.out.persistence.mapper.GroomingTestAnswerMapper;
import com.ftm.server.adapter.out.persistence.mapper.GroomingTestQuestionMapper;
import com.ftm.server.adapter.out.persistence.model.GroomingTestAnswerJpaEntity;
import com.ftm.server.adapter.out.persistence.model.GroomingTestQuestionJpaEntity;
import com.ftm.server.adapter.out.persistence.repository.GroomingTestAnswerRepository;
import com.ftm.server.adapter.out.persistence.repository.GroomingTestQuestionRepository;
import com.ftm.server.application.port.out.persistence.grooming.LoadGroomingTestAnswerPort;
import com.ftm.server.application.port.out.persistence.grooming.LoadGroomingTestQuestionPort;
import com.ftm.server.common.annotation.Adapter;
import com.ftm.server.domain.entity.GroomingTestAnswer;
import com.ftm.server.domain.entity.GroomingTestQuestion;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Adapter
@RequiredArgsConstructor
@Slf4j
public class GroomingDomainPersistenceAdapter
implements LoadGroomingTestQuestionPort, LoadGroomingTestAnswerPort {

// Repository
private final GroomingTestQuestionRepository groomingTestQuestionRepository;
private final GroomingTestAnswerRepository groomingTestAnswerRepository;

// Mapper
private final GroomingTestQuestionMapper groomingTestQuestionMapper;
private final GroomingTestAnswerMapper groomingTestAnswerMapper;

@Override
public List<GroomingTestQuestion> loadGroomingTestQuestions() {
List<GroomingTestQuestionJpaEntity> questions = groomingTestQuestionRepository.findAll();
return questions.stream()
.map(groomingTestQuestionMapper::toDomain)
.collect(Collectors.toList());
}

@Override
public List<GroomingTestAnswer> loadGroomingTestAnswers() {
List<GroomingTestAnswerJpaEntity> answers = groomingTestAnswerRepository.findAll();
return answers.stream()
.map(groomingTestAnswerMapper::toDomain)
.collect(Collectors.toList());
}
}
Empty file.
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.vo.grooming.GroomingTestQuestionWithAnswersVo;
import com.ftm.server.common.annotation.UseCase;
import java.util.Set;

@UseCase
public interface LoadGroomingTestsUseCase {

Set<GroomingTestQuestionWithAnswersVo> execute();
}
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.common.annotation.Port;
import com.ftm.server.domain.entity.GroomingTestAnswer;
import java.util.List;

@Port
public interface LoadGroomingTestAnswerPort {

List<GroomingTestAnswer> loadGroomingTestAnswers();
}
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.common.annotation.Port;
import com.ftm.server.domain.entity.GroomingTestQuestion;
import java.util.List;

@Port
public interface LoadGroomingTestQuestionPort {

List<GroomingTestQuestion> loadGroomingTestQuestions();
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.ftm.server.application.service.grooming;

import static com.ftm.server.common.consts.StaticConsts.GROOMING_TESTS_INFO_CACHE_KEY_ALL;
import static com.ftm.server.common.consts.StaticConsts.GROOMING_TESTS_INFO_CACHE_NAME;

import com.ftm.server.application.port.in.grooming.LoadGroomingTestsUseCase;
import com.ftm.server.application.port.out.persistence.grooming.LoadGroomingTestAnswerPort;
import com.ftm.server.application.port.out.persistence.grooming.LoadGroomingTestQuestionPort;
import com.ftm.server.application.vo.grooming.GroomingTestAnswerInfoVo;
import com.ftm.server.application.vo.grooming.GroomingTestQuestionWithAnswersVo;
import com.ftm.server.domain.entity.GroomingTestAnswer;
import com.ftm.server.domain.entity.GroomingTestQuestion;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LoadGroomingTestsService implements LoadGroomingTestsUseCase {

private final LoadGroomingTestQuestionPort loadGroomingTestQuestionPort;
private final LoadGroomingTestAnswerPort loadGroomingTestAnswerPort;

@Override
@Cacheable(value = GROOMING_TESTS_INFO_CACHE_NAME, key = GROOMING_TESTS_INFO_CACHE_KEY_ALL)
public Set<GroomingTestQuestionWithAnswersVo> execute() {
// 모든 그루밍 테스트 질문 목록 조회
List<GroomingTestQuestion> questions =
loadGroomingTestQuestionPort.loadGroomingTestQuestions();

// 모든 그루밍 테스트 답변 목록 조회
List<GroomingTestAnswer> answers = loadGroomingTestAnswerPort.loadGroomingTestAnswers();

// 같은 질문의 답변 목록끼리 그룹화
Map<Long, List<GroomingTestAnswerInfoVo>> answersByQuestionId =
answers.stream()
.map(GroomingTestAnswerInfoVo::from)
.collect(
Collectors.groupingBy(
GroomingTestAnswerInfoVo::getGroomingTestQuestionId));

// 각 그루밍 테스트 질문에 속한 답변목록을 함께 담아 응답, 순서 랜덤
return questions.stream()
.map(
question ->
GroomingTestQuestionWithAnswersVo.from(
question,
answersByQuestionId.getOrDefault(
question.getId(), List.of())))
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.ftm.server.application.vo.grooming;

import com.ftm.server.domain.entity.GroomingTestAnswer;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class GroomingTestAnswerInfoVo {

private Long groomingTestAnswerId;
private Long groomingTestQuestionId;
private String answer;
private Integer score;

private GroomingTestAnswerInfoVo(GroomingTestAnswer groomingTestAnswer) {
this.groomingTestAnswerId = groomingTestAnswer.getId();
this.groomingTestQuestionId = groomingTestAnswer.getGroomingTestQuestionId();
this.answer = groomingTestAnswer.getAnswer();
this.score = groomingTestAnswer.getScore();
}

public static GroomingTestAnswerInfoVo from(GroomingTestAnswer groomingTestAnswer) {
return new GroomingTestAnswerInfoVo(groomingTestAnswer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.ftm.server.application.vo.grooming;

import com.ftm.server.domain.entity.GroomingTestQuestion;
import com.ftm.server.domain.enums.GroomingCategory;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class GroomingTestQuestionWithAnswersVo {

private Long groomingTestQuestionId;
private String question;
private GroomingCategory groomingCategory;
private List<GroomingTestAnswerInfoVo> answers;

private GroomingTestQuestionWithAnswersVo(
GroomingTestQuestion groomingTestQuestion, List<GroomingTestAnswerInfoVo> answers) {
this.groomingTestQuestionId = groomingTestQuestion.getId();
this.question = groomingTestQuestion.getQuestion();
this.groomingCategory = groomingTestQuestion.getGroomingCategory();
this.answers = answers;
}

public static GroomingTestQuestionWithAnswersVo from(
GroomingTestQuestion groomingTestQuestion, List<GroomingTestAnswerInfoVo> answers) {
return new GroomingTestQuestionWithAnswersVo(groomingTestQuestion, answers);
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/ftm/server/common/consts/StaticConsts.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ public class StaticConsts {
public static final String AUTHORIZATION_GRANT_TYPE = "authorization_code";
public static final String PENDING_SOCIAL_USER_SESSION_KEY = "PENDING_SOCIAL_USER_INFO";
public static final int PENDING_SOCIAL_USER_SESSION_TTL = 300; // 5분
public static final String GROOMING_TESTS_INFO_CACHE_NAME = "ftm:grooming:tests:info";
public static final String GROOMING_TESTS_INFO_CACHE_KEY_ALL = "'all'";
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class SecurityConfig {
"https://fittheman.site"); // 개발 환경 클라이언트 도메인

private static final String[] GET_ANONYMOUS_MATCHERS = {
"/api/users/email/duplication", "/api/users/options"
"/api/users/email/duplication", "/api/users/options", "/api/grooming/tests"
};

private static final String[] POST_ANONYMOUS_MATCHERS = {
Expand Down
88 changes: 88 additions & 0 deletions src/test/java/com/ftm/server/grooming/LoadGroomingTestsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.ftm.server.grooming;

import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.JsonFieldType.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.ftm.server.BaseTest;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;

public class LoadGroomingTestsTest extends BaseTest {

private final List<FieldDescriptor> responseFieldLoadGroomingTests =
List.of(
fieldWithPath("status").type(NUMBER).description("응답 상태"),
fieldWithPath("code").type(STRING).description("상태 코드"),
fieldWithPath("message").type(STRING).description("메시지"),
fieldWithPath("data").type(OBJECT).optional().description("data"),
fieldWithPath("data.totalCount").type(NUMBER).description("그루밍 테스트 총 문항 수"),
fieldWithPath("data.groomingTests")
.type(ARRAY)
.description("그루밍 테스트 목록 (순서 랜덤, 질문:답변목록 조합)"),
fieldWithPath("data.groomingTests[].groomingTestQuestionId")
.type(NUMBER)
.description("그루밍 테스트 질문 id"),
fieldWithPath("data.groomingTests[].question")
.type(STRING)
.description("그루밍 테스트 질문 내용"),
fieldWithPath("data.groomingTests[].groomingCategory")
.type(STRING)
.description("그루밍 테스트 질문 카테고리"),
fieldWithPath("data.groomingTests[].answers")
.type(ARRAY)
.description("그루밍 테스트 답변 목록"),
fieldWithPath("data.groomingTests[].answers[].groomingTestAnswerId")
.type(NUMBER)
.description("그루밍 테스트 답변 id"),
fieldWithPath("data.groomingTests[].answers[].groomingTestQuestionId")
.type(NUMBER)
.description("그루밍 테스트 답변이 속한 질문 id"),
fieldWithPath("data.groomingTests[].answers[].answer")
.type(STRING)
.description("그루밍 테스트 답변 내용"),
fieldWithPath("data.groomingTests[].answers[].score")
.type(NUMBER)
.description("그루밍 테스트 답변 점수"));

private RestDocumentationResultHandler getDocument(Integer identifier) {
return document(
"loadGroomingTests/" + identifier,
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint(), getModifiedHeader()),
responseFields(responseFieldLoadGroomingTests),
resource(
ResourceSnippetParameters.builder()
.tag("그루밍 테스트")
.summary("그루밍 테스트 목록 조회 api")
.description("그루밍 테스트 목록 조회 api 입니다.")
.responseFields(responseFieldLoadGroomingTests)
.build()));
}

@Test
@Transactional
void 그루밍_테스트_목록_조회_성공() throws Exception {
// when
ResultActions resultActions =
mockMvc.perform(RestDocumentationRequestBuilders.get("/api/grooming/tests"));

// then
resultActions.andExpect(status().isOk()).andDo(print());

// documentation
resultActions.andDo(getDocument(1));
}
}