Packages

phoenix_kit

1.7.39
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit migrations postgres v51.ex
Raw

lib/phoenix_kit/migrations/postgres/v51.ex

defmodule PhoenixKit.Migrations.Postgres.V51 do
@moduledoc """
V51: Cart items unique constraint fix + User deletion FK constraints
## Part 1: Cart Items Unique Constraint
The original constraint only checked (cart_id, product_id), preventing
users from adding the same product with different options to their cart.
New constraint uses MD5 hash of selected_specs JSONB for efficient
unique checking across all option combinations.
## Part 2: User Deletion Foreign Key Constraints
Changes ON DELETE behavior for user-related tables to support GDPR
compliance - preserve financial/support records while allowing user deletion.
## Changes
- Drops existing idx_shop_cart_items_unique index
- Creates new unique index including MD5 hash of selected_specs
- orders.user_id: RESTRICT → SET NULL (preserve orders, anonymize user)
- billing_profiles.user_id: CASCADE → SET NULL (preserve for history)
- tickets.user_id: DELETE_ALL → SET NULL (preserve support history)
- orders.user_id: DROP NOT NULL constraint
- billing_profiles.user_id: DROP NOT NULL constraint
- tickets.user_id: DROP NOT NULL constraint
"""
use Ecto.Migration
def up(%{prefix: prefix} = _opts) do
prefix_str = if prefix && prefix != "public", do: "#{prefix}.", else: ""
# Drop old index that doesn't include selected_specs
execute """
DROP INDEX IF EXISTS #{prefix_str}idx_shop_cart_items_unique
"""
# Create new index that includes selected_specs via MD5 hash
# MD5 provides consistent hashing of JSONB for unique comparison
execute """
CREATE UNIQUE INDEX idx_shop_cart_items_unique
ON #{prefix_str}phoenix_kit_shop_cart_items(
cart_id,
product_id,
MD5(COALESCE(selected_specs::text, '{}'))
)
WHERE variant_id IS NULL
"""
# ===========================================
# Part 2: Fix User Deletion Foreign Key Constraints
# ===========================================
# 2.1 ORDERS: RESTRICT → SET NULL (only if table exists)
execute """
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '#{if prefix && prefix != "public", do: prefix, else: "public"}' AND table_name = 'phoenix_kit_orders') THEN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_orders_user_id_fkey'
AND conrelid = '#{prefix_str}phoenix_kit_orders'::regclass
) THEN
ALTER TABLE #{prefix_str}phoenix_kit_orders
DROP CONSTRAINT phoenix_kit_orders_user_id_fkey;
END IF;
ALTER TABLE #{prefix_str}phoenix_kit_orders
ADD CONSTRAINT phoenix_kit_orders_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_str}phoenix_kit_users(id)
ON DELETE SET NULL;
ALTER TABLE #{prefix_str}phoenix_kit_orders ALTER COLUMN user_id DROP NOT NULL;
END IF;
END $$;
"""
# 2.2 BILLING PROFILES: CASCADE → SET NULL (only if table exists)
execute """
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '#{if prefix && prefix != "public", do: prefix, else: "public"}' AND table_name = 'phoenix_kit_billing_profiles') THEN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_billing_profiles_user_id_fkey'
AND conrelid = '#{prefix_str}phoenix_kit_billing_profiles'::regclass
) THEN
ALTER TABLE #{prefix_str}phoenix_kit_billing_profiles
DROP CONSTRAINT phoenix_kit_billing_profiles_user_id_fkey;
END IF;
ALTER TABLE #{prefix_str}phoenix_kit_billing_profiles
ADD CONSTRAINT phoenix_kit_billing_profiles_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_str}phoenix_kit_users(id)
ON DELETE SET NULL;
ALTER TABLE #{prefix_str}phoenix_kit_billing_profiles ALTER COLUMN user_id DROP NOT NULL;
END IF;
END $$;
"""
# 2.3 TICKETS: DELETE_ALL → SET NULL (only if table exists)
execute """
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '#{if prefix && prefix != "public", do: prefix, else: "public"}' AND table_name = 'phoenix_kit_tickets') THEN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_tickets_user_id_fkey'
AND conrelid = '#{prefix_str}phoenix_kit_tickets'::regclass
) THEN
ALTER TABLE #{prefix_str}phoenix_kit_tickets
DROP CONSTRAINT phoenix_kit_tickets_user_id_fkey;
END IF;
ALTER TABLE #{prefix_str}phoenix_kit_tickets
ADD CONSTRAINT phoenix_kit_tickets_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_str}phoenix_kit_users(id)
ON DELETE SET NULL;
ALTER TABLE #{prefix_str}phoenix_kit_tickets ALTER COLUMN user_id DROP NOT NULL;
END IF;
END $$;
"""
# Record migration version
execute "COMMENT ON TABLE #{prefix_str}phoenix_kit IS '51'"
end
def down(%{prefix: prefix} = _opts) do
prefix_str = if prefix && prefix != "public", do: "#{prefix}.", else: ""
# ===========================================
# Revert FK Constraints
# ===========================================
# Revert ORDERS: SET NULL → RESTRICT (only if table exists)
execute """
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '#{if prefix && prefix != "public", do: prefix, else: "public"}' AND table_name = 'phoenix_kit_orders') THEN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_orders_user_id_fkey'
AND conrelid = '#{prefix_str}phoenix_kit_orders'::regclass
) THEN
ALTER TABLE #{prefix_str}phoenix_kit_orders
DROP CONSTRAINT phoenix_kit_orders_user_id_fkey;
END IF;
ALTER TABLE #{prefix_str}phoenix_kit_orders
ADD CONSTRAINT phoenix_kit_orders_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_str}phoenix_kit_users(id)
ON DELETE RESTRICT;
ALTER TABLE #{prefix_str}phoenix_kit_orders ALTER COLUMN user_id SET NOT NULL;
END IF;
END $$;
"""
# Revert BILLING PROFILES: SET NULL → CASCADE (only if table exists)
execute """
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '#{if prefix && prefix != "public", do: prefix, else: "public"}' AND table_name = 'phoenix_kit_billing_profiles') THEN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_billing_profiles_user_id_fkey'
AND conrelid = '#{prefix_str}phoenix_kit_billing_profiles'::regclass
) THEN
ALTER TABLE #{prefix_str}phoenix_kit_billing_profiles
DROP CONSTRAINT phoenix_kit_billing_profiles_user_id_fkey;
END IF;
ALTER TABLE #{prefix_str}phoenix_kit_billing_profiles
ADD CONSTRAINT phoenix_kit_billing_profiles_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_str}phoenix_kit_users(id)
ON DELETE CASCADE;
ALTER TABLE #{prefix_str}phoenix_kit_billing_profiles ALTER COLUMN user_id SET NOT NULL;
END IF;
END $$;
"""
# Revert TICKETS: SET NULL → CASCADE (only if table exists)
execute """
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '#{if prefix && prefix != "public", do: prefix, else: "public"}' AND table_name = 'phoenix_kit_tickets') THEN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_tickets_user_id_fkey'
AND conrelid = '#{prefix_str}phoenix_kit_tickets'::regclass
) THEN
ALTER TABLE #{prefix_str}phoenix_kit_tickets
DROP CONSTRAINT phoenix_kit_tickets_user_id_fkey;
END IF;
ALTER TABLE #{prefix_str}phoenix_kit_tickets
ADD CONSTRAINT phoenix_kit_tickets_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_str}phoenix_kit_users(id)
ON DELETE CASCADE;
ALTER TABLE #{prefix_str}phoenix_kit_tickets ALTER COLUMN user_id SET NOT NULL;
END IF;
END $$;
"""
# ===========================================
# Revert Cart Items Index
# ===========================================
# Restore original index (will fail if duplicate cart_id+product_id exist)
execute """
DROP INDEX IF EXISTS #{prefix_str}idx_shop_cart_items_unique
"""
execute """
CREATE UNIQUE INDEX idx_shop_cart_items_unique
ON #{prefix_str}phoenix_kit_shop_cart_items(cart_id, product_id)
WHERE variant_id IS NULL
"""
# Record migration version
execute "COMMENT ON TABLE #{prefix_str}phoenix_kit IS '50'"
end
end