lib/bbox/voters/voter.ex

defmodule Bbox.Voters.Voter do
  @doc """
  Voter is a election voter.
  """
  use Ecto.Schema
  import Ecto.Changeset
  alias Bbox.Votes.Vote

  @primary_key false
  @foreign_key_type :string
  @derive {Jason.Encoder, except: [:__meta__, :votes]}
  schema "voters" do
    field(:address, :string)
    field(:username, :string)
    field(:email, :string)
    field(:private_key, :string, virtual: true)
    field(:public_key, :string, virtual: true)
    has_many(:votes, Vote, foreign_key: :voter_address, references: :address)

    timestamps()
  end

  def changeset(voter, params \\ %{}) do
    voter
    |> cast(params, [:address, :username, :email, :public_key, :private_key])
    |> validate_required([:username, :email])
    |> validate_format(:username, ~r/^[a-zA-Z0-9_]{3,20}$/, message: "invalid username")
    |> validate_format(:email, ~r/^\S+@\S+\.\S+$/, message: "invalid email")
    |> put_params()
  end

  defp put_params(%Ecto.Changeset{valid?: true} = changeset) do
    private_key = X509.PrivateKey.new_rsa(2048)

    public_key =
      private_key
      |> X509.PublicKey.derive()
      |> X509.PublicKey.to_pem()
      |> Base.encode32()

    private_key =
      private_key
      |> X509.PrivateKey.to_pem()
      |> Base.encode32()

    address = String.slice(public_key, 0..20)

    changeset
    |> put_change(:public_key, public_key)
    |> put_change(:private_key, private_key)
    |> put_change(:address, address)
  end

  defp put_params(changeset), do: changeset
end