-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] 기관별 템플릿 조회 기능 구현 #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
41ff1a7
f14a7b3
8b793eb
ac4a3af
7cb7f9b
5c62383
409390c
7af3de9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.debatetimer.controller.organization; | ||
|
|
||
| import com.debatetimer.dto.organization.OrganizationResponses; | ||
| import com.debatetimer.service.organization.OrganizationService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| public class OrganizationController { | ||
|
|
||
| private final OrganizationService organizationService; | ||
|
|
||
| @GetMapping("/api/organizations/templates") | ||
| public ResponseEntity<OrganizationResponses> getOrganizationTemplates() { | ||
| return ResponseEntity.ok(organizationService.findAll()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.debatetimer.dto.organization; | ||
|
|
||
| import com.debatetimer.domain.organization.Organization; | ||
| import com.debatetimer.domain.organization.OrganizationTemplate; | ||
| import java.util.List; | ||
|
|
||
| public record OrganizationResponse( | ||
| String organization, | ||
| String affiliation, | ||
| String iconPath, | ||
| List<OrganizationTemplateResponse> templates | ||
| ) { | ||
|
|
||
| public OrganizationResponse(Organization organization) { | ||
| this( | ||
| organization.getName(), | ||
| organization.getAffiliation(), | ||
| organization.getIconPath(), | ||
| toTemplatesResponse(organization.getTemplates()) | ||
| ); | ||
| } | ||
|
|
||
| private static List<OrganizationTemplateResponse> toTemplatesResponse(List<OrganizationTemplate> templates) { | ||
| return templates.stream() | ||
| .map(OrganizationTemplateResponse::new) | ||
| .toList(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.debatetimer.dto.organization; | ||
|
|
||
| import com.debatetimer.domain.organization.Organization; | ||
| import java.util.List; | ||
|
|
||
| public record OrganizationResponses(List<OrganizationResponse> organizations) { | ||
|
|
||
| public static OrganizationResponses from(List<Organization> organizations) { | ||
| return new OrganizationResponses(toOrganizationsResponse(organizations)); | ||
| } | ||
|
|
||
| private static List<OrganizationResponse> toOrganizationsResponse(List<Organization> organizations) { | ||
| return organizations.stream() | ||
| .map(OrganizationResponse::new) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.debatetimer.dto.organization; | ||
|
|
||
| import com.debatetimer.domain.organization.OrganizationTemplate; | ||
|
|
||
| public record OrganizationTemplateResponse(String name, String data) { | ||
|
|
||
| public OrganizationTemplateResponse(OrganizationTemplate template) { | ||
| this(template.getName(), template.getData()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.debatetimer.service.organization; | ||
|
|
||
| import com.debatetimer.domainrepository.organization.OrganizationDomainRepository; | ||
| import com.debatetimer.dto.organization.OrganizationResponses; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class OrganizationService { | ||
|
|
||
| private final OrganizationDomainRepository organizationDomainRepository; | ||
|
|
||
| public OrganizationResponses findAll() { | ||
| return OrganizationResponses.from(organizationDomainRepository.findAll()); | ||
| } | ||
| } |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전엔 아무 생각없었는데 지금 이렇게 보니 이 기능을 백엔드로 가져온 건 기관이 추가될 때마다 배포해야 되는 게 싫어서 백엔드로 가져온 건데 결국 배포해야 되는게 프론트에서 백엔드로 바뀐 것 뿐이네 라는 생각이 드네요. 지금 당장 안 바꿔도 좋지만 나중에라도 S3에 업로드 하는 게 좋을 것 같아요.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 하지만 FE에 들어가는 리소스보다 BE에서 하는 리소스가 더 적지 않을까? 라는 것이 개인적인 생각.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 근원적으로 리팩터링이 시작된 원인을 살펴보면 "템플릿 접수부터 홈페이지 내 반영까지의 주기가 너무 길고 복잡하다" 차원인데 그런 차원에서 S3에 접근하지 않더라도 Git으로 관리하는게 아직까지는 더 편한 느낌이에요 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.debatetimer.controller.organization; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.junit.jupiter.api.Assertions.assertAll; | ||
|
|
||
| import com.debatetimer.controller.BaseControllerTest; | ||
| import com.debatetimer.dto.organization.OrganizationResponses; | ||
| import com.debatetimer.entity.organization.OrganizationEntity; | ||
| import io.restassured.http.ContentType; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| class OrganizationControllerTest extends BaseControllerTest { | ||
|
|
||
| @Nested | ||
| class GetOrganizationTemplates { | ||
|
|
||
| @Test | ||
| void 모든_기관의_토론_템플릿을_조회할_수_있다() { | ||
| OrganizationEntity organization1 = organizationEntityGenerator.generate("한앎", "한양대"); | ||
| OrganizationEntity organization2 = organizationEntityGenerator.generate("한모름", "양한대"); | ||
| organizationTemplateEntityGenerator.generate(organization1, "템플릿1"); | ||
| organizationTemplateEntityGenerator.generate(organization1, "템플릿2"); | ||
| organizationTemplateEntityGenerator.generate(organization2, "릿플템1"); | ||
|
|
||
| OrganizationResponses response = given() | ||
| .contentType(ContentType.JSON) | ||
| .when().get("/api/organizations/templates") | ||
| .then().statusCode(HttpStatus.OK.value()) | ||
| .extract().as(OrganizationResponses.class); | ||
|
|
||
| assertAll( | ||
| () -> assertThat(response.organizations()).hasSize(2), | ||
| () -> assertThat(response.organizations().get(0).templates()).hasSize(2), | ||
| () -> assertThat(response.organizations().get(1).templates()).hasSize(1) | ||
| ); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.debatetimer.controller.organization; | ||
|
|
||
| import static org.mockito.Mockito.doReturn; | ||
| import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; | ||
| import static org.springframework.restdocs.payload.JsonFieldType.STRING; | ||
| import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; | ||
|
|
||
| import com.debatetimer.controller.BaseDocumentTest; | ||
| import com.debatetimer.controller.RestDocumentationRequest; | ||
| import com.debatetimer.controller.RestDocumentationResponse; | ||
| import com.debatetimer.controller.Tag; | ||
| import com.debatetimer.dto.organization.OrganizationResponse; | ||
| import com.debatetimer.dto.organization.OrganizationResponses; | ||
| import com.debatetimer.dto.organization.OrganizationTemplateResponse; | ||
| import io.restassured.http.ContentType; | ||
| import java.util.List; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class OrganizationDocumentTest extends BaseDocumentTest { | ||
|
|
||
| @Nested | ||
| class GetOrganizationTemplates { | ||
|
|
||
| private final RestDocumentationRequest requestDocument = request() | ||
| .tag(Tag.ORGANIZATION_API) | ||
| .summary("기관별 템플릿 조회"); | ||
|
|
||
| private final RestDocumentationResponse responseDocument = response() | ||
| .responseBodyField( | ||
| fieldWithPath("organizations").type(ARRAY).description("기관 정보"), | ||
| fieldWithPath("organizations[].organization").type(STRING).description("기관 명"), | ||
| fieldWithPath("organizations[].affiliation").type(STRING).description("소속"), | ||
| fieldWithPath("organizations[].iconPath").type(STRING).description("아이콘 경로 (해당 경로로 서버 요청)"), | ||
| fieldWithPath("organizations[].templates").type(ARRAY).description("기관 템플릿 목록"), | ||
| fieldWithPath("organizations[].templates[].name").type(STRING).description("템플릿 명"), | ||
| fieldWithPath("organizations[].templates[].data").type(STRING).description("템플릿 데이터") | ||
| ); | ||
|
|
||
| private static final String DEFAULT_TEMPLATE_CONTENT = "eJyrVspMUbIytjDXUcrMS8tXsqpWykvMTVWyUjJWKCtWMFZ427b1TXPj27YFrxcueN3T8HZWj8LbGVPfdM9V0lEqqSwAqXQODQ7x9%2FWMcgUKJaan5qUkAgWB7IKi%2FOKQ1MRcP4iBbzasedOyESienJ%2BHLP56wwygwUDx8sSivMy8dKfUnBwlq7TEnOJUHaW0zLzM4gwkoVqgtYlJOUCN0dVKxSWJeckgMwKC%2FIOBJhQXpKYmZ4RAnPVmXivQzUDRpPwKqJCff5Cvow%2FI5Zkgqw2NDICyYLPzSnNyIMIBqUUgx6EJBRekJmYDHQcTLgbxU4sg3FodJKc4%2B%2FsNFqf4uYaGBIEtQXPNhDdzFkCiFMVNIZ6%2BrvFOjsGuLnB3QazA6TBzkLMx3AX2DIkhtHXOm0WtCq83zHkzbQfdAwpr8qG7i2JrAbdLRw0%3D"; | ||
|
|
||
| @Test | ||
| void 기관_템플릿_조회_성공() { | ||
| OrganizationResponses response = new OrganizationResponses(List.of( | ||
| new OrganizationResponse("한앎", "한양대", "/icons/icon1.png", List.of( | ||
| new OrganizationTemplateResponse("템플릿1", DEFAULT_TEMPLATE_CONTENT), | ||
| new OrganizationTemplateResponse("템플릿2", DEFAULT_TEMPLATE_CONTENT) | ||
| )), | ||
| new OrganizationResponse("한모름", "양한대", "/icons/icon2.png", List.of( | ||
| new OrganizationTemplateResponse("템플릿1", DEFAULT_TEMPLATE_CONTENT), | ||
| new OrganizationTemplateResponse("템플릿2", DEFAULT_TEMPLATE_CONTENT) | ||
| )) | ||
| )); | ||
| doReturn(response).when(organizationService).findAll(); | ||
|
|
||
| var document = document("organization/get-templates", 200) | ||
| .request(requestDocument) | ||
| .response(responseDocument) | ||
| .build(); | ||
|
|
||
| given(document) | ||
| .contentType(ContentType.JSON) | ||
| .when().get("/api/organizations/templates") | ||
| .then().statusCode(200); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.debatetimer.service.organization; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.junit.jupiter.api.Assertions.assertAll; | ||
|
|
||
| import com.debatetimer.dto.organization.OrganizationResponses; | ||
| import com.debatetimer.entity.organization.OrganizationEntity; | ||
| import com.debatetimer.service.BaseServiceTest; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
|
|
||
| class OrganizationServiceTest extends BaseServiceTest { | ||
|
|
||
| @Autowired | ||
| private OrganizationService organizationService; | ||
|
|
||
| @Nested | ||
| class FindAll { | ||
|
|
||
| @Test | ||
| void 저장한_모든_기관_템플릿을_반환한다() { | ||
| OrganizationEntity organization1 = organizationEntityGenerator.generate("한앎", "한양대"); | ||
| OrganizationEntity organization2 = organizationEntityGenerator.generate("한모름", "양한대"); | ||
| organizationTemplateEntityGenerator.generate(organization1, "템플릿1"); | ||
| organizationTemplateEntityGenerator.generate(organization1, "템플릿2"); | ||
| organizationTemplateEntityGenerator.generate(organization2, "릿플템1"); | ||
|
|
||
| OrganizationResponses response = organizationService.findAll(); | ||
|
|
||
| assertAll( | ||
| () -> assertThat(response.organizations()).hasSize(2), | ||
| () -> assertThat(response.organizations().get(0).templates()).hasSize(2), | ||
| () -> assertThat(response.organizations().get(1).templates()).hasSize(1) | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void 비어있을_경우_빈_객체를_반환한다() { | ||
| OrganizationResponses response = organizationService.findAll(); | ||
|
|
||
| assertThat(response.organizations()).isEmpty(); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OrganizationResponse레코드의 필드명organization은name으로 변경하는 것이 더 명확해 보입니다. 현재organization이라는 이름은Organization도메인 객체 자체로 오해될 소지가 있으며, 실제로는 기관의 이름을 담고 있기 때문입니다. REST API 응답에서 필드명은 그 의미를 명확하게 전달하는 것이 중요합니다.이 변경에 따라
src/test/java/com/debatetimer/controller/organization/OrganizationDocumentTest.java파일의 API 문서 정의도 다음과 같이 수정해야 합니다: