"""site_sections — **개인화 데이터**의 단일 자리. ★ 규칙(2026-09-09) area_* = 공용. 지역 단위, 여러 사이트가 나눠 쓴다. 렌더러 모양 그대로. site_sections = 개인화 싸그리. 사이트마다 달라지는 것 전부 — 거리·숨김·순서·사장님 편집. ★ 이 표는 이미 있었는데 **아무도 읽지 않았다**(실측 2026-09-09: 10행이 마이그레이션 0003 으로 들어간 뒤 방치, 발행 파이프라인은 `sites.theme.sections[].data` 만 봤다). 그 자리를 정본으로 세우면서 CRUD 를 붙인다. ★ 유일성은 `(site_id, section_id)` 다 — 섹션당 한 행. 그래서 upsert 가 갱신을 겸한다. """ from sqlalchemy import select from sqlalchemy.dialects.postgresql import insert as pg_insert from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import site_sections from common.utils.gtime import GTime class SiteSectionCRUD: async def list_by_site(self, db, site_id): return await DB_SESSION_MNG.execute( db, select(site_sections).where( site_sections.site_id == site_id, site_sections.deleted == False, # noqa: E712 ).order_by(site_sections.sort_order.asc()), ) async def upsert(self, db, values: dict): """섹션 하나의 개인화 값을 넣거나 갱신한다. ★ `uq_site_contents_section (site_id, section_id) WHERE deleted = false` 에 태운다. ★ source_type 은 갱신하지 않는다 — 사장님이 손으로 고친 섹션(OWNER)을 수집이 API 값으로 되돌리면, 고쳐 둔 것이 다음 수집에 조용히 사라진다. """ stmt = pg_insert(site_sections).values(**values) return await DB_SESSION_MNG.add( db, stmt.on_conflict_do_update( index_elements=[site_sections.site_id, site_sections.section_id], index_where=(site_sections.deleted == False), # noqa: E712 set_={ "data": stmt.excluded.data, "shared_ref": stmt.excluded.shared_ref, "updated_at": GTime.UTC(), }, ), )