diff --git a/.env.example b/.env.example index ec50a64..e151cba 100644 --- a/.env.example +++ b/.env.example @@ -110,7 +110,7 @@ CUSTOMER_ORDER_LIST_URL=/orderlist/ # 카페24 재연결(재인증)이 필요하다. # CAFE24_TOKEN_SECRET= # -# ── 모바일 스와이프(product-swiper.js) 편집용 — 디자인 보관함 FTP ── +# ── 디자인 보관함 FTP 편집(모바일 스와이프 / PC·모바일 상품상세 템플릿) ── # Admin API(OAuth)로는 스킨 파일을 못 읽는다. 카페24 관리자 → 디자인 → # 웹FTP 화면에 표시된 값을 그대로 쓴다(SSL/TLS 미사용, 평문 FTP, 포트 21). # 호스트 미설정 시 CAFE24_MALL_ID 기준 기본값({mall_id}.ftp.cafe24.com)을 쓴다. @@ -118,8 +118,11 @@ CUSTOMER_ORDER_LIST_URL=/orderlist/ # CAFE24_FTP_PORT=21 # CAFE24_FTP_USER= # CAFE24_FTP_PASSWORD= -# 편집 대상 파일의 절대 경로(스킨 번호가 바뀌면 함께 바뀐다). +# 편집 대상 파일들의 절대 경로(스킨 번호가 바뀌면 함께 바뀐다). 각각 선택 — +# 미설정 시 아래 기본값(실물 확인된 미라스키친 mobile11/skin11 스킨)을 쓴다. # CAFE24_SWIPER_FTP_PATH=/sde_design/mobile11/product-swiper/product-swiper.js +# CAFE24_MOBILE_DETAIL_FTP_PATH=/sde_design/mobile11/product/detail.html +# CAFE24_PC_DETAIL_FTP_PATH=/sde_design/skin11/product/detail.html # ─── 상품 검색 (itemcode_db 읽기 전용) ─── # cupang 설정 화면에서 제품명을 itemcode_db 에서 검색해 등록한다(읽기만). diff --git a/app/integrations/cafe24/__init__.py b/app/integrations/cafe24/__init__.py index 6cc1cc3..6b9f457 100644 --- a/app/integrations/cafe24/__init__.py +++ b/app/integrations/cafe24/__init__.py @@ -29,11 +29,14 @@ from . import design_ftp, products from .client import Cafe24Client from .config import ( DEFAULT_SCOPES, + DESIGN_FILE_SPECS, DESIGN_SCOPES, ORDER_SCOPES, PRODUCT_SCOPES, Cafe24Config, Cafe24FtpConfig, + design_file_label, + design_file_path, load_config, load_ftp_config, ) @@ -56,6 +59,9 @@ __all__ = [ "Cafe24FtpConfig", "load_ftp_config", "design_ftp", + "DESIGN_FILE_SPECS", + "design_file_label", + "design_file_path", "TokenService", "TokenBundle", "load_config", diff --git a/app/integrations/cafe24/config.py b/app/integrations/cafe24/config.py index 679a6ef..efa98fb 100644 --- a/app/integrations/cafe24/config.py +++ b/app/integrations/cafe24/config.py @@ -118,7 +118,8 @@ def load_config(*, scopes: tuple[str, ...] = DEFAULT_SCOPES) -> Cafe24Config: # ════════════════════════════════════════════════════════════ -# 디자인 보관함 FTP — 모바일 스와이프(product-swiper.js) 편집용 +# 디자인 보관함 FTP — 상품 API 로 못 건드리는 스킨 파일 편집용 +# (모바일 스와이프 product-swiper.js, PC/모바일 상품상세 템플릿 detail.html 등) # ════════════════════════════════════════════════════════════ # Admin API(OAuth)로는 스킨 파일을 읽거나 쓸 수 없다(위 설명 참고). 카페24가 # 스킨 파일에 제공하는 유일한 프로그램적 접근은 "디자인 보관함" FTP 계정이다 @@ -133,12 +134,10 @@ class Cafe24FtpConfig: port: int user: str password: str - # 편집 대상 파일의 절대 경로(디자인 보관함 기준). 스킨 번호가 바뀌면 함께 바뀐다. - swiper_path: str @property def configured(self) -> bool: - return bool(self.host and self.user and self.password and self.swiper_path) + return bool(self.host and self.user and self.password) @property def missing(self) -> list[str]: @@ -146,7 +145,6 @@ class Cafe24FtpConfig: ("CAFE24_FTP_HOST", self.host), ("CAFE24_FTP_USER", self.user), ("CAFE24_FTP_PASSWORD", self.password), - ("CAFE24_SWIPER_FTP_PATH", self.swiper_path), ) return [name for name, value in pairs if not value] @@ -166,9 +164,35 @@ def load_ftp_config(*, mall_id: str = "") -> Cafe24FtpConfig: port=port, user=_env("CAFE24_FTP_USER"), password=_env("CAFE24_FTP_PASSWORD"), - # 실물 확인된 기본 경로(미라스키친 mobile11 스킨). 스킨을 바꾸면 - # CAFE24_SWIPER_FTP_PATH 로 덮어쓴다. - swiper_path=_env( - "CAFE24_SWIPER_FTP_PATH", "/sde_design/mobile11/product-swiper/product-swiper.js" - ), ) + + +# FTP 로 편집하는 디자인 파일들 — key: (표시 이름, 경로 환경변수 이름, 기본 경로). +# 기본 경로는 실물 확인된 값(미라스키친: 모바일 mobile11 / PC skin11 스킨)이다. +# 스킨을 바꾸면 각 CAFE24_*_FTP_PATH 로 덮어쓴다. +DESIGN_FILE_SPECS: dict[str, tuple[str, str, str]] = { + "swiper": ( + "모바일 스와이프", + "CAFE24_SWIPER_FTP_PATH", + "/sde_design/mobile11/product-swiper/product-swiper.js", + ), + "mobile_detail": ( + "모바일 상품상세", + "CAFE24_MOBILE_DETAIL_FTP_PATH", + "/sde_design/mobile11/product/detail.html", + ), + "pc_detail": ( + "PC 상품상세", + "CAFE24_PC_DETAIL_FTP_PATH", + "/sde_design/skin11/product/detail.html", + ), +} + + +def design_file_label(key: str) -> str: + return DESIGN_FILE_SPECS[key][0] + + +def design_file_path(key: str) -> str: + _, env_name, default = DESIGN_FILE_SPECS[key] + return _env(env_name, default) diff --git a/app/modules/cafe24/db.py b/app/modules/cafe24/db.py index e7d9530..bf3e580 100644 --- a/app/modules/cafe24/db.py +++ b/app/modules/cafe24/db.py @@ -320,14 +320,16 @@ class Cafe24Store: return self._serialize(row) # ════════════════════════════════════════════════════════════ - # 모바일 스와이프(product-swiper.js) 버전 (append-only) - # 상품이 아니라 디자인 보관함 FTP 파일이라 별도 테이블을 쓴다 - # (cafe24_product_revisions.product_no 는 NOT NULL). + # 디자인 보관함 FTP 파일 버전 (append-only) — 모바일 스와이프 + # (product-swiper.js), PC/모바일 상품상세 템플릿(detail.html) 등. + # 상품이 아니라 FTP 파일이라 별도 테이블을 쓴다 + # (cafe24_product_revisions.product_no 는 NOT NULL). file_key 로 파일을 + # 구분한다 — "swiper" / "mobile_detail" / "pc_detail" (config.py 의 + # DESIGN_FILE_SPECS 키와 일치해야 한다). # ════════════════════════════════════════════════════════════ - SWIPER_FILE_KEY = "product-swiper" - - def add_swiper_revision( - self, *, content: str, revision_type: str, memo: str = "", created_by: str = "" + def add_design_revision( + self, *, file_key: str, content: str, revision_type: str, + memo: str = "", created_by: str = "", ) -> int: with self._pool.connection() as conn: row = conn.execute( @@ -338,7 +340,7 @@ class Cafe24Store: RETURNING id """, ( - self.SWIPER_FILE_KEY, + file_key, content or "", store.normalize_revision_type(revision_type), (memo or "")[:500], @@ -347,7 +349,7 @@ class Cafe24Store: ).fetchone() return int(row["id"]) if row else 0 - def list_swiper_revisions(self, *, limit: int = 20) -> list[dict[str, Any]]: + def list_design_revisions(self, file_key: str, *, limit: int = 20) -> list[dict[str, Any]]: """버전 목록. content 는 커질 수 있어 길이만 계산해서 준다.""" with self._pool.connection() as conn: rows = conn.execute( @@ -359,7 +361,7 @@ class Cafe24Store: ORDER BY created_at DESC, id DESC LIMIT %s """, - (self.SWIPER_FILE_KEY, max(1, min(int(limit), 200))), + (file_key, max(1, min(int(limit), 200))), ).fetchall() return [self._serialize(r) for r in rows] diff --git a/app/modules/cafe24/router.py b/app/modules/cafe24/router.py index be2283b..6857135 100644 --- a/app/modules/cafe24/router.py +++ b/app/modules/cafe24/router.py @@ -12,9 +12,9 @@ routes_products 상품 목록/검색 · 상세설명 조회·편집·적용 routes_schedules 예약 등록·목록·취소 (실행은 worker.py) routes_system 연결(OAuth)·상태·API 로그·작업 로그 - routes_swiper 모바일 스와이프(product-swiper.js) 편집 — 카페24 디자인 - 보관함 FTP (Admin API 가 아니다. app/integrations/cafe24/ - design_ftp.py 참고) + routes_design_files 모바일 스와이프·PC/모바일 상품상세 템플릿 편집 — + 카페24 디자인 보관함 FTP (Admin API 가 아니다. + app/integrations/cafe24/design_ftp.py 참고) """ from __future__ import annotations @@ -23,9 +23,9 @@ import logging from fastapi import APIRouter +from .routes_design_files import design_files_router from .routes_products import products_router from .routes_schedules import schedules_router -from .routes_swiper import swiper_router from .routes_system import system_router logger = logging.getLogger("cafe24.router") @@ -34,7 +34,7 @@ router = APIRouter(prefix="/cafe24", tags=["cafe24"]) router.include_router(products_router) router.include_router(schedules_router) -router.include_router(swiper_router) +router.include_router(design_files_router) router.include_router(system_router) diff --git a/app/modules/cafe24/templates/cafe24/_nav.html b/app/modules/cafe24/templates/cafe24/_nav.html index 022c7b4..d078bfe 100644 --- a/app/modules/cafe24/templates/cafe24/_nav.html +++ b/app/modules/cafe24/templates/cafe24/_nav.html @@ -1,9 +1,13 @@ -{# 카페24 모듈 공용 상단 탭. active_tab: products | swiper | schedules | system #} +{# 카페24 모듈 공용 상단 탭. active_tab: products | design:pc_detail | design:mobile_detail | design:swiper | schedules | system #}
상품관리 - 모바일 스와이프 + PC 상품상세 + 모바일 상품상세 + 모바일 스와이프 예약관리 ` 는 줄바꿈을 CRLF 로 보낸다 — 적용 직전 LF 로 정규화한다 (안 하면 실수정 없이도 매번 파일 전체 줄바꿈이 바뀌어 지문 비교·"변경 없음" 판정이 어긋난다). - env: `CAFE24_FTP_HOST`(미설정 시 `{mall_id}.ftp.cafe24.com`), `CAFE24_FTP_PORT` - (기본 21), `CAFE24_FTP_USER`, `CAFE24_FTP_PASSWORD`, `CAFE24_SWIPER_FTP_PATH` - (기본 `/sde_design/mobile11/product-swiper/product-swiper.js`). + (기본 21), `CAFE24_FTP_USER`, `CAFE24_FTP_PASSWORD` — 세 파일이 계정을 공유한다. + 경로는 파일별로 `CAFE24_SWIPER_FTP_PATH` / `CAFE24_MOBILE_DETAIL_FTP_PATH` / + `CAFE24_PC_DETAIL_FTP_PATH` (각각 위 표의 기본값을 쓴다). --- @@ -397,12 +413,14 @@ CAFE24_API_VERSION=2026-03-01 CAFE24_TOKEN_SECRET= CAFE24_SHOP_URL=https://www.miras.co.kr # 선택 — 다이렉트 주소용 커스텀 도메인 -# 모바일 스와이프(product-swiper.js) — 디자인 보관함 FTP (OAuth 와 별개 계정) +# 디자인 보관함 FTP (OAuth 와 별개 계정) — 모바일 스와이프 / PC·모바일 상품상세 CAFE24_FTP_HOST=miraskitchen.ftp.cafe24.com # 선택 — 미설정 시 {mall_id}.ftp.cafe24.com CAFE24_FTP_PORT=21 CAFE24_FTP_USER=... CAFE24_FTP_PASSWORD=... CAFE24_SWIPER_FTP_PATH=/sde_design/mobile11/product-swiper/product-swiper.js +CAFE24_MOBILE_DETAIL_FTP_PATH=/sde_design/mobile11/product/detail.html +CAFE24_PC_DETAIL_FTP_PATH=/sde_design/skin11/product/detail.html ``` ### 5-4. 재기동 + 권한 부여 @@ -455,7 +473,7 @@ DB·네트워크 없이 암호화 왕복, 토큰 만료/자동갱신, 상태 노 | 5 | 예약 DB · Worker(compose 서비스) · 예약관리 화면 | ✅ 완료 | | 6 | 자동 종료/복원 · 롤백 | ✖ 되돌리기는 쓰지 않기로 결정. 버전 선택 복원만 남음 | | 7 | 일괄 수정 · 일괄 예약 · Rate limit 제어 | ✖ 일괄수정은 사용하지 않기로 제거. 필요해지면 다시 논의 | -| 8 | 모바일 스와이프(product-swiper.js) 편집 — 디자인 보관함 FTP | ✅ 완료. 예약 적용은 없음(파일 1개, 상품 전용 개념이라 범위 밖) | +| 8 | 디자인 보관함 파일 편집(모바일 스와이프 · PC/모바일 상품상세 템플릿) — FTP | ✅ 완료. 예약 적용은 없음(상품 전용 개념이라 범위 밖) | Phase 5 의 worker 는 `app/modules/cafe24/worker.py` 에 둔다 — `Dockerfile` 이 `COPY app/ ./app/` 만 하므로 `scripts/` 에 두면 이미지에 포함되지 않는다.