Skip to content

Commit 244b967

Browse files
authored
Merge deploy into main before branch cleanup
Sync current deploy branch into main before moving to the dev -> main workflow.
2 parents 5d644bd + 036dccf commit 244b967

16 files changed

Lines changed: 423 additions & 77 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,11 @@ jobs:
9393
SPRING_DATASOURCE_URL=${{ secrets.SPRING_DATASOURCE_URL }}
9494
SPRING_DATASOURCE_USERNAME=${{ secrets.SPRING_DATASOURCE_USERNAME }}
9595
SPRING_DATASOURCE_PASSWORD=${{ secrets.SPRING_DATASOURCE_PASSWORD }}
96-
SPRING_DATASOURCE_DRIVER_CLASS_NAME=${{ secrets.SPRING_DATASOURCE_DRIVER_CLASS_NAME }}
97-
SPRING_JPA_HIBERNATE_DDL_AUTO=${{ secrets.SPRING_JPA_HIBERNATE_DDL_AUTO }}
96+
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
97+
SPRING_JPA_HIBERNATE_DDL_AUTO=validate
9898
9999
SPRING_FLYWAY_ENABLED=true
100-
SPRING_FLYWAY_URL=${{ secrets.SPRING_FLYWAY_URL }}
101-
SPRING_FLYWAY_USER=${{ secrets.SPRING_FLYWAY_USER }}
102-
SPRING_FLYWAY_PASSWORD=${{ secrets.SPRING_FLYWAY_PASSWORD }}
103-
SPRING_FLYWAY_BASELINE_ON_MIGRATE=true
100+
SPRING_FLYWAY_BASELINE_ON_MIGRATE=false
104101
105102
JWT_SECRET_KEY=${{ secrets.JWT_SECRETKEY }}
106103
JWT_ACCESS_EXPIRATION=${{ secrets.JWT_ACCESS_EXPIRATION }}
@@ -139,6 +136,47 @@ jobs:
139136
FIREBASE_CREDENTIALS_BASE64=${{ secrets.FIREBASE_CREDENTIALS_BASE64 }}
140137
EOF
141138
139+
fail_deploy() {
140+
echo "Unsafe production database configuration: $1" >&2
141+
exit 1
142+
}
143+
144+
get_env_value() {
145+
grep -E "^$1=" .env | tail -n 1 | cut -d= -f2-
146+
}
147+
148+
DB_URL="$(get_env_value SPRING_DATASOURCE_URL)"
149+
DB_USERNAME="$(get_env_value SPRING_DATASOURCE_USERNAME)"
150+
DB_PASSWORD="$(get_env_value SPRING_DATASOURCE_PASSWORD)"
151+
DDL_AUTO="$(get_env_value SPRING_JPA_HIBERNATE_DDL_AUTO)"
152+
FLYWAY_BASELINE="$(get_env_value SPRING_FLYWAY_BASELINE_ON_MIGRATE)"
153+
NORMALIZED_DB_URL="$(printf '%s' "$DB_URL" | tr '[:upper:]' '[:lower:]')"
154+
NORMALIZED_DB_USERNAME="$(printf '%s' "$DB_USERNAME" | tr '[:upper:]' '[:lower:]')"
155+
156+
[ -n "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL is required."
157+
[ -n "$DB_USERNAME" ] || fail_deploy "SPRING_DATASOURCE_USERNAME is required."
158+
[ -n "$DB_PASSWORD" ] || fail_deploy "SPRING_DATASOURCE_PASSWORD is required."
159+
[ "$NORMALIZED_DB_USERNAME" != "root" ] || fail_deploy "SPRING_DATASOURCE_USERNAME must not be root."
160+
[ "$DDL_AUTO" = "validate" ] || fail_deploy "SPRING_JPA_HIBERNATE_DDL_AUTO must be validate."
161+
[ "$FLYWAY_BASELINE" = "false" ] || fail_deploy "SPRING_FLYWAY_BASELINE_ON_MIGRATE must be false."
162+
163+
case "$NORMALIZED_DB_URL" in
164+
*allowpublickeyretrieval=true*) fail_deploy "SPRING_DATASOURCE_URL must not enable allowPublicKeyRetrieval." ;;
165+
esac
166+
167+
case "$NORMALIZED_DB_URL" in
168+
*createdatabaseifnotexist=true*) fail_deploy "SPRING_DATASOURCE_URL must not create databases at startup." ;;
169+
esac
170+
171+
case "$NORMALIZED_DB_URL" in
172+
*usessl=false*) fail_deploy "SPRING_DATASOURCE_URL must not disable TLS." ;;
173+
esac
174+
175+
case "$NORMALIZED_DB_URL" in
176+
*sslmode=required*|*sslmode=verify_ca*|*sslmode=verify_identity*) ;;
177+
*) fail_deploy "SPRING_DATASOURCE_URL must declare sslMode=REQUIRED, VERIFY_CA, or VERIFY_IDENTITY." ;;
178+
esac
179+
142180
echo "${{ secrets.GHCR_READ_TOKEN }}" | sudo docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
143181
144182
if sudo docker compose version >/dev/null 2>&1; then

.github/workflows/test.yml

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -62,62 +62,12 @@ jobs:
6262
6363
6464
65-
# 4. 환경 변수 설정 파일 생성
66-
- name: Create Config Files
67-
run: |
68-
mkdir -p ontime-back/src/main/resources
69-
mkdir -p ontime-back/src/main/resources/key
70-
echo "spring.application.name=${{ secrets.SPRING_APPLICATION_NAME }}" > ontime-back/src/main/resources/application.properties
71-
echo "spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC&useSSL=false" >> ontime-back/src/main/resources/application.properties
72-
echo "spring.datasource.username=test_user" >> ontime-back/src/main/resources/application.properties
73-
echo "spring.datasource.password=test_password" >> ontime-back/src/main/resources/application.properties
74-
echo "spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver" >> ontime-back/src/main/resources/application.properties
75-
echo "spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect" >> ontime-back/src/main/resources/application.properties
76-
echo "spring.jpa.hibernate.ddl-auto=validate" >> ontime-back/src/main/resources/application.properties
77-
echo "spring.sql.init.mode=always" >> ontime-back/src/main/resources/application.properties
78-
echo "jwt.secret.key=${{ secrets.JWT_SECRETKEY }}" >> ontime-back/src/main/resources/application.properties
79-
echo "jwt.access.expiration=${{ secrets.JWT_ACCESS_EXPIRATION }}" >> ontime-back/src/main/resources/application.properties
80-
echo "jwt.refresh.expiration=${{ secrets.JWT_REFRESH_EXPIRATION }}" >> ontime-back/src/main/resources/application.properties
81-
echo "jwt.access.header=${{ secrets.JWT_ACCESS_HEADER }}" >> ontime-back/src/main/resources/application.properties
82-
echo "jwt.refresh.header=${{ secrets.JWT_REFRESH_HEADER }}" >> ontime-back/src/main/resources/application.properties
83-
echo "google.web.client-id = ${{ secrets.GOOGLE_WEB_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
84-
echo "google.app.client-id = ${{ secrets.GOOGLE_APP_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
85-
echo "spring.security.oauth2.client.registration.google.client-secret=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET }}" >> ontime-back/src/main/resources/application.properties
86-
echo "spring.security.oauth2.client.registration.google.scope=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE }}" >> ontime-back/src/main/resources/application.properties
87-
echo "spring.security.oauth2.client.registration.google.redirect-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI }}" >> ontime-back/src/main/resources/application.properties
88-
echo "spring.security.oauth2.client.registration.google.authorization-grant-type=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE }}" >> ontime-back/src/main/resources/application.properties
89-
echo "spring.security.oauth2.client.registration.google.client-name=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME }}" >> ontime-back/src/main/resources/application.properties
90-
echo "spring.security.oauth2.client.provider.google.authorization-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI }}" >> ontime-back/src/main/resources/application.properties
91-
echo "spring.security.oauth2.client.provider.google.token-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI }}" >> ontime-back/src/main/resources/application.properties
92-
echo "spring.security.oauth2.client.provider.google.user-info-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI }}" >> ontime-back/src/main/resources/application.properties
93-
echo "spring.security.oauth2.client.provider.google.user-name-attribute=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE }}" >> ontime-back/src/main/resources/application.properties
94-
echo "spring.security.oauth2.client.registration.kakao.client-id=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
95-
echo "spring.security.oauth2.client.registration.kakao.scope=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE }}" >> ontime-back/src/main/resources/application.properties
96-
echo "spring.security.oauth2.client.registration.kakao.redirect-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI }}" >> ontime-back/src/main/resources/application.properties
97-
echo "spring.security.oauth2.client.registration.kakao.authorization-grant-type=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE }}" >> ontime-back/src/main/resources/application.properties
98-
echo "spring.security.oauth2.client.registration.kakao.client-name=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME }}" >> ontime-back/src/main/resources/application.properties
99-
echo "spring.security.oauth2.client.provider.kakao.authorization-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI }}" >> ontime-back/src/main/resources/application.properties
100-
echo "spring.security.oauth2.client.provider.kakao.token-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI }}" >> ontime-back/src/main/resources/application.properties
101-
echo "spring.security.oauth2.client.provider.kakao.user-info-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI }}" >> ontime-back/src/main/resources/application.properties
102-
echo "spring.security.oauth2.client.provider.kakao.user-name-attribute=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE }}" >> ontime-back/src/main/resources/application.properties
103-
echo "apple.client.id=${{ secrets.APPLE_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
104-
echo "apple.client.secret=${{ secrets.APPLE_CLIENT_SECRET }}" >> ontime-back/src/main/resources/application.properties
105-
echo "apple.login.key=${{ secrets.APPLE_LOGIN_KEY }}" >> ontime-back/src/main/resources/application.properties
106-
echo "apple.team.id=${{ secrets.APPLE_TEAM_ID }}" >> ontime-back/src/main/resources/application.properties
107-
echo "spring.flyway.enabled=true" >> ontime-back/src/main/resources/application.properties
108-
echo "spring.flyway.url=jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC&useSSL=false" >> ontime-back/src/main/resources/application.properties
109-
echo "spring.flyway.user=test_user" >> ontime-back/src/main/resources/application.properties
110-
echo "spring.flyway.password=test_password" >> ontime-back/src/main/resources/application.properties
111-
echo "spring.flyway.baseline-on-migrate=true" >> ontime-back/src/main/resources/application.properties
112-
echo "management.endpoints.web.exposure.include=health" >> ontime-back/src/main/resources/application.properties
113-
echo "management.endpoint.health.show-details=always" >> ontime-back/src/main/resources/application.properties
114-
echo "${{ secrets.ONTIME_PUSH_FIREBASE_ADMINSDK }}" > ontime-back/src/main/resources/ontime-c63f1-firebase-adminsdk-fbsvc-a043cdc829.json
115-
echo "${{ secrets.AUTHKEY_743M7R5W3W }}" > ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8
116-
117-
# 5. Gradle 빌드 & JUnit 테스트 실행
65+
# 4. Gradle 빌드 & JUnit 테스트 실행
11866
- name: Run Tests with Gradle
11967
id: run-tests # 실행 결과를 output으로 저장할 id 추가
12068
continue-on-error: true
69+
env:
70+
SPRING_PROFILES_ACTIVE: test
12171
run: |
12272
cd ontime-back
12373
./gradlew test
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Database Configuration
2+
3+
Production uses MySQL with environment-provided database credentials.
4+
5+
## Production
6+
7+
- Spring profile: `prod`
8+
- Datasource URL: `SPRING_DATASOURCE_URL`
9+
- Datasource username: `SPRING_DATASOURCE_USERNAME`
10+
- Datasource password: `SPRING_DATASOURCE_PASSWORD`
11+
- Datasource driver: `com.mysql.cj.jdbc.Driver`
12+
- Hibernate DDL mode: `validate`
13+
- SQL logging: disabled
14+
- Formatted SQL logging: disabled
15+
- Flyway: enabled
16+
- Flyway baseline on migrate: disabled

ontime-back/docs/deployment/ec2.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# EC2 Deployment
2+
3+
This service deploys to Amazon EC2 through `.github/workflows/deploy.yml`.
4+
5+
## How to Deploy
6+
7+
1. Make sure the EC2 instance has Docker installed and the security group allows inbound traffic for the service port, currently `8080`.
8+
2. Add the required GitHub Actions secrets listed below.
9+
3. Run the `Deploy` workflow manually from GitHub Actions, or push to the `deploy` branch.
10+
11+
The workflow builds the Spring Boot jar, creates deploy-only config files from GitHub Secrets, uploads them to `/home/ubuntu/OnTime-back`, and restarts Docker Compose on the EC2 instance.
12+
13+
## Required EC2 Secrets
14+
15+
- `EC2_HOST`
16+
- `EC2_USER`
17+
- `EC2_SSH_KEY`
18+
19+
## Required Application Secrets
20+
21+
- `SPRING_APPLICATION_NAME`
22+
- `SPRING_DATASOURCE_URL`
23+
- `SPRING_DATASOURCE_USERNAME`
24+
- `SPRING_DATASOURCE_PASSWORD`
25+
- `SPRING_DATASOURCE_DRIVER_CLASS_NAME`
26+
- `SPRING_JPA_HIBERNATE_DDL_AUTO`
27+
- `JWT_SECRETKEY`
28+
- `JWT_ACCESS_EXPIRATION`
29+
- `JWT_REFRESH_EXPIRATION`
30+
- `JWT_ACCESS_HEADER`
31+
- `JWT_REFRESH_HEADER`
32+
- `GOOGLE_WEB_CLIENT_ID`
33+
- `GOOGLE_APP_CLIENT_ID`
34+
- `APPLE_CLIENT_ID`
35+
- `APPLE_LOGIN_KEY`
36+
- `APPLE_TEAM_ID`
37+
- `AUTHKEY_743M7R5W3W`
38+
- `SPRING_FLYWAY_URL`
39+
- `SPRING_FLYWAY_USER`
40+
- `SPRING_FLYWAY_PASSWORD`
41+
- `ONTIME_PUSH_FIREBASE_ADMINSDK`
42+
43+
## Optional Secrets
44+
45+
- `SPRING_JPA_DATABASE_PLATFORM` defaults to `org.hibernate.dialect.MySQL8Dialect`.
46+
- `FEATURE_APPLE_LOGIN_ENABLED` defaults to `true`.
47+
- Google and Kakao OAuth provider/registration secrets are included by the workflow when configured.
48+
49+
## Runtime Files on EC2
50+
51+
The deploy workflow writes these files under `/home/ubuntu/OnTime-back`:
52+
53+
- `project.jar`
54+
- `Dockerfile`
55+
- `docker-compose.yml`
56+
- `config/application.properties`
57+
- `secrets/firebase-adminsdk.json`
58+
- `secrets/AuthKey_743M7R5W3W.p8`
59+
60+
Do not commit local `application.properties`, Firebase service account JSON, Apple `.p8` keys, or `.env` files.

ontime-back/src/main/java/devkor/ontime_back/controller/SocialAuthController.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import devkor.ontime_back.dto.OAuthKakaoUserDto;
77
import devkor.ontime_back.global.oauth.apple.AppleLoginService;
88
import devkor.ontime_back.global.oauth.google.GoogleLoginService;
9+
import devkor.ontime_back.response.ApiResponseForm;
910
import devkor.ontime_back.service.UserAuthService;
1011
import io.swagger.v3.oas.annotations.Operation;
1112
import io.swagger.v3.oas.annotations.media.Content;
@@ -16,6 +17,7 @@
1617
import jakarta.servlet.http.HttpServletResponse;
1718
import lombok.RequiredArgsConstructor;
1819
import lombok.extern.slf4j.Slf4j;
20+
import org.springframework.http.ResponseEntity;
1921
import org.springframework.web.bind.annotation.*;
2022

2123
@Slf4j
@@ -113,24 +115,32 @@ public String appleRegisterOrLogin(@RequestBody OAuthAppleRequestDto appleLoginR
113115
summary = "애플 소셜 로그인 회원탈퇴"
114116
)
115117
@DeleteMapping("/apple/me")
116-
public String appleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) throws Exception {
118+
public ResponseEntity<ApiResponseForm<?>> appleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) {
117119
Long userId = userAuthService.getUserIdFromToken(request);
118120
log.info("userId: {}", userId);
119-
appleLoginService.revokeToken(userId);
121+
try {
122+
appleLoginService.revokeToken(userId);
123+
} catch (Exception e) {
124+
log.warn("Apple 토큰 철회에 실패했지만 계정 삭제를 계속 진행합니다. userId={}, reason={}", userId, e.getMessage());
125+
}
120126
userAuthService.deleteUser(userId, feedbackAddDto);
121-
return "애플 로그인 회원탈퇴 성공";
127+
return ResponseEntity.ok(ApiResponseForm.success(null, "애플 로그인 회원탈퇴 성공"));
122128
}
123129

124130
@Operation(
125131
summary = "구글 소셜 로그인 회원탈퇴"
126132
)
127133
@DeleteMapping("/google/me")
128-
public String googleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) throws Exception {
134+
public ResponseEntity<ApiResponseForm<?>> googleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) {
129135
Long userId = userAuthService.getUserIdFromToken(request);
130136
log.info("userId: {}", userId);
131-
googleLoginService.revokeToken(userId);
137+
try {
138+
googleLoginService.revokeToken(userId);
139+
} catch (Exception e) {
140+
log.warn("Google 토큰 철회에 실패했지만 계정 삭제를 계속 진행합니다. userId={}, reason={}", userId, e.getMessage());
141+
}
132142
userAuthService.deleteUser(userId, feedbackAddDto);
133-
return "구글 로그인 회원탈퇴 성공";
143+
return ResponseEntity.ok(ApiResponseForm.success(null, "구글 로그인 회원탈퇴 성공"));
134144
}
135145

136146

ontime-back/src/main/java/devkor/ontime_back/entity/User.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public class User {
5656

5757
private String firebaseToken;
5858

59+
@Lob
60+
@Column(columnDefinition = "LONGTEXT")
5961
private String socialLoginToken;
6062

6163
@OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
@@ -134,4 +136,4 @@ public void updateFirebaseToken(String firebaseToken) {
134136
public void updateAccessToken(String accessToken) {
135137
this.accessToken = accessToken;
136138
}
137-
}
139+
}

ontime-back/src/main/java/devkor/ontime_back/global/generallogin/service/LoginService.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ public class LoginService implements UserDetailsService {
1818
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
1919
User user = userRepository.findByEmail(email)
2020
.orElseThrow(() -> new UsernameNotFoundException("해당 이메일이 존재하지 않습니다."));
21-
22-
System.out.println("유저임다: "+user);
2321

2422
return org.springframework.security.core.userdetails.User.builder()
2523
.username(user.getEmail())
2624
.password(user.getPassword())
2725
.roles(user.getRole().name())
2826
.build();
2927
}
30-
}
28+
}

0 commit comments

Comments
 (0)