lib/code_corps/accounts/accounts.ex
defmodule CodeCorps.Accounts do
@moduledoc ~S"""
Main entry-point for managing accounts.
All actions to accounts should go through here.
"""
alias CodeCorps.{
Accounts.Changesets,
Comment,
GitHub.Adapters,
GithubAppInstallation,
GithubUser,
Processor,
Task,
User,
Repo
}
alias Ecto.{Changeset, Multi}
import Ecto.Query
@doc ~S"""
Creates a user record using attributes from a GitHub payload.
"""
@spec create_from_github(map) :: {:ok, User.t} | {:error, Changeset.t}
def create_from_github(%{} = attrs) do
with {:ok, user} <- do_create_from_github(attrs) do
user |> upload_github_photo_async
{:ok, user}
else
error -> error
end
end
@spec do_create_from_github(map) :: {:ok, User.t} | {:error, Changeset.t}
defp do_create_from_github(%{} = attrs) do
%User{}
|> Changesets.create_from_github_changeset(attrs)
|> Repo.insert
end
@doc ~S"""
Creates a user record using attributes from a GitHub payload.
"""
@spec create_from_github_user(GithubUser.t) :: {:ok, User.t} | {:error, Changeset.t}
def create_from_github_user(%GithubUser{} = github_user) do
with {:ok, user} <- do_create_from_github_user(github_user) do
user |> upload_github_photo_async
{:ok, user}
else
error -> error
end
end
@spec do_create_from_github_user(GithubUser.t) :: {:ok, User.t} | {:error, Changeset.t}
defp do_create_from_github_user(%GithubUser{} = github_user) do
%User{}
|> Changesets.create_from_github_changeset(github_user |> Adapters.User.to_user_attrs())
|> Changeset.put_assoc(:github_user, github_user)
|> Repo.insert
end
@spec update_with_github_user(User.t, GithubUser.t) :: {:ok, User.t} | {:error, Changeset.t}
def update_with_github_user(%User{} = user, %GithubUser{} = github_user) do
with {:ok, user} <- do_update_with_github_user(user, github_user) do
user |> upload_github_photo_async
{:ok, user}
else
error -> error
end
end
@spec do_update_with_github_user(User.t, GithubUser.t) :: {:ok, User.t} | {:error, Changeset.t}
defp do_update_with_github_user(%User{} = user, %GithubUser{} = github_user) do
user
|> Changesets.update_with_github_user_changeset(github_user |> Adapters.User.to_user_attrs())
|> Changeset.put_assoc(:github_user, github_user)
|> Repo.update
end
@doc ~S"""
Updates a user record using attributes from a GitHub payload along with the
access token.
"""
@spec update_from_github_oauth(User.t, map, String.t) :: {:ok, User.t} | {:error, Changeset.t}
def update_from_github_oauth(%User{} = user, %{} = params, access_token) do
params =
params
|> Adapters.User.to_user()
|> Map.put(:github_auth_token, access_token)
changeset = user |> Changesets.update_from_github_oauth_changeset(params)
multi =
Multi.new
|> Multi.run(:existing_user, fn _ -> params |> update_existing_user_if_any() end)
|> Multi.update(:user, changeset)
|> Multi.run(:installations, fn %{user: %User{} = user} -> user |> associate_installations() end)
|> Multi.run(:tasks, fn %{user: %User{} = user} -> user |> associate_tasks() end)
|> Multi.run(:comments, fn %{user: %User{} = user} -> user |> associate_comments() end)
case Repo.transaction(multi) do
{:ok, %{user: %User{} = user, installations: installations}} ->
user |> upload_github_photo_async
{:ok, user |> Map.put(:github_app_installations, installations)}
{:error, :user, %Changeset{} = changeset, _actions_done} ->
{:error, changeset}
end
end
defp update_existing_user_if_any(%{github_id: github_id}) do
case Repo.get_by(User, github_id: github_id, sign_up_context: "github") do
%User{} = existing_user -> existing_user |> do_update_existing_user()
_ -> {:ok, nil}
end
end
defp do_update_existing_user(%User{github_id: github_id} = user) do
params = %{github_id: nil, github_id_was: github_id}
user
|> Changesets.dissociate_github_user_changeset(params)
|> Repo.update()
end
@spec upload_github_photo_async(User.t) :: User.t | Processor.result
defp upload_github_photo_async(%User{cloudinary_public_id: nil} = user) do
Processor.process(fn -> upload_github_photo(user) end)
end
defp upload_github_photo_async(%User{} = user), do: user
defp upload_github_photo(%User{github_avatar_url: github_avatar_url} = user) do
{:ok, %Cloudex.UploadedImage{public_id: cloudinary_public_id}} =
github_avatar_url
|> CodeCorps.Cloudex.Uploader.upload()
user
|> Changeset.change(%{cloudinary_public_id: cloudinary_public_id})
|> Repo.update!
end
@spec associate_installations(User.t) :: {:ok, list(GithubAppInstallation.t)}
defp associate_installations(%User{id: user_id, github_id: github_id}) do
updates = [set: [user_id: user_id]]
update_options = [returning: true]
GithubAppInstallation
|> where([i], i.sender_github_id == ^github_id)
|> where([i], is_nil(i.user_id))
|> Repo.update_all(updates, update_options)
|> (fn {_count, installations} -> {:ok, installations} end).()
end
@spec associate_tasks(User.t) :: {:ok, list(Task.t)}
defp associate_tasks(%User{id: user_id, github_id: github_id}) do
updates = [set: [user_id: user_id]]
update_options = [returning: true]
existing_user_ids =
User
|> where(github_id_was: ^github_id)
|> select([u], u.id)
|> Repo.all
Task
|> where([t], t.user_id in ^existing_user_ids)
|> Repo.update_all(updates, update_options)
|> (fn {_count, tasks} -> {:ok, tasks} end).()
end
@spec associate_comments(User.t) :: {:ok, list(Comment.t)}
defp associate_comments(%User{id: user_id, github_id: github_id}) do
updates = [set: [user_id: user_id]]
update_options = [returning: true]
existing_user_ids =
User
|> where(github_id_was: ^github_id)
|> select([u], u.id)
|> Repo.all
Comment
|> where([c], c.user_id in ^existing_user_ids)
|> Repo.update_all(updates, update_options)
|> (fn {_count, comments} -> {:ok, comments} end).()
end
end