diff --git a/insta-lab/app/internal_router.py b/insta-lab/app/internal_router.py index f4e00ae..9888382 100644 --- a/insta-lab/app/internal_router.py +++ b/insta-lab/app/internal_router.py @@ -10,18 +10,50 @@ from __future__ import annotations import json import logging +import os from typing import Optional from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field -from . import db +from . import config, db from .auth import verify_internal_key logger = logging.getLogger(__name__) router = APIRouter() +def _register_rendered_assets(slate_id: int) -> int: + """워커가 저장한 10장 PNG를 card_assets로 등록. + + cutover(2026-05-19) 후 렌더는 Windows insta-render 워커가 NAS SMB 볼륨에 + 직접 쓰지만, NAS DB에 card_assets를 등록하는 단계가 누락됐었다. 이 함수가 + 그 갭을 메운다. 워커 출력 경로 후보를 순서대로 스캔해 실제 파일만 등록한다 + (경로 정합 가드: CARDS_DIR 하위 / INSTA_DATA_PATH 직하 둘 다 수용). + + 저장하는 file_path는 insta-lab 컨테이너 내부 절대경로 → + get_asset(FileResponse) / package(zip)가 그대로 읽는다. + """ + candidates = [ + os.path.join(config.CARDS_DIR, str(slate_id)), # /app/data/insta_cards/{id} + os.path.join(config.INSTA_DATA_PATH, str(slate_id)), # /app/data/{id} + ] + for base in candidates: + if not os.path.isdir(base): + continue + count = 0 + for page in range(1, 11): + fp = os.path.join(base, f"{page:02d}.png") + if os.path.exists(fp) and os.path.getsize(fp) > 0: + db.add_card_asset(slate_id, page, fp) + count += 1 + if count: + logger.info("card_assets 등록: slate=%s pages=%d dir=%s", slate_id, count, base) + return count + logger.warning("렌더 PNG를 찾지 못함: slate=%s (후보=%s)", slate_id, candidates) + return 0 + + class UpdatePayload(BaseModel): task_id: str status: str = Field(..., description="processing|succeeded|failed") @@ -56,12 +88,16 @@ def insta_update(payload: UpdatePayload): result_id=result_id, error=payload.error, ) - # succeeded 시 slate_status도 'rendered'로 갱신 (cutover 후 NAS가 처리) + # succeeded 시 slate_status도 'rendered'로 갱신 + card_assets 등록 (cutover 후 NAS가 처리) if payload.status == "succeeded" and result_id is not None: try: db.update_slate_status(result_id, "rendered") except Exception: logger.exception("update_slate_status %s 실패 (무시)", result_id) + try: + _register_rendered_assets(result_id) + except Exception: + logger.exception("card_assets 등록 %s 실패 (무시)", result_id) logger.info( "internal/insta/update task=%s status=%s progress=%d", payload.task_id, payload.status, payload.progress, diff --git a/insta-lab/tests/test_internal_router.py b/insta-lab/tests/test_internal_router.py index b7c5994..b693b30 100644 --- a/insta-lab/tests/test_internal_router.py +++ b/insta-lab/tests/test_internal_router.py @@ -78,3 +78,55 @@ def test_update_failed_records_error(client): task = db.get_task(tid) assert task["status"] == "failed" assert "Chromium" in (task.get("error") or "") + + +def test_succeeded_registers_card_assets(client, tmp_path, monkeypatch): + """succeeded 시 워커가 쓴 PNG들을 card_assets로 등록 (cutover 후 누락된 단계).""" + from app import config + + # FK 충족용 실제 슬레이트 + sid = db.add_card_slate({"keyword": "금리", "category": "economy"}) + # 워커가 PNG 10장을 쓴 디렉토리 시뮬 (CARDS_DIR/{sid}) + cards_root = tmp_path / "insta_cards" + sdir = cards_root / str(sid) + sdir.mkdir(parents=True) + for p in range(1, 11): + (sdir / f"{p:02d}.png").write_bytes(b"\x89PNG\r\n\x1a\n" + b"x" * 100) + monkeypatch.setattr(config, "CARDS_DIR", str(cards_root)) + monkeypatch.setattr(config, "INSTA_DATA_PATH", str(tmp_path)) + + tid = db.create_task("slate_render", {"slate_id": sid}) + r = client.post( + "/api/internal/insta/update", + headers={"X-Internal-Key": "test-secret"}, + json={ + "task_id": tid, + "status": "succeeded", + "progress": 100, + "result_path": f"/media/insta/{sid}/01.png", + }, + ) + assert r.status_code == 200 + assets = db.list_card_assets(sid) + assert len(assets) == 10 + assert assets[0]["page_index"] == 1 + assert assets[0]["file_path"].endswith("01.png") + assert db.get_card_slate(sid)["status"] == "rendered" + + +def test_succeeded_no_files_registers_nothing(client, tmp_path, monkeypatch): + """워커 출력이 없으면(파일 미존재) 잘못된 asset 등록 금지 — 200은 유지.""" + from app import config + + sid = db.add_card_slate({"keyword": "환율", "category": "economy"}) + monkeypatch.setattr(config, "CARDS_DIR", str(tmp_path / "insta_cards")) + monkeypatch.setattr(config, "INSTA_DATA_PATH", str(tmp_path)) + + tid = db.create_task("slate_render", {"slate_id": sid}) + r = client.post( + "/api/internal/insta/update", + headers={"X-Internal-Key": "test-secret"}, + json={"task_id": tid, "status": "succeeded", "progress": 100}, + ) + assert r.status_code == 200 + assert db.list_card_assets(sid) == []