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,26 @@
package com.debatetimer.domain.organization;

import java.util.List;
import lombok.Getter;

@Getter
public class Organization {
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시, Lombok 안쓴 이유가 따로 있나요?

Suggested change
public class Organization {
@RequiresArgsConstructor
public class Organization {

Copy link
Member Author

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와 같은 전형적인 타입이 들어오기 때문에 일부러 사용하지 않았습니다. (그렇게 합의하기로 했던 것 같습니다.)
image


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

Choose a reason for hiding this comment

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

단순 궁금증인데 iconPath 보면 아이콘들을 서버에서 관리할 것 같은데 대략적으로 어떻게 관리할 예정인가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

git으로 같이 관리할 예정입니다.
Image

this.templates = templates;
}
}
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;
Copy link
Contributor

Choose a reason for hiding this comment

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

굳이 싶긴 하지만 확인해보니 Test에서도 실제 json을 인코딩한 값을 넣어주고 있던데 그러면 여기서 디코딩해서 json형식인지 검증하는 것도 괜찮아 보이네요

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

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

제가 생각했던 방식은 서버에서 단순히 인코딩된 정보를 저장하고 반환만 하는 것이 아닌 인코딩/디코딩도 서버로 가져오고 테이블을 DB에 저장하는 것이였는데 나중에 토의해보면 좋을 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

The 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();
}
}
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);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,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 @@ -49,6 +51,12 @@ public abstract class BaseDomainRepositoryTest {
@Autowired
protected VoteEntityGenerator voteEntityGenerator;

@Autowired
protected OrganizationEntityGenerator organizationEntityGenerator;

@Autowired
protected OrganizationTemplateEntityGenerator organizationTemplateEntityGenerator;

@Autowired
protected PollRepository pollRepository;

Expand Down
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
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
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

테스트 결과 검증이 순서에 의존적입니다.

organizations.get(0)organizations.get(1)의 순서가 보장되지 않을 수 있습니다. 데이터베이스 또는 쿼리 구현에 따라 순서가 달라질 수 있어 테스트가 불안정해질 수 있습니다.

순서에 의존하지 않는 검증으로 개선하는 것을 권장합니다:

             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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assertAll(
() -> assertThat(organizations).hasSize(2),
() -> assertThat(organizations.get(0).getTemplates()).hasSize(2),
() -> assertThat(organizations.get(1).getTemplates()).hasSize(1)
);
assertAll(
() -> assertThat(organizations).hasSize(2),
() -> assertThat(organizations)
.extracting(org -> org.getTemplates().size())
.containsExactlyInAnyOrder(2, 1)
);
🤖 Prompt for AI Agents
In
src/test/java/com/debatetimer/domainrepository/organization/OrganizationDomainRepositoryTest.java
around lines 32-36, the assertions rely on organizations.get(0)/get(1) order
which may be non-deterministic; make the test order-independent by either
sorting the organizations list by a stable key (e.g., id or name) before
asserting template sizes, or by asserting on the multiset of template counts
(extract template sizes and assert it contains 2 and 1 in any order) so the test
no longer depends on element order.

}
}
}
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);
}
}