diff --git a/src/main/java/com/debatetimer/controller/organization/OrganizationController.java b/src/main/java/com/debatetimer/controller/organization/OrganizationController.java new file mode 100644 index 00000000..46efeae6 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/organization/OrganizationController.java @@ -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 getOrganizationTemplates() { + return ResponseEntity.ok(organizationService.findAll()); + } +} diff --git a/src/main/java/com/debatetimer/domainrepository/organization/OrganizationDomainRepository.java b/src/main/java/com/debatetimer/domainrepository/organization/OrganizationDomainRepository.java index f13054e6..917de8cc 100644 --- a/src/main/java/com/debatetimer/domainrepository/organization/OrganizationDomainRepository.java +++ b/src/main/java/com/debatetimer/domainrepository/organization/OrganizationDomainRepository.java @@ -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; @@ -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; @@ -25,15 +22,15 @@ public class OrganizationDomainRepository { public List findAll() { Map> 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(); } } diff --git a/src/main/java/com/debatetimer/dto/organization/OrganizationResponse.java b/src/main/java/com/debatetimer/dto/organization/OrganizationResponse.java new file mode 100644 index 00000000..59bcfa90 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/organization/OrganizationResponse.java @@ -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 templates +) { + + public OrganizationResponse(Organization organization) { + this( + organization.getName(), + organization.getAffiliation(), + organization.getIconPath(), + toTemplatesResponse(organization.getTemplates()) + ); + } + + private static List toTemplatesResponse(List templates) { + return templates.stream() + .map(OrganizationTemplateResponse::new) + .toList(); + } +} diff --git a/src/main/java/com/debatetimer/dto/organization/OrganizationResponses.java b/src/main/java/com/debatetimer/dto/organization/OrganizationResponses.java new file mode 100644 index 00000000..f28ca43f --- /dev/null +++ b/src/main/java/com/debatetimer/dto/organization/OrganizationResponses.java @@ -0,0 +1,17 @@ +package com.debatetimer.dto.organization; + +import com.debatetimer.domain.organization.Organization; +import java.util.List; + +public record OrganizationResponses(List organizations) { + + public static OrganizationResponses from(List organizations) { + return new OrganizationResponses(toOrganizationsResponse(organizations)); + } + + private static List toOrganizationsResponse(List organizations) { + return organizations.stream() + .map(OrganizationResponse::new) + .toList(); + } +} diff --git a/src/main/java/com/debatetimer/dto/organization/OrganizationTemplateResponse.java b/src/main/java/com/debatetimer/dto/organization/OrganizationTemplateResponse.java new file mode 100644 index 00000000..9c6add1f --- /dev/null +++ b/src/main/java/com/debatetimer/dto/organization/OrganizationTemplateResponse.java @@ -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()); + } +} diff --git a/src/main/java/com/debatetimer/service/organization/OrganizationService.java b/src/main/java/com/debatetimer/service/organization/OrganizationService.java new file mode 100644 index 00000000..ab07e4b5 --- /dev/null +++ b/src/main/java/com/debatetimer/service/organization/OrganizationService.java @@ -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()); + } +} diff --git a/src/main/resources/static/icon/debate_commission_icon.png b/src/main/resources/static/icon/debate_commission_icon.png new file mode 100644 index 00000000..a16e8e8a Binary files /dev/null and b/src/main/resources/static/icon/debate_commission_icon.png differ diff --git a/src/main/resources/static/icon/government_icon.png b/src/main/resources/static/icon/government_icon.png new file mode 100644 index 00000000..27c93db6 Binary files /dev/null and b/src/main/resources/static/icon/government_icon.png differ diff --git a/src/main/resources/static/icon/han_alm_icon.png b/src/main/resources/static/icon/han_alm_icon.png new file mode 100644 index 00000000..1167f15d Binary files /dev/null and b/src/main/resources/static/icon/han_alm_icon.png differ diff --git a/src/main/resources/static/icon/hantomak_icon.png b/src/main/resources/static/icon/hantomak_icon.png new file mode 100644 index 00000000..9e4faee5 Binary files /dev/null and b/src/main/resources/static/icon/hantomak_icon.png differ diff --git a/src/main/resources/static/icon/igam_icon.png b/src/main/resources/static/icon/igam_icon.png new file mode 100644 index 00000000..65f8a591 Binary files /dev/null and b/src/main/resources/static/icon/igam_icon.png differ diff --git a/src/main/resources/static/icon/kogito_icon.png b/src/main/resources/static/icon/kogito_icon.png new file mode 100644 index 00000000..36292c6c Binary files /dev/null and b/src/main/resources/static/icon/kogito_icon.png differ diff --git a/src/main/resources/static/icon/kondae_time_icon.png b/src/main/resources/static/icon/kondae_time_icon.png new file mode 100644 index 00000000..f75c2b84 Binary files /dev/null and b/src/main/resources/static/icon/kondae_time_icon.png differ diff --git a/src/main/resources/static/icon/mcu_icon.png b/src/main/resources/static/icon/mcu_icon.png new file mode 100644 index 00000000..9ac43528 Binary files /dev/null and b/src/main/resources/static/icon/mcu_icon.png differ diff --git a/src/main/resources/static/icon/nogotte_icon.png b/src/main/resources/static/icon/nogotte_icon.png new file mode 100644 index 00000000..56ac3066 Binary files /dev/null and b/src/main/resources/static/icon/nogotte_icon.png differ diff --git a/src/main/resources/static/icon/osansi_icon.png b/src/main/resources/static/icon/osansi_icon.png new file mode 100644 index 00000000..5eba3fbb Binary files /dev/null and b/src/main/resources/static/icon/osansi_icon.png differ diff --git a/src/main/resources/static/icon/seobangjeongto_icon.png b/src/main/resources/static/icon/seobangjeongto_icon.png new file mode 100644 index 00000000..95c4daed Binary files /dev/null and b/src/main/resources/static/icon/seobangjeongto_icon.png differ diff --git a/src/main/resources/static/icon/todallae_icon.png b/src/main/resources/static/icon/todallae_icon.png new file mode 100644 index 00000000..45250602 Binary files /dev/null and b/src/main/resources/static/icon/todallae_icon.png differ diff --git a/src/main/resources/static/icon/visual_icon.png b/src/main/resources/static/icon/visual_icon.png new file mode 100644 index 00000000..1a74153f Binary files /dev/null and b/src/main/resources/static/icon/visual_icon.png differ diff --git a/src/main/resources/static/icon/yuppm_law_track_icon.png b/src/main/resources/static/icon/yuppm_law_track_icon.png new file mode 100644 index 00000000..ab1558a4 Binary files /dev/null and b/src/main/resources/static/icon/yuppm_law_track_icon.png differ diff --git a/src/test/java/com/debatetimer/controller/BaseControllerTest.java b/src/test/java/com/debatetimer/controller/BaseControllerTest.java index 504c5dcd..2c43b995 100644 --- a/src/test/java/com/debatetimer/controller/BaseControllerTest.java +++ b/src/test/java/com/debatetimer/controller/BaseControllerTest.java @@ -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; @@ -57,6 +59,12 @@ public abstract class BaseControllerTest { @Autowired protected VoteEntityGenerator voteEntityGenerator; + @Autowired + protected OrganizationEntityGenerator organizationEntityGenerator; + + @Autowired + protected OrganizationTemplateEntityGenerator organizationTemplateEntityGenerator; + @Autowired protected HeaderGenerator headerGenerator; diff --git a/src/test/java/com/debatetimer/controller/BaseDocumentTest.java b/src/test/java/com/debatetimer/controller/BaseDocumentTest.java index 0a74a3cc..bbac2b81 100644 --- a/src/test/java/com/debatetimer/controller/BaseDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/BaseDocumentTest.java @@ -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; @@ -70,6 +71,9 @@ public abstract class BaseDocumentTest { @MockitoBean protected VoteService voteService; + @MockitoBean + protected OrganizationService organizationService; + @MockitoBean protected AuthManager authManager; diff --git a/src/test/java/com/debatetimer/controller/GlobalControllerTest.java b/src/test/java/com/debatetimer/controller/GlobalControllerTest.java index 4e4cace9..6de1f29a 100644 --- a/src/test/java/com/debatetimer/controller/GlobalControllerTest.java +++ b/src/test/java/com/debatetimer/controller/GlobalControllerTest.java @@ -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 { @@ -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"); + } + } } diff --git a/src/test/java/com/debatetimer/controller/Tag.java b/src/test/java/com/debatetimer/controller/Tag.java index 39a3c588..5da09bb2 100644 --- a/src/test/java/com/debatetimer/controller/Tag.java +++ b/src/test/java/com/debatetimer/controller/Tag.java @@ -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; diff --git a/src/test/java/com/debatetimer/controller/organization/OrganizationControllerTest.java b/src/test/java/com/debatetimer/controller/organization/OrganizationControllerTest.java new file mode 100644 index 00000000..1c6cbd25 --- /dev/null +++ b/src/test/java/com/debatetimer/controller/organization/OrganizationControllerTest.java @@ -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) + ); + } + } +} diff --git a/src/test/java/com/debatetimer/controller/organization/OrganizationDocumentTest.java b/src/test/java/com/debatetimer/controller/organization/OrganizationDocumentTest.java new file mode 100644 index 00000000..1933b7db --- /dev/null +++ b/src/test/java/com/debatetimer/controller/organization/OrganizationDocumentTest.java @@ -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); + } + } +} diff --git a/src/test/java/com/debatetimer/service/BaseServiceTest.java b/src/test/java/com/debatetimer/service/BaseServiceTest.java index 3bd0a8c1..f7ba2145 100644 --- a/src/test/java/com/debatetimer/service/BaseServiceTest.java +++ b/src/test/java/com/debatetimer/service/BaseServiceTest.java @@ -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; @@ -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 threads = IntStream.range(0, count) .mapToObj(i -> new Thread(task)) diff --git a/src/test/java/com/debatetimer/service/organization/OrganizationServiceTest.java b/src/test/java/com/debatetimer/service/organization/OrganizationServiceTest.java new file mode 100644 index 00000000..86e0b251 --- /dev/null +++ b/src/test/java/com/debatetimer/service/organization/OrganizationServiceTest.java @@ -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(); + } + } +}