Packages

Encrypted fields for Ecto

Current section

Files

Jump to
ad_cloak_ecto lib cloak_ecto migrator.ex
Raw

lib/cloak_ecto/migrator.ex

defmodule Cloak.Ecto.Migrator do
@moduledoc false
import Ecto.Query
alias Ecto.Changeset
alias Cloak.Ecto.Migrator.CursorStream
def migrate(repo, schema) when is_atom(repo) and is_atom(schema) do
validate(repo, schema)
[primary_key | _] = schema.__schema__(:primary_key)
case repo.aggregate(schema, :count, primary_key) do
0 -> :ok
_ -> migrate_schema_with_data(repo, schema)
end
end
defp migrate_schema_with_data(repo, schema) do
fields = cloak_fields(schema)
repo
|> CursorStream.new(schema, 100)
|> Task.async_stream(&migrate_row(&1, repo, schema, fields))
|> Stream.run()
end
defp migrate_row(id, repo, schema, fields) do
[primary_key | _] = schema.__schema__(:primary_key)
repo.transaction(fn ->
query =
schema
|> where([s], field(s, ^primary_key) == ^id)
|> lock("FOR UPDATE")
case repo.one(query) do
nil ->
:noop
row ->
row
|> force_changes(fields)
|> repo.update()
end
end)
end
defp force_changes(row, fields) do
Enum.reduce(fields, Changeset.change(row), fn field, changeset ->
Changeset.force_change(changeset, field, Map.get(row, field))
end)
end
defp cloak_fields(schema) do
:fields
|> schema.__schema__()
|> Enum.map(fn field ->
{field, schema.__schema__(:type, field)}
end)
|> Enum.filter(&cloak_field?/1)
|> Enum.map(fn {field, _type} ->
field
end)
end
defp cloak_field?({_field, {:embed, %Ecto.Embedded{}}}) do
false
end
defp cloak_field?({_field, {:parameterized, Ecto.Embedded, %Ecto.Embedded{}}}) do
false
end
defp cloak_field?({_field, {:parameterized, Ecto.Enum, _opts}}) do
false
end
defp cloak_field?({field, {kind, inner_type}}) when kind in [:array, :map] do
cloak_field?({field, inner_type})
end
defp cloak_field?({_field, type}) do
Code.ensure_loaded?(type) && function_exported?(type, :__cloak__, 0)
end
defp validate(repo, schema) do
unless ecto_repo?(repo) do
raise ArgumentError, "#{inspect(repo)} is not an Ecto.Repo"
end
unless ecto_schema?(schema) do
raise ArgumentError, "#{inspect(schema)} is not an Ecto.Schema"
end
end
defp ecto_repo?(repo) do
Code.ensure_loaded?(repo) && function_exported?(repo, :__adapter__, 0)
end
defp ecto_schema?(schema) do
Code.ensure_loaded?(schema) && function_exported?(schema, :__schema__, 1)
end
end