Skip to content

Commit

Permalink
Merge pull request #1143 from code-corps/1111-sync-comments
Browse files Browse the repository at this point in the history
Sync repo
  • Loading branch information
joshsmith authored Nov 4, 2017
2 parents a0dcb68 + 33cf7b3 commit 886434b
Show file tree
Hide file tree
Showing 51 changed files with 2,652 additions and 149 deletions.
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

@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

0 comments on commit 886434b

Please sign in to comment.