Packages
potionx
0.4.0
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.1.1
0.1.0
Potionx is a set of generators and functions that speeds up the process of setting up and deploying a full-stack application that uses Elixir with GraphQL for the server-side component and Vue for the frontend component.
Current section
Files
Jump to
Current section
Files
lib/potionx/auth/auth_session_service.ex
defmodule Potionx.Auth.SessionService do
alias Potionx.Context.Service
alias Ecto.Multi
@callback create(Potionx.Context.Service.t(), struct() | nil) :: {:ok, struct()} | {:error, map()}
@callback delete(Potionx.Context.Service.t()) :: {:ok, struct()} | {:error, String.t()}
@callback one(Potionx.Context.Service.t()) :: struct()
@callback one_from_cache(Potionx.Context.Service.t()) :: struct() | map() | nil
@callback patch(Potionx.Context.Service.t()) :: {:ok, struct()} | {:error, map()}
@callback use_redis() :: boolean()
defmacro __using__(opts) do
if !Keyword.get(opts, :identity_service) do
raise "Potionx.Auth.SessionService requires an identity service"
end
if !Keyword.get(opts, :repo) do
raise "Potionx.Auth.SessionService requires a repo"
end
if !Keyword.get(opts, :session_schema) do
raise "Potionx.Auth.SessionService requires a session schema"
end
if !Keyword.get(opts, :user_service) do
raise "Potionx.Auth.SessionService requires a user service"
end
quote do
@behaviour Potionx.Auth.SessionService
@repo unquote(opts[:repo])
@identity_service unquote(opts[:identity_service])
@session_schema unquote(opts[:session_schema])
@user_service unquote(opts[:user_service])
import Ecto.Query
def create(%Service{changes: %{session: _} = changes, filters: filters}, previous_session) do
Multi.new
|> Multi.run(:session_delete, fn _, _ ->
if previous_session do
delete_from_repo(previous_session)
else
{:ok, nil}
end
end)
|> Multi.run(:redis_old_session_deleted, fn
_, %{session_delete: %{id: _} = session} ->
delete_from_redis(session)
_, _ -> {:ok, nil}
end)
|> Multi.run(:user, fn _, _ ->
case changes do
%{user: %{"email" => email}} ->
@user_service.one(%Service{filters: %{email: email}})
|> case do
nil -> {:error, "user_not_found"}
user -> {:ok, user}
end
_ ->
{:ok, nil}
end
end)
|> Multi.run(:identity, fn
_, %{user: %{id: user_id}} ->
process_identity(changes.identity, user_id)
_, _ -> {:ok, nil}
end)
|> Multi.run(:session, fn _, %{user: user} ->
struct(@session_schema)
|> @session_schema.changeset(
Map.put(
changes.session,
:user_id,
Map.get(user || %{}, :id)
)
)
|> @repo.insert
end)
|> Multi.run(:redis, fn _, %{session: session, user: user} ->
session = %{session | user: user}
if (use_redis()) do
[{:uuid_access, :ttl_access_seconds}, {:uuid_renewal, :ttl_renewal_seconds}]
|> Enum.reduce([], fn {key, ttl_key}, acc ->
if Map.get(session, key) do
acc ++ [
Potionx.Redis.put(
Map.get(session, key),
Jason.encode!(session),
Map.get(session, ttl_key)
)
]
else
acc
end
end)
|> Potionx.Utils.Ecto.reduce_results
else
{:ok, nil}
end
end)
|> @repo.transaction
end
def create(%Service{changes: changes} = srv, previous_session) do
create(
%{
srv | changes: %{session: changes}
},
previous_session
)
end
def delete(%Service{filters: %{id: id}}) do
Multi.new
|> Multi.run(:session, fn _, _ ->
@repo.get(@session_schema, id)
|> case do
nil -> {:error, "missing_session"}
session -> {:ok, session}
end
end)
|> Multi.run(
:session_delete,
fn _repo, %{session: session} ->
delete_from_repo(session)
end
)
|> Multi.run(:redis, fn _, %{session: session} ->
delete_from_redis(session)
end)
|> @repo.transaction
end
def delete_from_repo(%{id: _} = session) do
session
|> @session_schema.changeset(%{
deleted_at: NaiveDateTime.truncate(NaiveDateTime.utc_now, :second)
})
|> @repo.update
end
def delete_from_redis(%{uuid_access: _} = session) do
if (use_redis()) do
[{:uuid_access, :ttl_access_seconds}, {:uuid_renewal, :ttl_renewal_seconds}]
|> Enum.reduce([], fn {key, ttl_key}, acc ->
if Map.get(session, key) do
acc ++ [
Potionx.Redis.delete(
Map.get(session, key)
)
]
else
acc
end
end)
|> Potionx.Utils.Ecto.reduce_results
else
{:ok, nil}
end
end
def one(%Service{} = ctx) do
query(ctx)
|> preload([:user])
|> @repo.one
end
def one_from_cache(%Service{} = ctx) do
token = case ctx.filters do
%{uuid_access: token} -> token
%{uuid_renewal: token} -> token
end
if (use_redis()) do
Potionx.Redis.get(token)
|> case do
{:ok, nil} -> nil
{:ok, res} ->
struct!(
@session_schema,
Jason.decode!(res)
|> transform_keys_to_atoms
)
_ -> nil
end
else
one(ctx)
end
end
def patch(%Service{filters: %{id: id}} = ctx) when not is_nil(id) do
Multi.new
|> Multi.run(:session_old, fn _, _ ->
from(s in @session_schema, where: s.id == ^id)
|> preload([s], [:user])
|> @repo.one
|> case do
nil -> {:error, "missing_session"}
session -> {:ok, session}
end
end)
|> Multi.run(
:session_patch,
fn _repo, %{session_old: session} ->
session
|> @session_schema.changeset(ctx.changes)
|> @repo.update
end
)
|> Multi.run(:redis_old, fn _, %{session_old: session} ->
if (use_redis()) do
[{:uuid_access, :ttl_access_seconds}, {:uuid_renewal, :ttl_renewal_seconds}]
|> Enum.map(fn {key, ttl_key} ->
Potionx.Redis.delete(
Map.get(session, key)
)
end)
|> Potionx.Utils.Ecto.reduce_results
else
{:ok, nil}
end
end)
|> Multi.run(:redis, fn _, %{session_patch: session} ->
if (use_redis()) do
[{:uuid_access, :ttl_access_seconds}, {:uuid_renewal, :ttl_renewal_seconds}]
|> Enum.map(fn {key, ttl_key} ->
Potionx.Redis.put(
Map.get(session, key),
Jason.encode!(session),
Map.get(session, ttl_key)
)
end)
|> Potionx.Utils.Ecto.reduce_results
else
{:ok, nil}
end
end)
|> @repo.transaction
end
def process_identity(changes, user_id) do
@identity_service.query(%Service{filters: %{user_id: user_id}})
|> @repo.all
|> (fn identities ->
existing_identity =
Enum.find(identities, fn i -> i.provider === changes["provider"] end)
cond do
Enum.count(identities) === 0 ->
@identity_service.create(%Service{
changes: changes |> Map.put("user_id", user_id)
})
not is_nil(existing_identity) ->
{:ok, existing_identity}
true ->
{:error, "invalid_provider"}
end
end).()
end
def query(%Service{} = ctx) do
@session_schema
|> where(
^(
ctx.filters
|> Map.to_list
)
)
|> order_by([desc: :id])
|> where([s], is_nil(s.deleted_at))
end
def query(q, _args), do: q
def transform_keys_to_atoms(session) do
session
|> Map.new(fn {k, v} ->
{String.to_existing_atom(k), v}
end)
|> case do
%{user: %{"id" => _} = user} = session ->
%{
session |
user: Map.new(user, fn {k, v} ->
{String.to_existing_atom(k), v}
end)
}
session -> session
end
end
def use_redis do
Application.get_env(:redix, :url) !== nil
end
defoverridable(Potionx.Auth.SessionService)
end
end
end