From ba1fc6eb00ce8194a8d9d61cd516a51e01d4e7f8 Mon Sep 17 00:00:00 2001 From: Martin Porwoll Date: Thu, 8 Jan 2026 15:47:40 +0000 Subject: [PATCH] fix(db): add migration for Favorites and Series collections - Create migration with ENUMs and tables for BlogWoman collections - favorites table with category, badge, priceRange, affiliateNetwork enums - series table with localized fields (title, tagline, description) - Associated _rels and _locales tables - Set push: false to enforce migration-based schema changes Co-Authored-By: Claude Opus 4.5 --- ...260108_160000_add_blogwoman_collections.ts | 247 ++++++++++++++++++ src/migrations/index.ts | 6 + src/payload.config.ts | 2 +- 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/migrations/20260108_160000_add_blogwoman_collections.ts diff --git a/src/migrations/20260108_160000_add_blogwoman_collections.ts b/src/migrations/20260108_160000_add_blogwoman_collections.ts new file mode 100644 index 0000000..ef1a4f7 --- /dev/null +++ b/src/migrations/20260108_160000_add_blogwoman_collections.ts @@ -0,0 +1,247 @@ +import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres' + +/** + * Migration: Add BlogWoman Collections + * + * Creates: + * - favorites table (Affiliate-Produkte) + * - favorites_rels (relations to media) + * - series table (YouTube-Serien) + * - series_locales (localized fields) + * - series_rels (relations to media) + */ +export async function up({ db, payload, req }: MigrateUpArgs): Promise { + await db.execute(sql` + + -- ============================================================ + -- ENUMS FOR FAVORITES + -- ============================================================ + DO $$ BEGIN + CREATE TYPE "public"."enum_favorites_category" AS ENUM('fashion', 'beauty', 'travel', 'tech', 'home'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + CREATE TYPE "public"."enum_favorites_price_range" AS ENUM('budget', 'mid', 'premium', 'luxury'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + CREATE TYPE "public"."enum_favorites_affiliate_network" AS ENUM('amazon', 'awin', 'ltk', 'direct', 'other'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + CREATE TYPE "public"."enum_favorites_badge" AS ENUM('investment-piece', 'daily-driver', 'grfi-approved', 'new', 'bestseller'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + -- ============================================================ + -- FAVORITES TABLE + -- ============================================================ + CREATE TABLE IF NOT EXISTS "favorites" ( + "id" serial PRIMARY KEY NOT NULL, + "tenant_id" integer, + "title" varchar NOT NULL, + "slug" varchar NOT NULL, + "description" varchar, + "category" "enum_favorites_category" NOT NULL, + "subcategory" varchar, + "price" numeric, + "price_range" "enum_favorites_price_range", + "affiliate_url" varchar NOT NULL, + "affiliate_network" "enum_favorites_affiliate_network", + "image_id" integer, + "badge" "enum_favorites_badge", + "featured" boolean DEFAULT false, + "is_active" boolean NOT NULL DEFAULT true, + "order" numeric DEFAULT 0, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL + ); + + -- Add unique constraint for slug + DO $$ BEGIN + ALTER TABLE "favorites" ADD CONSTRAINT "favorites_slug_idx" UNIQUE ("slug"); + EXCEPTION + WHEN duplicate_table THEN null; + WHEN duplicate_object THEN null; + END $$; + + -- Foreign key for tenant + DO $$ BEGIN + ALTER TABLE "favorites" ADD CONSTRAINT "favorites_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + -- Foreign key for image + DO $$ BEGIN + ALTER TABLE "favorites" ADD CONSTRAINT "favorites_image_id_media_id_fk" FOREIGN KEY ("image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + -- Indexes + CREATE INDEX IF NOT EXISTS "favorites_tenant_idx" ON "favorites" USING btree ("tenant_id"); + CREATE INDEX IF NOT EXISTS "favorites_image_idx" ON "favorites" USING btree ("image_id"); + CREATE INDEX IF NOT EXISTS "favorites_created_at_idx" ON "favorites" USING btree ("created_at"); + + -- ============================================================ + -- FAVORITES_RELS TABLE (for polymorphic relations) + -- ============================================================ + CREATE TABLE IF NOT EXISTS "favorites_rels" ( + "id" serial PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" varchar NOT NULL, + "media_id" integer + ); + + DO $$ BEGIN + ALTER TABLE "favorites_rels" ADD CONSTRAINT "favorites_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."favorites"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + ALTER TABLE "favorites_rels" ADD CONSTRAINT "favorites_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + CREATE INDEX IF NOT EXISTS "favorites_rels_order_idx" ON "favorites_rels" USING btree ("order"); + CREATE INDEX IF NOT EXISTS "favorites_rels_parent_idx" ON "favorites_rels" USING btree ("parent_id"); + CREATE INDEX IF NOT EXISTS "favorites_rels_path_idx" ON "favorites_rels" USING btree ("path"); + CREATE INDEX IF NOT EXISTS "favorites_rels_media_idx" ON "favorites_rels" USING btree ("media_id"); + + -- ============================================================ + -- SERIES TABLE + -- ============================================================ + CREATE TABLE IF NOT EXISTS "series" ( + "id" serial PRIMARY KEY NOT NULL, + "tenant_id" integer, + "slug" varchar NOT NULL, + "logo_id" integer, + "cover_image_id" integer, + "brand_color" varchar, + "accent_color" varchar, + "youtube_playlist_id" varchar, + "youtube_playlist_url" varchar, + "order" numeric DEFAULT 0, + "is_active" boolean NOT NULL DEFAULT true, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL + ); + + -- Add unique constraint for slug + DO $$ BEGIN + ALTER TABLE "series" ADD CONSTRAINT "series_slug_idx" UNIQUE ("slug"); + EXCEPTION + WHEN duplicate_table THEN null; + WHEN duplicate_object THEN null; + END $$; + + -- Foreign keys + DO $$ BEGIN + ALTER TABLE "series" ADD CONSTRAINT "series_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + ALTER TABLE "series" ADD CONSTRAINT "series_logo_id_media_id_fk" FOREIGN KEY ("logo_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + ALTER TABLE "series" ADD CONSTRAINT "series_cover_image_id_media_id_fk" FOREIGN KEY ("cover_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + -- Indexes + CREATE INDEX IF NOT EXISTS "series_tenant_idx" ON "series" USING btree ("tenant_id"); + CREATE INDEX IF NOT EXISTS "series_logo_idx" ON "series" USING btree ("logo_id"); + CREATE INDEX IF NOT EXISTS "series_cover_image_idx" ON "series" USING btree ("cover_image_id"); + CREATE INDEX IF NOT EXISTS "series_created_at_idx" ON "series" USING btree ("created_at"); + + -- ============================================================ + -- SERIES_LOCALES TABLE (for localized fields) + -- ============================================================ + CREATE TABLE IF NOT EXISTS "series_locales" ( + "title" varchar NOT NULL, + "tagline" varchar, + "description" jsonb, + "id" serial PRIMARY KEY NOT NULL, + "_locale" "_locales" NOT NULL, + "_parent_id" integer NOT NULL + ); + + DO $$ BEGIN + ALTER TABLE "series_locales" ADD CONSTRAINT "series_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."series"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + -- Unique constraint for parent + locale combination + DO $$ BEGIN + ALTER TABLE "series_locales" ADD CONSTRAINT "series_locales_locale_parent_id_unique" UNIQUE ("_locale", "_parent_id"); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + -- ============================================================ + -- SERIES_RELS TABLE (for polymorphic relations) + -- ============================================================ + CREATE TABLE IF NOT EXISTS "series_rels" ( + "id" serial PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" varchar NOT NULL, + "media_id" integer + ); + + DO $$ BEGIN + ALTER TABLE "series_rels" ADD CONSTRAINT "series_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."series"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + DO $$ BEGIN + ALTER TABLE "series_rels" ADD CONSTRAINT "series_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + + CREATE INDEX IF NOT EXISTS "series_rels_order_idx" ON "series_rels" USING btree ("order"); + CREATE INDEX IF NOT EXISTS "series_rels_parent_idx" ON "series_rels" USING btree ("parent_id"); + CREATE INDEX IF NOT EXISTS "series_rels_path_idx" ON "series_rels" USING btree ("path"); + CREATE INDEX IF NOT EXISTS "series_rels_media_idx" ON "series_rels" USING btree ("media_id"); + + `) +} + +export async function down({ db, payload, req }: MigrateDownArgs): Promise { + await db.execute(sql` + + -- Drop tables in reverse order of dependencies + DROP TABLE IF EXISTS "series_rels"; + DROP TABLE IF EXISTS "series_locales"; + DROP TABLE IF EXISTS "series"; + DROP TABLE IF EXISTS "favorites_rels"; + DROP TABLE IF EXISTS "favorites"; + + -- Drop enums + DROP TYPE IF EXISTS "enum_favorites_category"; + DROP TYPE IF EXISTS "enum_favorites_price_range"; + DROP TYPE IF EXISTS "enum_favorites_affiliate_network"; + DROP TYPE IF EXISTS "enum_favorites_badge"; + + `) +} diff --git a/src/migrations/index.ts b/src/migrations/index.ts index ec9d579..3cf0b22 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -20,6 +20,7 @@ import * as migration_20251214_000000_add_priority_collections from './20251214_ import * as migration_20251214_010000_tenant_specific_collections from './20251214_010000_tenant_specific_collections'; import * as migration_20251216_073000_add_video_collections from './20251216_073000_add_video_collections'; import * as migration_20251216_080000_posts_featured_video_processed_fields from './20251216_080000_posts_featured_video_processed_fields'; +import * as migration_20260108_160000_add_blogwoman_collections from './20260108_160000_add_blogwoman_collections'; export const migrations = [ { @@ -132,4 +133,9 @@ export const migrations = [ down: migration_20251216_080000_posts_featured_video_processed_fields.down, name: '20251216_080000_posts_featured_video_processed_fields', }, + { + up: migration_20260108_160000_add_blogwoman_collections.up, + down: migration_20260108_160000_add_blogwoman_collections.down, + name: '20260108_160000_add_blogwoman_collections', + }, ]; diff --git a/src/payload.config.ts b/src/payload.config.ts index ca4a8ab..21b4f77 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -226,7 +226,7 @@ export default buildConfig({ pool: { connectionString: env.DATABASE_URI, }, - // push: false - Schema manuell erstellt + // push: false - Migrationen für Schema-Änderungen verwenden push: false, }), // Sharp für Bildoptimierung