Packages
phoenix_kit
1.7.76
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/v08.ex
defmodule PhoenixKit.Migrations.Postgres.V08 do
@moduledoc """
PhoenixKit V08 Migration: Username Support
This migration adds username functionality to the user system, allowing users
to have unique usernames in addition to their email addresses.
## Changes
### Username System
- Adds username field to phoenix_kit_users table
- Creates unique constraint on username field
- Migrates existing users to have default usernames based on email
- Provides username validation and generation logic
### New Features
- **Username Field**: Optional unique username for each user
- **Email-based Generation**: Automatically generates usernames from email addresses
- **Uniqueness Handling**: Handles duplicate usernames with incremental suffixes
- **Backwards Compatibility**: Existing functionality remains unchanged
## PostgreSQL Support
- Supports PostgreSQL prefix for schema isolation
- Optimized indexes for username lookups
- Safe migration of existing user data
"""
use Ecto.Migration
alias Ecto.Adapters.SQL
alias PhoenixKit.RepoHelper
@doc """
Run the V08 migration to add username support.
"""
def up(%{prefix: prefix} = _opts) do
# Add username field to users table using Ecto's alter (should work within transaction)
alter table(:phoenix_kit_users, prefix: prefix) do
add :username, :string, null: true
end
# Force the DDL to be executed before proceeding with data changes
flush()
# Migrate existing users to have usernames based on email
migrate_existing_users_to_have_usernames(prefix)
# Create unique index on username (for non-null values only) - after data is populated
create_if_not_exists unique_index(:phoenix_kit_users, [:username],
prefix: prefix,
name: :phoenix_kit_users_username_uidx,
where: "username IS NOT NULL"
)
# Set version comment on phoenix_kit table for version tracking
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '8'"
end
# Migrate existing users to have default usernames generated from their email addresses.
defp migrate_existing_users_to_have_usernames(prefix) do
# Get all users (since username column was just added, all will be NULL)
users_query = """
SELECT id, email
FROM #{prefix_table_name("phoenix_kit_users", prefix)}
ORDER BY id ASC
"""
{:ok, result} = SQL.query(RepoHelper.repo(), users_query, [])
users = Enum.map(result.rows, fn [id, email] -> {id, email} end)
# Generate and assign usernames with a simple counter for duplicates
{_final_used_set, _} =
Enum.reduce(users, {MapSet.new(), 0}, fn {user_id, email}, {used_usernames, _count} ->
base_username = generate_base_username_from_email(email)
username = ensure_unique_username_simple(base_username, used_usernames)
# Update the database
update_query = """
UPDATE #{prefix_table_name("phoenix_kit_users", prefix)}
SET username = $1
WHERE id = $2
"""
SQL.query!(PhoenixKit.RepoHelper.repo(), update_query, [username, user_id])
# Return updated used_usernames set for next iteration
{MapSet.put(used_usernames, username), 0}
end)
end
# Generate base username from an email address without uniqueness checking.
defp generate_base_username_from_email(email) do
email
|> String.split("@")
|> List.first()
|> String.downcase()
# Remove dots and make valid username format
|> String.replace(".", "_")
# Ensure it starts with a letter and contains only valid chars
|> clean_username()
end
# Clean username to ensure it meets validation rules.
defp clean_username(username) do
# Remove any invalid characters and ensure it starts with a letter
cleaned =
username
|> String.replace(~r/[^a-zA-Z0-9_]/, "")
# Max length
|> String.slice(0, 30)
# Ensure it starts with a letter
case String.match?(cleaned, ~r/^[a-zA-Z]/) do
true -> cleaned
# Leave room for "user_" prefix
false -> "user_" <> String.slice(cleaned, 0, 25)
end
|> ensure_minimum_length()
end
# Ensure username meets minimum length requirement.
defp ensure_minimum_length(username) when byte_size(username) >= 3, do: username
defp ensure_minimum_length(username), do: username <> "_1"
# Simple uniqueness check using in-memory set (for migration only).
defp ensure_unique_username_simple(base_username, used_usernames, attempt \\ 0) do
candidate =
case attempt do
0 -> base_username
n -> "#{base_username}_#{n}"
end
if MapSet.member?(used_usernames, candidate) do
ensure_unique_username_simple(base_username, used_usernames, attempt + 1)
else
candidate
end
end
# Helper function to build table name with prefix
defp prefix_table_name(table_name, nil), do: table_name
defp prefix_table_name(table_name, prefix), do: "#{prefix}.#{table_name}"
def down(%{prefix: prefix} = _opts) do
# Drop the unique index on username
drop_if_exists index(:phoenix_kit_users, [:username],
prefix: prefix,
name: :phoenix_kit_users_username_uidx
)
# Remove username field from users table
alter table(:phoenix_kit_users, prefix: prefix) do
remove :username
end
# Set version comment back to V07
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '7'"
end
end