여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - 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>
265 lines
7.1 KiB
Python
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
|