Conversation
|
Caution Review failedThe pull request is closed. Walkthrough음성 멀티모달 VA 퓨전 파이프라인, VoiceComposite/VoiceJobProcess/FcmToken 모델·마이그레이션, WAV 변환·S3 presigned URL, FCM 멀티캐스트, 성능 로거·메모리 모니터, 예외 계층 및 전역 핸들러, 관련 리포지토리·서비스·API 엔드포인트가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant API as API Endpoint
participant VoiceService
participant S3
participant DB
participant JobRepo as Job Repo
participant STT as STT/NLP Thread
participant Emotion as Emotion Thread
participant Composite as CompositeService
participant FCM as FCM Service
User->>API: POST /users/voices (파일 업로드)
API->>VoiceService: upload_user_voice()
VoiceService->>VoiceService: WAV 변환, 성능 로그 시작
VoiceService->>S3: WAV 업로드
VoiceService->>DB: Voice 레코드 생성
VoiceService->>JobRepo: ensure_job_row()
VoiceService->>API: voice_id 반환
API-->>User: 201 Created
par 비동기 처리
VoiceService->>STT: _process_stt_and_nlp_background()
STT->>DB: VoiceContent 저장
STT->>JobRepo: mark_text_done()
VoiceService->>Emotion: _process_audio_emotion_background()
Emotion->>DB: VoiceAnalyze 저장
Emotion->>JobRepo: mark_audio_done()
end
JobRepo->>JobRepo: try_aggregate() (FOR UPDATE 락)
JobRepo->>Composite: compute_and_save_composite(voice_id)
Composite->>DB: audio/text 데이터 조회
Composite->>Composite: fuse_VA()
Composite->>DB: VoiceComposite 업서트
JobRepo->>FCM: _send_composite_completion_notification()
FCM->>DB: 토큰 조회 및 multicast 전송
sequenceDiagram
participant Care
participant API
participant CareService
participant DB
Care->>API: GET /care/users/voices/analyzing/weekly?month=&week=
API->>CareService: get_emotion_weekly_summary()
CareService->>DB: 연결 사용자 음성 및 VoiceComposite 조회
loop 각 날짜
CareService->>CareService: 비-Unknown 필터 적용
alt 비-Unknown 존재
CareService->>CareService: 비-Unknown 중 최상위 선택 (tie-break day-first)
else 모두 Unknown
CareService->>CareService: 빈도 기반 최다 감정 선택
end
end
CareService->>API: 날짜별 top_emotion 응답
API-->>Care: 200 JSON
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/auth_service.py (1)
45-118: 치명적: CARE 연결 username을 20자 컬럼에 저장하면 즉시 실패이제
connecting_user_code에 USER의 username을 저장하도록 바뀌었지만,app/models.py에서 해당 컬럼은 여전히String(20)(VARCHAR(20))으로 정의돼 있습니다. USER username은 64자까지 허용되므로 21자 이상 username을 연결하는 순간 MySQL에서는DataError가 나거나(STRICT 모드) 무음으로 잘려 실제 username과 불일치합니다. CARE 회원가입·조회가 바로 깨지므로, 컬럼 길이를 username과 동일한 64자로 확장하고 마이그레이션을 동반하거나 기존대로 user_code를 사용하도록 로직을 되돌려야 합니다.
🧹 Nitpick comments (5)
requirements.txt (3)
15-18: 버전 범위 지정 방식 검토 권장.모든 의존성이
>=연산자를 사용하여 하한만 지정되어 있습니다. 프로덕션 환경에서는 주요 의존성의 상한을 지정하거나 특정 마이너 버전으로 핀딩하는 것이 권장됩니다. 특히firebase-admin,torch,transformers등 주요 라이브러리는 더 엄격한 버전 제약이 필요할 수 있습니다.예를 들어:
-firebase-admin>=6.0.0 +firebase-admin>=6.0.0,<7.0.0[scratchpad_end] -->
1-18: 의존성 설치 테스트 권장.새로운 의존성 4개가 추가되었으므로, 모든 의존성이 호환되고 충돌이 없는지 확인하기 위해 설치 테스트를 수행할 것을 권장합니다. 특히
torch와transformers같은 무거운 의존성과 새로 추가된 의존성 간의 호환성을 확인하세요.다음 명령으로 의존성 호환성을 테스트할 수 있습니다:
pip install -r requirements.txt --dry-run[scratchpad_end] -->
| user = self.db.query(User).filter(User.username == care.connecting_user_code).first() | ||
| if not user: | ||
| return {"success": False, "frequency": {}, "message": "Connected user not found."} |
There was a problem hiding this comment.
연결 사용자 조회 키가 잘못되었습니다.
기존 스키마에서 care.connecting_user_code 컬럼에는 말 그대로 user_code 값이 저장되어 왔는데, 여기서 User.username으로 비교하도록 바꾸면 기존 데이터는 전부 조회에 실패하여 보호자용 월간/주간 감정 통계가 전부 빈 결과를 돌려주게 됩니다. 새로운 가입 흐름에서 username을 저장하도록 바꾸었더라도, 과거 레코드를 일괄 마이그레이션하지 않으면 바로 회귀가 발생합니다. 안전하게 User.user_code 비교를 유지하거나, 저장되는 컬럼 자체를 username으로 교체하고 기존 데이터를 모두 갱신하는 과정을 함께 포함시켜 주세요.
Also applies to: 58-60
🤖 Prompt for AI Agents
In app/care_service.py around lines 23-25 (and similarly at lines 58-60), the
code compares care.connecting_user_code to User.username which breaks lookup for
existing records because the legacy column stores user_code; change the query to
compare against User.user_code (or alternatively, only do this if you've
included a full data migration and column rename) so existing data continues to
match—update both places to use User.user_code or implement and document a
migration that renames/moves values to username before switching the comparison.
| # 2) 연결된 사용자 조회 (username으로 조회) | ||
| linked_user = self.get_user_by_username(care.connecting_user_code) | ||
| if not linked_user: | ||
| return [] | ||
| # 3) 연결 사용자 음성 중 분석 완료만(join) 페이징 | ||
| q = ( | ||
| self.db.query(Voice) | ||
| .join(VoiceAnalyze, VoiceAnalyze.voice_id == Voice.voice_id) | ||
| .filter(Voice.user_id == linked_user.user_id) | ||
| .options(joinedload(Voice.questions), joinedload(Voice.voice_analyze)) | ||
| .options(joinedload(Voice.questions), joinedload(Voice.voice_analyze), joinedload(Voice.voice_composite)) | ||
| .order_by(Voice.created_at.desc()) |
There was a problem hiding this comment.
CARE 연결 사용자 조회가 끊어집니다.
여기서도 care.connecting_user_code를 username으로 바라보고 있는데, 컬럼 명세는 여전히 user_code를 담는 형태라서 기존 연결 정보가 그대로라면 보호자가 연결된 사용자의 음성을 한 건도 조회하지 못합니다. 위 흐름과 동일하게 조회 키를 User.user_code로 유지하거나, 저장 데이터/마이그레이션까지 함께 반영해 주세요.
🤖 Prompt for AI Agents
In app/db_service.py around lines 100 to 110, the code is using
care.connecting_user_code as if it were a username which breaks lookup because
the stored value is actually user_code; change the lookup to use the user_code
column (e.g., call or implement get_user_by_user_code or a similar method that
queries User.user_code == care.connecting_user_code) so the linked_user resolves
correctly and existing connections return voice records, or if you intend to
migrate stored values then update the stored data/migration accordingly.
| # 5. 데이터베이스 저장 (기본 정보만) | ||
| # 파일 크기로 대략적인 duration 추정 | ||
| file_size_mb = len(file_content) / (1024 * 1024) | ||
| file_size_mb = len(wav_content) / (1024 * 1024) | ||
| estimated_duration_ms = int(file_size_mb * 1000) # 대략적인 추정 | ||
|
|
||
| # Voice 저장 (STT 없이 기본 정보만) | ||
| voice = self.db_service.create_voice( | ||
| voice_key=key, | ||
| voice_name=file.filename, | ||
| voice_name=wav_filename, | ||
| duration_ms=estimated_duration_ms, | ||
| user_id=user.user_id, | ||
| sample_rate=16000 # 기본값 | ||
| ) | ||
|
|
||
| # 5. 비동기 후처리 (STT→NLP, 음성 감정 분석) | ||
| asyncio.create_task(self._process_stt_and_nlp_background(file_content, file.filename, voice.voice_id)) | ||
| asyncio.create_task(self._process_audio_emotion_background(file_content, file.filename, voice.voice_id)) | ||
| # ensure job row |
There was a problem hiding this comment.
WAV 길이 추정 로직이 잘못되었습니다.
WAV 파일 크기를 MB→ms로 그대로 곱해서 길이를 추정하면 16kHz PCM 기준으로 실제 길이 대비 약 1/32 수준만 측정됩니다. 예를 들어 10초짜리 음성(약 0.3MB)은 300ms로 기록되어 리스트/상세 UI, 통계, 집계 로직에 모두 잘못된 시간이 반영됩니다. WAV 바이트를 다시 열어 프레임 수와 샘플레이트로 정확한 길이를 계산해 주세요.
- file_size_mb = len(wav_content) / (1024 * 1024)
- estimated_duration_ms = int(file_size_mb * 1000) # 대략적인 추정
+ with sf.SoundFile(BytesIO(wav_content)) as wav_file:
+ frames = len(wav_file)
+ sample_rate = wav_file.samplerate
+ estimated_duration_ms = int(frames / sample_rate * 1000)위 계산은 upload_user_voice와 upload_voice_with_question 두 곳 모두에 동일하게 적용해야 합니다.
Also applies to: 565-574
🤖 Prompt for AI Agents
In app/voice_service.py around lines 125-138 (and also apply same fix at
565-574), the current WAV duration estimate uses file size which is incorrect;
instead open the WAV bytes with wave (or librosa/soundfile) via io.BytesIO, read
n_frames and framerate and compute duration_ms = int((n_frames / framerate) *
1000), and use that duration_ms when creating the voice record (also set
sample_rate to the WAV framerate); ensure you handle invalid WAV data with a
clear error/log and fallback behavior as needed.
| original_start = logger.start_time | ||
| clear_logger(0) | ||
| logger = get_performance_logger(voice.voice_id, preserve_time=original_start) | ||
| # 기존 단계들 복사 | ||
| for step in ["시작", "파일변환 완료", "s3업로드 완료", "데이터베이스 입력 완료"]: | ||
| if step in logger.steps: | ||
| continue | ||
| logger.steps[step] = time.time() - original_start | ||
| logger.voice_id = voice.voice_id | ||
|
|
There was a problem hiding this comment.
초기 단계 로그가 요약에서 사라집니다.
clear_logger(0) 이후 새 로거로 갈아끼우면서 logger.steps만 수동으로 채우고 step_order와 step_category를 복원하지 않아, get_summary() 결과에서 “시작/파일변환/s3업로드/데이터베이스 입력” 단계가 전부 누락됩니다. 아래처럼 기존 순서와 카테고리를 저장해 add_step_with_time으로 복원하면 일관성을 유지할 수 있습니다.
- original_start = logger.start_time
- clear_logger(0)
- logger = get_performance_logger(voice.voice_id, preserve_time=original_start)
- for step in ["시작", "파일변환 완료", "s3업로드 완료", "데이터베이스 입력 완료"]:
- if step in logger.steps:
- continue
- logger.steps[step] = time.time() - original_start
+ original_start = logger.start_time
+ existing_steps = dict(logger.steps)
+ existing_order = list(logger.step_order)
+ existing_categories = dict(logger.step_category)
+ clear_logger(0)
+ logger = get_performance_logger(voice.voice_id, preserve_time=original_start)
+ for step in existing_order:
+ elapsed = existing_steps[step]
+ category = existing_categories.get(step, "serial")
+ logger.add_step_with_time(step, elapsed, category)
logger.voice_id = voice.voice_id이렇게 하면 파일 업로드 단계가 performance 로그 요약에 온전히 남습니다.
🤖 Prompt for AI Agents
In app/voice_service.py around lines 143 to 152, after calling clear_logger(0)
you only repopulate logger.steps and miss restoring logger.step_order and
logger.step_category which causes those initial steps to be omitted from
get_summary(); capture the original step_order and step_category before
clearing, then after creating the new logger iterate the original ordered steps
and call add_step_with_time(step_name, timestamp - original_start,
category=original_category) (or equivalent helper) to restore both the ordering
and categories along with times, and finally set logger.voice_id as before.
| revision = '202510310001_add_voice_composite' | ||
| down_revision = None # set to current head in real chain if needed |
There was a problem hiding this comment.
down_revision을 기존 헤드로 연결해야 합니다.
현재 down_revision = None으로 남아 있어서 이 마이그레이션이 독립된 루트 헤드로 인식됩니다. 이미 voice 테이블이 존재하는 다른 마이그레이션 체인이 있다는 전제에서, 이 상태로는 Alembic이 두 개의 시작점을 갖게 되어 alembic upgrade head가 실패하거나 예기치 않은 분리 브랜치가 생깁니다. 기존 최신 헤드 revision ID로 down_revision을 정확히 설정해 주세요.
- down_revision = None # set to current head in real chain if needed
+ down_revision = "<현재 헤드 revision ID로 교체>"Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In migrations/versions/202510310001_add_voice_composite.py around lines 5 to 6,
the down_revision is set to None which makes this migration an independent root;
update down_revision to the current latest head revision ID from your Alembic
revision history (e.g., check alembic/versions or run alembic history/current to
get the exact revision string) and replace None with that revision ID so this
file links to the existing migration chain.
| bcrypt>=4.0.0 | ||
| alembic>=1.12.0 | ||
| psutil>=5.9.0 | ||
| firebase-admin>=6.0.0 |
There was a problem hiding this comment.
🧩 Analysis chain
새로운 의존성의 버전과 호환성 검증 필요.
추가된 의존성들이 PR 목표(FCM 설정, 데이터베이스 마이그레이션, 성능 모니터링, 인증 강화)와 일치하나, 버전이 최신이고 보안 문제가 없는지 확인이 필요합니다. 특히 firebase-admin>=6.0.0의 경우 최신 버전을 사용하는 것이 좋습니다.
다음을 확인해주시기 바랍니다:
[scratchpad_end] -->
🌐 Web query:
firebase-admin 최신 버전 및 보안 상태
💡 Result:
요약: 2025-08-28 기준 Firebase Admin Node.js SDK 최신 안정버전은 v13.5.0이며(릴리스 노트·GitHub), 현재 공개된 직접적인 취약점은 보고되지 않았습니다(취약점 DB·Snyk). 과거 다른 언어용 Admin SDK(예: PHP)에서 발견된 CVE는 있으나 Node.js 최신 릴리스에는 직접 영향이 없는 것으로 보입니다. 권장사항: 최신 버전(v13.5.0)으로 유지하고 의존성(간접 의존성)·보안 공지(OSS 어드바이저·GitHub Advisory)를 주기적으로 모니터링하세요.
근거:
- Firebase 릴리스 노트(관리자 SDK 업데이트 기록, 2025-05·2025-10 등). [1]
- firebase-admin-node GitHub 릴리스 (v13.5.0 — 2025-08-28). [2]
- Snyk 패키지/취약점 확인: firebase-admin 최신 버전(13.5.0)에 직접 보고된 취약점 없음(간접 의존성은 별도 확인 권장). [3]
- 과거 취약점 예시: GHSA/CVE (Firebase Admin SDK for PHP 관련 CVE-2018-1000025). [4]
참고(원하시면 바로 확인해 드립니다):
- 현재 프로젝트의 package.json/package-lock 또는 npm audit·Snyk/OSS Index 스캔 결과를 분석해 간접 의존성 취약 여부를 점검해 드릴 수 있습니다.
🌐 Web query:
bcrypt 4.0.0 이상 버전의 호환성
💡 Result:
짧게 정리합니다 — 어떤 언어/패키지인지에 따라 의미가 다릅니다.
-
Node.js용 (npm) bcrypt (node.bcrypt.js)
- 패키지별 호환성 표: 오래된 4.x 버전은 Node 최신 버전과 호환이 떨어집니다. 예: Node 18 이상에서는 bcrypt >= 6.0.0이 권장/필요합니다. 자세한 호환표는 공식 repo 참조. [1]
-
Python용 bcrypt (PyPI)
- bcrypt 4.0.0부터 구현이 Rust로 변경되어(소스에서 빌드하려면 Rust 컴파일러 필요) 윈도우/리눅스 등에서 휠(wheel)이 없으면 빌드 도구(Rust)가 필요합니다. 또한 manylinux wheel 정책이 변경되었습니다. [2]
-
보안/취약점 주의
- bcrypt < 5.0.0 계열은 NUL 처리·데이터 잘림 등 취약점이 보고되어 있으므로 가능한 경우 5.0.0 이상(또는 최신 안정 버전)으로 업그레이드 권장. [3]
원하시면 (a) 사용 중인 언어/npm 패키지 이름(bcrypt 또는 bcryptjs 등), (b) 현재 버전과 Node/Python 버전을 알려주시면 구체적으로 업그레이드/대체(예: bcryptjs, bcrypt-ts, prebuilt wheel 사용) 방안과 명령을 바로 제시하겠습니다.
참고자료
- node.bcrypt.js GitHub - 호환표 및 설명. [1]
- PyPI bcrypt 4.2.0 릴리즈 노트(4.0.0 변경점: Rust 구현 등). [2]
- Snyk 취약점 정보(bcrypt 관련 권고). [3]
bcrypt 버전을 5.0.0 이상으로 업그레이드 필요. firebase-admin 및 기타 의존성도 최신 버전 검토 권장.
bcrypt>=4.0.0은 보안 취약점(NUL 처리, 데이터 잘림 등)이 보고되어 있으므로 5.0.0 이상 업그레이드 권장합니다.
추가로 firebase-admin의 최신 안정 버전은 v13.5.0(2025-08-28 기준)이며 현재 공개된 직접적인 취약점은 보고되지 않았습니다. 현재 지정된 firebase-admin>=6.0.0은 매우 낮은 버전이므로 최신 버전 사용을 검토하세요. 의존성 및 보안 공지를 주기적으로 모니터링하길 권장합니다.
🤖 Prompt for AI Agents
In requirements.txt around lines 15–18, upgrade bcrypt to at least 5.0.0 and
review/pin newer stable versions for firebase-admin and other low-version deps:
replace the bcrypt line with a constraint of bcrypt>=5.0.0 (or a specific tested
version like bcrypt==5.x.y), update firebase-admin to a recent stable release
(e.g., >=13.5.0) or a specific tested version, and run your dependency
installation, test suite, and a security audit (pip-audit or OWASP
dependency-check) to ensure compatibility; if your project uses a lockfile or
dependency tool (pip-compile/poetry), update and regenerate that lockfile and
resolve any breaking changes in code that depend on bcrypt or firebase-admin
APIs.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
app/voice_service.py (1)
567-574: 파일 크기로 길이를 추정하면 duration이 크게 틀어집니다.
동일한 WAV라도 비트레이트에 따라 길이 추정이 크게 빗나가, 리스트·통계·집계에 잘못된 시간이 저장됩니다. 바로 위upload_user_voice처럼 프레임 수와 샘플레이트로 정확히 계산해 주세요.- file_size_mb = len(wav_content) / (1024 * 1024) - estimated_duration_ms = int(file_size_mb * 1000) + with sf.SoundFile(BytesIO(wav_content)) as wav_file: + frames = len(wav_file) + sr = wav_file.samplerate + estimated_duration_ms = int((frames / sr) * 1000)
🧹 Nitpick comments (1)
app/voice_service.py (1)
145-154: 성능 로그 복원 시 단계 순서와 카테고리가 사라집니다.
logger.steps만 직접 채우면step_order와step_category가 비어 있어 요약에서 초기 단계가 누락됩니다. 아래와 같이 기존 순서·카테고리까지 백업 후add_step_with_time으로 복원해 주세요.- original_start = logger.start_time - clear_logger(0) - logger = get_performance_logger(voice.voice_id, preserve_time=original_start) - for step in ["시작", "파일변환 완료", "s3업로드 완료", "데이터베이스 입력 완료"]: - if step in logger.steps: - continue - logger.steps[step] = time.time() - original_start - logger.voice_id = voice.voice_id + original_start = logger.start_time + existing_steps = dict(logger.steps) + existing_order = list(logger.step_order) + existing_categories = dict(logger.step_category) + clear_logger(0) + logger = get_performance_logger(voice.voice_id, preserve_time=original_start) + for step in existing_order: + elapsed = existing_steps[step] + category = existing_categories.get(step, "serial") + logger.add_step_with_time(step, elapsed, category) + logger.voice_id = voice.voice_id
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
app/main.py(12 hunks)app/repositories/fcm_repo.py(1 hunks)app/repositories/job_repo.py(1 hunks)app/routers/composite_router.py(1 hunks)app/services/fcm_service.py(1 hunks)app/voice_service.py(17 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/repositories/fcm_repo.py
🧰 Additional context used
🧬 Code graph analysis (5)
app/repositories/job_repo.py (4)
app/models.py (3)
VoiceJobProcess(183-194)Voice(32-58)User(8-29)app/services/composite_service.py (2)
CompositeService(8-34)compute_and_save_composite(12-34)app/services/fcm_service.py (2)
FcmService(40-174)send_notification_to_user(47-75)app/performance_logger.py (4)
get_performance_logger(109-115)log_step(22-37)save_to_file(91-102)clear_logger(118-121)
app/voice_service.py (10)
app/s3_service.py (2)
upload_fileobj(26-32)get_presigned_url(45-57)app/stt_service.py (1)
transcribe_voice(144-146)app/nlp_service.py (1)
analyze_text_sentiment(202-204)app/emotion_service.py (1)
analyze_voice_emotion(225-227)app/db_service.py (3)
get_db_service(307-309)create_voice(45-60)get_user_by_username(32-34)app/auth_service.py (2)
get_auth_service(297-299)get_user_by_username(144-146)app/repositories/job_repo.py (4)
ensure_job_row(64-71)mark_text_done(74-77)mark_audio_done(80-83)try_aggregate(86-136)app/performance_logger.py (5)
get_performance_logger(109-115)clear_logger(118-121)log_step(22-37)save_to_file(91-102)add_step_with_time(39-44)app/models.py (2)
VoiceAnalyze(85-110)Voice(32-58)app/memory_monitor.py (1)
log_memory_info(59-77)
app/services/fcm_service.py (2)
app/repositories/fcm_repo.py (2)
get_active_fcm_tokens_by_user(71-76)get_active_fcm_tokens_by_username(79-84)app/models.py (1)
FcmToken(197-219)
app/routers/composite_router.py (3)
app/database.py (1)
get_db(38-44)app/services/composite_service.py (2)
CompositeService(8-34)compute_and_save_composite(12-34)app/models.py (1)
VoiceComposite(146-180)
app/main.py (10)
app/s3_service.py (2)
list_bucket_objects(35-42)list_bucket_objects_with_urls(60-82)app/database.py (1)
get_db(38-44)app/models.py (3)
Question(113-128)VoiceComposite(146-180)Voice(32-58)app/dto.py (6)
UserInfoResponse(38-42)CareInfoResponse(45-49)FcmTokenRegisterRequest(53-57)FcmTokenRegisterResponse(60-64)FcmTokenDeactivateResponse(67-70)UserVoiceDetailResponse(126-132)app/care_service.py (3)
CareService(7-118)get_emotion_monthly_frequency(12-44)get_emotion_weekly_summary(46-118)app/exceptions.py (6)
AppException(6-10)ValidationException(13-16)RuntimeException(19-22)DatabaseException(25-28)OutOfMemoryException(31-34)InternalServerException(37-40)app/memory_monitor.py (2)
get_memory_info(9-47)log_memory_info(59-77)app/auth_service.py (3)
get_user_by_username(144-146)get_user_info(207-251)get_care_info(253-294)app/repositories/fcm_repo.py (3)
deactivate_fcm_tokens_by_user(46-53)register_fcm_token(7-43)deactivate_fcm_token_by_device(56-68)app/voice_service.py (4)
get_user_voice_list(363-432)get_user_voice_detail(451-487)get_user_emotion_monthly_frequency(623-647)get_user_emotion_weekly_summary(649-709)
🪛 Ruff (0.14.2)
app/repositories/job_repo.py
58-58: Do not catch blind exception: Exception
(BLE001)
61-61: Use explicit conversion flag
Replace with conversion flag
(RUF010)
123-123: Do not catch blind exception: Exception
(BLE001)
126-126: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
126-126: Use explicit conversion flag
Replace with conversion flag
(RUF010)
133-133: Consider moving this statement to an else block
(TRY300)
app/voice_service.py
46-46: Unpacked variable sr is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
63-63: Do not use bare except
(E722)
63-64: try-except-pass detected, consider logging the exception
(S110)
68-68: Do not use bare except
(E722)
68-69: try-except-pass detected, consider logging the exception
(S110)
160-160: Store a reference to the return value of asyncio.create_task
(RUF006)
161-161: Store a reference to the return value of asyncio.create_task
(RUF006)
163-167: Consider moving this statement to an else block
(TRY300)
168-168: Do not catch blind exception: Exception
(BLE001)
174-174: Use explicit conversion flag
Replace with conversion flag
(RUF010)
261-261: Undefined name k
(F821)
262-263: try-except-pass detected, consider logging the exception
(S110)
262-262: Do not catch blind exception: Exception
(BLE001)
601-601: Store a reference to the return value of asyncio.create_task
(RUF006)
602-602: Store a reference to the return value of asyncio.create_task
(RUF006)
620-620: Use explicit conversion flag
Replace with conversion flag
(RUF010)
631-631: Do not catch blind exception: Exception
(BLE001)
645-645: Consider moving this statement to an else block
(TRY300)
646-646: Do not catch blind exception: Exception
(BLE001)
647-647: Use explicit conversion flag
Replace with conversion flag
(RUF010)
657-657: Do not catch blind exception: Exception
(BLE001)
707-707: Consider moving this statement to an else block
(TRY300)
708-708: Do not catch blind exception: Exception
(BLE001)
709-709: Use explicit conversion flag
Replace with conversion flag
(RUF010)
app/services/fcm_service.py
30-33: Avoid specifying long messages outside the exception class
(TRY003)
157-157: Do not catch blind exception: Exception
(BLE001)
app/routers/composite_router.py
11-11: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
15-15: Do not catch blind exception: Exception
(BLE001)
16-16: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
16-16: Use explicit conversion flag
Replace with conversion flag
(RUF010)
20-20: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
app/main.py
46-46: Unused function argument: request
(ARG001)
65-65: Unused function argument: request
(ARG001)
86-86: Unused function argument: request
(ARG001)
99-99: Unused function argument: request
(ARG001)
106-106: Use explicit conversion flag
Replace with conversion flag
(RUF010)
112-112: Unused function argument: request
(ARG001)
119-119: Use explicit conversion flag
Replace with conversion flag
(RUF010)
125-125: Unused function argument: request
(ARG001)
132-132: Use explicit conversion flag
Replace with conversion flag
(RUF010)
138-138: Unused function argument: request
(ARG001)
236-236: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
258-258: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
277-277: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
299-299: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
312-312: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
318-318: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
333-333: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
345-345: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
362-362: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
371-371: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
384-384: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
411-411: Do not catch blind exception: Exception
(BLE001)
412-412: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
412-412: Use explicit conversion flag
Replace with conversion flag
(RUF010)
419-419: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
447-447: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
457-457: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
471-471: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
484-484: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
491-491: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
504-504: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
511-511: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
536-536: Value being cast to int is already an integer
Remove unnecessary int call
(RUF046)
683-683: Avoid specifying long messages outside the exception class
(TRY003)
| if status_code in (400, 401, 403, 404, 422): | ||
| status_code = 400 |
There was a problem hiding this comment.
HTTPException 상태코드를 400으로 덮어쓰면 REST 계약이 깨집니다.
401·403·404 같은 구분이 모두 400으로 변해 호출자가 인증/인가 실패와 존재하지 않는 자원을 식별할 수 없게 됩니다. 최소한 422만 400으로 매핑하도록 좁혀 주세요.
- if status_code in (400, 401, 403, 404, 422):
- status_code = 400
+ if status_code == 422:
+ status_code = 400📝 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.
| if status_code in (400, 401, 403, 404, 422): | |
| status_code = 400 | |
| if status_code == 422: | |
| status_code = 400 |
🤖 Prompt for AI Agents
In app/main.py around lines 51-52, the code currently maps multiple
HTTPException status codes (400, 401, 403, 404, 422) to 400 which breaks the
REST contract; change the logic to only remap 422 to 400 and leave 401, 403, 404
(and other non-422 codes) untouched so callers can distinguish
auth/permission/not-found errors; update the conditional to check specifically
for 422 and set status_code = 400 only in that case.
| if not row: | ||
| raise HTTPException(status_code=404, detail="not found") | ||
| return { | ||
| "voice_id": voice_id, |
There was a problem hiding this comment.
CARE 연결 사용자 조회 조건이 잘못되어 알림이 항상 건너뜁니다.
User.connecting_user_code 칼럼에는 보호자가 입력한 사용자 코드가 저장되는데, 현재 username과 비교해서 매칭이 절대 성립하지 않습니다. 그 결과 FCM 알림이 전혀 발송되지 않습니다. user_code와 비교하도록 수정해 주세요.
- User.connecting_user_code == user.username
+ User.connecting_user_code == user.user_codeCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/routers/composite_router.py around lines 22 to 25, the lookup uses
User.connecting_user_code compared against the requesting user's username so the
condition never matches; change the query to compare User.connecting_user_code
to the provided user_code (or the variable holding the guardian-entered code)
instead of username, so the row is found and FCM notifications are sent; update
any variable name usage to reflect user_code and keep the 404 behavior if no row
is returned.
🔎 Description
🔗 Related Issue
🏷️ What type of PR is this?
📋 Changes Made
🧪 Testing
Summary by CodeRabbit
새로운 기능
개선 사항
기타