Skip to content

Commit

Permalink
Merge pull request #26 from AJD-Archive/develop
Browse files Browse the repository at this point in the history
Feat(#6, #12,#14, #15): 로그인 기능, 블록 생성, queryDSL 기능, deloy.yml 파일 추가
  • Loading branch information
dongkyun0713 authored Jul 17, 2024
2 parents 96681ed + 2bf6049 commit 61c0824
Show file tree
Hide file tree
Showing 51 changed files with 1,874 additions and 9 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ on:
- develop

env:
DOCKER_IMAGE_NAME:dongkyun0713/kkeujeok_repository
EC2_HOST:${{ EC2_HOST }}
EC2_SSH_USER:ec2-user
PRIVATE_KEY:${{ secrets.EC2_SSH_PRIVATE_KEY }}
DOCKER_IMAGE_NAME: ${{ secrets.DEV_DOCKER_IMAGE_NAME }}
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_SSH_USER: ec2-user
PRIVATE_KEY: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
CONTAINER_NAME: ${{ secrets.DEV_CONTAINER_NAME }}

jobs:
build-and-push-docker:
Expand All @@ -30,7 +31,7 @@ jobs:
run: echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.yml

- name: Build with Gradle
run: ./gradlew build -x tesst
run: ./gradlew build -x test

- name: Build the Docker image
run: docker build . --file Dockerfile --tag ${{ env.DOCKER_IMAGE_NAME }}:latest
Expand All @@ -55,12 +56,12 @@ jobs:
username: ${{ env.EC2_SSH_USER }}
key: ${{ env.PRIVATE_KEY }}
script: |
CONTAINER_ID=$(sudo docker ps -q --filter "publish=80-8080")
CONTAINER_ID=$(sudo docker ps -q --filter "publish=8089-8089")
if [ ! -z "$CONTAINER_ID" ]; then
sudo docker stop $CONTAINER_ID
sudo docker rm $CONTAINER_ID
fi
sudo docker pull ${{ env.DOCKER_HUB }}
sudo docker run --name ${{ env.CONTAINER_NAME }} -d -p 80:8080 -e TZ=Asia/Seoul ${{ env.DOCKER_HUB }}
sudo docker pull ${{ env.DOCKER_IMAGE_NAME }}
sudo docker run --name ${{ env.CONTAINER_NAME }} -d -p 8089:8089 -e TZ=Asia/Seoul ${{ env.DOCKER_IMAGE_NAME }}
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM amazoncorretto:17
# FROM openjdk:17-jdk
ARG JAR_FILE=build/libs/*.jar

COPY ${JAR_FILE} kkeujeok-backend-0.0.1-SNAPSHOT.jar
# COPY build/libs/*.jar my-project.jar
ENTRYPOINT ["java","-jar","/kkeujeok-backend-0.0.1-SNAPSHOT.jar"]

RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
19 changes: 19 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,35 @@ configurations {

repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'

// logback
implementation 'com.github.napstr:logback-discord-appender:1.0.0'
implementation 'com.github.maricn:logback-slack-appender:1.4.0'

runtimeOnly 'com.mysql:mysql-connector-j'

annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

//jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package shop.kkeujeok.kkeujeokbackend.auth.api;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.RefreshTokenReqDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.TokenReqDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.MemberLoginResDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.UserInfo;
import shop.kkeujeok.kkeujeokbackend.auth.application.AuthMemberService;
import shop.kkeujeok.kkeujeokbackend.auth.application.AuthService;
import shop.kkeujeok.kkeujeokbackend.auth.application.AuthServiceFactory;
import shop.kkeujeok.kkeujeokbackend.auth.application.TokenService;
import shop.kkeujeok.kkeujeokbackend.global.jwt.api.dto.TokenDto;
import shop.kkeujeok.kkeujeokbackend.global.oauth.GoogleAuthService;
import shop.kkeujeok.kkeujeokbackend.global.oauth.KakaoAuthService;
import shop.kkeujeok.kkeujeokbackend.global.template.RspTemplate;
import shop.kkeujeok.kkeujeokbackend.member.domain.SocialType;

@RestController
@Slf4j
@RequestMapping("/api")
@RequiredArgsConstructor
public class AuthController {
private final AuthServiceFactory authServiceFactory;
private final AuthMemberService memberService;
private final TokenService tokenService;
private final GoogleAuthService getGoogleAccessToken;
private final KakaoAuthService kakaoAuthService;

@GetMapping("oauth2/callback/google")
public JsonNode googleCallback(@RequestParam(name = "code") String code) {
return getGoogleAccessToken.getGoogleIdToken(code);
}

@GetMapping("oauth2/callback/kakao")
public JsonNode kakaoCallback(@RequestParam(name = "code") String code) {
return kakaoAuthService.getKakaoAccessToken(code);
}

// @Operation(summary = "로그인 후 토큰 발급", description = "액세스, 리프레쉬 토큰을 발급합니다.")
@PostMapping("/{provider}/token")
public RspTemplate<TokenDto> generateAccessAndRefreshToken(
@PathVariable(name = "provider") String provider,
@RequestBody TokenReqDto tokenReqDto) {
AuthService authService = authServiceFactory.getAuthService(provider);
UserInfo userInfo = authService.getUserInfo(tokenReqDto.authCode());

MemberLoginResDto getMemberDto = memberService.saveUserInfo(userInfo,
SocialType.valueOf(provider.toUpperCase()));
TokenDto getToken = tokenService.getToken(getMemberDto);

return new RspTemplate<>(HttpStatus.OK, "토큰 발급", getToken);
}

// @Operation(summary = "액세스 토큰 재발급", description = "리프레쉬 토큰으로 액세스 토큰을 발급합니다.")
@PostMapping("/token/access")
public RspTemplate<TokenDto> generateAccessToken(@RequestBody RefreshTokenReqDto refreshTokenReqDto) {
TokenDto getToken = tokenService.generateAccessToken(refreshTokenReqDto);

return new RspTemplate<>(HttpStatus.OK, "액세스 토큰 발급", getToken);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.request;

public record RefreshTokenReqDto(
String refreshToken
){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.request;

public record TokenReqDto(
String authCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.response;

import lombok.Builder;

@Builder
public record AccessAndRefreshTokenResDto(
String accessToken,
String refreshToken
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.response;


import lombok.Builder;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;

@Builder
public record MemberLoginResDto(
Member findMember
) {
public static MemberLoginResDto from(Member member) {
return MemberLoginResDto.builder()
.findMember(member)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package shop.kkeujeok.kkeujeokbackend.auth.api.dto.response;

public record UserInfo(
String email,
String name,
String picture,
String nickname
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.MemberLoginResDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.UserInfo;
import shop.kkeujeok.kkeujeokbackend.global.entity.Status;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;
import shop.kkeujeok.kkeujeokbackend.member.domain.Role;
import shop.kkeujeok.kkeujeokbackend.member.domain.SocialType;
import shop.kkeujeok.kkeujeokbackend.member.domain.repository.MemberRepository;

import java.util.Optional;

@Service
@Transactional(readOnly = true)
public class AuthMemberService {
private final MemberRepository memberRepository;

public AuthMemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@Transactional
public MemberLoginResDto saveUserInfo(UserInfo userInfo, SocialType provider) {
validateNotFoundEmail(userInfo.email());

Member member = getExistingMemberOrCreateNew(userInfo, provider);

validateSocialType(member, provider);

return MemberLoginResDto.from(member);
}

private void validateNotFoundEmail(String email) {
if (email == null) {
throw new RuntimeException();
}
}

private Member getExistingMemberOrCreateNew(UserInfo userInfo, SocialType provider) {
return memberRepository.findByEmail(userInfo.email()).orElseGet(() -> createMember(userInfo, provider));
}

private Member createMember(UserInfo userInfo, SocialType provider) {
String userPicture = getUserPicture(userInfo.picture());
String name = userInfo.name();
String nickname = userInfo.nickname();

if (name == null && nickname != null) {
name = nickname;
} else if (nickname == null && name != null) {
nickname = name;
}

return memberRepository.save(
Member.builder()
.status(Status.A)
.email(userInfo.email())
.name(name)
.picture(userPicture)
.socialType(provider)
.role(Role.ROLE_USER)
.firstLogin(true)
.nickname(nickname)
.build()
);
}

private String getUserPicture(String picture) {
return Optional.ofNullable(picture)
.map(this::convertToHighRes).orElseThrow();
}

private String convertToHighRes(String url){
return url.replace("s96-c", "s2048-c");
}

private void validateSocialType(Member member, SocialType provider) {
if (!provider.equals(member.getSocialType())) {
throw new RuntimeException();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.UserInfo;

public interface AuthService {
UserInfo getUserInfo(String authCode);

String getProvider();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class AuthServiceFactory {
private final Map<String, AuthService> authServiceMap;

@Autowired
public AuthServiceFactory(List<AuthService> authServiceList) {
authServiceMap = new HashMap<>();
for (AuthService authService : authServiceList) {
authServiceMap.put(authService.getProvider(), authService);
}
}

public AuthService getAuthService(String provider) {
return authServiceMap.get(provider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package shop.kkeujeok.kkeujeokbackend.auth.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.RefreshTokenReqDto;
import shop.kkeujeok.kkeujeokbackend.auth.api.dto.response.MemberLoginResDto;
import shop.kkeujeok.kkeujeokbackend.global.jwt.TokenProvider;
import shop.kkeujeok.kkeujeokbackend.global.jwt.api.dto.TokenDto;
import shop.kkeujeok.kkeujeokbackend.global.jwt.domain.Token;
import shop.kkeujeok.kkeujeokbackend.global.jwt.domain.repository.TokenRepository;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;
import shop.kkeujeok.kkeujeokbackend.member.domain.repository.MemberRepository;

@Service
@Transactional(readOnly = true)
public class TokenService {

private final TokenProvider tokenProvider;
private final TokenRepository tokenRepository;
private final MemberRepository memberRepository;

public TokenService(TokenProvider tokenProvider, TokenRepository tokenRepository, MemberRepository memberRepository) {
this.tokenProvider = tokenProvider;
this.tokenRepository = tokenRepository;
this.memberRepository = memberRepository;
}

@Transactional
public TokenDto getToken(MemberLoginResDto memberLoginResDto) {
TokenDto tokenDto = tokenProvider.generateToken(memberLoginResDto.findMember().getEmail());

tokenSaveAndUpdate(memberLoginResDto, tokenDto);

return tokenDto;
}

private void tokenSaveAndUpdate(MemberLoginResDto memberLoginResDto, TokenDto tokenDto) {
if (!tokenRepository.existsByMember(memberLoginResDto.findMember())) {
tokenRepository.save(Token.builder()
.member(memberLoginResDto.findMember())
.refreshToken(tokenDto.refreshToken())
.build());
}

refreshTokenUpdate(memberLoginResDto, tokenDto);
}

private void refreshTokenUpdate(MemberLoginResDto memberLoginResDto, TokenDto tokenDto) {
Token token = tokenRepository.findByMember(memberLoginResDto.findMember()).orElseThrow();
token.refreshTokenUpdate(tokenDto.refreshToken());
}

@Transactional
public TokenDto generateAccessToken(RefreshTokenReqDto refreshTokenReqDto) {
if (!tokenRepository.existsByRefreshToken(refreshTokenReqDto.refreshToken()) || !tokenProvider.validateToken(refreshTokenReqDto.refreshToken())) {
throw new RuntimeException();
}

Token token = tokenRepository.findByRefreshToken(refreshTokenReqDto.refreshToken()).orElseThrow();
Member member = memberRepository.findById(token.getMember().getId()).orElseThrow();

return tokenProvider.generateAccessTokenByRefreshToken(member.getEmail(), token.getRefreshToken());
}

}
Loading

0 comments on commit 61c0824

Please sign in to comment.