Rankly API 변경 이력 (Changelog)
이 문서는 사람이 읽는 요약입니다. 스키마·요청·응답 필드의 기계-readable 1차 명세는 Swagger UI 및 OpenAPI JSON을 기준으로 합니다.
2026-09-01
관리자 행동 분석 세션 지표
GET /api/v1/admin/analytics/summary의 top_pages, top_exits와 GET /api/v1/admin/analytics/growth의 top_exit_pages에 unique_sessions를 추가합니다.
- 반복 이벤트 수보다 실제 세션 수를 우선해 정렬합니다.
- Breaking 아님. 기존 필드는 유지됩니다.
UserLog 민감정보 마스킹
- 신규 HTTP 감사 로그는 비밀번호·OTP·인증 헤더·쿠키·토큰·소셜 인증 query 값을 저장 전에
[REDACTED]로 치환합니다.
- 오류 코드·상태 코드·실행 시간 등 운영 진단 필드는 유지합니다.
- 기존 로그 데이터는 이번 변경에서 수정하지 않습니다.
2026-08-31
스토어 목록 순위 이력 HTTP 캐시
GET /api/v1/slots/all ETag에 최신 SlotRankLog.id를 넣습니다. 순위만 쌓이고 슬롯 updated_at이 그대로여도 304로 어제 이력을 붙잡지 않습니다.
Cache-Control은 private, no-cache, must-revalidate입니다.
- Breaking 아님. JSON 계약은 그대로입니다.
네이버 순위 프록시 기본값 Flashproxy
NAVER_PROXY_PROVIDER 기본값은 flash입니다. flashproxy 별칭도 동일합니다.
- Evomi·IPRoyal 은
NAVER_PROXY_PROVIDER=evomi / iproyal 로 명시할 때만 씁니다.
- Breaking 아님. 공개 API 계약은 그대로입니다.
2026-08-25
스토어 목록 순위 이력 부착·캐시
GET /api/v1/slots/all(및 레거시 GET /api/v1/accounts/{user_id}/slots/all)은 히스토리 부착에 실패해도 페이지 전체를 rank_history=[]로 비우지 않습니다. Slot.rank로 오늘 날짜를 위조하던 폴백도 제거했습니다.
page_size>=50이어도 요청한 history_page_size(최대 30)를 그대로 붙입니다.
GET /api/v1/slots/ranks 단일 조회 캐시 TTL은 1시간에서 5분입니다. 순위 로그 저장 시 해당 슬롯 캐시 버전을 올립니다. 기본 기간은 timezone.now().date()입니다 (USE_TZ=False에서 localdate() 실패 방지).
- Breaking 아님. 빈 이력이면 필드가 생략되거나
[]이며, 오늘 1칸 가짜 이력은 더 이상 내려가지 않습니다.
2026-08-24
네이버 쇼핑 순위 조회 — plus_slot HTTP/2·HTTP/1.1 하이브리드
- msearch 본경로를 plus_slot_BE(2026-08-24)와 맞춥니다. nlog(NNB)는 HTTP/2, 딥
/api/search/all 은 HTTP/1.1입니다.
- BUC 쿠키는 유지합니다. 프록시 auto 풀은 Evomi+Flash이며 DataImpulse/IPRoyal 은 기본에서 뺍니다.
- Breaking 아님. 공개·슬롯 API 계약은 그대로입니다.
2026-08-21
로그인 실시간 조회는 400위까지 정확 순위
POST /api/v1/slots/rank/check_public에 Bearer가 있으면 네이버 쇼핑 1~400위 정확한 순위를 반환합니다 (result_status=ranked).
- 비로그인은 기존처럼 40위까지 정확, 41~400은
ranked_band 구간입니다.
- Breaking 아님. 로그인 응답만 더 정확해집니다.
운영 테스트 계정은 무료 휴면 제외
test@naver.com, test3@naver.com, e2e.* 계정은 7일 미접속 휴면 배치 대상이 아닙니다.
- 슬롯 생성 한도 면제는 그대로 관리자만 해당합니다.
2026-08-20
비로그인 공개 조회 한도 5→2
- 비로그인 공개 실시간 조회는 IP당 일 2회입니다 (
PUBLIC_RANK_DAILY_LIMIT). 무료 로그인 5회·유료 40회는 그대로입니다.
GET /api/v1/accounts/public-config의 public_rank_anon_daily_limit이 2입니다.
- Breaking (게스트 한도): 비로그인 5→2. 이미 소진한 IP는 자정까지 추가 조회가 막힙니다.
무료 한도 축소·삭제 미복구·7일 휴면
- 로그인 무료 공개 실시간 조회는 일 5회입니다 (
AUTH_PUBLIC_RANK_DAILY_LIMIT). 비로그인 2회·유료 40회.
- 신규 가입은
무료 slot 플랜 base_slots=5입니다. 기존 기본 slot 플랜(10)은 덮어쓰지 않습니다. migration 0009_new_free_plan_five_slots.
- 순수 무료는
slots_created_total이 한도입니다. 삭제해도 자리가 돌아오지 않습니다. 유료·extra_slots 확장은 기존 활성 수 + 누적 ×4입니다.
- 무료 7일 미접속(last_login과 UserLog 중 더 최근) ACTIVE 슬롯은
dormant로 수집에서 빠지고, 로그인/토큰 발급·본인 슬롯 목록 조회 시 ACTIVE로 복귀합니다.
- Breaking (무료 한도): 로그인 무료 조회 15→5, 신규 가입 슬롯 10→5, 무료 삭제 후 재생성 불가. 기존 무료 구독 10칸은 유지됩니다.
GET/POST/PATCH /api/v1/slot-groups/, GET /api/v1/slot-groups/{id} 응답에 platform_name_en을 추가했습니다. 기존 platform_name은 그대로입니다.
- 그룹
slot_count와 상세 slot_ids는 Slot.group_id 기준입니다 (SlotGroupMember만 채워진 경우와 불일치하지 않음).
POST /api/v1/slot-groups/{id}/slots는 이동 0건이면 400입니다. 플랫폼 불일치는 PLATFORM_MISMATCH, 그 외(이미 같은 그룹 등)는 SLOT_NOT_MOVED입니다. 200/400 모두 moved, skipped_platform, skipped_already_in_group을 포함합니다.
- Breaking 아님. 필드 추가와 이동 실패 시 상태코드만 200→400입니다.
2026-08-13
유료 전환 상담 퍼널·Place 공유 URL 정합
GET /admin/analytics/journeys에 is_sales_candidate, inquiry_channel, inquiry_location,
inquiry_at 필드를 추가했습니다. 기존 필드는 유지되는 하위호환 변경입니다.
- 결제 요청 승인 시 최근 카카오/이메일 상담 클릭 source를 요청
meta.sales_source에 연결하고
subscription_payment_approved 분석 이벤트를 기록합니다.
- 슬롯 생성 시
naver.me Place 공유 URL을 최종 네이버 Place URL로 확장한 뒤 검증·저장합니다.
- 카카오 OAuth의 비정형·숫자 오류 응답과 동시 첫 로그인 email 충돌이 500으로 번지지 않도록
파싱 오류 또는 기존 카카오 계정 재사용으로 처리합니다.
2026-08-10
무료 플랜: 슬롯 재수집 일 2회 · /slots/{id}/run 쿼터 적용
POST /api/v1/slots/{slot_id}/run (FE 재수집 버튼)에 무료 플랜 슬롯당 하루 2회 한도를 적용합니다.
한도 초과 시 400 / error=MANUAL_RERANK_LIMIT.
- 성공·실패 응답에
used_today, daily_limit, remaining_today, unlimited를 포함합니다 (유료·면제는 unlimited=true).
get_free_plan_limits().daily_reranks_per_slot을 3→2로 정합. 자동 수집은 기존과 같이 10:00 / 17:00 KST.
- 무료 활성 슬롯 10개(
FREE_PLAN_BASE_SLOTS) 정책 카피·알림톡 fallback을 코드/문서에 재정렬했습니다.
2026-08-06
관리자 활동 로그: SlotRankLog 배치가 목록을 잠식하던 문제 수정
GET /admin/analytics/activity-logs는 AnalyticsEvent(로그인·슬롯등록·구독·웹 순위조회)만 반환합니다.
- 계정 자동/일상 순위 수집(
SlotRankLog → slot_rank_checked)은 하루 수천~수만 건이라
limit을 잠식하고 FE에서도 숨기므로 응답에 포함하지 않습니다.
- 실패 필터는
rank_check_activity의 status/outcome으로 판별합니다.
- 응답에
source(예: web_ui)를 포함합니다.
API 무응답 장애 대응: 순위 스케줄러 분리·공유 캐시·gthread
17:00 KST 전체 슬롯 순위 배치가 gunicorn 워커를 모두 점유해 API가 무응답이 된 장애의 후속 조치입니다.
GET /api/v1/health 응답에 cache 필드 추가
(healthy / degraded: local fallback / unavailable / unhealthy: ...).
- 슬롯 순위 스케줄러(
daily-rank-10/17, hourly-rank-guard)는 celery-worker에서만 실행합니다.
web·celery-beat·discord-bot은 DISABLE_SLOT_RANK_SCHEDULER=true.
- Django 기본 캐시를 Redis로 지정합니다. 기존에는
CACHES 미설정으로 LocMemCache(프로세스 로컬)가 쓰여
rate limit·공개 조회 한도·스케줄러 리더 락이 프로세스마다 따로 동작했습니다.
Redis 장애 시에는 프로세스 로컬 캐시로 degrade하며, 30초간 Redis 접속을 건너뛰어
요청마다 커넥트 타임아웃이 쌓이지 않게 합니다 (commons.cache_backends.ResilientRedisCache).
- gunicorn을
sync 2 워커에서 gthread 2 워커 × 8 스레드로 변경해
느린 요청 몇 건이 API 전체를 막지 못하게 합니다.
- Celery 브로커·Django 캐시 Redis를 서버 내부 컨테이너(
rankly-redis) 로 옮깁니다.
기존 외부 Redis(158.247.244.77)가 No route to host 상태여서 Celery가 전혀 동작하지
않고 있었습니다. 호스트 포트를 열지 않아 도커 네트워크 안에서만 접근됩니다.
네이버 cert용 Redis(NAVER_CERT_REDIS_URL)는 정상이라 그대로 둡니다.
공개 순위조회: 모바일 스마트스토어 URL·빈 키워드·플레이스 예산
m.smartstore.naver.com / m.brand.naver.com 상품 URL을 허용하고 데스크톱 URL로 정규화합니다.
(check_public / preview_url / validate_store_url)
check_public은 빈·공백 키워드를 한도 차감 전에 400으로 거절합니다.
- 공개 플레이스 조회 soft budget 기본값을 60초로 상향 (
PUBLIC_PLACE_BUDGET_SEC).
- 관리자 활동 로그는
rank_check_activity만 표시해 스파스 rank_check_failed 중복 행을 제거합니다.
2026-08-05
플레이스 순위 로그: 스캔완료 vs 통신실패 구분 (f1 정렬)
check_public place 응답이 scanned/error를 보고
transport_failed와 out_of_range를 구분합니다.
- 슬롯 수집(
process_slot_item / SlotUpdater)은 순위권 밖(스캔 완료)일 때
SlotRankLog(rank=0, devicePlatform=mobile)를 남기고, 통신 실패는 로그 없이 재시도합니다.
공개 순위조회: 검증·통신 실패 시 일일 한도 미소모
check_public / preview_url은 URL·플랫폼 검증을 한도 차감 전에 수행합니다.
- 통신 실패(
transport_failed)·서버 오류·미구현 미리보기는 차감한 횟수를 되돌립니다.
- 정상 조회(순위 발견·미발견
out_of_range 포함)만 일일 한도에 포함됩니다.
로그인 시 공개 실시간 순위조회 일 40회
POST /api/v1/slots/rank/check_public, POST .../preview_url에 Bearer 토큰이 있으면
IP 한도(일 5회) 대신 사용자당 하루 40회 한도를 적용합니다.
- 비로그인은 기존과 같이 IP 기준 일 5회입니다. 응답
limit/remaining으로 한도를 확인합니다.
슬롯 생성 URL 검증 오류를 JSON으로 통일
POST /api/v1/slots/user/create에서 플랫폼 URL 검증 실패 시
plain text 대신 { "error": "VALIDATION_ERROR", "detail": "..." } JSON을 반환합니다.
- UserLog 미들웨어는 JSON이 아닌 응답 본문도
detail로 보존합니다.
2026-08-04
관리자 사용자별 슬롯·활동 데이터 격리
GET /api/v1/accounts/{user_id}/slots/all은 관리자가 조회하더라도 선택 계정이
직접 소유한 슬롯만 반환합니다. slot_type=0으로 전체 상태 조회를 지원합니다.
GET /api/v1/admin/analytics/activity-logs?user_id=...의 사용자 필터를 관리자
화면에서 직접 사용하며, 가입 여정은 동일 anonymous_id라도 세션·계정이 충돌하면
다른 계정의 이벤트를 합치지 않습니다.
GET /api/v1/admin/analytics/journeys에 관리자 전용 마스킹 IP 목록
ip_addresses를 추가했습니다.
- 분석 이벤트 저장 시
page_path 쿼리를 제거하고 OAuth/token 계열 URL 파라미터를
저장하지 않습니다. 모두 하위호환 가능한 필드 추가·필터 정확성 수정입니다.
네이버 쇼핑 400위 페이징 누락 수정
- 네이버 모바일 검색은
pagingSize를 80 이상으로 요청해도 오가닉 상품을 40개만
반환하면서 다음 offset은 요청값 기준으로 이동하는 동작을 확인했습니다.
pagingSize=40으로 고정해 10페이지가 중복·누락 없이 실제 1~400위를 구성하도록
수정했습니다.
- 잘못 구성된 기존 일자 SERP 캐시를 사용하지 않도록 캐시 스키마를 갱신했습니다.
- 스마트스토어 URL·단일 MID가 가격비교 묶음과 단일상품에 모두 등장하면 단일상품
순위를 우선하고, 단일 노출이 없을 때만 가격비교 묶음 판매처 순위를 반환합니다.
- 카탈로그 URL·카탈로그 MID 조회는 기존처럼 가격비교 순위를 반환합니다.
네이버 가격비교 상품 매칭 정보 공개
GET /api/v1/slots/all의 슬롯 응답에서 기존 is_compare_product, compare_mid를
실제 boolean/카탈로그 MID 값으로 일관되게 제공합니다.
POST /api/v1/slots/rank/check_public 및 인증 순위 조회 응답에
is_compare_product, compare_mid, match_type(direct 또는 catalog_offer)을 추가했습니다.
- 가격비교 묶음 내부 판매처로 순위가 매칭되면 슬롯에도
is_compare_product=true, compare_mid가 저장됩니다.
GA4 Measurement ID + 관리자 성장 지표
| 구분 |
내용 |
| env |
GA_MEASUREMENT_ID (기본 G-V6TZ8H8KRG). FE는 NEXT_PUBLIC_GA_MEASUREMENT_ID |
GET /api/v1/admin/analytics/growth |
가입 전환·재방문·순위조회→가입·가입→슬롯 + GA4 콘솔 링크·이탈 페이지 |
| 하위호환 |
신규 엔드포인트. summary/funnel 유지 |
| 테스트 |
commons.tests.test_analytics_api.AdminAnalyticsAPITest.test_growth_200 |
신뢰 정합: 무료 10슬롯·공개 요금제·플레이스/쿠팡 공개 조회
| 구분 |
내용 |
| 무료 플랜 |
FREE_PLAN_BASE_SLOTS=10 (utils·한도 헬퍼·가입 카피 통일). migration 0008_align_public_slot_plans |
| 공개 요금제 |
Free(기본 slot)·Basic(150/15,900)·Pro(500/35,900) visible=True upsert — FE 폴백 허위 노출 방지 |
POST .../check_public place |
45초 전체 예산. 초과·예외 시 result_status=transport_failed + 안내 (FE hang 완화) |
POST .../check_public coupang |
준비중 고정 응답 (크롤 시도하지 않음) |
preview_url / 상품 메타 |
__PRELOADED_STATE__ 없을 때 og:title / title 폴백으로 name 채움 |
| 하위호환 |
응답 필드 추가만. breaking 없음 |
공개 네이버 순위: 40위 정확 + 41~400 구간 안내
| 구분 |
내용 |
POST /api/v1/slots/rank/check_public (naver) |
msearch로 최대 400위까지 탐색. 40위 이내는 result_status=ranked + 정확한 rank |
| 41~400 |
result_status=ranked_band, found=true, rank=null, rank_range_low/high(50단위, 예: 250~300), signup_required=true |
| 미발견 |
out_of_range + 회원가입 유도 (정확한 깊은 순위는 가입 후) |
| 하위호환 |
rank_range_*는 optional 추가. 기존 serp_top40 유지 |
| 테스트 |
test_public_rank_check_naver, test_public_rank_check_http |
| FE |
ranked_band / rank_range_low / rank_range_high UI 동기 권장 |
2026-08-03
공개 네이버 순위 40위 한도 + 쇼핑 OpenAPI 종료
| 구분 |
내용 |
POST /api/v1/slots/rank/check_public (naver) |
cert 1페이지(오가닉 최대 40위). 미발견 시 result_status=out_of_range, signup_required=true, 회원가입·슬롯 등록 유도 메시지 |
| serp_top40 |
동일 응답에 상위 목록 {rank,title,image_url,mall_name,price,is_matched}[] (신뢰 UI용, 내부 ID 미노출) |
| 로그인·슬롯 |
cert 기본 최대 400위 (AUTH_MAX_PAGE=10) |
| 쇼핑 OpenAPI |
openapi.naver.com/v1/search/shop 사용 중단(네이버 서비스 종료). 폴백 제거 — 상세 docs/ai/naver-shopping-openapi-retired.md |
| 테스트 |
test_public_rank_check_*, test_cert_rank_day_cache (40위 초과 시 순위 미노출) |
| FE |
signup_required / serp_top40로 목록·가입 유도 UI 동기 (rankly_FE) |
공개 순위체크 cert 고정 + 키워드 일 캐시 + 활동로그 outcome
| 구분 |
내용 |
POST /api/v1/slots/rank/check_public (naver) |
cert SERP 경로. 응답에 product_name, result_status(ranked|out_of_range|transport_failed), 미발견 시 rank=-1 |
| SERP 캐시 |
cert_rank — 프로세스 캐시 + Redis 일자 키 naver_serp:day:{YYYY-MM-DD}:{kw} (TTL NAVER_CERT_SERP_DAY_TTL_SEC, 기본 36h) |
| 커맨드 |
python manage.py warm_public_rank_cache --days 3 — Analytics 공개 조회를 cert로 재측정·캐시 워밍 (--dry-run 지원) |
| Analytics |
rank_check_activity.params.outcome (ranked|out_of_range|failed|rate_limited). 관리자 activity API에 outcome 노출 |
| cert producer |
Google Chrome + FlashProxy 한국 sticky 세션을 사용하는 독립 cert-minter 컨테이너 추가. 캡챠 완료 후 WTM 쿠키와 딥페이지 요청 헤더를 캡처 |
| 테스트 |
slots.tests.test_cert_rank_day_cache, slots.tests.test_public_rank_check_naver, slots.tests.test_public_rank_check_http, slots.tests.test_naver_cert_minter |
2026-07-20
Billing Phase 1 — iOS 슬롯 구독 MVP
| 구분 |
내용 |
| 신규 앱 |
독립 billing 앱 및 신규 테이블(기존 subscriptions 변경 없음) |
GET /api/v1/billing/entitlements/me |
JWT 사용자의 IAP 구독 상태, 플랜, 활성 슬롯 한도 조회 |
POST /api/v1/billing/purchases/verify |
iOS transaction_id를 Apple App Store Server API로 재조회·검증 후 entitlement 갱신 |
| 운영 |
Django Admin에서 Plan, StoreProduct, Subscription, 거래, 수기 권한, entitlement 조회·수기 권한 철회 |
| 설정 |
APPLE_APP_STORE_ISSUER_ID, APPLE_APP_STORE_KEY_ID, APPLE_APP_STORE_PRIVATE_KEY, APPLE_APP_STORE_BUNDLE_ID — 모두 서버 환경변수 |
| 제외 |
기존 웹/계좌이체 구독 통합은 후속 SPEC |
초기 월간 상품은 Basic(150 슬롯, com.rankly.sub.slot.3.monthly)과
Pro(500 슬롯, com.rankly.sub.slot.10.monthly)이며, iOS bundle ID는
dev.tuist.RankLog다. 활성 또는 grace 상태의 IAP 구독이 있으면 슬롯 생성
검증에서 해당 플랜의 고정 한도를 우선 적용한다.
Billing Phase 2 — Apple 알림 동기화
| 구분 |
내용 |
POST /internal/billing/webhooks/apple |
인증 없음. Apple Notifications V2의 JWS 및 x5c 인증서 체인 검증 후 동기 처리 |
| 멱등성 |
notificationUUID를 WebhookEvent의 외부 이벤트 키로 사용해 중복 수신은 2xx 종료 |
| 운영 |
실패 이벤트는 Admin 재처리 가능. revalidate_billing_subscriptions는 운영자 수동 진단용 |
| 설정 |
APPLE_APP_STORE_ROOT_CERTIFICATE(PEM), 선택 BILLING_ALERT_DISCORD_WEBHOOK_URL |
2026-07-15
카카오 로그인 — client_secret + 오류 코드 세분화
| 구분 |
내용 |
| env |
KAKAO_CLIENT_SECRET (선택·콘솔 ON 시 필수). settings 기본값 없음 — env만 |
| POST /api/v1/accounts/social/kakao/code |
토큰 교환 시 시크릿이 설정돼 있으면 client_secret 포함 |
| 400 error |
KAKAO_CLIENT_SECRET · KAKAO_REDIRECT_URI · KAKAO_INVALID_GRANT · KAKAO_INVALID_CLIENT · KAKAO_CONFIG 등 |
| 400 추가 필드 |
provider_error, provider_error_code (카카오 원문, optional) — 하위호환 |
| 테스트 |
ccounts.tests.test_kakao_oauth |
| 운영 |
docker-compose web에 KAKAO_CLIENT_SECRET 설정. FE public env에 넣지 않음 |
2026-07-14
공개 URL 미리보기 + 플레이스 모바일 크롤
| 구분 |
내용 |
POST /api/v1/slots/rank/preview_url |
auth=None. body { url }. 플랫폼 자동 판별 후 미리보기(name/image/category/address) + recommended_keywords. 성공 시 IP 일 5회 한도 −1 (check_public과 pub_rank:{ip}:{date} 공유) |
POST /api/v1/slots/rank/check_public |
한도 로직을 공유 헬퍼로 통합. place 분기는 place_rank_utils.get_place_ranking(기본 mobile SSR) |
| 플레이스 크롤 |
place_mobile_client.py / place_rank_utils.py 추가 (f1_slot_BE8 정합). 쇼핑 크롤 변경 없음 |
| 플레이스 추천 칩 |
상호·업종 + 지역(동/역/권역)×업종 (예: 운정 네일샵) |
| 쇼핑 미리보기 |
기존 get_product_info_async + related_tags |
| 테스트 |
slots.tests.test_place_preview_keywords |
2026-07-13
제품 웹 분석 (AnalyticsEvent + 관리자 집계)
| 구분 |
내용 |
| 모델 |
commons.AnalyticsEvent — session/UTM/dwell/exit (migration commons/0005_analyticsevent) |
POST /api/v1/analytics/track |
필드 확장: session_id, anonymous_id, dwell_ms, is_exit, utm_*, landing_path. 하위호환 (기존 필드 유지). 전환 이벤트만 AdminEvent에도 요약 |
GET /api/v1/admin/analytics/summary |
JWT admin — 방문·체류·Top pages/exits·일별 |
GET /api/v1/admin/analytics/funnel |
방문→순위조회→가입→슬롯→구독 퍼널 |
GET /api/v1/admin/analytics/sources |
UTM·리퍼러 + GSC top queries 힌트 |
GET /api/v1/admin/analytics/alerts |
병목 감지 (emit=true 시 AdminEvent) |
| Rate limit |
/api/v1/analytics/track 120/분 (폴백) |
| 테스트 |
commons.tests.test_analytics_api |
| SPEC |
docs/specs/product-analytics-insights.md |
쿠팡 슬롯 등록·공개 조회 일시 중단
| 구분 |
내용 |
| POST /slots/.../create (user create) |
플랫폼이 쿠팡이면 503 PLATFORM_UNAVAILABLE — 「쿠팡 슬롯 등록은 현재 준비중입니다.」 |
| POST /slots/rank/check_public |
platform=coupang이면 503 동일 정책 (공개 체험 차단) |
| 유지 |
기존 쿠팡 슬롯 cron(_task_coupang)은 변경 없음 |
네이버 쇼핑 SEO 진단 규칙 API (저장 없음)
| 구분 |
내용 |
| 엔진 |
slots/services/seo_diagnostic.py — |
| ules_version=naver_shopping_seo_v1, 네트워크 없음 |
|
| POST /slots/diagnostic/seo/evaluate |
auth=None. keyword + 선택 메타(product_name, image_url, related_tags, rank, found) → 항목별 pass/warn/fail/unknown. IP당 60회/시간 (429 RATE_LIMIT_EXCEEDED) |
| POST /slots/diagnostic/seo/from-slot |
JWT. slot_id로 Slot 메타 평가. 네이버 쇼핑 외 플랫폼은 400 |
| 정책 |
|
| elated_tags=null → 태그 규칙 unknown(0점 처리 금지). DiagnosticReport migration 미포함 |
|
| 테스트 |
slots.tests.test_seo_diagnostic (규칙 + evaluate HTTP 200/400/429, from-slot 권한·플랫폼·404) |
| SPEC |
docs/specs/agency-product-diagnostic.md |
2026-06-02
보고서 브랜딩 — 배경색·워터마크 필드 추가
| 구분 |
내용 |
| 모델 |
UserReportBranding에 background_color, watermark_enabled, watermark_text, watermark_opacity 추가 — migration accounts/0009_userreportbranding_watermark_fields |
GET/PUT /accounts/user/me/report-branding |
응답·요청에 배경색(#RRGGBB), 워터마크 on/off·문구·투명도(0.03~0.3) 포함 |
| 검증 |
background_color는 #RRGGBB, watermark_opacity는 0.03~0.3 범위 |
2026-06-01
보고서 브랜딩 (계정 단위 저장)
| 구분 |
내용 |
| 모델 |
UserReportBranding — migration accounts/0008_userreportbranding |
GET /accounts/user/me/report-branding |
JWT. 회사명·로고 URL/data URL·템플릿·강조색·하단 메모 조회 |
PUT /accounts/user/me/report-branding |
JWT. 보고서 브랜딩 설정 저장 (없으면 생성) |
2026-05-19
온보딩 · 공개 설정
| 구분 |
내용 |
| 모델 |
User.onboarding_step (welcome → completed) — migration accounts/0007_user_onboarding_step |
GET /accounts/user/me/onboarding |
JWT. 현재 단계·단계별 완료(가입·슬롯·순위 로그 추론 포함) |
PATCH /accounts/user/me/onboarding |
{ "step": "profile" \| "first_slot" \| ... } 완료 마킹 |
POST /accounts/complete-signup |
signup_source 저장 시 profile 온보딩 단계 자동 반영 |
GET /accounts/public-config |
auth=None. kakao_channel_id, kakao_channel_url, support_email |
| env |
KAKAO_CHANNEL_PUBLIC_ID, KAKAO_CHANNEL_CHAT_URL (미설정 시 null) |
구독 플랜 노출 순서 (sort_order)
| 구분 |
내용 |
| 모델 |
SubscriptionPlan.sort_order (INT, default 0) — 작을수록 먼저 노출 |
| 마이그레이션 |
subscriptions/migrations/0006_subscriptionplan_sort_order.py (멱등) |
GET /subscriptions/plans |
정렬: sort_order → category → price |
GET /subscriptions/plans/public |
응답에 sort_order 추가, 동일 정렬 |
POST/PATCH /subscriptions/plans |
생성·수정 시 sort_order 입력 가능 (관리자) |
| 하위호환 |
기존 플랜은 sort_order=0 — 기존 category·price 정렬과 동일하게 동작 |
2026-04-28
플레이스 N1/N2/N3 지수 통합 (네이버 PlaceSummary.gdid 기반)
| 구분 |
내용 |
| 모델 |
SlotRankLog, PlaceSnapshot에 naver_index1(Float, nullable), naver_index2, naver_index3 필드 추가 |
| 마이그레이션 |
slots/migrations/0015_add_naver_n123_indexes.py |
| 유틸 |
rank_utils.fetch_naver_n123(keyword, target_place_id, target_place_name) — 네이버 통합검색 SSR HTML → __APOLLO_STATE__ → PlaceSummary.gdid 파싱으로 N1/N2/N3 추출. RestaurantListSummary 키워드(맛집/카페 등)는 "{지역} 주변 음식" fallback으로 PlaceSummary 유도 |
| 배치 기록 |
process_slot_item() (slot_rank_api.py), _handle_place() (slot_updater.py) — place 순위 업데이트 시 N1/N2/N3 자동 기록 |
POST /check_rank |
place 플랫폼 응답 info에 naver_index1, naver_index2, naver_index3 추가 |
POST /check |
place 플랫폼 raw 응답에 naver_index1, naver_index2, naver_index3 추가 |
GET /slots/ranks |
히스토리 응답 항목에 naver_index1, naver_index2, naver_index3 추가 (SlotRankMainItemOut) |
슬롯 목록 rank_history |
각 히스토리 항목에 naver_index1, naver_index2, naver_index3 추가 (SlotRankHistoryItem) |
SlotRankLogSchemaOut |
naver_index1, naver_index2, naver_index3 (Optional[float]) 추가 |
N1/N2/N3 의미:
- N1: 키워드 유사도 지수 (카테고리 기반, 동일 키워드 내 거의 고정)
- N2: 관련성/인기도 지수 (장소 고유, fallback에서도 동일 값 유지)
- N3: 랭킹 지수 (순위와 강한 상관관계)
fallback 전략: PlaceSummary 미반환 키워드(강남역 맛집, 홍대 카페 등 RestaurantListSummary)의 경우 "{지역} 주변 음식" 검색으로 PlaceSummary를 유도하여 N1/N2/N3 추출. fallback N1/N3는 원본 키워드와 다른 컨텍스트값이지만 N2는 동일.
구독 · 공개 플랜 API
| 구분 |
내용 |
GET /api/v1/subscriptions/plans/public |
auth=None. 비로그인 가격·랜딩 페이지용 공개 플랜 목록. visible=True · is_active=True 플랜만 반환 (기존 GET /subscriptions/plans 와 동일 필터). 응답 스키마는 SubscriptionPlanPublicOut (created_at 등 운영 메타 제외). 응답 헤더 Cache-Control: public, max-age=300. |
2026-04-14
계정 · OTP · 탈퇴
| 구분 |
내용 |
POST /api/v1/accounts/otp/send |
Celery(delay)가 Redis에서 지연·hang 될 때 약 5초 타임아웃 후 SMTP 스레드 폴백으로 발송. HTTP 응답이 수 초 걸릴 수 있음(정상 범위). 브로커 타임아웃 설정(CELERY_BROKER_*) 보강. |
POST /api/v1/accounts/otp/verify |
(동작 동일) Swagger 설명 보강. |
POST /api/v1/accounts/login |
탈퇴 유예 중(pending_delete_at 미래, is_active=False) 로그인 허용 — 탈퇴 취소·안내용. |
POST /api/v1/accounts/withdraw |
유료(플랜 가격이 0보다 큰) 활성 구독만 탈퇴 차단. 무료 구독 자동 해지, 활성 슬롯 INACTIVE 처리. JWT 필수. |
POST /api/v1/accounts/withdraw/cancel |
auth=None. 본문 email + password 권장(JWT 없이 취소). 유예 기간 내 복구, 구독 없으면 기본 무료 구독 재생성. 슬롯은 자동 복구 없음(정책). |
| JWT |
ninja_jwt USER_AUTHENTICATION_RULE 커스텀(탈퇴 유예 사용자 인증 허용). |
| 문서 |
Swagger accounts 태그 엔드포인트 description 대폭 보강. 루트 /docs/accounts-otp-withdraw/ 가이드 추가. |
배포 · 인프라
| 구분 |
내용 |
| GitHub Actions / Docker |
docker compose vs docker-compose 분기 명확화. |
docker-compose (web) |
Healthcheck start_period 300s, retries 증가. collectstatic에서 --clear 제거(기동 시간 단축). |
| discord-bot |
depends_on.web: service_healthy → service_started (웹 헬스 대기로 배포 전체가 막히지 않도록). |
기타 (동일 기간 커밋 로그에 포함된 변경)
- 쿠팡 모바일 403 대응(쿠키·attestation·Akamai 등), 711 프록시 계정 교체.
- 구독
current_usage null 관련 버그 수정 등(자세한 내용은 Git 히스토리 참고).
운영 메모
- OTP는 Redis/Celery가 정상이면 큐 발송이 우선입니다. 장기적으로 브로커·네트워크 점검을 권장합니다.
2025-12-18 (참고)
- 프론트엔드 공유용 API 변경·Deprecated 안내: 루트 개발자 문서 →
frontend-api-changes-2025-12-18.
이전 이력
세부 커밋은 저장소에서 확인하세요.
git log --oneline -30