Skip to content

Commit

Permalink
Merge pull request #807 from code-corps/github-connect
Browse files Browse the repository at this point in the history
[GITHUB]: connect
  • Loading branch information
joshsmith committed May 31, 2017
2 parents ed4f4d1 + 2edca91 commit e072844
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 26 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export CLOUDEX_API_KEY=
export CLOUDEX_CLOUD_NAME=
export CLOUDEX_SECRET=
export CLOUDFRONT_DOMAIN=
export GITHUB_CLIENT_ID=
export GITHUB_CLIENT_SECRET=
export POSTMARK_API_KEY=
export S3_BUCKET=
export SEGMENT_WRITE_KEY=
Expand Down
6 changes: 6 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ config :guardian, Guardian,

config :code_corps, :analytics, CodeCorps.Analytics.InMemoryAPI

config :code_corps, :github_api, CodeCorps.Github.API

# Configures stripe for dev mode
config :code_corps, :stripe, Stripe
config :code_corps, :stripe_env, :dev
Expand All @@ -71,3 +73,7 @@ if System.get_env("CLOUDEX_API_KEY") == nil do
config :code_corps, :cloudex, CloudexTest
config :cloudex, api_key: "test_key", secret: "test_secret", cloud_name: "test_cloud_name"
end

config :code_corps,
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET")
4 changes: 4 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ config :code_corps,
postmark_project_acceptance_template: "1447041",
postmark_receipt_template: "1255222"

config :code_corps,
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET")

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
Expand Down
4 changes: 4 additions & 0 deletions config/remote-development.exs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ config :code_corps,
postmark_project_acceptance_template: "123",
postmark_receipt_template: "123"

config :code_corps,
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET")

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
Expand Down
4 changes: 4 additions & 0 deletions config/staging.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ config :code_corps,
postmark_project_acceptance_template: "1447022",
postmark_receipt_template: "1252361"

config :code_corps,
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET")

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
Expand Down
2 changes: 2 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ config :guardian, Guardian,

config :code_corps, :analytics, CodeCorps.Analytics.TestAPI

config :code_corps, :github_api, CodeCorps.Github.TestAPI

# Configures stripe for test mode
config :code_corps, :stripe, CodeCorps.StripeTesting
config :code_corps, :stripe_env, :test
Expand Down
34 changes: 30 additions & 4 deletions lib/code_corps/github.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
defmodule CodeCorps.Github do

alias CodeCorps.{User, Repo}

@api Application.get_env(:code_corps, :github_api)

@doc """
Temporary function until the actual behavior is implemented.
Posts code to github to receive an auth token, associates user with that
auth token.
Accepts a third parameter, which is a custom API module, for the purposes of
explicit dependency injection during testing.
Returns one of the following:
- {:ok, %CodeCorps.User{}}
- {:error, %Ecto.Changeset{}}
- {:error, "some_github_error"}
"""
def connect(user, _code), do: {:ok, user}
@spec connect(User.t, String.t, module) :: {:ok, User.t} | {:error, String.t}
def connect(%User{} = user, code, api \\ @api) do
case code |> api.connect do
{:ok, github_auth_token} -> user |> associate(%{github_auth_token: github_auth_token})
{:error, error} -> {:error, error}
end
end

@doc """
Associates user with an auth token
Returns one of the following:
- {:ok, %CodeCorps.User{}}
- {:error, %Ecto.Changeset{}}
"""
@spec associate(User.t, map) :: {:ok, User.t} | {:error, Ecto.Changeset.t}
def associate(user, params) do
user
|> User.github_associate_changeset(params)
|> User.github_association_changeset(params)
|> Repo.update()
end
end
44 changes: 44 additions & 0 deletions lib/code_corps/github/api.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule CodeCorps.Github.API do
@moduledoc """
The boundary module which communicates with the Github API using either
direct requests, or through Tentacat
"""
@behaviour CodeCorps.Github.APIContract

@client_secret Application.get_env(:code_corps, :github_client_secret)
@client_id Application.get_env(:code_corps, :github_client_id)

@base_connect_params %{
client_id: @client_id,
client_secret: @client_secret
}

@doc """
Receives a code generated through the client-side github connect process and
posts it to github.
Returns either an {:ok, access_token}, or an {:error, error_message}.
"""
@spec connect(String.t) :: {:ok, String.t} | {:error, String.t}
def connect(code) do
with {:ok, %HTTPoison.Response{body: response}} <- code |> build_connect_params() |> do_connect(),
{:ok, %{"access_token" => access_token}} <- response |> Poison.decode
do
{:ok, access_token}
else
{:ok, %{"error" => error}} -> {:error, error}
end
end

@connect_url "https://github.com/login/oauth/access_token"

@spec do_connect(map) :: {:ok, HTTPoison.Response.t | HTTPoison.AsyncResponse.t} | {:error, HTTPoison.Error.t}
defp do_connect(params) do
HTTPoison.post(@connect_url, "", [{"Accept", "application/json"}], [params: params])
end

@spec build_connect_params(String.t) :: map
defp build_connect_params(code) do
@base_connect_params |> Map.put(:code, code)
end
end
17 changes: 17 additions & 0 deletions lib/code_corps/github/api_contract.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule CodeCorps.Github.APIContract do
@moduledoc """
Defines a contract for a github API module, listing all functions the module
should implement.
This contract should be specified as behaviour for the default `Github.API`
module, as well as any custom module we inject in tests.
"""

@doc """
Receives a code string, created in the client part of the github connect process,
returns either an :ok tupple indicating a successful connect process, where
the second element is the auth token string, or an :error tuple, where the
second element is an error message, or a struct
"""
@callback connect(code :: String.t) :: {:ok, auth_token :: String.t} | {:error, error :: String.t}
end
13 changes: 13 additions & 0 deletions lib/code_corps/github/test_api.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule CodeCorps.Github.TestAPI do
@moduledoc """
The most basic implementation of an API module for testing. All functions
here should return successes.
If we want to test the wrapper module, we can specify custom API modules
during function calls.
"""
@behaviour CodeCorps.Github.APIContract

@spec connect(String.t) :: {:ok, String.t}
def connect(code), do: send(self(), {:ok, code})
end
2 changes: 2 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ defmodule CodeCorps.Mixfile do
:segment,
:sentry,
:stripity_stripe,
:tentacat,
:timber,
:timex_ecto
]
Expand Down Expand Up @@ -96,6 +97,7 @@ defmodule CodeCorps.Mixfile do
{:sentry, "~> 2.0"}, # Sentry error tracking
{:stripity_stripe, git: "https://github.com/code-corps/stripity_stripe.git", branch: "2.0"}, # Stripe
{:sweet_xml, "~> 0.5"},
{:tentacat, "~> 0.5"},
{:timber, "~> 0.4"}, # Logging
{:timex, "~> 3.0"},
{:timex_ecto, "~> 3.0"},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
"stripity_stripe": {:git, "https://github.com/code-corps/stripity_stripe.git", "1e9b87d73710ef901dd6d735d6f2d0a2cef75d1c", [branch: "2.0"]},
"sweet_xml": {:hex, :sweet_xml, "0.6.5", "dd9cde443212b505d1b5f9758feb2000e66a14d3c449f04c572f3048c66e6697", [:mix], []},
"tentacat": {:hex, :tentacat, "0.6.2", "e5e5ad95d577dd441e4dcfcab259c9d92b0049f0481a4be6453769d61a956a3b", [:mix], [{:exjsx, "~> 3.2", [hex: :exjsx, optional: false]}, {:httpoison, "~> 0.8", [hex: :httpoison, optional: false]}]},
"timber": {:hex, :timber, "0.4.7", "df3fcd79bcb4eb4b53874d906ef5f3a212937b4bc7b7c5b244745202cc389443", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: true]}, {:phoenix, "~> 1.2", [hex: :phoenix, optional: true]}, {:plug, "~> 1.2", [hex: :plug, optional: true]}, {:poison, "~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"timex": {:hex, :timex, "3.0.8", "71d5ebafcdc557c6c866cdc196c5054f587e7cd1118ad8937e2293d51fc85608", [:mix], [{:combine, "~> 0.7", [hex: :combine, optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, optional: false]}]},
"timex_ecto": {:hex, :timex_ecto, "3.1.1", "37d54f6879d96a6789bb497296531cfb853631de78e152969d95cff03c1368dd", [:mix], [{:ecto, "~> 2.1.0", [hex: :ecto, optional: false]}, {:timex, "~> 3.0", [hex: :timex, optional: false]}]},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule CodeCorps.Repo.Migrations.AddGithubAuthTokenToUsers do
use Ecto.Migration

def change do
alter table(:users) do
add :github_auth_token, :string
end
end
end
6 changes: 3 additions & 3 deletions priv/repo/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,8 @@ CREATE TABLE users (
cloudinary_public_id character varying(255),
default_color character varying(255),
sign_up_context character varying(255) DEFAULT 'default'::character varying,
github_id character varying(255)
github_id character varying(255),
github_auth_token character varying(255)
);


Expand Down Expand Up @@ -2598,5 +2599,4 @@ ALTER TABLE ONLY user_tasks
-- PostgreSQL database dump complete
--

INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713), (20170308222552), (20170313130611), (20170318032449), (20170318082740), (20170324194827), (20170424215355), (20170501225441);

INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713), (20170308222552), (20170313130611), (20170318032449), (20170318082740), (20170324194827), (20170424215355), (20170501225441), (20170526095401);
2 changes: 1 addition & 1 deletion test/controllers/user_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ defmodule CodeCorps.UserControllerTest do
test "return the user when current user connects successfully", %{conn: conn} do
user = insert(:user)

code = %{"code" => "client generated code"}
code = %{"code" => "valid_code"}

path = user_path(conn, :github_connect, code)

Expand Down
46 changes: 39 additions & 7 deletions test/lib/code_corps/github_test.exs
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
defmodule CodeCorps.GithubTest do
use CodeCorps.ModelCase
alias CodeCorps.Github

alias CodeCorps.{
Github, User
}

alias Ecto.Changeset

describe "associate/2" do
test "should update the user with the github_id" do
test "updates the user, returns :ok tuple with user" do
user = insert(:user)
params = %{github_id: "foobar"}
{:ok, result} = Github.associate(user, params)
assert result.github_id == "foobar"
params = %{github_auth_token: "foobar"}
{:ok, %User{} = returned_user} = Github.associate(user, params)
assert user.id == returned_user.id
assert returned_user.github_auth_token == "foobar"
end

test "should return the error with a changeset" do
test "returns :error tupple with changeset if there are validation errors" do
user = insert(:user)
params = %{}
{:error, changeset} = Github.associate(user, params)
{:error, %Changeset{} = changeset} = Github.associate(user, params)
refute changeset.valid?
end
end

defmodule SuccessAPI do
@behaviour CodeCorps.Github.APIContract
def connect(_code), do: {:ok, "foo_auth_token"}
end

defmodule ErrorAPI do
@behaviour CodeCorps.Github.APIContract
def connect(_code), do: {:error, "foo_error"}
end

describe "connect/2" do
test "posts to github, updates user if reply is ok, returns updated user" do
user = insert(:user)

{:ok, %User{} = returned_user} = Github.connect(user, "foo", SuccessAPI)

assert returned_user.id == user.id
assert returned_user.github_auth_token == "foo_auth_token"
end

test "posts to github, returns error if reply is not ok" do
user = insert(:user)
assert {:error, "foo_error"} == Github.connect(user, "foo", ErrorAPI)
end
end
end
12 changes: 6 additions & 6 deletions test/models/user_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,16 @@ defmodule CodeCorps.UserTest do
end
end

describe "github_associate_changeset" do
test "should cast github_id" do
describe "github_association_changeset" do
test "should cast github_auth_token" do
user = insert(:user)
changeset = user |> User.github_associate_changeset(%{github_id: "foobar"})
changeset = user |> User.github_association_changeset(%{github_auth_token: "foobar"})
assert changeset.valid?
assert changeset.changes.github_id == "foobar"
assert changeset.changes.github_auth_token == "foobar"
end
test "github_id should be required" do
test "github_auth_token should be required" do
user = insert(:user)
changeset = user |> User.github_associate_changeset(%{})
changeset = user |> User.github_association_changeset(%{})
refute changeset.valid?
end
end
Expand Down
2 changes: 1 addition & 1 deletion web/controllers/user_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule CodeCorps.UserController do
end
end

def github_connect(conn, code) do
def github_connect(conn, %{"code" => code}) do
current_user = Guardian.Plug.current_resource(conn)
with {:ok, user} <- Github.connect(current_user, code)
do
Expand Down
2 changes: 2 additions & 0 deletions web/models/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ defmodule CodeCorps.Project do
alias CodeCorps.Services.MarkdownRendererService
alias CodeCorps.TaskList

alias Ecto.Changeset

@type t :: %__MODULE__{}

schema "projects" do
Expand Down
10 changes: 6 additions & 4 deletions web/models/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ defmodule CodeCorps.User do
field :twitter, :string
field :username, :string
field :website, :string
field :github_id, :string
field :state, :string, default: "signed_up"
field :state_transition, :string, virtual: true

field :github_id, :string
field :github_auth_token, :string

has_one :slugged_route, SluggedRoute

has_many :project_users, CodeCorps.ProjectUser
Expand Down Expand Up @@ -95,10 +97,10 @@ defmodule CodeCorps.User do
|> apply_state_transition(struct)
end

def github_associate_changeset(struct, params) do
def github_association_changeset(struct, params) do
struct
|> cast(params, [:github_id])
|> validate_required([:github_id])
|> cast(params, [:github_auth_token])
|> validate_required([:github_auth_token])
end

def reset_password_changeset(struct, params) do
Expand Down

0 comments on commit e072844

Please sign in to comment.