Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Team02] 깃허브 로그인 구현, 라벨 수정/이슈 필터링 카운트 기능 수정 #91

Open
wants to merge 22 commits into
base: team-02
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
287bc50
Update server-cd.yml
zzawang May 29, 2024
cfa8de6
fix: 사용하지 않는 메소드 삭제
zzawang May 22, 2024
9883d5e
refactor: 필요없는 어노테이션 삭제
zzawang May 26, 2024
d136d90
feat: WebClient를 빈으로 등록하는 설정파일 생성
zzawang May 26, 2024
7148daa
feat: 깃허브 로그인을 통해 사용자 정보를 가져오는 기능 구현
zzawang May 26, 2024
4795b00
feat: 이미지 URL을 MultipartFile로 변환하는 기능 구현
zzawang May 26, 2024
cea8877
feat: 깃허브 로그인 사용자를 MemberCreateDto로 매핑하는 기능 추가
zzawang May 26, 2024
7a6a65e
feat: 깃허브 로그인을 통해 사용자에게 유저 정보와 JWT 토큰을 전달하는 기능 구현
zzawang May 26, 2024
c739f6f
fix: 메소드의 재사용을 위해 매개변수를 변경
zzawang May 26, 2024
3c5d377
feat: 사용자의 프로필 이미지를 저장하는 기능 추가
zzawang May 27, 2024
9676c5b
setting: 필요한 의존성 추가
zzawang May 28, 2024
e92a48e
setting: FE의 배포된 url 추가
zzawang May 29, 2024
5b8ee2d
Update server-cd.yml
zzawang May 29, 2024
c35530c
Update Dockerfile
zzawang May 29, 2024
a469603
Update server-cd.yml
zzawang May 29, 2024
a208852
fix: 필요없는 값 삭제
zzawang May 29, 2024
7b8ce68
feat: 라벨 개수와 마일스톤 개수를 묶어서 보내는 API 구현
zzawang May 28, 2024
fa722db
setting: 도커 파일 수정
zzawang May 30, 2024
ce31817
fix: 필터링된 이슈의 열림/닫힘 개수를 구하는 기능 수정
zzawang May 30, 2024
283d0fb
fix: 라벨 수정 기능 오류 해결
zzawang May 30, 2024
721d029
Update server-cd.yml
zzawang May 30, 2024
7a7f8cc
test: 메소드 이름 변경에 따른 테스트 수정
zzawang May 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .github/workflows/server-cd.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
name: Deploy Spring Application to Ec2

# 임시로 배포 브랜치 be/solve로 설정
on:
push:
branches: [ "be/solve" ]
branches: [ "main" ]
pull_request:
branches: [ "be/solve" ]
branches: [ "main" ]

# 워크플로우가 저장소의 콘텐츠를 읽을 수 있는 권한을 갖도록 설정
permissions:
Expand All @@ -23,12 +22,23 @@ jobs:
with:
distribution: 'corretto'
java-version: '17'

- name: Make application.properties

- name: Make KeyStore
env:
KEYSTORE_P12: ${{ secrets.KEYSTORE_P12 }}
run: |
cd ./be/issue-tracker/src/main
mkdir resources
cd resources
mkdir ssl
cd ssl
touch keystore.p12
echo "${{ secrets.KEYSTORE_P12 }}" | base64 -d > ./keystore.p12
shell: bash

- name: Make application.properties
run: |
cd ./be/issue-tracker/src/main/resources
touch application.properties
echo "${{ secrets.PROPERTIES }}" > ./application.properties
shell: bash
Expand Down
2 changes: 2 additions & 0 deletions be/issue-tracker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ dependencies {
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.1'
implementation 'org.apache.tika:tika-core:2.9.1'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0'


compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.issuetracker;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("http://*:3000")
.allowedOriginPatterns("https://issue-tracker.site", "http://*:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.issuetracker.file.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.tika.Tika;
import org.springframework.web.multipart.MultipartFile;

/**
* byte 배열을 MultipartFile 객체로 변환하기 위한 클래스
*/
public class CustomMultipartFile implements MultipartFile {
private final Tika tika = new Tika();

Choose a reason for hiding this comment

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

제가 Tika를 잘 모르긴하지만, 유틸성 느낌이 강하다고 한다면 static하게 설정해주는 것도 좋을 것 같습니다!

인스턴스 별로 (1) 다르게 가지고 있어야하는 필드와 (2) 모든 인스턴스들이 공유해도 괜찮은 필드를 구분해서 생각해주시면 좋습니다😃


private byte[] input;
private String filename;

public CustomMultipartFile(byte[] input, String filename) {
this.input = input;
this.filename = filename;
}

@Override
public String getName() {
return filename;
}

@Override
public String getOriginalFilename() {
return filename;
}

@Override
public String getContentType() {
try {
return tika.detect(getInputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public boolean isEmpty() {
return input == null || input.length == 0;
}

@Override
public long getSize() {
return input.length;
}

@Override
public byte[] getBytes() {
return input;
}

@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(input);
}

@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
try (FileOutputStream fos = new FileOutputStream(dest)) {
fos.write(input);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.issuetracker.file.util;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import org.springframework.web.multipart.MultipartFile;

public class ImgUrlConverter {
public static MultipartFile toMultipartFile(String imgUrl) throws MalformedURLException {
URL url = new URL(imgUrl);
try (InputStream inputStream = url.openStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
BufferedImage urlImage = ImageIO.read(inputStream);
ImageIO.write(urlImage, "jpg", bos);
byte[] byteArray = bos.toByteArray();
return new CustomMultipartFile(byteArray, imgUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.issuetracker.global.controller;

import com.issuetracker.global.dto.HomeComponentResponse;
import com.issuetracker.global.dto.HomeIssueResponse;
import com.issuetracker.global.service.HomeService;
import com.issuetracker.issue.dto.IssueQueryDto;
Expand All @@ -16,6 +17,12 @@
public class HomeController {
private final HomeService homeService;

@GetMapping("/components")
public ResponseEntity<HomeComponentResponse> getComponents() {
HomeComponentResponse homeComponentResponse = homeService.getComponents();
return ResponseEntity.ok().body(homeComponentResponse);
}

@GetMapping("/issues")
public ResponseEntity<HomeIssueResponse> getFilteredIssues(@ModelAttribute IssueQueryDto issueQueryDto) {
HomeIssueResponse homeIssueResponse = homeService.getFilteredIssues(issueQueryDto);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.issuetracker.global.dto;

import com.issuetracker.label.dto.LabelListDto;
import com.issuetracker.member.dto.SimpleMemberDto;
import com.issuetracker.milestone.dto.MilestoneListDto;
import java.util.List;
import com.issuetracker.label.dto.LabelCountDto;
import com.issuetracker.milestone.dto.MilestoneCountDto;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
Expand All @@ -12,8 +10,6 @@
@ToString
@Builder
public class HomeComponentResponse {
private final List<SimpleMemberDto> assignees;
private final LabelListDto labels;
private final MilestoneListDto milestones;
private final List<SimpleMemberDto> authors;
private final LabelCountDto labelCount;
private final MilestoneCountDto milestoneCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import com.issuetracker.global.dto.HomeComponentResponse;
import com.issuetracker.global.dto.HomeIssueResponse;
import com.issuetracker.issue.dto.IssueFilterResult;
import com.issuetracker.issue.dto.IssueQueryDto;
import com.issuetracker.issue.service.IssueFilterService;
import com.issuetracker.issue.service.IssueQueryService;
import com.issuetracker.label.service.LabelService;
import com.issuetracker.member.service.MemberService;
import com.issuetracker.milestone.service.MilestoneService;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -17,22 +17,18 @@
@RequiredArgsConstructor
@Slf4j
public class HomeService {
private final IssueQueryService issueQueryService;
private final IssueFilterService issueFilterService;
private final LabelService labelService;
private final MilestoneService milestoneService;
private final MemberService memberService;

/**
* 홈 화면의 상위 컴포넌트를 반환한다.
* 라벨의 개수와 마일스톤의 개수를 나타내는 홈 화면의 상위 컴포넌트를 반환한다.
*/
@Transactional(readOnly = true)
public HomeComponentResponse getComponents() {
return HomeComponentResponse.builder()
.assignees(memberService.getMembers())
.labels(labelService.getLabelListDto())
.milestones(milestoneService.showMilestoneList(false))
.authors(memberService.getMembers())
.labelCount(labelService.countLabels())
.milestoneCount(milestoneService.countMilestones())
.build();
}

Expand All @@ -41,9 +37,10 @@ public HomeComponentResponse getComponents() {
*/
@Transactional(readOnly = true)
public HomeIssueResponse getFilteredIssues(IssueQueryDto issueQueryDto) {
Set<IssueFilterResult> filteredIssues = issueFilterService.filterIssues(issueQueryDto);
return HomeIssueResponse.builder()
.count(issueQueryService.countIssues())
.filteredIssues(issueFilterService.getFilteredIssues(issueQueryDto))
.count(issueFilterService.countFilteredIssues(filteredIssues))
.filteredIssues(issueFilterService.getIssueFilterResponse(issueQueryDto.getIsClosed(), filteredIssues))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,71 +18,77 @@ public IssueFilterQueryGenerator(IssueQueryDto issueQueryDto) {

public Map<String, Object> generate(Long labelId, Long milestoneId) {
Map<String, Object> result = new HashMap<>();
StringBuilder sql = new StringBuilder("SELECT i.id, i.title, i.create_date, m.name " +
StringBuilder sql = new StringBuilder("SELECT i.id, i.title, i.create_date, i.is_closed, i.member_id, m.name " +
"FROM issue i " +
"LEFT JOIN issue_label il ON i.id = il.issue_id " +
"LEFT JOIN issue_assignee ia ON i.id = ia.issue_id " +
"LEFT JOIN comment c ON i.id = c.issue_id " +
"LEFT JOIN milestone m ON i.milestone_id = m.id " +
"WHERE i.is_closed = ? ");
"LEFT JOIN milestone m ON i.milestone_id = m.id");

List<String> conditions = new ArrayList<>();
List<Object> params = new ArrayList<>();
params.add(issueQueryDto.getIsClosed()); // 열림/닫힘 여부는 기본으로 설정되어 있는 필터

appendAuthorFilter(sql, params, issueQueryDto.getAuthorId());
appendAssigneeFilter(sql, params, issueQueryDto.getAssigneeId());
appendMentionedFilter(sql, params, issueQueryDto.getMentionedId());
appendLabelFilter(sql, params, labelId);
appendMilestoneFilter(sql, params, milestoneId);

sql.append("ORDER BY i.create_date DESC, i.id DESC;"); // 최신순으로 정렬.
appendAuthorFilter(conditions, params, issueQueryDto.getAuthorId());
appendAssigneeFilter(conditions, params, issueQueryDto.getAssigneeId());
appendMentionedFilter(conditions, params, issueQueryDto.getMentionedId());
appendLabelFilter(conditions, params, labelId);
appendMilestoneFilter(conditions, params, milestoneId);
appendConditions(conditions, sql);
sql.append(" ORDER BY i.create_date DESC, i.id DESC;"); // 최신순으로 정렬

result.put("params", params.toArray());
result.put("sql", sql.toString());
return result;
}

private void appendAuthorFilter(StringBuilder sql, List<Object> params, String authorId) {
private void appendAuthorFilter(List<String> conditions, List<Object> params, String authorId) {
if (StringUtils.hasText(authorId)) { // 작성자 필터 조건이 있다면
sql.append("AND i.member_id = ? ");
conditions.add("i.member_id = ?");
params.add(authorId);
}
}

private void appendAssigneeFilter(StringBuilder sql, List<Object> params, String assigneeId) {
private void appendAssigneeFilter(List<String> conditions, List<Object> params, String assigneeId) {
if (StringUtils.hasText(assigneeId)) { // 담당자 필터 조건이 있다면
sql.append("AND ia.member_id = ? ");
conditions.add("ia.member_id = ?");
params.add(assigneeId);
}
if (noValues.contains("assignee")) { // 담당자가 없는 필터 조건
sql.append("AND ia.member_id IS NULL ");
conditions.add("ia.member_id IS NULL");
}
}

private void appendMentionedFilter(StringBuilder sql, List<Object> params, String mentionedId) {
private void appendMentionedFilter(List<String> conditions, List<Object> params, String mentionedId) {
if (StringUtils.hasText(mentionedId)) { // 댓글을 작성한 사용자 필터 조건이 있다면
sql.append("AND c.member_id = ? ");
conditions.add("c.member_id = ?");
params.add(mentionedId);
}
}

private void appendLabelFilter(StringBuilder sql, List<Object> params, Long labelId) {
private void appendLabelFilter(List<String> conditions, List<Object> params, Long labelId) {
if (labelId != null) { // 라벨 필터 조건이 있다면
sql.append("AND il.label_id = ? ");
conditions.add("il.label_id = ?");
params.add(labelId);
}
if (noValues.contains("label")) { // 라벨이 없는 필터 조건
sql.append("AND il.label_id IS NULL ");
conditions.add("il.label_id IS NULL");
}
}

private void appendMilestoneFilter(StringBuilder sql, List<Object> params, Long milestoneId) {
private void appendMilestoneFilter(List<String> conditions, List<Object> params, Long milestoneId) {
if (milestoneId != null) { // 마일스톤 필터 조건이 있다면
sql.append("AND i.milestone_id = ? ");
conditions.add("i.milestone_id = ?");
params.add(milestoneId);
}
if (noValues.contains("milestone")) { // 마일스톤이 없는 필터 조건
sql.append("AND i.milestone_id IS NULL ");
conditions.add("i.milestone_id IS NULL");
}
}

private void appendConditions(List<String> conditions, StringBuilder sql) {
if (!conditions.isEmpty()) {
sql.append(" WHERE ");
sql.append(String.join(" AND ", conditions));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
@ToString
@RequiredArgsConstructor
public class IssueCountDto {
private final long openedIssueCount;
private final long closedIssueCount;
private final long openedCount;
private final long closedCount;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.issuetracker.issue.dto;

import java.time.LocalDateTime;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
@Builder
@EqualsAndHashCode

Choose a reason for hiding this comment

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

동일성 vs 동등성 개념의 차이를 확인해보시면 좋을 것 같습니다!

public class IssueFilterResult {
private final Long id;
private final String title;
private final LocalDateTime createDate;
private final Boolean isClosed;
private final String authorId;
private final String milestoneName;
private final String closedCount;
private final String openCount;
}
Loading