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

[team-32][BE][산토리 & 포키] IssueTracker 5차 PR #237

Open
wants to merge 54 commits into
base: team-32
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
91084c1
chore: github actions 설정
seyoung755 Jun 26, 2022
53d56dd
chore: github actions 디렉토리 설정
seyoung755 Jun 26, 2022
4851abd
style: github actions 스크립트 수정
seyoung755 Jun 26, 2022
7bf558d
chore: 패키지 경로 수정
seyoung755 Jun 26, 2022
51b26bd
chore: application-prod 생성 설정
seyoung755 Jun 26, 2022
a882d27
Update actions.yml
seyoung755 Jun 26, 2022
10e891e
chore: step 순서 변경
seyoung755 Jun 26, 2022
f49f23a
chore: application-yml 파일 출력
seyoung755 Jun 26, 2022
46c5dcc
chore: docker 빌드 후 배포 스크립트 작성
seyoung755 Jun 26, 2022
da0eb2d
style: indent 수정
seyoung755 Jun 26, 2022
93ba08b
chore: Dockerfile 작성
seyoung755 Jun 26, 2022
a74782d
style: 컨테이너 버전 명기
seyoung755 Jun 26, 2022
ce86ad0
Update actions.yml
seyoung755 Jun 26, 2022
0b24365
style: 오타 수정
seyoung755 Jun 26, 2022
d339c59
style: 불필요한 옵션 수정
seyoung755 Jun 26, 2022
056999a
style: sudo 추가
seyoung755 Jun 26, 2022
f30ab23
style: 오타 수정
seyoung755 Jun 26, 2022
b031de5
chore: 불필요한 script 삭제
seyoung755 Jun 26, 2022
0549cf0
chore: 빌드 시 프로필 명기
seyoung755 Jun 26, 2022
a4aca8b
chore: 빌드 시 환경변수가 들어간 application.yml으로 교체
seyoung755 Jun 26, 2022
2e49f07
chore: plain jar 생성되지 않도록 설정
seyoung755 Jun 26, 2022
d130535
refactor : 환경변수를 필드로 가지는 Property 클래스 작성
Jun 28, 2022
b8d00e0
refactor : Oauth 프로바이더에 공통 메서드 추가
Jun 28, 2022
b1a6f04
refactor : 로그인 api 추상화
Jun 28, 2022
3d4ec4c
refactor : ExceptionType에 @Getter 어노테이션 추가
Jun 27, 2022
45b4e7c
feat : Issue, Comment, Label, Milestone 엔티티 작성
Jun 27, 2022
9ef688d
feat : Issue, Label, Milestone Repository 작성
Jun 27, 2022
1a7fdfb
feat : 서비스 레이어에서 사용할 Dto 작성
Jun 27, 2022
3003785
feat : 이슈 등록 api 구현
Jun 27, 2022
94c36c3
refactor : findAllByName -> findAllByNameIn으로 메서드명 변경
Jun 27, 2022
ebff7c8
feat: Column 제약조건 추가
seyoung755 Jun 27, 2022
8b23037
feat: equals 메서드 재정의
seyoung755 Jun 27, 2022
3d36625
feat: Issue assignee 수정 api 구현
seyoung755 Jun 27, 2022
247f6ba
feat: Issue label 수정 api 구현
seyoung755 Jun 27, 2022
9648fed
feat: Issue milestone 수정 api 구현
seyoung755 Jun 27, 2022
967a251
feat: 생성일자, 삭제 필드를 포함한 Base entity 생성
seyoung755 Jun 27, 2022
44b21bd
feat: 엔티티 객체에 Base Entity 적용
seyoung755 Jun 27, 2022
a5dfbd9
feat: boolean 타입 정의
seyoung755 Jun 27, 2022
635ef6a
feat: Issue 제목, 내용 수정 및 삭제 api 구현
seyoung755 Jun 27, 2022
2c08fb1
docs: refresh api 설명 작성
seyoung755 Jun 27, 2022
f75e6ec
fix: 프로퍼티 스캔 설정 추가
seyoung755 Jun 28, 2022
073c5a2
feat: 이슈 열거나 닫는 api 추가
seyoung755 Jun 28, 2022
387add8
refactor: 불필요한 dto 삭제
seyoung755 Jun 28, 2022
a7e5acb
feat: 이슈를 여러 개 열고 닫을 수 있도록 구현
seyoung755 Jun 28, 2022
164053a
feat: 이슈가 열려있는 지 판단하는 메서드 추가
seyoung755 Jun 28, 2022
a407ad6
feat: 마일스톤 조회/작성 api 구현
seyoung755 Jun 28, 2022
548f7b0
refactor : 로그인 시 refresh 토큰은 쿠키에 담아주도록 변경
Jun 28, 2022
e2022d2
refactor : 토큰 갱신 api에서 refresh 토큰을 쿠키에서 조회하도록 변경
Jun 28, 2022
13ede8f
fix : 마일스톤 엔티티 수정
Jun 28, 2022
f1fad6b
refactor: oauth 로그인 시 클라이언트 url로 리다이렉트 처리
Jun 28, 2022
eb1f94b
refactor : 마일스톤 조회 시 발생하는 n+1 문제 해결
Jun 28, 2022
fa1476a
fix: 쿠키 CORS 오류 해결 테스트
seyoung755 Jun 28, 2022
6eae0be
chore: github actions 스크립트 수정
seyoung755 Jun 28, 2022
46ae914
fix: CORS 오류 해결 중
seyoung755 Jun 28, 2022
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
71 changes: 71 additions & 0 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: CI

on:
push:
branches: [ "deploy" ]
pull_request:
branches: [ "develop-BE" ]

workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: "./be"

steps:
- uses: actions/checkout@v3

- name: Set application.yml for deploy
working-directory: ./be/src/main/resources
run: |
cat /dev/null > application.yml
echo "${{ secrets.APPLICATION_DEVELOP }}" >> ./application.yml
echo "${{ secrets.APPLICATION }}" >> ./application-prod.yml

- name: Setup Java JDK
uses: actions/[email protected]
with:
distribution: 'adopt-hotspot'
java-version: '11'

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Setup MySQL
uses: mirromutth/[email protected]
with:
mysql database: issuetracker
mysql user: root
mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }}

- name: Build with Gradle
run: ./gradlew build

- name: Docker Login
uses: docker/[email protected]
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_SECRET }}

- name: Docker build
run: |
docker build -t issue-tracker .
docker tag issue-tracker ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest
docker push ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest

- name: deploy!
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
sudo docker ps -a -q -f "name=issue-tracker" | grep -q . && sudo docker stop issue-tracker && sudo docker rm issue-tracker | true
sudo docker rmi ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest
sudo docker tag ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest issue-tracker
sudo docker run -d --name issue-tracker -p 80:8080 issue-tracker
5 changes: 5 additions & 0 deletions be/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM adoptopenjdk/openjdk11
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "/app.jar"]

4 changes: 4 additions & 0 deletions be/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ plugins {
id 'java'
}

jar {
enabled = false
}

group = 'com.codesquad'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@ConfigurationPropertiesScan
@EnableJpaAuditing
@SpringBootApplication
public class IssuetrackerApplication {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ public class AuthService {
private final JwtProvider jwtProvider;
private final UserService userService;

public AccessTokenDto refreshAccessToken(String authorization) {
String refreshToken = TokenParser.parseToken(authorization);

public AccessTokenDto refreshAccessToken(String refreshToken) {
jwtProvider.validateJwtToken(refreshToken);
Long userId = jwtProvider.getClaimFromToken(refreshToken, JwtProvider.USER_ID_CLAIM_KEY);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,46 @@
import com.codesquad.issuetracker.auth.application.AuthService;
import com.codesquad.issuetracker.auth.presentation.dto.AccessTokenDto;
import com.codesquad.issuetracker.exception.domain.type.AuthExceptionType;
import com.codesquad.issuetracker.exception.dto.ExceptionResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingRequestCookieException;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;

@RequiredArgsConstructor
@RestController
public class AuthController {

private final AuthService authService;

@Operation(summary = "Access 토큰 갱신하기", description = "Refresh token을 통해 Access 토큰을 재발급합니다.")
@GetMapping("/auth/refresh")
public AccessTokenDto refresh(HttpServletRequest request) {
String refreshToken = request.getHeader("Authorization");
return authService.refreshAccessToken(refreshToken);
public AccessTokenDto refresh(@CookieValue(value = "refreshToken") Cookie refreshToken) {
return authService.refreshAccessToken(refreshToken.getValue());
}

@ExceptionHandler(JWTVerificationException.class)
public ResponseEntity<String> handleJwtVerificationException(JWTVerificationException ex) {
public ResponseEntity<ExceptionResponseDto> handleJwtVerificationException(JWTVerificationException ex) {
ex.printStackTrace();
AuthExceptionType type = AuthExceptionType.INVALID_REFRESH_TOKEN;
return ResponseEntity.status(type.getStatusCode()).body(type.getMessage());
ExceptionResponseDto exceptionResponseDto =
new ExceptionResponseDto(type.getErrorCode(), type.getMessage());
return ResponseEntity.status(type.getStatusCode()).body(exceptionResponseDto);
}

@ExceptionHandler(MissingRequestCookieException.class)
public ResponseEntity<ExceptionResponseDto> handleMissingRequestCookieException(MissingRequestCookieException ex) {
ex.printStackTrace();
AuthExceptionType type = AuthExceptionType.REFRESH_TOKEN_NOT_FOUND;
ExceptionResponseDto exceptionResponseDto =
new ExceptionResponseDto(type.getErrorCode(), type.getMessage());
return ResponseEntity.status(type.getStatusCode()).body(exceptionResponseDto);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.codesquad.issuetracker.comment.domain;

import com.codesquad.issuetracker.common.domain.BaseEntity;
import com.codesquad.issuetracker.issue.domain.Issue;
import com.codesquad.issuetracker.user.domain.User;

import javax.persistence.*;

@Entity
public class Comment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String content;

@ManyToOne(fetch = FetchType.LAZY)
private User user;

@ManyToOne(fetch = FetchType.LAZY)
private Issue issue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

import com.codesquad.issuetracker.auth.presentation.argumentresolver.AuthArgumentResolver;
import com.codesquad.issuetracker.auth.presentation.interceptor.AuthInterceptor;
import com.codesquad.issuetracker.user.application.oauth.OAuthProvider;
import com.codesquad.issuetracker.user.domain.LoginType;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Configuration
Expand Down Expand Up @@ -44,4 +50,10 @@ public void addCorsMappings(CorsRegistry registry) {
.allowedOrigins("*")
.allowedMethods("*");
}

@Bean
public EnumMap<LoginType, OAuthProvider> oAuthProviderEnumMap(List<OAuthProvider> oAuthProviders) {
return new EnumMap<>(oAuthProviders.stream()
.collect(Collectors.toMap(OAuthProvider::getOAuthType, u -> u)));
Comment on lines +54 to +57

Choose a reason for hiding this comment

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

꼭 EnumMap으로 만들필요가 있을까요? enummap이 가져다주는 어떤 장점이 있는지 고려해보시면 좋을 것 같습니다 ㅎㅎ

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.codesquad.issuetracker.common.domain;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {

@CreatedDate
@Column(columnDefinition = "TIMESTAMP", nullable = false)
private LocalDateTime createdAt;

@Column(columnDefinition = "BOOLEAN", nullable = false)
private boolean isDeleted;
Comment on lines +19 to +20

Choose a reason for hiding this comment

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

soft delete도 적용하시는군요!


protected void changeDeleted(boolean isDeleted) {
this.isDeleted = isDeleted;
}
Comment on lines +22 to +24

Choose a reason for hiding this comment

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

다시 복구 시킬 케이스도 고려하신걸까요?

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.codesquad.issuetracker.common.properties;

import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;

@Getter
@ConstructorBinding
@ConfigurationProperties(prefix = "oauth.github")
public class GithubProperty {

private final String clientId;
private final String clientSecret;
private final String redirectUrl;
private final String accessTokenUrl;
private final String resourceUrl;

public GithubProperty(String clientId, String clientSecret, String redirectUrl, String accessTokenUrl, String resourceUrl) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUrl = redirectUrl;
this.accessTokenUrl = accessTokenUrl;
this.resourceUrl = resourceUrl;
}
}
Comment on lines +10 to +25

Choose a reason for hiding this comment

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

abstract_class로 빼보는건 어떨까요?

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.codesquad.issuetracker.common.properties;

import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;

@Getter
@ConstructorBinding
@ConfigurationProperties(prefix = "oauth.google")
public class GoogleProperty {

private final String clientId;
private final String clientSecret;
private final String redirectUrl;
private final String accessTokenUrl;
private final String resourceUrl;


public GoogleProperty(String clientId, String clientSecret, String redirectUrl, String accessTokenUrl, String resourceUrl) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUrl = redirectUrl;
this.accessTokenUrl = accessTokenUrl;
this.resourceUrl = resourceUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static String parseToken(String authorization) {
token = authorization.split(" ")[1].trim();
} catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
e.printStackTrace();
throw new BusinessException(AuthExceptionType.TOKEN_NOT_FOUND);
throw new BusinessException(AuthExceptionType.ACCESS_TOKEN_NOT_FOUND);
}
return token;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.codesquad.issuetracker.exception.domain.type;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum AuthExceptionType implements ExceptionType {

TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH001", "이 api에 접근하기 위해서는 Access 토큰이 필요합니다."),
ACCESS_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH001", "이 api에 접근하기 위해서는 Access 토큰이 필요합니다."),
TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH002", "액세스 토큰이 만료되었습니다."),
INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH003", "유효하지 않은 Access 토큰입니다."),
INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH004", "유효하지 않은 Refresh 토큰입니다.");
INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH004", "유효하지 않은 Refresh 토큰입니다."),
REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH005", "refresh 토큰이 존재하지 않습니다.");

private final HttpStatus statusCode;
private final String errorCode;
Expand All @@ -19,15 +22,4 @@ public enum AuthExceptionType implements ExceptionType {
this.message = message;
}

public HttpStatus getStatusCode() {
return statusCode;
}

public String getErrorCode() {
return errorCode;
}

public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.codesquad.issuetracker.exception.domain.type;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum IssueExceptionType implements ExceptionType {

NOT_FOUND(HttpStatus.NOT_FOUND, "ISSUE001", "이슈를 찾을 수 없습니다");

private final HttpStatus statusCode;
private final String errorCode;
private final String message;

IssueExceptionType(HttpStatus statusCode, String errorCode, String message) {
this.statusCode = statusCode;
this.errorCode = errorCode;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.codesquad.issuetracker.exception.domain.type;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum MilestoneExceptionType implements ExceptionType{

NOT_FOUND(HttpStatus.NOT_FOUND, "MILE001", "마일스톤이 존재하지 않습니다.");

private final HttpStatus statusCode;
private final String errorCode;
private final String message;

MilestoneExceptionType(HttpStatus statusCode, String errorCode, String message) {
this.statusCode = statusCode;
this.errorCode = errorCode;
this.message = message;
}
}
Loading