fix(cafe24): 앞 커밋에서 누락된 디자인파일 일반화 변경분 포함

config.py(Cafe24FtpConfig 일반화 + DESIGN_FILE_SPECS)·db.py(add/list_design_
revision)·router.py(design_files_router 연결)·_nav.html(탭 3개)·테스트·
.env.example·문서가 앞 커밋(8c2a9c4)에 함께 올라갔어야 하는데 스테이징 누락.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 14:50:58 +09:00
parent 8c2a9c47e1
commit e14d303b82
8 changed files with 140 additions and 64 deletions
+36 -18
View File
@@ -21,14 +21,15 @@ app/integrations/cafe24/ ← 공통 (상품관리 + 향후 주문관리
├─ tokens.py TokenService — 저장·만료판정·자동갱신(행 잠금)
├─ client.py Cafe24Client — 전송·재시도·401/429/5xx·호출간격·API 로그
├─ products.py 상품 엔드포인트 래퍼 (향후 orders.py 를 형제로 추가)
├─ design_ftp.py 디자인 보관함 FTP 읽기/쓰기 (product-swiper.js 등 스킨 파일)
├─ design_ftp.py 디자인 보관함 FTP 읽기/쓰기 (product-swiper.js·detail.html 등 스킨 파일)
└─ errors.py 공통 예외
app/modules/cafe24/ ← 상품관리 모듈
├─ router.py 루트 라우터(prefix=/cafe24) + 서브 라우터 결합
├─ routes_products.py 2분할 화면 · 편집기 조각 · 적용(쓰기)
├─ routes_schedules.py 예약 등록·목록·취소
├─ routes_swiper.py 모바일 스와이프(product-swiper.js) 편집·적용(FTP)
├─ routes_design_files.py 디자인 보관함 파일 편집·적용(FTP) — 모바일 스와이프·
│ PC/모바일 상품상세 템플릿(file_key 로 하나의 라우트 공유)
├─ worker.py 예약 실행기 (compose 서비스 dbx-cafe24-worker)
├─ routes_system.py 연결(OAuth)·상태·API 로그·작업 로그
├─ common.py 가드/컨텍스트 헬퍼 (순환 import 방지로 분리)
@@ -36,7 +37,7 @@ app/modules/cafe24/ ← 상품관리 모듈
├─ store.py 순수 로직 — 상수·상태전이·재시도 규칙·검증
├─ tests/ DB/네트워크 없는 유닛테스트
└─ templates/cafe24/ _nav.html · products.html(2분할) ·
_editor.html(오른쪽 조각) · swiper.html ·
_editor.html(오른쪽 조각) · design_editor.html ·
schedules.html · system.html
```
@@ -67,16 +68,27 @@ app/modules/cafe24/ ← 상품관리 모듈
| `GET /cafe24/system/oauth/start` | 카페24 인증 시작 | **admin** |
| `GET /cafe24/oauth/callback` | 카페24 콜백 (code→토큰) | **admin** |
| `POST /cafe24/system/oauth/disconnect` | 저장된 토큰 삭제 | **admin** |
| `GET /cafe24/swiper` | 모바일 스와이프(product-swiper.js) 편집 화면 | `cafe24` |
| `POST /cafe24/swiper/apply` | 편집한 JS 를 디자인 보관함(FTP)에 즉시 적용 | `cafe24` |
| `GET /cafe24/design/{file_key}` | 디자인 보관함 파일 편집 화면(`swiper`\|`mobile_detail`\|`pc_detail`) | `cafe24` |
| `POST /cafe24/design/{file_key}/apply` | 편집한 내용을 디자인 보관함(FTP)에 즉시 적용 | `cafe24` |
| `GET /cafe24/health` | 포털 카드 상태 점 | 없음 |
---
### 2-0. 모바일 스와이프(product-swiper.js) — 상품 API 와 완전히 다른 경로
### 2-0. 디자인 보관함 파일 편집 — 상품 API 와 완전히 다른 경로
세 파일이 이 방식이다 — 상품이 아니라 카페24 **"디자인 보관함"** 에 올라간
스킨 파일이라서다.
| `file_key` | 상단 탭 | 기본 경로 |
| --- | --- | --- |
| `swiper` | 모바일 스와이프 | `/sde_design/mobile11/product-swiper/product-swiper.js` |
| `mobile_detail` | 모바일 상품상세 | `/sde_design/mobile11/product/detail.html` |
| `pc_detail` | PC 상품상세 | `/sde_design/skin11/product/detail.html` |
`mobile_detail`/`pc_detail` 은 **스킨 템플릿**이다 — 상품마다 다른
`description`(상세페이지 API, 2-3 절)과 달리 전 상품이 공유하는 레이아웃이므로
잘못 고치면 모든 상품 페이지에 영향을 준다.
`product-swiper.js` 는 상품이 아니라 카페24 **"디자인 보관함"** 에 올라간 스킨
파일이다(모바일 스킨 `mobile11` 아래 `/product-swiper/product-swiper.js`).
Admin API(OAuth)로는 스킨 파일을 읽거나 쓸 방법이 없다(`config.py` 상단 주석의
확인 내용 — themes/themes-pages 어디에도 파일 내용이 없다). 카페24가 스킨
파일에 제공하는 유일한 프로그램적 접근은 **디자인 보관함 FTP 계정**이며, OAuth
@@ -85,22 +97,26 @@ Admin API(OAuth)로는 스킨 파일을 읽거나 쓸 방법이 없다(`config.p
- 실물 확인(카페24 관리자 → 디자인 → 웹FTP 화면): 호스트 `{mall_id}.ftp.cafe24.com`,
포트 21, **SSL/TLS 미사용(평문 FTP)**. `app/integrations/cafe24/design_ftp.py`
가 표준 라이브러리 `ftplib` 로 직접 붙는다(Cafe24Client/httpx 경로가 아니다).
- 화면(`swiper.html`)은 상세페이지 편집기(`_editor.html`)와 **완전히 같은**
문법강조·색상·단축키 JS 를 그대로 복사해 쓴다(요청사항). 다만 목록·진열/판매·
예약처럼 "상품" 전용 UI는 없다 — 편집·적용·버전 이력만 있다.
- 세 파일 모두 화면·로직이 완전히 같아 `routes_design_files.py` 하나가
`/cafe24/design/{file_key}` 로 공유한다(경로만 `file_key` 로 갈라짐).
화면(`design_editor.html`)은 상세페이지 편집기(`_editor.html`)와 **완전히
같은** 문법강조·색상·단축키 JS 를 그대로 복사해 쓴다(요청사항). 다만
목록·진열/판매·예약처럼 "상품" 전용 UI는 없다 — 편집·적용·버전 이력만 있다.
- 적용 순서도 상세페이지와 같은 원칙: FTP 에서 현재 내용을 다시 읽는다(로컬 값을
현재값으로 가정하지 않는다) → **BACKUP** 버전 저장 → 지문 대조(편집 중 다른
경로로 파일이 바뀌었으면 거부) → FTP 로 쓴다 → **MANUAL** 버전 + 감사로그
(`apply_swiper`, `product_no` 는 NULL).
- 버전 이력은 `cafe24_swiper_revisions` 전용 테이블에 쌓인다
(`apply_design:{file_key}`, `product_no` 는 NULL).
- 버전 이력은 `cafe24_swiper_revisions` 전용 테이블에 `file_key` 로 구분해 쌓인다
(`cafe24_product_revisions.product_no` 가 NOT NULL 이라 재사용 불가 —
`scripts/sql/cafe24_db_003_swiper_revisions.sql`).
`scripts/sql/cafe24_db_003_swiper_revisions.sql`. 테이블 이름은 처음 만들 때
기준인 스와이프에서 왔지만 `file_key` 컬럼으로 세 파일을 함께 담게 설계했다).
- 브라우저 `<textarea>` 는 줄바꿈을 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=<openssl rand -hex 32>
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/` 에 두면 이미지에 포함되지 않는다.