25ed369583
사용자 결정에 따라 없던 기능으로 되돌린다. 카페24 API 로 스킨 detail.html 의
상품번호 목록을 고칠 수 없다는 것이 확인된 뒤, 대안(상세설명 CSS 주입 / 스크립트
태그)을 모두 채택하지 않기로 정리했다.
제거 대상:
store.py HIDE_PROMO_* 상수, has_hidden_promo, set_promo_hidden
routes_products 편집기 컨텍스트의 promo 상태, hide_promo 폼 필드,
적용 시 블록 주입, 분리 상품 모바일 블록 동기화, 감사로그 표기
_editor.html 「상단 공통 홍보 숨기기」 체크박스
cafe24.css .cf24-check-inline
tests promo 관련 4건
docs 2-3 절
적용 시 "변경 없음" 판정에 모바일 비교를 포함시킨 것은 남겼다. 공통 홍보와 무관하게
맞는 동작이다(PC 는 그대로여도 모바일이 PC 와 다르면 맞춰줘야 한다).
문서 정리도 함께 했다. 앞선 편집에서 3-1·2-2 절이 중복 삽입되고 절 순서가
뒤섞여 있던 것을 2 → 2-1 → 2-2 → 3 → 3-1 → 3-2 로 바로잡았다. 편집기 절에는
아직 이전 방식(크기 맞추기·sticky 줄번호)이 적혀 있었는데 실제 구현인 스크롤
동기화로 갱신하고, 크기 계산이 두 번 실패한 이유를 근거 수치와 함께 남겼다.
⚠️ 이미 이 기능으로 적용한 상품이 있다면 그 상품 상세설명에
<style id="cf24-hide-common-promo"> 블록이 남아 있다. 편집기에서 그 줄을 지우고
적용하면 된다.
검증: 유닛테스트 45개 통과, 라우트 10개 등록, 템플릿 컴파일 확인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
4.0 KiB
Python
97 lines
4.0 KiB
Python
"""카페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 등)을 읽거나 쓰는 엔드포인트는 없다.
|
|
#
|
|
# 즉 스킨에 박힌 값(예: 상단 공통 홍보를 제외할 상품번호 목록)을 API 로 고치는
|
|
# 방법은 없다. 스킨은 카페24 관리자에서 직접 관리해야 한다.
|
|
#
|
|
# 페이지에 코드를 주입하는 유일한 수단은 스크립트 태그(/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,
|
|
)
|