-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] 기관별 템플릿 조회 기능 구현 #228
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
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,26 @@ | ||
| package com.debatetimer.domain.organization; | ||
|
|
||
| import java.util.List; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class Organization { | ||
|
|
||
| private final Long id; | ||
| private final String name; | ||
| private final String affiliation; | ||
| private final String iconPath; | ||
| private final List<OrganizationTemplate> templates; | ||
|
|
||
| public Organization(Long id, | ||
| String name, | ||
| String affiliation, | ||
| String iconPath, | ||
| List<OrganizationTemplate> templates) { | ||
| this.id = id; | ||
| this.name = name; | ||
| this.affiliation = affiliation; | ||
| this.iconPath = iconPath; | ||
|
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. 단순 궁금증인데 iconPath 보면 아이콘들을 서버에서 관리할 것 같은데 대략적으로 어떻게 관리할 예정인가요?
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. |
||
| this.templates = templates; | ||
| } | ||
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.debatetimer.domain.organization; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class OrganizationTemplate { | ||
|
|
||
| private final Long id; | ||
| private final String name; | ||
| private final String data; | ||
|
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. 굳이 싶긴 하지만 확인해보니 Test에서도 실제 json을 인코딩한 값을 넣어주고 있던데 그러면 여기서 디코딩해서 json형식인지 검증하는 것도 괜찮아 보이네요
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. 이게 인코딩/디코딩 방식이 '특정 인코딩 방식' <-> BASE 64 <-> JSON 형식으로 이중으로 되어있어요. (BASE64로만 하면 너무 길다고 해서;;) |
||
|
|
||
| public OrganizationTemplate(Long id, String name, String data) { | ||
| this.id = id; | ||
| this.name = name; | ||
| this.data = data; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| 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; | ||
| import com.debatetimer.repository.organization.OrganizationRepository; | ||
| import com.debatetimer.repository.organization.OrganizationTemplateRepository; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class OrganizationDomainRepository { | ||
|
|
||
| private final OrganizationRepository organizationRepository; | ||
| private final OrganizationTemplateRepository organizationTemplateRepository; | ||
|
|
||
| public List<Organization> findAll() { | ||
| Map<Long, List<OrganizationTemplate>> idToTemplatesEntity = organizationTemplateRepository.findAll() | ||
| .stream() | ||
| .collect(groupingBy( | ||
| OrganizationTemplateEntity::getOrganizationId, | ||
| mapping(OrganizationTemplateEntity::toDomain, toList())) | ||
| ); | ||
|
|
||
| return organizationRepository.findAll() | ||
| .stream() | ||
| .map(entity -> entity.toDomain( | ||
| idToTemplatesEntity.getOrDefault(entity.getId(), Collections.emptyList())) | ||
| ).toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package com.debatetimer.entity.organization; | ||
|
|
||
| import com.debatetimer.domain.organization.Organization; | ||
| import com.debatetimer.domain.organization.OrganizationTemplate; | ||
| import com.debatetimer.entity.BaseTimeEntity; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import java.util.List; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Table(name = "organization") | ||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class OrganizationEntity extends BaseTimeEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @NotBlank | ||
| private String name; | ||
|
|
||
| @NotNull | ||
| private String affiliation; | ||
|
|
||
| @NotBlank | ||
| private String iconPath; | ||
|
|
||
| public OrganizationEntity(String name, String affiliation, String iconPath) { | ||
| this.name = name; | ||
| this.affiliation = affiliation; | ||
| this.iconPath = iconPath; | ||
| } | ||
|
|
||
| public Organization toDomain(List<OrganizationTemplate> templates) { | ||
| return new Organization(this.id, this.name, this.affiliation, this.iconPath, templates); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.debatetimer.entity.organization; | ||
|
|
||
|
|
||
| import com.debatetimer.domain.organization.OrganizationTemplate; | ||
| import com.debatetimer.entity.BaseTimeEntity; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.FetchType; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import jakarta.persistence.Table; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Table(name = "organization_template") | ||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class OrganizationTemplateEntity extends BaseTimeEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @NotNull | ||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "organization_id") | ||
| private OrganizationEntity organization; | ||
|
|
||
| @NotBlank | ||
| private String name; | ||
|
|
||
| @NotBlank | ||
| @Column(length = 8191) | ||
| private String data; | ||
|
Comment on lines
+39
to
+41
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. 제가 생각했던 방식은 서버에서 단순히 인코딩된 정보를 저장하고 반환만 하는 것이 아닌 인코딩/디코딩도 서버로 가져오고 테이블을 DB에 저장하는 것이였는데 나중에 토의해보면 좋을 것 같아요
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. 처음 테크독 작성할 때 피드백했으면 반영을 했을수도 있는데, 그렇지 않았어요. 이걸 옮기는 것은 내가 여유있을 때 진행하도록 합니다. (YAPP 제발 끝나라... 제발...) |
||
|
|
||
| public OrganizationTemplateEntity(OrganizationEntity organization, String name, String data) { | ||
| this.organization = organization; | ||
| this.name = name; | ||
| this.data = data; | ||
| } | ||
|
|
||
| public OrganizationTemplate toDomain() { | ||
| return new OrganizationTemplate(this.id, this.name, this.data); | ||
| } | ||
|
|
||
| public Long getOrganizationId() { | ||
| return this.organization.getId(); | ||
| } | ||
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.debatetimer.repository.organization; | ||
|
|
||
| import com.debatetimer.entity.organization.OrganizationEntity; | ||
| import java.util.List; | ||
| import org.springframework.data.repository.Repository; | ||
|
|
||
| public interface OrganizationRepository extends Repository<OrganizationEntity, Long> { | ||
|
|
||
| List<OrganizationEntity> findAll(); | ||
|
|
||
| OrganizationEntity save(OrganizationEntity organizationEntity); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.debatetimer.repository.organization; | ||
|
|
||
| import com.debatetimer.entity.organization.OrganizationTemplateEntity; | ||
| import java.util.List; | ||
| import org.springframework.data.repository.Repository; | ||
|
|
||
| public interface OrganizationTemplateRepository extends Repository<OrganizationTemplateEntity, Long> { | ||
|
|
||
| List<OrganizationTemplateEntity> findAll(); | ||
|
|
||
| OrganizationTemplateEntity save(OrganizationTemplateEntity entity); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| create table organization | ||
| ( | ||
| id bigint auto_increment, | ||
| name varchar(255) not null, | ||
| affiliation varchar(255) not null, | ||
| icon_path varchar(255) not null, | ||
| created_at timestamp not null DEFAULT CURRENT_TIMESTAMP, | ||
| modified_at timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | ||
| primary key (id) | ||
| ); | ||
|
|
||
| create table organization_template | ||
| ( | ||
| id bigint auto_increment, | ||
| name varchar(255) not null, | ||
| data varchar(8191) not null, | ||
| organization_id bigint not null, | ||
| created_at timestamp not null DEFAULT CURRENT_TIMESTAMP, | ||
| modified_at timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | ||
| primary key (id) | ||
| ); | ||
|
|
||
| alter table organization_template | ||
| add constraint organization_template_to_organization | ||
| foreign key (organization_id) | ||
| references organization (id); | ||
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||
| package com.debatetimer.domainrepository.organization; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||||||||||||||||||||||
| import static org.junit.jupiter.api.Assertions.assertAll; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import com.debatetimer.domain.organization.Organization; | ||||||||||||||||||||||||
| import com.debatetimer.domainrepository.BaseDomainRepositoryTest; | ||||||||||||||||||||||||
| import com.debatetimer.entity.organization.OrganizationEntity; | ||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||
| import org.junit.jupiter.api.Nested; | ||||||||||||||||||||||||
| import org.junit.jupiter.api.Test; | ||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class OrganizationDomainRepositoryTest extends BaseDomainRepositoryTest { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Autowired | ||||||||||||||||||||||||
| private OrganizationDomainRepository organizationDomainRepository; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Nested | ||||||||||||||||||||||||
leegwichan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
| class FindAll { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||
| void 모든_조직_템플릿을_가져온다() { | ||||||||||||||||||||||||
| OrganizationEntity organization1 = organizationEntityGenerator.generate("한앎", "한양대"); | ||||||||||||||||||||||||
| OrganizationEntity organization2 = organizationEntityGenerator.generate("한모름", "양한대"); | ||||||||||||||||||||||||
| organizationTemplateEntityGenerator.generate(organization1, "템플릿1"); | ||||||||||||||||||||||||
| organizationTemplateEntityGenerator.generate(organization1, "템플릿2"); | ||||||||||||||||||||||||
| organizationTemplateEntityGenerator.generate(organization2, "릿플템1"); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| List<Organization> organizations = organizationDomainRepository.findAll(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| assertAll( | ||||||||||||||||||||||||
| () -> assertThat(organizations).hasSize(2), | ||||||||||||||||||||||||
| () -> assertThat(organizations.get(0).getTemplates()).hasSize(2), | ||||||||||||||||||||||||
| () -> assertThat(organizations.get(1).getTemplates()).hasSize(1) | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Comment on lines
+32
to
+36
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. 테스트 결과 검증이 순서에 의존적입니다.
순서에 의존하지 않는 검증으로 개선하는 것을 권장합니다: assertAll(
() -> assertThat(organizations).hasSize(2),
- () -> assertThat(organizations.get(0).getTemplates()).hasSize(2),
- () -> assertThat(organizations.get(1).getTemplates()).hasSize(1)
+ () -> assertThat(organizations)
+ .extracting(org -> org.getTemplates().size())
+ .containsExactlyInAnyOrder(2, 1)
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.debatetimer.fixture.entity; | ||
|
|
||
| import com.debatetimer.entity.organization.OrganizationEntity; | ||
| import com.debatetimer.repository.organization.OrganizationRepository; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class OrganizationEntityGenerator { | ||
|
|
||
| private static final String DEFAULT_ICON_PATH = "/static/icons/default_icon.png"; | ||
|
|
||
| private final OrganizationRepository organizationRepository; | ||
|
|
||
| public OrganizationEntityGenerator(OrganizationRepository organizationRepository) { | ||
| this.organizationRepository = organizationRepository; | ||
| } | ||
|
|
||
| public OrganizationEntity generate(String name, String affiliation) { | ||
| OrganizationEntity organization = new OrganizationEntity(name, affiliation, DEFAULT_ICON_PATH); | ||
| return organizationRepository.save(organization); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.debatetimer.fixture.entity; | ||
|
|
||
| import com.debatetimer.entity.organization.OrganizationEntity; | ||
| import com.debatetimer.entity.organization.OrganizationTemplateEntity; | ||
| import com.debatetimer.repository.organization.OrganizationTemplateRepository; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class OrganizationTemplateEntityGenerator { | ||
|
|
||
| private static final String DEFAULT_TEMPLATE_CONTENT = "eJyrVspMUbIytjDXUcrMS8tXsqpWykvMTVWyUjJWKCtWMFZ427b1TXPj27YFrxcueN3T8HZWj8LbGVPfdM9V0lEqqSwAqXQODQ7x9%2FWMcgUKJaan5qUkAgWB7IKi%2FOKQ1MRcP4iBbzasedOyESienJ%2BHLP56wwygwUDx8sSivMy8dKfUnBwlq7TEnOJUHaW0zLzM4gwkoVqgtYlJOUCN0dVKxSWJeckgMwKC%2FIOBJhQXpKYmZ4RAnPVmXivQzUDRpPwKqJCff5Cvow%2FI5Zkgqw2NDICyYLPzSnNyIMIBqUUgx6EJBRekJmYDHQcTLgbxU4sg3FodJKc4%2B%2FsNFqf4uYaGBIEtQXPNhDdzFkCiFMVNIZ6%2BrvFOjsGuLnB3QazA6TBzkLMx3AX2DIkhtHXOm0WtCq83zHkzbQfdAwpr8qG7i2JrAbdLRw0%3D"; | ||
|
|
||
| private final OrganizationTemplateRepository organizationTemplateRepository; | ||
|
|
||
| public OrganizationTemplateEntityGenerator(OrganizationTemplateRepository organizationTemplateRepository) { | ||
| this.organizationTemplateRepository = organizationTemplateRepository; | ||
| } | ||
|
|
||
| public OrganizationTemplateEntity generate(OrganizationEntity organization, String name) { | ||
| OrganizationTemplateEntity template = | ||
| new OrganizationTemplateEntity(organization, name, DEFAULT_TEMPLATE_CONTENT); | ||
| return organizationTemplateRepository.save(template); | ||
| } | ||
| } |

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.
혹시, Lombok 안쓴 이유가 따로 있나요?
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.
@RequiresArgsConstructor는 Controller, Service, DomainRepository와 같은 계층형 객체에만 사용하기로 했었습니다. 도메인 객체에는 생성자에 String, int와 같은 전형적인 타입이 들어오기 때문에 일부러 사용하지 않았습니다. (그렇게 합의하기로 했던 것 같습니다.)