113 lines
5.3 KiB
SQL
113 lines
5.3 KiB
SQL
-- W3-3 포스팅 보드. 멱등. db/apply-local-ddl.sh 로 실행 DB 비파괴 적용.
|
|
-- posts / post_categories / unity_feed_sources / unity_feed_items. 추가만, 파괴 없음.
|
|
|
|
-- 1) post_categories (운영자 CRUD 카테고리 — D1/G2)
|
|
CREATE SEQUENCE IF NOT EXISTS "post_categories_id_seq";
|
|
CREATE TABLE IF NOT EXISTS "post_categories" (
|
|
"id" bigint DEFAULT nextval('post_categories_id_seq'::regclass) NOT NULL,
|
|
"name" character varying(80) NOT NULL,
|
|
"slug" character varying(80) NOT NULL,
|
|
"sort_order" integer DEFAULT 0 NOT NULL,
|
|
"is_active" boolean DEFAULT true NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
PRIMARY KEY ("id")
|
|
);
|
|
ALTER SEQUENCE "post_categories_id_seq" OWNED BY "post_categories"."id";
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "ux_post_categories_slug"
|
|
ON "post_categories" ("slug");
|
|
|
|
-- 2) posts (D1) — body_markdown 원본 + body_sanitized_html 캐시(D4), og_* 캐시(D5)
|
|
CREATE SEQUENCE IF NOT EXISTS "posts_id_seq";
|
|
CREATE TABLE IF NOT EXISTS "posts" (
|
|
"id" bigint DEFAULT nextval('posts_id_seq'::regclass) NOT NULL,
|
|
"category_id" bigint NOT NULL,
|
|
"author_user_id" bigint NOT NULL,
|
|
"title" character varying(200) NOT NULL,
|
|
"body_markdown" text NOT NULL,
|
|
"body_sanitized_html" text NOT NULL,
|
|
"link_url" character varying(2048),
|
|
"og_title" character varying(300),
|
|
"og_description" character varying(600),
|
|
"og_image_url" character varying(2048),
|
|
"og_site_name" character varying(200),
|
|
"og_fetched_at" timestamp with time zone,
|
|
"status" character varying(20) DEFAULT 'PUBLISHED' NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"deleted_at" timestamp with time zone,
|
|
"is_delete" boolean DEFAULT false NOT NULL,
|
|
PRIMARY KEY ("id")
|
|
);
|
|
ALTER SEQUENCE "posts_id_seq" OWNED BY "posts"."id";
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'posts_category_id_fkey') THEN
|
|
ALTER TABLE "posts"
|
|
ADD CONSTRAINT "posts_category_id_fkey"
|
|
FOREIGN KEY ("category_id") REFERENCES "post_categories" ("id");
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'posts_author_user_id_fkey') THEN
|
|
ALTER TABLE "posts"
|
|
ADD CONSTRAINT "posts_author_user_id_fkey"
|
|
FOREIGN KEY ("author_user_id") REFERENCES "users" ("id");
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'posts_status_check') THEN
|
|
ALTER TABLE "posts"
|
|
ADD CONSTRAINT "posts_status_check"
|
|
CHECK ("status" IN ('DRAFT', 'PUBLISHED'));
|
|
END IF;
|
|
END
|
|
$$;
|
|
CREATE INDEX IF NOT EXISTS "idx_posts_published_keyset"
|
|
ON "posts" ("status", "is_delete", "created_at" DESC, "id" DESC);
|
|
CREATE INDEX IF NOT EXISTS "idx_posts_category_keyset"
|
|
ON "posts" ("category_id", "created_at" DESC, "id" DESC)
|
|
WHERE "is_delete" = false AND "status" = 'PUBLISHED';
|
|
|
|
-- 3) unity_feed_sources (외부 피드 감시 소스 — D6/G5)
|
|
CREATE SEQUENCE IF NOT EXISTS "unity_feed_sources_id_seq";
|
|
CREATE TABLE IF NOT EXISTS "unity_feed_sources" (
|
|
"id" bigint DEFAULT nextval('unity_feed_sources_id_seq'::regclass) NOT NULL,
|
|
"name" character varying(120) NOT NULL,
|
|
"feed_url" character varying(2048) NOT NULL,
|
|
"is_active" boolean DEFAULT true NOT NULL,
|
|
"last_polled_at" timestamp with time zone,
|
|
"last_seen_guid" character varying(512),
|
|
"last_error" character varying(500),
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
PRIMARY KEY ("id")
|
|
);
|
|
ALTER SEQUENCE "unity_feed_sources_id_seq" OWNED BY "unity_feed_sources"."id";
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "ux_unity_feed_sources_url"
|
|
ON "unity_feed_sources" ("feed_url");
|
|
|
|
-- 4) unity_feed_items (감지된 새 글 — 운영자 알림 표면 + dedupe 영속화)
|
|
CREATE SEQUENCE IF NOT EXISTS "unity_feed_items_id_seq";
|
|
CREATE TABLE IF NOT EXISTS "unity_feed_items" (
|
|
"id" bigint DEFAULT nextval('unity_feed_items_id_seq'::regclass) NOT NULL,
|
|
"source_id" bigint NOT NULL,
|
|
"guid" character varying(512) NOT NULL,
|
|
"title" character varying(500) NOT NULL,
|
|
"link_url" character varying(2048) NOT NULL,
|
|
"published_at" timestamp with time zone,
|
|
"is_acknowledged" boolean DEFAULT false NOT NULL,
|
|
"detected_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
PRIMARY KEY ("id")
|
|
);
|
|
ALTER SEQUENCE "unity_feed_items_id_seq" OWNED BY "unity_feed_items"."id";
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'unity_feed_items_source_id_fkey') THEN
|
|
ALTER TABLE "unity_feed_items"
|
|
ADD CONSTRAINT "unity_feed_items_source_id_fkey"
|
|
FOREIGN KEY ("source_id") REFERENCES "unity_feed_sources" ("id");
|
|
END IF;
|
|
END
|
|
$$;
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "ux_unity_feed_items_source_guid"
|
|
ON "unity_feed_items" ("source_id", "guid");
|
|
CREATE INDEX IF NOT EXISTS "idx_unity_feed_items_unack"
|
|
ON "unity_feed_items" ("is_acknowledged", "detected_at" DESC)
|
|
WHERE "is_acknowledged" = false;
|