- 지역 이야기(가요·인물·연표·엽서·퀴즈) 생성 경로: story_service · grounding/story · section_prompts. 지금까지 만들 자리가 없어 시안에만 손으로 넣은 3만 자였다 - 발행본 섹션: ItinerarySection · Carousel 레일 자동재생(use-rail-autoplay) · Festival · LocalGuide · Weather · Gallery · Header/Footer - 목업 payload 를 payloads-mockup/ 으로 분리 — 발행 대상과 섞이지 않게 - DB 새 구조 후속: site_payload · local_content_crud 조인 정리 · 테스트 - 마이그레이션 주석 축약: 9개 파일 합계 주석 비율 48% → 25%. 실측과 밟은 함정만 남기고 논증은 커밋 메시지로 옮겼다 검증: site·frontend 빌드 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
62 lines
3.6 KiB
SQL
62 lines
3.6 KiB
SQL
-- 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 는 읽는 쪽이 옮겨간 뒤 별도 번호로 뗀다.
|