From 47e51c0f8e84bb0c66ef0e7b29edeed07dc16c56 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 6 May 2025 20:21:54 +0900 Subject: [PATCH 01/19] Refactor: organize token refresh implementation --- .../token/application/TokenServiceImpl.java | 80 +++++++++++++++++++ .../com/blog/token/domain/TokenService.java | 8 ++ .../token/presentation/TokenController.java | 20 +++++ 3 files changed, 108 insertions(+) create mode 100644 src/main/java/com/blog/token/application/TokenServiceImpl.java create mode 100644 src/main/java/com/blog/token/domain/TokenService.java diff --git a/src/main/java/com/blog/token/application/TokenServiceImpl.java b/src/main/java/com/blog/token/application/TokenServiceImpl.java new file mode 100644 index 00000000..34493c33 --- /dev/null +++ b/src/main/java/com/blog/token/application/TokenServiceImpl.java @@ -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; + } +} diff --git a/src/main/java/com/blog/token/domain/TokenService.java b/src/main/java/com/blog/token/domain/TokenService.java new file mode 100644 index 00000000..b2f1baec --- /dev/null +++ b/src/main/java/com/blog/token/domain/TokenService.java @@ -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); +} diff --git a/src/main/java/com/blog/token/presentation/TokenController.java b/src/main/java/com/blog/token/presentation/TokenController.java index f97b440c..dd4ab047 100644 --- a/src/main/java/com/blog/token/presentation/TokenController.java +++ b/src/main/java/com/blog/token/presentation/TokenController.java @@ -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 refresh(@RequestBody RefreshTokenRequest request) { + TokenResponse response = tokenService.refresh(request); + return ResponseEntity.ok(response); + } } From 7a071bc9d63a13f04b6b8ccd9dfe9f4833d77d84 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 6 May 2025 22:10:34 +0900 Subject: [PATCH 02/19] feat: add user read, update, and delete features --- .../user/application/UserProfileService.java | 11 ++ .../application/UserProfileServiceImpl.java | 62 +++++++++ .../com/blog/user/domain/UserRepository.java | 4 +- .../infrastructure/UserRepositoryImpl.java | 123 ++++++------------ .../presentation/UserProfileController.java | 53 ++++++++ 5 files changed, 166 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/blog/user/application/UserProfileService.java create mode 100644 src/main/java/com/blog/user/application/UserProfileServiceImpl.java create mode 100644 src/main/java/com/blog/user/presentation/UserProfileController.java diff --git a/src/main/java/com/blog/user/application/UserProfileService.java b/src/main/java/com/blog/user/application/UserProfileService.java new file mode 100644 index 00000000..bee540b0 --- /dev/null +++ b/src/main/java/com/blog/user/application/UserProfileService.java @@ -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); // 삭제 +} diff --git a/src/main/java/com/blog/user/application/UserProfileServiceImpl.java b/src/main/java/com/blog/user/application/UserProfileServiceImpl.java new file mode 100644 index 00000000..ef1d7da3 --- /dev/null +++ b/src/main/java/com/blog/user/application/UserProfileServiceImpl.java @@ -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); + } +} diff --git a/src/main/java/com/blog/user/domain/UserRepository.java b/src/main/java/com/blog/user/domain/UserRepository.java index a23dde33..6b99adc6 100644 --- a/src/main/java/com/blog/user/domain/UserRepository.java +++ b/src/main/java/com/blog/user/domain/UserRepository.java @@ -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); // 탈퇴용 } diff --git a/src/main/java/com/blog/user/infrastructure/UserRepositoryImpl.java b/src/main/java/com/blog/user/infrastructure/UserRepositoryImpl.java index 6c8d71b6..7d5d43b2 100644 --- a/src/main/java/com/blog/user/infrastructure/UserRepositoryImpl.java +++ b/src/main/java/com/blog/user/infrastructure/UserRepositoryImpl.java @@ -2,119 +2,70 @@ import com.blog.user.domain.User; import com.blog.user.domain.UserRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; -import java.sql.*; -import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; @Repository public class UserRepositoryImpl implements UserRepository { - private final DataSource dataSource; + private final JdbcTemplate jdbcTemplate; - public UserRepositoryImpl(DataSource dataSource) { - this.dataSource = dataSource; + public UserRepositoryImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void save(User user) { + String sql = "INSERT INTO users (email, password, name, nickname, birth_date, profile_image, introduction, auth_method, created_at, updated_at, is_active) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + jdbcTemplate.update(sql, + user.getEmail(), + user.getPassword(), + user.getName(), + user.getNickname(), + user.getBirthDate(), + user.getProfileImage(), + user.getIntroduction(), + user.getAuthMethod(), + user.getCreatedAt(), + user.getUpdatedAt(), + user.isActive() + ); } @Override public User findByEmail(String email) { String sql = "SELECT * FROM users WHERE email = ?"; - try (Connection conn = dataSource.getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { - - ps.setString(1, email); - ResultSet rs = ps.executeQuery(); - - if (rs.next()) { - return mapRowToUser(rs); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; + return jdbcTemplate.query(sql, new Object[]{email}, userRowMapper()) + .stream().findFirst().orElse(null); } @Override public User findById(Long userId) { String sql = "SELECT * FROM users WHERE user_id = ?"; - try (Connection conn = dataSource.getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { - - ps.setLong(1, userId); - ResultSet rs = ps.executeQuery(); - - if (rs.next()) { - return mapRowToUser(rs); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - } - - @Override - public void save(User user) { - String sql = "INSERT INTO users (email, password, name, nickname, birth_date, profile_image, introduction, auth_method, created_at, updated_at, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - try (Connection conn = dataSource.getConnection(); - PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { - - ps.setString(1, user.getEmail()); - ps.setString(2, user.getPassword()); - ps.setString(3, user.getName()); - ps.setString(4, user.getNickname()); - ps.setString(5, user.getBirthDate()); - ps.setString(6, user.getProfileImage()); - ps.setString(7, user.getIntroduction()); - ps.setString(8, user.getAuthMethod()); - ps.setTimestamp(9, user.getCreatedAt()); - ps.setTimestamp(10, user.getUpdatedAt()); - ps.setBoolean(11, user.isActive()); - - ps.executeUpdate(); - - ResultSet keys = ps.getGeneratedKeys(); - if (keys.next()) { - user.setUserId(keys.getLong(1)); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } + return jdbcTemplate.query(sql, new Object[]{userId}, userRowMapper()) + .stream().findFirst().orElse(null); } @Override - public void update(User user) { - String sql = "UPDATE users SET nickname = ?, profile_image = ?, introduction = ?, updated_at = ? WHERE user_id = ?"; - try (Connection conn = dataSource.getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { - - ps.setString(1, user.getNickname()); - ps.setString(2, user.getProfileImage()); - ps.setString(3, user.getIntroduction()); - ps.setTimestamp(4, user.getUpdatedAt()); - ps.setLong(5, user.getUserId()); - - ps.executeUpdate(); - } catch (SQLException e) { - throw new RuntimeException(e); - } + public void update(Long userId, String nickname, String profileImage, Timestamp updatedAt) { + String sql = "UPDATE users SET nickname = ?, profile_image = ?, updated_at = ? WHERE user_id = ?"; + jdbcTemplate.update(sql, nickname, profileImage, updatedAt, userId); } @Override public void softDelete(Long userId) { String sql = "UPDATE users SET is_active = false WHERE user_id = ?"; - try (Connection conn = dataSource.getConnection(); - PreparedStatement ps = conn.prepareStatement(sql)) { - - ps.setLong(1, userId); - ps.executeUpdate(); - } catch (SQLException e) { - throw new RuntimeException(e); - } + jdbcTemplate.update(sql, userId); } - private User mapRowToUser(ResultSet rs) throws SQLException { - return new User( + private RowMapper userRowMapper() { + return (rs, rowNum) -> new User( rs.getLong("user_id"), rs.getString("email"), rs.getString("password"), diff --git a/src/main/java/com/blog/user/presentation/UserProfileController.java b/src/main/java/com/blog/user/presentation/UserProfileController.java new file mode 100644 index 00000000..f8dc187e --- /dev/null +++ b/src/main/java/com/blog/user/presentation/UserProfileController.java @@ -0,0 +1,53 @@ +package com.blog.user.presentation; + +import com.blog.user.application.UserProfileService; +import com.blog.token.infrastructure.JwtTokenProvider; +import com.blog.user.presentation.dto.UserResponse; +import com.blog.user.presentation.dto.UserUpdateRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/user") +public class UserProfileController { + + private final UserProfileService userProfileService; + private final JwtTokenProvider jwtTokenProvider; + + public UserProfileController(UserProfileService userProfileService, JwtTokenProvider jwtTokenProvider) { + this.userProfileService = userProfileService; + this.jwtTokenProvider = jwtTokenProvider; + } + + // 유저 정보 조회 + @GetMapping + public ResponseEntity getUser(@RequestHeader("Authorization") String token) { + Long userId = extractUserId(token); + return ResponseEntity.ok(userProfileService.getUser(userId)); + } + + // 유저 정보 수정 + @PutMapping + public ResponseEntity updateUser( + @RequestHeader("Authorization") String token, + @RequestBody UserUpdateRequest request + ) { + Long userId = extractUserId(token); + return ResponseEntity.ok(userProfileService.updateUser(userId, request)); + } + + // 유저 삭제 + @DeleteMapping + public ResponseEntity deleteUser(@RequestHeader("Authorization") String token) { + Long userId = extractUserId(token); + userProfileService.deleteUser(userId); + return ResponseEntity.ok().build(); + } + + private Long extractUserId(String token) { + if (token.startsWith("Bearer ")) { + token = token.substring(7); + } + return Long.parseLong(jwtTokenProvider.parseUserId(token)); + } +} From 9abaf6b78d005197fe492747522b0df271aab212 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Mon, 19 May 2025 23:54:22 +0900 Subject: [PATCH 03/19] chore: add Dockerfile --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..3cc4fb63 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +# Dockerfile +FROM eclipse-temurin:21-jdk +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"] From bb04832fc35d7bdf1be2abb2cdf5960870ab6847 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:05:45 +0900 Subject: [PATCH 04/19] chore: git CI/CD test --- .github/workflows/deploy.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..cb718261 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,35 @@ +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: Copy files to EC2 + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_KEY }} + source: "." + target: "/home/ubuntu/app" + + - name: Run Docker on EC2 + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_KEY }} + script: | + cd ~/app + docker stop blog-be-itor || true + docker rm blog-be-itor || true + docker build -t blog-be-itor . + docker run -d -p 8080:8080 --name blog-be-itor blog-be-itor From 0ee7c824dc53044156bba573d3e0205802d78a23 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:25:31 +0900 Subject: [PATCH 05/19] [chore]fix: deploy.yml --- .github/workflows/deploy.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cb718261..3cb8ddb2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,6 +12,18 @@ jobs: - 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 + - name: Copy files to EC2 uses: appleboy/scp-action@v0.1.4 with: From b1722b4fd65be20cbf08e2721227048189528661 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:31:06 +0900 Subject: [PATCH 06/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3cb8ddb2..a372cf5f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,7 +22,7 @@ jobs: run: chmod +x ./gradlew - name: Build JAR - run: ./gradlew build + run: ./gradlew build -x test - name: Copy files to EC2 uses: appleboy/scp-action@v0.1.4 From ebe76909823d88ff03e53f6d690f463bfcd339e1 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:38:25 +0900 Subject: [PATCH 07/19] [chore] fix: Dockerfile --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3cc4fb63..6f66f085 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ # Dockerfile FROM eclipse-temurin:21-jdk -ARG JAR_FILE=build/libs/*.jar -COPY ${JAR_FILE} app.jar +COPY build/libs/blog-be-itor-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] From aafe023747651b389bb996afe8520c9bc838b1fe Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:43:31 +0900 Subject: [PATCH 08/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a372cf5f..a216adac 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,7 +30,7 @@ jobs: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_KEY }} - source: "." + source: "./build ./Dockerfile ./gradlew ./gradlew.bat ./settings.gradle ./build.gradle ./src" target: "/home/ubuntu/app" - name: Run Docker on EC2 From 40d5835d2a9466efad4223494e7156936a597e89 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:49:44 +0900 Subject: [PATCH 09/19] [chore] fix: Dockerfile --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6f66f085..8d782e85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,3 @@ -# Dockerfile FROM eclipse-temurin:21-jdk -COPY build/libs/blog-be-itor-0.0.1-SNAPSHOT.jar app.jar +COPY build/libs/blog-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] From 6bbb7d621b41ad5900ee59aa34b8545364fe665f Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:53:53 +0900 Subject: [PATCH 10/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a216adac..c436c7c8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,16 +24,19 @@ jobs: - name: Build JAR run: ./gradlew build -x test - - name: Copy files to EC2 + - 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/scp-action@v0.1.4 with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_KEY }} - source: "./build ./Dockerfile ./gradlew ./gradlew.bat ./settings.gradle ./build.gradle ./src" + source: "deploy-package.zip" target: "/home/ubuntu/app" - - name: Run Docker on EC2 + - name: SSH into EC2 and deploy uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.EC2_HOST }} @@ -41,6 +44,10 @@ jobs: key: ${{ secrets.EC2_KEY }} script: | cd ~/app + rm -rf app-unzipped + mkdir app-unzipped + unzip deploy-package.zip -d app-unzipped + cd app-unzipped docker stop blog-be-itor || true docker rm blog-be-itor || true docker build -t blog-be-itor . From 17172849564623ff37b6ea611179030c976f9dc9 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 15:58:38 +0900 Subject: [PATCH 11/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c436c7c8..6b44855f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -43,6 +43,7 @@ jobs: username: ${{ secrets.EC2_USER }} key: ${{ secrets.EC2_KEY }} script: | + sudo apt update && sudo apt install unzip -y cd ~/app rm -rf app-unzipped mkdir app-unzipped From 3a03fb317637e65cb0cf4df76a48bedd9f27a3e2 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 16:31:24 +0900 Subject: [PATCH 12/19] [chore] add: application.properties --- application.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 application.properties diff --git a/application.properties b/application.properties new file mode 100644 index 00000000..e17f4097 --- /dev/null +++ b/application.properties @@ -0,0 +1 @@ +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration From 2ee0b451dd65a6475d75e9aa85543a50aa8497a1 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 16:36:23 +0900 Subject: [PATCH 13/19] [chore] fix: application.properties --- application.properties | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application.properties b/application.properties index e17f4097..043e4015 100644 --- a/application.properties +++ b/application.properties @@ -1 +1,3 @@ -spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +spring.autoconfigure.exclude=\ + org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration From 4a3b9a223db2b116df80d50d861c63c0ea90c4d4 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 16:58:40 +0900 Subject: [PATCH 14/19] [chore] fix: database --- application.properties | 15 ++++++++++++--- build.gradle | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/application.properties b/application.properties index 043e4015..309b362f 100644 --- a/application.properties +++ b/application.properties @@ -1,3 +1,12 @@ -spring.autoconfigure.exclude=\ - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration +# src/main/resources/application.properties + +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +# Hibernate ?? +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=none + +spring.jpa.show-sql=true diff --git a/build.gradle b/build.gradle index dfb7fa6b..78ddad34 100644 --- a/build.gradle +++ b/build.gradle @@ -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' From c1eafd3f0c41aaea7b6c90785c17cc58dbd489fb Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 17:20:48 +0900 Subject: [PATCH 15/19] [chore] fix: jwt.secret value --- application.properties | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/application.properties b/application.properties index 309b362f..bba25627 100644 --- a/application.properties +++ b/application.properties @@ -1,12 +1,10 @@ -# src/main/resources/application.properties - +# H2 ?? DB ?? spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password= - -# Hibernate ?? spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=none - spring.jpa.show-sql=true + +jwt.secret=dummy-secret From 8a8b5950dd75a948f5e864ef9802210e8f0b4df3 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 21:09:23 +0900 Subject: [PATCH 16/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6b44855f..440afe98 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,7 +25,7 @@ jobs: 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 + run: zip -r deploy-package.zip build Dockerfile gradlew gradlew.bat settings.gradle build.gradle src src/main/resources - name: Copy zipped project to EC2 uses: appleboy/scp-action@v0.1.4 From a75a0734f2273a2f5b4acd3627382c4523906260 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 23:08:56 +0900 Subject: [PATCH 17/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 25 +++++++++++++++++++++---- application.properties | 10 ---------- 2 files changed, 21 insertions(+), 14 deletions(-) delete mode 100644 application.properties diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 440afe98..b6d7bd68 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -38,10 +38,16 @@ jobs: - name: SSH into EC2 and deploy uses: appleboy/ssh-action@v1.0.0 + 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 @@ -49,7 +55,18 @@ jobs: mkdir app-unzipped unzip deploy-package.zip -d app-unzipped cd app-unzipped - docker stop blog-be-itor || true - docker rm blog-be-itor || true - docker build -t blog-be-itor . - docker run -d -p 8080:8080 --name blog-be-itor blog-be-itor + + # application.properties 만들기 + cat < 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 --name blog-be-itor blog-be-itor diff --git a/application.properties b/application.properties deleted file mode 100644 index bba25627..00000000 --- a/application.properties +++ /dev/null @@ -1,10 +0,0 @@ -# H2 ?? DB ?? -spring.datasource.url=jdbc:h2:mem:testdb -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect -spring.jpa.hibernate.ddl-auto=none -spring.jpa.show-sql=true - -jwt.secret=dummy-secret From 6da5bc8f00286a2d6492603832a158c25de764ce Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 23:15:00 +0900 Subject: [PATCH 18/19] [chore] fix: deploy.yml --- .github/workflows/deploy.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b6d7bd68..2ee2c7b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -56,17 +56,17 @@ jobs: unzip deploy-package.zip -d app-unzipped cd app-unzipped - # application.properties 만들기 + # application.properties 생성 cat < 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 + 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 + 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 --name blog-be-itor blog-be-itor + docker stop blog-be-itor || true + docker rm blog-be-itor || true + docker build -t blog-be-itor . + docker run -d -p 8080:8080 --name blog-be-itor blog-be-itor From 1f750dc0e5c6807ac6b830727f68c0fa4de0770e Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Tue, 20 May 2025 23:26:51 +0900 Subject: [PATCH 19/19] fix: CI/CD --- .github/workflows/deploy.yml | 11 +++++++---- Dockerfile | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2ee2c7b7..acb5e084 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,7 +25,7 @@ jobs: 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 src/main/resources + run: zip -r deploy-package.zip build Dockerfile gradlew gradlew.bat settings.gradle build.gradle src - name: Copy zipped project to EC2 uses: appleboy/scp-action@v0.1.4 @@ -50,23 +50,26 @@ jobs: 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 생성 + # application.properties 만들기 + mkdir -p src/main/resources cat < 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 --name blog-be-itor 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 diff --git a/Dockerfile b/Dockerfile index 8d782e85..c5d17745 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ FROM eclipse-temurin:21-jdk COPY build/libs/blog-0.0.1-SNAPSHOT.jar app.jar -ENTRYPOINT ["java", "-jar", "/app.jar"] +COPY src/main/resources/application.properties /app/application.properties +ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.config.location=file:/app/application.properties"]