Usher (Usher v0.2.0)

View Source

Usher is a web framework-agnostic invitation link management library for any Elixir application with Ecto.

Getting Started

To use Usher in your application, you need to:

  1. Run the migrations
  2. Configure Usher with your repo
  3. Use the functions provided to manage invitations

Database Setup

Generate and run the migration:

mix ecto.gen.migration create_usher_tables

Then add the Usher schema to your migration:

defmodule MyApp.Repo.Migrations.CreateUsherTables do
  use Ecto.Migration
  import Usher.Migration

  def change do
    migrate_to_latest()
  end
end

For existing installations upgrading to a new version, generate a new migration:

mix ecto.gen.migration upgrade_usher_tables

And use the same migrate_to_latest/0 function - it will automatically detect your current version and apply only the necessary migrations.

Configuration

In your config/config.exs (or whichever environment you prefer), set up the Usher configuration:

config :usher,
  repo: MyApp.Repo,
  token_length: 16,
  default_expires_in: {7, :days},
  table_name: "myapp_invitations",
  validations: %{
    invitation: %{
      name_required: false
    }
  }

All the values above have defaults, which you can find in Usher.Config.

Basic Usage

# Create an invitation
{:ok, invitation} = Usher.create_invitation()

# Create an invitation with a name
{:ok, invitation} = Usher.create_invitation(%{name: "Team Welcome"})

# Get invitation by token
invitation = Usher.get_invitation_by_token("abc123")

# Validate and consume invitation
case Usher.validate_invitation_token("abc123") do
  {:ok, invitation} ->
    # Proceed with registration
    Usher.increment_joined_count(invitation)
  {:error, :invalid_token} ->
    # Handle invalid token
  {:error, :invitation_expired} ->
    # Handle expired token
end

Features

  • Token generation using cryptographic functions
  • Configurable token length and expiration periods
  • Optional name field with configurable validation
  • Versioned migration system for safe upgrades
  • Framework-agnostic design works with any Ecto-based application

Summary

Functions

Returns an %Ecto.Changeset{} for tracking invitation changes.

Creates a new invitation with a token and default expiration datetime.

Deletes an invitation.

Gets a single invitation by ID. Raises if not found.

Gets an invitation by token.

Increments the joined count for an invitation.

Builds an invitation URL for the given token and base URL.

Retrieves all invitations.

Validates an invitation token exists and returns the invitation if valid.

Functions

change_invitation(invitation, attrs \\ %{}, opts \\ [])

Returns an %Ecto.Changeset{} for tracking invitation changes.

Options

  • :require_name - Whether to require the name field (defaults to false)

Examples

iex> Usher.change_invitation(invitation)
%Ecto.Changeset{data: %Usher.Invitation{}}

iex> Usher.change_invitation(invitation, %{name: "Test"})
%Ecto.Changeset{data: %Usher.Invitation{}}

iex> Usher.change_invitation(invitation, %{}, require_name: true)
%Ecto.Changeset{data: %Usher.Invitation{}, errors: [name: {"can't be blank", _}]}

create_invitation(attrs \\ %{}, opts \\ [])

Creates a new invitation with a token and default expiration datetime.

Attributes

  • :name - Name for the invitation
  • :expires_at - Custom expiration datetime (overrides default)
  • :token - Custom token (overrides generated token)

Options

  • :require_name - Whether to require the name field (defaults to false)

Examples

iex> Usher.create_invitation()
{:ok, %Usher.Invitation{token: "abc123...", expires_at: ~U[...]}}

iex> Usher.create_invitation(%{name: "Welcome Team"})
{:ok, %Usher.Invitation{name: "Welcome Team"}}

iex> Usher.create_invitation(%{}, require_name: true)
{:error, %Ecto.Changeset{errors: [name: {"can't be blank", _}]}}

delete_invitation(invitation)

Deletes an invitation.

Examples

iex> Usher.delete_invitation(invitation)
{:ok, %Usher.Invitation{}}

iex> Usher.delete_invitation(bad_invitation)
{:error, %Ecto.Changeset{}}

get_invitation!(id)

Gets a single invitation by ID. Raises if not found.

Examples

iex> Usher.get_invitation!(id)
%Usher.Invitation{}

iex> Usher.get_invitation!("nonexistent")
** (Ecto.NoResultsError)

get_invitation_by_token(token)

Gets an invitation by token.

Examples

iex> Usher.get_invitation_by_token("valid_token")
%Usher.Invitation{}

iex> Usher.get_invitation_by_token("invalid")
nil

increment_joined_count(invitation)

Increments the joined count for an invitation.

This is typically called when a user successfully registers using the invitation.

Examples

iex> Usher.increment_joined_count(invitation)
{:ok, %Usher.Invitation{joined_count: 1}}

invitation_url(token, base_url)

Builds an invitation URL for the given token and base URL.

Examples

iex> Usher.invitation_url("abc123", "https://example.com/signup")
"https://example.com/signup?invitation_token=abc123"

list_invitations()

Retrieves all invitations.

Examples

iex> Usher.list_invitations()
[%Usher.Invitation{}, ...]

validate_invitation_token(token)

Validates an invitation token exists and returns the invitation if valid.

Returns {:ok, invitation} if the token exists and hasn't expired. Returns {:error, reason} if the token is invalid or expired.

Examples

iex> Usher.validate_invitation_token("valid_token")
{:ok, %Usher.Invitation{}}

iex> Usher.validate_invitation_token("expired_token")
{:error, :invitation_expired}

iex> Usher.validate_invitation_token("invalid_token")
{:error, :invalid_token}