Skip to content

Sync repo #1143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ config :code_corps, CodeCorpsWeb.Endpoint,
]

# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
config :logger,
:console, format: "[$level] $message\n"

# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
Expand Down
42 changes: 41 additions & 1 deletion lib/code_corps/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule CodeCorps.Accounts do
Comment,
GitHub.Adapters,
GithubAppInstallation,
GithubUser,
Task,
User,
Repo
Expand Down Expand Up @@ -39,6 +40,45 @@ defmodule CodeCorps.Accounts do
|> 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(Usert.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.
Expand All @@ -47,7 +87,7 @@ defmodule CodeCorps.Accounts do
def update_from_github_oauth(%User{} = user, %{} = params, access_token) do
params =
params
|> Adapters.User.from_github_user()
|> Adapters.User.to_user()
|> Map.put(:github_auth_token, access_token)

changeset = user |> Changesets.update_from_github_oauth_changeset(params)
Expand Down
20 changes: 17 additions & 3 deletions lib/code_corps/accounts/changesets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,35 @@ defmodule CodeCorps.Accounts.Changesets do
alias Ecto.Changeset

@doc ~S"""
Casts a changeset used for creating a user account from a github user payload
Casts a changeset used for creating a user account from a GitHub user payload
"""
@spec create_from_github_changeset(struct, map) :: Changeset.t
def create_from_github_changeset(struct, %{} = params) do
struct
|> Changeset.change(params |> Adapters.User.from_github_user())
|> Changeset.change(params |> Adapters.User.to_user())
|> Changeset.put_change(:sign_up_context, "github")
|> Changeset.validate_inclusion(:type, ["bot", "user"])
|> RandomIconColor.generate_icon_color(:default_color)
|> Changeset.unique_constraint(:email)
|> Changeset.assoc_constraint(:github_user)
|> unique_github_constraint()
end

@doc ~S"""
Casts a changeset used for creating a user account from a github user payload
Casts a changeset used for updating a user account from a GitHub user payload
"""
@spec update_with_github_user_changeset(struct, map) :: Changeset.t
def update_with_github_user_changeset(struct, %{} = params) do
struct
|> Changeset.cast(params, [:github_avatar_url, :github_id, :github_username, :type])
|> ensure_email_without_overwriting(params)
|> Changeset.validate_required([:github_avatar_url, :github_id, :github_username, :type])
|> Changeset.unique_constraint(:email)
|> unique_github_constraint()
end

@doc ~S"""
Casts a changeset used for updating a user account from a GitHub OAuth payload
"""
@spec update_from_github_oauth_changeset(struct, map) :: Changeset.t
def update_from_github_oauth_changeset(struct, %{} = params) do
Expand Down
16 changes: 14 additions & 2 deletions lib/code_corps/github/adapters/comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule CodeCorps.GitHub.Adapters.Comment do
alias CodeCorps.{
Adapter.MapTransformer,
Comment,
GithubComment,
GitHub.Adapters.Utils.BodyDecorator
}

Expand All @@ -17,8 +18,8 @@ defmodule CodeCorps.GitHub.Adapters.Comment do
]

@doc ~S"""
Converts a Github Issue Comment payload into a set of attributes suitable to
create or update a `CodeCorps.Comment`
Converts a Github Issue Comment payload into a set of attributes suitable for
creating or updating a `CodeCorps.Comment`
"""
@spec to_comment(map) :: map
def to_comment(%{} = payload) do
Expand All @@ -36,6 +37,17 @@ defmodule CodeCorps.GitHub.Adapters.Comment do
{:url, ["url"]}
]

@doc ~S"""
Converts a `GithubComment` record into attributes with the same keys as the
GitHub API's Issue Comment
"""
@spec to_comment_attrs(GithubComment.t) :: map
def to_comment_attrs(%GithubComment{} = github_comment) do
github_comment
|> Map.from_struct
|> MapTransformer.transform_inverse(@github_comment_mapping)
end

@doc ~S"""
Converts a GitHub Issue Comment payload into a set of attributes suitable for
creating or updating a `CodeCorps.GithubComment`
Expand Down
11 changes: 11 additions & 0 deletions lib/code_corps/github/adapters/issue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ defmodule CodeCorps.GitHub.Adapters.Issue do
payload |> MapTransformer.transform(@task_mapping)
end

@doc ~S"""
Converts a `GithubIssue` record into attributes with the same keys as the
GitHub API's Issue
"""
@spec to_issue_attrs(GithubIssue.t) :: map
def to_issue_attrs(%GithubIssue{} = github_issue) do
github_issue
|> Map.from_struct
|> MapTransformer.transform_inverse(@issue_mapping)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming makes sense, but considering we were switching already, I'm thinking maybe we want to write a behavior module and document what an adapter needs to look like and what the naming convention is.

I'm just imagining where, in a couple of weeks, I write a new one and end up using a new scheme because I wasn't 100% what the old one is.


@autogenerated_github_keys ~w(closed_at comments_url created_at events_url html_url id labels_url number updated_at url)

@doc ~S"""
Expand Down
45 changes: 39 additions & 6 deletions lib/code_corps/github/adapters/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ defmodule CodeCorps.GitHub.Adapters.User do
a `CodeCorps.Task`.
"""

@mapping [
alias CodeCorps.{
Adapter.MapTransformer,
GithubUser
}

@user_mapping [
{:github_avatar_url, ["avatar_url"]},
{:github_id, ["id"]},
{:github_username, ["login"]},
Expand All @@ -16,20 +21,48 @@ defmodule CodeCorps.GitHub.Adapters.User do
Converts a Github user payload into a map of attributes suitable for creating
or updating a `CodeCorps.User`

Any `nil` values are removed here. For example, we don't want to delete
an existing email just because the GitHub payload is missing that data.
Any `nil` values are removed. For example, we don't want to delete an
existing email just because it's `nil` in the payload.

The `type` gets transformed to match our expected values for user type.
"""
@spec from_github_user(map) :: map
def from_github_user(%{} = payload) do
@spec to_user(map) :: map
def to_user(%{} = payload) do
payload
|> CodeCorps.Adapter.MapTransformer.transform(@mapping)
|> CodeCorps.Adapter.MapTransformer.transform(@user_mapping)
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Map.new
|> transform_type
end

@github_user_mapping [
{:avatar_url, ["avatar_url"]},
{:github_id, ["id"]},
{:username, ["login"]},
{:email, ["email"]},
{:type, ["type"]}
]

@doc ~S"""
Converts a GitHub User payload into a set of attributes used to create or
update a `GithubUser` record.
"""
@spec to_github_user(map) :: map
def to_github_user(%{} = payload) do
payload |> CodeCorps.Adapter.MapTransformer.transform(@github_user_mapping)
end

@doc ~S"""
Converts a `GithubUser` into a set of attributes used to create or update a
GitHub User on the GitHub API.
"""
@spec to_user_attrs(GithubUser.t) :: map
def to_user_attrs(%GithubUser{} = github_user) do
github_user
|> Map.from_struct()
|> MapTransformer.transform_inverse(@github_user_mapping)
end

@spec transform_type(map) :: map
defp transform_type(%{:type => "Bot"} = map), do: Map.put(map, :type, "bot")
defp transform_type(%{:type => "User"} = map), do: Map.put(map, :type, "user")
Expand Down
3 changes: 2 additions & 1 deletion lib/code_corps/github/api/issue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule CodeCorps.GitHub.API.Issue do

alias CodeCorps.{
GitHub,
GitHub.API,
GithubAppInstallation,
GithubIssue,
GithubRepo,
Expand All @@ -19,7 +20,7 @@ defmodule CodeCorps.GitHub.API.Issue do
def from_url(url, %GithubRepo{github_app_installation: %GithubAppInstallation{} = installation}) do
"https://api.github.com/" <> endpoint = url

with opts when is_list(opts) <- GitHub.API.opts_for(installation) do
with opts when is_list(opts) <- API.opts_for(installation) do
GitHub.request(:get, endpoint, %{}, %{}, opts)
else
{:error, github_error} -> {:error, github_error}
Expand Down
68 changes: 56 additions & 12 deletions lib/code_corps/github/api/repository.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule CodeCorps.GitHub.API.Repository do
@moduledoc ~S"""
Functions for working with issues on GitHub.
Functions for retrieving a GitHub repository's issues, pull requests, and
comments from the GitHub API.
"""

alias CodeCorps.{
Expand All @@ -10,22 +11,65 @@ defmodule CodeCorps.GitHub.API.Repository do
GithubRepo,
}

@doc ~S"""
Retrieves issues for a repository

All pages of records are retrieved.
Closed issues are included.
"""
@spec issues(GithubRepo.t) :: {:ok, list(map)} | {:error, GitHub.api_error_struct}
def issues(%GithubRepo{github_app_installation: %GithubAppInstallation{} = installation} = github_repo) do
with {:ok, access_token} <- API.Installation.get_access_token(installation),
issues <- fetch_issues(github_repo, access_token)
do
{:ok, issues}
def issues(%GithubRepo{
github_app_installation: %GithubAppInstallation{
github_account_login: owner
} = installation,
name: repo
}) do
with {:ok, access_token} <- API.Installation.get_access_token(installation) do
"repos/#{owner}/#{repo}/issues"
|> GitHub.get_all(%{}, [access_token: access_token, params: [per_page: 100, state: "all"]])
|> (&{:ok, &1}).()
else
{:error, error} -> {:error, error}
end
end

defp fetch_issues(%GithubRepo{github_app_installation: %GithubAppInstallation{github_account_login: owner}, name: repo}, access_token) do
per_page = 100
path = "repos/#{owner}/#{repo}/issues"
params = [per_page: per_page, state: "all"]
opts = [access_token: access_token, params: params]
GitHub.get_all(path, %{}, opts)
@doc ~S"""
Retrieves pull requests for a repository.

All pages of records are retrieved.
"""
@spec pulls(GithubRepo.t) :: {:ok, list(map)} | {:error, GitHub.api_error_struct}
def pulls(%GithubRepo{
github_app_installation: %GithubAppInstallation{
github_account_login: owner
} = installation,
name: repo
}) do
with {:ok, access_token} <- API.Installation.get_access_token(installation) do
"repos/#{owner}/#{repo}/pulls"
|> GitHub.get_all(%{}, [access_token: access_token, params: [per_page: 100, state: "all"]])
|> (&{:ok, &1}).()
else
{:error, error} -> {:error, error}
end
end

@doc ~S"""
Retrieves comments from all issues in a github repository.
"""
@spec issue_comments(GithubRepo.t) :: {:ok, list(map)} | {:error, GitHub.api_error_struct}
def issue_comments(%GithubRepo{
github_app_installation: %GithubAppInstallation{
github_account_login: owner
} = installation,
name: repo
}) do
with {:ok, access_token} <- API.Installation.get_access_token(installation) do
"repos/#{owner}/#{repo}/issues/comments"
|> GitHub.get_all(%{}, [access_token: access_token, params: [per_page: 100]])
|> (&{:ok, &1}).()
else
{:error, error} -> {:error, error}
end
end
end
20 changes: 18 additions & 2 deletions lib/code_corps/github/sync/comment/comment/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ defmodule CodeCorps.GitHub.Sync.Comment.Comment.Changeset do
alias Ecto.Changeset

@doc ~S"""
Constructs a changeset for syncing a task when processing a GitHub Comment
payload
Constructs a changeset for syncing a task from a GitHub API Comment payload.
"""
@spec build_changeset(Comment.t, map, GithubComment.t, Task.t, User.t) :: Changeset.t
def build_changeset(
Expand All @@ -33,6 +32,23 @@ defmodule CodeCorps.GitHub.Sync.Comment.Comment.Changeset do
comment |> update_changeset(attrs)
end

@doc ~S"""
Constructs a changeset for syncing a task from a `GithubComment` record.
"""
@spec build_changeset(Comment.t, GithubComment.t, Task.t, User.t) :: Changeset.t
def build_changeset(
%Comment{id: comment_id} = comment,
%GithubComment{} = github_comment,
%Task{} = task,
%User{} = user) do

comment_attrs = github_comment |> CommentAdapter.to_comment_attrs()
case is_nil(comment_id) do
true -> create_changeset(comment, comment_attrs, github_comment, task, user)
false -> update_changeset(comment, comment_attrs)
end
end

@create_attrs ~w(created_at markdown modified_at)a
@spec create_changeset(Comment.t, map, GithubComment.t, Task.t, User.t) :: Changeset.t
defp create_changeset(
Expand Down
Loading