Files
klz-cables.com/src/migrations/20260225_175000_native_localization.ts
2026-02-26 01:32:22 +01:00

286 lines
14 KiB
TypeScript

import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
/**
* Migration: native_localization
*
* Transforms the existing schema (manual `locale` select column on each row)
* into Payload's native localization join-table structure.
*
* Each statement is a separate db.execute call to avoid Drizzle multi-statement issues.
*/
export async function up({ db }: MigrateUpArgs): Promise<void> {
// ── 1. Global locale enum ────────────────────────────────────────────────────
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__locales" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__posts_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__products_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_pages_layout" AS ENUM('default', 'fullBleed');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_version_layout" AS ENUM('default', 'fullBleed');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__posts_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__products_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 2. Alter pages table ─────────────────────────────────────────────────────
await db.execute(sql`ALTER TABLE "pages" ADD COLUMN IF NOT EXISTS "layout" "enum_pages_layout" DEFAULT 'default'`);
await db.execute(sql`ALTER TABLE "pages" ADD COLUMN IF NOT EXISTS "_status" "enum_pages_status" DEFAULT 'draft'`);
// ── 3. Create pages_locales join table ───────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "pages_locales" (
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "pages_locales" ADD CONSTRAINT "pages_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "pages_locales" ADD CONSTRAINT "pages_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "pages"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 4. Backfill pages_locales from old pages rows ────────────────────────────
await db.execute(sql`
INSERT INTO "pages_locales" ("title", "slug", "excerpt", "content", "_locale", "_parent_id")
SELECT
p.title, p.slug, p.excerpt, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "pages" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO UPDATE
SET "title" = EXCLUDED."title",
"slug" = EXCLUDED."slug",
"excerpt" = EXCLUDED."excerpt",
"content" = EXCLUDED."content"
`);
// ── 5. Drop old columns from pages ───────────────────────────────────────────
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "slug"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "excerpt"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "locale"`);
// ── 6. Create posts_locales join table ───────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "posts_locales" (
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"category" varchar,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "posts_locales" ADD CONSTRAINT "posts_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "posts_locales" ADD CONSTRAINT "posts_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "posts"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 7. Backfill posts_locales ────────────────────────────────────────────────
await db.execute(sql`
INSERT INTO "posts_locales" ("title", "slug", "excerpt", "category", "content", "_locale", "_parent_id")
SELECT
p.title, p.slug, p.excerpt, p.category, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "posts" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO UPDATE
SET "title" = EXCLUDED."title",
"slug" = EXCLUDED."slug",
"excerpt" = EXCLUDED."excerpt",
"category" = EXCLUDED."category",
"content" = EXCLUDED."content"
`);
// ── 8. Drop old columns from posts ───────────────────────────────────────────
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "slug"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "excerpt"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "category"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "locale"`);
// ── 9. Create products_locales join table ────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "products_locales" (
"title" varchar,
"description" varchar,
"application" jsonb,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products_locales" ADD CONSTRAINT "products_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products_locales" ADD CONSTRAINT "products_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "products"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 10. Backfill products_locales ────────────────────────────────────────────
// Products were separate DE/EN rows — each becomes a locale entry on its own id
await db.execute(sql`
INSERT INTO "products_locales" ("title", "description", "application", "content", "_locale", "_parent_id")
SELECT
p.title, p.description, p.application, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "products" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO NOTHING
`);
// ── 11. Drop old columns from products ───────────────────────────────────────
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "description"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "application"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "locale"`);
// ── 12. Version tables (_posts_v, _products_v, _pages_v) locale columns ──────
await db.execute(sql`ALTER TABLE "_posts_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__posts_v_published_locale"`);
await db.execute(sql`ALTER TABLE "_products_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__products_v_published_locale"`);
await db.execute(sql`ALTER TABLE "_pages_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__pages_v_published_locale"`);
// ── 13. Create _posts_v_locales ──────────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_posts_v_locales" (
"version_title" varchar, "version_slug" varchar, "version_excerpt" varchar,
"version_category" varchar, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_posts_v_locales" ADD CONSTRAINT "_posts_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_posts_v_locales" ADD CONSTRAINT "_posts_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_posts_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 14. Create _products_v_locales ───────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_products_v_locales" (
"version_title" varchar, "version_description" varchar,
"version_application" jsonb, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v_locales" ADD CONSTRAINT "_products_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v_locales" ADD CONSTRAINT "_products_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_products_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 15. Create _pages_v_locales ──────────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_pages_v_locales" (
"version_title" varchar, "version_slug" varchar,
"version_excerpt" varchar, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_pages_v_locales" ADD CONSTRAINT "_pages_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_pages_v_locales" ADD CONSTRAINT "_pages_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_pages_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 16. Drop the now-redundant old locale enum ───────────────────────────────
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_pages_locale"`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_posts_locale"`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_products_locale"`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`DROP TABLE IF EXISTS "pages_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_pages_v_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "posts_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_posts_v_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "products_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_products_v_locales" CASCADE`);
}