Feat/prod server#123
Conversation
VECTOR_HNSW_M, EF_CONSTRUCTION, EF_SEARCH, DIST_METHOD 설정을 추가하고 VectorStoreService와 벡터 insert 스크립트가 동일한 hnsw_kwargs를 사용하도록 통일한다.
prod 환경에서 complaint-email·geo·*-pins API를 숨기고 contest/policy-admin은 노출하도록 ROUTER_REGISTRY를 조정한다. 환경별 라우터 집합 회귀 방지 테스트를 추가한다.
There was a problem hiding this comment.
Code Review
This pull request introduces CDN configuration options (cdn_enabled, cdn_base_url) to serve S3 assets via a CDN, and exposes PostgreSQL HNSW index parameters (m, ef_construction, ef_search, dist_method) to customize vector search indexing. It also adds test endpoints for S3 operations (upload, download, delete) and updates environment-specific router registries. The review feedback suggests validating the CDN base URL format, adding warning logs for CDN misconfigurations, properly encoding non-ASCII filenames in the Content-Disposition header for downloads, and merging custom HNSW parameters with default values to prevent missing configuration keys.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| @field_validator("cdn_base_url", mode="before") | ||
| @classmethod | ||
| def _empty_cdn_base_url_to_none(cls, value: object) -> object | None: | ||
| if isinstance(value, str) and value.strip() == "": | ||
| return None | ||
| return value |
There was a problem hiding this comment.
CDN_BASE_URL이 설정되었을 때, 올바른 URL 형식(예: http:// 또는 https://로 시작)인지 검증하는 로직을 추가하는 것이 안전합니다. 잘못된 형식으로 설정되면 서비스 전체에서 생성되는 퍼블릭 URL이 깨질 수 있습니다.
@field_validator("cdn_base_url", mode="before")
@classmethod
def _empty_cdn_base_url_to_none(cls, value: object) -> object | None:
if isinstance(value, str):
val = value.strip()
if val == "":
return None
if not val.startswith(("http://", "https://")):
raise ValueError("CDN_BASE_URL must start with http:// or https://")
return val
return value| cdn_base_url = (settings.cdn_base_url or "").strip() | ||
| if settings.cdn_enabled and cdn_base_url: | ||
| return f"{cdn_base_url.rstrip('/')}/{encoded_key}" |
There was a problem hiding this comment.
CDN_ENABLED가 True로 설정되어 있지만 CDN_BASE_URL이 비어 있는 경우, 의도치 않은 설정 오류일 가능성이 높습니다. 이 경우 S3 URL로 폴백하기 전에 경고 로그를 남겨두면 운영 환경에서 문제를 디버깅하는 데 큰 도움이 됩니다.
| cdn_base_url = (settings.cdn_base_url or "").strip() | |
| if settings.cdn_enabled and cdn_base_url: | |
| return f"{cdn_base_url.rstrip('/')}/{encoded_key}" | |
| cdn_base_url = (settings.cdn_base_url or "").strip() | |
| if settings.cdn_enabled: | |
| if cdn_base_url: | |
| return f"{cdn_base_url.rstrip('/')}/{encoded_key}" | |
| logger.warning("CDN_ENABLED is True but CDN_BASE_URL is not configured. Falling back to S3 URL.") |
| filename = Path(key).name or "download.bin" | ||
| return StreamingResponse( | ||
| io.BytesIO(data), | ||
| media_type=content_type or "application/octet-stream", | ||
| headers={"Content-Disposition": f'attachment; filename="{filename}"'}, | ||
| ) |
There was a problem hiding this comment.
다운로드할 파일명에 한글 등 비ASCII 문자가 포함되어 있을 경우, Content-Disposition 헤더의 filename="..." 포맷은 브라우저에 따라 파일명이 깨지거나 헤더 파싱 에러를 유발할 수 있습니다. RFC 5987 표준에 따라 filename*=utf-8''... 포맷을 사용하고 파일명을 퍼센트 인코딩(urllib.parse.quote)하는 것이 안전합니다.
| filename = Path(key).name or "download.bin" | |
| return StreamingResponse( | |
| io.BytesIO(data), | |
| media_type=content_type or "application/octet-stream", | |
| headers={"Content-Disposition": f'attachment; filename="{filename}"'}, | |
| ) | |
| from urllib.parse import quote | |
| filename = Path(key).name or "download.bin" | |
| encoded_filename = quote(filename) | |
| return StreamingResponse( | |
| io.BytesIO(data), | |
| media_type=content_type or "application/octet-stream", | |
| headers={"Content-Disposition": f"attachment; filename*=utf-8''{encoded_filename}"}, | |
| ) |
| self._hnsw_kwargs = hnsw_kwargs or dict(DEFAULT_HNSW_KWARGS) | ||
| logger.info("Vector HNSW config: %s", self._hnsw_kwargs) |
There was a problem hiding this comment.
hnsw_kwargs가 제공될 때 DEFAULT_HNSW_KWARGS를 완전히 대체(override)하게 됩니다. 만약 호출부에서 일부 키(예: {"hnsw_m": 24})만 전달할 경우, 나머지 필수 설정 키들이 누락되어 에러가 발생하거나 기본값이 적용되지 않을 수 있습니다. 기본값 딕셔너리와 입력받은 딕셔너리를 병합(merge)하도록 수정하는 것이 안전합니다.
| self._hnsw_kwargs = hnsw_kwargs or dict(DEFAULT_HNSW_KWARGS) | |
| logger.info("Vector HNSW config: %s", self._hnsw_kwargs) | |
| self._hnsw_kwargs = {**DEFAULT_HNSW_KWARGS, **(hnsw_kwargs or {})} | |
| logger.info("Vector HNSW config: %s", self._hnsw_kwargs) |
✨ 작업 개요
주요 변경 사항
1. Prod CI/CD 추가 (
prod_deploy.yml)prod브랜치(또는test/CICD) PR 머지 시 Elastic Beanstalk(issueissyu-ai-prod/issueissyu-ai-env)에 자동 배포PROD_ENV,AWS_PROD_ACCESS_KEY,AWS_PROD_SECRET_KEY시크릿 기반.env생성 후 배포 패키지 zip 업로드workflow_dispatch로 수동 배포도 지원2. CDN URL 지원
CDN_ENABLED,CDN_BASE_URL환경변수 추가S3Util공개 URL 생성 시 CDN 활성화 시 CDN base URL 우선 사용, 비활성/미설정 시 기존 S3 URL fallback3. HNSW 벡터 인덱스 파라미터 환경변수화
VECTOR_HNSW_M,VECTOR_HNSW_EF_CONSTRUCTION,VECTOR_HNSW_EF_SEARCH,VECTOR_HNSW_DIST_METHOD추가VectorStoreService,insert_chunks_to_vector스크립트가build_hnsw_kwargs()로 동일한 HNSW 설정 사용4. Prod Swagger 노출 라우터 정리
complaint-email,geo,festival-pins,contest-pins,policy-pins,test,vector-testusers,issue,complaint-apply,festival-admin,contest-admin,policy-admincontest-admin,policy-admin은 prod에서도 노출되도록disabled_envs조정5. 테스트 엔드포인트 보강 (
TestRoute)커밋 내역
074039e214e77253cd2446f5b155