- 지역 이야기(가요·인물·연표·엽서·퀴즈) 생성 경로: 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>
66 lines
3.5 KiB
SQL
66 lines
3.5 KiB
SQL
-- 0008 · 공용과 개인화를 이름으로 가른다
|
|
--
|
|
-- area_* 공용. 지역 단위, 여러 사이트가 나눠 쓴다 → 렌더러 모양 그대로 담는다.
|
|
-- site_sections 개인화 전부(거리·숨김·순서·사장님 편집).
|
|
--
|
|
-- 지금은 body 가 TourAPI 원문 이름이고(빌드마다 바꿔 왔다), 거리·숨김이 공용 쪽에 있다.
|
|
|
|
-- 1. 타입명을 모든 행에 채운다
|
|
-- 인덱스를 먼저 뗀다 — 옛 (region_code, kind) 는 같은 지역 관광지 여러 건이 전부
|
|
-- kind='attraction' 이 되는 순간 부딪친다. 장소류 유일성은 (source, external_id) 가 잡는다.
|
|
DROP INDEX IF EXISTS public.uq_local_contents_kind;
|
|
|
|
UPDATE public.area_contents SET kind = CASE content_type
|
|
WHEN 1 THEN 'weather' WHEN 2 THEN 'festival' WHEN 3 THEN 'attraction'
|
|
WHEN 4 THEN 'restaurant' WHEN 5 THEN 'course' END,
|
|
updated_at = now()
|
|
WHERE deleted = FALSE AND kind IS NULL AND content_type BETWEEN 1 AND 5;
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_local_contents_kind
|
|
ON public.area_contents (region_code, kind)
|
|
WHERE deleted = FALSE AND kind IS NOT NULL AND external_id IS NULL;
|
|
|
|
-- 2. body 를 렌더러 모양으로
|
|
-- 좌표는 뺀다 — latitude/longitude 컬럼이 그 자리다(0004).
|
|
-- lclsSystm2 는 남긴다 — 주변 맛집에서 같은 업태를 뺄 때 쓴다.
|
|
UPDATE public.area_contents SET body = (
|
|
jsonb_strip_nulls(jsonb_build_object(
|
|
'name', COALESCE(NULLIF(body->>'title', ''), title),
|
|
'searchQuery', COALESCE(NULLIF(body->>'title', ''), title),
|
|
'location', NULLIF(body->>'addr1', ''),
|
|
'imageUrl', NULLIF(body->>'firstimage', ''),
|
|
'lclsSystm2', NULLIF(body->>'lclsSystm2', '')
|
|
))
|
|
-- 축제는 날짜 원값을 남긴다 — 노출 기간 필터가 읽는다.
|
|
|| CASE WHEN content_type = 2 THEN jsonb_strip_nulls(jsonb_build_object(
|
|
'eventstartdate', NULLIF(body->>'eventstartdate', ''),
|
|
'eventenddate', NULLIF(body->>'eventenddate', ''),
|
|
'homepage', NULLIF(body->>'homepage', ''),
|
|
'overview', NULLIF(body->>'overview', '')
|
|
)) ELSE '{}'::jsonb END
|
|
), updated_at = now()
|
|
WHERE deleted = FALSE AND content_type BETWEEN 2 AND 5 AND body ? 'contentid';
|
|
|
|
-- 3. 거리·숨김을 사이트 쪽으로
|
|
-- 사이트가 없는 업장은 다음 수집이 다시 쓴다 — 여기서 sites 행을 만들지 않는다.
|
|
-- items 배열이 아니라 ref → 값 맵이다. 순서는 읽는 쪽이 정한다.
|
|
INSERT INTO public.site_sections (site_id, section_id, data, source_type, status, sort_order)
|
|
SELECT s.site_id,
|
|
'local',
|
|
jsonb_build_object('kind', 'local', 'places', COALESCE(jsonb_object_agg(
|
|
r.local_content_id::text,
|
|
jsonb_build_object('kind', a.kind, 'distanceMeters', r.distance_m, 'hidden', r.hidden)
|
|
), '{}'::jsonb)),
|
|
2, -- SourceType.API — 사람이 쓴 게 아니라 수집이 만든 개인화 값이다
|
|
1,
|
|
0
|
|
FROM public.place_area_refs r
|
|
JOIN public.area_contents a ON a.local_content_id = r.local_content_id AND a.deleted = FALSE
|
|
JOIN public.sites s ON s.place_id = r.place_id AND s.deleted = FALSE
|
|
WHERE r.deleted = FALSE
|
|
GROUP BY s.site_id
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- place_area_refs 는 아직 지우지 않는다. 읽는 코드가 옮겨 간 것을 확인한 뒤 별도 번호로 뗀다 —
|
|
-- 같은 마이그레이션에서 옮기고 지우면, 이관이 틀렸을 때 되돌릴 원본이 없다.
|