Packages
phoenix_kit
1.7.25
1.7.211
1.7.210
1.7.209
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/uuid_repair.ex
defmodule PhoenixKit.Migrations.UUIDRepair do
@moduledoc """
Repairs missing UUID columns for databases upgrading from PhoenixKit < 1.7.0.
## Problem
PhoenixKit 1.7.x added UUID columns to all legacy tables in migration V40.
However, some migrations before V40 (e.g., V31) use Ecto schemas that expect
the `uuid` column to exist. This creates a chicken-and-egg problem for users
upgrading from older versions.
## Solution
This module detects the condition and adds UUID columns BEFORE running
migrations, ensuring V31+ can use Ecto schemas without errors.
## When It Runs
Called automatically by `mix phoenix_kit.update` when:
1. Current database version is < 40
2. Tables exist but are missing uuid columns
## Tables Affected
Only repairs tables that:
1. Exist in the database
2. Are missing the uuid column
3. Are used by Ecto schemas in migrations before V40
Primary tables:
- phoenix_kit_settings (used by V35, V36)
- phoenix_kit_email_templates (used by V31)
"""
require Logger
@tables_needing_repair [
:phoenix_kit_settings,
:phoenix_kit_email_templates
]
@doc """
Checks if UUID repair is needed and performs it if necessary.
Returns:
- `{:ok, :not_needed}` - No repair needed (fresh install or already has uuid)
- `{:ok, :repaired}` - Repair was performed successfully
- `{:error, reason}` - Repair failed
"""
def maybe_repair(opts \\ []) do
prefix = Keyword.get(opts, :prefix, "public")
with {:ok, repo} <- get_repo(),
{:ok, version} <- get_current_version(repo, prefix),
{:ok, needs_repair} <- check_needs_repair(repo, prefix, version) do
if needs_repair do
perform_repair(repo, prefix)
else
{:ok, :not_needed}
end
end
end
@doc """
Checks if repair is needed without performing it.
Useful for dry-run or status checks.
"""
def needs_repair?(opts \\ []) do
prefix = Keyword.get(opts, :prefix, "public")
with {:ok, repo} <- get_repo(),
{:ok, version} <- get_current_version(repo, prefix) do
check_needs_repair(repo, prefix, version)
end
end
## Private Functions
defp get_repo do
case PhoenixKit.RepoHelper.repo() do
nil -> {:error, :no_repo_configured}
repo -> {:ok, repo}
end
end
defp get_current_version(repo, prefix) do
query = """
SELECT obj_description('#{prefix}.phoenix_kit'::regclass, 'pg_class')::integer
"""
case repo.query(query, [], log: false) do
{:ok, %{rows: [[version]]}} when is_integer(version) ->
{:ok, version}
{:ok, %{rows: [[nil]]}} ->
# No version comment - treat as very old
{:ok, 0}
{:error, %Postgrex.Error{postgres: %{code: :undefined_table}}} ->
# Table doesn't exist - fresh install, no repair needed
{:ok, :fresh_install}
{:error, reason} ->
{:error, {:version_check_failed, reason}}
end
end
defp check_needs_repair(_repo, _prefix, :fresh_install) do
# Fresh install - V15+ will create tables with uuid
{:ok, false}
end
defp check_needs_repair(_repo, _prefix, version) when version >= 40 do
# Already at V40+ - uuid columns should exist
{:ok, false}
end
defp check_needs_repair(repo, prefix, version) when version < 40 do
# Version < 40, need to check if tables are missing uuid
missing_uuid =
Enum.any?(@tables_needing_repair, fn table ->
table_exists?(repo, table, prefix) and not column_exists?(repo, table, :uuid, prefix)
end)
{:ok, missing_uuid}
end
defp perform_repair(repo, prefix) do
Logger.info("[PhoenixKit] Starting UUID column repair for upgrade compatibility...")
# First ensure pgcrypto extension exists
ensure_pgcrypto(repo)
# Repair each table that needs it
results =
Enum.map(@tables_needing_repair, fn table ->
repair_table(repo, table, prefix)
end)
errors = Enum.filter(results, &match?({:error, _}, &1))
if Enum.empty?(errors) do
repaired_count = Enum.count(results, &match?({:ok, :repaired}, &1))
Logger.info("[PhoenixKit] UUID repair complete. #{repaired_count} table(s) repaired.")
{:ok, :repaired}
else
{:error, {:repair_failed, errors}}
end
end
defp ensure_pgcrypto(repo) do
repo.query("CREATE EXTENSION IF NOT EXISTS pgcrypto", [], log: false)
end
defp repair_table(repo, table, prefix) do
table_name = prefix_table_name(Atom.to_string(table), prefix)
cond do
not table_exists?(repo, table, prefix) ->
# Table doesn't exist - skip (will be created properly by migrations)
{:ok, :skipped}
column_exists?(repo, table, :uuid, prefix) ->
# UUID column already exists - nothing to do
{:ok, :skipped}
true ->
# Need to add uuid column
Logger.info("[PhoenixKit] Adding uuid column to #{table_name}...")
add_uuid_query = """
ALTER TABLE #{table_name}
ADD COLUMN uuid UUID DEFAULT gen_random_uuid()
"""
backfill_query = """
UPDATE #{table_name}
SET uuid = gen_random_uuid()
WHERE uuid IS NULL
"""
with {:ok, _} <- repo.query(add_uuid_query, [], log: false),
{:ok, _} <- repo.query(backfill_query, [], log: false) do
Logger.info("[PhoenixKit] Successfully added uuid to #{table_name}")
{:ok, :repaired}
else
{:error, reason} ->
Logger.error("[PhoenixKit] Failed to add uuid to #{table_name}: #{inspect(reason)}")
{:error, {table, reason}}
end
end
end
defp table_exists?(repo, table, prefix) do
table_name = Atom.to_string(table)
query = """
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = $1
AND table_name = $2
)
"""
case repo.query(query, [prefix, table_name], log: false) do
{:ok, %{rows: [[true]]}} -> true
_ -> false
end
end
defp column_exists?(repo, table, column, prefix) do
table_name = Atom.to_string(table)
column_name = Atom.to_string(column)
query = """
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = $1
AND table_name = $2
AND column_name = $3
)
"""
case repo.query(query, [prefix, table_name, column_name], log: false) do
{:ok, %{rows: [[true]]}} -> true
_ -> false
end
end
defp prefix_table_name(table_name, "public"), do: "public.#{table_name}"
defp prefix_table_name(table_name, prefix), do: "#{prefix}.#{table_name}"
end