-- 0003 · 섹션 콘텐츠를 sites.theme 에서 꺼내 행으로 -- -- theme 42,150 B 중 디자인은 636 B 뿐이고 나머지가 콘텐츠다. 크기가 아니라 쓰기 단위가 -- 문제였다 — 영상 주소 하나(592 B)를 고쳐도 42 KB 를 다시 쓰고, 둘이 만지면 뒤가 앞을 덮고, -- 항목마다 출처·검증 상태를 물을 자리가 없다. -- ★ 이 테이블이 JSON import/export 의 단위다. CREATE TABLE IF NOT EXISTS site.site_contents ( site_content_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), site_id uuid NOT NULL, -- site.sites.site_id section_id VARCHAR(50) NOT NULL, -- 'songs' 'itinerary' 'video' 'people' … data JSONB NOT NULL, -- 그 섹션의 항목 배열(shared 의 XxxItem[] 계약) source_type SMALLINT NOT NULL DEFAULT 1, -- SourceType: 1=owner 2=api 3=crawl 4=llm -- ★ 공유 콘텐츠는 값을 복제하지 않고 **id 로 가리킨다**(축제·지역 이야기). -- 가리키는 동안 data 는 비어 있을 수 있다 — 발행할 때 원본을 펼쳐 payload 에 싣는다. shared_ref uuid NULL, status SMALLINT NOT NULL DEFAULT 1, -- FactStatus 와 같은 축: 3=verified 4=corrected 만 노출 sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted BOOLEAN NOT NULL DEFAULT FALSE ); -- 한 사이트의 한 섹션은 한 행이다. 순서·on/off 는 theme 이 갖는다. CREATE UNIQUE INDEX IF NOT EXISTS uq_site_contents_section ON site.site_contents (site_id, section_id) WHERE deleted = FALSE; -- 지역 이야기는 업장이 아니라 지역의 것이다 — 군산 이야기는 군산 숙소가 같이 쓴다. CREATE TABLE IF NOT EXISTS local.region_stories ( region_story_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), region_code VARCHAR(10) NOT NULL, -- 카카오 행정구역 코드 kind VARCHAR(50) NOT NULL, -- 'songs' 'people' 'chronicle' 'postcard' 'quiz' data JSONB NOT NULL, source_type SMALLINT NOT NULL DEFAULT 4, -- 4=llm 1=운영자 status SMALLINT NOT NULL DEFAULT 1, -- FactStatus. 검수 전에는 안 나간다 created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted BOOLEAN NOT NULL DEFAULT FALSE ); CREATE UNIQUE INDEX IF NOT EXISTS uq_region_stories_kind ON local.region_stories (region_code, kind) WHERE deleted = FALSE; -- theme 의 콘텐츠를 옮긴다. data 는 문자열이라 JSONB 로 되돌린다 — 깨진 것은 건너뛴다. INSERT INTO site.site_contents (site_id, section_id, data, source_type, status, sort_order) SELECT s.site_id, sec->>'id', (sec->>'data')::JSONB, 1, -- 사장님이 넣은 것으로 본다 3, -- 이미 발행에 쓰이던 값이라 verified ordinality - 1 FROM site.sites s CROSS JOIN LATERAL jsonb_array_elements(s.theme->'sections') WITH ORDINALITY AS t(sec, ordinality) WHERE s.deleted = FALSE AND s.theme ? 'sections' AND sec->>'data' IS NOT NULL AND sec->>'data' <> '' AND jsonb_typeof((sec->>'data')::JSONB) IS NOT NULL ON CONFLICT DO NOTHING; -- theme.sections[].data 는 읽는 쪽이 옮겨간 뒤 별도 번호로 뗀다.