Skip to content

20260804_#115_Blog_Ingest_API에_이미지_파일_업로드_엔드포인트_추가 : feat : 블로그 ingest API에 이미지/파일 업로드 엔드포인트 추가 https://github.com/Chuseok22/chuseok22-home-server/issues/115 - #116

Merged
Chuseok22 merged 2 commits into
mainfrom
20260804_#115_Blog_Ingest_API에_이미지_파일_업로드_엔드포인트_추가
Aug 4, 2026

Hidden character warning

The head ref may contain hidden characters: "20260804_#115_Blog_Ingest_API\uc5d0_\uc774\ubbf8\uc9c0_\ud30c\uc77c_\uc5c5\ub85c\ub4dc_\uc5d4\ub4dc\ud3ec\uc778\ud2b8_\ucd94\uac00"

Conversation

@Chuseok22

@Chuseok22 Chuseok22 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

✨ 변경 사항


  • POST /api/v1/blog/ingest/images/ 엔드포인트 추가 — 외부(주로 Claude Code blog-post/retrospective 스킬)가 블로그 글 본문에 삽입할 이미지/동영상/PDF를 파일 1~10개까지 한 번에 업로드
  • 기존 apps/blog/services/media_storage.pysave_uploaded_media()(webp 변환, 50MB 상한 등)와 HasBlogIngestKey 인증을 그대로 재사용 — 신규 검증 로직 없음
  • 파일별 부분 성공/실패를 배열로 반환({"results": [{filename, success, url, markdown, error_message}, ...]}). 요청 형식(파일 1~10개)이 유효하면 개별 파일 성공/실패와 무관하게 항상 200을 반환하고, 파일 누락/10개 초과만 400
  • 신규 스로틀 스코프 blog_ingest_upload(100/day)를 기존 blog_ingest(30/day)와 분리 추가
  • drf-spectacular @extend_schema 문서화(BlogIngestImageUploadResponseSerializer가 실제 응답 구조({"results": [...]})와 일치하도록 래퍼 시리얼라이저로 문서화)

✅ 테스트


  • 수동 테스트 완료 (개발 서버 기동 후 curl 스모크 테스트는 미실행)
  • 테스트 코드 완료 — 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.md
  • 이 엔드포인트를 실제로 사용하는 blog-post/retrospective Claude Code 스킬 수정은 별도 후속 작업으로 진행 예정(이 저장소 밖에서 관리되는 스킬)

Closes #115

Summary by CodeRabbit

  • 새 기능
    • 여러 이미지를 한 번에 업로드할 수 있는 이미지 업로드 API를 추가했습니다.
    • 한 번에 1~10개의 파일을 업로드할 수 있으며, 파일별 성공 여부와 변환된 WebP URL, Markdown 링크, 오류 메시지를 제공합니다.
    • 일부 파일이 실패해도 성공한 파일의 결과를 함께 반환합니다.
    • 전용 인증과 일일 요청 제한을 적용했습니다.
  • 유효성 검사
    • 파일 누락, 10개 초과 업로드, 잘못된 인증 키를 명확한 오류로 처리합니다.

@Chuseok22 Chuseok22 added the enhancement 기능 개선/향상 (Enhancement) label Aug 4, 2026
@Chuseok22 Chuseok22 self-assigned this Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

블로그 이미지 업로드 API를 추가했습니다. 요청은 1~10개 파일을 multipart 형식으로 받고, 전용 API 키와 throttling을 적용합니다. 파일별 성공·실패 결과와 URL, Markdown, 오류 메시지를 반환합니다. 라우팅, 설정, 직렬화기 및 테스트를 함께 변경했습니다.

Changes

블로그 이미지 업로드

Layer / File(s) Summary
업로드 계약과 결과 스키마
apps/blog/serializers.py, apps/blog/tests/test_serializers.py
업로드 파일을 1~10개로 검증합니다. 파일별 결과와 results 응답 구조를 정의합니다. 허용 수량과 오류 입력을 테스트합니다.
업로드 엔드포인트 처리와 연결
apps/blog/views.py, apps/blog/urls.py, config/settings/base.py
전용 API 키, multipart 파싱, blog_ingest_upload throttling을 적용합니다. save_uploaded_media로 파일을 저장하고 /ingest/images/ 경로를 등록합니다.
업로드 API 검증 테스트
apps/blog/tests/test_views.py
단일·다중 업로드, 파일 순서, 부분 실패, WebP URL과 Markdown, 파일 수 오류 및 잘못된 API 키를 검증합니다.

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 배열 반환
Loading

Possibly related PRs

Suggested labels: feat

Poem

토끼가 파일을 한 장씩 담고
열 장까지 귀를 세었네.
성공한 그림은 WebP로 뛰고
Markdown 링크가 따라가네.
실패한 파일도 결과에 남아
API 응답이 또렷해졌네. 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive 엔드포인트, 인증, 결과 반환, 저장 처리는 확인되지만 파일 크기·형식 제한과 문서 갱신은 요약만으로 확인할 수 없습니다. 파일 크기·형식 검증과 Blog Ingest OpenAPI 및 관련 문서 갱신 구현을 확인하거나 근거를 추가하십시오.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 Blog Ingest 이미지·파일 업로드 엔드포인트 추가라는 주요 변경을 명확히 설명합니다.
Out of Scope Changes check ✅ Passed 변경 사항은 업로드 엔드포인트, 시리얼라이저, 라우팅, 스로틀 설정 및 관련 테스트로 연결 이슈 범위에 포함됩니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 20260804_#115_Blog_Ingest_API에_이미지_파일_업로드_엔드포인트_추가

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

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2698be5 and 1c38196.

📒 Files selected for processing (6)
  • apps/blog/serializers.py
  • apps/blog/tests/test_serializers.py
  • apps/blog/tests/test_views.py
  • apps/blog/urls.py
  • apps/blog/views.py
  • config/settings/base.py

Comment thread apps/blog/views.py
Comment on lines +143 to +145
results = [
_to_upload_result(uploaded_file, save_uploaded_media(uploaded_file))
for uploaded_file in serializer.validated_data['files']

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 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.py

Repository: 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.py

Repository: 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:


_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.

@Chuseok22
Chuseok22 merged commit 90d8390 into main Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement 기능 개선/향상 (Enhancement)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

⚙️[기능추가][블로그] Blog Ingest API에 이미지/파일 업로드 엔드포인트 추가

1 participant