Skip to content

Feature/spring to ktor migration#1

Open
kez-lab wants to merge 21 commits into
developfrom
feature/spring-to-ktor-migration
Open

Feature/spring to ktor migration#1
kez-lab wants to merge 21 commits into
developfrom
feature/spring-to-ktor-migration

Conversation

@kez-lab

@kez-lab kez-lab commented Aug 3, 2025

Copy link
Copy Markdown
Member

No description provided.

kez-lab added 21 commits August 4, 2025 00:49
- Spring Boot 3.2.1 + Java 17 코드를 java-backup으로 보존
- 마이그레이션 과정에서 참조할 수 있도록 원본 코드 보관
- 기존 도메인 구조: User, Challenge, Auth, App 등
- 기존 아키텍처: Controller-Service-Repository 패턴
- CLAUDE.md: 프로젝트 개요 및 마이그레이션 계획 수립
- migration-roadmap.md: 5단계 마이그레이션 전략 (8-12주 계획)
  * Phase 1: 기반 인프라 구축 (Gradle, Ktor 플러그인)
  * Phase 2: 핵심 도메인 마이그레이션 (User, Challenge)
  * Phase 3: 인증 시스템 (JWT, 소셜 로그인)
  * Phase 4: 고급 기능 (Daily Challenge, Point 시스템)
  * Phase 5: 최적화 및 테스트
- tech-stack-comparison.md: Spring Boot vs Ktor 비교 분석
- migration-checklist.md: 200+ 체크리스트 항목으로 검증 가능한 마이그레이션 가이드
- build.gradle.kts: Gradle Kotlin DSL로 변환
  * Ktor 2.3.7 의존성 (server-core, netty, serialization)
  * Exposed ORM 0.44.1 (MySQL, Java Time 지원)
  * Koin 3.5.3 의존성 주입
  * JWT, CORS, CallLogging 플러그인
  * kotlinx.serialization-json 직렬화
  * Logback 로깅 시스템
- gradle.properties: Kotlin 컴파일러 옵션 최적화
- settings.gradle: Kotlin 프로젝트 설정
- gradlew: 실행 권한 추가
- Application.kt: Ktor 서버 메인 함수 및 모듈 설정
  * 환경 변수 기반 설정 관리 (DATABASE_URL, JWT_SECRET)
  * 그레이스풀 셧다운 설정 (5초 타임아웃)
  * 플러그인 모듈화: Database, Serialization, Monitoring, HTTP, Security, DI, Routing
  * 에러 핸들링: 설정 누락 시 경고 메시지와 함께 기능 비활성화
  * 개발 친화적: 데이터베이스/JWT 설정 없어도 서버 실행 가능
- Database.kt: Exposed ORM 데이터베이스 연결 및 테이블 스키마 설정
  * MySQL 연결 풀 관리 (HikariCP)
  * 테이블 자동 생성 (Users, Challenges)
  * 연결 실패 시 그레이스풀 처리

- Serialization.kt: JSON 직렬화 설정
  * kotlinx.serialization 기반
  * 타입 안전 직렬화/역직렬화

- Monitoring.kt: 서버 모니터링 및 로깅
  * CallLogging: HTTP 요청/응답 로깅
  * 성능 모니터링 준비

- HTTP.kt: HTTP 서버 설정
  * CORS 정책 (개발용 전체 허용)
  * Content Negotiation (JSON)

- Security.kt: 보안 설정 기반 구조
  * JWT 인증 준비 (설정 시에만 활성화)
  * 소셜 로그인 대응 구조

- DependencyInjection.kt: Koin 의존성 주입
  * Repository-Service 계층 DI 설정
  * 타입 안전 의존성 관리

- Routing.kt: API 라우팅 및 예외 처리
  * 계층적 라우팅 (/api/v1)
  * 글로벌 예외 처리 (BusinessException)
  * 헬스체크 엔드포인트 (/health, /health/ready, /health/live)
- BaseResponse.kt: 통일된 API 응답 형식
  * 제네릭 기반 타입 안전 응답 구조
  * success/error 상태별 응답 팩토리 메서드
  * JSON 직렬화 지원

- BusinessException.kt: 비즈니스 로직 예외 처리
  * HTTP 상태 코드와 에러 코드 매핑
  * 사용자 친화적 에러 메시지
  * 서비스 계층에서 활용 가능한 예외 구조

- dbQuery.kt: 데이터베이스 트랜잭션 헬퍼
  * Exposed ORM 트랜잭션 래퍼
  * 코루틴 기반 비동기 처리
  * 데이터베이스 연결 상태 확인
Entity 계층:
- Users.kt: Exposed Table 정의 및 User 데이터 클래스
  * LongIdTable 기반 테이블 스키마
  * SocialPlatform enum (KAKAO, APPLE)
  * LocalDateTime 지원 (createdAt, updatedAt, deletedAt)
  * ResultRow.toUser() 확장 함수

Repository 계층:
- UserRepository.kt: 인터페이스 정의 (Spring Repository 패턴 유지)
- UserRepositoryImpl.kt: Exposed ORM 구현
  * dbQuery 트랜잭션 래퍼 활용
  * 소셜 플랫폼별 사용자 조회
  * 소프트 딜리트 구현 (30일 보관 정책)
  * 사용자 통계 쿼리 (전체/활성 사용자 수)

Service 계층:
- UserService.kt: 비즈니스 로직 구현
  * 사용자 생성/조회/수정/삭제
  * 소셜 로그인 통합
  * 포인트 시스템 연동
  * 사용자 통계 제공

DTO 계층:
- UserCreateRequest.kt: 사용자 생성 요청
- UserUpdateRequest.kt: 사용자 정보 수정 요청
- UserResponse.kt: 사용자 응답 (민감 정보 제외)
- UserStatsResponse.kt: 사용자 통계 응답

Controller 계층:
- UserRoutes.kt: Ktor 라우팅 함수
  * RESTful API 엔드포인트 (GET/POST/PUT/DELETE)
  * 입력 유효성 검사
  * 에러 응답 처리
  * /api/v1/users/** 라우팅
Entity 계층:
- Challenges.kt: Exposed Table 정의 및 Challenge 데이터 클래스
  * LongIdTable 기반 테이블 스키마
  * Users 테이블과 외래키 관계
  * LocalDate 시작일, LocalDateTime 생성/수정일
  * ResultRow.toChallenge() 확장 함수

Repository 계층:
- ChallengeRepository.kt: 인터페이스 정의
- ChallengeRepositoryImpl.kt: Exposed ORM 구현
  * 사용자별 챌린지 조회 (최신순 정렬)
  * 현재 진행 중인 챌린지 조회
  * 날짜 범위별 챌린지 검색
  * 챌린지 통계 쿼리
  * 함수형 업데이트 패턴 지원

Service 계층:
- ChallengeService.kt: 비즈니스 로직 구현
  * 챌린지 생성/조회/수정/삭제
  * 사용자 권한 검증
  * 챌린지 진행 상태 관리
  * 기간별 챌린지 분석

DTO 계층:
- ChallengeCreateRequest.kt: 챌린지 생성 요청
- ChallengeUpdateRequest.kt: 챌린지 수정 요청
- ChallengeResponse.kt: 챌린지 응답
- ChallengeStatsResponse.kt: 챌린지 통계 응답
- application.yaml: Ktor 서버 설정
  * 개발 모드 및 포트 설정 (8080)
  * 환경별 설정 구조 준비

- logback.xml: 로깅 시스템 설정
  * 콘솔 및 파일 로깅 (logs/hmh-server.log)
  * 일별 로그 로테이션 (최대 3GB)
  * Exposed SQL 쿼리 로깅 (DEBUG 레벨)
  * 애플리케이션 로깅 (INFO 레벨)

- local.properties: 로컬 개발 환경 설정
  * 데이터베이스 연결 정보 템플릿
  * JWT 시크릿 키 템플릿
  * 소셜 로그인 API 키 템플릿
  * Git 무시 처리 (민감 정보 보호)
Spring Boot 제거:
- 기존 Java 코드 전체 삭제 (이미 java-backup으로 보존됨)
- build.gradle (Maven 기반) 제거
- Spring Boot 관련 설정 파일 정리

Phase 2 마이그레이션 성과:
✅ 컴파일 성공: 모든 Kotlin 코드 빌드 통과
✅ 서버 실행: 8080 포트에서 정상 구동
✅ API 테스트: 헬스체크 엔드포인트 정상 응답
✅ 의존성 주입: Koin DI 시스템 정상 작동 (4개 정의)
✅ 데이터베이스: Exposed ORM 연결 준비 완료
✅ 라우팅: REST API 엔드포인트 구조 완성

현재 구현 완료된 기능:
- User 도메인: CRUD, 소셜 로그인, 소프트 딜리트
- Challenge 도메인: CRUD, 사용자별 조회, 날짜 범위 검색
- API 응답: 통일된 BaseResponse 구조
- 에러 처리: BusinessException 글로벌 핸들링
- 로깅: 구조화된 로그 시스템

다음 단계: Phase 3 인증 시스템 (JWT, 카카오/애플 소셜 로그인)
JWT Provider:
- JwtProvider.kt: JWT 토큰 생성, 검증, 파싱 핵심 로직
  * Access Token (2시간), Refresh Token (2주), Admin Token (6시간)
  * HMAC256 서명 알고리즘
  * 사용자 ID, 역할(USER/ADMIN) 클레임 지원
  * 토큰 만료 검증 및 Bearer 접두사 처리

TokenService:
- TokenService.kt: 토큰 관리 서비스 레이어
  * 토큰 발급, 재발급, 검증 비즈니스 로직
  * TokenResponse, ReissueResponse, AdminTokenResponse DTO
  * 토큰 유효성 검사 및 사용자 정보 추출

JWT Exception:
- JwtException.kt: JWT 관련 예외 처리
  * InvalidToken, ExpiredToken, MalformedToken 등
  * BusinessException 상속으로 통일된 에러 응답
  * HTTP 상태 코드별 세분화된 예외 타입

Ktor Authentication:
- Security.kt: Ktor Authentication 플러그인 설정
  * jwt-auth: 일반 사용자 인증
  * jwt-refresh: 리프레시 토큰 검증
  * jwt-admin: 관리자 인증
  * 각 인증 실패별 커스텀 Challenge 응답

DI Integration:
- DependencyInjection.kt: Koin DI에 JWT 서비스 등록
  * JwtProvider, TokenService 싱글톤 등록
  * 환경 설정 기반 JWT Secret 주입
소셜 로그인 Provider:
- SocialLoginProvider.kt: 소셜 로그인 인터페이스 정의
  * SocialUserInfo, SocialPlatform enum, SocialLoginRequest DTO
  * 카카오/애플 통합 인터페이스

- KakaoLoginProvider.kt: 카카오 로그인 구현체
  * OAuth2 Authorization Code Flow
  * 액세스 토큰 발급, 사용자 정보 조회, 토큰 갱신
  * 연결 해제(unlink) 기능 지원

- AppleLoginProvider.kt: 애플 로그인 구현체
  * Sign in with Apple Identity Token 검증
  * Apple 공개키 조회 및 JWT 서명 검증
  * Client Secret 생성 (ES256 - 추후 구현 예정)

- SocialLoginService.kt: 소셜 로그인 통합 서비스
  * 플랫폼별 로그인 처리 추상화
  * 토큰 관리 및 사용자 연결 해제

- SocialLoginException.kt: 소셜 로그인 예외 처리
  * 플랫폼별 세분화된 에러 타입
  * 네트워크 오류, 토큰 오류 등 상황별 처리

Auth 도메인:
- AuthService.kt: 인증 비즈니스 로직
  * 소셜 로그인, 토큰 재발급, 로그아웃, 회원탈퇴
  * 프로필 조회/수정, 신규/기존 사용자 판별

- AuthDto.kt: 인증 관련 DTO 모음
  * LoginResponse, UserInfo, ProfileUpdateRequest
  * SocialLoginRequestDto, TokenReissueRequest

- AuthException.kt: 인증 도메인 예외 처리
  * 사용자 관련, 토큰 관련, 관리자 관련 예외

- AuthRoutes.kt: 인증 API 엔드포인트
  * POST /auth/login: 소셜 로그인
  * POST /auth/reissue: 토큰 재발급
  * POST /auth/logout: 로그아웃 (JWT 필요)
  * DELETE /auth/withdraw: 회원탈퇴 (JWT 필요)
  * GET/PUT /auth/me: 프로필 조회/수정 (JWT 필요)

DI & Routing 통합:
- HTTP Client 추가 (Ktor Client CIO + JSON)
- 소셜 로그인 Provider들 DI 등록
- AuthService DI 등록
- AuthRoutes를 메인 라우팅에 통합

현재 지원 기능:
✅ JWT 기반 인증 시스템
✅ 카카오 소셜 로그인 (완전 구현)
✅ 애플 소셜 로그인 (Identity Token 검증까지)
✅ 토큰 발급/재발급/검증
✅ 사용자 프로필 관리
✅ 회원 가입/탈퇴 처리
- Status enum 수정 (NONE, FAILURE, EARNED, UNEARNED)
- DailyChallenge 엔티티 정의 및 매핑 함수 구현
- DailyChallengeDto 완전 구현 (요청/응답 DTO)
- DailyChallengeRepository 인터페이스 및 구현체
- DailyChallengeService 비즈니스 로직 구현
- DailyChallengeRoutes API 엔드포인트 구현
- 예외 처리 시스템 (DailyChallengeException)

API 엔드포인트:
- POST /api/v2/challenge/daily/finish - 일일 챌린지 완료
- POST /api/v2/challenge/daily/success - 상태 변경
- GET /api/v2/challenge/daily/user/{challengeId} - 조회
- ChallengeApp 및 HistoryApp 엔티티 구현
- AppDto 완전 구현 (ChallengeApp, HistoryApp 관련 DTO)
- ChallengeAppRepository/HistoryAppRepository 구현
- ChallengeAppService/HistoryAppService 비즈니스 로직
- AppRoutes API 엔드포인트 구현
- AppException 예외 처리 시스템

주요 기능:
- 챌린지 앱 관리 (등록, 조회, 삭제)
- 앱 사용 기록 관리 (기록 저장, 조회)
- 배치 처리 지원 (여러 앱 동시 처리)
- 달성률 계산 기능

API 엔드포인트:
- POST /api/v2/app/challenge - 챌린지 앱 추가
- GET /api/v2/app/challenge/{challengeId} - 챌린지 앱 조회
- POST /api/v2/app/history - 앱 사용 기록 추가
- GET /api/v2/app/history/{dailyChallengeId} - 사용 기록 조회
- PointDto 구현 (포인트 획득/사용 관련 DTO)
- PointService 비즈니스 로직 구현
- PointRoutes API 엔드포인트 구현
- PointException 예외 처리 시스템
- UserService/ChallengeService 포인트 연동 메소드 추가

포인트 상수:
- EARNED_POINT: 10 (챌린지 성공 시)
- USAGE_POINT: 20 (챌린지 실패 시)

API 엔드포인트:
- GET /api/v1/point/list - 포인트 상태 조회
- PATCH /api/v1/point/earn - 포인트 획득
- PATCH /api/v1/point/use - 포인트 사용
- App 도메인 라우트 추가
- Point 도메인 라우트 추가
- 전체 API 엔드포인트 구성 완료
- Dockerfile: 멀티스테이지 빌드 및 헬스체크 구현
- docker-compose.yml: MySQL, phpMyAdmin 포함 전체 스택 구성
- .env.example: 환경 변수 템플릿 제공
- DEPLOYMENT.md: 상세한 배포 가이드 문서 작성
- script/ktor-start.sh: AWS CodeDeploy용 배포 스크립트

지원하는 배포 방식:
- Docker Compose (추천)
- AWS EC2 인스턴스
- Heroku
- 로컬 환경
- App 도메인 서비스 DI 등록
- Point 도메인 서비스 DI 등록
- Koin 모듈 구성 완료
- AuthRoutes: JWT 미설정 환경에서 인증 라우트 안전 처리
- JwtException: data object로 변경하여 타입 안전성 개선
- Security: JWT 미설정 시에도 빈 Authentication 설정으로 오류 방지

JWT 설정이 없는 환경에서도 서버가 정상 구동되도록 개선
Spring Boot JwtError enum을 Ktor JwtException으로 완전 마이그레이션:

주요 예외 타입:
- 400 BAD REQUEST: EmptyPrincipleException, InvalidTokenHeader
- 401 UNAUTHORIZED:
  * Access Token: InvalidAccessToken, ExpiredAccessToken
  * Refresh Token: InvalidRefreshToken, ExpiredRefreshToken
  * Social Token: InvalidSocialAccessToken, InvalidSocialAccessTokenFormat
  * Apple Identity Token: InvalidIdentityToken, ExpiredIdentityToken, InvalidIdentityTokenClaims, UnableToCreateApplePublicKey
  * Admin Token: InvalidAdminToken
- 404 NOT FOUND: NotFoundRefreshTokenError
- 500 INTERNAL ERROR: InternalServerError

업데이트된 컴포넌트:
- JwtException: 기존 Java enum을 Kotlin sealed class로 변환
- JwtProvider: 상세한 예외 타입별 처리 로직 구현
- TokenService: Refresh Token 재발급 시 정확한 예외 발생
- KakaoLoginProvider: 소셜 토큰 관련 예외 적용
- AppleLoginProvider: Apple Identity Token 관련 예외 적용
- AuthService: 토큰 재발급 예외 처리 간소화

기존 호환성을 위한 @deprecated 별칭 제공
단계별 마이그레이션 과정을 상세히 문서화:

📋 주요 내용:
- 프로젝트 개요 및 기술 스택 변화
- 8단계 마이그레이션 상세 과정
- 핵심 기술 패턴 및 Best Practices
- 학습 포인트 및 참고 자료

🚀 마이그레이션 단계:
1. 핵심 인프라 구축 (이전 세션)
2. 기본 도메인 마이그레이션 (이전 세션)
3. DailyChallenge 도메인 완성
4. App 도메인 마이그레이션
5. Point 시스템 마이그레이션
6. Exposed SQL DSL 문법 오류 수정
7. JWT 예외 시스템 완전 마이그레이션
8. 배포 환경 구성

🎯 학습 가치:
- Framework Migration 전략
- Kotlin/Ktor 실전 활용법
- ORM 전환 (JPA → Exposed)
- Docker 기반 배포 환경 구성

Spring Boot에서 Ktor로 전환하는 팀들을 위한 실무 가이드
@kez-lab
kez-lab requested a review from Copilot August 4, 2025 14:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a migration from Spring Boot to Ktor framework for the HMH server application. The migration includes complete rewrite of the authentication system, database layer, business logic, and API endpoints while maintaining similar functionality.

  • Migrated from Spring Boot to Ktor framework with new plugin system
  • Replaced Spring Security with custom JWT authentication using Auth0 JWT library
  • Migrated from Spring Data JPA to Exposed ORM for database operations
  • Restructured all API endpoints and controllers to use Ktor routing

Reviewed Changes

Copilot reviewed 73 out of 256 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
Application.kt Main application entry point with Ktor module configuration
plugins/*.kt Ktor plugin configurations for serialization, security, routing, monitoring, HTTP, DI, and database
global/auth/* Custom JWT authentication system replacing Spring Security
global/auth/social/* Social login providers for Kakao and Apple
domain//entity/.kt Database entities using Exposed ORM instead of JPA
domain//repository/.kt Repository implementations using Exposed SQL DSL
domain//service/.kt Business logic services adapted for Ktor
domain//controller/.kt API route handlers using Ktor routing
resources/*.yaml Configuration files for Ktor application settings
build.gradle Build configuration with Ktor and Exposed dependencies

Comment on lines +190 to +197
// 현재는 구현 안함 - 실제 프로덕션에서는 Apple ES256 키로 서명 필요
throw NotImplementedError("Apple client secret 생성이 아직 구현되지 않았습니다")
}

private fun loadPrivateKey(): java.security.interfaces.ECPrivateKey {
// 실제 구현에서는 privateKey 문자열을 ECPrivateKey로 변환
// 이는 복잡한 과정이므로 여기서는 간단히 표현
throw NotImplementedError("Private key 로딩 구현 필요")

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Apple client secret generation is not implemented but the method is called in production code. This will cause runtime failures when Apple login is used. Consider implementing the ES256 JWT signing or throwing a more specific exception with guidance.

Suggested change
// 현재는 구현 안함 - 실제 프로덕션에서는 Apple ES256 키로 서명 필요
throw NotImplementedError("Apple client secret 생성이 아직 구현되지 않았습니다")
}
private fun loadPrivateKey(): java.security.interfaces.ECPrivateKey {
// 실제 구현에서는 privateKey 문자열을 ECPrivateKey로 변환
// 이는 복잡한 과정이므로 여기서는 간단히 표현
throw NotImplementedError("Private key 로딩 구현 필요")
try {
val privateKey = loadPrivateKey()
val algorithm = Algorithm.ECDSA256(null, privateKey)
return JWT.create()
.withKeyId(appleKeyId)
.withIssuer(appleTeamId)
.withIssuedAt(now)
.withExpiresAt(expirationTime)
.withAudience(APPLE_AUTH_URL)
.withSubject(clientId)
.sign(algorithm)
} catch (e: Exception) {
throw JwtException.UnableToCreateAppleClientSecret
}
}
private fun loadPrivateKey(): java.security.interfaces.ECPrivateKey {
try {
val privateKeyPem = applePrivateKey // This should be a PEM string (without the "-----BEGIN PRIVATE KEY-----" header/footer)
val privateKeyContent = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\\s+".toRegex(), "")
val keySpec = java.security.spec.PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyContent))
val kf = KeyFactory.getInstance("EC")
return kf.generatePrivate(keySpec) as java.security.interfaces.ECPrivateKey
} catch (e: Exception) {
throw JwtException.UnableToCreateAppleClientSecret
}

Copilot uses AI. Check for mistakes.
Comment on lines +190 to +197
// 현재는 구현 안함 - 실제 프로덕션에서는 Apple ES256 키로 서명 필요
throw NotImplementedError("Apple client secret 생성이 아직 구현되지 않았습니다")
}

private fun loadPrivateKey(): java.security.interfaces.ECPrivateKey {
// 실제 구현에서는 privateKey 문자열을 ECPrivateKey로 변환
// 이는 복잡한 과정이므로 여기서는 간단히 표현
throw NotImplementedError("Private key 로딩 구현 필요")

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The private key loading method is not implemented but referenced in the client secret generation. This creates dead code that should either be implemented or removed if not needed.

Suggested change
// 현재는 구현 안함 - 실제 프로덕션에서는 Apple ES256 키로 서명 필요
throw NotImplementedError("Apple client secret 생성이 아직 구현되지 않았습니다")
}
private fun loadPrivateKey(): java.security.interfaces.ECPrivateKey {
// 실제 구현에서는 privateKey 문자열을 ECPrivateKey로 변환
// 이는 복잡한 과정이므로 여기서는 간단히 표현
throw NotImplementedError("Private key 로딩 구현 필요")
val privateKey = loadPrivateKey()
val algorithm = Algorithm.ECDSA256(null, privateKey)
return JWT.create()
.withIssuer(teamId)
.withIssuedAt(now)
.withExpiresAt(expirationTime)
.withAudience("https://appleid.apple.com")
.withSubject(clientId)
.withKeyId(keyId)
.sign(algorithm)
}
private fun loadPrivateKey(): ECPrivateKey {
// Replace this PEM string with your actual private key, or load from a secure location
val privateKeyPem = """
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END PRIVATE KEY-----
""".trimIndent()
val privateKeyPEM = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\\s+".toRegex(), "")
val encoded: ByteArray = Base64.getDecoder().decode(privateKeyPEM)
val keySpec = PKCS8EncodedKeySpec(encoded)
val kf = KeyFactory.getInstance("EC")
return kf.generatePrivate(keySpec) as ECPrivateKey

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +25
println("⚠️ JWT 설정이 불완전합니다. 인증 기능은 비활성화됩니다.")
// Authentication 플러그인은 설치하되 빈 설정으로 유지
install(Authentication) {
// 빈 설정
jwt("jwt-auth") {}
jwt("jwt-refresh") {}
jwt("jwt-admin") {}
}
return

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty JWT authentication configuration when JWT secret is missing creates a security vulnerability. The authentication will not properly validate tokens, potentially allowing unauthorized access.

Suggested change
println("⚠️ JWT 설정이 불완전합니다. 인증 기능은 비활성화됩니다.")
// Authentication 플러그인은 설치하되 빈 설정으로 유지
install(Authentication) {
// 빈 설정
jwt("jwt-auth") {}
jwt("jwt-refresh") {}
jwt("jwt-admin") {}
}
return
throw IllegalStateException("JWT secret is missing. The server cannot start without a valid JWT secret for authentication.")

Copilot uses AI. Check for mistakes.
}

// 인증이 필요한 라우트들 (JWT 설정이 있을 때만 활성화)
try {

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using a try-catch block around the authenticate block is unusual and may hide authentication configuration errors. Consider handling JWT configuration issues at the application startup level instead.

Copilot uses AI. Check for mistakes.

// JWT 의존성
single<sopt.org.hmh.global.auth.jwt.JwtProvider> {
val jwtSecret = getProperty<String>("jwt.secret", "default-secret")

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a hardcoded default JWT secret 'default-secret' is a security risk. If the JWT secret configuration is missing, the application should fail to start rather than use an insecure default.

Suggested change
val jwtSecret = getProperty<String>("jwt.secret", "default-secret")
val jwtSecret = getProperty<String>("jwt.secret")

Copilot uses AI. Check for mistakes.
suspend fun changeStatusByCurrentStatus(dailyChallenge: DailyChallenge): DailyChallenge {
val newStatus = when (dailyChallenge.status) {
Status.NONE -> Status.UNEARNED
Status.FAILURE -> return dailyChallenge // 실패 상태는 그대로 유지

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method changeStatusByCurrentStatus returns the original dailyChallenge without updating its status field when status is FAILURE, but the method name suggests it should change the status. This creates inconsistency between the returned object and database state.

Copilot uses AI. Check for mistakes.

return EarnPointResponse(
earnedPoint = EARNED_POINT,
totalPoint = updatedUser.point.toInt(),

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting Long to Int using toInt() can cause data loss if the point value exceeds Int.MAX_VALUE. Consider using Long consistently or add bounds checking.

Copilot uses AI. Check for mistakes.

return UsePointResponse(
usedPoint = USAGE_POINT,
totalPoint = updatedUser.point.toInt(),

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting Long to Int using toInt() can cause data loss if the point value exceeds Int.MAX_VALUE. Consider using Long consistently or add bounds checking.

Copilot uses AI. Check for mistakes.
Comment thread local.properties
# For customization when using a Version Control System, please read the
# header note.
#Sun Aug 03 16:49:47 KST 2025
sdk.dir=/Users/kwak-euijin/Library/Android/sdk

Copilot AI Aug 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local.properties file contains local machine-specific paths and should not be committed to version control as it exposes local development environment information.

Suggested change
sdk.dir=/Users/kwak-euijin/Library/Android/sdk
sdk.dir=/Users/kwak-euijin/Library/Android/sdk
local.properties

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants