From ad951dcbffebd2bb7e50fb6e5be70293eaab07d5 Mon Sep 17 00:00:00 2001 From: Park-JiYeong <89844427+ParkJiYeoung8297@users.noreply.github.com> Date: Fri, 16 Jan 2026 20:42:02 +0900 Subject: [PATCH] =?UTF-8?q?feat(#7):=20=EC=9E=90=EB=8F=99=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20CD=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B6=94=EA=B0=80=20(#1?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit related_to : #7 ## πŸ“ μž‘μ—… λ‚΄μš© μžλ™ 배포λ₯Ό μœ„ν•œ CD μΆ”κ°€ ## βœ… λ³€κ²½ 사항 - [x] cd-prod.yml 파일 μΆ”κ°€ - [x] μ„œλ²„μ— .env 파일 μΆ”κ°€ (λ…Έμ…˜ μ°Έκ³ ) ## πŸ“· μŠ€ν¬λ¦°μƒ· (선택) ## πŸ’¬ λ¦¬λ·°μ–΄μ—κ²Œ Main λΈŒλžœμΉ˜μ— pr이 μ˜¬λΌκ°€μ•Ό μž‘λ™ν•˜κΈ° λ•Œλ¬Έμ—, dev 브랜치 κ·ΈλŒ€λ‘œ PR λ‚ λ¦½λ‹ˆλ‹€. ν™˜κ²½λ³€μˆ˜λŠ” μ„œλ²„μ— 직접 .env 파일둜 μ˜¬λ €λ‘μ—ˆμŠ΅λ‹ˆλ‹€. application-prod.yml을 κΈ°μ€€μœΌλ‘œ μž‘λ™ν•©λ‹ˆλ‹€. (β†’ SPRING_PROFILES_ACTIVE=prod) --------- Co-authored-by: Yoonchul Chung <84674889+Yoonchulchung@users.noreply.github.com> Co-authored-by: yoonchulchung --- .DS_Store | Bin 6148 -> 0 bytes .github/workflows/cd-prod.yml | 62 +++++++++++ .github/workflows/pr-check.yml | 64 +++++++++-- .gitignore | 14 ++- Docker/.DS_Store | Bin 6148 -> 0 bytes Docker/Dockerfile | 6 +- README.md | 5 + build.gradle | 42 +++++++- docker-compose.yaml | 27 ++--- .../RealMatch/RealMatchApplication.java | 6 +- .../RealMatch/brand/application/service/.keep | 0 .../RealMatch/brand/domain/entity/.keep | 0 .../RealMatch/brand/domain/repository/.keep | 0 .../brand/presentation/controller/.keep | 0 .../brand/presentation/dto/request/.keep | 0 .../brand/presentation/dto/response/.keep | 0 .../campaign/application/service/.keep | 0 .../RealMatch/campaign/domain/entity/.keep | 0 .../campaign/domain/repository/.keep | 0 .../campaign/presentation/controller/.keep | 0 .../campaign/presentation/dto/request/.keep | 0 .../campaign/presentation/dto/response/.keep | 0 .../RealMatch/chat/application/service/.keep | 0 .../RealMatch/chat/domain/entity/.keep | 0 .../RealMatch/chat/domain/repository/.keep | 0 .../chat/presentation/controller/.keep | 0 .../chat/presentation/dto/request/.keep | 0 .../chat/presentation/dto/response/.keep | 0 .../RealMatch/global/common/BaseEntity.java | 24 +++++ .../global/common/UpdateBaseEntity.java | 42 ++++++++ .../global/config/SecurityConfig.java | 76 +++++++++++++ .../global/config/SwaggerConfig.java | 52 +++++++++ .../global/config/jwt/CustomUserDetails.java | 58 ++++++++++ .../config/jwt/JwtAuthenticationFilter.java | 62 +++++++++++ .../global/config/jwt/JwtProvider.java | 101 ++++++++++++++++++ .../global/controller/TestController.java | 58 ++++++++++ .../global/presentation/CustomResponse.java | 41 +++++++ .../advice/CustomAccessDeniedHandler.java | 42 ++++++++ .../advice/CustomAuthEntryPoint.java | 42 ++++++++ .../advice/DuplicateUserException.java | 8 ++ .../advice/GlobalExceptionHandler.java | 77 +++++++++++++ .../advice/ResourceNotFoundException.java | 8 ++ .../presentation/code/BaseErrorCode.java | 12 +++ .../presentation/code/BaseSuccessCode.java | 12 +++ .../presentation/code/GeneralErrorCode.java | 48 +++++++++ .../presentation/code/GeneralSuccessCode.java | 32 ++++++ .../RealMatch/match/application/service/.keep | 0 .../RealMatch/match/domain/entity/.keep | 0 .../RealMatch/match/domain/repository/.keep | 0 .../match/presentation/controller/.keep | 0 .../match/presentation/dto/request/.keep | 0 .../match/presentation/dto/response/.keep | 0 .../RealMatch/user/application/service/.keep | 0 .../RealMatch/user/domain/entity/.keep | 0 .../RealMatch/user/domain/repository/.keep | 0 .../user/presentation/controller/.keep | 0 .../user/presentation/dto/request/.keep | 0 .../user/presentation/dto/response/.keep | 0 src/main/resources/.keep | 0 src/main/resources/application-prod.yml | 48 +++++++++ src/main/resources/application-test.yml | 42 ++++++++ src/main/resources/application.yaml | 3 - .../RealMatch/RealMatchApplicationTests.java | 6 +- src/test/resources/.keep | 0 64 files changed, 1088 insertions(+), 32 deletions(-) delete mode 100644 .DS_Store create mode 100644 .github/workflows/cd-prod.yml delete mode 100644 Docker/.DS_Store create mode 100644 src/main/java/com/example/RealMatch/brand/application/service/.keep create mode 100644 src/main/java/com/example/RealMatch/brand/domain/entity/.keep create mode 100644 src/main/java/com/example/RealMatch/brand/domain/repository/.keep create mode 100644 src/main/java/com/example/RealMatch/brand/presentation/controller/.keep create mode 100644 src/main/java/com/example/RealMatch/brand/presentation/dto/request/.keep create mode 100644 src/main/java/com/example/RealMatch/brand/presentation/dto/response/.keep create mode 100644 src/main/java/com/example/RealMatch/campaign/application/service/.keep create mode 100644 src/main/java/com/example/RealMatch/campaign/domain/entity/.keep create mode 100644 src/main/java/com/example/RealMatch/campaign/domain/repository/.keep create mode 100644 src/main/java/com/example/RealMatch/campaign/presentation/controller/.keep create mode 100644 src/main/java/com/example/RealMatch/campaign/presentation/dto/request/.keep create mode 100644 src/main/java/com/example/RealMatch/campaign/presentation/dto/response/.keep create mode 100644 src/main/java/com/example/RealMatch/chat/application/service/.keep create mode 100644 src/main/java/com/example/RealMatch/chat/domain/entity/.keep create mode 100644 src/main/java/com/example/RealMatch/chat/domain/repository/.keep create mode 100644 src/main/java/com/example/RealMatch/chat/presentation/controller/.keep create mode 100644 src/main/java/com/example/RealMatch/chat/presentation/dto/request/.keep create mode 100644 src/main/java/com/example/RealMatch/chat/presentation/dto/response/.keep create mode 100644 src/main/java/com/example/RealMatch/global/common/BaseEntity.java create mode 100644 src/main/java/com/example/RealMatch/global/common/UpdateBaseEntity.java create mode 100644 src/main/java/com/example/RealMatch/global/config/SecurityConfig.java create mode 100644 src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java create mode 100644 src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java create mode 100644 src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java create mode 100644 src/main/java/com/example/RealMatch/global/controller/TestController.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/DuplicateUserException.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/advice/ResourceNotFoundException.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/code/GeneralErrorCode.java create mode 100644 src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java create mode 100644 src/main/java/com/example/RealMatch/match/application/service/.keep create mode 100644 src/main/java/com/example/RealMatch/match/domain/entity/.keep create mode 100644 src/main/java/com/example/RealMatch/match/domain/repository/.keep create mode 100644 src/main/java/com/example/RealMatch/match/presentation/controller/.keep create mode 100644 src/main/java/com/example/RealMatch/match/presentation/dto/request/.keep create mode 100644 src/main/java/com/example/RealMatch/match/presentation/dto/response/.keep create mode 100644 src/main/java/com/example/RealMatch/user/application/service/.keep create mode 100644 src/main/java/com/example/RealMatch/user/domain/entity/.keep create mode 100644 src/main/java/com/example/RealMatch/user/domain/repository/.keep create mode 100644 src/main/java/com/example/RealMatch/user/presentation/controller/.keep create mode 100644 src/main/java/com/example/RealMatch/user/presentation/dto/request/.keep create mode 100644 src/main/java/com/example/RealMatch/user/presentation/dto/response/.keep create mode 100644 src/main/resources/.keep create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application-test.yml delete mode 100644 src/main/resources/application.yaml create mode 100644 src/test/resources/.keep diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a145f93ce93c0be55807ccd3bdcaf8e01efcf6d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}xR_5N-jX1Wh<#!m){05<$UeysWssfNS)i1X-5AhGk3mkpm&wc+~hlzJYIG z;@dd=!HD>CBWh-n={KFuwA*ht?G7P?R>xk6kSrmDfl7>}q4`AQxYQ+y8BY?BsySL@ z;7*2pHxTYpG&+7G1M=M^iA@A)k}g?%e~)fI@Q0&f@tMZriM3>krC5g5oQ{|{qsC}F ztTqOhvUes#;Lh!ad+xU;^=$4~grkNZwgxJ}_gWBgb>WAem{i3$^oAYI7;2iG3SlCn0E;&un z>(2u|j`xIZn{Sq_KfKg(w@*jQG2V|c{m%F6+&F_U5MdkUo zAeLF#intImfDHT<2ITp`K_yxSBaQ0lfJT)7fC+Fb0bAA*GKU+q3`QDZ1cd5TK%GiW zi9vNb__>L*3`QDtI-#ccpk`%iDio?#hxxe*C)Co2Ei!-%EHV(+-K@O-Prk1I7n87u z3?KvliUFFi9lHu!GI#6J=Hy+=L9aoj$hb)3TM8KJDu!5j73V>ffS*eP&@vcl1P=)Q N2q+q`K?YXJz$;0RXm|hs diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml new file mode 100644 index 00000000..5d68986b --- /dev/null +++ b/.github/workflows/cd-prod.yml @@ -0,0 +1,62 @@ +name: CD - PROD + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: "21" + distribution: "temurin" + + - uses: gradle/actions/setup-gradle@v3 + + - name: Grant execute permission + run: chmod +x gradlew + + - name: Build JAR + run: ./gradlew build -x test + + - name: Docker Login + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build & Push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/realmatch-backend:prod + ${{ secrets.DOCKERHUB_USERNAME }}/realmatch-backend:${{ github.sha }} + + - name: Deploy + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_SERVER_IP }} + username: ubuntu + key: ${{ secrets.PROD_SERVER_SSH_KEY }} + script: | + cd /home/ubuntu/realmatch + git pull origin main + + docker compose pull + docker compose up -d + + echo "Waiting for app to start..." + sleep 10 + + if ! docker compose ps | grep "Up"; then + echo "❌ Container is not running" + docker compose logs + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index fba7594f..5b5dbb93 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -8,6 +8,29 @@ jobs: build-and-test: runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + env: + MYSQL_DATABASE: test_db + MYSQL_USER: test_user + MYSQL_PASSWORD: test + MYSQL_ROOT_PASSWORD: test_root + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -ptest_root" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + + env: + SPRING_PROFILES_ACTIVE: test + JWT_SECRET: pPmJ9ViYt8f6HAh2q5s36QmEUeyEFRcquPaNpnIGUK8er5DjfTKa4xbDsTFXQ7HRVfTLR2DIYs7s9iGdJ+Yb7Q== + JWT_ACCESS_EXPIRE_MS: 600000 + JWT_REFRESH_EXPIRE_MS: 1209600000 + steps: - name: Checkout code uses: actions/checkout@v4 @@ -19,6 +42,9 @@ jobs: distribution: 'temurin' cache: gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -33,12 +59,38 @@ jobs: run: ./gradlew test continue-on-error: true - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: | - build/test-results/**/*.xml + - name: Test application startup + run: | + ./gradlew bootRun > bootrun.log 2>&1 & + APP_PID=$! + echo "Started bootRun with PID: $APP_PID" + + # μ΅œλŒ€ 60초 λŒ€κΈ°ν•˜λ©΄μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹œμž‘ 확인 + for i in {1..60}; do + # λ‘œκ·Έμ—μ„œ μ‹œμž‘ μ™„λ£Œ λ©”μ‹œμ§€ 확인 + if grep -q "Started RealMatchApplication" bootrun.log; then + echo "βœ… Application started successfully after ${i}s" + kill $APP_PID 2>/dev/null || true + exit 0 + fi + + # ν”„λ‘œμ„ΈμŠ€κ°€ μ£½μ—ˆλŠ”μ§€ 확인 + if ! kill -0 $APP_PID 2>/dev/null; then + echo "❌ Application process terminated unexpectedly" + echo "=== bootRun log ===" + cat bootrun.log + exit 1 + fi + + sleep 1 + done + + echo "❌ Application failed to start within 60 seconds" + echo "=== bootRun log ===" + cat bootrun.log + kill $APP_PID 2>/dev/null || true + exit 1 + timeout-minutes: 2 - name: Upload build artifacts if: failure() diff --git a/.gitignore b/.gitignore index e63e239a..45116f7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ # νŠΉμ • 파일 μ œμ™Έ -src/main/resources/application-*.yml +#src/main/resources/application-*.yml +src/main/resources/application.yml +src/test/resources/application.yml +src/test/resources/application-*.yml + +# ν™˜κ²½ λ³€μˆ˜ +.env +.env.* ### Gradle ### .gradle/ @@ -37,3 +44,8 @@ Thumbs.db ### Log files ### *.log logs/ + +### mysql ### +mysql_data/ + +bin/ \ No newline at end of file diff --git a/Docker/.DS_Store b/Docker/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + + .exceptionHandling(exception -> exception + .authenticationEntryPoint(customAuthEntryPoint) // 401 + .accessDeniedHandler(customAccessDeniedHandler) // 403 + ) + + .authorizeHttpRequests(auth -> auth + .requestMatchers(REQUEST_AUTHENTICATED_ARRAY).authenticated() + .requestMatchers(PERMIT_ALL_URL_ARRAY).permitAll() + .anyRequest().denyAll() + ) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of(allowedOrigin, "http://localhost:8080", swaggerUrl)); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); // μΏ ν‚€/인증정보 포함 μš”μ²­ + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} + diff --git a/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java new file mode 100644 index 00000000..258b3553 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/SwaggerConfig.java @@ -0,0 +1,52 @@ +package com.example.RealMatch.global.config; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; + +@Configuration +public class SwaggerConfig { + + @Value("${swagger.server-url}") + private String swaggerUrl; + + @Bean + public OpenAPI localOpenAPI() { + Info info = new Info() + .title("πŸ”— RealMatch API") + .version("1.0.0") + .description("RealMatch API λͺ…μ„Έμ„œμž…λ‹ˆλ‹€."); + + String jwtSchemeName = "JWT Authentication"; + + io.swagger.v3.oas.models.security.SecurityScheme securityScheme = new io.swagger.v3.oas.models.security.SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"); + + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, securityScheme); + + return new OpenAPI() + .info(info) + .addSecurityItem(securityRequirement) + .components(components) + .servers(List.of( + new Server() + .url(swaggerUrl) + )); + } + +} diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java b/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java new file mode 100644 index 00000000..4f6af748 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/jwt/CustomUserDetails.java @@ -0,0 +1,58 @@ +package com.example.RealMatch.global.config.jwt; + +import java.util.Collection; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import lombok.Getter; + +@Getter +public class CustomUserDetails implements UserDetails { + + private final Long userId; // DB PK + private final String providerId; // μ†Œμ…œ 고유 ID + private final String role; // USER / ADMIN + + public CustomUserDetails(Long userId, String providerId, String role) { + this.userId = userId; + this.providerId = providerId; + this.role = role; + } + + @Override + public Collection getAuthorities() { + return List.of(() -> "ROLE_" + role); + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return providerId; + } // μ†Œμ…œ UUID κΈ°μ€€ + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java new file mode 100644 index 00000000..37964ae8 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,62 @@ +package com.example.RealMatch.global.config.jwt; + +import java.io.IOException; + +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtProvider jwtProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + String token = authHeader.substring(7); + + if (!jwtProvider.validateToken(token)) { + filterChain.doFilter(request, response); + return; + } + + Long userId = jwtProvider.getUserId(token); + String providerId = jwtProvider.getProviderId(token); + String role = jwtProvider.getRole(token); + + CustomUserDetails userDetails = + new CustomUserDetails(userId, providerId, role); + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + filterChain.doFilter(request, response); + } + +} diff --git a/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java b/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java new file mode 100644 index 00000000..9071df96 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/config/jwt/JwtProvider.java @@ -0,0 +1,101 @@ +package com.example.RealMatch.global.config.jwt; + +import java.util.Base64; +import java.util.Date; + +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +@Component +public class JwtProvider { + + private final SecretKey secretKey; + private final long accessTokenExpireMillis; + private final long refreshTokenExpireMillis; + + public JwtProvider( + @Value("${jwt.secret}") String secret, + @Value("${jwt.access-expire-ms}") long accessTokenExpireMillis, + @Value("${jwt.refresh-expire-ms}") long refreshTokenExpireMillis + ) { + byte[] keyBytes = Base64.getDecoder().decode(secret); + this.secretKey = Keys.hmacShaKeyFor(keyBytes); + this.accessTokenExpireMillis = accessTokenExpireMillis; + this.refreshTokenExpireMillis = refreshTokenExpireMillis; + } + + // ========================= + // 토큰 생성 + // ========================= + public String createAccessToken(Long userId, String providerId, String role) { + return createToken(userId, providerId, role, accessTokenExpireMillis); + } + + public String createRefreshToken(Long userId, String providerId, String role) { + return createToken(userId, providerId, role, refreshTokenExpireMillis); + } + + private String createToken(Long userId, String providerId, String role, long expireMillis) { + long now = System.currentTimeMillis(); + + return Jwts.builder() + .subject(String.valueOf(userId)) + .claim("providerId", providerId) + .claim("role", role) + .issuedAt(new Date(now)) + .expiration(new Date(now + expireMillis)) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + // ========================= + // 토큰 검증 + // ========================= + public boolean validateToken(String token) { + try { + parseClaims(token); + return true; + } catch (ExpiredJwtException e) { + return false; // 만료 + } catch (JwtException | IllegalArgumentException e) { + return false; // λ³€μ‘°, 손상 λ“± + } + } + + // ========================= + // Claims κ°€μ Έμ˜€κΈ° + // ========================= + public Claims getClaims(String token) { + return parseClaims(token).getPayload(); + } + + public Long getUserId(String token) { + return Long.parseLong(getClaims(token).getSubject()); + } + + public String getProviderId(String token) { + return getClaims(token).get("providerId", String.class); + } + + public String getRole(String token) { + return getClaims(token).get("role", String.class); + } + + private Jws parseClaims(String token) { + return Jwts.parser() + .verifyWith(secretKey) // Key νƒ€μž… κ°•μ œ + .build() + .parseSignedClaims(token); + } +} + diff --git a/src/main/java/com/example/RealMatch/global/controller/TestController.java b/src/main/java/com/example/RealMatch/global/controller/TestController.java new file mode 100644 index 00000000..3ee96566 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/controller/TestController.java @@ -0,0 +1,58 @@ +package com.example.RealMatch.global.controller; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.RealMatch.global.config.jwt.CustomUserDetails; +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralSuccessCode; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "test", description = "ν…ŒμŠ€νŠΈμš© API") +@RestController +@RequestMapping("/api") +public class TestController { + + @Operation(summary = "api ν…ŒμŠ€νŠΈ 확인", + description = """ + ν…ŒμŠ€νŠΈμš© apiμž…λ‹ˆλ‹€. + λ§Œμ•½ 이 apiκ°€ ν†΅κ³Όν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄, SecurityConfig에 url을 μΆ”κ°€ν•΄μ•Όν•©λ‹ˆλ‹€. + + 인증이 ν•„μš”μ—†λ‹€λ©΄, PERMIT_ALL_URL_ARRAY에 μΆ”κ°€ν•˜κ³ , + 인증이 ν•„μš”ν•˜λ‹€λ©΄, REQUEST_AUTHENTICATED_ARRAY에 μΆ”κ°€ν•΄μ£Όμ„Έμš”. + """) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "ν…ŒμŠ€νŠΈ 성곡") + }) + @GetMapping("/test") + public CustomResponse test() { + String response = "Hello from Spring Boot πŸ‘‹"; + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST, response); + } + + @Operation(summary = "api κΆŒν•œ ν…ŒμŠ€νŠΈ 확인", + description = """ + ν…ŒμŠ€νŠΈμš© apiμž…λ‹ˆλ‹€. + Swaggerμ—μ„œ Authorize에 토큰을 μž…λ ₯ν•œ ν›„ μ‚¬μš©ν•΄μ•Ό 정상 μž‘λ™ν•©λ‹ˆλ‹€. + """) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "ν…ŒμŠ€νŠΈ 성곡"), + @ApiResponse(responseCode = "COMMON401_1", description = "인증이 ν•„μš”ν•©λ‹ˆλ‹€.") + }) + @GetMapping("/test-auth") + public CustomResponse testAuth( + @AuthenticationPrincipal CustomUserDetails user + ) { + String response = "Hello from Spring Boot πŸ‘‹"; + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST, response); + } + + +} + diff --git a/src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java b/src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java new file mode 100644 index 00000000..af6f39ae --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/CustomResponse.java @@ -0,0 +1,41 @@ +package com.example.RealMatch.global.presentation; + +import com.example.RealMatch.global.presentation.code.BaseErrorCode; +import com.example.RealMatch.global.presentation.code.BaseSuccessCode; +import com.example.RealMatch.global.presentation.code.GeneralSuccessCode; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class CustomResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonProperty("result") + private T result; + + // 200 OK + public static CustomResponse ok(T result) { + return CustomResponse.onSuccess(GeneralSuccessCode.GOOD_REQUEST, result); + } + + public static CustomResponse onSuccess(BaseSuccessCode code, T result) { + return new CustomResponse<>(true, code.getCode(), code.getMessage(), result); + } + + public static CustomResponse onFailure(BaseErrorCode code, T result) { + return new CustomResponse<>(false, code.getCode(), code.getMessage(), result); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java new file mode 100644 index 00000000..012d123c --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAccessDeniedHandler.java @@ -0,0 +1,42 @@ +package com.example.RealMatch.global.presentation.advice; + +import java.io.IOException; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralErrorCode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private final ObjectMapper objectMapper; + + @Override + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException + ) throws IOException { + + response.setStatus(GeneralErrorCode.FORBIDDEN.getStatus().value()); + response.setContentType("application/json;charset=UTF-8"); + + CustomResponse body = + CustomResponse.onFailure( + GeneralErrorCode.FORBIDDEN, + null + ); + + response.getWriter() + .write(objectMapper.writeValueAsString(body)); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java new file mode 100644 index 00000000..79fdc7ed --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/CustomAuthEntryPoint.java @@ -0,0 +1,42 @@ +package com.example.RealMatch.global.presentation.advice; + +import java.io.IOException; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralErrorCode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CustomAuthEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException + ) throws IOException { + + response.setStatus(GeneralErrorCode.UNAUTHORIZED.getStatus().value()); + response.setContentType("application/json;charset=UTF-8"); + + CustomResponse body = + CustomResponse.onFailure( + GeneralErrorCode.UNAUTHORIZED, + null + ); + + response.getWriter() + .write(objectMapper.writeValueAsString(body)); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/DuplicateUserException.java b/src/main/java/com/example/RealMatch/global/presentation/advice/DuplicateUserException.java new file mode 100644 index 00000000..bff3e442 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/DuplicateUserException.java @@ -0,0 +1,8 @@ +package com.example.RealMatch.global.presentation.advice; + +public class DuplicateUserException extends RuntimeException { + + public DuplicateUserException(String message) { + super(message); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java b/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java new file mode 100644 index 00000000..f40ff4fa --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/GlobalExceptionHandler.java @@ -0,0 +1,77 @@ +package com.example.RealMatch.global.presentation.advice; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; + +import com.example.RealMatch.global.presentation.CustomResponse; +import com.example.RealMatch.global.presentation.code.GeneralErrorCode; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgument(IllegalArgumentException e) { + + log.warn("[IllegalArgumentException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.BAD_REQUEST.getStatus()) + .body(CustomResponse.onFailure(GeneralErrorCode.BAD_REQUEST, null)); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity> handleConstraintViolation(ConstraintViolationException e) { + + log.warn("[ConstraintViolationException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.INVALID_PAGE.getStatus()) + .body(CustomResponse.onFailure(GeneralErrorCode.INVALID_PAGE, null)); + } + + @ExceptionHandler(HandlerMethodValidationException.class) + public ResponseEntity> handleHandlerMethodValidation(HandlerMethodValidationException e) { + + log.warn("[HandlerMethodValidationException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.INVALID_PAGE.getStatus()) + .body(CustomResponse.onFailure(GeneralErrorCode.INVALID_PAGE, null)); + } + + @ExceptionHandler(SecurityException.class) + public ResponseEntity> handleSecurityException(SecurityException e) { + + log.warn("[SecurityException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.UNAUTHORIZED.getStatus()) + .body(CustomResponse.onFailure(GeneralErrorCode.UNAUTHORIZED, null)); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity> handleResourceNotFound(ResourceNotFoundException e) { + + log.warn("[ResourceNotFoundException] {}", e.getMessage()); + + return ResponseEntity + .status(GeneralErrorCode.NOT_FOUND.getStatus()) + .body(CustomResponse.onFailure(GeneralErrorCode.NOT_FOUND, null)); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleUnexpectedException(Exception e) { + + log.error("[UnexpectedException]", e); + + return ResponseEntity + .status(GeneralErrorCode.INTERNAL_SERVER_ERROR.getStatus()) + .body(CustomResponse.onFailure(GeneralErrorCode.INTERNAL_SERVER_ERROR, null)); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/advice/ResourceNotFoundException.java b/src/main/java/com/example/RealMatch/global/presentation/advice/ResourceNotFoundException.java new file mode 100644 index 00000000..c312ca5d --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/advice/ResourceNotFoundException.java @@ -0,0 +1,8 @@ +package com.example.RealMatch.global.presentation.advice; + +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java new file mode 100644 index 00000000..43c2f3fe --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/code/BaseErrorCode.java @@ -0,0 +1,12 @@ +package com.example.RealMatch.global.presentation.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + + HttpStatus getStatus(); + + String getCode(); + + String getMessage(); +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java new file mode 100644 index 00000000..6ae9b199 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/code/BaseSuccessCode.java @@ -0,0 +1,12 @@ +package com.example.RealMatch.global.presentation.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + + HttpStatus getStatus(); + + String getCode(); + + String getMessage(); +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/GeneralErrorCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/GeneralErrorCode.java new file mode 100644 index 00000000..a9190d13 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/code/GeneralErrorCode.java @@ -0,0 +1,48 @@ +package com.example.RealMatch.global.presentation.code; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode { + + BAD_REQUEST(HttpStatus.BAD_REQUEST, + "COMMON400_1", + "잘λͺ»λœ μš”μ²­μž…λ‹ˆλ‹€."), + + INVALID_DATA(HttpStatus.BAD_REQUEST, + "COMMON4001_2", + "μœ νš¨ν•˜μ§€ μ•Šμ€ λ°μ΄ν„°μž…λ‹ˆλ‹€."), + + INVALID_PAGE(HttpStatus.BAD_REQUEST, + "COMMON400_3", + "νŽ˜μ΄μ§€ λ²ˆν˜ΈλŠ” 1 이상이어야 ν•©λ‹ˆλ‹€."), + + DUPLICATE_MISSION(HttpStatus.BAD_REQUEST, + "COMMON400_4", + "이미 도전 쀑인 λ―Έμ…˜μž…λ‹ˆλ‹€."), + + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, + "COMMON401_1", + "인증이 ν•„μš”ν•©λ‹ˆλ‹€."), + + FORBIDDEN(HttpStatus.FORBIDDEN, + "COMMON403_1", + "μ ‘κ·Ό κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€."), + + NOT_FOUND(HttpStatus.NOT_FOUND, + "COMMON404_1", + "μš”μ²­ν•œ λ¦¬μ†ŒμŠ€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."), + + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500_1", + "μ„œλ²„ λ‚΄λΆ€ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java b/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java new file mode 100644 index 00000000..b326b6e5 --- /dev/null +++ b/src/main/java/com/example/RealMatch/global/presentation/code/GeneralSuccessCode.java @@ -0,0 +1,32 @@ +package com.example.RealMatch.global.presentation.code; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode { + + GOOD_REQUEST(HttpStatus.OK, + "COMMON200_1", + "정상적인 μš”μ²­μž…λ‹ˆλ‹€."), + AUTHORIZED(HttpStatus.CREATED, + "AUTH201_1", + "인증이 ν™•μΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€."), + CREATE(HttpStatus.CREATED, + "CREATE200_1", + "μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€."), + ALLOWED(HttpStatus.ACCEPTED, + "AUTH203_1", + "μš”μ²­μ΄ ν—ˆμš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€."), + FOUND(HttpStatus.FOUND, + "COMMON302_1", + "μš”μ²­ν•œ λ¦¬μ†ŒμŠ€λ₯Ό μ°Ύμ•˜μŠ΅λ‹ˆλ‹€."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/example/RealMatch/match/application/service/.keep b/src/main/java/com/example/RealMatch/match/application/service/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/match/domain/entity/.keep b/src/main/java/com/example/RealMatch/match/domain/entity/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/match/domain/repository/.keep b/src/main/java/com/example/RealMatch/match/domain/repository/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/match/presentation/controller/.keep b/src/main/java/com/example/RealMatch/match/presentation/controller/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/match/presentation/dto/request/.keep b/src/main/java/com/example/RealMatch/match/presentation/dto/request/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/match/presentation/dto/response/.keep b/src/main/java/com/example/RealMatch/match/presentation/dto/response/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/user/application/service/.keep b/src/main/java/com/example/RealMatch/user/application/service/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/user/domain/entity/.keep b/src/main/java/com/example/RealMatch/user/domain/entity/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/user/domain/repository/.keep b/src/main/java/com/example/RealMatch/user/domain/repository/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/user/presentation/controller/.keep b/src/main/java/com/example/RealMatch/user/presentation/controller/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/request/.keep b/src/main/java/com/example/RealMatch/user/presentation/dto/request/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/response/.keep b/src/main/java/com/example/RealMatch/user/presentation/dto/response/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/.keep b/src/main/resources/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 00000000..074981e3 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,48 @@ +cors: + allowed-origin: http://${SERVER_IP} + +swagger: + server-url: ${SWAGGER_SERVER_URL} + +server: + timezone: Asia/Seoul + +spring: + application: + name: Realmatch + activate: + on-profile: prod + + datasource: + url: ${SPRING_DATASOURCE_URL} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + driver-class-name: ${SPRING_DATASOURCE_DRIVER_CLASS_NAME} + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 30000 + connection-timeout: 10000 + max-lifetime: 600000 + + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + format_sql: true + + security: + refresh-token: + expire-days: 14 + +logging: + level: + org.hibernate.SQL: warn + +jwt: + secret: ${JWT_SECRET} + access-expire-ms: ${JWT_ACCESS_EXPIRE_MS} + refresh-expire-ms: ${JWT_REFRESH_EXPIRE_MS} + diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 00000000..d2881f5b --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,42 @@ +cors: + allowed-origin: http://localhost:3000 + +swagger: + server-url: http://localhost:8080 + +server: + timezone: Asia/Seoul + +spring: + application: + name: Realmatch + + datasource: + url: jdbc:mysql://localhost:3306/test_db + username: test_user + password: test + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 30000 + connection-timeout: 10000 + max-lifetime: 600000 + + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + format_sql: true + show-sql: false + + security: + refresh-token: + expire-days: 14 + +jwt: + secret: ${JWT_SECRET} + access-expire-ms: ${JWT_ACCESS_EXPIRE_MS} + refresh-expire-ms: ${JWT_REFRESH_EXPIRE_MS} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml deleted file mode 100644 index 5487359f..00000000 --- a/src/main/resources/application.yaml +++ /dev/null @@ -1,3 +0,0 @@ -spring: - application: - name: RealMatch diff --git a/src/test/java/com/example/RealMatch/RealMatchApplicationTests.java b/src/test/java/com/example/RealMatch/RealMatchApplicationTests.java index 73c5c16a..1228974e 100644 --- a/src/test/java/com/example/RealMatch/RealMatchApplicationTests.java +++ b/src/test/java/com/example/RealMatch/RealMatchApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class RealMatchApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/resources/.keep b/src/test/resources/.keep new file mode 100644 index 00000000..e69de29b