아래는 현재 QuestHouse 백엔드 애플리케이션의 구조와 주요 컴포넌트를 정리한 문서입니다.

프로젝트의 전체적인 아키텍처, 패키지 구조, 주요 기능(엔티티, 서비스, 컨트롤러 등), 보안 설정, 외부 연동 지점 등을 포함하고 있습니다.


1. 프로젝트 개요

  • 프로젝트명: QuestHouse

  • 기술 스택:

    • JVM: Java 17 이상

    • 프레임워크: Spring Boot 3.x

    • 데이터베이스: PostgreSQL (+ P6Spy)

    • 보안: Spring Security (OAuth2 로그인, JWT 인증)

    • 문서: Springdoc OpenAPI (Swagger)

    • 알림: Firebase Cloud Messaging + WebSocket(SockJS/STOMP)

    • 로깅: Logback + P6Spy + CommonsRequestLoggingFilter

    • 빌드: Gradle (또는 Maven)

  • 주요 기능:

    1. 소셜 로그인(카카오 / 구글) → JWT 발급

    2. 회원 관리 (프로필 조회/수정, 탈퇴)

    3. 그룹 관리 (생성/조회/수정/삭제, 멤버 초대/가입/권한 부여)

    4. 퀘스트(Quest) 기능 (퀘스트 생성/조회/수정/삭제, 할당/수행/완료)

    5. 제출(Submission) 기능 (제출 등록/조회/수정/삭제, 피드백 관리)

    6. 알림(Notification) 기능 (실시간 WebSocket + FCM 푸시)

    7. 예외 처리, 로깅, 모니터링(기본 Actuator 제외)


2. 패키지 구조

src/main/java
└── site.beyondchasm.questhouse
    ├── QuesthouseApplication.java                      // 애플리케이션 진입점
    ├── core
    │   ├── config
    │   │   ├── FirebaseConfig.java                      // FCM 초기화
    │   │   ├── JwtConfig.java                           // JWT 디코더 설정
    │   │   ├── OpenApiConfig.java                       // Swagger/OpenAPI 설정
    │   │   ├── RequestLoggingConfig.java                // HTTP 요청 로깅 필터 설정
    │   │   └── WebConfig.java                            // RestTemplate 빈 등록
    │   ├── exception
    │   │   └── GlobalExceptionHandler.java               // 공통 예외 처리기
    │   └── security
    │       ├── CustomOAuth2UserService.java              // OAuth2 로그인 후 사용자 처리
    │       ├── JwtAuthenticationFilter.java              // JWT 인증 필터
    │       ├── JwtTokenProvider.java                     // JWT 생성/검증 유틸
    │       ├── OAuth2AuthenticationSuccessHandler.java   // OAuth2 로그인 성공 후 JWT 발급
    │       ├── SecurityConfig.java                       // Spring Security 설정
    │       └── SecurityUtil.java                         // (추가될 수 있는) 보안 헬퍼 메서드
    │
    ├── features
    │   ├── auth
    │   │   ├── data
    │   │   │   └── dto
    │   │   │       ├── RefreshRequestDto.java
    │   │   │       ├── SocialLoginRequestDto.java
    │   │   │       ├── SocialLoginResponseDto.java
    │   │   │       └── TokenResponseDto.java
    │   │   ├── domain
    │   │   │   └── entity
    │   │   │       └── RefreshToken.java                  // 리프레시 토큰 엔티티
    │   │   ├── service
    │   │   │   ├── AuthService.java                      // 인터페이스
    │   │   │   └── impl
    │   │   │       └── AuthServiceImpl.java              // 로그인/리프레시/로그아웃 로직
    │   │   └── web
    │   │       └── AuthController.java                   // 인증 API (소셜 로그인, 리프레시)
    │   │
    │   ├── common
    │   │   └── data
    │   │       └── entity
    │   │           └── BaseTimeEntity.java               // createdAt, updatedAt 상속용
    │   │
    │   ├── group
    │   │   ├── data
    │   │   │   └── dto
    │   │   │       ├── CreateGroupRequest.java
    │   │   │       ├── GroupResponse.java
    │   │   │       └── UpdateGroupRequest.java
    │   │   ├── domain
    │   │   │   └── entity
    │   │   │       └── Group.java                         // 그룹 엔티티
    │   │   ├── repository
    │   │   │   └── GroupRepository.java                  // JPA 리포지토리
    │   │   ├── service
    │   │   │   ├── GroupAuthChecker.java                  // 권한 검사 유틸 (Owner, Leader, Member 검증)
    │   │   │   ├── GroupService.java                      // 인터페이스
    │   │   │   └── impl
    │   │   │       └── GroupServiceImpl.java              // 그룹 생성/수정/삭제 등
    │   │   └── web
    │   │       └── GroupController.java                   // 그룹 CRUD API
    │   │
    │   ├── groupMember
    │   │   ├── data
    │   │   │   └── dto
    │   │   │       ├── CreateGroupMemberRequest.java
    │   │   │       ├── GroupMemberResponse.java
    │   │   │       └── UpdateGroupMemberRequest.java
    │   │   ├── domain
    │   │   │   ├── entity
    │   │   │   │   └── GroupMember.java                    // 그룹 멤버 연결 테이블
    │   │   │   └── enums
    │   │   │       └── GroupMemberRole.java               // 역할(ADMIN, MEMBER, PENDING, INVITED, BANNED)
    │   │   ├── repository
    │   │   │   └── GroupMemberRepository.java             // JPA 리포지토리
    │   │   ├── service
    │   │   │   ├── GroupMemberService.java                // 인터페이스
    │   │   │   └── impl
    │   │   │       └── GroupMemberServiceImpl.java        // 초대, 가입 대기, 벤 등 상태 관리
    │   │   └── web
    │   │       └── GroupMemberController.java             // 그룹 멤버 API
    │   │
    │   ├── notification
    │   │   ├── config
    │   │   │   └── WebSocketConfig.java                   // STOMP/WebSocket 설정
    │   │   ├── data
    │   │   │   └── dto
    │   │   │       ├── NotificationResponse.java
    │   │   │       ├── UserDeviceTokenRequest.java
    │   │   │       └── UserDeviceTokenResponse.java
    │   │   ├── domain
    │   │   │   ├── entity
    │   │   │   │   ├── Notification.java                   // 알림 엔티티
    │   │   │   │   └── UserDeviceToken.java                 // 디바이스 토큰 엔티티
    │   │   │   └── enums
    │   │   │       └── NotificationType.java               // 알림 유형(퀘스트 할당, 피드백 등)
    │   │   ├── repository
    │   │   │   ├── NotificationRepository.java
    │   │   │   └── UserDeviceTokenRepository.java
    │   │   ├── service
    │   │   │   ├── FcmPushService.java                      // 인터페이스
    │   │   │   ├── NotificationService.java
    │   │   │   ├── UserDeviceTokenService.java
    │   │   │   └── impl
    │   │   │       ├── FcmPushServiceImpl.java              // FCM 푸시 전송
    │   │   │       ├── NotificationServiceImpl.java         // DB 저장 → FCM + WebSocket
    │   │   │       └── UserDeviceTokenServiceImpl.java      // 토큰 등록/조회/삭제
    │   │   └── web
    │   │       ├── NotificationController.java              // 알림 조회/읽음/삭제
    │   │       └── UserDeviceTokenController.java           // 디바이스 토큰 관리
    │   │
    │   ├── quest
    │   │   ├── data
    │   │   │   └── dto
    │   │   │       ├── CreateQuestAssignRequest.java
    │   │   │       ├── CreateQuestRequest.java
    │   │   │       ├── QuestAssignResponse.java
    │   │   │       ├── QuestResponse.java
    │   │   │       ├── RewardResponse.java
    │   │   │       ├── UpdateQuestAssignRequest.java
    │   │   │       └── UpdateQuestRequest.java
    │   │   ├── domain
    │   │   │   ├── entity
    │   │   │   │   ├── Quest.java                            // 퀘스트 엔티티
    │   │   │   │   ├── QuestAssignment.java                  // 할당 엔티티
    │   │   │   │   ├── QuestMedia.java                       // 퀘스트 미디어(이미지/파일) 엔티티
    │   │   │   │   └── Reward.java                           // 보상(Reward) 엔티티
    │   │   │   └── enums
    │   │   │       ├── QuestAssigneeStatus.java             // 상태(PENDING, COMPLETED, REJECTED 등)
    │   │   │       ├── QuestAssigneeType.java               // 할당 타입(TEXT, IMAGE 등)
    │   │   │       ├── QuestRewardType.java                 // 보상 타입(COIN, XP 등)
    │   │   │       └── QuestType.java                       // 퀘스트 유형(TEXT, SURVEY 등)
    │   │   ├── repository
    │   │   │   ├── QuestAssignmentRepository.java
    │   │   │   ├── QuestMediaRepository.java
    │   │   │   ├── QuestRepository.java
    │   │   │   └── RewardRepository.java
    │   │   ├── service
    │   │   │   ├── QuestService.java                         // 인터페이스
    │   │   │   ├── QuestAssignService.java
    │   │   │   ├── RewardService.java
    │   │   │   └── impl
    │   │   │       ├── QuestServiceImpl.java
    │   │   │       ├── QuestAssignServiceImpl.java
    │   │   │       └── RewardServiceImpl.java
    │   │   └── web
    │   │       ├── QuestController.java                      // 퀘스트 CRUD
    │   │       ├── QuestAssignController.java                // 할당 CRUD
    │   │       └── RewardController.java                     // 보상 조회/설정
    │   │
    │   └── submission
    │       ├── data
    │       │   └── dto
    │       │       ├── CreateSubmissionFeedbackRequest.java
    │       │       ├── CreateSubmissionRequest.java
    │       │       ├── SubmissionFeedbackMediaRequest.java
    │       │       ├── SubmissionFeedbackMediaResponse.java
    │       │       ├── SubmissionFeedbackResponse.java
    │       │       ├── SubmissionMediaRequest.java
    │       │       ├── SubmissionMediaResponse.java
    │       │       ├── SubmissionResponse.java
    │       │       └── UpdateSubmissionRequest.java
    │       ├── domain
    │       │   ├── entity
    │       │   │   ├── Submission.java                        // 제출 엔티티
    │       │   │   ├── SubmissionFeedback.java                // 피드백 엔티티
    │       │   │   ├── SubmissionFeedbackMedia.java           // 피드백 미디어 엔티티
    │       │   │   └── SubmissionMedia.java                   // 제출 미디어 엔티티
    │       │   └── enums
    │       │       ├── FeedbackType.java                     // 피드백 유형(APPROVE, REJECT 등)
    │       │       ├── ReactionType.java                     // 리액션 유형(좋아요 등)
    │       │       ├── ReportStatus.java                      // 신고 상태
    │       │       └── ReportType.java                        // 신고 유형
    │       ├── repository
    │       │   ├── SubmissionRepository.java
    │       │   ├── SubmissionFeedbackRepository.java
    │       │   ├── SubmissionFeedbackMediaRepository.java
    │       │   └── SubmissionMediaRepository.java
    │       ├── service
    │       │   ├── SubmissionService.java                     // 인터페이스
    │       │   ├── SubmissionFeedbackService.java
    │       │   ├── SubmissionMediaService.java
    │       │   └── impl
    │       │       ├── SubmissionServiceImpl.java
    │       │       ├── SubmissionFeedbackServiceImpl.java
    │       │       └── SubmissionFeedbackMediaServiceImpl.java
    │       └── web
    │           ├── SubmissionController.java                  // 제출 CRUD
    │           ├── SubmissionMediaController.java             // 제출 미디어 CRUD
    │           ├── SubmissionFeedbackController.java          // 피드백 CRUD
    │           └── SubmissionFeedbackMediaController.java     // 피드백 미디어 CRUD

application.properties
firebase-service-account.json

3. 주요 컴포넌트 상세

3.1. Core Configurations

3.1.1. FirebaseConfig.java

  • 위치: core/config/FirebaseConfig.java

  • 역할:

    • @PostConstruct로 애플리케이션 시작 시 firebase-service-account.json을 로드하여 FirebaseApp.initializeApp() 호출

    • 이후 FirebaseMessaging.getInstance()를 통해 FCM 푸시를 전송할 수 있도록 준비

3.1.2. JwtConfig.java

  • 위치: core/config/JwtConfig.java

  • 역할:

    • application.properties의 jwt.secret(Base64 인코딩된 값)을 디코딩해 SecretKey 빈 등록

    • NimbusJwtDecoder를 사용해 JWT 디코더 빈을 생성

    • (현재 Spring Security Resource Server로 JWT 검증만 담당, JwtTokenProvider__는 직접 생성/서명)

3.1.3. OpenApiConfig.java

  • 위치: core/config/OpenApiConfig.java

  • 역할:

    • Springdoc OpenAPI 설정

    • API 문서 타이틀/버전/설명, Contact, License, Server, SecurityScheme(“BearerAuth” — JWT 인증)

    • 주요 태그(User, Group, Quest, Submission, Notification) 정의

3.1.4. RequestLoggingConfig.java

  • 위치: core/config/RequestLoggingConfig.java

  • 역할:

    • CommonsRequestLoggingFilter 빈 등록

    • HTTP 요청 시 “IP, 쿼리스트링, 바디(최대 5000바이트) 등”을 로그에 남김

    • 필요 시 민감 정보 마스킹, 특정 경로 제외, 포맷 커스터마이징 등을 적용 가능

3.1.5. WebConfig.java

  • 위치: core/config/WebConfig.java

  • 역할:

    • RestTemplate 빈 등록

    • 외부 API(소셜 로그인, 알림 등) 호출 시 재사용


3.2. 예외 처리

3.2.1. GlobalExceptionHandler.java

  • 위치: core/exception/GlobalExceptionHandler.java

  • 역할:

    • @ControllerAdvice로 전역 예외를 잡아서 JSON 형태로 응답

    • 주요 핸들러:

      • MethodArgumentNotValidException → 400 Bad Request (요청 바디 유효성)

      • ConstraintViolationException → 400 Bad Request (파라미터 유효성)

      • EntityNotFoundException → 404 Not Found

      • IllegalArgumentException → 400 Bad Request

      • IllegalStateException → 409 Conflict

      • AccessDeniedException → 403 Forbidden

      • AuthenticationException → 401 Unauthorized

      • 기타 Exception → 500 Internal Server Error

    • 응답 포맷:

{
  "timestamp": "2025-06-05T15:23:10.123Z",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed: field1: must not be blank, field2: ...",
  "path": "/api/users"
}

3.3. 보안(Security)

3.3.1. CustomOAuth2UserService.java

  • 위치: core/security/CustomOAuth2UserService.java

  • 역할:

    1. 소셜 로그인 시(/oauth2/authorize/{provider} → /oauth2/callback/{provider}) loadUser(…) 호출

    2. 제공받은 attrs에서 id, properties.nickname, kakao_account.email 등 정보 추출

    3. UserRepository.findBySocialProviderAndProviderId(…)로 기존 사용자 조회

      • 없으면 User.builder()…build() → DB 저장(new user)

    4. DB에 존재하는 roles 정보를 SimpleGrantedAuthority 리스트로 변환하여 DefaultOAuth2User 생성 → 인증 컨텍스트로 반환

3.3.2. JwtTokenProvider.java

  • 위치: core/security/JwtTokenProvider.java

  • 역할:

    • JWT 생성/검증 유틸 클래스

    • 주요 메서드:

      1. generateAccessToken(Authentication auth)

        • sub = auth.getName() (UUID 형식),

        • roles = auth.getAuthorities() → List

        • issuedAt = now, expiration = now + accessTokenValidityInMs

        • HS256 서명, compact

      2. generateAccessToken(String subject, List roles)

        • 편의 메서드 (Auth 대신 subject+roles 직접 지정)

      3. generateRefreshToken(String subject)

        • sub = subject, issuedAt = now, expiration = now + refreshTokenValidityInMs, 서명

      4. getUsername(String token) → 토큰에서 sub 꺼내기

      5. getExpirationDate(String token) → 토큰 만료일 조회

      6. validateToken(String token) → 파싱 성공/실패 여부

      7. getRoles(String token) → 토큰에서 roles 클레임(String 리스트) 꺼내기

3.3.3. OAuth2AuthenticationSuccessHandler.java

  • 위치: core/security/OAuth2AuthenticationSuccessHandler.java

  • 역할:

    • CustomOAuth2UserService에서 인증 성공 후 호출됨

    • onAuthenticationSuccess(…)에서:

      1. roles = auth.getAuthorities() → List

      2. accessToken = jwtProvider.generateAccessToken(auth.getName(), roles)

      3. refreshToken = jwtProvider.generateRefreshToken(auth.getName())

      4. expiresAt = jwtProvider.getExpirationDate(accessToken)

      5. JSON 형태로 HTTP 응답:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cC...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cC...",
  "expiresAt": "2025-06-06T15:23:10Z"
}

3.3.4. JwtAuthenticationFilter.java

  • 위치: core/security/JwtAuthenticationFilter.java

  • 역할:

    • 모든 요청 직전(OncePerRequestFilter)에 실행되어 HTTP 헤더 Authorization: Bearer {token}을 확인

    • jwtProvider.validateToken(token) → true이면

      1. username = jwtProvider.getUsername(token)

      2. roles = jwtProvider.getRoles(token) → List

      3. authorities = roles.stream().map(SimpleGrantedAuthority::new)

      4. auth = new UsernamePasswordAuthenticationToken(username, null, authorities)

      5. SecurityContextHolder.getContext().setAuthentication(auth)

    • 이 후 필터 체인 계속 진행

3.3.5. SecurityConfig.java

  • 위치: core/security/SecurityConfig.java

  • 역할:

    • @EnableWebSecurity, @EnableMethodSecurity(prePostEnabled=true)

    • HTTP 보안 설정:

http
  .csrf().disable()
  .sessionManagement().sessionCreationPolicy(STATELESS)
  .and()
  .authorizeHttpRequests(auth -> auth
      // Swagger / OpenAPI
      .requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/swagger-ui/index.html", "/swagger-resources/**", "/webjars/**").permitAll()
      // OAuth2, 인증 API
      .requestMatchers("/oauth2/**", "/api/auth/**").permitAll()
      // 회원가입 API
      .requestMatchers(HttpMethod.POST, "/api/users").permitAll()
      // WebSocket 핸드셰이크 엔드포인트
      .requestMatchers("/ws-notifications/**").permitAll()
      // 나머지 요청은 인증 필요
      .anyRequest().authenticated()
  )
  .oauth2Login(oauth -> oauth
      .authorizationEndpoint().baseUri("/oauth2/authorize")
      .and()
      .redirectionEndpoint().baseUri("/oauth2/callback/*")
      .and()
      .userInfoEndpoint().userService(oAuth2UserService)
      .and()
      .successHandler(successHandler)
  )
  .oauth2ResourceServer(rs -> rs.jwt() /* JWK Set URI 별도 관리 */)
  .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  • Method-level 인가

    • @PreAuthorize(“@groupAuthChecker.isOwner(#groupId, authentication.name)”) 등으로 세밀한 권한 검증 가능

    • DefaultMethodSecurityExpressionHandler 빈 등록해 두어 SpEL에서 @groupAuthChecker 등을 참조하도록 설정

3.3.6. SecurityUtil.java

  • 위치: core/security/SecurityUtil.java

  • 역할 (예시):

    • public static UUID getCurrentUserId()

      • Authentication auth = SecurityContextHolder.getContext().getAuthentication()

      • return UUID.fromString(auth.getName())

    • public static String getCurrentUsername()

      • return SecurityContextHolder.getContext().getAuthentication().getName()


3.4. Auth Feature

3.4.1. RefreshToken.java

  • 위치: features/auth/domain/entity/RefreshToken.java

  • 필드:

    • @Id @GeneratedValue UUID id

    • String token (리프레시 토큰 값)

    • User user (@ManyToOne)

    • Instant createdAt, expiresAt (BaseTimeEntity 상속 or 별도 컬럼)

  • 역할: 리프레시 토큰 영속화 → 로그아웃 시 토큰 삭제, 리프레시 시 유효성 체크

3.4.2. DTO (data/dto)

  • RefreshRequestDto.java: 리프레시 토큰 요청 (body: { “refreshToken”: ”…” })

  • SocialLoginRequestDto.java: 소셜 로그인 요청 (body: { “provider”: “KAKAO”, “accessToken”: ”…” })

  • SocialLoginResponseDto.java: 소셜 로그인 응답 (body: { “userId”: ”…”, “accessToken”: ”…”, “refreshToken”: ”…” })

  • TokenResponseDto.java: 단순 토큰 응답 (body: { “accessToken”: ”…”, “expiresAt”: ”…”, “refreshToken”: ”…” })

3.4.3. AuthService.java / AuthServiceImpl.java

  • 주요 메서드:

    1. socialLogin(SocialLoginRequestDto)

      • OAuth2 공급자(Token 검증 → 사용자 정보 조회)

      • DB에 사용자 정보 저장/조회 (CustomOAuth2UserService와 유사)

      • accessToken, refreshToken 생성 + DB 저장

      • SocialLoginResponseDto 반환

    2. refreshToken(RefreshRequestDto)

      • DB에서 RefreshToken 엔티티 조회 → 만료일 검사

      • 새로운 AccessToken 생성 → TokenResponseDto 반환

    3. logout(String refreshToken)

      • DB에서 RefreshToken 삭제

3.4.4. AuthController.java

  • 엔드포인트:

    • POST /api/auth/social-login → 소셜 로그인 요청

    • POST /api/auth/refresh-token → 리프레시 요청

    • POST /api/auth/logout → 로그아웃 요청


3.5. User Feature

3.5.1. User.java

  • 위치: features/user/domain/entity/User.java

  • 주요 필드:

    • UUID id (@GeneratedValue(generator=“UUID”))

    • String email (unique)

    • String passwordHash (optional: 소셜 로그인 시 null)

    • SocialProvider socialProvider (OAuth2 공급자)

    • String providerId (소셜 공급자 고유 ID)

    • String nickname, String avatarUrl

    • LocalDate birthDate, String gender (optional)

    • int xp, coins

    • Set roles (@ElementCollection)

    • Boolean isActive, Boolean isLocked

    • 연관관계:

      • List refreshTokens

      • List memberships

      • List quests

      • List rewards

      • List notifications

3.5.2. DTO (data/dto)

  • CreateUserRequest.java:
String email; 
String password; 
String nickname; 
String avatarUrl;  // optional  
LocalDate birthDate;  // optional  
String gender;  // optional  
  • UpdateUserRequest.java:

String nickname;       // optional  
String avatarUrl;      // optional  
String password;       // optional (새 비밀번호)  
LocalDate birthDate;   // optional  
String gender;         // optional  
  • UserResponse.java:

UUID id; 
String email; 
String nickname; 
String avatarUrl; 
LocalDate birthDate; 
String gender; 
int xp; 
int coins; 
Set<String> roles; 
Boolean isActive; 
Boolean isLocked; 
Instant createdAt; 
Instant updatedAt; 

3.5.3. UserService.java / UserServiceImpl.java

  • 주요 메서드:

    1. UserResponse createUser(CreateUserRequest)

      • 이메일 중복 검사 → password는 반드시 암호화(PBKDF2/BCrypt 등) 후 저장.

      • roles=Set.of(“ROLE_USER”), isActive=true, isLocked=false 기본 세팅 → DB 저장 → DTO 반환

    2. UserResponse updateUser(UUID id, UpdateUserRequest)

      • 해당 유저 조회 → 필드별 null 체크 후 덮어쓰기 → 저장 → DTO 반환

    3. void deleteUser(UUID id)

      • 실제 삭제 대신 isActive=false(Soft Delete) → 저장

    4. UserResponse getUserById(UUID id)

      • 조회 → 404 예외 → DTO 반환

    5. List getAllUsers()

      • 전체 조회 → toDto() 스트림 변환 → 반환

    6. UserResponse registerOrGet(SocialProvider, providerId, email, nickname)

      • 소셜 로그인 시 호출

      • DB 조회 → 없으면 새로 생성 → DTO 반환

3.5.4. UserController.java

  • 엔드포인트:

    1. GET /api/users/me → 내 정보 조회 (JWT 인증 필요)

    2. PUT /api/users/me → 내 정보 수정 (닉네임/아바타/비밀번호/생년월일/성별)

    3. GET /api/users/all → 전체 사용자 조회 (관리자 전용이라고 가정)

    4. GET /api/users/{id} → 특정 사용자 조회

    5. POST /api/users → 새 사용자 생성 (회원가입, 인증 없이 접근 가능)

    6. DELETE /api/users/{id} → 사용자 비활성화(Soft Delete, 관리자 전용)


3.6. Group Feature

3.6.1. Group.java

  • 위치: features/group/domain/entity/Group.java

  • 주요 필드:

    • UUID id (@GeneratedValue(generator=“UUID”))

    • String name (unique, max=255)

    • String logoUrl, String coverImageUrl, String description

    • Boolean isPublic (기본 true)

    • User owner (@ManyToOne) → 그룹 소유자(Owner)

    • String inviteCode (unique, length=50) → 임의의 랜덤 문자열

    • Integer memberCount (기본 0)

    • Integer maxMembers (기본 5 혹은 요청에 따라 변경)

    • Boolean isPremium (기본 false) → 유료 업그레이드 시 true

    • List members (@OneToMany) → 연관된 멤버 리스트

3.6.2. DTO (data/dto)

  • CreateGroupRequest.java:
String name;
String logoUrl;          // optional
String coverImageUrl;    // optional
String description;      // optional
Boolean isPublic;        // optional (기본 true)
Integer maxMembers;      // optional (기본 5)
  • UpdateGroupRequest.java:

String name;
String logoUrl;
String coverImageUrl;
String description;
Boolean isPublic;
Integer maxMembers;
  • GroupResponse.java:

UUID id;
String name;
String logoUrl;
String coverImageUrl;
String description;
Boolean isPublic;
String inviteCode;
Integer memberCount;
Integer maxMembers;
Boolean isPremium;
UUID ownerId;
Instant createdAt;
Instant updatedAt;

3.6.3. GroupService.java / GroupServiceImpl.java

  • 주요 메서드:

    1. GroupResponse createGroup(CreateGroupRequest, String userId)

      • 소유자(Owner) 조회 → 이름 중복 검사 → new Group(…) 빌더 생성 → DB 저장 → DTO 반환

      • inviteCode는 추후 별도 로직에서 랜덤 생성

      • 기본 maxMembers = req.maxMembers != null ? req.maxMembers : 5

      • isPremium = false

    2. GroupResponse getGroup(UUID groupId)

      • 조회 → 404 예외 → DTO 반환

    3. List getAllGroups()

      • 전체 조회 → 스트림 toDto()

    4. GroupResponse updateGroup(UUID groupId, UpdateGroupRequest, String userId)

      • 권한: group.getOwner().getId() == userId(Owner만 수정 가능)

      • 이름 중복 검사(같은 이름 다른 그룹)

      • 각 필드별 null 체크 후 덮어쓰기

      • req.maxMembers < memberCount이면 예외

      • 저장 후 DTO 반환

    5. void deleteGroup(UUID groupId, String userId)

      • 권한: Owner만

      • DB 삭제

    6. boolean existsByName(String name) → 이름 중복 체크

    7. GroupResponse upgradeToPremium(UUID groupId, String userId, int additionalCnt)

      • 권한: Owner만

      • isPremium == false 검사 후 isPremium=true, maxMembers += additionalCnt

      • 중복 결제 방지(이미 isPremium=true이면 예외)

      • DTO 반환

3.6.4. GroupAuthChecker.java

  • 위치: features/group/service/GroupAuthChecker.java

  • 역할:

    • DB에 직접 접근하기보다는, GroupRepository, GroupMemberRepository 등을 주입받아

      • boolean isOwner(UUID groupId, String userId)

      • boolean isLeader(UUID groupId, String userId) (만약 Leader 개념 추가 시)

      • boolean isMember(UUID groupId, String userId)

    • 이를 SpEL이나 서비스 레벨 권한 검사에 활용

3.6.5. GroupController.java

  • 엔드포인트:

    1. POST /api/groups → 그룹 생성 (인증된 사용자만)

    2. GET /api/groups/{id} → 그룹 조회 (공개 그룹은 누구나, 비공개 그룹은 Member 또는 Owner/Leader만)

    3. GET /api/groups → 모든 그룹 조회 (페이징/필터링 추후 추가 가능)

    4. PUT /api/groups/{id} → 그룹 수정 (Owner만)

    5. DELETE /api/groups/{id} → 그룹 삭제 (Owner만)


3.7. GroupMember Feature

3.7.1. GroupMember.java

  • 위치: features/groupMember/domain/entity/GroupMember.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • Group group (@ManyToOne)

    • User user (@ManyToOne)

    • GroupMemberRole role (EnumType.STRING, 기본 MEMBER)

      • ADMIN (Owner가 지정한 관리자)

      • MEMBER (승인된 일반 멤버)

      • PENDING (가입 대기 중)

      • INVITED (초대 대기 중)

      • BANNED (추후에 차단 상태)

    • Instant joinedAt (@Column, 기본 Instant.now())

    • Instant leftAt (탈퇴 또는 강제 탈퇴 시 설정)

3.7.2. DTO (data/dto)

  • CreateGroupMemberRequest.java:
UUID groupId;
UUID userId;         // 초대할(또는 가입 요청할) 사용자
GroupMemberRole role; // 초대 시 기본 역할: INVITED / 가입 요청 시 PENDING
  • UpdateGroupMemberRequest.java:

GroupMemberRole role;  // ADMIN, MEMBER, BANNED 등으로 업데이트
  • GroupMemberResponse.java:

UUID id;
UUID groupId;
UUID userId;
String role;        // ADMIN, MEMBER, PENDING, INVITED, BANNED
Instant joinedAt;
Instant leftAt;
Instant createdAt;
Instant updatedAt;

3.7.3. GroupMemberService.java / GroupMemberServiceImpl.java

  • 주요 메서드:

    1. GroupMemberResponse create(GroupMember entity)

      • 초대 시: role = INVITED, 가입 요청 시: role = PENDING 처리

      • GroupMemberRepository.save(…) → DTO 반환

      • 그룹의 memberCount 증가

    2. GroupMemberResponse update(UUID id, UpdateGroupMemberRequest)

      • role 변경(ADMIN ↔ MEMBER, PENDING → MEMBER 승인, BANNED 등)

      • leftAt 처리(탈퇴/강제 탈퇴 시)

    3. void delete(UUID id)

      • 멤버 삭제(탈퇴) → leftAt = now, 그룹의 memberCount—

    4. GroupMemberResponse getById(UUID id)

    5. List getAll()

    6. List getByUserId(UUID userId)

    7. List getByGroupId(UUID groupId)

3.7.4. GroupMemberController.java

  • 엔드포인트:

    1. POST /api/group-members → 초대 또는 가입 요청

      • 요청 Body: CreateGroupMemberRequest

      • 권한:

        • 초대: 그룹 Owner/Leader만 가능

        • 가입 요청: 인증된 사용자 누구나 가능

    2. GET /api/group-members/{id} → 특정 멤버 조회 (Owner/Leader 또는 본인)

    3. GET /api/group-members → 전체 조회 (관리자용, 혹은 Owner/Leader)

    4. GET /api/group-members/by-user/{userId} → 사용자별 멤버십 목록 조회 (본인 또는 관리자)

    5. GET /api/group-members/by-group/{groupId} → 그룹별 멤버 목록 조회 (Member 이상)

    6. PUT /api/group-members/{id} → 멤버 역할/상태 수정 (Owner/Leader)

    7. DELETE /api/group-members/{id} → 멤버 탈퇴(본인) 또는 강제 탈퇴(Owner/Leader)


3.8. Quest Feature

3.8.1. Quest.java

  • 위치: features/quest/domain/entity/Quest.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • User sender (@ManyToOne) → 퀘스트를 만든 사람(Owner/Leader)

    • Group group (@ManyToOne) → 소속 그룹

    • String title, String description

    • QuestType type (EnumType.STRING, 예: TEXT, SURVEY 등)

    • Instant dueDate → 마감일

    • String repeatRule → cron 식(반복 설정)

    • String rewardConfig → JSON 형태 보상 설정 (예: {“type”:“COIN”,“amount”:10} 등)

    • Boolean isPublic (퀘스트 공개 여부, 기본 true)

    • Instant createdAt, Instant updatedAt (BaseTimeEntity 상속)

3.8.2. QuestAssignment.java

  • 위치: features/quest/domain/entity/QuestAssignment.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • Quest quest (@ManyToOne)

    • UUID assigneeId → 할당받는 유저 ID (GroupMember.user.id)

    • QuestAssigneeStatus status (EnumType.STRING, 기본 PENDING)

      • PENDING, COMPLETED, REJECTED 등

    • Instant respondedAt → 오너/리더가 승인/거절 처리한 시각

    • Instant completedAt → 피드백 완료 시각(멤버가 완료 시)

    • Instant createdAt, Instant updatedAt

3.8.3. QuestMedia.java

  • 위치: features/quest/domain/entity/QuestMedia.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • Quest quest (@ManyToOne)

    • String mediaType (“IMAGE”, “VIDEO”, “FILE”)

    • String url → 저장소(S3, 로컬 등)에 저장된 파일 URL

    • Integer sortOrder → 정렬 순서

    • Instant createdAt, Instant updatedAt

3.8.4. Reward.java

  • 위치: features/quest/domain/entity/Reward.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • User user (@ManyToOne) → 보상을 받은 유저

    • Quest quest (@ManyToOne) → 보상 출처 퀘스트

    • QuestRewardType type (EnumType.STRING, 예: COIN, XP 등)

    • int amount → 보상 수량

    • Instant grantedAt → 부여 시각

    • Instant createdAt, Instant updatedAt

3.8.5. DTO (data/dto)

  • CreateQuestRequest.java:
String title;
String description;
QuestType type;
Instant dueDate;         // ex: "2025-06-10T15:00:00Z"
String repeatRule;       // optional
String rewardConfig;     // optional (JSON 문자열)
Boolean isPublic;        // optional (기본 true)
  • UpdateQuestRequest.java:

String title;
String description;
QuestType type;
Instant dueDate;
String repeatRule;
String rewardConfig;
Boolean isPublic;
  • QuestResponse.java:

UUID id;
UUID senderId;
UUID groupId;
String title;
String description;
String type;
Instant dueDate;
String repeatRule;
String rewardConfig;
Boolean isPublic;
Instant createdAt;
Instant updatedAt;
  • CreateQuestAssignRequest.java:

UUID questId;
UUID assigneeId;
QuestAssigneeStatus status;    // 기본 PENDING
  • UpdateQuestAssignRequest.java:

QuestAssigneeStatus status;
Instant respondedAt;   // 승인/거절 시각
Instant completedAt;   // 완료 시각
  • QuestAssignResponse.java:

UUID id;
UUID questId;
UUID assigneeId;
String status;
Instant respondedAt;
Instant completedAt;
Instant createdAt;
Instant updatedAt;
  • RewardResponse.java:

UUID id;
UUID userId;
UUID questId;
String type;
int amount;
Instant grantedAt;
Instant createdAt;
Instant updatedAt;

3.8.6. QuestService.java / QuestServiceImpl.java

  • 주요 메서드:

    1. QuestResponse createQuest(CreateQuestRequest, String userId)

      • User sender, Group group 조회(권한 검사: Owner/Leader만), Quest.builder() → 저장 → DTO 반환

    2. QuestResponse getQuestById(UUID questId, String userId)

      • 퀘스트 조회 → 권한 검사(비공개 그룹은 Member 이상) → DTO 반환

    3. List getAllQuests(String userId)

      • 인증된 사용자 관점에서 볼 수 있는 모든 퀘스트 조회(페이징 옵션 추가 가능)

    4. QuestResponse updateQuest(UUID questId, UpdateQuestRequest, String userId)

      • 권한: Owner/Leader만

      • 필드별 null 체크 후 덮어쓰기 → 저장 → DTO 반환

    5. void deleteQuest(UUID questId, String userId)

      • 권한: Owner/Leader만 → 삭제

    6. List getQuestsByGroup(UUID groupId, String userId)

      • 그룹별 퀘스트 조회(비공개 그룹은 Member 이상)

3.8.7. QuestAssignService.java / QuestAssignServiceImpl.java

  • 주요 메서드:

    1. QuestAssignResponse createAssignment(CreateQuestAssignRequest, String userId)

      • Quest quest 조회 → 권한 검사(Owner/Leader) → QuestAssignment.builder() → 저장 → DTO 반환

      • 알림: 할당 대상(assigneeId)에게 NotificationType.QUEST_ASSIGNED

    2. QuestAssignResponse updateAssignment(UUID assignId, UpdateQuestAssignRequest, String userId)

      • QuestAssignment existing 조회 → Quest quest = existing.getQuest() → groupId 추출 →

        • 권한: Owner/Leader 전용 (모든 상태 변경 가능)

        • 권한: assignee 전용 (PENDING → COMPLETED만 가능)

      • 상태/resp ondedAt/completedAt 업데이트 → 저장 → DTO 반환

      • 알림: 완료 시 NotificationType.QUEST_COMPLETED

    3. void deleteAssignment(UUID assignId, String userId)

      • 권한: Owner/Leader만 → 삭제

    4. List getAssignmentsByQuest(UUID questId, String userId)

      • Quest quest 조회 → 그룹 공개 여부 검사(비공개면 Member 이상) → 조회 → DTO 리스트 반환

    5. List getAssignmentsByAssignee(UUID assigneeId, String userId)

      • 권한: 본인 only → 조회 → DTO 리스트 반환

    6. List getAssignmentsByGroup(UUID groupId, String userId)

      • 권한: 그룹 공개 여부 검사(비공개면 Member 이상) → 조회 → DTO 리스트 반환

3.8.8. RewardService.java / RewardServiceImpl.java

  • 주요 메서드:

    1. RewardResponse grantReward(UUID questId, UUID userId, QuestRewardType, int amount)

      • User user 조회 → Quest quest 조회(권한: Owner/Leader만) → Reward.builder() → 저장 → DTO 반환

    2. List getRewardsByUser(UUID userId)

      • 해당 사용자에게 부여된 모든 보상 조회 → DTO 리스트 반환

3.8.9. QuestController.java, QuestAssignController.java, RewardController.java

  • 엔드포인트 요약:

    QuestController

메서드경로설명권한
POST/api/quests퀘스트 생성Owner/Leader
GET/api/quests/{id}단일 퀘스트 조회그룹 공개 or Member 이상
GET/api/quests모든 퀘스트 조회인증된 사용자
PUT/api/quests/{id}퀘스트 수정Owner/Leader
DELETE/api/quests/{id}퀘스트 삭제Owner/Leader
GET/api/quests/by-group/{groupId}그룹별 퀘스트 조회그룹 공개 or Member 이상
  • QuestAssignController
메서드경로설명권한
POST/api/quest-assignments할당 생성Owner/Leader
GET/api/quest-assignments/{id}단일 할당 조회Owner/Leader or Assignee
GET/api/quest-assignments/by-quest/{questId}퀘스트별 할당 조회그룹 공개 or Member 이상
GET/api/quest-assignments/by-user/{assigneeId}사용자별 할당 조회본인 only
GET/api/quest-assignments/by-group/{groupId}그룹별 할당 조회그룹 공개 or Member 이상
PUT/api/quest-assignments/{id}할당 업데이트Owner/Leader or Assignee
DELETE/api/quest-assignments/{id}할당 삭제Owner/Leader
  • RewardController
메서드경로설명권한
POST/api/rewards보상 부여Owner/Leader
GET/api/rewards/by-user/{userId}사용자별 보상 조회본인 or Owner/Leader

3.9. Submission Feature

3.9.1. Submission.java

  • 위치: features/submission/domain/entity/Submission.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • QuestAssignment assignment (@ManyToOne) → 어떤 할당에 대한 제출인지

    • User submittedBy (@ManyToOne) → 제출한 유저

    • String content → 텍스트 콘텐츠(optional)

    • Instant createdAt, Instant updatedAt

3.9.2. SubmissionFeedback.java

  • 위치: features/submission/domain/entity/SubmissionFeedback.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • Submission submission (@ManyToOne) → 어떤 제출에 대한 피드백인지

    • User feedbackBy (@ManyToOne) → 피드백 작성자(Owner/Leader)

    • FeedbackType feedbackType (EnumType.STRING, 예: APPROVE, REJECT, COMMENT)

    • String comment → 코멘트(선택)

    • Instant createdAt, Instant updatedAt

3.9.3. SubmissionMedia.java

  • 위치: features/submission/domain/entity/SubmissionMedia.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • Submission submission (@ManyToOne)

    • String mediaType (“IMAGE”, “VIDEO”, “FILE”)

    • String url

    • Integer sortOrder

    • Instant createdAt, Instant updatedAt

3.9.4. SubmissionFeedbackMedia.java

  • 위치: features/submission/domain/entity/SubmissionFeedbackMedia.java

  • 주요 필드:

    • UUID id (@GeneratedValue)

    • SubmissionFeedback feedback (@ManyToOne)

    • String mediaType (“IMAGE”, “VIDEO”, “FILE”)

    • String url

    • Integer sortOrder

    • Instant createdAt, Instant updatedAt

3.9.5. DTO (data/dto)

  • CreateSubmissionRequest.java:
UUID assignmentId;
String content;                // optional
List<SubmissionMediaRequest> media;  // optional
  • UpdateSubmissionRequest.java:

String content;                // optional
List<SubmissionMediaRequest> media;  // optional
  • SubmissionResponse.java:

UUID id;
UUID assignmentId;
UUID submittedBy;
String content;
List<SubmissionMediaResponse> mediaList;
List<SubmissionFeedbackResponse> feedbackList;
  • SubmissionMediaRequest.java:

String mediaType;
String url;
Integer sortOrder;
  • SubmissionMediaResponse.java:

UUID id;
UUID submissionId;
String mediaType;
String url;
Integer sortOrder;
Instant createdAt;
  • CreateSubmissionFeedbackRequest.java:

UUID submissionId;
FeedbackType feedbackType;    // APPROVE, REJECT, COMMENT
String comment;               // optional
List<SubmissionFeedbackMediaRequest> media;  // optional
  • UpdateSubmissionFeedbackRequest.java:

FeedbackType feedbackType;
String comment;
List<SubmissionFeedbackMediaRequest> media;
  • SubmissionFeedbackResponse.java:

UUID id;
UUID submissionId;
UUID feedbackBy;
String feedbackType;
String comment;
List<SubmissionFeedbackMediaResponse> mediaList;
Instant createdAt;
Instant updatedAt;
  • SubmissionFeedbackMediaRequest.java:

String mediaType;
String url;
Integer sortOrder;
  • SubmissionFeedbackMediaResponse.java:

UUID id;
UUID feedbackId;
String mediaType;
String url;
Integer sortOrder;
Instant createdAt;

3.9.6. SubmissionService.java / SubmissionServiceImpl.java

  • 주요 메서드:

    1. SubmissionResponse createSubmission(CreateSubmissionRequest, String userId)

      • QuestAssignment assignment 조회 → 권한 검사(본인 or Owner/Leader)

      • Submission.builder() → 저장 → 미디어가 있으면 SubmissionMedia 별도 저장 → DTO 반환

      • 알림: 제출 시 NotificationType.QUEST_SUBMITTED → 퀘스트 소유자에게 푸시

    2. SubmissionResponse updateSubmission(UUID submissionId, UpdateSubmissionRequest, String userId)

      • Submission existing 조회 → 권한: “본인 또는 Owner/Leader”

      • content, media 업데이트(기존 미디어 삭제 후 신규 추가) → 저장 → DTO 반환

    3. void deleteSubmission(UUID submissionId, String userId)

      • 권한: “본인 또는 Owner/Leader” → 삭제

    4. SubmissionResponse getSubmissionById(UUID submissionId, String userId)

      • 권한: “본인 또는 Owner/Leader 또는 Member 이상” → DTO 반환

    5. List getSubmissionsByAssignment(UUID assignmentId, String userId)

      • 권한: “본인 또는 Owner/Leader 또는 Member 이상” → DTO 리스트 반환

3.9.7. SubmissionFeedbackService.java / SubmissionFeedbackServiceImpl.java

  • 주요 메서드:

    1. SubmissionFeedbackResponse createFeedback(CreateSubmissionFeedbackRequest, String userId)

      • Submission submission 조회 → 권한: Owner/Leader만

      • SubmissionFeedback.builder() → 저장 → 미디어가 있으면 SubmissionFeedbackMedia 저장 → DTO 반환

      • 알림: 피드백 시 NotificationType.FEEDBACK_RECEIVED → 제출자에게 푸시

    2. SubmissionFeedbackResponse updateFeedback(UUID feedbackId, UpdateSubmissionFeedbackRequest, String userId)

      • SubmissionFeedback existing 조회 → 권한: Owner/Leader만 → 업데이트 → DTO 반환

    3. void deleteFeedback(UUID feedbackId, String userId)

      • 권한: Owner/Leader만 → 삭제

    4. List getFeedbacksBySubmission(UUID submissionId, String userId)

      • 권한: “제출자, Owner/Leader, Member 이상” → DTO 리스트 반환

3.9.8. SubmissionMediaService.java / SubmissionMediaServiceImpl.java

  • 주요 메서드:

    • listMediaBySubmission(UUID submissionId) → 미디어 목록 조회 → SubmissionMediaResponse 리스트 반환

    • deleteMedia(UUID mediaId, String userId) → 권한: Owner/Leader or 제출자 → 삭제

3.9.9. SubmissionFeedbackMediaService.java / SubmissionFeedbackMediaServiceImpl.java

  • 주요 메서드:

    • listMediaByFeedback(UUID feedbackId) → 피드백 미디어 조회 → SubmissionFeedbackMediaResponse 리스트 반환

    • deleteMedia(UUID mediaId, String userId) → 권한: Owner/Leader → 삭제

3.9.10. 컨트롤러

  • SubmissionController.java
메서드경로설명권한
POST/api/submissions제출 생성인증된 사용자 (할당된 멤버 또는 Owner/Leader)
GET/api/submissions/{id}단일 제출 조회제출자, Owner/Leader, Member 이상
GET/api/submissions/by-assignment/{assignmentId}할당별 제출 조회제출자, Owner/Leader, Member 이상
PUT/api/submissions/{id}제출 수정제출자, Owner/Leader
DELETE/api/submissions/{id}제출 삭제제출자, Owner/Leader
  • SubmissionMediaController.java

메서드경로설명권한
GET/api/submission-media/by-submission/{subId}제출별 미디어 조회제출자, Owner/Leader, Member 이상
DELETE/api/submission-media/{mediaId}제출 미디어 삭제제출자, Owner/Leader
  • SubmissionFeedbackController.java

메서드경로설명권한
POST/api/submission-feedback피드백 생성Owner/Leader
GET/api/submission-feedback/by-submission/{subId}제출별 피드백 조회제출자, Owner/Leader
PUT/api/submission-feedback/{feedbackId}피드백 수정Owner/Leader
DELETE/api/submission-feedback/{feedbackId}피드백 삭제Owner/Leader
  • SubmissionFeedbackMediaController.java

메서드경로설명권한
GET/api/submission-feedback-media/by-feedback/{fbId}피드백별 미디어 조회제출자, Owner/Leader
DELETE/api/submission-feedback-media/{mediaId}피드백 미디어 삭제Owner/Leader

4. 기타 설정 파일

4.1. 

application.properties

# ──────────────────────────────────────────────────────────────────────
# 1) 애플리케이션 이름 및 서버 포트
# ──────────────────────────────────────────────────────────────────────
spring.application.name=questhouse
server.port=8080

# ──────────────────────────────────────────────────────────────────────
# 2) 데이터베이스 (P6Spy + PostgreSQL)
# ──────────────────────────────────────────────────────────────────────
spring.datasource.url=jdbc:p6spy:postgresql://localhost:5432/beyondchasm
spring.datasource.username=beyondchasm
spring.datasource.password=beyondchasm!a
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver

spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.type.format_sql=true

logging.level.com.p6spy=DEBUG
logging.level.p6spy=TRACE
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.springframework.jdbc.core=TRACE
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
logging.level.org.springframework.security=DEBUG

# P6Spy 예쁘게 찍히도록 src/main/resources/p6spy.properties 추가 권장
# ──────────────────────────────────────────────────────────────────────

# ──────────────────────────────────────────────────────────────────────
# 3) OAuth2 (Kakao & Google)
# ──────────────────────────────────────────────────────────────────────
spring.security.oauth2.client.registration.kakao.client-id=...
spring.security.oauth2.client.registration.kakao.client-secret=...
spring.security.oauth2.client.registration.kakao.provider=kakao
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.kakao.redirect-uri={baseUrl}/oauth2/callback/kakao
spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email
spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id

spring.security.oauth2.client.registration.google.client-id=YOUR_GOOGLE_ID
spring.security.oauth2.client.registration.google.client-secret=YOUR_GOOGLE_SECRET
spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/oauth2/callback/google
spring.security.oauth2.client.registration.google.scope=openid,profile,email

# ──────────────────────────────────────────────────────────────────────
# 4) JWT 설정
# ──────────────────────────────────────────────────────────────────────
jwt.secret=G7sx1nxQv7d5iiZvNRTHjHIG7kKodXVAZVdJ0/O/y5o=
jwt.expiration-ms=86400000
jwt.refresh-expiration-ms=604800000

# ──────────────────────────────────────────────────────────────────────
# 5) RequestLoggingConfig 설정 (필요 시 커스터마이즈)
# ──────────────────────────────────────────────────────────────────────
logging.request.max-payload=5000
logging.request.exclude-paths=/favicon.ico,/health

# ──────────────────────────────────────────────────────────────────────
# 6) 기타
# ──────────────────────────────────────────────────────────────────────
# spring.profiles.active=dev  # 개발/운영 분리 시 사용

4.2. 

p6spy.properties

 (예시)

# src/main/resources/p6spy.properties
driverlist=org.postgresql.Driver
logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
filter=true
module.log=com.p6spy.engine.logging.P6LogFactory

5. ToDo 목록

아래는 현재 구현된 기능 외에, 앞으로 추가/개선해야 할 Task들을 정리한 ToDo 문서입니다.

5.1. 기능 구현 보강

  1. MapStruct 적용

    • DTO ↔ 엔티티 매핑을 MapStruct로 자동화

    • 현재 수동 매핑(toDto(), fromDto()) → 컴파일 시점 매핑 오류 잡기

  2. Database Migration

    • Flyway 또는 Liquibase 도입

    • src/main/resources/db/migration/V1__init_schema.sql 등의 스크립트 작성

    • 배포 시점 자동 스키마 버전 관리

  3. 테스트 커버리지 강화

    • 단위 테스트(Unit Test): Service/Util/Repository 로직

    • 통합 테스트(Integration Test): @SpringBootTest, @AutoConfigureMockMvc를 사용하여 Controller + 실제 DB 연동 테스트

    • Testcontainers: PostgreSQL 컨테이너를 이용한 CI 환경 테스트

  4. 캐싱 도입

    • @EnableCaching + @Cacheable, @CacheEvict 사용

    • 사용자 정보, 그룹 목록, 퀘스트 목록 등 빈번 조회되는 데이터 캐시

    • 운영 환경에서 Redis, Caffeine 등의 캐시 솔루션 연동

  5. 스케줄링 / 비동기

    • @EnableScheduling + @Scheduled 추가

      • 예: 매일 새벽 3시에 “미완료 퀘스트 리마인더” 이메일/푸시 발송

    • @EnableAsync + @Async

      • 외부 API(FCM, 이메일) 호출 비동기 처리

      • 스레드풀 설정 (TaskExecutor 커스터마이징)

  6. 추가 비즈니스 기능

    • Search / Paging

      • 그룹, 퀘스트, 사용자 목록 조회 시 페이징(Pageable) 및 검색(QueryDSL or Spring Data Specifications)

    • 태그(Tag) 기능

      • 퀘스트에 태그(tag) 붙이기 → 태그별 필터링

    • 파일 업로드 / S3 연동

      • 퀘스트 미디어, 제출 미디어, 피드백 미디어 모두 AWS S3에 저장

      • 파일 업로드 엔드포인트 구현 (MultipartFile → S3 SDK 업로드 → URL 반환)

    • 통계(Analytics) 기능

      • “좋아요 수”, “제출 횟수”, “퀘스트 완료율” 등 실시간 통계 API 제공

      • Redis나 InfluxDB 연동 고려


5.2. 보안/운영 고도화

  1. Refresh Token 관리 고도화

    • DB에 저장된 리프레시 토큰에 “만료일자(expiresAt)” 외에 “발급 IP”, “User-Agent” 정보 추가 → 탈취 방어

    • “동시 로그인 기기 제한”: 리프레시 토큰 수량 제한(예: 3개)

    • “리프레시 토큰 블랙리스트”: 로그아웃 시 해당 토큰 무효화

  2. 권한(Role) 세분화

    • 현재 roles = Set (ROLE_USER, ROLE_ADMIN) →

    • “그룹 내 역할”(Owner, Leader, Admin, Member, Pending, Banned) + 글로벌 권한(ROLE_USER, ROLE_ADMIN) 구분

    • @PreAuthorize(“hasRole(‘ROLE_ADMIN’) or @groupAuthChecker.isOwner(#groupId, authentication.name)”) 등 복합 체크

  3. Spring Actuator + 모니터링

    • spring-boot-starter-actuator 추가

    • 어플리케이션 헬스체크, 메트릭(타이머, 카운터) 수집

    • /actuator/health, /actuator/metrics, /actuator/prometheus 등 노출 (보안 설정 필요)

    • Prometheus + Grafana 연동하여 대시보드 구성

  4. 분산 트레이싱

    • Spring Cloud Sleuth + Zipkin 또는 Jaeger 연동

    • “퀘스트 생성 → 알림 발송 → 퀘스트 할당 → 제출 → 피드백” 전체 흐름 트레이싱

    • 분산 환경(마이크로서비스 확장 시)에서도 추적

  5. 로그 포맷팅 & 중앙집중 로깅

    • JSON 로깅: Logback JSON Encoder(예: logstash-logback-encoder)로 로그를 JSON 형태로 저장

    • ELK / EFK / Loki: 수집기(Fluentd, Filebeat)와 연동하여 콘솔+파일 로그를 중앙 서버에 집계

    • 민감 정보(비밀번호, 토큰 등)는 즉시 마스킹


5.3. 배포/인프라

  1. Docker & Docker Compose

    • Dockerfile 작성 (spring-boot:3-jdk-slim 기반)

    • docker-compose.yml 작성

      • 서비스: app(QuestHouse), db(PostgreSQL), redis(Caching), prometheus, grafana, zipkin

    • 로컬/스테이징/프로덕션 환경에서 동일하게 실행되도록 설정

  2. CI/CD 파이프라인

    • GitHub Actions / GitLab CI / Jenkins 활용

    • “코드 푸시 → 유닛/통합 테스트 → Docker 이미지 빌드 → 레지스트리에 푸시 → 스테이징/프로덕션 배포” 자동화

    • QA 환경, 자동 배포 환경 구축

  3. Database Migration

    • Flyway 또는 Liquibase 스크립트 관리

    • V1__create_schema.sql, V2__add_columns.sql 등 버전별 커밋

    • 배포 시점에 자동 마이그레이션 적용

  4. 환경별 설정 관리

    • application-dev.yml, application-prod.yml 분리

    • spring.profiles.active에 따라 로드

    • 민감 정보(비밀번호, API 키)는 AWS Secrets Manager, HashiCorp Vault 등 사용


6. 요약

  • 현재 구현 상태

    • 소셜 로그인 인증 → JWT 발급

    • 회원/그룹/그룹멤버/퀘스트/할당/보상/제출/피드백/알림 등 핵심 도메인 정상 동작

    • 예외 처리, 로깅, Swagger 문서화, WebSocket(Firebase) 연동 완료

  • 추가/개선할 부분 (ToDo)

    1. 매핑 자동화 (MapStruct)

    2. DB 마이그레이션 (Flyway/Liquibase)

    3. 테스트 커버리지 강화 (Unit + Integration + Testcontainers)

    4. 캐싱 도입 (Spring Cache + Redis)

    5. 스케줄링/비동기 (Quartz/Spring Scheduler + Async)

    6. 보안 고도화 (Refresh Token 관리, 권한 분리)

    7. 모니터링/관찰 (Actuator + Prometheus + Grafana + Sleuth)

    8. 중앙집중 로깅 (JSON 로깅 + ELK/EFK/Loki)

    9. Docker/CI-CD 구축 (GitHub Actions, Docker Compose)

    10. 배포 환경 별도 설정 (프로파일 분리, 시크릿 매니저 연동)


이 문서를 기반으로, 현재 구현된 API와 도메인, 설정을 빠르게 파악할 수 있으며, 남은 ToDo 목록을 따라 단계별로 고도화 작업을 진행할 수 있습니다. 추가적인 세부 사항이나 코드 예시가 필요하면 언제든지 알려주세요!