Skip to content

[VISION] 분석 일일 사용량 DB 관리 및 인증 UX 전면 개선#71

Closed
23MinL wants to merge 1 commit intoPETTY-HUB:mainfrom
23MinL:feat/vision-config
Closed

[VISION] 분석 일일 사용량 DB 관리 및 인증 UX 전면 개선#71
23MinL wants to merge 1 commit intoPETTY-HUB:mainfrom
23MinL:feat/vision-config

Conversation

@23MinL
Copy link
Contributor

@23MinL 23MinL commented Jun 8, 2025

📜 PR 내용 요약

Vision 일일 분석 사용량을 DB로 관리하는 기능 도입 및 프론트엔드 인증/UX 전면 리팩토링

  • Vision 분석 기능의 사용량 제한을 기존 세션/메모리 방식에서 DB 기반으로 변경
  • 리프레시 토큰 처리, 인증 자동화 등 UX/보안 대폭 강화
  • Supabase 설정, 서비스/컨트롤러/유효성 코드 및 프론트엔드 코드 전체 업데이트

⚒️ 작업 및 변경 내용(상세하게)

  1. Vision 일일 사용량 DB 관리 기능 추가
  • 엔티티/리포지토리/서비스 계층 추가
    • VisionUsage 엔티티: 사용자별, 날짜별 사용 횟수 기록
    • VisionUsageRepository: JPA 쿼리 메소드 정의
    • VisionUsageService: 사용량 조회/증가/초과 체크 로직 구현
  1. VisionController 비즈니스 로직 변경
  • 컨트롤러에서 로그인/사용량 초과/DB 체크로직 도입
  • 종분석/최종분석 모두 사용량 체크, 응답 메시지 개선
  • ImageValidator 수정/보완
  1. Supabase 연동 및 설정파일 강화
  • SupabaseDataSourceConfig.java에서 외부 DB 커넥션 관리
  • application.yml에서 관련 환경변수, 일일 한도 등 관리
  1. 프론트엔드 UX/보안 개선 (visionUpload.html)
  • 로그인 체크, 사용량 안내, 초과시 버튼 비활성화 등
  • 토큰 만료시 자동 리프레시(authenticatedFetch, refreshAccessToken 함수 추가)
  • UX 대폭 개선(로딩, 결과 표시, 에러 메시지, 접근 제한, 미리보기 등)
  • 모바일 대응 및 접근성 강화
  1. 기타
  • 코드 정리 및 스타일 일관성 유지
  • 기존 session 기반 사용량 체크 로직 완전 제거(DB 일원화)

📚 기타 참고 사항

  • DB 마이그레이션 필요: VisionUsage 테이블 생성 후 정상 작동
  • 배포 환경(application.yml)에서 일일 한도, DB URI, Supabase 키 등 반드시 점검
  • 테스트는 기본적으로 로컬 DB, 실제 배포시 supabase prod로 변경 필요
  • 프론트엔드 자동 토큰갱신(fetch) 동작 안할시, 서버 /api/auth/refresh 엔드포인트 점검 필요

@coderabbitai
Copy link

coderabbitai bot commented Jun 8, 2025

Summary by CodeRabbit

  • 신규 기능

    • 비전 기능의 일일 사용량 추적 및 제한 기능이 추가되었습니다. 사용자는 자신의 일일 사용 현황과 남은 사용 가능 횟수를 확인할 수 있습니다.
    • 비전 업로드 페이지에 토큰 상태 및 오프라인 상태 알림, 이미지 미리보기, 분석 진행 스피너 등 UI가 대폭 개선되었습니다.
    • 자동 토큰 갱신 및 오프라인 감지, 이미지 업로드 전 리사이즈, 안전한 DOM 조작 등 클라이언트 기능이 강화되었습니다.
  • 버그 수정

    • 인증 실패 및 이미지 유효성 검사 오류 시 사용자에게 명확한 에러 메시지가 제공됩니다.
    • 이미지 해상도 및 동물 인식 기준이 완화되어 더 다양한 이미지를 업로드할 수 있습니다.
  • 문서화

    • 비전 업로드 페이지 내 안내 및 상태 메시지가 더욱 명확하게 개선되었습니다.
  • 리팩터링

    • 인증 및 사용량 로직이 통합되어 일관된 처리와 견고한 오류 응답이 제공됩니다.
  • 환경설정

    • 일일 사용량 제한 및 데이터 정리 정책이 환경설정에 추가되었습니다.

Walkthrough

이 변경사항은 Vision 기능에 대한 인증, 사용량 추적, 오류 응답, 그리고 토큰 갱신 로직을 통합적으로 리팩토링합니다. Vision 사용량을 DB에 기록하는 엔티티, 리포지토리, 서비스가 추가되고, 프론트엔드에서는 토큰 상태 확인 및 자동 갱신, 오프라인 감지, 이미지 리사이즈 등 UI/UX가 대폭 개선되었습니다.

Changes

파일/경로 그룹 변경 요약
src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java Vision 관련 JPA 리포지토리 및 엔티티 패키지 스캔 경로 추가
src/main/java/io/github/petty/vision/adapter/in/VisionController.java 인증, 사용량 추적, 오류 응답 일원화 및 토큰 갱신 로직 도입, 메서드 시그니처 및 반환타입 변경, 내부 AuthenticationResult 클래스 추가
src/main/java/io/github/petty/vision/entity/VisionUsage.java
src/main/java/io/github/petty/vision/repository/VisionUsageRepository.java
src/main/java/io/github/petty/vision/service/VisionUsageService.java
Vision 사용량 DB 기록용 엔티티, JPA 리포지토리, 서비스 신규 추가(사용량 증가, 제한, 기록 조회, 정리 등)
src/main/java/io/github/petty/vision/helper/ImageValidator.java 이미지 유효성 검사 완화(해상도, 동물 인식, 라벨, 신뢰도 기준 완화 및 경고만 로그)
src/main/resources/application.yml Spring profile을 dev로 변경, Vision 일일 사용량 제한 및 데이터 정리 정책 추가
src/main/resources/templates/visionUpload.html 토큰 상태/오프라인 알림, 이미지 미리보기, 로딩 스피너, 안전한 DOM 조작, 자동 토큰 갱신, 이미지 리사이즈, UI/UX 대폭 개선, JS 함수 다수 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant VisionController
    participant VisionUsageService
    participant VisionUsageRepository
    participant AuthService

    User->>Browser: 이미지 업로드 및 분석 요청
    Browser->>VisionController: /vision/species (POST, 토큰 포함)
    VisionController->>AuthService: 인증 및 토큰 갱신 확인
    AuthService-->>VisionController: 인증 결과, 사용자 정보
    VisionController->>VisionUsageService: 오늘 사용량 조회/증가
    VisionUsageService->>VisionUsageRepository: DB에서 사용량 확인/증가
    VisionUsageRepository-->>VisionUsageService: 사용량 정보 반환
    VisionUsageService-->>VisionController: 사용량/제한 정보 반환
    VisionController-->>Browser: 분석 결과 또는 오류(사용량 초과, 인증 실패 등)
    Browser->>VisionController: /vision/analyze (POST, 토큰 포함)
    VisionController->>VisionUsageService: 사용량 증가 및 제한 확인
    VisionUsageService->>VisionUsageRepository: DB 업데이트
    VisionUsageRepository-->>VisionUsageService: 결과 반환
    VisionUsageService-->>VisionController: 결과 반환
    VisionController-->>Browser: 분석 결과 또는 오류
Loading

Possibly related PRs

Poem

( ˘▽˘)っ♬

토끼가 본 Vision 세상,
사용량은 DB에 꼼꼼히 기록!
토큰은 자동으로 싱싱하게 갱신,
이미지도 작아도 괜찮아, 걱정 마!
오프라인에도 알림 띄우며
오늘도 Vision은 한층 더 똑똑해졌지!

🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@23MinL
Copy link
Contributor Author

23MinL commented Jun 8, 2025

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Jun 8, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (10)
src/main/java/io/github/petty/vision/helper/ImageValidator.java (2)

36-87: 매우 낮은 해상도 이미지의 분석 품질 고려 필요

50×50 픽셀로 해상도 제한을 대폭 완화했지만, 이렇게 작은 이미지는 Vision API의 분석 품질에 영향을 줄 수 있습니다. 경고만 로깅하고 통과시키는 방식은 UX 개선에는 도움이 되지만, 사용자가 의미 없는 분석 결과를 받을 가능성이 있습니다.

최소한의 품질 보장을 위해 프론트엔드에서 이미지 업로드 시 해상도 안내 메시지를 표시하거나, 분석 결과에 해상도 관련 품질 경고를 포함시키는 것을 고려해보세요.


105-106: Rekognition API 파라미터 변경의 비용 영향 고려

maxLabels를 20으로 증가시키고 minConfidence를 30%로 낮춘 것은 더 많은 라벨을 반환받게 되어:

  • API 응답 크기 증가
  • 처리 시간 증가
  • 낮은 신뢰도의 불필요한 라벨 포함

실제로 필요한 라벨 수와 신뢰도 수준을 재검토하여 최적화하는 것을 권장합니다.

src/main/resources/application.yml (1)

25-25: 파일 끝에 개행 문자 누락

YAML 파일 끝에 개행 문자가 없습니다. 이는 YAML 표준 규약입니다.

다음과 같이 수정하세요:

       enabled: true
-      retention-days: 30  # 30일 이전 데이터 삭제
+      retention-days: 30  # 30일 이전 데이터 삭제
+
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 25-25: no new line character at the end of file

(new-line-at-end-of-file)

src/main/java/io/github/petty/vision/entity/VisionUsage.java (1)

52-55: 동시성 문제 방지를 위한 검증 로직 추가를 고려하세요.

incrementUsage() 메서드에서 한도 초과 시에도 카운트가 증가할 수 있습니다. 서비스 레이어에서 검증이 있지만, 엔티티 레벨에서도 방어적 프로그래밍을 적용하는 것이 좋겠습니다.

 public void incrementUsage() {
+    if (this.usageCount >= this.dailyLimit) {
+        throw new IllegalStateException("일일 사용 한도를 초과할 수 없습니다.");
+    }
     this.usageCount++;
     this.updatedAt = LocalDateTime.now();
 }
src/main/resources/templates/visionUpload.html (1)

223-246: 토큰 갱신 실패 시 무한 루프 방지 로직 추가를 고려하세요.

현재 토큰 갱신 로직에서 연속적인 실패 시 재시도 제한이 없어 무한 루프가 발생할 수 있습니다. 재시도 횟수 제한을 추가하는 것이 좋겠습니다.

재시도 카운터를 추가한 개선안:

+let tokenRefreshAttempts = 0;
+const MAX_REFRESH_ATTEMPTS = 3;

 async function refreshAccessToken() {
     try {
+        if (tokenRefreshAttempts >= MAX_REFRESH_ATTEMPTS) {
+            console.log(' [TOKEN] 최대 재시도 횟수 초과');
+            return { success: false, error: '토큰 갱신 재시도 횟수 초과' };
+        }
+        
+        tokenRefreshAttempts++;
         console.log(' [TOKEN] 리프레시 토큰으로 액세스 토큰 갱신 시도');

         const response = await fetch('/api/auth/refresh', {
             method: 'POST',
             credentials: 'include',
             headers: { 'Content-Type': 'application/json' }
         });

         if (response.ok) {
             const result = await response.json();
             console.log(' [TOKEN] 토큰 갱신 성공:', result.message);
+            tokenRefreshAttempts = 0; // 성공 시 카운터 리셋
             return { success: true };
         }
src/main/java/io/github/petty/vision/adapter/in/VisionController.java (5)

43-46: 하드코딩된 기본값 대신 설정에서 읽어오는 것을 고려하세요.

DEFAULT_DAILY_LIMIT이 3으로 하드코딩되어 있습니다. application.yml의 설정값과 동기화를 위해 @Value 어노테이션을 사용하는 것이 좋겠습니다.

-    private static final int DEFAULT_DAILY_LIMIT = 3;
+    @Value("${vision.daily-limit:3}")
+    private int defaultDailyLimit;

53-54: 로그 메시지의 언어 일관성을 개선하세요.

로그 메시지에 한글과 영어가 혼재되어 있습니다. 일관된 언어 사용을 권장합니다.

-            log.info("🚫 Vision 페이지 접근 실패: 인증 필요");
+            log.info("Vision page access failed: authentication required");

Also applies to: 73-74, 77-77, 94-95


109-109: 프로덕션 환경을 위한 로그 레벨 검토가 필요합니다.

DEBUG 레벨 로그가 매우 상세하게 작성되어 있습니다. 프로덕션 환경에서는 성능에 영향을 줄 수 있으므로, 로그 레벨 설정을 확인하세요.

-        log.info("🔍 [DEBUG] /species 호출됨 - petName: {}, fileSize: {}", petName, file.getSize());
+        log.debug("/species called - petName: {}, fileSize: {}", petName, file.getSize());

Also applies to: 110-110, 114-114, 119-119, 123-123, 128-128, 137-137, 141-141, 146-146


294-316: Java record 사용을 고려하세요.

AuthenticationResult는 불변 데이터 홀더로 Java 14+ record로 간결하게 표현할 수 있습니다.

-    private static class AuthenticationResult {
-        private final boolean authenticated;
-        private final UUID userId;
-        private final String username;
-        // ... constructors and methods
-    }
+    private record AuthenticationResult(
+        boolean authenticated,
+        UUID userId,
+        String username
+    ) {
+        public static AuthenticationResult authenticated(UUID userId, String username) {
+            return new AuthenticationResult(true, userId, username);
+        }
+        
+        public static AuthenticationResult unauthenticated() {
+            return new AuthenticationResult(false, null, null);
+        }
+        
+        public boolean isAuthenticated() { return authenticated; }
+    }

380-401: 사용자 친화적 메시지 변환이 훌륭합니다!

구체적인 해결 방법을 제시하여 UX가 크게 개선되었습니다. 향후 메시지 관리를 위해 외부 설정 파일(messages.properties)로 관리하는 것도 고려해보세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e0e064 and 5cdfd50.

📒 Files selected for processing (8)
  • src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java (2 hunks)
  • src/main/java/io/github/petty/vision/adapter/in/VisionController.java (3 hunks)
  • src/main/java/io/github/petty/vision/entity/VisionUsage.java (1 hunks)
  • src/main/java/io/github/petty/vision/helper/ImageValidator.java (3 hunks)
  • src/main/java/io/github/petty/vision/repository/VisionUsageRepository.java (1 hunks)
  • src/main/java/io/github/petty/vision/service/VisionUsageService.java (1 hunks)
  • src/main/resources/application.yml (2 hunks)
  • src/main/resources/templates/visionUpload.html (6 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
src/main/resources/application.yml

[error] 25-25: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (11)
src/main/java/io/github/petty/vision/helper/ImageValidator.java (1)

98-133: 동물 감지 선택사항 변경의 영향 검토 필요

동물 감지를 선택사항으로 변경하면서 동물이 없는 이미지도 분석이 가능해졌습니다. 이는 UX 개선에는 도움이 되지만:

  1. 서비스가 동물 전용이라면 사용자의 일일 사용량이 무의미하게 소진될 수 있습니다
  2. Rekognition API 호출 비용이 불필요하게 발생할 수 있습니다

동물이 감지되지 않은 경우 사용자에게 경고 메시지를 반환하여 의도하지 않은 사용을 방지하는 것을 고려해보세요.

src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java (1)

42-42: Vision 패키지 설정 추가가 적절함

Vision 기능의 레포지토리와 엔티티 패키지를 JPA 스캔 대상에 추가한 것이 적절합니다. 기존 패턴을 잘 따르고 있으며 새로운 Vision 사용량 추적 기능에 필요한 설정입니다.

Also applies to: 79-79

src/main/java/io/github/petty/vision/entity/VisionUsage.java (1)

13-19: 엔티티 정의가 잘 구조화되어 있습니다.

JPA 어노테이션 사용과 유니크 제약조건 설정이 적절합니다. user_idusage_date 조합에 대한 유니크 제약조건으로 중복 데이터 생성을 방지할 수 있습니다.

src/main/java/io/github/petty/vision/service/VisionUsageService.java (1)

115-127:

Details

❓ Verification inconclusive

관리자 기능에 대한 인증 및 권한 검증이 필요합니다.

updateDailyLimit 메서드는 관리자 전용 기능이지만 권한 검증 로직이 없습니다. 이 메서드를 호출하는 컨트롤러에서 적절한 권한 검증이 이루어지는지 확인이 필요합니다.

이 메서드를 사용하는 컨트롤러에서 권한 검증을 확인해보세요:


🏁 Script executed:

#!/bin/bash
# Description: updateDailyLimit 메서드 사용처에서 권한 검증 확인

# updateDailyLimit 메서드를 호출하는 코드 검색
rg -A 10 -B 5 "updateDailyLimit" --type java

# 관리자 권한 관련 어노테이션 확인
rg -A 3 -B 3 "@PreAuthorize|@RolesAllowed|@Secured" --type java

Length of output: 1653


관리자 전용 기능 권한 검증 필요

updateDailyLimit 메서드는 관리자 전용 기능이지만, 현재 코드 검색 결과 메서드를 호출하는 컨트롤러나 @PreAuthorize·@RolesAllowed·@Secured 등의 어노테이션이 발견되지 않습니다.
컨트롤러 호출부 및 보안 설정(WebSecurityConfigurerAdapter/SecurityFilterChain)에서 ADMIN 권한으로 보호되고 있는지 직접 확인해주세요.

  • 컨트롤러에서 updateDailyLimit(UUID, int) 호출 위치 확인
  • 해당 엔드포인트 혹은 컨트롤러 클래스에 @PreAuthorize("hasRole('ADMIN')") 등 메서드/클래스 단위 보안 어노테이션 적용 여부 확인
  • SecurityConfig(또는 SecurityFilterChain)에서 URL 패턴(/vision/usage/daily-limit 등)별 ADMIN 권한 제한 설정 확인
src/main/resources/templates/visionUpload.html (4)

177-218: 토큰 갱신 로직이 잘 구현되어 있습니다.

authenticatedFetch 함수는 401 응답 시 자동으로 토큰을 갱신하고 요청을 재시도하는 로직이 잘 구현되어 있어 사용자 경험을 크게 개선합니다.


319-364: 이미지 리사이즈 로직이 잘 구현되어 있습니다.

최소 크기 요구사항을 충족하기 위한 이미지 리사이즈 로직이 canvas를 사용해 효율적으로 구현되어 있습니다. 품질 설정과 에러 처리도 적절합니다.


492-499: 429 상태 코드 처리가 적절하게 구현되어 있습니다.

사용량 초과 시 429 상태 코드를 처리하여 페이지를 새로고침하는 로직이 사용자 경험 측면에서 적절합니다.


546-551: 페이지 가시성 변경 시 토큰 상태 확인이 보안을 강화합니다.

사용자가 다른 탭에서 돌아왔을 때 토큰 상태를 확인하는 로직이 보안성을 높이는 좋은 구현입니다.

src/main/java/io/github/petty/vision/adapter/in/VisionController.java (3)

3-3: Import 추가가 적절합니다!

DB 기반 사용량 추적 및 향상된 HTTP 응답 처리를 위한 필수 import들이 잘 추가되었습니다.

Also applies to: 8-8, 21-21, 24-25, 30-30


318-348: 사용량 조회 API가 잘 구현되었습니다!

DB와 세션 fallback 메커니즘이 적절히 구현되었고, source 필드로 데이터 출처를 명시하여 디버깅에 유용합니다.


350-378: 세션 기반 fallback 메서드가 견고하게 구현되었습니다!

날짜 기반 리셋과 null 체크가 적절히 처리되어 있습니다.

Comment on lines +89 to +91
// 🔥 동물 감지도 선택적으로 (실패해도 무조건 통과)
return validateAnimalContentOptional(bytes);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

검증 로직이 실질적으로 무효화됨

모든 경로에서 ValidationResult.valid()를 반환하도록 변경되어 실질적인 검증이 이루어지지 않습니다. 이는:

  • 잘못된 이미지가 다운스트림 서비스로 전달될 수 있음
  • 오류 처리가 다른 레이어로 이동해야 함
  • 로깅만으로는 문제 추적이 어려울 수 있음

최소한의 검증 기준은 유지하면서 UX를 개선하는 균형점을 찾는 것이 좋겠습니다.

Also applies to: 126-126, 131-131

🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/helper/ImageValidator.java around lines
89-91, 126, and 131, the current code returns ValidationResult.valid()
unconditionally, effectively disabling validation. To fix this, reinstate
meaningful validation logic that performs actual checks and returns appropriate
ValidationResult based on the image content. Ensure that failures are handled
properly, either by returning failure results or throwing exceptions as needed,
while still allowing optional paths like animal content validation to pass
gracefully but not bypass all validation. Add proper logging and error handling
to aid troubleshooting without compromising validation integrity.


profiles:
active: prod
active: dev
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

프로덕션 프로파일이 dev로 변경됨

활성 프로파일이 prod에서 dev로 변경되었습니다. 이는 의도적인 변경인지 확인이 필요합니다:

  • 개발 환경 설정이 프로덕션에 배포될 위험
  • 디버그 정보 노출 가능성
  • 성능 최적화 미적용

프로덕션 배포 시에는 반드시 prod 프로파일을 사용해야 합니다.

🤖 Prompt for AI Agents
In src/main/resources/application.yml at line 9, the active profile is set to
'dev' instead of 'prod'. Verify if this change is intentional; if the
application is meant for production deployment, revert the active profile back
to 'prod' to avoid exposing debug information, prevent using development
configurations in production, and ensure performance optimizations are applied.

Comment on lines +41 to +42
@Column(name = "updated_at")
private LocalDateTime updatedAt;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

updatedAt 필드의 자동 업데이트 개선을 고려해보세요.

현재 updatedAt 필드가 수동으로 관리되고 있습니다. 더 일관성 있는 타임스탬프 관리를 위해 @UpdateTimestamp 어노테이션 사용을 권장합니다.

+import org.hibernate.annotations.UpdateTimestamp;

 @Column(name = "updated_at")
+@UpdateTimestamp
 private LocalDateTime updatedAt;

그리고 incrementUsage() 메서드에서 수동 설정 제거:

 public void incrementUsage() {
     this.usageCount++;
-    this.updatedAt = LocalDateTime.now();
 }
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/entity/VisionUsage.java at lines 41-42,
the updatedAt field is currently managed manually. To improve consistency, add
the @UpdateTimestamp annotation to the updatedAt field to enable automatic
timestamp updates on entity changes. Then, remove any manual setting of
updatedAt in the incrementUsage() method to avoid redundant or conflicting
updates.

Comment on lines +25 to +26
@Query("SELECT v FROM VisionUsage v WHERE v.userId = :userId AND v.usageDate = CURRENT_DATE")
Optional<VisionUsage> findTodayUsage(@Param("userId") UUID userId);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

CURRENT_DATE 사용에 대한 시간대 고려가 필요합니다.

CURRENT_DATE는 데이터베이스 서버의 시간대를 기준으로 작동합니다. 애플리케이션과 DB 서버의 시간대가 다를 경우 예상과 다른 결과가 나올 수 있습니다.

시간대 일관성을 확인하는 스크립트를 실행해보세요:


🏁 Script executed:

#!/bin/bash
# Description: 애플리케이션과 DB 시간대 설정 확인

# application.yml에서 시간대 설정 확인
rg -A 3 -B 3 "timezone|time-zone" --type yaml

# Java 코드에서 LocalDate.now() 사용 패턴 확인
rg "LocalDate\.now\(\)" --type java

Length of output: 577


타임존 일관성 확보 필요

현재 JPQL CURRENT_DATE는 DB 서버의 타임존을, LocalDate.now()는 애플리케이션 서버의 타임존을 기준으로 동작하기 때문에 불일치로 인한 데이터 조회 오류가 발생할 수 있습니다. 또한 application.yml에 타임존 설정이 누락되어 있습니다.

수정 제안:

  • 리포지토리 메서드 파라미터화
    CURRENT_DATE 대신 애플리케이션에서 계산한 날짜를 파라미터로 전달하도록 변경합니다.

    - @Query("SELECT v FROM VisionUsage v WHERE v.userId = :userId AND v.usageDate = CURRENT_DATE")
    - Optional<VisionUsage> findTodayUsage(@Param("userId") UUID userId);
    + @Query("SELECT v FROM VisionUsage v WHERE v.userId = :userId AND v.usageDate = :today")
    + Optional<VisionUsage> findUsageByDate(@Param("userId") UUID userId,
    +                                       @Param("today") LocalDate today);

    호출부에서는 LocalDate.now(ZoneId.of("UTC")) 등 애플리케이션 기준 타임존을 명시하여 전달하세요.

  • 애플리케이션 타임존 설정 추가
    src/main/resources/application.yml에 아래 설정을 추가해 애플리케이션과 DB 간 타임존을 통일합니다.

    spring:
      datasource:
        url: jdbc:mysql://<host>:<port>/<db>?serverTimezone=UTC
      jpa:
        properties:
          hibernate:
            jdbc:
              time_zone: UTC
      jackson:
        time-zone: UTC

점검 대상 파일:

  • src/main/java/io/github/petty/vision/repository/VisionUsageRepository.java
  • src/main/java/io/github/petty/vision/service/VisionUsageService.java
  • src/main/resources/application.yml

이와 같이 처리하여 날짜 조회 시 생성되는 타임존 차이를 방지하세요.

🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/repository/VisionUsageRepository.java
around lines 25 to 26, replace the JPQL CURRENT_DATE usage with a method
parameter for the date to avoid timezone inconsistencies between the database
and application server. Modify the method signature to accept a LocalDate
parameter representing the date calculated in the application with a consistent
timezone (e.g., UTC). Update the query to use this parameter instead of
CURRENT_DATE. Additionally, ensure src/main/resources/application.yml includes
timezone settings to unify application and database timezones as specified.

Comment on lines +37 to +39
@Modifying
@Query("DELETE FROM VisionUsage v WHERE v.usageDate < :cutoffDate")
void deleteOldUsageRecords(@Param("cutoffDate") LocalDate cutoffDate);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

@Modifying 쿼리에 트랜잭션 관리가 필요합니다.

deleteOldUsageRecords 메서드는 @Modifying 어노테이션을 사용하지만 트랜잭션 컨텍스트에서 실행되어야 합니다. 서비스 레이어에서 @Transactional로 감싸져 있는지 확인이 필요합니다.

서비스에서 이 메서드 호출 시 트랜잭션 처리를 확인해보세요:


🏁 Script executed:

#!/bin/bash
# Description: deleteOldUsageRecords 메서드 사용처에서 트랜잭션 처리 확인

# 해당 메서드를 호출하는 코드 검색
rg -A 5 -B 5 "deleteOldUsageRecords" --type java

# @Transactional 어노테이션과 함께 사용되는지 확인
ast-grep --pattern 'class $_ {
  $$$
  @Transactional
  $$$
  deleteOldUsageRecords($_)
  $$$
}'

Length of output: 2453


서비스 스케줄러 메서드에 트랜잭션 처리 추가 필요

@Modifying 쿼리는 반드시 트랜잭션 컨텍스트에서 실행되어야 합니다. 현재 cleanupOldRecords() 메서드는 @Scheduled만 붙어 있고, 트랜잭션 관리가 적용되지 않은 상태입니다. 아래 위치에 @Transactional 어노테이션을 추가해 주세요.

• 파일: src/main/java/io/github/petty/vision/service/VisionUsageService.java

  • 메서드 cleanupOldRecords()@Transactional 추가

예시 변경(diff):

    @Scheduled(cron = "0 0 2 * * *")
+   @Transactional
    public void cleanupOldRecords() {
        LocalDate cutoffDate = LocalDate.now().minusDays(30);
        visionUsageRepository.deleteOldUsageRecords(cutoffDate);
        log.info("Vision 사용량 기록 정리 완료: {} 이전 데이터 삭제", cutoffDate);
    }
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/service/VisionUsageService.java around
the cleanupOldRecords() method, add the @Transactional annotation to ensure the
method runs within a transaction. This is necessary because the
deleteOldUsageRecords() method in VisionUsageRepository uses @Modifying and
requires a transactional context. Locate the cleanupOldRecords() method and
annotate it with @Transactional above the existing @Scheduled annotation or
method signature.

Comment on lines +105 to +110
@Scheduled(cron = "0 0 2 * * *")
public void cleanupOldRecords() {
LocalDate cutoffDate = LocalDate.now().minusDays(30);
visionUsageRepository.deleteOldUsageRecords(cutoffDate);
log.info("Vision 사용량 기록 정리 완료: {} 이전 데이터 삭제", cutoffDate);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

스케줄러 설정의 시간대 일관성을 확인하세요.

cron 표현식 "0 0 2 * * *"은 서버의 기본 시간대를 사용합니다. 애플리케이션의 시간대 설정과 일치하는지 확인이 필요합니다.

시간대 설정을 확인해보세요:

만약 특정 시간대를 명시해야 한다면:

-@Scheduled(cron = "0 0 2 * * *")
+@Scheduled(cron = "0 0 2 * * *", zone = "Asia/Seoul")

🏁 Script executed:

#!/bin/bash
# Description: 스케줄러 시간대 설정 확인

# application.yml에서 시간대 관련 설정 확인
rg -A 5 -B 5 "spring\.task\.scheduling|timezone|time-zone" --type yaml

# @Scheduled 어노테이션 사용 패턴 확인
rg -A 3 -B 1 "@Scheduled.*cron" --type java

Length of output: 1182


스케줄러 타임존 설정 일관성 보장 필요

현재 application.yml에는 스케줄러의 기본 시간대를 지정하는 설정이 없고, VisionUsageService@Scheduled에는 zone 옵션이 빠져 있어 다른 스케줄러(SyncService)와 동작 환경이 달라질 수 있습니다.
다음 중 하나를 적용해주세요:

  • 전역 설정을 추가
    spring:
      task:
        scheduling:
          time-zone: Asia/Seoul
  • 또는 해당 애노테이션에 zone 옵션 명시
    --- a/src/main/java/io/github/petty/vision/service/VisionUsageService.java
    +++ b/src/main/java/io/github/petty/vision/service/VisionUsageService.java
       @Scheduled(cron = "0 0 2 * * *", zone = "Asia/Seoul")
       public void cleanupOldRecords() {
           LocalDate cutoffDate = LocalDate.now().minusDays(30);
           visionUsageRepository.deleteOldUsageRecords(cutoffDate);
           log.info("Vision 사용량 기록 정리 완료: {} 이전 데이터 삭제", cutoffDate);
       }

• 대상 파일:

  • src/main/java/io/github/petty/vision/service/VisionUsageService.java
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/service/VisionUsageService.java around
lines 105 to 110, the @Scheduled annotation for cleanupOldRecords lacks an
explicit time zone, which may cause inconsistency with the application's
intended scheduling time zone. Fix this by adding the 'zone' attribute to the
@Scheduled annotation, specifying the correct time zone (e.g., zone =
"Asia/Seoul") to ensure the scheduled task runs consistently with the
application's time zone settings.

Comment on lines +47 to +64
public VisionUsage incrementUsage(UUID userId) {
VisionUsage usage = getTodayUsage(userId);

if (!usage.canUse()) {
throw new IllegalStateException(
String.format("일일 사용 한도(%d회)를 초과했습니다. 현재 사용량: %d회",
usage.getDailyLimit(), usage.getUsageCount())
);
}

usage.incrementUsage();
VisionUsage saved = visionUsageRepository.save(usage);

log.info("Vision 사용량 증가: 사용자={}, 날짜={}, 사용량={}/{}",
userId, usage.getUsageDate(), saved.getUsageCount(), saved.getDailyLimit());

return saved;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

동시성 환경에서의 race condition 방지가 필요합니다.

여러 요청이 동시에 들어올 때 같은 사용자가 한도를 초과하여 사용할 수 있는 race condition이 발생할 수 있습니다. 비관적 락이나 낙관적 락 적용을 고려해보세요.

비관적 락을 사용한 개선안:

 public VisionUsage incrementUsage(UUID userId) {
-    VisionUsage usage = getTodayUsage(userId);
+    // 비관적 락으로 동시성 제어
+    LocalDate today = LocalDate.now();
+    VisionUsage usage = visionUsageRepository.findByUserIdAndUsageDate(userId, today)
+            .orElseGet(() -> createNewUsageRecord(userId, today));
+    
+    // 락을 걸고 다시 조회하여 최신 상태 확인
+    usage = visionUsageRepository.findById(usage.getId())
+            .orElseThrow(() -> new IllegalStateException("사용량 기록을 찾을 수 없습니다."));

     if (!usage.canUse()) {
         throw new IllegalStateException(
                 String.format("일일 사용 한도(%d회)를 초과했습니다. 현재 사용량: %d회",
                         usage.getDailyLimit(), usage.getUsageCount())
         );
     }

또는 리포지토리에 @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션을 추가한 메서드를 만들어 사용하세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/service/VisionUsageService.java around
lines 47 to 64, the incrementUsage method is vulnerable to race conditions in
concurrent environments, allowing usage limits to be exceeded. To fix this,
apply a pessimistic lock by creating a repository method annotated with
@Lock(LockModeType.PESSIMISTIC_WRITE) that fetches the VisionUsage entity with a
write lock. Use this locked method to retrieve the usage before checking and
incrementing usage, ensuring that concurrent requests are serialized and the
usage limit is enforced correctly.

Comment on lines +430 to +447
// 리사이즈된 파일로 FormData 생성
const originalFile = fileInput.files[0];
if (!originalFile) {
alert('이미지를 선택해주세요.');
return;
}

console.log(' [DEBUG] 이미지 리사이즈 확인 중...');
const processedFile = await resizeImageIfNeeded(originalFile, 200, 200);

const fd = new FormData();
fd.append('file', processedFile);
fd.append('petName', document.getElementById('petName').value);

// 이미지 표시 준비
if (showImg) {
showImg.src = URL.createObjectURL(processedFile);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

파일 크기 검증 로직 추가를 권장합니다.

현재 HTML에서 최대 5MB라고 안내하지만 JavaScript에서 실제 파일 크기를 검증하는 로직이 없습니다. 서버 요청 전에 클라이언트에서 파일 크기를 검증하여 사용자 경험을 개선하세요.

파일 크기 검증 로직 추가:

 const originalFile = fileInput.files[0];
 if (!originalFile) {
     alert('이미지를 선택해주세요.');
     return;
 }

+// 파일 크기 검증 (5MB)
+const maxFileSize = 5 * 1024 * 1024; // 5MB
+if (originalFile.size > maxFileSize) {
+    alert('파일 크기는 5MB를 초과할 수 없습니다.');
+    return;
+}

 console.log(' [DEBUG] 이미지 리사이즈 확인 중...');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 리사이즈된 파일로 FormData 생성
const originalFile = fileInput.files[0];
if (!originalFile) {
alert('이미지를 선택해주세요.');
return;
}
console.log(' [DEBUG] 이미지 리사이즈 확인 중...');
const processedFile = await resizeImageIfNeeded(originalFile, 200, 200);
const fd = new FormData();
fd.append('file', processedFile);
fd.append('petName', document.getElementById('petName').value);
// 이미지 표시 준비
if (showImg) {
showImg.src = URL.createObjectURL(processedFile);
}
// 리사이즈된 파일로 FormData 생성
const originalFile = fileInput.files[0];
if (!originalFile) {
alert('이미지를 선택해주세요.');
return;
}
// 파일 크기 검증 (5MB)
const maxFileSize = 5 * 1024 * 1024; // 5MB
if (originalFile.size > maxFileSize) {
alert('파일 크기는 5MB를 초과할 수 없습니다.');
return;
}
console.log(' [DEBUG] 이미지 리사이즈 확인 중...');
const processedFile = await resizeImageIfNeeded(originalFile, 200, 200);
const fd = new FormData();
fd.append('file', processedFile);
fd.append('petName', document.getElementById('petName').value);
// 이미지 표시 준비
if (showImg) {
showImg.src = URL.createObjectURL(processedFile);
}
🤖 Prompt for AI Agents
In src/main/resources/templates/visionUpload.html around lines 430 to 447, add a
file size validation before processing the image. Check the size of the
originalFile and if it exceeds 5MB (5 * 1024 * 1024 bytes), alert the user that
the file is too large and stop further processing by returning early. This
ensures the client-side enforces the file size limit consistent with the UI
guidance.

Comment on lines +178 to 212
try {
log.info(" [DEBUG] DB 사용량 확인 시작");
canAnalyze = visionUsageService.canUseToday(userId);
log.info(" [DEBUG] DB 사용량 확인 성공: canAnalyze={}", canAnalyze);

if (!canAnalyze) {
VisionUsage todayUsage = visionUsageService.getTodayUsage(userId);
String errorMessage = String.format(
"오늘의 분석 한도(%d회)를 모두 사용하셨습니다. (사용: %d회)\n" +
"내일 다시 이용해주세요! 🐾",
todayUsage.getDailyLimit(), todayUsage.getUsageCount());
log.warn(" [DEBUG] 사용량 한도 초과");
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(Map.of("error", errorMessage));
}
} catch (Exception e) {
log.warn(" [DEBUG] DB 사용량 확인 실패, 세션으로 fallback: {}", e.getMessage());

Map<String, Integer> sessionUsage = getSessionUsage(session);
canAnalyze = sessionUsage.get("remaining") > 0;
usageCheckMethod = "Session";

log.info(" [DEBUG] 세션 사용량 확인: canAnalyze={}, remaining={}",
canAnalyze, sessionUsage.get("remaining"));

if (!canAnalyze) {
String errorMessage = String.format(
"오늘의 분석 한도(%d회)를 모두 사용하셨습니다. (사용: %d회)\n" +
"내일 다시 이용해주세요! 🐾",
DEFAULT_DAILY_LIMIT, sessionUsage.get("used"));
log.warn(" [DEBUG] 세션 사용량 한도 초과");
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(Map.of("error", errorMessage));
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

사용량 확인과 증가 사이의 동시성 문제를 고려하세요.

사용량 확인(canUseToday)과 증가(incrementUsage) 사이에 다른 요청이 들어올 수 있습니다. 트랜잭션이나 락을 사용한 원자적 처리를 고려하세요.

// VisionUsageService에서 checkAndIncrementUsage 메서드 구현 제안
@Transactional
public boolean checkAndIncrementUsage(UUID userId) {
    // 확인과 증가를 원자적으로 처리
}

Also applies to: 231-247

🤖 Prompt for AI Agents
In src/main/java/io/github/petty/vision/adapter/in/VisionController.java around
lines 178 to 212 and also lines 231 to 247, there is a concurrency issue between
usage checking and incrementing. To fix this, refactor the usage check and
increment operations into a single atomic method in VisionUsageService, such as
checkAndIncrementUsage, annotated with @Transactional to ensure atomicity.
Replace separate calls to canUseToday and incrementUsage with this new method to
prevent race conditions.

@23MinL 23MinL closed this Jun 8, 2025
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.

1 participant