lib/code_corps/github/sync/github_repo/github_repo.ex
defmodule CodeCorps.GitHub.Sync.GithubRepo do
import Ecto.Query
alias CodeCorps.{
GitHub.Adapters,
GitHub.API.Installation,
GitHub.Utils.ResultAggregator,
GithubAppInstallation,
GithubRepo,
Repo
}
alias Ecto.{Changeset, Multi}
@typep commit_result :: {:ok, GithubRepo.t()} | {:error, Changeset.t()}
@typep aggregated_result ::
{:ok, list(GithubRepo.t())} |
{:error, {list(GithubRepo.t()), list(Changeset.t())}}
@spec sync_installation(GithubAppInstallation.t(), map) :: aggregated_result()
def sync_installation(
%GithubAppInstallation{} = installation,
%{"action" => "added", "repositories_added" => repositories}) do
do_sync_installation(installation, [], repositories)
end
def sync_installation(
%GithubAppInstallation{} = installation,
%{"action" => "removed", "repositories_removed" => repositories}) do
github_ids = repositories |> Enum.map(&Map.get(&1, "id"))
do_sync_installation(installation, github_ids, [])
end
@spec sync_installation(GithubAppInstallation.t()) ::
aggregated_result() | {:error, struct}
def sync_installation(%GithubAppInstallation{} = installation) do
with {:ok, payloads} <- installation |> Installation.repositories() do
%GithubAppInstallation{github_repos: repos} = installation =
installation |> Repo.preload(:github_repos)
master_id_list = payloads |> Enum.map(&Map.get(&1, "id"))
ids_to_delete =
repos
|> Enum.filter(fn repo -> not(repo.github_id in master_id_list) end)
|> Enum.map(&Map.get(&1, :github_id))
do_sync_installation(installation, ids_to_delete, payloads)
else
{:error, api_error} -> {:error, {:api_error, api_error}}
end
end
@spec do_sync_installation(GithubAppInstallation.t(), list, list) ::
aggregated_result()
defp do_sync_installation(
%GithubAppInstallation{} = installation, ids_to_delete, payloads_to_sync)
when is_list(ids_to_delete) and is_list(payloads_to_sync) do
Multi.new
|> Multi.run(:delete, fn _ -> ids_to_delete |> delete_all() end)
|> Multi.run(:sync, fn _ -> installation |> sync_all(payloads_to_sync) end)
|> Multi.run(:mark_processed, fn _ -> installation |> mark_processed() end)
|> Repo.transaction()
|> marshall_result()
end
@spec sync_all(GithubAppInstallation.t(), list) :: aggregated_result()
defp sync_all(%GithubAppInstallation{} = installation, payloads)
when is_list(payloads) do
payloads
|> Enum.map(&find_or_create(installation, &1))
|> ResultAggregator.aggregate()
end
@spec delete_all(list) :: {:ok, list(GithubRepo.t)}
defp delete_all(github_ids) when is_list(github_ids) do
GithubRepo
|> where([r], r.github_id in ^github_ids)
|> Repo.delete_all(returning: true)
|> (fn {_count, records} -> {:ok, records} end).()
end
@spec find_or_create(GithubAppInstallation.t(), map) :: {:ok, GithubRepo.t()} | {:error, Changeset.t()}
defp find_or_create(%GithubAppInstallation{} = installation, %{} = payload) do
case find_repo(payload) do
nil -> create_repo(installation, payload)
%GithubRepo{} = repo -> repo |> update_repo(payload)
end
end
@spec find_repo(map) :: GithubRepo.t() | nil
defp find_repo(%{"id" => github_id}) do
GithubRepo
|> Repo.get_by(github_id: github_id)
|> Repo.preload(:github_app_installation)
end
@spec create_repo(GithubAppInstallation.t(), map) :: commit_result()
defp create_repo(%GithubAppInstallation{} = installation, %{} = payload) do
attrs =
payload
|> Adapters.Repo.from_api()
|> Map.merge(installation |> Adapters.AppInstallation.to_github_repo_attrs())
%GithubRepo{}
|> GithubRepo.changeset(attrs)
|> Changeset.put_assoc(:github_app_installation, installation)
|> Repo.insert()
end
@spec update_repo(GithubRepo.t(), map) :: commit_result()
defp update_repo(%GithubRepo{} = github_repo, %{} = payload) do
github_repo
|> Changeset.change(payload |> Adapters.Repo.from_api())
|> Repo.update()
end
@spec mark_processed(GithubAppInstallation.t()) :: {:ok, GithubAppInstallation.t()}
defp mark_processed(%GithubAppInstallation{} = installation) do
installation
|> Changeset.change(%{state: "processed"})
|> Repo.update()
end
@spec marshall_result(tuple) :: tuple
defp marshall_result({:ok, %{sync: synced_repos, delete: deleted_repos}}) do
{:ok, {synced_repos, deleted_repos}}
end
defp marshall_result({:error, errored_step, error_response, _steps}) do
{:error, {errored_step, error_response}}
end
end