20260804_#115_Blog_Ingest_API에_이미지_파일_업로드_엔드포인트_추가 : feat : 블로그 ingest API에 이미지/파일 업로드 엔드포인트 추가 https://github.com/Chuseok22/chuseok22-home-server/issues/115 - #116
Hidden character warning
Conversation
Walkthrough블로그 이미지 업로드 API를 추가했습니다. 요청은 1~10개 파일을 multipart 형식으로 받고, 전용 API 키와 throttling을 적용합니다. 파일별 성공·실패 결과와 URL, Markdown, 오류 메시지를 반환합니다. 라우팅, 설정, 직렬화기 및 테스트를 함께 변경했습니다. Changes블로그 이미지 업로드
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant BlogIngestImageUploadView
participant BlogIngestImageUploadSerializer
participant save_uploaded_media
Client->>BlogIngestImageUploadView: multipart/form-data와 API 키 전송
BlogIngestImageUploadView->>BlogIngestImageUploadSerializer: files 1~10개 검증
BlogIngestImageUploadView->>save_uploaded_media: 각 파일 저장
save_uploaded_media-->>BlogIngestImageUploadView: 성공 또는 실패 결과 반환
BlogIngestImageUploadView-->>Client: results 배열 반환
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/blog/views.py`:
- Around line 143-145: Update _save_image to catch OSError and ValueError raised
during image.save encoding, including malformed GIF/WebP inputs. Convert those
exceptions into MediaUploadResult(success=False, error_message=...) so the
per-file upload comprehension continues and the overall response does not become
a 500.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: d68914be-c6e6-4b45-a8ee-c4b8e618473e
📒 Files selected for processing (6)
apps/blog/serializers.pyapps/blog/tests/test_serializers.pyapps/blog/tests/test_views.pyapps/blog/urls.pyapps/blog/views.pyconfig/settings/base.py
| results = [ | ||
| _to_upload_result(uploaded_file, save_uploaded_media(uploaded_file)) | ||
| for uploaded_file in serializer.validated_data['files'] |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline apps/blog/services/media_storage.py --items all
rg -n -P -C 12 'def (save_uploaded_media|_save_image|_store)\b|except\b|Image\.(open|save)\b' \
apps/blog/services/media_storage.pyRepository: Chuseok22/chuseok22-home-server
Length of output: 6495
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== media_storage.py relevant lines =="
sed -n '42,86p' apps/blog/services/media_storage.py
echo
echo "== MediaUploadResult uses =="
rg -n 'save_uploaded_media\(|MediaUploadResult|results = \[' apps -g '*.py'
echo
echo "== views.py relevant lines =="
sed -n '130,165p' apps/blog/views.pyRepository: Chuseok22/chuseok22-home-server
Length of output: 5950
🌐 Web query:
Pillow Image save can raise exceptions malformed or corrupt image documentation
💡 Result:
In Pillow, exceptions related to malformed or corrupt images typically occur because the library uses lazy loading, where metadata is read upon opening but the actual image data is only processed (and validated) when you perform an operation like load() or save() [1][2]. Common exceptions and behaviors include: OSError (e.g., "broken data stream", "image file is truncated"): This is the most common error when Pillow encounters malformed or incomplete data during decoding [3][4]. Because of lazy loading, if you do not explicitly call load() or access image data after Image.open(), such errors may be deferred and only raised later during a save() operation [1]. Handling Truncated Images: If you need to process images that are partially corrupt or truncated, you can set ImageFile.LOAD_TRUNCATED_IMAGES = True [3][5]. This allows Pillow to attempt to parse and load as much data as possible instead of raising an error [4]. Integrity Checking: The Image.verify() method is available to check for file integrity without decoding the entire image [5]. However, be aware that its effectiveness varies by format; for example, it is primarily implemented for PNG files to verify CRC checksums and may not catch all types of corruption in other formats [4]. Encoder and Memory Errors: During save(), you may encounter OSError (e.g., "encoder error -2"), MemoryError, or ValueError due to internal buffer limitations, unsupported image modes, or invalid data structures (e.g., zero-dimension images, invalid palette sizes) [1][6][7][8]. If you encounter persistent encoder errors during save operations—often with progressive or optimized JPEGs—adjusting ImageFile.MAXBLOCK can sometimes serve as a workaround by increasing the internal buffer size [6][9]. To robustly handle potential issues, it is recommended to wrap image loading and processing operations in a try-except block and explicitly call img.load() if you need to validate the integrity of the image data immediately upon opening [4][1].
Citations:
- 1: MemoryError when handling malformed data python-pillow/Pillow#4250
- 2: https://pillow.readthedocs.io/en/stable/reference/Image.html
- 3:
Image.verifyfails to detect corrupted file python-pillow/Pillow#6342 - 4: How to detect if my image is truncated? python-pillow/Pillow#3012
- 5: https://pillow.readthedocs.io/en/stable/reference/ImageFile.html
- 6: Saving a jpeg throws an "encoder error -2 when writing image file" for some images python-pillow/Pillow#5448
- 7: Pillow 11.0.0 throws ValueError when saving GIF python-pillow/Pillow#8493
- 8: Fix unexpected error when saving zero dimension images python-pillow/Pillow#9391
- 9: save to bytes and got this error: io.UnsupportedOperation: fileno python-pillow/Pillow#8902
_save_image()의 Pillow 인코딩 실패를 실패 결과로 변환하십시오.
image.save(buffer, ...)는 손상/잘린 GIF/WebP 입력 등에서 OSError/ValueError를 발생할 수 있습니다. 현재는 image.load()만 처리하므로 이 경로가 예외를 전달하면 파일별 컴프리헨션이 실패하고 전체 업로드 응답이 500이 됩니다. 해당 예외를 MediaUploadResult(success=False, error_message=...)로 변환하십시오.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blog/views.py` around lines 143 - 145, Update _save_image to catch
OSError and ValueError raised during image.save encoding, including malformed
GIF/WebP inputs. Convert those exceptions into MediaUploadResult(success=False,
error_message=...) so the per-file upload comprehension continues and the
overall response does not become a 500.
✨ 변경 사항
POST /api/v1/blog/ingest/images/엔드포인트 추가 — 외부(주로 Claude Codeblog-post/retrospective스킬)가 블로그 글 본문에 삽입할 이미지/동영상/PDF를 파일 1~10개까지 한 번에 업로드apps/blog/services/media_storage.py의save_uploaded_media()(webp 변환, 50MB 상한 등)와HasBlogIngestKey인증을 그대로 재사용 — 신규 검증 로직 없음{"results": [{filename, success, url, markdown, error_message}, ...]}). 요청 형식(파일 1~10개)이 유효하면 개별 파일 성공/실패와 무관하게 항상 200을 반환하고, 파일 누락/10개 초과만 400blog_ingest_upload(100/day)를 기존blog_ingest(30/day)와 분리 추가drf-spectacular@extend_schema문서화(BlogIngestImageUploadResponseSerializer가 실제 응답 구조({"results": [...]})와 일치하도록 래퍼 시리얼라이저로 문서화)✅ 테스트
apps/blog/tests/test_serializers.py,apps/blog/tests/test_views.py에 신규 케이스 10개 추가(성공/다중 파일 순서 보존/부분 실패/파일 없음 400/10개 초과 400/잘못된 키 403). 전체 스위트 645개 통과,python manage.py check/spectacular --validate확인 완료(기존health_check예외 외 신규 오류 없음)참고
docs/는.gitignore대상이라 이 PR에는 포함되지 않음):docs/superpowers/plans/2026-08-04-blog-ingest-image-upload.mdblog-post/retrospectiveClaude Code 스킬 수정은 별도 후속 작업으로 진행 예정(이 저장소 밖에서 관리되는 스킬)Closes #115
Summary by CodeRabbit