o2o-site-AEO/solution/backend/scripts/backfill_thumbnails.py
Mina Choi 11d30bb3d1 [chore] solution,admin,ontology: 코드 주석을 한 줄로 — 히스토리 주석 삭제
여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다.

- ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제
- 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다
- eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다

파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이).
site·frontend·admin tsc, site vitest 105 passed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:19 +09:00

94 lines
3.5 KiB
Python

"""썸네일이 없는 발행 사이트에 썸네일을 채운다."""
import argparse, asyncio, os, sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault("APP_ENV", "local")
from sqlalchemy import select # noqa: E402
from common.database.db_session_manager import DB_SESSION_MNG # noqa: E402
from common.database.model.models import places, site_versions, sites # noqa: E402
from common.enums import DBWRType, ErrorType, SiteStatus # noqa: E402
from crud.site_crud import SiteCRUD # noqa: E402
from services import site_payload, site_thumbnail # noqa: E402
_crud = SiteCRUD()
def _targets(only_missing: bool):
"""발행된 사이트 + 그 사이트의 현재 버전 스냅샷."""
stmt = (
select(sites, places, site_versions)
.join(places, places.place_id == sites.place_id)
.join(site_versions, site_versions.site_version_id == sites.current_version_id)
.where(
sites.deleted == False, # noqa: E712
sites.status == SiteStatus.PUBLISHED.value,
)
)
if only_missing:
stmt = stmt.where(sites.thumbnail_url.is_(None))
async def run_query(session):
return await DB_SESSION_MNG.execute(session, stmt)
return run_query
async def run(dry_run: bool, only_missing: bool) -> None:
# -dry-run 은 목록만 본다 — Azure 설정 없이도 대상이 맞는지 확인할 수 있어야 한다.
if not dry_run and not site_thumbnail.is_configured():
raise SystemExit(
"AZURE_STORAGE_CONNECTION_STRING 이 없습니다 — 업로드할 곳이 없어 아무것도 하지 않습니다."
)
err, rows = await DB_SESSION_MNG.execute_lambda(
sites.DBType(), DBWRType.DB_READ.value, _targets(only_missing)
)
if err != ErrorType.SUCCESS:
raise SystemExit(f"대상 조회 실패: {err}")
print(f"[thumb] 대상 {len(rows)}건 ({'없는 것만' if only_missing else '전부'})")
made = skipped = failed = 0
for site, place, version in rows:
slug = site_payload.publish_slug(place, site)
snapshot = version.snapshot or {}
if dry_run:
has_photo = bool(site_payload.primary_media(snapshot))
print(f" - {slug:<24} {place.name} {'' if has_photo else '(대표 사진 없음 — 건너뜀)'}")
continue
try:
url = await site_thumbnail.store(slug, snapshot)
except Exception as ex: # noqa: BLE001 — 한 건 실패가 나머지를 막지 않는다
print(f" ✗ {slug}: {type(ex).__name__}: {ex}")
failed += 1
continue
if not url:
skipped += 1
continue
await DB_SESSION_MNG.execute_lambda_claim(
sites.DBType(), lambda s, sid=site.site_id, u=url: _crud.update_site(s, sid, {"thumbnail_url": u})
)
print(f" ✓ {slug} → {url}")
made += 1
if not dry_run:
print(f"[thumb] 완료 — 만듦 {made} · 사진 없음 {skipped} · 실패 {failed}")
await DB_SESSION_MNG.dispose_all()
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", action="store_true", help="업로드 없이 대상만 출력")
parser.add_argument("--all", action="store_true", help="이미 썸네일이 있는 사이트도 다시 만든다")
args = parser.parse_args()
asyncio.run(run(args.dry_run, only_missing=not args.all))
if __name__ == "__main__":
main()