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
@@ -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
@@ -1,9 +1,5 @@
package com.debatetimer.domainrepository.organization;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

import com.debatetimer.domain.organization.Organization;
import com.debatetimer.domain.organization.OrganizationTemplate;
import com.debatetimer.entity.organization.OrganizationTemplateEntity;
Expand All @@ -12,6 +8,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

Expand All @@ -25,15 +22,15 @@ public class OrganizationDomainRepository {
public List<Organization> findAll() {
Map<Long, List<OrganizationTemplate>> idToTemplatesEntity = organizationTemplateRepository.findAll()
.stream()
.collect(groupingBy(
.collect(Collectors.groupingBy(
OrganizationTemplateEntity::getOrganizationId,
mapping(OrganizationTemplateEntity::toDomain, toList()))
);
Collectors.mapping(OrganizationTemplateEntity::toDomain, Collectors.toList())
));

return organizationRepository.findAll()
.stream()
.map(entity -> entity.toDomain(
idToTemplatesEntity.getOrDefault(entity.getId(), Collections.emptyList()))
).toList();
idToTemplatesEntity.getOrDefault(entity.getId(), Collections.emptyList())
)).toList();
}
}
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,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

OrganizationResponse 레코드의 필드명 organizationname으로 변경하는 것이 더 명확해 보입니다. 현재 organization이라는 이름은 Organization 도메인 객체 자체로 오해될 소지가 있으며, 실제로는 기관의 이름을 담고 있기 때문입니다. REST API 응답에서 필드명은 그 의미를 명확하게 전달하는 것이 중요합니다.

이 변경에 따라 src/test/java/com/debatetimer/controller/organization/OrganizationDocumentTest.java 파일의 API 문서 정의도 다음과 같이 수정해야 합니다:

fieldWithPath("organizations[].name").type(STRING).description("기관 명"),
Suggested change
String organization,
String name,

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());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전엔 아무 생각없었는데 지금 이렇게 보니 이 기능을 백엔드로 가져온 건 기관이 추가될 때마다 배포해야 되는 게 싫어서 백엔드로 가져온 건데 결국 배포해야 되는게 프론트에서 백엔드로 바뀐 것 뿐이네 라는 생각이 드네요. 지금 당장 안 바꿔도 좋지만 나중에라도 S3에 업로드 하는 게 좋을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하지만 FE에 들어가는 리소스보다 BE에서 하는 리소스가 더 적지 않을까? 라는 것이 개인적인 생각.
이번에 AWS 프리티어 만료되면 이곳저곳 옮겨다녀야 하는 입장에서 오히려 Git으로 관리되는 범위 내에 있는 것도 좋은 생각 아닐까?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근원적으로 리팩터링이 시작된 원인을 살펴보면 "템플릿 접수부터 홈페이지 내 반영까지의 주기가 너무 길고 복잡하다" 차원인데
템플릿 접수를 내가 하니까, 접수와 배포 모두 내가 익숙한 접근경로 내에서 하는게 효율적일 수 있다는 생각도 들어요.

그런 차원에서 S3에 접근하지 않더라도 Git으로 관리하는게 아직까지는 더 편한 느낌이에요

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/han_alm_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/igam_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/kogito_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/mcu_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/nogotte_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/osansi_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/todallae_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/resources/static/icon/visual_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import com.debatetimer.fixture.entity.CustomizeTableEntityGenerator;
import com.debatetimer.fixture.entity.CustomizeTimeBoxEntityGenerator;
import com.debatetimer.fixture.entity.MemberGenerator;
import com.debatetimer.fixture.entity.OrganizationEntityGenerator;
import com.debatetimer.fixture.entity.OrganizationTemplateEntityGenerator;
import com.debatetimer.fixture.entity.PollEntityGenerator;
import com.debatetimer.fixture.entity.VoteEntityGenerator;
import com.debatetimer.repository.customize.CustomizeTableRepository;
Expand Down Expand Up @@ -57,6 +59,12 @@ public abstract class BaseControllerTest {
@Autowired
protected VoteEntityGenerator voteEntityGenerator;

@Autowired
protected OrganizationEntityGenerator organizationEntityGenerator;

@Autowired
protected OrganizationTemplateEntityGenerator organizationTemplateEntityGenerator;

@Autowired
protected HeaderGenerator headerGenerator;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.debatetimer.service.auth.AuthService;
import com.debatetimer.service.customize.CustomizeService;
import com.debatetimer.service.member.MemberService;
import com.debatetimer.service.organization.OrganizationService;
import com.debatetimer.service.poll.PollService;
import com.debatetimer.service.poll.VoteService;
import io.restassured.RestAssured;
Expand Down Expand Up @@ -70,6 +71,9 @@ public abstract class BaseDocumentTest {
@MockitoBean
protected VoteService voteService;

@MockitoBean
protected OrganizationService organizationService;

@MockitoBean
protected AuthManager authManager;

Expand Down
15 changes: 15 additions & 0 deletions src/test/java/com/debatetimer/controller/GlobalControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Value;

public class GlobalControllerTest extends BaseControllerTest {
Expand Down Expand Up @@ -42,4 +44,17 @@ class CorsConfigTest {
.then().statusCode(403);
}
}

@Nested
class StaticFileTest {

@ValueSource(strings = {"/icon/government_icon.png", "/icon/han_alm_icon.png"})
@ParameterizedTest
void 정적_파일을_정상적으로_조회할_수_있다(String filePath) {
given()
.when().get(filePath)
.then().statusCode(200)
.contentType("image/png");
}
}
}
2 changes: 1 addition & 1 deletion src/test/java/com/debatetimer/controller/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public enum Tag {
TIME_BASED_API("Time Based Table API"),
CUSTOMIZE_API("Customize Table API"),
POLL_API("Poll API"),
;
ORGANIZATION_API("Organization API");

private final String displayName;

Expand Down
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);
}
}
}
8 changes: 8 additions & 0 deletions src/test/java/com/debatetimer/service/BaseServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.debatetimer.fixture.entity.CustomizeTableEntityGenerator;
import com.debatetimer.fixture.entity.CustomizeTimeBoxEntityGenerator;
import com.debatetimer.fixture.entity.MemberGenerator;
import com.debatetimer.fixture.entity.OrganizationEntityGenerator;
import com.debatetimer.fixture.entity.OrganizationTemplateEntityGenerator;
import com.debatetimer.fixture.entity.PollEntityGenerator;
import com.debatetimer.fixture.entity.VoteEntityGenerator;
import com.debatetimer.repository.customize.BellRepository;
Expand Down Expand Up @@ -55,6 +57,12 @@ public abstract class BaseServiceTest {
@Autowired
protected VoteEntityGenerator voteEntityGenerator;

@Autowired
protected OrganizationEntityGenerator organizationEntityGenerator;

@Autowired
protected OrganizationTemplateEntityGenerator organizationTemplateEntityGenerator;

protected void runAtSameTime(int count, Runnable task) throws InterruptedException {
List<Thread> threads = IntStream.range(0, count)
.mapToObj(i -> new Thread(task))
Expand Down
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();
}
}
}