o2o-site-AEO/solution/backend/services/search_console_settings.py
Mina Choi 3f47d5ecd2 [feat] solution/backend: 서치콘솔 자동 제출·색인 상태 추적 추가
사이트 발행 성공과 Google 색인 관측은 별도 상태다. 외부 API 장애로 발행이 실패하거나 재시작 때 추적 정보가 사라지지 않도록 분리.

- Google 클라이언트·배치·DB·Teams 알림 모듈 분리
- 기존 스케줄러 연결, 재시도·중복 실행 방지와 선택 설정 추가
- ORM·초기 DDL·마이그레이션·운영 설정 문서 동시 갱신

검증: 관련 59건 통과, compose 설정·diff 검사 통과. 추가 회귀 23건 통과, 기존 발행 검수 실패 1건은 변경 전 코드에서도 재현. 운영 배포·Google/Teams 실호출 미실행.
2026-09-15 14:50:30 +09:00

44 lines
1.8 KiB
Python

"""Google 로그인 설정과 분리한다. 서비스 계정 키는 서버 파일로만 읽는다."""
import os
from dataclasses import dataclass
from urllib.parse import urlsplit
@dataclass(frozen=True)
class SearchConsoleSettings:
property_url: str
credentials_file: str
alert_url: str = ""
alert_days: int = 7
def load_settings() -> SearchConsoleSettings | None:
if os.environ.get("GSC_ENABLED") != "1":
return None
prop = os.environ.get("GSC_PROPERTY_URL", "").strip()
key_file = os.environ.get("GSC_CREDENTIALS_FILE", "").strip()
if not prop or not key_file:
raise ValueError("GSC_CONFIG_MISSING")
parsed = urlsplit(prop)
domain_property = prop.startswith("sc-domain:") and bool(prop.removeprefix("sc-domain:"))
root_property = (parsed.scheme == "https" and parsed.hostname and parsed.path == "/"
and not parsed.query and not parsed.fragment and not parsed.username)
if not domain_property and not root_property:
raise ValueError("GSC_PROPERTY_INVALID")
days = int(os.environ.get("GSC_ALERT_DAYS", "7"))
if days < 1:
raise ValueError("GSC_ALERT_DAYS_INVALID")
return SearchConsoleSettings(prop, key_file, os.environ.get("GSC_ALERT_WEBHOOK_URL", ""), days)
def belongs_to_property(page_url: str, property_url: str) -> bool:
parsed = urlsplit(page_url)
if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password:
return False
if parsed.query or parsed.fragment:
return False
if property_url.startswith("sc-domain:"):
domain = property_url.removeprefix("sc-domain:").lower()
return bool(domain) and (parsed.hostname == domain or parsed.hostname.endswith("." + domain))
return page_url.startswith(property_url)