Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
75 changes: 75 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Deploy to EC2 with Docker

on:
push:
branches: [ "main" ]

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'

- name: Grant execute permission to gradlew
run: chmod +x ./gradlew

- name: Build JAR
run: ./gradlew build -x test

- name: Zip project for transfer
run: zip -r deploy-package.zip build Dockerfile gradlew gradlew.bat settings.gradle build.gradle src

- name: Copy zipped project to EC2
uses: appleboy/[email protected]
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
source: "deploy-package.zip"
target: "/home/ubuntu/app"

- name: SSH into EC2 and deploy
uses: appleboy/[email protected]
env:
DB_URL: ${{ secrets.DB_URL }}
DB_USERNAME: ${{ secrets.DB_USERNAME }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
envs: DB_URL,DB_USERNAME,DB_PASSWORD,JWT_SECRET
script: |
sudo apt update && sudo apt install unzip -y

cd ~/app
rm -rf app-unzipped
mkdir app-unzipped
unzip deploy-package.zip -d app-unzipped
cd app-unzipped

# application.properties 만들기
mkdir -p src/main/resources
cat <<EOF > src/main/resources/application.properties
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=${JWT_SECRET}
EOF

docker stop blog-be-itor || true
docker rm blog-be-itor || true
docker build -t blog-be-itor .
docker run -d -p 8080:8080 \
-v $(pwd)/src/main/resources/application.properties:/app/application.properties \
--name blog-be-itor blog-be-itor
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM eclipse-temurin:21-jdk
COPY build/libs/blog-0.0.1-SNAPSHOT.jar app.jar
COPY src/main/resources/application.properties /app/application.properties
ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.config.location=file:/app/application.properties"]
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.mysql:mysql-connector-j'

// H2 추가
runtimeOnly 'com.h2database:h2'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
80 changes: 80 additions & 0 deletions src/main/java/com/blog/token/application/TokenServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.blog.token.application;

import com.blog.token.domain.Token;
import com.blog.token.domain.TokenRepository;
import com.blog.token.domain.TokenService;
import com.blog.token.infrastructure.JwtTokenProvider;
import com.blog.token.presentation.dto.RefreshTokenRequest;
import com.blog.token.presentation.dto.TokenResponse;
import com.blog.user.domain.User;
import com.blog.user.domain.UserRepository;
import com.blog.user.presentation.dto.UserResponse;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;

@Service
public class TokenServiceImpl implements TokenService {

private final TokenRepository tokenRepository;
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;

public TokenServiceImpl(TokenRepository tokenRepository, UserRepository userRepository, JwtTokenProvider jwtTokenProvider) {
this.tokenRepository = tokenRepository;
this.userRepository = userRepository;
this.jwtTokenProvider = jwtTokenProvider;
}

@Override
public TokenResponse refresh(RefreshTokenRequest request) {
String refreshToken = request.getRefreshToken();

if (!jwtTokenProvider.validateToken(refreshToken)) {
throw new RuntimeException("유효하지 않은 리프레시 토큰입니다.");
}

Long userId = Long.parseLong(jwtTokenProvider.parseUserId(refreshToken));
Token saved = tokenRepository.findByUserId(userId);

if (saved == null || !saved.getRefreshToken().equals(refreshToken)) {
throw new RuntimeException("토큰이 일치하지 않거나 존재하지 않습니다.");
}

tokenRepository.deleteByUserId(userId);

User user = userRepository.findById(userId);
if (user == null) {
throw new RuntimeException("사용자를 찾을 수 없습니다.");
}

String newAccessToken = jwtTokenProvider.createAccessToken(user);
String newRefreshToken = jwtTokenProvider.createRefreshToken(user);
Timestamp expiresAt = jwtTokenProvider.getRefreshTokenExpiry();

Token newToken = new Token(user.getUserId(), newRefreshToken, expiresAt, new Timestamp(System.currentTimeMillis()));
tokenRepository.save(newToken);

UserResponse userResponse = new UserResponse();
userResponse.setUserId(user.getUserId());
userResponse.setEmail(user.getEmail());
userResponse.setName(user.getName());
userResponse.setNickname(user.getNickname());
userResponse.setProfileImage(user.getProfileImage());

if (user.getCreatedAt() != null) {
userResponse.setCreatedAt(user.getCreatedAt().toString());
}
if (user.getUpdatedAt() != null) {
userResponse.setUpdatedAt(user.getUpdatedAt().toString());
}

TokenResponse response = new TokenResponse();
response.setAccessToken(newAccessToken);
response.setRefreshToken(newRefreshToken);
response.setExpiresAt(expiresAt.toString());
response.setUser(userResponse);

return response;
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/blog/token/domain/TokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.blog.token.domain;

import com.blog.token.presentation.dto.RefreshTokenRequest;
import com.blog.token.presentation.dto.TokenResponse;

public interface TokenService {
TokenResponse refresh(RefreshTokenRequest request);
}
20 changes: 20 additions & 0 deletions src/main/java/com/blog/token/presentation/TokenController.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
package com.blog.token.presentation;

import com.blog.token.domain.TokenService;
import com.blog.token.presentation.dto.RefreshTokenRequest;
import com.blog.token.presentation.dto.TokenResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class TokenController {

private final TokenService tokenService;

public TokenController(TokenService tokenService) {
this.tokenService = tokenService;
}

@PostMapping("/refresh")
public ResponseEntity<TokenResponse> refresh(@RequestBody RefreshTokenRequest request) {
TokenResponse response = tokenService.refresh(request);
return ResponseEntity.ok(response);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/blog/user/application/UserProfileService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.blog.user.application;

import com.blog.user.presentation.dto.UserResponse;
import com.blog.user.presentation.dto.UserUpdateRequest;

//사용자 정보 관리 전용 인터페이스
public interface UserProfileService {
UserResponse getUser(Long userId); // 조회
UserResponse updateUser(Long userId, UserUpdateRequest request); // 수정
void deleteUser(Long userId); // 삭제
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.blog.user.application;

import com.blog.user.domain.User;
import com.blog.user.domain.UserRepository;
import com.blog.user.presentation.dto.UserResponse;
import com.blog.user.presentation.dto.UserUpdateRequest;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;

@Service
public class UserProfileServiceImpl implements UserProfileService {

private final UserRepository userRepository;

public UserProfileServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserResponse getUser(Long userId) {
User user = userRepository.findById(userId);
if (user == null || !user.isActive()) {
throw new RuntimeException("존재하지 않는 사용자입니다.");
}

UserResponse response = new UserResponse();
response.setUserId(user.getUserId());
response.setEmail(user.getEmail());
response.setName(user.getName());
response.setNickname(user.getNickname());
response.setProfileImage(user.getProfileImage());
response.setCreatedAt(user.getCreatedAt().toString());

return response;
}

@Override
public UserResponse updateUser(Long userId, UserUpdateRequest request) {
Timestamp updatedAt = new Timestamp(System.currentTimeMillis());
userRepository.update(userId, request.getNickname(), request.getProfileImage(), updatedAt);

User updated = userRepository.findById(userId);
if (updated == null) {
throw new RuntimeException("사용자 정보를 수정할 수 없습니다.");
}

UserResponse response = new UserResponse();
response.setUserId(updated.getUserId());
response.setEmail(updated.getEmail());
response.setNickname(updated.getNickname());
response.setProfileImage(updated.getProfileImage());
response.setUpdatedAt(updated.getUpdatedAt().toString());

return response;
}

@Override
public void deleteUser(Long userId) {
userRepository.softDelete(userId);
}
}
4 changes: 3 additions & 1 deletion src/main/java/com/blog/user/domain/UserRepository.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.blog.user.domain;

import java.sql.Timestamp;

public interface UserRepository {
User findByEmail(String email); // 로그인용
User findById(Long userId); // 유저 조회용
void save(User user); // 회원가입용
void update(User user); // 정보 수정용
void update(Long userId, String nickname, String profileImage, Timestamp updatedAt); // 정보 수정용
void softDelete(Long userId); // 탈퇴용
}
Loading