-- 실행 순서: 이 파일 먼저 → migration_2026-07-30_social_upload_merge.sql -- (§1 의 ssul_content 를 FK 로 참조하므로 순서를 바꾸면 실패한다) -- 대상: MySQL 8.0.16+ (CHECK 가 실제로 강제되는 버전. 운영은 8.4) -- 참고: MySQL 은 ADD COLUMN IF NOT EXISTS 를 지원하지 않는다. 일부만 적용된 DB 라면 -- 이미 적용된 문은 개별적으로 건너뛸 것(각 문이 독립적으로 실행 가능하다). -- ============================================================================= -- §1. 신규 테이블 -- ============================================================================= -- 썰박스 1편 — 생성 잡과 산출물을 한 행으로 관리 (castad video 와 같은 구조). -- id 가 크레딧 원장 멱등 키(job_type='ssul', job_ref=str(id))의 앵커다. CREATE TABLE IF NOT EXISTS `ssul_content` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '고유 식별자 (크레딧 원장 job_ref 앵커)', `user_uuid` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '생성 요청한 사용자 UUID (탈퇴 시 NULL)', `scenario` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '시나리오 코드 (joseon/samgukji/greek/odyssey)', `input` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '입력값 (네이버 지도 URL 또는 업장명)', `scenes` int NOT NULL COMMENT '생성할 장면 수 (요청 스키마에서 4~20 제한, 기본 9)', `seconds` int NOT NULL COMMENT '장면당 초 길이 (요청 스키마에서 20~90 제한, 기본 30)', `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'queued' COMMENT '상태 (queued/running/done/error). 목록에는 done 만 노출', `step` int NOT NULL DEFAULT '0' COMMENT '진행 단계 0~4 (폴링 응답용. 0=준비, 4=영상 합성 완료)', `error` text COLLATE utf8mb4_unicode_ci COMMENT '실패 사유', `video_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '완성 영상 URL (Azure Blob 공개 URL 또는 로컬 서빙 경로)', `poster_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '포스터 URL (없으면 프론트가 시나리오 표지로 대체)', `store_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '대상 업장명 (통합 목록에서 castad video.store_name 자리에 대응)', `region` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '지역 (통합 목록의 지역 필터에 사용)', `detail_region_info` text COLLATE utf8mb4_unicode_ci COMMENT '상세 지역 정보 (도로명 우선, 없으면 지번). 지역 필터 별칭 매칭용', `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '소프트 삭제 여부', `created_at` datetime NOT NULL DEFAULT (now()) COMMENT '생성 요청 일시 (목록 정렬 기준)', PRIMARY KEY (`id`), KEY `idx_ssul_content_status` (`status`), KEY `idx_ssul_content_list` (`is_deleted`,`status`,`created_at`), KEY `idx_ssul_content_user_created` (`user_uuid`,`created_at`), KEY `idx_ssul_content_scen_created` (`scenario`,`created_at`), CONSTRAINT `ssul_content_ibfk_1` FOREIGN KEY (`user_uuid`) REFERENCES `user` (`user_uuid`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ============================================================================= -- §2. 기존 테이블 확장 -- ============================================================================= -- 2-1. credit_transaction — 크레딧 멱등 키. -- (job_type, job_ref, type) 유니크가 "작업 1건당 consume 1행 / refund 1행"을 -- DB 레벨에서 보장한다. 기존 행은 job_type 이 NULL 이라 제약에 걸리지 않는다 -- (MySQL 은 유니크에서 NULL 을 서로 다른 값으로 취급). ALTER TABLE `credit_transaction` ADD COLUMN `job_type` VARCHAR(20) NULL COMMENT '차감 유발 작업 종류 (ssul/video)', ADD COLUMN `job_ref` VARCHAR(64) NULL COMMENT '작업 식별자 (ssul_content.id 문자열 또는 video.task_id)', ADD UNIQUE KEY `uq_credit_job` (`job_type`, `job_ref`, `type`); -- 2-2. comment — ADO2 영상과 썰박스 댓글을 한 테이블로. -- 대상은 video_id / content_id 중 **정확히 하나**만 채워지며 CHECK 로 강제한다. -- 기존 행은 전부 video_id 가 채워져 있어 CHECK 를 이미 만족한다. ALTER TABLE `comment` ADD COLUMN `content_id` BIGINT NULL COMMENT '썰박스 콘텐츠 id (ADO2 댓글이면 NULL)', ADD CONSTRAINT `fk_comment_content` FOREIGN KEY (`content_id`) REFERENCES `ssul_content` (`id`) ON DELETE CASCADE; ALTER TABLE `comment` ADD INDEX `idx_comment_content_id` (`content_id`); ALTER TABLE `comment` MODIFY COLUMN `video_id` INT NULL COMMENT 'ADO2 영상 id (썰박스 댓글이면 NULL)'; ALTER TABLE `comment` ADD CONSTRAINT `ck_comment_one_target` CHECK ((`video_id` IS NULL) <> (`content_id` IS NULL)); -- 2-3. video_reaction — 좋아요도 동일하게 병합. -- 1인 1회 보장은 유니크 두 개로 나눈다. 썰박스 행(video_id IS NULL)은 -- uq_video_reaction_user_video 에 걸리지 않는다(NULL 은 서로 다른 값 취급). -- content_id 단독 인덱스는 카운트 집계(GROUP BY content_id)용이다 — -- 유니크는 user_uuid 가 선행이라 이 용도로 못 쓴다. ALTER TABLE `video_reaction` ADD COLUMN `content_id` BIGINT NULL COMMENT '썰박스 콘텐츠 id (ADO2 반응이면 NULL)', ADD CONSTRAINT `fk_video_reaction_content` FOREIGN KEY (`content_id`) REFERENCES `ssul_content` (`id`) ON DELETE CASCADE; ALTER TABLE `video_reaction` ADD INDEX `idx_video_reaction_content_id` (`content_id`), ADD UNIQUE KEY `uq_video_reaction_user_content` (`user_uuid`, `content_id`); ALTER TABLE `video_reaction` MODIFY COLUMN `video_id` INT NULL COMMENT 'ADO2 영상 id (썰박스 반응이면 NULL)'; ALTER TABLE `video_reaction` ADD CONSTRAINT `ck_video_reaction_one_target` CHECK ((`video_id` IS NULL) <> (`content_id` IS NULL));