Skip to content

Commit 0f7de10

Browse files
authored
feat: add user sync functionality to handle username changes and/or collisions during migration (#65)
1 parent fe3f3f4 commit 0f7de10

File tree

2 files changed

+77
-7
lines changed

2 files changed

+77
-7
lines changed

lib/algora/workspace/workspace.ex

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule Algora.Workspace do
22
@moduledoc false
3+
import Ecto.Changeset
34
import Ecto.Query
45

56
alias Algora.Accounts.User
@@ -92,10 +93,33 @@ defmodule Algora.Workspace do
9293

9394
def create_repository_from_github(token, owner, repo) do
9495
with {:ok, repository} <- Github.get_repository(token, owner, repo),
95-
{:ok, user} <- ensure_user(token, owner) do
96-
repository
97-
|> Repository.github_changeset(user)
98-
|> Repo.insert()
96+
{:ok, user} <- ensure_user_by_repo(token, repository, owner),
97+
{:ok, user} <- sync_user(user, repository, owner, repo),
98+
{:ok, repo} <- repository |> Repository.github_changeset(user) |> Repo.insert() do
99+
{:ok, repo}
100+
else
101+
{:error,
102+
%Ecto.Changeset{
103+
errors: [provider: {_, [constraint: :unique, constraint_name: "repositories_provider_provider_id_index"]}]
104+
} = changeset} ->
105+
Repo.fetch_by(Repository, provider: "github", provider_id: changeset.changes.provider_id)
106+
107+
{:error, _reason} = error ->
108+
error
109+
end
110+
end
111+
112+
def ensure_user_by_repo(token, repository, owner) do
113+
case Repo.get_by(User, provider: "github", provider_id: to_string(repository["owner"]["id"])) do
114+
%User{} = user ->
115+
{:ok, user}
116+
117+
nil ->
118+
if repository["owner"]["login"] != owner do
119+
Logger.warning("might need to rename #{owner} -> #{repository["owner"]["login"]}")
120+
end
121+
122+
ensure_user(token, repository["owner"]["login"])
99123
end
100124
end
101125

@@ -106,6 +130,39 @@ defmodule Algora.Workspace do
106130
end
107131
end
108132

133+
def sync_user(user, repository, owner, repo) do
134+
github_user = repository["owner"]
135+
136+
if github_user["login"] == user.provider_login and not is_nil(user.provider_id) do
137+
{:ok, user}
138+
else
139+
if github_user["login"] != user.provider_login do
140+
Logger.warning(
141+
"renaming #{user.provider_login} -> #{github_user["login"]} (reason: #{owner}/#{repo} moved to #{repository["full_name"]})"
142+
)
143+
end
144+
145+
res =
146+
user
147+
|> change(%{
148+
provider_id: to_string(github_user["id"]),
149+
provider_login: github_user["login"],
150+
provider_meta: Util.normalize_struct(github_user)
151+
})
152+
|> unique_constraint([:provider, :provider_id])
153+
|> Repo.update()
154+
155+
case res do
156+
{:ok, user} ->
157+
{:ok, user}
158+
159+
error ->
160+
Logger.error("#{owner}/#{repo} | failed to remap #{user.provider_login} -> #{github_user["login"]}")
161+
error
162+
end
163+
end
164+
end
165+
109166
def create_user_from_github(token, owner) do
110167
with {:ok, user_data} <- Github.get_user_by_username(token, owner) do
111168
user_data

test/support/github_mock.ex

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,27 @@ defmodule Algora.Support.GithubMock do
2222
%{
2323
"id" => random_id(),
2424
"name" => repo,
25-
"html_url" => "https://github.com/#{owner}/#{repo}"
25+
"html_url" => "https://github.com/#{owner}/#{repo}",
26+
"owner" => %{
27+
"login" => owner
28+
}
2629
}}
2730
end
2831

2932
@impl true
3033
def get_repository(_access_token, id) do
31-
name = "repo_#{id}"
32-
{:ok, %{"id" => id, "name" => name, "html_url" => "https://github.com/owner_#{random_id()}/#{name}"}}
34+
owner = "owner_#{random_id()}"
35+
name = "repo_#{random_id()}"
36+
37+
{:ok,
38+
%{
39+
"id" => id,
40+
"name" => name,
41+
"html_url" => "https://github.com/#{owner}/#{name}",
42+
"owner" => %{
43+
"login" => owner
44+
}
45+
}}
3346
end
3447

3548
@impl true

0 commit comments

Comments
 (0)