diff --git a/BE/build.gradle b/BE/build.gradle index 9198f34823..8ee5be9918 100644 --- a/BE/build.gradle +++ b/BE/build.gradle @@ -1,6 +1,13 @@ +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} + plugins { id 'org.springframework.boot' version '2.7.0' id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" id 'java' } @@ -26,6 +33,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'javax.xml.bind:jaxb-api:2.3.1' + implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" + annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}" compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java' @@ -41,3 +50,18 @@ jar { tasks.named('test') { useJUnitPlatform() } + +def querydslDir = "$buildDir/generated/querydsl" +querydsl { + jpa = true + querydslSourcesDir = querydslDir +} +sourceSets { + main.java.srcDir querydslDir +} +configurations { + querydsl.extendsFrom compileClasspath +} +compileQuerydsl { + options.annotationProcessorPath = configurations.querydsl +} diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/LoginNameArgumentResolver.java b/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/LoginUserArgumentResolver.java similarity index 76% rename from BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/LoginNameArgumentResolver.java rename to BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/LoginUserArgumentResolver.java index 6773e58473..18e08d0ba5 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/LoginNameArgumentResolver.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/LoginUserArgumentResolver.java @@ -1,6 +1,7 @@ package com.team31.codesquad.issuetracker.config.mvc; -import com.team31.codesquad.issuetracker.config.mvc.annotation.LoginName; +import com.team31.codesquad.issuetracker.config.mvc.annotation.LoginUser; +import com.team31.codesquad.issuetracker.domain.user.UserRepository; import com.team31.codesquad.issuetracker.util.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; @@ -12,12 +13,13 @@ @Component @RequiredArgsConstructor -public class LoginNameArgumentResolver implements HandlerMethodArgumentResolver { +public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver { private final JwtUtil jwtUtil; + private final UserRepository userRepository; @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.getParameterAnnotation(LoginName.class) != null; + return parameter.getParameterAnnotation(LoginUser.class) != null; } @Override @@ -32,10 +34,10 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m String token = authorizationHeader.substring(7); if (token.equals(jwtUtil.getAdminPassword())) { - return jwtUtil.getAdminLoginName(); + return userRepository.findByLoginName(jwtUtil.getAdminLoginName()); } + String loginName = jwtUtil.getPayload(token); - System.out.println("loginName = " + loginName); - return loginName; + return userRepository.findByLoginName(loginName); } } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/WebConfig.java b/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/WebConfig.java index 534ebca063..1c592038fc 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/WebConfig.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/WebConfig.java @@ -12,11 +12,11 @@ @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { - private final LoginNameArgumentResolver loginNameArgumentResolver; + private final LoginUserArgumentResolver loginUserArgumentResolver; @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(loginNameArgumentResolver); + resolvers.add(loginUserArgumentResolver); } @Bean @@ -24,7 +24,9 @@ public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:3000") + .allowedOrigins("http://localhost:3000", + "http://ec2-3-37-163-26.ap-northeast-2.compute.amazonaws.com", + "http://localhost:80") .allowedMethods("*") .allowedHeaders("*") .exposedHeaders("*") diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/annotation/LoginName.java b/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/annotation/LoginUser.java similarity index 90% rename from BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/annotation/LoginName.java rename to BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/annotation/LoginUser.java index d1932b7245..1fe0f17288 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/annotation/LoginName.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/config/mvc/annotation/LoginUser.java @@ -7,6 +7,6 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface LoginName { +public @interface LoginUser { } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/config/querydsl/QuerydslConfig.java b/BE/src/main/java/com/team31/codesquad/issuetracker/config/querydsl/QuerydslConfig.java new file mode 100644 index 0000000000..6033efcffa --- /dev/null +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/config/querydsl/QuerydslConfig.java @@ -0,0 +1,16 @@ +package com.team31.codesquad.issuetracker.config.querydsl; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import javax.persistence.EntityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + @Bean + public JPAQueryFactory jpaQueryFactory(EntityManager em) { + return new JPAQueryFactory(em); + } + +} diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Comment.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Comment.java index 62447b2cfc..5ffc8cd92d 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Comment.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Comment.java @@ -57,10 +57,14 @@ public Comment(Issue issue, User author, String content) { this.systemMessage = false; } - public static Comment createStatusChangeComment(Issue issue, - IssueStatus status, User statusChangeUser) { + public Comment(User author, String content) { + this.author = author; + this.content = content; + this.systemMessage = false; + } + + public static Comment createStatusChangeComment(IssueStatus status, User statusChangeUser) { Comment comment = new Comment(); - comment.issue = issue; comment.author = statusChangeUser; comment.content = status.equals(IssueStatus.OPEN) ? ISSUE_OPEN_MESSAGE : ISSUE_CLOSED_MESSAGE; @@ -79,8 +83,8 @@ public void validateIssue(Long issueId) { } } - public void validateAuthor(String loginName) { - if (!loginName.equals(getAuthor().getLoginName())) { + public void validateAuthor(User user) { + if (!user.equals(getAuthor())) { throw new IllegalArgumentException("작성자만 접근이 가능합니다."); } } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/CommentRepository.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/CommentRepository.java index 6518b9add1..96585b4725 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/CommentRepository.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/CommentRepository.java @@ -1,7 +1,10 @@ package com.team31.codesquad.issuetracker.domain.comment; +import com.team31.codesquad.issuetracker.domain.issue.Issue; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface CommentRepository extends JpaRepository { + List findAllByIssue(Issue issue); } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Reaction.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Reaction.java index 3e4a7b625e..287ed8af9c 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Reaction.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/Reaction.java @@ -36,11 +36,11 @@ public class Reaction extends BaseTimeEntity { @Enumerated(EnumType.STRING) @Column(nullable = false) - private ReactionEmoji reactionEmoji; + private ReactionEmoji emoji; - public Reaction(User user, Comment comment, ReactionEmoji reactionEmoji) { + public Reaction(User user, Comment comment, ReactionEmoji emoji) { this.user = user; this.comment = comment; - this.reactionEmoji = reactionEmoji; + this.emoji = emoji; } } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/ReactionRepository.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/ReactionRepository.java index cd950b5469..85a4000719 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/ReactionRepository.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/comment/ReactionRepository.java @@ -2,9 +2,13 @@ import com.team31.codesquad.issuetracker.domain.user.User; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface ReactionRepository extends JpaRepository { List findAllByUserAndComment(User user, Comment comment); + + Optional findByUserAndCommentAndEmoji(User user, Comment comment, + ReactionEmoji emoji); } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/Issue.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/Issue.java index dd1636a963..de8b23dc22 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/Issue.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/Issue.java @@ -7,7 +7,9 @@ import com.team31.codesquad.issuetracker.domain.user.User; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -47,10 +49,10 @@ public class Issue extends BaseTimeEntity { private User author; @OneToMany(mappedBy = "issue", cascade = CascadeType.ALL) - private List assignees = new ArrayList<>(); + private Set assignees = new HashSet<>(); @OneToMany(mappedBy = "issue", cascade = CascadeType.ALL) - private List issueLabels = new ArrayList<>(); + private Set issueLabels = new HashSet<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "milestone_id") @@ -65,21 +67,16 @@ public class Issue extends BaseTimeEntity { @JoinColumn(name = "status_change_user_id") private User statusChangeUser; - public static Issue createIssue(String title, User author, List assignedUsers, - List issueLabels, Milestone milestone) { + public static Issue createIssue(String title, User author, Milestone milestone, + Comment comment) { Issue issue = new Issue(); issue.status = IssueStatus.OPEN; issue.title = title; issue.author = author; - for (AssignedUser assignedUser : assignedUsers) { - issue.addAssignedUser(assignedUser); - } - for (IssueLabel issueLabel : issueLabels) { - issue.addIssueLabel(issueLabel); - } issue.milestone = milestone; issue.statusChangedAt = LocalDateTime.now(); issue.statusChangeUser = author; + issue.addComment(comment); return issue; } @@ -91,7 +88,7 @@ private void addIssueLabel(IssueLabel issueLabel) { } public void updateAssignedUsers(List assignedUsers) { - this.assignees = new ArrayList<>(); + this.assignees = new HashSet<>(); for (AssignedUser assignedUser : assignedUsers) { addAssignedUser(assignedUser); } @@ -123,7 +120,7 @@ public void changStatus(IssueStatus status, User statusChangeUser) { this.status = status; this.statusChangeUser = statusChangeUser; this.statusChangedAt = LocalDateTime.now(); - Comment comment = Comment.createStatusChangeComment(this, status, statusChangeUser); + Comment comment = Comment.createStatusChangeComment(status, statusChangeUser); addComment(comment); } @@ -137,7 +134,7 @@ public void updateMilestone(Milestone milestone) { } public void updateIssueLabels(List issueLabels) { - this.issueLabels = new ArrayList<>(); + this.issueLabels = new HashSet<>(); for (IssueLabel issueLabel : issueLabels) { addIssueLabel(issueLabel); } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueLabel.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueLabel.java index 8306b9f8c9..4a65ed2bdc 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueLabel.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueLabel.java @@ -2,6 +2,7 @@ import com.team31.codesquad.issuetracker.domain.BaseTimeEntity; import com.team31.codesquad.issuetracker.domain.label.Label; +import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -32,11 +33,29 @@ public class IssueLabel extends BaseTimeEntity { @JoinColumn(name = "label_id") private Label label; - public IssueLabel(Label label) { + public IssueLabel(Label label, Issue issue) { this.label = label; + this.issue = issue; } public void setIssue(Issue issue) { this.issue = issue; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IssueLabel that = (IssueLabel) o; + return Objects.equals(getId(), that.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } } diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueQueryRepository.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueQueryRepository.java new file mode 100644 index 0000000000..222d746f1d --- /dev/null +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/issue/IssueQueryRepository.java @@ -0,0 +1,119 @@ +package com.team31.codesquad.issuetracker.domain.issue; + +import static com.querydsl.jpa.JPAExpressions.select; +import static com.team31.codesquad.issuetracker.domain.issue.QIssue.issue; +import static com.team31.codesquad.issuetracker.domain.issue.QIssueLabel.issueLabel; +import static com.team31.codesquad.issuetracker.domain.label.QLabel.label; +import static com.team31.codesquad.issuetracker.domain.milestone.QMilestone.milestone; +import static com.team31.codesquad.issuetracker.domain.user.QAssignedUser.assignedUser; +import static com.team31.codesquad.issuetracker.domain.user.QUser.user; + +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.team31.codesquad.issuetracker.dto.OpenClosedCount; +import com.team31.codesquad.issuetracker.dto.issue.IssueSearchCondition; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +@RequiredArgsConstructor +@Repository +public class IssueQueryRepository { + + private final JPAQueryFactory queryFactory; + + public List findAllByCondition(IssueSearchCondition condition, Pageable pageable) { + return queryFactory + .select(issue) + .from(issue) + .leftJoin(issue.author, user).fetchJoin() + .leftJoin(issue.milestone, milestone).fetchJoin() + .leftJoin(issue.issueLabels, issueLabel).fetchJoin() + .leftJoin(issueLabel.label, label).fetchJoin() + .leftJoin(issue.assignees, assignedUser).fetchJoin() + .leftJoin(assignedUser.assignee, user).fetchJoin() + .where(statusEq(condition.getStatus()), + authorEq(condition.getAuthorLoginName()), + milestoneEq(condition.getMilestoneName()), + labelIn(condition.getLabelNames()), + assigneeEq(condition.getAssigneeLoginName())) + .distinct() + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + private BooleanExpression milestoneEq(String milestoneName) { + if (!StringUtils.hasText(milestoneName)) { + return null; + } + return milestone.title.eq(milestoneName); + } + + public OpenClosedCount countAllByCondition(IssueSearchCondition condition) { + Map result = queryFactory + .from(issue) + .leftJoin(issue.milestone, milestone) + .leftJoin(issue.issueLabels, issueLabel) + .leftJoin(issueLabel.label, label) + .leftJoin(issue.assignees, assignedUser) + .leftJoin(assignedUser.assignee, user) + .where( + authorEq(condition.getAuthorLoginName()), + labelIn(condition.getLabelNames()), + milestoneEq(condition.getMilestoneName()), + assigneeEq(condition.getAssigneeLoginName())) + .groupBy(issue.status) + .transform(GroupBy.groupBy(issue.status).as(issue.countDistinct())); + + return new OpenClosedCount(Optional.ofNullable(result.get(IssueStatus.OPEN)).orElse(0L), + Optional.ofNullable(result.get(IssueStatus.CLOSED)).orElse(0L)); + } + + private BooleanExpression assigneeEq(String assigneeLoginName) { + if (!StringUtils.hasText(assigneeLoginName)) { + return null; + } + return issue.author.loginName.eq(assigneeLoginName); + } + + private BooleanExpression labelIn(List labelNames) { + if (labelNames == null || labelNames.size() == 0) { + return null; + } + return issue.id.in(select(issue.id) + .from(issue) + .leftJoin(issue.issueLabels, issueLabel) + .leftJoin(issueLabel.label, label) + .where(label.name.in(labelNames)) + .groupBy(issue.id) + .having(issue.id.count().eq((long) labelNames.size()))); + + } + + private BooleanExpression authorEq(String authorLoginName) { + if (!StringUtils.hasText(authorLoginName)) { + return null; + } + return issue.author.loginName.eq(authorLoginName); + } + + private BooleanExpression statusEq(IssueStatus status) { + return issue.status.eq(status); + } + + public Issue findIssueWithAuthorAndMilestone(Long issueId) { + return queryFactory + .select(issue) + .from(issue) + .leftJoin(issue.author, user).fetchJoin() + .leftJoin(issue.milestone, milestone).fetchJoin() + .where(issue.id.eq(issueId)) + .fetchOne(); + } +} diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/IssueLabelQueryRepository.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/IssueLabelQueryRepository.java new file mode 100644 index 0000000000..69ec621496 --- /dev/null +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/IssueLabelQueryRepository.java @@ -0,0 +1,28 @@ +package com.team31.codesquad.issuetracker.domain.label; + +import static com.team31.codesquad.issuetracker.domain.issue.QIssueLabel.issueLabel; +import static com.team31.codesquad.issuetracker.domain.label.QLabel.label; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.team31.codesquad.issuetracker.domain.issue.Issue; +import com.team31.codesquad.issuetracker.domain.issue.IssueLabel; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class IssueLabelQueryRepository { + + private final JPAQueryFactory queryFactory; + + + public List findIssueLabelWithLabelByIssue(Issue issue) { + return queryFactory + .select(issueLabel) + .from(issueLabel) + .leftJoin(issueLabel.label, label).fetchJoin() + .where(issueLabel.issue.eq(issue)) + .fetch(); + } +} diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/Label.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/Label.java index d9baeecbb3..ba0a374a76 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/Label.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/Label.java @@ -39,7 +39,7 @@ public class Label extends BaseTimeEntity { @Column(nullable = false) private TextColor textColor; - @OneToMany(mappedBy = "label", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "label", cascade = CascadeType.REMOVE) private List issueLabels = new ArrayList<>(); public Label(String name, String description, String labelColor, diff --git a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/LabelRepository.java b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/LabelRepository.java index b6cefe2979..e7b8135047 100644 --- a/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/LabelRepository.java +++ b/BE/src/main/java/com/team31/codesquad/issuetracker/domain/label/LabelRepository.java @@ -1,9 +1,8 @@ package com.team31.codesquad.issuetracker.domain.label; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface LabelRepository extends JpaRepository { - Optional