Packages
phoenix_kit
1.7.70
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/v67.ex
defmodule PhoenixKit.Migrations.Postgres.V67 do
@moduledoc """
V67: Make all remaining legacy NOT NULL integer FK columns nullable.
After the UUID cleanup (V56+), schemas only write `_uuid` foreign keys.
Many tables still have legacy integer FK columns with NOT NULL constraints,
causing inserts to fail with `not_null_violation`.
This migration makes all remaining affected columns nullable in one pass.
All operations are idempotent (guarded by table/column existence checks).
## Tables & Columns (42 total)
### Roles (3)
- user_role_assignments.user_id, user_role_assignments.role_id
- role_permissions.role_id
### Posts (3)
- posts.user_id
- comment_likes.user_id
- comment_dislikes.user_id
### Tickets (2)
- ticket_comments.user_id
- ticket_status_history.changed_by_id
### Storage (1)
- files.user_id
### Admin/Auth/Audit (5)
- admin_notes.user_id, admin_notes.author_id
- user_oauth_providers.user_id
- audit_logs.target_user_id, audit_logs.admin_user_id
### Connections (13)
- user_follows: follower_id, followed_id
- user_connections: requester_id, recipient_id
- user_blocks: blocker_id, blocked_id
- user_follows_history: follower_id, followed_id
- user_connections_history: user_a_id, user_b_id, actor_id
- user_blocks_history: blocker_id, blocked_id
### Billing (6)
- invoices.user_id
- transactions.user_id, transactions.invoice_id
- subscriptions.user_id, subscriptions.subscription_type_id (was plan_id)
- payment_methods.user_id
### Entities (3)
- entities.created_by
- entity_data.entity_id, entity_data.created_by
### Referrals (3)
- referral_codes.created_by
- referral_code_usage.code_id, referral_code_usage.used_by
### Standalone Comments (2)
- comments_likes.user_id
- comments_dislikes.user_id
### Shop (1)
- shop_cart_items.cart_id
"""
use Ecto.Migration
# {table_name, column_name}
@columns [
# Roles
{"phoenix_kit_user_role_assignments", "user_id"},
{"phoenix_kit_user_role_assignments", "role_id"},
{"phoenix_kit_role_permissions", "role_id"},
# Posts
{"phoenix_kit_posts", "user_id"},
{"phoenix_kit_comment_likes", "user_id"},
{"phoenix_kit_comment_dislikes", "user_id"},
# Tickets
{"phoenix_kit_ticket_comments", "user_id"},
{"phoenix_kit_ticket_status_history", "changed_by_id"},
# Storage
{"phoenix_kit_files", "user_id"},
# Admin / Auth / Audit
{"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"},
# Connections
{"phoenix_kit_user_follows", "follower_id"},
{"phoenix_kit_user_follows", "followed_id"},
{"phoenix_kit_user_connections", "requester_id"},
{"phoenix_kit_user_connections", "recipient_id"},
{"phoenix_kit_user_blocks", "blocker_id"},
{"phoenix_kit_user_blocks", "blocked_id"},
{"phoenix_kit_user_follows_history", "follower_id"},
{"phoenix_kit_user_follows_history", "followed_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_user_blocks_history", "blocker_id"},
{"phoenix_kit_user_blocks_history", "blocked_id"},
# Billing
{"phoenix_kit_invoices", "user_id"},
{"phoenix_kit_transactions", "user_id"},
{"phoenix_kit_transactions", "invoice_id"},
{"phoenix_kit_subscriptions", "user_id"},
{"phoenix_kit_payment_methods", "user_id"},
# Entities
{"phoenix_kit_entities", "created_by"},
{"phoenix_kit_entity_data", "entity_id"},
{"phoenix_kit_entity_data", "created_by"},
# Referrals
{"phoenix_kit_referral_codes", "created_by"},
{"phoenix_kit_referral_code_usage", "code_id"},
{"phoenix_kit_referral_code_usage", "used_by"},
# Standalone Comments
{"phoenix_kit_comments_likes", "user_id"},
{"phoenix_kit_comments_dislikes", "user_id"},
# Shop
{"phoenix_kit_shop_cart_items", "cart_id"}
]
# V65 renamed plan_id → subscription_type_id, but older installs may still
# have plan_id. Handle both names.
@subscription_type_columns [
{"phoenix_kit_subscriptions", "subscription_type_id"},
{"phoenix_kit_subscriptions", "plan_id"}
]
def up(%{prefix: prefix} = opts) do
escaped_prefix = Map.get(opts, :escaped_prefix, prefix)
flush()
for {table, column} <- @columns do
drop_not_null_if_exists(table, column, prefix, escaped_prefix)
end
# Handle subscription type column (could be either name)
for {table, column} <- @subscription_type_columns do
drop_not_null_if_exists(table, column, prefix, escaped_prefix)
end
execute("COMMENT ON TABLE #{prefix_table("phoenix_kit", prefix)} IS '67'")
end
def down(%{prefix: prefix} = opts) do
escaped_prefix = Map.get(opts, :escaped_prefix, prefix)
for {table, column} <- @subscription_type_columns do
set_not_null_if_exists(table, column, prefix, escaped_prefix)
end
for {table, column} <- Enum.reverse(@columns) do
set_not_null_if_exists(table, column, prefix, escaped_prefix)
end
execute("COMMENT ON TABLE #{prefix_table("phoenix_kit", prefix)} IS '66'")
end
defp drop_not_null_if_exists(table, column, prefix, escaped_prefix) do
if table_exists?(table, escaped_prefix) and
column_exists?(table, column, escaped_prefix) and
column_not_null?(table, column, escaped_prefix) do
execute("""
ALTER TABLE #{prefix_table(table, prefix)}
ALTER COLUMN #{column} DROP NOT NULL
""")
end
end
defp set_not_null_if_exists(table, column, prefix, escaped_prefix) do
if table_exists?(table, escaped_prefix) and
column_exists?(table, column, escaped_prefix) do
execute("""
ALTER TABLE #{prefix_table(table, prefix)}
ALTER COLUMN #{column} SET NOT NULL
""")
end
end
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 column_not_null?(table, column, escaped_prefix) do
case repo().query(
"""
SELECT is_nullable = 'NO'
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