diff --git a/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java b/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java index 1747c700e..c329e46c9 100644 --- a/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java +++ b/backend/src/acceptanceTest/java/wooteco/prolog/steps/GroupMemberStepDefinitions.java @@ -2,37 +2,39 @@ import io.cucumber.java.en.Given; import wooteco.prolog.AcceptanceSteps; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.*; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.member.domain.repository.MemberRepository; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; + public class GroupMemberStepDefinitions extends AcceptanceSteps { private final MemberRepository memberRepository; - private final MemberGroupRepository memberGroupRepository; - private final GroupMemberRepository groupMemberRepository; + private final DepartmentRepository departmentRepository; + private final DepartmentMemberRepository departmentMemberRepository; public GroupMemberStepDefinitions(MemberRepository memberRepository, - MemberGroupRepository memberGroupRepository, - GroupMemberRepository groupMemberRepository) { + DepartmentRepository departmentRepository, + DepartmentMemberRepository departmentMemberRepository) { this.memberRepository = memberRepository; - this.memberGroupRepository = memberGroupRepository; - this.groupMemberRepository = groupMemberRepository; + this.departmentRepository = departmentRepository; + this.departmentMemberRepository = departmentMemberRepository; } @Given("{string}을 멤버그룹과 그룹멤버에 등록하고") public void 그룹멤버를_생성하고(String title) { Member member = memberRepository.findById(1L).get(); - MemberGroup 프론트엔드 = memberGroupRepository.save( - new MemberGroup(null, "4기 프론트엔드", "4기 프론트엔드 설명")); - MemberGroup 백엔드 = memberGroupRepository.save(new MemberGroup(null, "4기 백엔드", "4기 백엔드 설명")); - MemberGroup 안드로이드 = memberGroupRepository.save( - new MemberGroup(null, "4기 안드로이드", "4기 안드로이드 설명")); - groupMemberRepository.save(new GroupMember(null, member, 백엔드)); - groupMemberRepository.save(new GroupMember(null, member, 프론트엔드)); - groupMemberRepository.save(new GroupMember(null, member, 안드로이드)); + Department 프론트엔드 = departmentRepository.save( + new Department(null, FRONTEND, FOURTH)); + Department 백엔드 = departmentRepository.save(new Department(null, BACKEND, FOURTH)); + Department 안드로이드 = departmentRepository.save( + new Department(null, ANDROID, FOURTH)); + departmentMemberRepository.save(new DepartmentMember(null, member, 백엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 프론트엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 안드로이드)); } + } diff --git a/backend/src/acceptanceTest/resources/wooteco/prolog/article.feature b/backend/src/acceptanceTest/resources/wooteco/prolog/article.feature index 50026d76e..ad5dfce3c 100644 --- a/backend/src/acceptanceTest/resources/wooteco/prolog/article.feature +++ b/backend/src/acceptanceTest/resources/wooteco/prolog/article.feature @@ -13,7 +13,6 @@ Feature: 아티클 관련 기능 # When 아티클을 전체 조회 하면 # Then 아티클이 전체 조회 된다 - Scenario: 아티클을 수정하기 Given 아티클을 여러개 작성하고 When 1번 아티클을 수정하면 diff --git a/backend/src/documentation/java/wooteco/prolog/Documentation.java b/backend/src/documentation/java/wooteco/prolog/Documentation.java index 60b90313f..09da9ae88 100644 --- a/backend/src/documentation/java/wooteco/prolog/Documentation.java +++ b/backend/src/documentation/java/wooteco/prolog/Documentation.java @@ -8,7 +8,9 @@ import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; +import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; +import java.util.HashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -45,6 +47,15 @@ public void setUp(RestDocumentationContextProvider restDocumentation) { .then().log().all() .extract().body().as(TokenResponse.class); + HashMap data = new HashMap<>(); + data.put("role", "CREW"); + + final RequestSpecification requestSpecification = RestAssured + .given().log().all() + .body(data).contentType(ContentType.JSON) + .auth().oauth2(로그인_사용자.getAccessToken()); + requestSpecification.patch("/members/" + 1 + "/role"); + this.spec = new RequestSpecBuilder() .addFilter(documentationConfiguration(restDocumentation)) .build(); diff --git a/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java b/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java index 96e3341c5..e88cc6334 100644 --- a/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java +++ b/backend/src/documentation/java/wooteco/prolog/docu/StudylogDocumentation.java @@ -1,6 +1,8 @@ package wooteco.prolog.docu; import static org.assertj.core.api.Assertions.assertThat; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -16,11 +18,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import wooteco.prolog.Documentation; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.*; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.member.domain.repository.MemberRepository; import wooteco.prolog.session.application.dto.MissionRequest; import wooteco.prolog.session.application.dto.MissionResponse; @@ -37,9 +37,9 @@ class StudylogDocumentation extends Documentation { @Autowired private MemberRepository memberRepository; @Autowired - private MemberGroupRepository memberGroupRepository; + private DepartmentRepository departmentRepository; @Autowired - private GroupMemberRepository groupMemberRepository; + private DepartmentMemberRepository departmentMemberRepository; @Test void 스터디로그를_생성한다() { @@ -312,14 +312,14 @@ private StudylogRequest createStudylogRequest3() { private void 회원과_멤버그룹_그룹멤버를_등록함() { Member member = memberRepository.findById(1L).get(); - MemberGroup 프론트엔드 = memberGroupRepository.save( - new MemberGroup(null, "4기 프론트엔드", "4기 프론트엔드 설명")); - MemberGroup 백엔드 = memberGroupRepository.save(new MemberGroup(null, "4기 백엔드", "4기 백엔드 설명")); - MemberGroup 안드로이드 = memberGroupRepository.save( - new MemberGroup(null, "4기 안드로이드", "4기 안드로이드 설명")); - groupMemberRepository.save(new GroupMember(null, member, 백엔드)); - groupMemberRepository.save(new GroupMember(null, member, 프론트엔드)); - groupMemberRepository.save(new GroupMember(null, member, 안드로이드)); + Department 프론트엔드 = departmentRepository.save( + new Department(null, FRONTEND, FOURTH)); + Department 백엔드 = departmentRepository.save(new Department(null, BACKEND, FOURTH)); + Department 안드로이드 = departmentRepository.save( + new Department(null, ANDROID, FOURTH)); + departmentMemberRepository.save(new DepartmentMember(null, member, 백엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 프론트엔드)); + departmentMemberRepository.save(new DepartmentMember(null, member, 안드로이드)); } private Long 미션_등록함(MissionRequest request) { diff --git a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java index 4dd8f2945..82ea51cff 100644 --- a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java +++ b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java @@ -86,13 +86,13 @@ public List getFilteredArticles(final LoginMember member, final ArticleFilterType course, final boolean onlyBookmarked) { if (member.isMember()) { - return articleRepository.findArticlesByCourseAndMember(course.getGroupName(), + return articleRepository.findArticlesByCourseAndMember(course.getPartName(), member.getId(), onlyBookmarked).stream() .map(article -> ArticleResponse.of(article, member.getId())) .collect(toList()); } - return articleRepository.findArticlesByCourse(course.getGroupName()).stream() + return articleRepository.findArticlesByCourse(course.getPartName()).stream() .map(article -> ArticleResponse.of(article, member.getId())) .collect(toList()); } diff --git a/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java b/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java index 8eddb65f6..94913a001 100644 --- a/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java +++ b/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java @@ -9,9 +9,9 @@ public enum ArticleFilterType { BACKEND("백엔드"), FRONTEND("프론트엔드"); - private final String groupName; + private final String partName; - ArticleFilterType(String groupName) { - this.groupName = groupName; + ArticleFilterType(String partName) { + this.partName = partName; } } diff --git a/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java b/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java index 2e02f23ea..69360be64 100644 --- a/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java +++ b/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java @@ -18,18 +18,18 @@ public interface ArticleRepository extends JpaRepository { Optional
findFetchLikeById(@Param("id") final Long id); @Query("SELECT DISTINCT a FROM Article a " + - "JOIN GroupMember gm ON a.member.id = gm.member.id " + - "JOIN gm.group mg " + - "WHERE mg.name LIKE %:course " + + "JOIN DepartmentMember dm ON dm.member.id = dm.member.id " + + "JOIN dm.department d " + + "WHERE d.part = :course " + "ORDER by a.createdAt desc") List
findArticlesByCourse(@Param("course") String course); @Query("SELECT DISTINCT a FROM Article a " + - "JOIN GroupMember gm ON a.member.id = gm.member.id " + - "JOIN gm.group mg " + + "JOIN DepartmentMember dm ON a.member.id = dm.member.id " + + "JOIN dm.department d " + "LEFT JOIN a.articleBookmarks.articleBookmarks ab " + "LEFT JOIN a.articleLikes.articleLikes al " + - "WHERE mg.name LIKE %:course AND (:onlyBookmarked = false OR (:onlyBookmarked = true and ab.memberId = :memberId))" + + "WHERE d.part = :course AND (:onlyBookmarked = false OR (:onlyBookmarked = true and ab.memberId = :memberId))" + "ORDER by a.createdAt desc") List
findArticlesByCourseAndMember(@Param("course") String course, @Param("memberId") Long memberId, diff --git a/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java b/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java index cafc1458f..25a47e25a 100644 --- a/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java +++ b/backend/src/main/java/wooteco/prolog/common/exception/BadRequestCode.java @@ -94,7 +94,7 @@ public enum BadRequestCode { ARTICLE_IMAGE_URL_OVER_LENGTH_EXCEPTION(12007, "ARTICLE_IMAGE_URL_OVER_LENGTH_EXCEPTION"), ARTICLE_INVALID_URL_EXCEPTION(12008, "ARTICLE_INVALID_URL_EXCEPTION"), UNVALIDATED_MEMBER_EXCEPTION(12009, "UNVALIDATED_MEMBER_EXCEPTION"); - + private int code; private String message; } diff --git a/backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java b/backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java new file mode 100644 index 000000000..80fd7d323 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java @@ -0,0 +1,21 @@ +package wooteco.prolog.member.application; + +import java.util.List; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import wooteco.prolog.member.domain.DepartmentMember; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; + +@Service +@AllArgsConstructor +@Transactional(readOnly = true) +public class DepartmentMemberService { + + private DepartmentMemberRepository departmentMemberRepository; + + public List findDepartmentMemberByDepartmentId(Long departmentId) { + return departmentMemberRepository.findByDepartmentId(departmentId); + } + +} diff --git a/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java b/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java deleted file mode 100644 index cd50ced04..000000000 --- a/backend/src/main/java/wooteco/prolog/member/application/GroupMemberService.java +++ /dev/null @@ -1,20 +0,0 @@ -package wooteco.prolog.member.application; - -import java.util.List; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; - -@Service -@AllArgsConstructor -@Transactional(readOnly = true) -public class GroupMemberService { - - private GroupMemberRepository groupMemberRepository; - - public List findGroupMemberByGroupId(Long groupId) { - return groupMemberRepository.findByGroupId(groupId); - } -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Department.java b/backend/src/main/java/wooteco/prolog/member/domain/Department.java new file mode 100644 index 000000000..23fe87c6a --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Department.java @@ -0,0 +1,40 @@ +package wooteco.prolog.member.domain; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Department { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(value = EnumType.STRING) + private Part part; + + @Enumerated(value = EnumType.STRING) + private Term term; + + public Department(Long id, Part part, Term term) { + this.id = id; + this.part = part; + this.term = term; + } + + public Department(Long id, String part, String term) { + this.id = id; + this.part = Part.getPartByName(part); + this.term = Term.getTermByName(term); + } + + public Part getPart() { + return part; + } + +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/GroupMember.java b/backend/src/main/java/wooteco/prolog/member/domain/DepartmentMember.java similarity index 75% rename from backend/src/main/java/wooteco/prolog/member/domain/GroupMember.java rename to backend/src/main/java/wooteco/prolog/member/domain/DepartmentMember.java index 3c6797a56..690ecad41 100644 --- a/backend/src/main/java/wooteco/prolog/member/domain/GroupMember.java +++ b/backend/src/main/java/wooteco/prolog/member/domain/DepartmentMember.java @@ -14,7 +14,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class GroupMember { +public class DepartmentMember { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -25,12 +25,12 @@ public class GroupMember { private Member member; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "group_id", nullable = false) - private MemberGroup group; + @JoinColumn(name = "department_id", nullable = false) + private Department department; - public GroupMember(Long id, Member member, MemberGroup group) { + public DepartmentMember(Long id, Member member, Department department) { this.id = id; this.member = member; - this.group = group; + this.department = department; } } diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Departments.java b/backend/src/main/java/wooteco/prolog/member/domain/Departments.java new file mode 100644 index 000000000..04ab1103e --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Departments.java @@ -0,0 +1,16 @@ +package wooteco.prolog.member.domain; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class Departments { + + private List values; + + public boolean isContainsDepartments(Department department) { + return values.contains(department); + } +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroup.java b/backend/src/main/java/wooteco/prolog/member/domain/MemberGroup.java deleted file mode 100644 index f45b9dc3f..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroup.java +++ /dev/null @@ -1,47 +0,0 @@ -package wooteco.prolog.member.domain; - -import static wooteco.prolog.common.exception.BadRequestCode.CANT_FIND_GROUP_TYPE; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import wooteco.prolog.common.exception.BadRequestCode; -import wooteco.prolog.common.exception.BadRequestException; - -@Entity -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -public class MemberGroup { - - private static final String BACKEND = "백엔드"; - private static final String FRONTEND = "프론트엔드"; - private static final String ANDROID = "안드로이드"; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - - private String description; - - public MemberGroup(Long id, String name, String description) { - this.id = id; - this.name = name; - this.description = description; - } - - public MemberGroupType groupType() { - for (MemberGroupType groupType : MemberGroupType.values()) { - if (groupType.isContainedBy(this.name)) { - return groupType; - } - } - throw new BadRequestException(CANT_FIND_GROUP_TYPE); - } - -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java b/backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java deleted file mode 100644 index c17b9e9ae..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroupType.java +++ /dev/null @@ -1,23 +0,0 @@ -package wooteco.prolog.member.domain; - -import lombok.Getter; - -@Getter -public enum MemberGroupType { - ANDROID("안드로이드"), - BACKEND("백엔드"), - FRONTEND("프론트엔드"); - - private final String groupName; - - MemberGroupType(String groupName) { - this.groupName = groupName; - } - - public boolean isContainedBy(String value) { - if (value == null) { - return false; - } - return value.contains(this.groupName); - } -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java b/backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java deleted file mode 100644 index 9c9424779..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/MemberGroups.java +++ /dev/null @@ -1,16 +0,0 @@ -package wooteco.prolog.member.domain; - -import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public class MemberGroups { - - private List values; - - public boolean isContainsMemberGroups(GroupMember groupMember) { - return values.contains(groupMember.getGroup()); - } -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Part.java b/backend/src/main/java/wooteco/prolog/member/domain/Part.java new file mode 100644 index 000000000..7f6960af6 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Part.java @@ -0,0 +1,24 @@ +package wooteco.prolog.member.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum Part { + + BACKEND("백엔드"), + FRONTEND("프론트엔드"), + ANDROID("안드로이드"); + + private final String name; + + public static Part getPartByName(String name) { + return Arrays.stream(values()) + .filter(part -> part.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("name과 일치하는 part가 존재하지 않습니다.")); + } +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/Term.java b/backend/src/main/java/wooteco/prolog/member/domain/Term.java new file mode 100644 index 000000000..162b67402 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/Term.java @@ -0,0 +1,27 @@ +package wooteco.prolog.member.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum Term { + + FIRST("1기"), + SECOND("2기"), + THIRD("3기"), + FOURTH("4기"), + FIFTH("5기"), + SIXTH("6기"); + + private final String name; + + public static Term getTermByName(String name) { + return Arrays.stream(values()) + .filter(term -> term.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("name과 일치하는 term이 존재하지 않습니다.")); + } +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java new file mode 100644 index 000000000..b574f4386 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java @@ -0,0 +1,14 @@ +package wooteco.prolog.member.domain.repository; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import wooteco.prolog.member.domain.DepartmentMember; +import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Department; + +public interface DepartmentMemberRepository extends JpaRepository { + + List findByDepartmentId(Long departmentId); + + boolean existsDepartmentMemberByMemberAndDepartment(Member member, Department department); +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java new file mode 100644 index 000000000..08314f19d --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java @@ -0,0 +1,8 @@ +package wooteco.prolog.member.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import wooteco.prolog.member.domain.Department; + +public interface DepartmentRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java deleted file mode 100644 index 5b2c76f1d..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/repository/GroupMemberRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package wooteco.prolog.member.domain.repository; - -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; - -public interface GroupMemberRepository extends JpaRepository { - - List findByGroupId(Long groupId); - - boolean existsGroupMemberByMemberAndGroup(Member member, MemberGroup memberGroup); -} diff --git a/backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java b/backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java deleted file mode 100644 index c98165eb2..000000000 --- a/backend/src/main/java/wooteco/prolog/member/domain/repository/MemberGroupRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package wooteco.prolog.member.domain.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import wooteco.prolog.member.domain.MemberGroup; - -public interface MemberGroupRepository extends JpaRepository { - -} diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/EssayAnswerService.java b/backend/src/main/java/wooteco/prolog/roadmap/application/EssayAnswerService.java index 3042eab57..1b2943dc4 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/EssayAnswerService.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/EssayAnswerService.java @@ -2,6 +2,7 @@ import static wooteco.prolog.common.exception.BadRequestCode.CURRICULUM_NOT_FOUND_EXCEPTION; import static wooteco.prolog.common.exception.BadRequestCode.ESSAY_ANSWER_NOT_FOUND_EXCEPTION; +import static wooteco.prolog.common.exception.BadRequestCode.MEMBER_NOT_ALLOWED; import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_QUIZ_NOT_FOUND_EXCEPTION; import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION; @@ -16,6 +17,7 @@ import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Role; import wooteco.prolog.roadmap.application.dto.EssayAnswerRequest; import wooteco.prolog.roadmap.application.dto.EssayAnswerSearchRequest; import wooteco.prolog.roadmap.application.dto.EssayAnswerUpdateRequest; @@ -30,6 +32,7 @@ import wooteco.prolog.session.domain.repository.SessionRepository; import wooteco.prolog.studylog.application.dto.EssayAnswersResponse; + @Transactional @Service public class EssayAnswerService { @@ -60,12 +63,19 @@ public Long createEssayAnswer(EssayAnswerRequest essayAnswerRequest, Long member .orElseThrow(() -> new BadRequestException(ROADMAP_QUIZ_NOT_FOUND_EXCEPTION)); Member member = memberService.findById(memberId); + validateMemberIsCrew(member); EssayAnswer essayAnswer = new EssayAnswer(quiz, essayAnswerRequest.getAnswer(), member); essayAnswerRepository.save(essayAnswer); return essayAnswer.getId(); } + private void validateMemberIsCrew(final Member member) { + if (member.hasLowerImportanceRoleThan(Role.CREW)) { + throw new BadRequestException(MEMBER_NOT_ALLOWED); + } + } + @Transactional public void updateEssayAnswer(Long answerId, EssayAnswerUpdateRequest request, Long memberId) { EssayAnswer essayAnswer = getById(answerId); @@ -129,7 +139,8 @@ public EssayAnswersResponse searchEssayAnswers( .and(EssayAnswerSpecification.inMemberIds(request.getMemberIds())) .and(EssayAnswerSpecification.orderByIdDesc()); - final Page essayAnswers = essayAnswerRepository.findAll(essayAnswerSpecification, + final Page essayAnswers = essayAnswerRepository.findAll( + essayAnswerSpecification, pageable); return EssayAnswersResponse.of(essayAnswers); } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/KeywordService.java b/backend/src/main/java/wooteco/prolog/roadmap/application/KeywordService.java index b7dd4b696..b76335c4c 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/KeywordService.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/KeywordService.java @@ -1,9 +1,5 @@ package wooteco.prolog.roadmap.application; -import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION; -import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION; - -import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import wooteco.prolog.common.exception.BadRequestException; @@ -15,6 +11,12 @@ import wooteco.prolog.roadmap.domain.repository.KeywordRepository; import wooteco.prolog.session.domain.repository.SessionRepository; +import java.util.List; + +import static java.util.Collections.emptyMap; +import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION; +import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION; + @Transactional @Service public class KeywordService { @@ -23,7 +25,7 @@ public class KeywordService { private final KeywordRepository keywordRepository; public KeywordService(final SessionRepository sessionRepository, - final KeywordRepository keywordRepository) { + final KeywordRepository keywordRepository) { this.sessionRepository = sessionRepository; this.keywordRepository = keywordRepository; } @@ -65,7 +67,7 @@ public KeywordResponse findKeywordWithAllChild(final Long sessionId, final Long Keyword keyword = keywordRepository.findFetchByIdOrderBySeq(keywordId); - return KeywordResponse.createWithAllChildResponse(keyword); + return KeywordResponse.createWithAllChildResponse(keyword, emptyMap(), emptyMap()); } @Transactional(readOnly = true) @@ -74,7 +76,7 @@ public KeywordResponse newFindKeywordWithAllChild(final Long keywordId) { Keyword keyword = keywordRepository.findFetchByIdOrderBySeq(keywordId); - return KeywordResponse.createWithAllChildResponse(keyword); + return KeywordResponse.createWithAllChildResponse(keyword, emptyMap(), emptyMap()); } @Transactional(readOnly = true) @@ -83,14 +85,14 @@ public KeywordsResponse findSessionIncludeRootKeywords(final Long sessionId) { List keywords = keywordRepository.findBySessionIdAndParentIsNull(sessionId); - return KeywordsResponse.createResponse(keywords); + return KeywordsResponse.of(keywords, emptyMap(), emptyMap()); } @Transactional(readOnly = true) public KeywordsResponse newFindSessionIncludeRootKeywords() { List keywords = keywordRepository.newFindByParentIsNull(); - return KeywordsResponse.createResponse(keywords); + return KeywordsResponse.of(keywords, emptyMap(), emptyMap()); } public void updateKeyword( diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java b/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java index 96215d86e..bff726073 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java @@ -1,122 +1,47 @@ package wooteco.prolog.roadmap.application; -import java.util.Comparator; -import java.util.HashSet; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import wooteco.prolog.common.exception.BadRequestException; -import wooteco.prolog.roadmap.application.dto.KeywordResponse; import wooteco.prolog.roadmap.application.dto.KeywordsResponse; -import wooteco.prolog.roadmap.application.dto.RecommendedPostResponse; -import wooteco.prolog.roadmap.domain.Curriculum; -import wooteco.prolog.roadmap.domain.EssayAnswer; import wooteco.prolog.roadmap.domain.Keyword; -import wooteco.prolog.roadmap.domain.Quiz; -import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; -import wooteco.prolog.roadmap.domain.repository.EssayAnswerRepository; import wooteco.prolog.roadmap.domain.repository.KeywordRepository; -import wooteco.prolog.roadmap.domain.repository.QuizRepository; -import wooteco.prolog.session.domain.Session; -import wooteco.prolog.session.domain.repository.SessionRepository; +import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndAnsweredQuizCount; +import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndTotalQuizCount; import java.util.List; import java.util.Map; -import java.util.Set; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static wooteco.prolog.common.exception.BadRequestCode.CURRICULUM_NOT_FOUND_EXCEPTION; +import static java.util.stream.Collectors.toMap; @RequiredArgsConstructor -@Transactional +@Transactional(readOnly = true) @Service public class RoadMapService { - private final CurriculumRepository curriculumRepository; - private final SessionRepository sessionRepository; private final KeywordRepository keywordRepository; - private final QuizRepository quizRepository; - private final EssayAnswerRepository essayAnswerRepository; - @Transactional(readOnly = true) public KeywordsResponse findAllKeywordsWithProgress(final Long curriculumId, final Long memberId) { - final Curriculum curriculum = curriculumRepository.findById(curriculumId) - .orElseThrow(() -> new BadRequestException(CURRICULUM_NOT_FOUND_EXCEPTION)); + final List keywords = keywordRepository.findAllByCurriculumId(curriculumId); + final Map totalQuizCounts = getTotalQuizCounts(); + final Map answeredQuizCounts = getAnsweredQuizCounts(memberId); - final List keywordsInCurriculum = getKeywordsInCurriculum(curriculum); - - final Map> quizzesInKeywords = quizRepository.findAll().stream() - .collect(groupingBy(Quiz::getKeyword, toSet())); - - return createResponsesWithProgress(keywordsInCurriculum, quizzesInKeywords, getDoneQuizzes(memberId)); - } - - private Set getDoneQuizzes(final Long memberId) { - return essayAnswerRepository.findAllByMemberId(memberId).stream() - .map(EssayAnswer::getQuiz) - .collect(toSet()); - } - - private List getKeywordsInCurriculum(final Curriculum curriculum) { - final Set sessionIds = sessionRepository.findAllByCurriculumId(curriculum.getId()) - .stream() - .map(Session::getId) - .collect(toSet()); - - return keywordRepository.findBySessionIdIn(sessionIds); - } - - private KeywordsResponse createResponsesWithProgress(final List keywords, - final Map> quizzesPerKeyword, - final Set doneQuizzes) { - final List keywordResponses = keywords.stream() - .filter(Keyword::isRoot) - .map(keyword -> createResponseWithProgress(keyword, quizzesPerKeyword, doneQuizzes)) - .sorted(Comparator.comparing(KeywordResponse::getKeywordId)) - .collect(toList()); - - return new KeywordsResponse(keywordResponses); - } - - private KeywordResponse createResponseWithProgress(final Keyword keyword, - final Map> quizzesPerKeyword, - final Set doneQuizzes) { - final int totalQuizCount = quizzesPerKeyword.getOrDefault(keyword, new HashSet<>()).size(); - final int doneQuizCount = getDoneQuizCount( - quizzesPerKeyword.getOrDefault(keyword, new HashSet<>()), doneQuizzes); - - final List recommendedPostResponses = keyword.getRecommendedPosts().stream() - .map(RecommendedPostResponse::from) - .collect(toList()); - - return new KeywordResponse( - keyword.getId(), - keyword.getName(), - keyword.getDescription(), - keyword.getSeq(), - keyword.getImportance(), - totalQuizCount, - doneQuizCount, - keyword.getParentIdOrNull(), - recommendedPostResponses, - createChildrenWithProgress(keyword.getChildren(), quizzesPerKeyword, doneQuizzes) - ); + return KeywordsResponse.of(keywords, totalQuizCounts, answeredQuizCounts); } - private int getDoneQuizCount(final Set quizzes, final Set doneQuizzes) { - quizzes.retainAll(doneQuizzes); - return quizzes.size(); + private Map getTotalQuizCounts() { + return keywordRepository.findTotalQuizCount().stream() + .collect( + toMap( + KeywordIdAndTotalQuizCount::getKeywordId, + KeywordIdAndTotalQuizCount::getTotalQuizCount)); } - private List createChildrenWithProgress(final Set children, - final Map> quizzesPerKeyword, - final Set userAnswers) { - return children.stream() - .map(child -> createResponseWithProgress(child, quizzesPerKeyword, userAnswers)) - .sorted(Comparator.comparing(KeywordResponse::getKeywordId)) - .collect(Collectors.toList()); + private Map getAnsweredQuizCounts(final Long memberId) { + return keywordRepository.findAnsweredQuizCountByMemberId(memberId).stream() + .collect( + toMap( + KeywordIdAndAnsweredQuizCount::getKeywordId, + KeywordIdAndAnsweredQuizCount::getAnsweredQuizCount)); } } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordResponse.java b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordResponse.java index aa94ea5d2..683bf3e49 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordResponse.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordResponse.java @@ -1,13 +1,13 @@ package wooteco.prolog.roadmap.application.dto; -import java.util.ArrayList; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import wooteco.prolog.roadmap.domain.Keyword; -import java.util.HashSet; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -21,14 +21,14 @@ public class KeywordResponse { private int order; private int importance; private int totalQuizCount; - private int doneQuizCount; + private int answeredQuizCount; private Long parentKeywordId; private List recommendedPosts; private List childrenKeywords; public KeywordResponse(final Long keywordId, final String name, final String description, final int order, final int importance, final int totalQuizCount, - final int doneQuizCount, final Long parentKeywordId, + final int answeredQuizCount, final Long parentKeywordId, final List recommendedPosts, final List childrenKeywords) { this.keywordId = keywordId; @@ -37,7 +37,7 @@ public KeywordResponse(final Long keywordId, final String name, final String des this.order = order; this.importance = importance; this.totalQuizCount = totalQuizCount; - this.doneQuizCount = doneQuizCount; + this.answeredQuizCount = answeredQuizCount; this.parentKeywordId = parentKeywordId; this.recommendedPosts = recommendedPosts; this.childrenKeywords = childrenKeywords; @@ -53,34 +53,36 @@ public static KeywordResponse createResponse(final Keyword keyword) { 0, 0, keyword.getParentIdOrNull(), createRecommendedPostResponses(keyword), - null); + Collections.emptyList()); } - private static List createRecommendedPostResponses(final Keyword keyword) { - return keyword.getRecommendedPosts().stream() - .map(RecommendedPostResponse::from) - .collect(Collectors.toList()); - } - - public static KeywordResponse createWithAllChildResponse(final Keyword keyword) { + public static KeywordResponse createWithAllChildResponse(final Keyword keyword, + final Map totalQuizCounts, + final Map answeredQuizCounts) { return new KeywordResponse( keyword.getId(), keyword.getName(), keyword.getDescription(), keyword.getSeq(), keyword.getImportance(), - 0, - 0, + totalQuizCounts.getOrDefault(keyword.getId(), 0), + answeredQuizCounts.getOrDefault(keyword.getId(), 0), keyword.getParentIdOrNull(), createRecommendedPostResponses(keyword), - createChildren(keyword.getChildren())); + createChildren(keyword.getChildren(), totalQuizCounts, answeredQuizCounts)); } - private static List createChildren(final Set children) { - List keywords = new ArrayList<>(); - for (Keyword keyword : children) { - keywords.add(createWithAllChildResponse(keyword)); - } - return keywords; + private static List createRecommendedPostResponses(final Keyword keyword) { + return keyword.getRecommendedPosts().stream() + .map(RecommendedPostResponse::from) + .collect(Collectors.toList()); + } + + private static List createChildren(final Set children, + final Map totalQuizCounts, + final Map answeredQuizCounts) { + return children.stream() + .map(keyword -> createWithAllChildResponse(keyword, totalQuizCounts, answeredQuizCounts)) + .collect(Collectors.toList()); } } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java index d904b67a3..c5927f0a1 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java @@ -1,12 +1,14 @@ package wooteco.prolog.roadmap.application.dto; -import java.util.List; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import wooteco.prolog.roadmap.domain.Keyword; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class KeywordsResponse { @@ -17,18 +19,14 @@ public KeywordsResponse(final List data) { this.data = data; } - public static KeywordsResponse createResponse(final List keywords) { - List keywordsResponse = keywords.stream() - .map(KeywordResponse::createResponse) - .collect(Collectors.toList()); - return new KeywordsResponse(keywordsResponse); - } - - public static KeywordsResponse createResponseWithChildren(final List keywords) { - List keywordsResponse = keywords.stream() + public static KeywordsResponse of(final List keywords, + final Map totalQuizCounts, + final Map answeredQuizCounts) { + final List keywordsResponse = keywords.stream() .filter(Keyword::isRoot) - .map(KeywordResponse::createWithAllChildResponse) + .map(rootKeyword -> KeywordResponse.createWithAllChildResponse(rootKeyword, totalQuizCounts, answeredQuizCounts)) .collect(Collectors.toList()); + return new KeywordsResponse(keywordsResponse); } } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java index 9fc136b14..6259312d2 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java @@ -7,7 +7,16 @@ import org.hibernate.annotations.BatchSize; import wooteco.prolog.common.exception.BadRequestException; -import javax.persistence.*; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -38,6 +47,7 @@ public class Keyword { @Column(name = "session_id", nullable = false) private Long sessionId; + @BatchSize(size = 1000) @OneToMany(mappedBy = "keyword", cascade = CascadeType.ALL, orphanRemoval = true) private Set recommendedPosts = new HashSet<>(); @@ -50,8 +60,7 @@ public class Keyword { private Set children = new HashSet<>(); public Keyword(final Long id, final String name, final String description, final int seq, - final int importance, - final Long sessionId, final Keyword parent, final Set children) { + final int importance, final Long sessionId, final Keyword parent, final Set children) { validateSeq(seq); this.id = id; this.name = name; @@ -69,7 +78,7 @@ public static Keyword createKeyword(final String name, final int importance, final Long sessionId, final Keyword parent) { - return new Keyword(null, name, description, seq, importance, sessionId, parent, null); + return new Keyword(null, name, description, seq, importance, sessionId, parent, new HashSet<>()); } public void update(final String name, final String description, final int seq, diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java index db4207efe..c9f96348b 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java @@ -1,35 +1,30 @@ package wooteco.prolog.roadmap.domain.repository; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import wooteco.prolog.roadmap.domain.Keyword; +import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndAnsweredQuizCount; +import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndTotalQuizCount; import java.util.List; -import java.util.Optional; -import java.util.Set; - -import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.FETCH; public interface KeywordRepository extends JpaRepository { - @EntityGraph(attributePaths = "recommendedPosts", type = FETCH) - Optional findById(final Long id); + @Query("SELECT k.id AS keywordId, COUNT (q.id) as totalQuizCount " + + "FROM Keyword k " + + "JOIN Quiz q ON q.keyword.id = k.id " + + "GROUP BY k.id") + List findTotalQuizCount(); - @EntityGraph(attributePaths = "recommendedPosts", type = FETCH) - List findAll(); + @Query("SELECT k.id AS keywordId, COUNT (e.id) AS answeredQuizCount " + + "FROM Keyword k " + + "JOIN Quiz q ON k.id = q.keyword.id " + + "JOIN EssayAnswer e ON e.quiz.id = q.id " + + "WHERE e.member.id = :memberId " + + "GROUP BY k.id ") + List findAnsweredQuizCountByMemberId(@Param("memberId") Long memberId); - @Query("SELECT k FROM Keyword k " - + "LEFT JOIN FETCH k.children c " - + "LEFT JOIN FETCH k.recommendedPosts " - + "LEFT JOIN FETCH k.parent p " - + "LEFT JOIN FETCH p.recommendedPosts " - + "LEFT JOIN FETCH c.recommendedPosts " - + "LEFT JOIN FETCH c.children lc " - + "LEFT JOIN FETCH lc.recommendedPosts " - + "LEFT JOIN FETCH lc.children " - + "WHERE k.id = :keywordId ORDER BY k.seq") Keyword findFetchByIdOrderBySeq(@Param("keywordId") Long keywordId); @Query("SELECT k FROM Keyword k " @@ -42,5 +37,8 @@ public interface KeywordRepository extends JpaRepository { + "WHERE k.parent IS NULL") List newFindByParentIsNull(); - List findBySessionIdIn(final Set sessionIds); + @Query("SELECT k FROM Keyword k " + + "JOIN Session s ON s.id = k.sessionId " + + "WHERE s.curriculumId = :curriculumId ") + List findAllByCurriculumId(Long curriculumId); } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java index 6ecdf58a1..944abf921 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/QuizRepository.java @@ -1,10 +1,11 @@ package wooteco.prolog.roadmap.domain.repository; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import wooteco.prolog.roadmap.domain.Quiz; +import java.util.List; + public interface QuizRepository extends JpaRepository { @Query("SELECT q FROM Quiz q" diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndAnsweredQuizCount.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndAnsweredQuizCount.java new file mode 100644 index 000000000..63009ab0e --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndAnsweredQuizCount.java @@ -0,0 +1,7 @@ +package wooteco.prolog.roadmap.domain.repository.dto; + +public interface KeywordIdAndAnsweredQuizCount { + long getKeywordId(); + + int getAnsweredQuizCount(); +} diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndTotalQuizCount.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndTotalQuizCount.java new file mode 100644 index 000000000..47836c550 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndTotalQuizCount.java @@ -0,0 +1,7 @@ +package wooteco.prolog.roadmap.domain.repository.dto; + +public interface KeywordIdAndTotalQuizCount { + long getKeywordId(); + + int getTotalQuizCount(); +} diff --git a/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java b/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java index b7635b1b3..9bf2df56c 100644 --- a/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java +++ b/backend/src/main/java/wooteco/prolog/session/application/SessionMemberService.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import wooteco.prolog.common.exception.BadRequestException; -import wooteco.prolog.member.application.GroupMemberService; +import wooteco.prolog.member.application.DepartmentMemberService; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.application.dto.MemberResponse; import wooteco.prolog.member.domain.Member; @@ -25,7 +25,7 @@ public class SessionMemberService { private SessionMemberRepository sessionMemberRepository; private SessionRepository sessionRepository; private MemberService memberService; - private GroupMemberService groupMemberService; + private DepartmentMemberService departmentMemberService; @Transactional public void registerMember(Long sessionId, Long memberId) { @@ -42,7 +42,7 @@ public void registerMembersByGroupId(Long sessionId, List alreadySessionMembers = sessionMemberRepository.findAllBySessionId( sessionId); - List members = groupMemberService.findGroupMemberByGroupId( + List members = departmentMemberService.findDepartmentMemberByDepartmentId( sessionGroupMemberRequest.getGroupId()).stream() .map(it -> it.getMember()) .collect(toList()); diff --git a/backend/src/main/java/wooteco/prolog/studylog/application/CommentService.java b/backend/src/main/java/wooteco/prolog/studylog/application/CommentService.java index b0a818ab7..4ed007288 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/application/CommentService.java +++ b/backend/src/main/java/wooteco/prolog/studylog/application/CommentService.java @@ -1,6 +1,7 @@ package wooteco.prolog.studylog.application; import static wooteco.prolog.common.exception.BadRequestCode.COMMENT_NOT_FOUND; +import static wooteco.prolog.common.exception.BadRequestCode.MEMBER_NOT_ALLOWED; import static wooteco.prolog.common.exception.BadRequestCode.MEMBER_NOT_FOUND; import static wooteco.prolog.common.exception.BadRequestCode.STUDYLOG_NOT_FOUND; @@ -11,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Role; import wooteco.prolog.member.domain.repository.MemberRepository; import wooteco.prolog.studylog.application.dto.CommentResponse; import wooteco.prolog.studylog.application.dto.CommentSaveRequest; @@ -33,6 +35,7 @@ public class CommentService { public Long insertComment(CommentSaveRequest request) { Member findMember = memberRepository.findById(request.getMemberId()) .orElseThrow(() -> new BadRequestException(MEMBER_NOT_FOUND)); + validateMemberIsCrew(findMember); Studylog findStudylog = studylogRepository.findById(request.getStudylogId()) .orElseThrow(() -> new BadRequestException(STUDYLOG_NOT_FOUND)); @@ -41,6 +44,12 @@ public Long insertComment(CommentSaveRequest request) { return commentRepository.save(comment).getId(); } + private void validateMemberIsCrew(final Member member) { + if (member.hasLowerImportanceRoleThan(Role.CREW)) { + throw new BadRequestException(MEMBER_NOT_ALLOWED); + } + } + @Transactional(readOnly = true) public CommentsResponse findComments(Long studylogId) { Studylog findStudylog = studylogRepository.findById(studylogId) diff --git a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java index cbe1a5ade..aadf4d314 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java +++ b/backend/src/main/java/wooteco/prolog/studylog/application/PopularStudylogService.java @@ -13,13 +13,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.MemberGroupType; -import wooteco.prolog.member.domain.MemberGroups; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.*; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.studylog.application.dto.PopularStudylogsResponse; import wooteco.prolog.studylog.application.dto.StudylogResponse; import wooteco.prolog.studylog.application.dto.StudylogsResponse; @@ -39,26 +35,26 @@ public class PopularStudylogService { private final StudylogService studylogService; private final StudylogRepository studylogRepository; private final PopularStudylogRepository popularStudylogRepository; - private final MemberGroupRepository memberGroupRepository; - private final GroupMemberRepository groupMemberRepository; + private final DepartmentRepository departmentRepository; + private final DepartmentMemberRepository departmentMemberRepository; @Transactional public void updatePopularStudylogs(Pageable pageable) { deleteAllLegacyPopularStudylogs(); - List groupMembers = groupMemberRepository.findAll(); - Map> memberGroupsBygroupType = memberGroupRepository.findAll() + List departmentMembers = departmentMemberRepository.findAll(); + Map> DepartmetsBygroupType = departmentRepository.findAll() .stream() - .collect(Collectors.groupingBy(MemberGroup::groupType)); + .collect(Collectors.groupingBy(Department::getPart)); final List recentStudylogs = findRecentStudylogs(LocalDateTime.now(), pageable.getPageSize()); List popularStudylogs = new ArrayList<>(); - for (MemberGroupType groupType : MemberGroupType.values()) { - popularStudylogs.addAll(filterStudylogsByMemberGroups(recentStudylogs, - new MemberGroups(memberGroupsBygroupType.get(groupType)), groupMembers).stream() + for (Part partType : Part.values()) { + popularStudylogs.addAll(filterStudylogsByDepartmets(recentStudylogs, + new Departments(DepartmetsBygroupType.get(partType)), departmentMembers).stream() .sorted(Comparator.comparing(Studylog::getPopularScore).reversed()) .limit(pageable.getPageSize()).collect(toList())); } @@ -89,43 +85,43 @@ private List findRecentStudylogs(final LocalDateTime dateTime, return recentStudylogs; } - private List filterStudylogsByMemberGroups(final List studylogs, - final MemberGroups memberGroups, - final List groupMembers) { + private List filterStudylogsByDepartmets(final List studylogs, + final Departments departments, + final List departmentMembers) { return studylogs.stream() .filter( - studylog -> checkMemberAssignedInMemberGroups(memberGroups, studylog.getMember(), - groupMembers)) + studylog -> checkMemberAssignedInDepartmets(departments, studylog.getMember(), + departmentMembers)) .collect(toList()); } - private boolean checkMemberAssignedInMemberGroups(MemberGroups memberGroups, Member member, - List groupMembers) { - return groupMembers.stream().anyMatch( - it -> it.getMember().equals(member) && memberGroups.isContainsMemberGroups(it)); + private boolean checkMemberAssignedInDepartmets(Departments departments, Member member, + List departmentMembers) { + return departmentMembers.stream().anyMatch( + it -> it.getMember().equals(member) && departments.isContainsDepartments(it.getDepartment())); } public PopularStudylogsResponse findPopularStudylogs(Pageable pageable, Long memberId, boolean isAnonymousMember) { List allPopularStudylogs = getSortedPopularStudyLogs(pageable); - List groupedMembers = groupMemberRepository.findAll(); - Map> memberGroupsBygroupType = memberGroupRepository.findAll() - .stream().collect(Collectors.groupingBy(MemberGroup::groupType)); + List groupedMembers = departmentMemberRepository.findAll(); + Map> DepartmetsBygroupType = departmentRepository.findAll() + .stream().collect(Collectors.groupingBy(Department::getPart)); return PopularStudylogsResponse.of( studylogsResponse(allPopularStudylogs, pageable, memberId), studylogsResponse( - filterStudylogsByMemberGroups(allPopularStudylogs, new MemberGroups(memberGroupsBygroupType.get(MemberGroupType.FRONTEND)), groupedMembers), + filterStudylogsByDepartmets(allPopularStudylogs, new Departments(DepartmetsBygroupType.get(Part.FRONTEND)), groupedMembers), pageable, memberId), studylogsResponse( - filterStudylogsByMemberGroups(allPopularStudylogs, new MemberGroups(memberGroupsBygroupType.get(MemberGroupType.BACKEND)), groupedMembers), + filterStudylogsByDepartmets(allPopularStudylogs, new Departments(DepartmetsBygroupType.get(Part.BACKEND)), groupedMembers), pageable, memberId), studylogsResponse( - filterStudylogsByMemberGroups(allPopularStudylogs, new MemberGroups(memberGroupsBygroupType.get(MemberGroupType.ANDROID)), groupedMembers), + filterStudylogsByDepartmets(allPopularStudylogs, new Departments(DepartmetsBygroupType.get(Part.ANDROID)), groupedMembers), pageable, memberId)); } diff --git a/backend/src/main/java/wooteco/prolog/studylog/application/StudylogService.java b/backend/src/main/java/wooteco/prolog/studylog/application/StudylogService.java index edf5a0cc3..6e321724b 100644 --- a/backend/src/main/java/wooteco/prolog/studylog/application/StudylogService.java +++ b/backend/src/main/java/wooteco/prolog/studylog/application/StudylogService.java @@ -4,6 +4,7 @@ import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; +import static wooteco.prolog.common.exception.BadRequestCode.MEMBER_NOT_ALLOWED; import static wooteco.prolog.common.exception.BadRequestCode.STUDYLOG_ARGUMENT; import static wooteco.prolog.common.exception.BadRequestCode.STUDYLOG_DOCUMENT_NOT_FOUND; import static wooteco.prolog.common.exception.BadRequestCode.STUDYLOG_NOT_FOUND; @@ -24,11 +25,13 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import wooteco.prolog.common.exception.BadRequestCode; import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.application.MemberTagService; import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Role; import wooteco.prolog.session.application.MissionService; import wooteco.prolog.session.application.SessionService; import wooteco.prolog.session.domain.Mission; @@ -93,6 +96,7 @@ public List insertStudylogs(Long memberId, @Transactional public StudylogResponse insertStudylog(Long memberId, StudylogRequest studylogRequest) { Member member = memberService.findById(memberId); + validateMemberIsCrew(member); Tags tags = tagService.findOrCreate(studylogRequest.getTags()); Session session = sessionService.findSessionById(studylogRequest.getSessionId()) .orElse(null); @@ -113,6 +117,12 @@ public StudylogResponse insertStudylog(Long memberId, StudylogRequest studylogRe return StudylogResponse.of(persistStudylog); } + private void validateMemberIsCrew(final Member member) { + if (member.hasLowerImportanceRoleThan(Role.CREW)) { + throw new BadRequestException(MEMBER_NOT_ALLOWED); + } + } + @Transactional public StudylogTempResponse insertStudylogTemp(Long memberId, StudylogRequest studylogRequest) { StudylogTemp createdStudylogTemp = creteStudylogTemp(memberId, studylogRequest); diff --git a/backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql b/backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql new file mode 100644 index 000000000..6766b3a8e --- /dev/null +++ b/backend/src/main/resources/db/migration/prod/V9__alter_table_group_and_member.sql @@ -0,0 +1,30 @@ +CREATE TABLE IF NOT EXISTS department +( + id bigint auto_increment primary key, + part varchar(50) not null, + term varchar(50) not null +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +create table if not exists department_member +( + id bigint auto_increment primary key, + member_id bigint not null, + department_id bigint not null, + constraint FK_DEPARTMENT_MEMBER_ON_MEMBERㅇ + foreign key (member_id) references prolog.member (id), + constraint FK_DEPARTMENT_MEMBER_ON_DEPARTMENT + foreign key (department_id) references prolog.department (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +insert into department(id, part, term) values (1, '백엔드', '3기'); +insert into department(id, part, term) values (2, '프론트엔드', '3기'); +insert into department(id, part, term) values (3, '백엔드', '4기'); +insert into department(id, part, term) values (4, '프론트엔드', '4기'); +insert into department(id, part, term) values (5, '백엔드', '5기'); +insert into department(id, part, term) values (6, '프론트엔드', '5기'); +insert into department(id, part, term) values (7, '안드로이드', '5기'); + +insert into department_member (id, member_id, department_id) + (select id, member_id, group_id from group_member); diff --git a/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java new file mode 100644 index 000000000..f95ed8e80 --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/member/application/DepartmentMemberServiceTest.java @@ -0,0 +1,50 @@ +package wooteco.prolog.member.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import wooteco.prolog.member.domain.*; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; + +@ExtendWith(MockitoExtension.class) +class DepartmentMemberServiceTest { + + @Mock + private DepartmentMemberRepository departmentMemberRepository; + + @InjectMocks + private DepartmentMemberService departmentMemberService; + + @DisplayName("GroupId로 GroupMember를 찾는다.") + @Test + void findDepartmentMemberByDepartmentId() { + //given + final Long memberId = 1L; + final Long DepartmentId = 2L; + final Long groupMemberId = 3L; + + final Member member = new Member(memberId, "송세연", "아마란스", Role.CREW, 1523L, "image"); + final Department department = new Department(DepartmentId, BACKEND, FIFTH); + final DepartmentMember departmentMember = new DepartmentMember(groupMemberId, member, department); + + when(departmentMemberRepository.findByDepartmentId(any())).thenReturn(ImmutableList.of(departmentMember)); + + //when + final List departmentMembers = departmentMemberService.findDepartmentMemberByDepartmentId( + DepartmentId); + + //then + assertThat(departmentMembers).containsExactly(departmentMember); + } +} diff --git a/backend/src/test/java/wooteco/prolog/member/application/GroupMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/member/application/GroupMemberServiceTest.java deleted file mode 100644 index a15d4ff53..000000000 --- a/backend/src/test/java/wooteco/prolog/member/application/GroupMemberServiceTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package wooteco.prolog.member.application; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import com.google.common.collect.ImmutableList; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.Role; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; - -@ExtendWith(MockitoExtension.class) -class GroupMemberServiceTest { - - @Mock - private GroupMemberRepository groupMemberRepository; - - @InjectMocks - private GroupMemberService groupMemberService; - - @DisplayName("GroupId로 GroupMember를 찾는다.") - @Test - void findGroupMemberByGroupId() { - //given - final Long memberId = 1L; - final Long memberGroupId = 2L; - final Long groupMemberId = 3L; - - final Member member = new Member(memberId, "송세연", "아마란스", Role.CREW, 1523L, "image"); - final MemberGroup memberGroup = new MemberGroup(memberGroupId, "백엔드", "2023 백엔드"); - final GroupMember groupMember = new GroupMember(groupMemberId, member, memberGroup); - - when(groupMemberRepository.findByGroupId(any())).thenReturn(ImmutableList.of(groupMember)); - - //when - final List groupMembers = groupMemberService.findGroupMemberByGroupId( - memberGroupId); - - //then - assertThat(groupMembers).containsExactly(groupMember); - } -} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java new file mode 100644 index 000000000..638cb356e --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/member/domain/DepartmentTest.java @@ -0,0 +1,31 @@ +package wooteco.prolog.member.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; + +import org.junit.jupiter.api.Test; + +class DepartmentTest { + + private static final Department ANDROID_DEPARTMENT = new Department(null, ANDROID, FIFTH); + private static final Department BACKEND_DEPARTMENT = new Department(null, BACKEND, FIFTH); + private static final Department FRONTEND_DEPARTMENT = new Department(null, FRONTEND, FOURTH); + + @Test + void getPartType_이름이_그룹명을_포함하면_그룹을_반환한다() { + assertThat(ANDROID_DEPARTMENT.getPart()).isEqualTo(ANDROID); + assertThat(BACKEND_DEPARTMENT.getPart()).isEqualTo(BACKEND); + assertThat(FRONTEND_DEPARTMENT.getPart()).isEqualTo(FRONTEND); + } + + @Test + void getPartType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { + + assertThatThrownBy(() -> new Department(null, "테스트", "test")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("name과 일치하는 part가 존재하지 않습니다."); + } + +} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java b/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java deleted file mode 100644 index 702640ade..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package wooteco.prolog.member.domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; - -import org.junit.jupiter.api.Test; -import wooteco.prolog.common.exception.BadRequestCode; -import wooteco.prolog.common.exception.BadRequestException; - -class MemberGroupTest { - - private static final MemberGroup ANDROID_GROUP = new MemberGroup(null, " 안드로이드 5기", "A"); - private static final MemberGroup BACKEND_GROUP = new MemberGroup(null, " 백엔드 5기", "B"); - private static final MemberGroup FRONTEND_GROUP = new MemberGroup(null, " 프론트엔드 5기", "F"); - - @Test - void getGroupType_이름이_그룹명을_포함하면_그룹을_반환한다() { - assertThat(ANDROID_GROUP.groupType()).isEqualTo(MemberGroupType.ANDROID); - assertThat(BACKEND_GROUP.groupType()).isEqualTo(MemberGroupType.BACKEND); - assertThat(FRONTEND_GROUP.groupType()).isEqualTo(MemberGroupType.FRONTEND); - } - - @Test - void getGroupType_이름이_포함하는_그룹명이_없으면_예외가_발생한다() { - MemberGroup memberGroup = new MemberGroup(null, "테스트", "test"); - - assertThatThrownBy(memberGroup::groupType) - .isInstanceOf(BadRequestException.class) - .hasMessage("해당 그룹의 타입을 결정할 수 없습니다."); - } - -} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java b/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java deleted file mode 100644 index 102652e26..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/MemberGroupTypeTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package wooteco.prolog.member.domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class MemberGroupTypeTest { - - @ParameterizedTest - @EnumSource(value = MemberGroupType.class) - void isContainedBy_null은_그룹명을_포함하지_않는다(MemberGroupType memberGroupType) { - assertThat(memberGroupType.isContainedBy(null)).isFalse(); - } - - @Test - void isContainedBy() { - assertThat(MemberGroupType.ANDROID.isContainedBy("안드로이드 5기")).isTrue(); - assertThat(MemberGroupType.ANDROID.isContainedBy("프론트엔드 5기")).isFalse(); - assertThat(MemberGroupType.ANDROID.isContainedBy("백엔드 5기")).isFalse(); - - assertThat(MemberGroupType.FRONTEND.isContainedBy("안드로이드 5기")).isFalse(); - assertThat(MemberGroupType.FRONTEND.isContainedBy("프론트엔드 5기")).isTrue(); - assertThat(MemberGroupType.FRONTEND.isContainedBy("백엔드 5기")).isFalse(); - - assertThat(MemberGroupType.BACKEND.isContainedBy("안드로이드 5기")).isFalse(); - assertThat(MemberGroupType.BACKEND.isContainedBy("프론트엔드 5기")).isFalse(); - assertThat(MemberGroupType.BACKEND.isContainedBy("백엔드 5기")).isTrue(); - } -} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java b/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java new file mode 100644 index 000000000..ae789f84e --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepositoryTest.java @@ -0,0 +1,43 @@ +package wooteco.prolog.member.domain.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static wooteco.prolog.member.domain.Part.*; +import static wooteco.prolog.member.domain.Term.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import wooteco.prolog.member.domain.*; +import wooteco.support.utils.RepositoryTest; + +@RepositoryTest +class DepartmentMemberRepositoryTest { + + @Autowired + private MemberRepository memberRepository; + @Autowired + private DepartmentMemberRepository departmentMemberRepository; + @Autowired + private DepartmentRepository departmentRepository; + + @Test + @DisplayName("작성된 studylog의 Member가 DepartmentMember의 Department에 포함되는 경우 true를 반환한다.") + void existsDepartmentMemberByMemberAndDepartment() { + // given + Member saveMember = memberRepository.save( + new Member("username", "nickname", Role.CREW, 1L, "imageUrl")); + Department saveDepartment = departmentRepository.save( + new Department(null, FRONTEND, FIFTH) + ); + departmentMemberRepository.save( + new DepartmentMember(null, saveMember, saveDepartment) + ); + + // when + boolean extract = departmentMemberRepository.existsDepartmentMemberByMemberAndDepartment( + saveMember, saveDepartment); + + // then + assertThat(extract).isTrue(); + } +} diff --git a/backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java b/backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java deleted file mode 100644 index ef64b761d..000000000 --- a/backend/src/test/java/wooteco/prolog/member/domain/repository/GroupMemberRepositoryTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package wooteco.prolog.member.domain.repository; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import wooteco.prolog.member.domain.GroupMember; -import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; -import wooteco.prolog.member.domain.Role; -import wooteco.support.utils.RepositoryTest; - -@RepositoryTest -class GroupMemberRepositoryTest { - - @Autowired - private MemberRepository memberRepository; - @Autowired - private GroupMemberRepository groupMemberRepository; - @Autowired - private MemberGroupRepository memberGroupRepository; - - @Test - @DisplayName("작성된 studylog의 Member가 GroupMember의 MemberGroup에 포함되는 경우 true를 반환한다.") - void existsGroupMemberByMemberAndMemberGroup() { - // given - Member saveMember = memberRepository.save( - new Member("username", "nickname", Role.CREW, 1L, "imageUrl")); - MemberGroup saveMemberGroup = memberGroupRepository.save( - new MemberGroup(null, "프론트엔드", "프론트엔드 설명") - ); - groupMemberRepository.save( - new GroupMember(null, saveMember, saveMemberGroup) - ); - - // when - boolean extract = groupMemberRepository.existsGroupMemberByMemberAndGroup( - saveMember, saveMemberGroup); - - // then - assertThat(extract).isTrue(); - } -} diff --git a/backend/src/test/java/wooteco/prolog/roadmap/application/EssayAnswerServiceTest.java b/backend/src/test/java/wooteco/prolog/roadmap/application/EssayAnswerServiceTest.java index 1f4a12766..1da29ad60 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/application/EssayAnswerServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/application/EssayAnswerServiceTest.java @@ -22,6 +22,7 @@ import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Role; import wooteco.prolog.roadmap.application.dto.EssayAnswerRequest; import wooteco.prolog.roadmap.application.dto.EssayAnswerUpdateRequest; import wooteco.prolog.roadmap.domain.EssayAnswer; @@ -75,7 +76,7 @@ void createEssayAnswer() { when(quizRepository.findById(anyLong())).thenReturn( Optional.of(new Quiz(null, "question"))); when(memberService.findById(anyLong())).thenReturn( - new Member(null, null, null, null, null)); + new Member(null, null, Role.CREW, null, null)); //when final Long essayAnswer = essayAnswerService.createEssayAnswer(ESSAY_ANSWER_REQUEST, 1L); diff --git a/backend/src/test/java/wooteco/prolog/roadmap/application/RecommendedPostServiceTest.java b/backend/src/test/java/wooteco/prolog/roadmap/application/RecommendedPostServiceTest.java index 5f3932c58..1a5417144 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/application/RecommendedPostServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/application/RecommendedPostServiceTest.java @@ -17,7 +17,6 @@ import wooteco.prolog.session.domain.repository.SessionRepository; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; @SpringBootTest class RecommendedPostServiceTest { @@ -90,10 +89,6 @@ void delete() { recommendedPostService.delete(recommendedPostId); //then - assertSoftly(softAssertions -> { - assertThat(recommendedPostRepository.findAll()).hasSize(0); - assertThat(keywordRepository.findById(keyword.getId()).get().getRecommendedPosts()) - .isEmpty(); - }); + assertThat(recommendedPostRepository.existsById(recommendedPostId)).isFalse(); } } diff --git a/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java b/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java index 45d584adb..fac37a2ff 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java @@ -1,92 +1,202 @@ package wooteco.prolog.roadmap.application; +import org.assertj.core.api.AbstractListAssert; +import org.assertj.core.api.ObjectAssert; +import org.assertj.core.api.ProxyableListAssert; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import wooteco.prolog.common.DataInitializer; import wooteco.prolog.member.domain.Member; import wooteco.prolog.member.domain.Role; +import wooteco.prolog.member.domain.repository.MemberRepository; import wooteco.prolog.roadmap.application.dto.KeywordResponse; import wooteco.prolog.roadmap.application.dto.KeywordsResponse; -import wooteco.prolog.roadmap.domain.Curriculum; import wooteco.prolog.roadmap.domain.EssayAnswer; import wooteco.prolog.roadmap.domain.Keyword; import wooteco.prolog.roadmap.domain.Quiz; -import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; import wooteco.prolog.roadmap.domain.repository.EssayAnswerRepository; import wooteco.prolog.roadmap.domain.repository.KeywordRepository; import wooteco.prolog.roadmap.domain.repository.QuizRepository; import wooteco.prolog.session.domain.Session; import wooteco.prolog.session.domain.repository.SessionRepository; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Optional; +import java.util.function.Consumer; +import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.when; -@ExtendWith(MockitoExtension.class) +@SpringBootTest class RoadMapServiceTest { - @Mock - private CurriculumRepository curriculumRepository; - @Mock + @Autowired private SessionRepository sessionRepository; - @Mock + @Autowired private KeywordRepository keywordRepository; - @Mock + @Autowired private QuizRepository quizRepository; - @Mock + @Autowired private EssayAnswerRepository essayAnswerRepository; - @InjectMocks + @Autowired + private MemberRepository memberRepository; + + @Autowired private RoadMapService roadMapService; + @Autowired + private DataInitializer dataInitializer; + + @AfterEach + void truncate() { + dataInitializer.execute(); + } + @Test - @DisplayName("curriculumId가 주어지면 해당 커리큘럼의 키워드들을 학습 진도와 함께 전부 조회할 수 있다.") + @DisplayName("커리큘럼 ID에 해당하는 모든 키워드를 진도율과 함께 조회할 수 있다 - 비회원") void findAllKeywordsWithProgress() { //given - final Curriculum curriculum = new Curriculum(1L, "커리큘럼1"); - final Session session = new Session(1L, curriculum.getId(), "세션1"); - final List sessions = Arrays.asList(session); - final Keyword keyword = new Keyword(1L, "자바1", "자바 설명1", 1, 5, session.getId(), - null, Collections.emptySet()); - final Quiz quiz = new Quiz(1L, keyword, "자바8을 왜 쓰나요?"); - final Member member = new Member(1L, "연어", "참치", Role.CREW, 1L, "image"); - final EssayAnswer essayAnswer = new EssayAnswer(quiz, "쓰라고 해서요ㅠㅠ", member); + final Long curriculumId = 1L; + final Long session1 = createSession(curriculumId); + final Long session2 = createSession(curriculumId); + + final Keyword root1 = createParentKeyword("스트림", "스트림 API 설명", session1); + final Keyword root2 = createParentKeyword("컬렉션", "컬렉션 API 설명", session2); + + final Keyword oneDepthChild = createChildKeyword(root1, "map()", "스트림 api의 map"); + final Keyword twoDepthChild = createChildKeyword(oneDepthChild, "map()의 파라미터", "파라미터 설명"); + + createQuiz(oneDepthChild, "map을 왜 쓰나요?"); + createQuiz(root2, "컬렉션을 왜 쓰나요?"); + + //when + final KeywordsResponse response = roadMapService.findAllKeywordsWithProgress(curriculumId, null); + + //then + assertSoftly(softAssertions -> { + final ProxyableListAssert roots = softAssertions.assertThat(response.getData()); + final AbstractListAssert, KeywordResponse, ObjectAssert> oneDepthChildren + = roots.flatMap(KeywordResponse::getChildrenKeywords); + final AbstractListAssert, KeywordResponse, ObjectAssert> twoDepthChildren + = oneDepthChildren.flatMap(KeywordResponse::getChildrenKeywords); + + roots.hasSize(2) + .satisfies(containsKeywordIds(root1.getId(), root2.getId())) + .satisfies(containsTotalQuizCounts(0, 1)) + .satisfies(containsAnsweredQuizCounts(0, 0)); + + oneDepthChildren.hasSize(1) + .satisfies(containsKeywordIds(oneDepthChild.getId())) + .satisfies(containsTotalQuizCounts(1)) + .satisfies(containsAnsweredQuizCounts(0)); + + twoDepthChildren.hasSize(1) + .satisfies(containsKeywordIds(twoDepthChild.getId())) + .satisfies(containsTotalQuizCounts(0)) + .satisfies(containsAnsweredQuizCounts(0)); + }); + } + + @Test + @DisplayName("커리큘럼 ID에 해당하는 모든 키워드를 진도율과 함께 조회할 수 있다 - 회원") + void findAllKeywordsWithProgress_login() { + //given + final Long curriculumId = 1L; + final Long session1 = createSession(curriculumId); + final Long session2 = createSession(curriculumId); - when(curriculumRepository.findById(anyLong())) - .thenReturn(Optional.of(curriculum)); + final Keyword root1 = createParentKeyword("스트림", "스트림 API 설명", session1); + final Keyword root2 = createParentKeyword("컬렉션", "컬렉션 API 설명", session2); - when(sessionRepository.findAllByCurriculumId(anyLong())) - .thenReturn(sessions); + final Keyword oneDepthChild = createChildKeyword(root1, "map()", "스트림 api의 map"); + final Keyword twoDepthChild = createChildKeyword(oneDepthChild, "map()의 파라미터", "파라미터 설명"); - when(keywordRepository.findBySessionIdIn(any())) - .thenReturn(Arrays.asList(keyword)); + final Quiz quiz1 = createQuiz(root2, "컬렉션을 왜 쓰나요?"); + final Quiz quiz2 = createQuiz(twoDepthChild, "파라미터의 종류는?"); + createQuiz(oneDepthChild, "map을 왜 쓰나요?"); - when(essayAnswerRepository.findAllByMemberId(1L)) - .thenReturn(new HashSet<>(Arrays.asList(essayAnswer))); + final Member member = createMember(); - when(quizRepository.findAll()) - .thenReturn(Arrays.asList(quiz)); + createEssayAnswer(quiz1, member, "배열을 쓸 수는 없으니까요"); + createEssayAnswer(quiz2, member, "Function 입니다"); //when - final KeywordsResponse actual = - roadMapService.findAllKeywordsWithProgress(curriculum.getId(), 1L); + final KeywordsResponse response = roadMapService.findAllKeywordsWithProgress(curriculumId, member.getId()); //then - final List responses = actual.getData(); - assertSoftly(soft -> { - assertThat(responses).hasSize(1); - assertThat(responses.get(0).getDoneQuizCount()).isOne(); - assertThat(responses.get(0).getTotalQuizCount()).isOne(); + assertSoftly(softAssertions -> { + final ProxyableListAssert roots = softAssertions.assertThat(response.getData()); + final AbstractListAssert, KeywordResponse, ObjectAssert> oneDepthChildren + = roots.flatMap(KeywordResponse::getChildrenKeywords); + final AbstractListAssert, KeywordResponse, ObjectAssert> twoDepthChildren + = oneDepthChildren.flatMap(KeywordResponse::getChildrenKeywords); + + roots.hasSize(2) + .satisfies(containsKeywordIds(root1.getId(), root2.getId())) + .satisfies(containsTotalQuizCounts(0, 1)) + .satisfies(containsAnsweredQuizCounts(0, 1)); + + oneDepthChildren.hasSize(1) + .satisfies(containsKeywordIds(oneDepthChild.getId())) + .satisfies(containsTotalQuizCounts(1)) + .satisfies(containsAnsweredQuizCounts(0)); + + twoDepthChildren.hasSize(1) + .satisfies(containsKeywordIds(twoDepthChild.getId())) + .satisfies(containsTotalQuizCounts(1)) + .satisfies(containsAnsweredQuizCounts(1)); }); } + + private Consumer> containsAnsweredQuizCounts(final Integer... expected) { + return keywords -> assertThat(keywords) + .map(KeywordResponse::getAnsweredQuizCount) + .containsExactlyInAnyOrder(expected); + } + + private Consumer> containsTotalQuizCounts(final Integer... expected) { + return keywords -> assertThat(keywords) + .map(KeywordResponse::getTotalQuizCount) + .containsExactlyInAnyOrder(expected); + } + + private Consumer> containsKeywordIds(final Long... expected) { + return keywords -> assertThat(keywords) + .map(KeywordResponse::getKeywordId) + .containsExactlyInAnyOrder(expected); + } + + private Long createSession(final Long curriculumId) { + Session session = new Session(curriculumId, "테스트 세션"); + sessionRepository.save(session); + return session.getId(); + } + + private Keyword createParentKeyword(final String name, final String description, final Long sessionId) { + final Keyword keyword = new Keyword(null, name, description, 1, 1, sessionId, null, emptySet()); + return keywordRepository.save(keyword); + } + + private Keyword createChildKeyword(final Keyword parent, final String name, final String description) { + final Keyword keyword = new Keyword(null, name, description, 1, 1, parent.getSessionId(), parent, emptySet()); + return keywordRepository.save(keyword); + } + + private Quiz createQuiz(final Keyword keyword, final String question) { + final Quiz quiz = new Quiz(keyword, question); + return quizRepository.save(quiz); + } + + private Member createMember() { + final Member member = new Member("id", "연어", Role.CREW, 1L, "image"); + return memberRepository.save(member); + } + + private EssayAnswer createEssayAnswer(final Quiz quiz, final Member member, final String answer) { + final EssayAnswer essayAnswer = new EssayAnswer(quiz, answer, member); + return essayAnswerRepository.save(essayAnswer); + } } diff --git a/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java b/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java index 47c67b86b..4f018ebce 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java @@ -1,23 +1,33 @@ package wooteco.prolog.roadmap.repository; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import javax.persistence.EntityManager; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.Role; +import wooteco.prolog.member.domain.repository.MemberRepository; import wooteco.prolog.roadmap.domain.Curriculum; +import wooteco.prolog.roadmap.domain.EssayAnswer; import wooteco.prolog.roadmap.domain.Keyword; +import wooteco.prolog.roadmap.domain.Quiz; import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; +import wooteco.prolog.roadmap.domain.repository.EssayAnswerRepository; import wooteco.prolog.roadmap.domain.repository.KeywordRepository; +import wooteco.prolog.roadmap.domain.repository.QuizRepository; +import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndAnsweredQuizCount; +import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndTotalQuizCount; import wooteco.prolog.session.domain.Session; import wooteco.prolog.session.domain.repository.SessionRepository; import wooteco.support.utils.RepositoryTest; +import javax.persistence.EntityManager; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.junit.jupiter.api.Assertions.assertAll; + @RepositoryTest class KeywordRepositoryTest { @@ -26,16 +36,20 @@ class KeywordRepositoryTest { @Autowired private SessionRepository sessionRepository; @Autowired + private EssayAnswerRepository essayAnswerRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired private EntityManager em; @Autowired private CurriculumRepository curriculumRepository; + @Autowired + private QuizRepository quizRepository; @Test void 부모_키워드와_1뎁스까지의_자식_키워드를_함께_조회할_수_있다() { // given - Session session = new Session("테스트 세션"); - sessionRepository.save(session); - Long sessionId = session.getId(); + Long sessionId = createSession(); Keyword keywordParent = createKeywordParent( Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, sessionId, null)); @@ -58,9 +72,7 @@ class KeywordRepositoryTest { @Test void 부모_키워드와_2뎁스까지의_자식_키워드를_함께_조회할_수_있다() { // given - Session session = new Session("테스트 세션"); - sessionRepository.save(session); - Long sessionId = session.getId(); + Long sessionId = createSession(); Keyword parent = createKeywordParent( Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, sessionId, null)); @@ -93,9 +105,7 @@ class KeywordRepositoryTest { @Test void 최상위_키워드만_조회한다() { // given - Session session = new Session("테스트 세션"); - sessionRepository.save(session); - Long sessionId = session.getId(); + Long sessionId = createSession(); Keyword keywordParent = createKeywordParent( Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, sessionId, null)); @@ -111,12 +121,12 @@ class KeywordRepositoryTest { sessionId); // then - assertThat(extractParentKeyword.size()).isEqualTo(1); + assertThat(extractParentKeyword).hasSize(1); } @Test - @DisplayName("세션 ID 리스트로 키워드 리스트를 조회한다") - void findBySessionIdIn() { + @DisplayName("newFindParentIsNull() : 모든 상위 키워드들을 조회할 수 있다.") + void newFindParentIsNull() { //given final Curriculum curriculum = curriculumRepository.save(new Curriculum("커리큘럼1")); @@ -134,74 +144,128 @@ void findBySessionIdIn() { Keyword.createKeyword("자바3", "자바 설명3", 3, 5, session1.getId(), null)); final Keyword keyword4 = keywordRepository.save( Keyword.createKeyword("자바4", "자바 설명4", 4, 5, session1.getId(), keyword3)); - keywordRepository.save( + final Keyword keyword5 = keywordRepository.save( Keyword.createKeyword("자바5", "자바 설명5", 5, 5, session2.getId(), null)); - keywordRepository.save( + final Keyword keyword6 = keywordRepository.save( Keyword.createKeyword("자바6", "자바 설명6", 6, 5, session2.getId(), keyword1)); - keywordRepository.save( + final Keyword keyword7 = keywordRepository.save( Keyword.createKeyword("자바7", "자바 설명7", 7, 5, session2.getId(), null)); - keywordRepository.save( + final Keyword keyword8 = keywordRepository.save( Keyword.createKeyword("자바8", "자바 설명8", 8, 5, session3.getId(), keyword2)); final Keyword keyword9 = keywordRepository.save( Keyword.createKeyword("자바9", "자바 설명9", 9, 5, session4.getId(), keyword2)); final Keyword keyword10 = keywordRepository.save( Keyword.createKeyword("자바10", "자바 설명10", 10, 5, session5.getId(), null)); - final HashSet sessionIds = new HashSet<>( - Arrays.asList(session1.getId(), session4.getId(), session5.getId()) - ); - //when - final List keywords = keywordRepository.findBySessionIdIn(sessionIds); + final List keywords = keywordRepository.newFindByParentIsNull(); //then assertThat(keywords) .usingRecursiveComparison() .ignoringFields("id", "parent.id") - .isEqualTo(Arrays.asList(keyword1, keyword2, keyword3, keyword4, keyword9, keyword10)); + .isEqualTo(Arrays.asList(keyword1, keyword3, keyword5, keyword7, keyword10)); } @Test - @DisplayName("newFindParentIsNull() : 모든 상위 키워드들을 조회할 수 있다.") - void newFindParentIsNull() { + @DisplayName("각 키워드별 퀴즈 개수를 조회할 수 있다") + void findTotalQuizCount() { //given - final Curriculum curriculum = curriculumRepository.save(new Curriculum("커리큘럼1")); + final Long sessionId = createSession(); - final Session session1 = sessionRepository.save(new Session(curriculum.getId(), "세션1")); - final Session session2 = sessionRepository.save(new Session(curriculum.getId(), "세션2")); - final Session session3 = sessionRepository.save(new Session(curriculum.getId(), "세션3")); - final Session session4 = sessionRepository.save(new Session(curriculum.getId(), "세션4")); - final Session session5 = sessionRepository.save(new Session(curriculum.getId(), "세션5")); + final Keyword parent = createKeywordParent( + Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, sessionId, null)); + final Keyword child1 = createKeywordChildren( + Keyword.createKeyword("List", "List에 대한 설명", 1, 1, sessionId, parent)); + createKeywordChildren( + Keyword.createKeyword("Set", "Set에 대한 설명", 2, 1, sessionId, parent)); - final Keyword keyword1 = keywordRepository.save( - Keyword.createKeyword("자바1", "자바 설명1", 1, 5, session1.getId(), null)); - final Keyword keyword2 = keywordRepository.save( - Keyword.createKeyword("자바2", "자바 설명2", 2, 5, session1.getId(), keyword1)); - final Keyword keyword3 = keywordRepository.save( - Keyword.createKeyword("자바3", "자바 설명3", 3, 5, session1.getId(), null)); - final Keyword keyword4 = keywordRepository.save( - Keyword.createKeyword("자바4", "자바 설명4", 4, 5, session1.getId(), keyword3)); - final Keyword keyword5 = keywordRepository.save( - Keyword.createKeyword("자바5", "자바 설명5", 5, 5, session2.getId(), null)); - final Keyword keyword6 = keywordRepository.save( - Keyword.createKeyword("자바6", "자바 설명6", 6, 5, session2.getId(), keyword1)); - final Keyword keyword7 = keywordRepository.save( - Keyword.createKeyword("자바7", "자바 설명7", 7, 5, session2.getId(), null)); - final Keyword keyword8 = keywordRepository.save( - Keyword.createKeyword("자바8", "자바 설명8", 8, 5, session3.getId(), keyword2)); - final Keyword keyword9 = keywordRepository.save( - Keyword.createKeyword("자바9", "자바 설명9", 9, 5, session4.getId(), keyword2)); - final Keyword keyword10 = keywordRepository.save( - Keyword.createKeyword("자바10", "자바 설명10", 10, 5, session5.getId(), null)); + quizRepository.save(new Quiz(parent, "자바란 무엇일까요?")); + quizRepository.save(new Quiz(parent, "자바를 왜 쓰죠?")); + quizRepository.save(new Quiz(child1, "리스트보단 배열이 낫지 않나요?")); //when - final List keywords = keywordRepository.newFindByParentIsNull(); + final List totalQuizCounts = keywordRepository.findTotalQuizCount(); //then - assertThat(keywords) - .usingRecursiveComparison() - .ignoringFields("id", "parent.id") - .isEqualTo(Arrays.asList(keyword1, keyword3, keyword5, keyword7, keyword10)); + assertSoftly(softAssertions -> { + softAssertions.assertThat(totalQuizCounts) + .map(KeywordIdAndTotalQuizCount::getKeywordId) + .containsExactlyInAnyOrder(parent.getId(), child1.getId()); + + softAssertions.assertThat(totalQuizCounts) + .map(KeywordIdAndTotalQuizCount::getTotalQuizCount) + .containsExactlyInAnyOrder(1, 2); + }); + } + + @Test + @DisplayName("회원의 id로 각 키워드별 완료한 답변 개수를 조회할 수 있다") + void findDoneQuizCountByMemberId() { + //given + final Long sessionId = createSession(); + + final Keyword parent = createKeywordParent( + Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, sessionId, null)); + final Keyword child1 = createKeywordChildren( + Keyword.createKeyword("List", "List에 대한 설명", 1, 1, sessionId, parent)); + + final Quiz quiz1 = quizRepository.save(new Quiz(parent, "자바란 무엇일까요?")); + final Quiz quiz2 = quizRepository.save(new Quiz(parent, "코틀린은 뭘까요?")); + quizRepository.save(new Quiz(child1, "리스트보단 배열이 낫지 않나요?")); + + final Member member = memberRepository.save(new Member("username1", "nickname1", Role.CREW, 1L, "imageUrl1")); + essayAnswerRepository.save(new EssayAnswer(quiz1, "쓰라고 해서요ㅠ", member)); + essayAnswerRepository.save(new EssayAnswer(quiz2, "쓰라고 해서요ㅠ", member)); + + //when + final List doneQuizCounts = keywordRepository.findAnsweredQuizCountByMemberId(member.getId()); + + //then + assertSoftly(softAssertions -> { + softAssertions.assertThat(doneQuizCounts) + .map(KeywordIdAndAnsweredQuizCount::getKeywordId) + .containsExactly(parent.getId()); + softAssertions.assertThat(doneQuizCounts) + .map(KeywordIdAndAnsweredQuizCount::getAnsweredQuizCount) + .containsExactly(2); + }); + } + + @Test + @DisplayName("커리큘럼 ID에 해당하는 모든 키워드들을 조회할 수 있다") + void findAllByCurriculumId() { + //given + final Long session1 = createSessionWithCurriculumId(1L); + final Long session2 = createSessionWithCurriculumId(1L); + + final Keyword parent1 = createKeywordParent( + Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, session1, null)); + createKeywordChildren( + Keyword.createKeyword("List", "List에 대한 설명", 1, 1, session1, parent1)); + createKeywordChildren( + Keyword.createKeyword("List", "List에 대한 설명", 1, 1, session1, parent1)); + + createKeywordParent( + Keyword.createKeyword("자바", "자바에 대한 설명", 1, 1, session2, null)); + + //when + final List keywords = keywordRepository.findAllByCurriculumId(1L); + + //then + assertThat(keywords).hasSize(4); + } + + private Long createSession() { + Session session = new Session("테스트 세션"); + sessionRepository.save(session); + return session.getId(); + } + + private Long createSessionWithCurriculumId(final Long curriculumId) { + Session session = new Session(curriculumId, "테스트 세션"); + sessionRepository.save(session); + return session.getId(); } private Keyword createKeywordParent(final Keyword keyword) { diff --git a/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java b/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java index 50ee17c60..5327feb44 100644 --- a/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/session/application/SessionMemberServiceTest.java @@ -20,7 +20,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import wooteco.prolog.common.exception.BadRequestException; -import wooteco.prolog.member.application.GroupMemberService; +import wooteco.prolog.member.application.DepartmentMemberService; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.application.dto.MemberResponse; import wooteco.prolog.member.domain.Member; @@ -45,7 +45,7 @@ class SessionMemberServiceTest { private MemberService memberService; @Mock - private GroupMemberService groupMemberService; + private DepartmentMemberService departmentMemberService; @DisplayName("Member가 회원가입을 할 수 있어야 한다.") @Test @@ -89,7 +89,7 @@ void registerMembersByGroupId() { // then verify(sessionMemberRepository, atMostOnce()).findAllBySessionId(1L); - verify(groupMemberService, atMostOnce()).findGroupMemberByGroupId(request.getGroupId()); + verify(departmentMemberService, atMostOnce()).findDepartmentMemberByDepartmentId(request.getGroupId()); verify(sessionMemberRepository, atMostOnce()).saveAll(null); } diff --git a/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java b/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java index 0dedf34bd..86e338c64 100644 --- a/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/studylog/application/PopularStudylogServiceTest.java @@ -20,12 +20,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; -import wooteco.prolog.member.domain.GroupMember; +import wooteco.prolog.member.domain.DepartmentMember; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroup; +import wooteco.prolog.member.domain.Department; import wooteco.prolog.member.domain.Role; -import wooteco.prolog.member.domain.repository.GroupMemberRepository; -import wooteco.prolog.member.domain.repository.MemberGroupRepository; +import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; +import wooteco.prolog.member.domain.repository.DepartmentRepository; import wooteco.prolog.session.domain.Mission; import wooteco.prolog.session.domain.Session; import wooteco.prolog.studylog.application.dto.PopularStudylogsResponse; @@ -46,9 +46,9 @@ class PopularStudylogServiceTest { @Mock private PopularStudylogRepository popularStudylogRepository; @Mock - private MemberGroupRepository memberGroupRepository; + private DepartmentRepository departmentRepository; @Mock - private GroupMemberRepository groupMemberRepository; + private DepartmentMemberRepository departmentMemberRepository; @InjectMocks private PopularStudylogService popularStudylogService; @@ -56,15 +56,15 @@ class PopularStudylogServiceTest { @Test void enoughWhileTwoCycle() { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudyLog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -75,16 +75,16 @@ void enoughWhileTwoCycle() { journeyStudylog.getId()); final PageRequest pageRequest = PageRequest.of(1, 2); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); final List studylogs = Arrays.asList(splitStudyLog, journeyStudylog); final List popularStudylogs = Arrays.asList(splitPopularStudylog, journeyPopularStudylog); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findByPastDays(any())).thenReturn(studylogs); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); when(popularStudylogRepository.saveAll(any())).thenReturn(popularStudylogs); @@ -100,15 +100,15 @@ void enoughWhileTwoCycle() { @Test void notEnoughWhileTwoCycle() { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -119,15 +119,15 @@ void notEnoughWhileTwoCycle() { journeyStudylog.getId()); final PageRequest pageRequest = PageRequest.of(1, 5); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); final List studylogs = Arrays.asList(splitStudylog, journeyStudylog); final List popularStudylogs = Arrays.asList(splitPopularStudylog, journeyPopularStudylog); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findByPastDays(any())).thenReturn(studylogs); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); when(popularStudylogRepository.saveAll(any())).thenReturn(popularStudylogs); @@ -142,15 +142,15 @@ void notEnoughWhileTwoCycle() { @DisplayName("익명의 사용자일 경우 스크랩과 읽음 여부가 표시하지 않고 학습로그를 조회한다.") @Test void findPopularStudylogs_IsAnonymousMemberTrue() { - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -163,14 +163,14 @@ void findPopularStudylogs_IsAnonymousMemberTrue() { final List studylogs = Arrays.asList(splitStudylog, journeyStudylog); final PageRequest pageRequest = PageRequest.of(0, 1); final Page pages = new PageImpl<>(studylogs, pageRequest, 2); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); final List popularStudylogs = Arrays.asList(splitPopularStudylog, journeyPopularStudylog); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findAllByIdIn(any(), any())).thenReturn(pages); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); @@ -218,15 +218,15 @@ void findPopularStudylogs_IsAnonymousMemberTrue() { void findPopularStudylogs_IsAnonymousMemberFalse() { { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); - final GroupMember splitGroupMember = setUpGroupMember(split, backend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, backend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -234,12 +234,12 @@ void findPopularStudylogs_IsAnonymousMemberFalse() { final List studylogs = Arrays.asList(splitStudylog, journeyStudylog); final PageRequest pageRequest = PageRequest.of(0, 1); final Page pages = new PageImpl<>(studylogs, pageRequest, 2); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, - journeyGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, + journeyDepartmentMember); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findAllByIdIn(any(), any())).thenReturn(pages); when(studylogService.findScrapIds(any())).thenReturn(Arrays.asList(1L, 2L)); doNothing().when(studylogService).updateScrap(any(), any()); @@ -288,20 +288,20 @@ void findPopularStudylogs_IsAnonymousMemberFalse() { @DisplayName("인기학습 로그를 분야별로 나누어서 반한한다.") @Test - void findPopularStudylogs_filterGroupType() { + void findPopularStudylogs_filterDepartmentType() { { //given - final MemberGroup frontend = setUpMemberGroup("5기 프론트엔드", "5기 프론트엔드 설명"); - final MemberGroup backend = setUpMemberGroup("5기 백엔드", "5기 백엔드 설명"); - final MemberGroup android = setUpMemberGroup("5기 안드로이드", "5기 안드로이드 설명"); + final Department frontend = setUpDepartment("프론트엔드", "5기"); + final Department backend = setUpDepartment("백엔드", "5기"); + final Department android = setUpDepartment("안드로이드", "5기"); final Member split = setUpMember(1L, "박상현", "스플릿", 1L); final Member journey = setUpMember(2L, "이지원", "져니", 2L); final Member pooh = setUpMember(3L, "백승준", "푸우", 3L); - final GroupMember splitGroupMember = setUpGroupMember(split, frontend); - final GroupMember journeyGroupMember = setUpGroupMember(journey, backend); - final GroupMember poohGroupMember = setUpGroupMember(pooh, android); + final DepartmentMember splitDepartmentMember = setUpDepartmentMember(split, frontend); + final DepartmentMember journeyDepartmentMember = setUpDepartmentMember(journey, backend); + final DepartmentMember poohDepartmentMember = setUpDepartmentMember(pooh, android); final Studylog splitStudylog = setUpStudyLog(split); final Studylog journeyStudylog = setUpStudyLog(journey); @@ -310,8 +310,8 @@ void findPopularStudylogs_filterGroupType() { final List studylogs = Arrays.asList(splitStudylog, journeyStudylog, poohStudylog); final PageRequest pageRequest = PageRequest.of(0, 3); final Page pages = new PageImpl<>(studylogs, pageRequest, 1); - final List memberGroups = Arrays.asList(frontend, backend, android); - final List groupMembers = Arrays.asList(splitGroupMember, journeyGroupMember, poohGroupMember); + final List departments = Arrays.asList(frontend, backend, android); + final List departmentMembers = Arrays.asList(splitDepartmentMember, journeyDepartmentMember, poohDepartmentMember); final PopularStudylog splitPopularStudylog = setUpPopularStudylog(1L, splitStudylog.getId()); @@ -324,8 +324,8 @@ void findPopularStudylogs_filterGroupType() { journeyPopularStudylog, poohPopularStudylog); when(popularStudylogRepository.findAllByDeletedFalse()).thenReturn(popularStudylogs); - when(groupMemberRepository.findAll()).thenReturn(groupMembers); - when(memberGroupRepository.findAll()).thenReturn(memberGroups); + when(departmentMemberRepository.findAll()).thenReturn(departmentMembers); + when(departmentRepository.findAll()).thenReturn(departments); when(studylogRepository.findAllByIdIn(any(), any())).thenReturn(pages); //when @@ -366,8 +366,8 @@ void findPopularStudylogs_filterGroupType() { } - private MemberGroup setUpMemberGroup(final String name, final String description) { - return new MemberGroup(null, name, description); + private Department setUpDepartment(final String part, final String term) { + return new Department(null, part, term); } private Member setUpMember(final Long id, final String userName, @@ -375,8 +375,8 @@ private Member setUpMember(final Long id, final String userName, return new Member(id, userName, nickname, Role.CREW, githubId, "image url"); } - private GroupMember setUpGroupMember(final Member member, final MemberGroup memberGroup) { - return new GroupMember(null, member, memberGroup); + private DepartmentMember setUpDepartmentMember(final Member member, final Department department) { + return new DepartmentMember(null, member, department); } private Studylog setUpStudyLog(final Member member) { diff --git a/backend/src/test/java/wooteco/prolog/studylog/application/StudylogServiceTest.java b/backend/src/test/java/wooteco/prolog/studylog/application/StudylogServiceTest.java index e3cede2d0..7663ba901 100644 --- a/backend/src/test/java/wooteco/prolog/studylog/application/StudylogServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/studylog/application/StudylogServiceTest.java @@ -1,5 +1,33 @@ package wooteco.prolog.studylog.application; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static wooteco.prolog.common.exception.BadRequestCode.ONLY_AUTHOR_CAN_EDIT; +import static wooteco.prolog.common.exception.BadRequestCode.STUDYLOG_ARGUMENT; +import static wooteco.prolog.common.exception.BadRequestCode.STUDYLOG_NOT_FOUND; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -14,7 +42,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; -import wooteco.prolog.common.exception.BadRequestCode; import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.member.application.MemberService; @@ -53,33 +80,6 @@ import wooteco.prolog.studylog.domain.repository.dto.CommentCount; import wooteco.prolog.studylog.event.StudylogDeleteEvent; -import java.lang.reflect.Field; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static wooteco.prolog.common.exception.BadRequestCode.*; - @ExtendWith(MockitoExtension.class) class StudylogServiceTest { @@ -119,8 +119,10 @@ class insertStudylog { Tags tags = Tags.of(singletonList("스터디로그")); Member member = new Member(1L, "김동해", "오션", Role.CREW, 1L, "image"); List tagRequests = singletonList(new TagRequest("스터디로그")); - StudylogRequest studylogRequest = new StudylogRequest(title, content, null, null, tagRequests); - Studylog studylog = new Studylog(member, studylogRequest.getTitle(), studylogRequest.getContent(), null, null, tags.getList()); + StudylogRequest studylogRequest = new StudylogRequest(title, content, null, null, + tagRequests); + Studylog studylog = new Studylog(member, studylogRequest.getTitle(), + studylogRequest.getContent(), null, null, tags.getList()); List expectedTagResponses = singletonList(new TagResponse(null, "스터디로그")); @DisplayName("StudyLogTemp가 존재할 경우 삭제하고, 스터디로그를 정상적으로 생성하고 반환한다.") @@ -139,7 +141,8 @@ void insertStudylog_existStudyLogTemp() { assertAll(() -> { assertThat(studylogResponse.getTitle()).isEqualTo(title); assertThat(studylogResponse.getContent()).isEqualTo(content); - assertThat(studylogResponse.getTags()).usingRecursiveComparison().isEqualTo(expectedTagResponses); + assertThat(studylogResponse.getTags()).usingRecursiveComparison() + .isEqualTo(expectedTagResponses); verify(studylogTempRepository, times(1)).deleteByMemberId(1L); }); } @@ -160,7 +163,8 @@ void insertStudylog_notExistStudyLogTemp() { assertAll(() -> { assertThat(studylogResponse.getTitle()).isEqualTo(title); assertThat(studylogResponse.getContent()).isEqualTo(content); - assertThat(studylogResponse.getTags()).usingRecursiveComparison().isEqualTo(expectedTagResponses); + assertThat(studylogResponse.getTags()).usingRecursiveComparison() + .isEqualTo(expectedTagResponses); verify(studylogTempRepository, never()).deleteByMemberId(1L); }); } @@ -176,8 +180,10 @@ class insertStudylogTemp { Member member = new Member(1L, "문채원", "라온", Role.CREW, 1L, "image"); List tagRequests = singletonList(new TagRequest("스터디로그")); List tagResponses = singletonList(new TagResponse(null, "스터디로그")); - StudylogRequest studylogRequest = new StudylogRequest(title, content, null, null, tagRequests); - StudylogTemp studylogTemp = new StudylogTemp(member, studylogRequest.getTitle(), studylogRequest.getContent(), null, null, tags.getList()); + StudylogRequest studylogRequest = new StudylogRequest(title, content, null, null, + tagRequests); + StudylogTemp studylogTemp = new StudylogTemp(member, studylogRequest.getTitle(), + studylogRequest.getContent(), null, null, tags.getList()); @DisplayName("StudyLogTemp가 존재하지 않는 경우 삭제하고, 임시 스터디로그를 정상적으로 생성하고 반환한다.") @Test @@ -189,13 +195,15 @@ void insertStudylogTemp_existStudylogTemp() { when(studylogTempRepository.existsByMemberId(1L)).thenReturn(true); // when - StudylogTempResponse studylogTempResponse = studylogService.insertStudylogTemp(1L, studylogRequest); + StudylogTempResponse studylogTempResponse = studylogService.insertStudylogTemp(1L, + studylogRequest); // then assertAll(() -> { assertThat(studylogTempResponse.getTitle()).isEqualTo(title); assertThat(studylogTempResponse.getContent()).isEqualTo(content); - assertThat(studylogTempResponse.getTags()).usingRecursiveComparison().isEqualTo(tagResponses); + assertThat(studylogTempResponse.getTags()).usingRecursiveComparison() + .isEqualTo(tagResponses); verify(studylogTempRepository, times(1)).deleteByMemberId(1L); }); } @@ -210,15 +218,16 @@ void insertStudylogTemp_notExistStudylogTemp() { when(studylogTempRepository.save(any())).thenReturn(studylogTemp); when(studylogTempRepository.existsByMemberId(1L)).thenReturn(false); - // when - StudylogTempResponse studylogTempResponse = studylogService.insertStudylogTemp(1L, studylogRequest); + StudylogTempResponse studylogTempResponse = studylogService.insertStudylogTemp(1L, + studylogRequest); // then assertAll(() -> { assertThat(studylogTempResponse.getTitle()).isEqualTo(title); assertThat(studylogTempResponse.getContent()).isEqualTo(content); - assertThat(studylogTempResponse.getTags()).usingRecursiveComparison().isEqualTo(tagResponses); + assertThat(studylogTempResponse.getTags()).usingRecursiveComparison() + .isEqualTo(tagResponses); verify(studylogTempRepository, never()).deleteByMemberId(1L); }); } @@ -232,7 +241,8 @@ void findScrapIds() { //given final Member member = new Member(1L, "hihi", "연어", Role.CREW, 1L, "image"); final Mission mission = new Mission(1L, "지하철", new Session("BE 레벨2")); - final Studylog studylog = new Studylog(member, "짜장면", "먹고싶다", mission, singletonList(new Tag("스프링"))); + final Studylog studylog = new Studylog(member, "짜장면", "먹고싶다", mission, + singletonList(new Tag("스프링"))); final StudylogScrap scrap = new StudylogScrap(member, studylog); when(studylogScrapRepository.findByMemberId(anyLong())).thenReturn(singletonList(scrap)); @@ -265,7 +275,8 @@ void findStudylogsWithoutKeyword() { 1L); //then - verify(studylogRepository, times(1)).findAll((Specification) any(), (Pageable) any()); + verify(studylogRepository, times(1)).findAll((Specification) any(), + (Pageable) any()); } @DisplayName("StudylogResponse의 scrap 여부를 설정할 수 있다") @@ -306,7 +317,9 @@ void insertStudyLogs() { final Member member = new Member(1L, "hihi", "연어", Role.CREW, 1L, "image"); final Mission mission = new Mission(1L, "지하철", new Session("BE 레벨2")); - when(studylogRepository.save(any())).thenReturn(new Studylog(member, "제목", "내용", mission, emptyList())); + when(studylogRepository.save(any())).thenReturn( + new Studylog(member, "제목", "내용", mission, emptyList())); + when(memberService.findById(any())).thenReturn(member); when(tagService.findOrCreate(any())).thenReturn(new Tags(emptyList())); //when @@ -332,6 +345,7 @@ void findStudylogById() { @Nested class retrieveStudylogById { + private Studylog studylog; private long studyLogId = 1L; @@ -360,14 +374,16 @@ void retrieveStudylogById_anonymous() { .willReturn(Optional.of(studylog)); final int previousViewCount = studylog.getViewCount(); - //when - final StudylogResponse studylogResponse = studylogService.retrieveStudylogById(loginMember, 1L, false); + final StudylogResponse studylogResponse = studylogService.retrieveStudylogById( + loginMember, 1L, false); //then assertAll( - () -> assertThat(studylogResponse).extracting(StudylogResponse::getViewCount).isEqualTo(previousViewCount + 1), - () -> assertThat(studylogResponse).extracting(StudylogResponse::getId).isEqualTo(studyLogId) + () -> assertThat(studylogResponse).extracting(StudylogResponse::getViewCount) + .isEqualTo(previousViewCount + 1), + () -> assertThat(studylogResponse).extracting(StudylogResponse::getId) + .isEqualTo(studyLogId) ); } @@ -376,7 +392,8 @@ void retrieveStudylogById_anonymous() { void retrieveStudylogById_readOtherUserStudylog() { //given final long otherUserId = 2L; - final LoginMember loginMember = new LoginMember(otherUserId, LoginMember.Authority.MEMBER); + final LoginMember loginMember = new LoginMember(otherUserId, + LoginMember.Authority.MEMBER); given(studylogRepository.findById(anyLong())) .willReturn(Optional.of(studylog)); given(studylogReadRepository.findByMemberIdAndStudylogId(anyLong(), anyLong())) @@ -389,14 +406,19 @@ void retrieveStudylogById_readOtherUserStudylog() { final int previousViewCount = studylog.getViewCount(); //when - final StudylogResponse studylogResponse = studylogService.retrieveStudylogById(loginMember, 1L, false); + final StudylogResponse studylogResponse = studylogService.retrieveStudylogById( + loginMember, 1L, false); //then assertAll( - () -> assertThat(studylogResponse).extracting(StudylogResponse::getViewCount).isEqualTo(previousViewCount + 1), - () -> assertThat(studylogResponse).extracting(StudylogResponse::getId).isEqualTo(studyLogId), - () -> assertThat(studylogResponse).extracting(StudylogResponse::isRead).isEqualTo(true), - () -> assertThat(studylogResponse).extracting(StudylogResponse::isScrap).isEqualTo(true) + () -> assertThat(studylogResponse).extracting(StudylogResponse::getViewCount) + .isEqualTo(previousViewCount + 1), + () -> assertThat(studylogResponse).extracting(StudylogResponse::getId) + .isEqualTo(studyLogId), + () -> assertThat(studylogResponse).extracting(StudylogResponse::isRead) + .isEqualTo(true), + () -> assertThat(studylogResponse).extracting(StudylogResponse::isScrap) + .isEqualTo(true) ); } @@ -417,14 +439,19 @@ void retrieveStudylogById_readMineStudyLog() { final int previousViewCount = studylog.getViewCount(); //when - final StudylogResponse studylogResponse = studylogService.retrieveStudylogById(loginMember, 1L, false); + final StudylogResponse studylogResponse = studylogService.retrieveStudylogById( + loginMember, 1L, false); //then assertAll( - () -> assertThat(studylogResponse).extracting(StudylogResponse::getViewCount).isEqualTo(previousViewCount), - () -> assertThat(studylogResponse).extracting(StudylogResponse::getId).isEqualTo(studyLogId), - () -> assertThat(studylogResponse).extracting(StudylogResponse::isRead).isEqualTo(true), - () -> assertThat(studylogResponse).extracting(StudylogResponse::isScrap).isEqualTo(true) + () -> assertThat(studylogResponse).extracting(StudylogResponse::getViewCount) + .isEqualTo(previousViewCount), + () -> assertThat(studylogResponse).extracting(StudylogResponse::getId) + .isEqualTo(studyLogId), + () -> assertThat(studylogResponse).extracting(StudylogResponse::isRead) + .isEqualTo(true), + () -> assertThat(studylogResponse).extracting(StudylogResponse::isScrap) + .isEqualTo(true) ); } } @@ -629,14 +656,17 @@ void findStudylogs_responseByRequestIds() { ); //when - final StudylogsResponse studylogsResponse = studylogService.findStudylogs(studylogsSearchRequest, 1L); + final StudylogsResponse studylogsResponse = studylogService.findStudylogs( + studylogsSearchRequest, 1L); int oneIndexedParameter = 1; //then assertAll( - () -> assertThat(studylogsResponse.getCurrPage()).isEqualTo(requestPage + oneIndexedParameter), + () -> assertThat(studylogsResponse.getCurrPage()).isEqualTo( + requestPage + oneIndexedParameter), () -> assertThat(studylogsResponse.getTotalSize()).isEqualTo(findElementCounts), - () -> assertThat(studylogsResponse.getTotalPage()).isEqualTo((findElementCounts / pageSize) + 1), + () -> assertThat(studylogsResponse.getTotalPage()).isEqualTo( + (findElementCounts / pageSize) + 1), () -> assertThat(studylogsResponse.getData()).hasSize(1) ); } @@ -677,14 +707,17 @@ void findStudylogs_findBySpecs() { ); //when - final StudylogsResponse studylogsResponse = studylogService.findStudylogs(studylogsSearchRequest, 1L); + final StudylogsResponse studylogsResponse = studylogService.findStudylogs( + studylogsSearchRequest, 1L); int oneIndexedParameter = 1; //then assertAll( - () -> assertThat(studylogsResponse.getCurrPage()).isEqualTo(requestPage + oneIndexedParameter), + () -> assertThat(studylogsResponse.getCurrPage()).isEqualTo( + requestPage + oneIndexedParameter), () -> assertThat(studylogsResponse.getTotalSize()).isEqualTo(findElementCounts), - () -> assertThat(studylogsResponse.getTotalPage()).isEqualTo((findElementCounts / pageSize) + 1), + () -> assertThat(studylogsResponse.getTotalPage()).isEqualTo( + (findElementCounts / pageSize) + 1), () -> assertThat(studylogsResponse.getData()).hasSize(1) ); } @@ -708,7 +741,8 @@ void findStudylogs_findByKeyword() { pageableRequest ); - given(studylogDocumentService.findBySearchKeyword(any(), any(), any(), any(), any(), any(), any(), any())) + given(studylogDocumentService.findBySearchKeyword(any(), any(), any(), any(), any(), + any(), any(), any())) .willReturn(StudylogDocumentResponse.of(Arrays.asList(1L), 1, 1, 0)); final int findElementCounts = 1; @@ -722,14 +756,17 @@ void findStudylogs_findByKeyword() { ); //when - final StudylogsResponse studylogsResponse = studylogService.findStudylogs(studylogsSearchRequest, 1L); + final StudylogsResponse studylogsResponse = studylogService.findStudylogs( + studylogsSearchRequest, 1L); int oneIndexedParameter = 1; //then assertAll( - () -> assertThat(studylogsResponse.getCurrPage()).isEqualTo(requestPage + oneIndexedParameter), + () -> assertThat(studylogsResponse.getCurrPage()).isEqualTo( + requestPage + oneIndexedParameter), () -> assertThat(studylogsResponse.getTotalSize()).isEqualTo(findElementCounts), - () -> assertThat(studylogsResponse.getTotalPage()).isEqualTo((findElementCounts / pageSize) + 1), + () -> assertThat(studylogsResponse.getTotalPage()).isEqualTo( + (findElementCounts / pageSize) + 1), () -> assertThat(studylogsResponse.getData()).hasSize(1) ); } @@ -770,7 +807,8 @@ void updateStudylogSession() { //then assertAll( - () -> assertEquals(updatedSession.getCurriculumId(), studylog.getSession().getCurriculumId()), + () -> assertEquals(updatedSession.getCurriculumId(), + studylog.getSession().getCurriculumId()), () -> assertEquals(updatedSession.getName(), studylog.getSession().getName()) ); } @@ -922,7 +960,8 @@ void findReadIds() throws NoSuchFieldException, IllegalAccessException { } private Studylog makeStudyLogFor(final long id) { - final Studylog idInjectedStudylog = new Studylog(judy, "제목", "내용", session, mission, new ArrayList<>()); + final Studylog idInjectedStudylog = new Studylog(judy, "제목", "내용", session, mission, + new ArrayList<>()); try { final Field idField = idInjectedStudylog.getClass().getDeclaredField("id"); idField.setAccessible(true); @@ -985,20 +1024,23 @@ void readRssFeeds() { .willReturn(studylogs); //when - List studylogRssFeedResponses = studylogService.readRssFeeds("URI"); + List studylogRssFeedResponses = studylogService.readRssFeeds( + "URI"); //then assertThat(studylogRssFeedResponses).hasSize(50); } private Studylog makeStudylogWithDateFor(final long id) { - final Studylog idAndDateInjectedStudylog = new Studylog(judy, "제목", "내용", session, mission, new ArrayList<>()); + final Studylog idAndDateInjectedStudylog = new Studylog(judy, "제목", "내용", session, + mission, new ArrayList<>()); try { final Field idField = idAndDateInjectedStudylog.getClass().getDeclaredField("id"); idField.setAccessible(true); idField.set(idAndDateInjectedStudylog, id); - final Field createdAtField = idAndDateInjectedStudylog.getClass().getSuperclass().getDeclaredField("createdAt"); + final Field createdAtField = idAndDateInjectedStudylog.getClass().getSuperclass() + .getDeclaredField("createdAt"); createdAtField.setAccessible(true); createdAtField.set(idAndDateInjectedStudylog, LocalDateTime.now()); diff --git a/frontend/src/components/Article/Article.tsx b/frontend/src/components/Article/Article.tsx index 64a9c7457..7c0cf3c69 100644 --- a/frontend/src/components/Article/Article.tsx +++ b/frontend/src/components/Article/Article.tsx @@ -1,7 +1,7 @@ import * as Styled from './Article.style'; import type { ArticleType } from '../../models/Article'; import Scrap from '../Reaction/Scrap'; -import { useRef, useState } from 'react'; +import { useContext, useRef, useState } from 'react'; import { usePostArticleViewsMutation, usePutArticleBookmarkMutation, @@ -9,6 +9,7 @@ import { } from '../../hooks/queries/article'; import debounce from '../../utils/debounce'; import Like from '../Reaction/Like'; +import { UserContext } from '../../contexts/UserProvider'; const Article = ({ id, @@ -28,10 +29,18 @@ const Article = ({ const { mutate: putLike } = usePutArticleLikeMutation(); const { mutate: postViews } = usePostArticleViewsMutation(); + const { user } = useContext(UserContext); + const { isLoggedIn } = user; + const toggleBookmark: React.MouseEventHandler = (e) => { e.preventDefault(); e.stopPropagation(); + if (!isLoggedIn) { + alert('로그인을 해주세요'); + return; + } + bookmarkRef.current = !bookmarkRef.current; setBookmark((prev) => !prev); @@ -44,6 +53,11 @@ const Article = ({ e.preventDefault(); e.stopPropagation(); + if (!isLoggedIn) { + alert('로그인을 해주세요'); + return; + } + likeRef.current = !likeRef.current; setLike((prev) => !prev); diff --git a/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts index 9ad24cfd3..2fb8589dc 100644 --- a/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts +++ b/frontend/src/components/DropdownMenu/DropdownMenu.styles.ts @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; import COLOR from '../../constants/color'; import { css } from '@emotion/react'; +import MEDIA_QUERY from '../../constants/mediaQuery'; const Container = styled.div<{ css: ReturnType; @@ -15,7 +16,13 @@ const Container = styled.div<{ padding: 1rem 1.2rem; position: absolute; z-index: 100; + right: 30px; + top: 50px; + ${MEDIA_QUERY.xs} { + right: 10px; + top: 40px; + } /* transform: translateY(30%); */ && { diff --git a/frontend/src/components/KeywordDetailSideSheet/KeywordDetailSideSheet.tsx b/frontend/src/components/KeywordDetailSideSheet/KeywordDetailSideSheet.tsx index 472a105ac..e3ebc3bd8 100644 --- a/frontend/src/components/KeywordDetailSideSheet/KeywordDetailSideSheet.tsx +++ b/frontend/src/components/KeywordDetailSideSheet/KeywordDetailSideSheet.tsx @@ -16,9 +16,11 @@ const KeywordDetailSideSheet = ({ }: KeywordDetailSideSheetProps) => { const { name, keywordId, description, recommendedPosts } = keywordDetail; - const { user: { isLoggedIn } } = useContext(UserContext); + const { user: { isLoggedIn, role } } = useContext(UserContext); const { quizList } = useGetQuizListByKeyword({ keywordId }); + const isAuthorized = isLoggedIn && role !== 'GUEST'; + return ( @@ -32,10 +34,10 @@ const KeywordDetailSideSheet = ({
    {quizList?.map(({ quizId, question }, index) => (
  1. - {isLoggedIn && ( + {isAuthorized && ( {index + 1}. {question} )} - {!isLoggedIn && ( + {!isAuthorized && ( <>{index + 1}. {question} )}  /  diff --git a/frontend/src/components/NavBar/NavBar.tsx b/frontend/src/components/NavBar/NavBar.tsx index 6bfe8ac09..99cb3a49d 100644 --- a/frontend/src/components/NavBar/NavBar.tsx +++ b/frontend/src/components/NavBar/NavBar.tsx @@ -50,7 +50,8 @@ const NavBar = () => { const { user, onLogout } = useContext(UserContext); - const { username, imageUrl: userImage = NoProfileImage, isLoggedIn } = user; + const { username, imageUrl: userImage = NoProfileImage, isLoggedIn, role } = user; + const authorized = isLoggedIn && role !== 'GUEST'; const [isDropdownToggled, setDropdownToggled] = useState(false); const [isWritingDropdownToggled, setWritingDropdownToggled] = useState(false); @@ -118,16 +119,19 @@ const NavBar = () => { ))} + {authorized && ( + +