feat(cafe24): 모바일 스와이프(product-swiper.js) 편집 화면 추가 (FTP)

- product-swiper.js는 상품이 아니라 카페24 "디자인 보관함" 스킨 파일이라
  Admin API(OAuth)로는 접근 불가 — 실물 확인(스크린샷) 결과 디자인 보관함
  FTP 계정(호스트/포트/ID/PW, OAuth와 별개)으로만 읽기/쓰기 가능.
  app/integrations/cafe24/design_ftp.py 를 표준 ftplib 로 새로 추가.
- 상단 탭에 "모바일 스와이프" 버튼 추가. 편집 화면(swiper.html)은 상세페이지
  편집기(_editor.html)와 완전히 같은 문법강조·색상·단축키 JS를 그대로 옮겨
  씀(요청사항) — 상품 전용 UI(목록·진열/판매·예약)는 제외.
- 적용 순서도 상세페이지와 동일한 원칙: FTP에서 현재값 재조회 → BACKUP →
  지문 대조(충돌 거부) → FTP 쓰기 → MANUAL 버전 + 감사로그(apply_swiper).
  textarea의 CRLF는 적용 전 LF로 정규화(안 하면 매번 "변경됨"으로 오판).
- 버전 이력은 cafe24_product_revisions(product_no NOT NULL)에 넣을 수 없어
  새 테이블 cafe24_swiper_revisions 추가(scripts/sql/cafe24_db_003_*.sql,
  서버에서 별도 실행 필요).
- 신규 env: CAFE24_FTP_HOST(미설정 시 {mall_id}.ftp.cafe24.com)/PORT/USER/
  PASSWORD, CAFE24_SWIPER_FTP_PATH. .env.example·문서 갱신.
- 유닛테스트 4건 추가(FTP config 기본값, 가짜 FTP로 읽기/쓰기 왕복·오류 처리).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 14:33:35 +09:00
parent 20f3f0f7df
commit 93037411fb
13 changed files with 894 additions and 5 deletions
+52 -3
View File
@@ -15,18 +15,20 @@
```
app/integrations/cafe24/ ← 공통 (상품관리 + 향후 주문관리 공유)
├─ config.py 환경변수 → Cafe24Config (하드코딩 금지)
├─ config.py 환경변수 → Cafe24Config / Cafe24FtpConfig (하드코딩 금지)
├─ crypto.py 토큰 Fernet 암복호화
├─ oauth.py 인증 URL / code→token / refresh
├─ tokens.py TokenService — 저장·만료판정·자동갱신(행 잠금)
├─ client.py Cafe24Client — 전송·재시도·401/429/5xx·호출간격·API 로그
├─ products.py 상품 엔드포인트 래퍼 (향후 orders.py 를 형제로 추가)
├─ design_ftp.py 디자인 보관함 FTP 읽기/쓰기 (product-swiper.js 등 스킨 파일)
└─ errors.py 공통 예외
app/modules/cafe24/ ← 상품관리 모듈
├─ router.py 루트 라우터(prefix=/cafe24) + 서브 라우터 결합
├─ routes_products.py 2분할 화면 · 편집기 조각 · 적용(쓰기)
├─ routes_schedules.py 예약 등록·목록·취소
├─ routes_swiper.py 모바일 스와이프(product-swiper.js) 편집·적용(FTP)
├─ worker.py 예약 실행기 (compose 서비스 dbx-cafe24-worker)
├─ routes_system.py 연결(OAuth)·상태·API 로그·작업 로그
├─ common.py 가드/컨텍스트 헬퍼 (순환 import 방지로 분리)
@@ -34,7 +36,7 @@ app/modules/cafe24/ ← 상품관리 모듈
├─ store.py 순수 로직 — 상수·상태전이·재시도 규칙·검증
├─ tests/ DB/네트워크 없는 유닛테스트
└─ templates/cafe24/ _nav.html · products.html(2분할) ·
_editor.html(오른쪽 조각) ·
_editor.html(오른쪽 조각) · swiper.html ·
schedules.html · system.html
```
@@ -65,10 +67,43 @@ 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/health` | 포털 카드 상태 점 | 없음 |
---
### 2-0. 모바일 스와이프(product-swiper.js) — 상품 API 와 완전히 다른 경로
`product-swiper.js` 는 상품이 아니라 카페24 **"디자인 보관함"** 에 올라간 스킨
파일이다(모바일 스킨 `mobile11` 아래 `/product-swiper/product-swiper.js`).
Admin API(OAuth)로는 스킨 파일을 읽거나 쓸 방법이 없다(`config.py` 상단 주석의
확인 내용 — themes/themes-pages 어디에도 파일 내용이 없다). 카페24가 스킨
파일에 제공하는 유일한 프로그램적 접근은 **디자인 보관함 FTP 계정**이며, OAuth
와는 완전히 별개의 인증(호스트/포트/아이디/비밀번호)이다.
- 실물 확인(카페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는 없다 — 편집·적용·버전 이력만 있다.
- 적용 순서도 상세페이지와 같은 원칙: FTP 에서 현재 내용을 다시 읽는다(로컬 값을
현재값으로 가정하지 않는다) → **BACKUP** 버전 저장 → 지문 대조(편집 중 다른
경로로 파일이 바뀌었으면 거부) → FTP 로 쓴다 → **MANUAL** 버전 + 감사로그
(`apply_swiper`, `product_no` 는 NULL).
- 버전 이력은 `cafe24_swiper_revisions` 전용 테이블에 쌓인다
(`cafe24_product_revisions.product_no` 가 NOT NULL 이라 재사용 불가 —
`scripts/sql/cafe24_db_003_swiper_revisions.sql`).
- 브라우저 `<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`).
---
### 2-1. 상품관리 화면 구성 (2분할)
```
@@ -360,7 +395,14 @@ CAFE24_CLIENT_SECRET=...
CAFE24_REDIRECT_URI=https://dbx.no1king.freeddns.org/cafe24/oauth/callback
CAFE24_API_VERSION=2026-03-01
CAFE24_TOKEN_SECRET=<openssl rand -hex 32>
CAFE24_SHOP_URL=https://miras.co.kr # 선택 — 다이렉트 주소용 커스텀 도메인
CAFE24_SHOP_URL=https://www.miras.co.kr # 선택 — 다이렉트 주소용 커스텀 도메인
# 모바일 스와이프(product-swiper.js) — 디자인 보관함 FTP (OAuth 와 별개 계정)
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
```
### 5-4. 재기동 + 권한 부여
@@ -375,6 +417,12 @@ cd /opt/www/main && docker compose up -d --build web cafe24-worker
docker exec -i postgres-db psql -U postgres -d cafe24_db < scripts/sql/cafe24_db_002_schedule_flags.sql
```
모바일 스와이프(버전 이력) 기능을 쓰려면 마이그레이션 003 도 적용해야 한다.
```bash
docker exec -i postgres-db psql -U postgres -d cafe24_db < scripts/sql/cafe24_db_003_swiper_revisions.sql
```
worker 가 도는지 확인:
```bash
@@ -407,6 +455,7 @@ DB·네트워크 없이 암호화 왕복, 토큰 만료/자동갱신, 상태 노
| 5 | 예약 DB · Worker(compose 서비스) · 예약관리 화면 | ✅ 완료 |
| 6 | 자동 종료/복원 · 롤백 | ✖ 되돌리기는 쓰지 않기로 결정. 버전 선택 복원만 남음 |
| 7 | 일괄 수정 · 일괄 예약 · Rate limit 제어 | ✖ 일괄수정은 사용하지 않기로 제거. 필요해지면 다시 논의 |
| 8 | 모바일 스와이프(product-swiper.js) 편집 — 디자인 보관함 FTP | ✅ 완료. 예약 적용은 없음(파일 1개, 상품 전용 개념이라 범위 밖) |
Phase 5 의 worker 는 `app/modules/cafe24/worker.py` 에 둔다 — `Dockerfile` 이
`COPY app/ ./app/` 만 하므로 `scripts/` 에 두면 이미지에 포함되지 않는다.