Packages
phoenix_kit
1.7.21
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/modules/referrals/referrals.ex
defmodule PhoenixKit.Modules.Referrals do
@moduledoc """
Referral code system for PhoenixKit - complete management in a single module.
This module provides both the Ecto schema definition and business logic for
managing referral codes. It includes code creation, validation, usage tracking,
and system configuration.
## Schema Fields
- `code`: The referral code string (unique, required)
- `description`: Human-readable description of the code
- `status`: Boolean indicating if the code is active
- `number_of_uses`: Current number of times the code has been used
- `max_uses`: Maximum number of times the code can be used
- `created_by`: User ID of the admin who created the code
- `beneficiary`: User ID who benefits when this code is used (optional)
- `date_created`: When the code was created
- `expiration_date`: When the code expires
## Core Functions
### Code Management
- `list_codes/0` - Get all referral codes
- `get_code!/1` - Get a referral code by ID (raises if not found)
- `get_code_by_string/1` - Get a referral code by its string value
- `create_code/1` - Create a new referral code
- `update_code/2` - Update an existing referral code
- `delete_code/1` - Delete a referral code
- `generate_random_code/0` - Generate a random code string
### Usage Tracking
- `use_code/2` - Record usage of a referral code by a user
- `get_usage_stats/1` - Get usage statistics for a code
- `list_usage_for_code/1` - Get all usage records for a code
- `user_used_code?/2` - Check if user has used a specific code
### System Settings
- `enabled?/0` - Check if referral codes system is enabled
- `required?/0` - Check if referral codes are required for registration
- `enable_system/0` - Enable the referral codes system
- `disable_system/0` - Disable the referral codes system
- `set_required/1` - Set whether referral codes are required
## Usage Examples
# Check if system is enabled
if PhoenixKit.Modules.Referrals.enabled?() do
# System is active
end
# Create a new referral code
{:ok, code} = PhoenixKit.Modules.Referrals.create_code(%{
code: "WELCOME2024",
description: "Welcome promotion",
max_uses: 100,
created_by: admin_user.id,
expiration_date: ~U[2024-12-31 23:59:59.000000Z]
})
# Use a referral code during registration
case PhoenixKit.Modules.Referrals.use_code("WELCOME2024", user.id) do
{:ok, usage} -> # Code used successfully
{:error, reason} -> # Handle error
end
"""
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, warn: false
alias PhoenixKit.Modules.Referrals.ReferralCodeUsage
alias PhoenixKit.Settings
@primary_key {:id, :id, autogenerate: true}
schema "phoenix_kit_referral_codes" do
field :uuid, Ecto.UUID
field :code, :string
field :description, :string
field :status, :boolean, default: true
field :number_of_uses, :integer, default: 0
field :max_uses, :integer
field :created_by, :integer
field :beneficiary, :integer
field :date_created, :utc_datetime_usec
field :expiration_date, :utc_datetime_usec
belongs_to :creator, PhoenixKit.Users.Auth.User, foreign_key: :created_by, define_field: false
belongs_to :beneficiary_user, PhoenixKit.Users.Auth.User,
foreign_key: :beneficiary,
define_field: false
has_many :usage_records, ReferralCodeUsage, foreign_key: :code_id
end
## --- Schema Functions ---
@doc """
Creates a changeset for referral code creation and updates.
Validates that code is unique and all required fields are present.
Automatically sets date_created on new records.
"""
def changeset(referral_code, attrs) do
referral_code
|> cast(attrs, [
:code,
:description,
:status,
:number_of_uses,
:max_uses,
:created_by,
:beneficiary,
:date_created,
:expiration_date
])
|> validate_required([:code, :description, :max_uses])
|> validate_length(:code, min: 3, max: 50)
|> validate_length(:description, min: 1, max: 255)
|> validate_number(:max_uses, greater_than: 0)
|> validate_max_uses_limit()
|> validate_number(:number_of_uses, greater_than_or_equal_to: 0)
|> validate_code_uniqueness()
|> unique_constraint(:code)
|> validate_expiration_date()
|> maybe_set_date_created()
|> maybe_set_default_expiration()
|> maybe_generate_uuid()
end
defp maybe_generate_uuid(changeset) do
case get_field(changeset, :uuid) do
nil -> put_change(changeset, :uuid, UUIDv7.generate())
_ -> changeset
end
end
@doc """
Generates a random 5-character alphanumeric referral code.
Returns a string with uppercase letters and numbers, excluding
potentially confusing characters (0, O, I, 1).
## Examples
iex> PhoenixKit.Modules.Referrals.generate_random_code()
"A7B2K"
"""
def generate_random_code do
# Exclude confusing characters: 0, O, I, 1
chars = ~w(A B C D E F G H J K L M N P Q R S T U V W X Y Z 2 3 4 5 6 7 8 9)
chars
|> Enum.take_random(5)
|> Enum.join()
end
@doc """
Checks if a referral code is currently valid for use.
A code is valid if:
- It exists and is active (status: true)
- It has not exceeded its maximum uses
- It has not expired
## Examples
iex> PhoenixKit.Modules.Referrals.valid_for_use?(code)
true
"""
def valid_for_use?(%__MODULE__{} = code) do
code.status &&
code.number_of_uses < code.max_uses &&
(is_nil(code.expiration_date) ||
DateTime.compare(DateTime.utc_now(), code.expiration_date) == :lt)
end
@doc """
Checks if a referral code has expired.
## Examples
iex> PhoenixKit.Modules.Referrals.expired?(code)
false
"""
def expired?(%__MODULE__{} = code) do
!is_nil(code.expiration_date) &&
DateTime.compare(DateTime.utc_now(), code.expiration_date) != :lt
end
@doc """
Checks if a referral code has reached its usage limit.
## Examples
iex> PhoenixKit.Modules.Referrals.usage_limit_reached?(code)
false
"""
def usage_limit_reached?(%__MODULE__{} = code) do
code.number_of_uses >= code.max_uses
end
## --- Business Logic Functions ---
@doc """
Returns the list of referral codes ordered by creation date.
## Examples
iex> PhoenixKit.Modules.Referrals.list_codes()
[%PhoenixKit.Modules.Referrals{}, ...]
"""
def list_codes do
__MODULE__
|> order_by([r], desc: r.date_created)
|> preload([:creator, :beneficiary_user])
|> repo().all()
end
@doc """
Gets a single referral code by ID.
Raises `Ecto.NoResultsError` if the code does not exist.
## Examples
iex> PhoenixKit.Modules.Referrals.get_code!(123)
%PhoenixKit.Modules.Referrals{}
iex> PhoenixKit.Modules.Referrals.get_code!(456)
** (Ecto.NoResultsError)
"""
def get_code!(id), do: repo().get!(__MODULE__, id)
@doc """
Gets a single referral code by its string value.
Returns the referral code if found, nil otherwise.
## Examples
iex> PhoenixKit.Modules.Referrals.get_code_by_string("WELCOME2024")
%PhoenixKit.Modules.Referrals{}
iex> PhoenixKit.Modules.Referrals.get_code_by_string("INVALID")
nil
"""
def get_code_by_string(code_string) when is_binary(code_string) do
repo().get_by(__MODULE__, code: code_string)
end
@doc """
Creates a referral code.
## Examples
iex> PhoenixKit.Modules.Referrals.create_code(%{code: "TEST123", max_uses: 10})
{:ok, %PhoenixKit.Modules.Referrals{}}
iex> PhoenixKit.Modules.Referrals.create_code(%{code: ""})
{:error, %Ecto.Changeset{}}
"""
def create_code(attrs \\ %{}) do
%__MODULE__{}
|> changeset(attrs)
|> repo().insert()
end
@doc """
Updates a referral code.
## Examples
iex> PhoenixKit.Modules.Referrals.update_code(code, %{description: "Updated"})
{:ok, %PhoenixKit.Modules.Referrals{}}
iex> PhoenixKit.Modules.Referrals.update_code(code, %{code: ""})
{:error, %Ecto.Changeset{}}
"""
def update_code(%__MODULE__{} = referral_code, attrs) do
referral_code
|> changeset(attrs)
|> repo().update()
end
@doc """
Deletes a referral code.
## Examples
iex> PhoenixKit.Modules.Referrals.delete_code(code)
{:ok, %PhoenixKit.Modules.Referrals{}}
iex> PhoenixKit.Modules.Referrals.delete_code(code)
{:error, %Ecto.Changeset{}}
"""
def delete_code(%__MODULE__{} = referral_code) do
repo().delete(referral_code)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking referral code changes.
## Examples
iex> PhoenixKit.Modules.Referrals.change_code(code)
%Ecto.Changeset{data: %PhoenixKit.Modules.Referrals{}}
"""
def change_code(%__MODULE__{} = referral_code, attrs \\ %{}) do
changeset(referral_code, attrs)
end
@doc """
Records usage of a referral code by a user.
Validates that the code is valid for use before recording the usage.
Updates the code's number_of_uses counter.
## Examples
iex> PhoenixKit.Modules.Referrals.use_code("WELCOME2024", user_id)
{:ok, %PhoenixKit.Modules.Referrals.ReferralCodeUsage{}}
iex> PhoenixKit.Modules.Referrals.use_code("EXPIRED", user_id)
{:error, :code_not_found}
"""
def use_code(code_string, user_id) when is_binary(code_string) and is_integer(user_id) do
case get_code_by_string(code_string) do
nil -> {:error, :code_not_found}
code -> process_code_usage(code, user_id)
end
end
defp process_code_usage(code, user_id) do
case valid_for_use?(code) do
true -> record_code_usage(code, user_id)
false -> get_code_error(code)
end
end
defp record_code_usage(code, user_id) do
repo().transaction(fn -> do_record_usage(code, user_id) end)
end
defp do_record_usage(code, user_id) do
usage_result =
%ReferralCodeUsage{}
|> ReferralCodeUsage.changeset(%{code_id: code.id, used_by: user_id})
|> repo().insert()
case usage_result do
{:ok, usage} ->
{:ok, _updated_code} = update_code(code, %{number_of_uses: code.number_of_uses + 1})
usage
{:error, changeset} ->
repo().rollback(changeset)
end
end
defp get_code_error(code) do
cond do
expired?(code) -> {:error, :code_expired}
usage_limit_reached?(code) -> {:error, :usage_limit_reached}
!code.status -> {:error, :code_inactive}
true -> {:error, :code_invalid}
end
end
@doc """
Gets usage statistics for a referral code.
## Examples
iex> PhoenixKit.Modules.Referrals.get_usage_stats(code_id)
%{total_uses: 5, unique_users: 3, last_used: ~U[...], recent_users: [...]}
"""
def get_usage_stats(code_id) when is_integer(code_id) do
ReferralCodeUsage.get_usage_stats(code_id)
end
@doc """
Lists all usage records for a referral code.
## Examples
iex> PhoenixKit.Modules.Referrals.list_usage_for_code(code_id)
[%PhoenixKit.Modules.Referrals.ReferralCodeUsage{}, ...]
"""
def list_usage_for_code(code_id) when is_integer(code_id) do
ReferralCodeUsage.for_code(code_id)
|> repo().all()
end
@doc """
Checks if a user has already used a specific referral code.
## Examples
iex> PhoenixKit.Modules.Referrals.user_used_code?(user_id, code_id)
false
"""
def user_used_code?(user_id, code_id) when is_integer(user_id) and is_integer(code_id) do
ReferralCodeUsage.user_used_code?(user_id, code_id)
end
## --- System Settings ---
@doc """
Checks if the referral codes system is enabled.
Returns true if the "referral_codes_enabled" setting is true.
## Examples
iex> PhoenixKit.Modules.Referrals.enabled?()
false
"""
def enabled? do
Settings.get_boolean_setting("referral_codes_enabled", false)
end
@doc """
Checks if referral codes are required for user registration.
Returns true if the "referral_codes_required" setting is true.
## Examples
iex> PhoenixKit.Modules.Referrals.required?()
false
"""
def required? do
Settings.get_boolean_setting("referral_codes_required", false)
end
@doc """
Enables the referral codes system.
Sets the "referral_codes_enabled" setting to true.
## Examples
iex> PhoenixKit.Modules.Referrals.enable_system()
{:ok, %Setting{}}
"""
def enable_system do
Settings.update_boolean_setting_with_module("referral_codes_enabled", true, "referral_codes")
end
@doc """
Disables the referral codes system.
Sets the "referral_codes_enabled" setting to false.
## Examples
iex> PhoenixKit.Modules.Referrals.disable_system()
{:ok, %Setting{}}
"""
def disable_system do
Settings.update_boolean_setting_with_module("referral_codes_enabled", false, "referral_codes")
end
@doc """
Sets whether referral codes are required for registration.
## Examples
iex> PhoenixKit.Modules.Referrals.set_required(true)
{:ok, %Setting{}}
iex> PhoenixKit.Modules.Referrals.set_required(false)
{:ok, %Setting{}}
"""
def set_required(required) when is_boolean(required) do
Settings.update_boolean_setting_with_module(
"referral_codes_required",
required,
"referral_codes"
)
end
@doc """
Gets the maximum number of uses allowed per referral code.
Returns the system-wide limit for how many times a single referral code can be used.
Defaults to 100 if not set.
## Examples
iex> PhoenixKit.Modules.Referrals.get_max_uses_per_code()
100
"""
def get_max_uses_per_code do
Settings.get_integer_setting("max_number_of_uses_per_code", 100)
end
@doc """
Gets the maximum number of referral codes a single user can create.
Returns the system-wide limit for referral code creation per user.
Defaults to 10 if not set.
## Examples
iex> PhoenixKit.Modules.Referrals.get_max_codes_per_user()
10
"""
def get_max_codes_per_user do
Settings.get_integer_setting("max_number_of_codes_per_user", 10)
end
@doc """
Sets the maximum number of uses allowed per referral code.
Updates the system-wide limit for referral code usage.
## Examples
iex> PhoenixKit.Modules.Referrals.set_max_uses_per_code(50)
{:ok, %Setting{}}
"""
def set_max_uses_per_code(max_uses) when is_integer(max_uses) and max_uses > 0 do
Settings.update_setting_with_module(
"max_number_of_uses_per_code",
to_string(max_uses),
"referral_codes"
)
end
@doc """
Sets the maximum number of referral codes a single user can create.
Updates the system-wide limit for referral code creation per user.
## Examples
iex> PhoenixKit.Modules.Referrals.set_max_codes_per_user(5)
{:ok, %Setting{}}
"""
def set_max_codes_per_user(max_codes) when is_integer(max_codes) and max_codes > 0 do
Settings.update_setting_with_module(
"max_number_of_codes_per_user",
to_string(max_codes),
"referral_codes"
)
end
@doc """
Gets the current referral codes system configuration.
Returns a map with the current settings.
## Examples
iex> PhoenixKit.Modules.Referrals.get_config()
%{enabled: false, required: false}
"""
def get_config do
%{
enabled: enabled?(),
required: required?(),
max_uses_per_code: get_max_uses_per_code(),
max_codes_per_user: get_max_codes_per_user()
}
end
@doc """
Gets codes that are currently valid for use.
Returns codes that are active, not expired, and haven't reached usage limits.
## Examples
iex> PhoenixKit.Modules.Referrals.list_valid_codes()
[%PhoenixKit.Modules.Referrals{}, ...]
"""
def list_valid_codes do
now = DateTime.utc_now()
from(r in __MODULE__,
where: r.status == true,
where: r.expiration_date > ^now,
where: r.number_of_uses < r.max_uses,
order_by: [desc: r.date_created]
)
|> repo().all()
end
@doc """
Gets summary statistics for the referral codes system.
Returns counts and metrics useful for admin dashboards.
## Examples
iex> PhoenixKit.Modules.Referrals.get_system_stats()
%{total_codes: 10, active_codes: 8, total_usage: 150, codes_with_usage: 6}
"""
def get_system_stats do
codes_query = from(r in __MODULE__)
usage_query = from(u in ReferralCodeUsage)
total_codes = repo().aggregate(codes_query, :count)
active_codes = repo().aggregate(from(r in codes_query, where: r.status == true), :count)
total_usage = repo().aggregate(usage_query, :count)
codes_with_usage =
repo().aggregate(from(r in codes_query, where: r.number_of_uses > 0), :count)
%{
total_codes: total_codes,
active_codes: active_codes,
total_usage: total_usage,
codes_with_usage: codes_with_usage
}
end
## --- Private Helpers ---
defp validate_code_uniqueness(changeset) do
case get_field(changeset, :code) do
nil ->
changeset
# Let validate_required handle empty strings
"" ->
changeset
code_string ->
case get_code_by_string(code_string) do
# No duplicate found, validation passes
nil ->
changeset
existing_code ->
# Check if this is the same record we're editing
current_id = get_field(changeset, :id)
if current_id && existing_code.id == current_id do
# This is the same record, validation passes
changeset
else
# Different record with same code, validation fails
add_error(changeset, :code, "has already been taken")
end
end
end
end
defp validate_expiration_date(changeset) do
case get_field(changeset, :expiration_date) do
nil ->
changeset
expiration_date ->
if DateTime.compare(expiration_date, DateTime.utc_now()) == :gt do
changeset
else
add_error(changeset, :expiration_date, "must be in the future")
end
end
end
defp maybe_set_date_created(changeset) do
case get_field(changeset, :id) do
nil -> put_change(changeset, :date_created, DateTime.utc_now())
_id -> changeset
end
end
defp validate_max_uses_limit(changeset) do
case get_field(changeset, :max_uses) do
nil ->
changeset
max_uses ->
system_limit = get_max_uses_per_code()
if max_uses <= system_limit do
changeset
else
add_error(changeset, :max_uses, "cannot exceed system limit of #{system_limit}")
end
end
end
@doc """
Validates that a user hasn't exceeded their referral code creation limit.
Checks the current number of codes created by the user against the system limit.
Returns `{:ok, :valid}` if within limits, `{:error, reason}` if limit exceeded.
## Examples
iex> PhoenixKit.Modules.Referrals.validate_user_code_limit(1)
{:ok, :valid}
iex> PhoenixKit.Modules.Referrals.validate_user_code_limit(1)
{:error, "You have reached the maximum limit of 10 referral codes"}
"""
def validate_user_code_limit(user_id) when is_integer(user_id) do
max_codes = get_max_codes_per_user()
current_count = count_user_codes(user_id)
if current_count < max_codes do
{:ok, :valid}
else
{:error, "You have reached the maximum limit of #{max_codes} referral codes"}
end
end
@doc """
Counts the total number of referral codes created by a user.
## Examples
iex> PhoenixKit.Modules.Referrals.count_user_codes(1)
5
"""
def count_user_codes(user_id) when is_integer(user_id) do
from(r in __MODULE__, where: r.created_by == ^user_id, select: count(r.id))
|> repo().one()
end
defp maybe_set_default_expiration(changeset) do
# Respect user's intent to leave expiration empty (nil = no expiration)
# Only set default expiration for programmatic creation without explicit intent
changeset
end
# Gets the configured repository for database operations
defp repo do
PhoenixKit.RepoHelper.repo()
end
end