Packages
phoenix_kit
1.7.121
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
Current section
Files
lib/phoenix_kit/migrations/postgres/v74.ex
defmodule PhoenixKit.Migrations.Postgres.V74 do
@moduledoc """
V74: Drop integer `id`/`_id` columns, promote `uuid` to PK on Category B tables.
V72 renamed `id` → `uuid` on 30 Category A tables. V73 fixed prerequisites
(NOT NULL, unique indexes, dynamic PK code). V74 completes the migration by:
1. **Drop all FK constraints** referencing `id` on `phoenix_kit_%` tables
2. **Drop integer FK columns** across all tables (both Category A and B)
3. **Drop bigint `id` + make `uuid` PK** on Category B tables
After V74, every PhoenixKit table has `uuid` as its PK column. No integer
`id` or `_id` columns remain.
All operations are idempotent (IF EXISTS / dynamic introspection guards).
"""
use Ecto.Migration
# ── Integer FK columns to drop ──────────────────────────────────────
# Comprehensive list from uuid_fk_columns.ex groups A–D plus extras.
# These exist on both Category A and Category B tables.
# Format: {table, column}
@integer_fk_columns [
# ── Group A: User FK columns (→ phoenix_kit_users.id) ──
{"phoenix_kit_users_tokens", "user_id"},
{"phoenix_kit_user_role_assignments", "user_id"},
{"phoenix_kit_user_role_assignments", "assigned_by"},
{"phoenix_kit_admin_notes", "user_id"},
{"phoenix_kit_admin_notes", "author_id"},
{"phoenix_kit_user_oauth_providers", "user_id"},
{"phoenix_kit_audit_logs", "target_user_id"},
{"phoenix_kit_audit_logs", "admin_user_id"},
{"phoenix_kit_role_permissions", "granted_by"},
{"phoenix_kit_comments", "user_id"},
{"phoenix_kit_comments_likes", "user_id"},
{"phoenix_kit_comments_dislikes", "user_id"},
{"phoenix_kit_posts", "user_id"},
{"phoenix_kit_post_comments", "user_id"},
{"phoenix_kit_post_likes", "user_id"},
{"phoenix_kit_post_dislikes", "user_id"},
{"phoenix_kit_post_views", "user_id"},
{"phoenix_kit_post_mentions", "user_id"},
{"phoenix_kit_post_groups", "user_id"},
{"phoenix_kit_comment_likes", "user_id"},
{"phoenix_kit_comment_dislikes", "user_id"},
{"phoenix_kit_tickets", "user_id"},
{"phoenix_kit_tickets", "assigned_to_id"},
{"phoenix_kit_ticket_comments", "user_id"},
{"phoenix_kit_ticket_status_history", "changed_by_id"},
{"phoenix_kit_user_blocks", "blocker_id"},
{"phoenix_kit_user_blocks", "blocked_id"},
{"phoenix_kit_user_blocks_history", "blocker_id"},
{"phoenix_kit_user_blocks_history", "blocked_id"},
{"phoenix_kit_user_follows", "follower_id"},
{"phoenix_kit_user_follows", "followed_id"},
{"phoenix_kit_user_follows_history", "follower_id"},
{"phoenix_kit_user_follows_history", "followed_id"},
{"phoenix_kit_user_connections", "requester_id"},
{"phoenix_kit_user_connections", "recipient_id"},
{"phoenix_kit_user_connections_history", "user_a_id"},
{"phoenix_kit_user_connections_history", "user_b_id"},
{"phoenix_kit_user_connections_history", "actor_id"},
{"phoenix_kit_files", "user_id"},
{"phoenix_kit_shop_carts", "user_id"},
{"phoenix_kit_shop_products", "created_by"},
{"phoenix_kit_shop_import_logs", "user_id"},
{"phoenix_kit_billing_profiles", "user_id"},
{"phoenix_kit_orders", "user_id"},
{"phoenix_kit_invoices", "user_id"},
{"phoenix_kit_transactions", "user_id"},
{"phoenix_kit_subscriptions", "user_id"},
{"phoenix_kit_payment_methods", "user_id"},
{"phoenix_kit_ai_requests", "user_id"},
{"phoenix_kit_sync_connections", "approved_by"},
{"phoenix_kit_sync_connections", "suspended_by"},
{"phoenix_kit_sync_connections", "revoked_by"},
{"phoenix_kit_sync_connections", "created_by"},
{"phoenix_kit_sync_transfers", "approved_by"},
{"phoenix_kit_sync_transfers", "denied_by"},
{"phoenix_kit_sync_transfers", "initiated_by"},
{"phoenix_kit_entities", "created_by"},
{"phoenix_kit_entity_data", "created_by"},
{"phoenix_kit_email_logs", "user_id"},
{"phoenix_kit_email_blocklist", "user_id"},
{"phoenix_kit_email_templates", "created_by_user_id"},
{"phoenix_kit_email_templates", "updated_by_user_id"},
{"phoenix_kit_referral_codes", "created_by"},
{"phoenix_kit_referral_codes", "beneficiary"},
{"phoenix_kit_referral_code_usage", "used_by"},
{"phoenix_kit_consent_logs", "user_id"},
# ── Group B: Role FK columns (→ phoenix_kit_user_roles.id) ──
{"phoenix_kit_user_role_assignments", "role_id"},
{"phoenix_kit_role_permissions", "role_id"},
# ── Group C: Entity FK columns (→ phoenix_kit_entities.id) ──
{"phoenix_kit_entity_data", "entity_id"},
# ── Group D: Module-internal FK columns ──
{"phoenix_kit_shop_cart_items", "cart_id"},
{"phoenix_kit_shop_cart_items", "product_id"},
{"phoenix_kit_shop_carts", "shipping_method_id"},
{"phoenix_kit_shop_carts", "merged_into_cart_id"},
{"phoenix_kit_shop_carts", "payment_option_id"},
{"phoenix_kit_shop_products", "category_id"},
{"phoenix_kit_shop_categories", "parent_id"},
{"phoenix_kit_shop_categories", "featured_product_id"},
{"phoenix_kit_orders", "billing_profile_id"},
{"phoenix_kit_invoices", "order_id"},
{"phoenix_kit_transactions", "invoice_id"},
{"phoenix_kit_subscriptions", "subscription_type_id"},
{"phoenix_kit_subscriptions", "billing_profile_id"},
{"phoenix_kit_subscriptions", "payment_method_id"},
{"phoenix_kit_email_events", "email_log_id"},
{"phoenix_kit_ai_requests", "endpoint_id"},
{"phoenix_kit_ai_requests", "prompt_id"},
{"phoenix_kit_sync_transfers", "connection_id"},
{"phoenix_kit_referral_code_usage", "code_id"},
# ── Extras: columns not in uuid_fk_columns.ex ──
{"phoenix_kit_invoices", "subscription_id"},
{"phoenix_kit_email_orphaned_events", "matched_email_log_id"},
{"phoenix_kit_publishing_posts", "created_by_id"},
{"phoenix_kit_publishing_posts", "updated_by_id"},
{"phoenix_kit_publishing_versions", "created_by_id"},
{"phoenix_kit_shop_cart_items", "variant_id"},
{"phoenix_kit_scheduled_jobs", "created_by_id"},
{"phoenix_kit_ai_requests", "account_id"},
# Additional columns found in schemas but not in uuid_fk_columns.ex
{"phoenix_kit_posts", "author_id"},
{"phoenix_kit_tickets", "created_by_user_id"},
{"phoenix_kit_ticket_attachments", "user_id"},
{"phoenix_kit_transactions", "order_id"},
{"phoenix_kit_transactions", "payment_method_id"}
]
# ── Category B tables: drop bigint `id`, make `uuid` PK ────────────
# These tables still have bigint `id` PK + separate `uuid` column.
# Category A (30 tables) were handled by V72 (id renamed to uuid).
# Idempotent: checks for `uuid` column existence before proceeding.
@category_b_tables ~w(
phoenix_kit_users
phoenix_kit_users_tokens
phoenix_kit_user_roles
phoenix_kit_user_role_assignments
phoenix_kit_role_permissions
phoenix_kit_settings
phoenix_kit_admin_notes
phoenix_kit_audit_logs
phoenix_kit_email_logs
phoenix_kit_email_events
phoenix_kit_email_metrics
phoenix_kit_email_templates
phoenix_kit_email_blocklist
phoenix_kit_email_orphaned_events
phoenix_kit_ai_accounts
phoenix_kit_ai_endpoints
phoenix_kit_ai_prompts
phoenix_kit_ai_requests
phoenix_kit_entities
phoenix_kit_entity_data
phoenix_kit_user_oauth_providers
phoenix_kit_consent_logs
phoenix_kit_referral_codes
phoenix_kit_referral_code_usage
phoenix_kit_billing_profiles
phoenix_kit_orders
phoenix_kit_invoices
phoenix_kit_transactions
phoenix_kit_payment_methods
phoenix_kit_payment_options
phoenix_kit_payment_provider_configs
phoenix_kit_subscriptions
phoenix_kit_subscription_types
phoenix_kit_currencies
phoenix_kit_shop_carts
phoenix_kit_shop_cart_items
phoenix_kit_shop_products
phoenix_kit_shop_categories
phoenix_kit_shop_shipping_methods
phoenix_kit_shop_config
phoenix_kit_shop_import_configs
phoenix_kit_shop_import_logs
phoenix_kit_sync_connections
phoenix_kit_sync_transfers
phoenix_kit_webhook_events
phoenix_kit_publishing_posts
phoenix_kit_publishing_versions
)
def up(%{prefix: prefix} = opts) do
escaped_prefix = Map.get(opts, :escaped_prefix, prefix)
flush()
# Step 1: Drop all FK constraints referencing `id` on phoenix_kit_% tables
drop_id_fk_constraints(prefix, escaped_prefix)
# Step 2: Drop integer FK columns
drop_integer_fk_columns(prefix, escaped_prefix)
# Step 3: Drop bigint `id` PK + make `uuid` PK on Category B tables
promote_uuid_to_pk(prefix, escaped_prefix)
execute("COMMENT ON TABLE #{prefix_table("phoenix_kit", prefix)} IS '74'")
end
def down(%{prefix: prefix} = _opts) do
# V74 is destructive — columns and data are dropped.
# Down migration only restores the version comment.
# To fully reverse, restore from backup.
execute("COMMENT ON TABLE #{prefix_table("phoenix_kit", prefix)} IS '73'")
end
# ---------------------------------------------------------------------------
# Step 1: Drop all FK constraints referencing `id`
# ---------------------------------------------------------------------------
defp drop_id_fk_constraints(_prefix, escaped_prefix) do
# Dynamic query: find and drop all FK constraints where the referenced
# column is `id` on phoenix_kit_% tables.
execute("""
DO $$ DECLARE r RECORD; BEGIN
FOR r IN
SELECT tc.constraint_name, tc.table_schema, tc.table_name
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu
USING (constraint_schema, constraint_name)
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = '#{escaped_prefix}'
AND ccu.table_name LIKE 'phoenix_kit_%'
AND ccu.column_name = 'id'
LOOP
EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT IF EXISTS %I',
r.table_schema, r.table_name, r.constraint_name);
END LOOP;
END $$;
""")
end
# ---------------------------------------------------------------------------
# Step 2: Drop integer FK columns
# ---------------------------------------------------------------------------
defp drop_integer_fk_columns(prefix, escaped_prefix) do
for {table, column} <- @integer_fk_columns do
if table_exists?(table, escaped_prefix) do
table_name = prefix_table(table, prefix)
execute("ALTER TABLE #{table_name} DROP COLUMN IF EXISTS #{column}")
end
end
end
# ---------------------------------------------------------------------------
# Step 3: Drop bigint `id` + make `uuid` PK
# ---------------------------------------------------------------------------
defp promote_uuid_to_pk(prefix, escaped_prefix) do
for table <- @category_b_tables do
if table_exists?(table, escaped_prefix) and
column_exists?(table, "uuid", escaped_prefix) and
column_exists?(table, "id", escaped_prefix) do
table_name = prefix_table(table, prefix)
# Drop the bigint id column. CASCADE ensures the PK constraint and
# its associated sequence are dropped along with the column, plus
# any FK constraints from other tables that depend on the PK index.
execute("ALTER TABLE #{table_name} DROP COLUMN id CASCADE")
# Promote uuid to PK (only if not already a PK)
execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE table_name = '#{table}'
AND table_schema = '#{escaped_prefix}'
AND constraint_type = 'PRIMARY KEY'
) THEN
ALTER TABLE #{table_name} ADD PRIMARY KEY (uuid);
END IF;
END $$;
""")
end
end
end
# ---------------------------------------------------------------------------
# Introspection Helpers
# ---------------------------------------------------------------------------
defp table_exists?(table, escaped_prefix) do
case repo().query(
"""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = '#{table}'
AND table_schema = '#{escaped_prefix}'
)
""",
[],
log: false
) do
{:ok, %{rows: [[true]]}} -> true
_ -> false
end
end
defp column_exists?(table, column, escaped_prefix) do
case repo().query(
"""
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = '#{table}'
AND column_name = '#{column}'
AND table_schema = '#{escaped_prefix}'
)
""",
[],
log: false
) do
{:ok, %{rows: [[true]]}} -> true
_ -> false
end
end
defp prefix_table(table_name, nil), do: table_name
defp prefix_table(table_name, "public"), do: "public.#{table_name}"
defp prefix_table(table_name, prefix), do: "#{prefix}.#{table_name}"
end