o2o-site-AEO/solution/backend/router/v1/site/protocol.py
Mina Choi 11d30bb3d1 [chore] solution,admin,ontology: 코드 주석을 한 줄로 — 히스토리 주석 삭제
여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다.

- ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제
- 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다
- eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다

파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이).
site·frontend·admin tsc, site vitest 105 passed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:19 +09:00

265 lines
7.1 KiB
Python

import uuid
from datetime import date, datetime
from typing import Any, Optional
from pydantic import ConfigDict
from common.enums import (
BuildStatus,
JobStatus,
PlaceCategory,
PlaceStatus,
PublishAction,
PublishRejectReason,
PublishResult,
SiteStatus,
)
from common.models.gmodel import Res_PageProtocol, Res_WebPacketProtocol, WebPacketProtocol
class SiteProtocol(WebPacketProtocol):
pass
class Req_StartBuild(SiteProtocol):
"""정적 빌드 시작."""
publish: bool = False
class Res_StartBuild(Res_WebPacketProtocol):
job_id: Optional[uuid.UUID] = None
status: Optional[JobStatus] = None
created: bool = True
class Req_Rollback(SiteProtocol):
"""재굽기가 아니다 — 대상 버전이 디스크에 아직 있으면 심볼릭 링크만 돌린다."""
target_version: int
class SiteVersionData(WebPacketProtocol):
model_config = ConfigDict(from_attributes=True)
site_version_id: uuid.UUID
version: int
build_status: BuildStatus
unique_content_count: int = 0
build_error: Optional[str] = None
built_at: Optional[datetime] = None
created_at: Optional[datetime] = None
class SiteData(WebPacketProtocol):
model_config = ConfigDict(from_attributes=True)
site_id: uuid.UUID
place_id: uuid.UUID
status: SiteStatus
domain: Optional[str] = None
# 사장님이 고른 템플릿.
template_id: Optional[str] = None
# 저장된 디자인(색·서체·섹션).
theme: Optional[dict[str, Any]] = None
current_version_id: Optional[uuid.UUID] = None
published_at: Optional[datetime] = None
# 발행 썸네일(Azure Blob 공개 URL).
thumbnail_url: Optional[str] = None
class MySiteData(WebPacketProtocol):
"""내 사이트 목록의 한 줄 — 사업장(place) + 사이트(site)."""
place_id: uuid.UUID
name: str
category: PlaceCategory
place_status: PlaceStatus
road_address: Optional[str] = None
created_at: Optional[datetime] = None
site_id: Optional[uuid.UUID] = None
status: Optional[SiteStatus] = None
domain: Optional[str] = None
template_id: Optional[str] = None
published_at: Optional[datetime] = None
# 목록 카드의 그림.
thumbnail_url: Optional[str] = None
# 단건과 같은 규칙 — 노출값이 마지막 빌드보다 나중에 바뀌었으면 재발행 대상이다.
needs_rebuild: bool = False
class Res_MySites(Res_PageProtocol):
sites: list[MySiteData] = []
class PublishLogData(WebPacketProtocol):
model_config = ConfigDict(from_attributes=True)
publish_log_id: uuid.UUID
action: PublishAction
result: PublishResult
reject_reason: Optional[PublishRejectReason] = None
detail: Optional[Any] = None
created_at: Optional[datetime] = None
class Req_SiteTemplate(SiteProtocol):
"""템플릿 선택 저장."""
template_id: str = ""
class Req_SiteTheme(SiteProtocol):
"""색·섹션 저장."""
theme: dict[str, Any] = {}
class RenderStatusData(WebPacketProtocol):
"""정적 페이지가 실제로 구워졌는지."""
# PENDING(아직) · STALE(낡음) · OK · FAILED
state: str = "PENDING"
rendered_at: Optional[str] = None
rendered_version: Optional[int] = None
error: Optional[str] = None
class Res_Site(Res_WebPacketProtocol):
site: Optional[SiteData] = None
current_version: Optional[SiteVersionData] = None
# 노출값이 바뀐 뒤 다시 빌드하지 않았으면 True — 개별 재빌드 대상이라는 표시.
needs_rebuild: bool = False
# 빌드(DB)와 렌더(정적 파일)는 다른 단계다.
render: RenderStatusData = RenderStatusData()
class Res_SiteVersions(Res_WebPacketProtocol):
versions: list[SiteVersionData] = []
class Res_PublishLogs(Res_WebPacketProtocol):
logs: list[PublishLogData] = []
class AuditCheckData(WebPacketProtocol):
id: str
group: str
label: str
points: int
earned: int
status: str
detail: str
recommendation: Optional[str] = None
class Res_SeoAudit(Res_WebPacketProtocol):
seo_score: int = 0
aeo_score: int = 0
overall_score: int = 0
checks: list[AuditCheckData] = []
summary: dict[str, Any] = {}
visibility: dict[str, int] = {}
disclaimer: str = ""
class Req_SiteSlug(SiteProtocol):
"""사이트 주소(네임스페이스) 예약."""
slug: str = ""
class Res_SlugCheck(Res_WebPacketProtocol):
"""주소 사용 가능 확인."""
available: bool = False
# 불가 사유(services/site_slug 의 REASON_*): INVALID_LENGTH / INVALID_FORMAT / RESERVED / TAKEN.
reason: Optional[str] = None
# 이름 자체는 멀쩡한데 못 쓰는 경우(예약어·중복)에만 대안을 하나 준다.
suggestion: Optional[str] = None
class Res_SiteSlug(Res_WebPacketProtocol):
"""주소 저장 결과."""
site: Optional[SiteData] = None
reason: Optional[str] = None
suggestion: Optional[str] = None
class Req_SiteStatus(SiteProtocol):
"""발행 상태 전이."""
action: PublishAction
class ShowcaseItem(WebPacketProtocol):
"""랜딩 쇼케이스 카드 한 장."""
name: str
category: PlaceCategory
# "강원특별자치도 양양군" 수준.
region: Optional[str] = None
# 발행 주소.
url: str
# 없으면 화면이 글자 카드로 떨어진다(썸네일은 발행의 부수 효과라 실패할 수 있다).
thumbnail_url: Optional[str] = None
class Res_Showcase(Res_WebPacketProtocol):
items: list[ShowcaseItem] = []
class PostData(WebPacketProtocol):
"""미니 블로그 글 하나 — 빌더 앱 '이번 달 생성된 글' 목록 카드."""
model_config = ConfigDict(from_attributes=True)
post_id: uuid.UUID
body: str
topic_kind: int
status: int
scheduled_date: Optional[date] = None
created_at: Optional[datetime] = None
sent_at: Optional[datetime] = None
approved_at: Optional[datetime] = None
published_at: Optional[datetime] = None
# 화면은 발행완료/발행실패만 보여준다(발행 전 상태는 안 보여준다) — 승인됐는데 BUILD 잡이 dead-letter 로 끝났을 때만 true(PostService._latest_build_failed).
build_failed: bool = False
class Res_MyPosts(Res_WebPacketProtocol):
posts: list[PostData] = []
class Req_EditPost(SiteProtocol):
"""수정하고 그대로 승인 — 로그인 세션 버전(메일 없이 목록에서 바로 고칠 때)."""
body: str
class Res_GenerateNow(Res_WebPacketProtocol):
"""즉시 생성 결과 — 사장님이 고른 구간(시작~끝) 중 몇 일을 채웠는지."""
requested: int = 0
created: int = 0
class GenerationBatch(WebPacketProtocol):
"""생성 회차 하나 — 같은 스윕에서 한 번에 만들어진 글 묶음(post_crud.generation_batches)."""
created_at: datetime
count: int
model: Optional[str] = None
class Res_GenerationHistory(Res_WebPacketProtocol):
batches: list[GenerationBatch] = []
class Res_GenerateOne(Res_WebPacketProtocol):
"""개별 생성 결과 — 달력에서 빈 날짜 하나를 콕 집어 만들었을 때."""
post: Optional[PostData] = None