"""카페24 연동 설정 — 환경변수만 읽는다(하드코딩 금지). app/main.py 의 env() 헬퍼와 동일하게 os.getenv + strip 규칙을 쓴다. integrations 계층은 app.main 을 import 하지 않는다(순환 import 방지). """ from __future__ import annotations import os from dataclasses import dataclass # 상품관리에 필요한 최소 scope. 향후 주문관리는 ORDER_SCOPES 를 더한다. PRODUCT_SCOPES: tuple[str, ...] = ("mall.read_product", "mall.write_product") ORDER_SCOPES: tuple[str, ...] = ("mall.read_order", "mall.write_order") # 디자인(테마) — **요청하지 않는다.** 아래 확인 결과 때문이다. # # 운영몰에서 직접 호출해 확인한 사실(2026-08-14): # GET /admin/themes → 조회는 가능(디자인 권한 필요)하지만 테마 "목록"뿐이며 # 스킨 파일 내용은 응답에 없다. # GET /admin/themes/pages → No API found. (버전 2026-03-01 에 존재하지 않음) # 스킨 HTML 파일(product/detail.html 등)을 읽거나 쓰는 엔드포인트는 없다. # # 즉 스킨의 `const numbers = [...]`(상단 공통 홍보 제외 목록)을 API 로 고치는 방법은 # 없다. 상단 공통 홍보 숨김은 상품 상세설명 안 CSS 로 처리한다 # (app/modules/cafe24/store.py 의 set_promo_hidden). # # 페이지에 코드를 주입하는 유일한 수단은 스크립트 태그(/admin/scripttags)이며, # 디자인이 아니라 **mall.write_store(상점)** 권한이 필요하고 인라인 코드가 아닌 # 외부 HTTPS URL 만 받는다. 쓰려면 우리 서버에 공개 JS 엔드포인트가 필요하다. DESIGN_SCOPES: tuple[str, ...] = ("mall.read_design", "mall.write_design") STORE_SCOPES: tuple[str, ...] = ("mall.read_store", "mall.write_store") # 실제로 요청하는 scope 묶음. 개발자센터 앱에 등록된 권한과 어긋나면 인증이 # 거부되므로, 여기에 추가할 때는 앱 권한도 함께 확인해야 한다. # 쓰지 않는 권한은 요청하지 않는다 — 토큰이 유출돼도 피해 범위를 좁히기 위함. DEFAULT_SCOPES: tuple[str, ...] = PRODUCT_SCOPES DEFAULT_API_VERSION = "2026-03-01" def _env(name: str, default: str = "") -> str: value = os.getenv(name, "").strip() return value if value else default @dataclass(frozen=True) class Cafe24Config: mall_id: str client_id: str client_secret: str redirect_uri: str api_version: str token_secret: str scopes: tuple[str, ...] @property def configured(self) -> bool: """OAuth 를 시작할 수 있는 최소 조건.""" return bool(self.mall_id and self.client_id and self.client_secret and self.redirect_uri) @property def missing(self) -> list[str]: """설정 안내 화면에 표시할 미설정 환경변수 이름들.""" pairs = ( ("CAFE24_MALL_ID", self.mall_id), ("CAFE24_CLIENT_ID", self.client_id), ("CAFE24_CLIENT_SECRET", self.client_secret), ("CAFE24_REDIRECT_URI", self.redirect_uri), ("CAFE24_TOKEN_SECRET", self.token_secret), ) return [name for name, value in pairs if not value] @property def api_base(self) -> str: return f"https://{self.mall_id}.cafe24api.com/api/v2" @property def scope_param(self) -> str: return ",".join(self.scopes) def load_config(*, scopes: tuple[str, ...] = DEFAULT_SCOPES) -> Cafe24Config: """환경변수에서 설정을 읽는다. 값이 없어도 예외를 던지지 않는다. 미설정 판단은 호출부가 `configured` / `missing` 으로 한다 (앱 기동을 막지 않기 위해 — 다른 모듈과 동일한 정책). """ return Cafe24Config( mall_id=_env("CAFE24_MALL_ID"), client_id=_env("CAFE24_CLIENT_ID"), client_secret=_env("CAFE24_CLIENT_SECRET"), redirect_uri=_env("CAFE24_REDIRECT_URI"), api_version=_env("CAFE24_API_VERSION", DEFAULT_API_VERSION), token_secret=_env("CAFE24_TOKEN_SECRET"), scopes=scopes, )