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 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-01-08 15:47:40 +00:00
parent 49d317fc84
commit ba1fc6eb00
3 changed files with 254 additions and 1 deletions

View file

@ -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<void> {
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<void> {
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";
`)
}

View file

@ -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_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_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_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 = [ export const migrations = [
{ {
@ -132,4 +133,9 @@ export const migrations = [
down: migration_20251216_080000_posts_featured_video_processed_fields.down, down: migration_20251216_080000_posts_featured_video_processed_fields.down,
name: '20251216_080000_posts_featured_video_processed_fields', 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',
},
]; ];

View file

@ -226,7 +226,7 @@ export default buildConfig({
pool: { pool: {
connectionString: env.DATABASE_URI, connectionString: env.DATABASE_URI,
}, },
// push: false - Schema manuell erstellt // push: false - Migrationen für Schema-Änderungen verwenden
push: false, push: false,
}), }),
// Sharp für Bildoptimierung // Sharp für Bildoptimierung