Skip to content

Commit 738cb39

Browse files
authored
Merge pull request #1016 from code-corps/981-add-unique-constraint-to-user-github-id
Add unique constraints for GitHub users
2 parents 470cb51 + 1e39de9 commit 738cb39

17 files changed

Lines changed: 154 additions & 123 deletions

File tree

lib/code_corps/accounts/changesets.ex

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule CodeCorps.Accounts.Changesets do
33
Changesets for Code Corps accounts.
44
"""
55

6+
import CodeCorpsWeb.Gettext
7+
68
alias CodeCorps.GitHub.Adapters
79
alias CodeCorps.Helpers.RandomIconColor
810
alias Ecto.Changeset
@@ -15,9 +17,10 @@ defmodule CodeCorps.Accounts.Changesets do
1517
struct
1618
|> Changeset.change(params |> Adapters.User.from_github_user())
1719
|> Changeset.put_change(:sign_up_context, "github")
18-
|> Changeset.unique_constraint(:email)
1920
|> Changeset.validate_inclusion(:type, ["bot", "user"])
2021
|> RandomIconColor.generate_icon_color(:default_color)
22+
|> Changeset.unique_constraint(:email)
23+
|> unique_github_constraint()
2124
end
2225

2326
@doc ~S"""
@@ -28,8 +31,9 @@ defmodule CodeCorps.Accounts.Changesets do
2831
struct
2932
|> Changeset.cast(params, [:github_auth_token, :github_avatar_url, :github_id, :github_username, :type])
3033
|> ensure_email_without_overwriting(params)
31-
|> Changeset.unique_constraint(:email)
3234
|> Changeset.validate_required([:github_auth_token, :github_avatar_url, :github_id, :github_username, :type])
35+
|> Changeset.unique_constraint(:email)
36+
|> unique_github_constraint()
3337
end
3438

3539
@spec ensure_email_without_overwriting(Changeset.t, map) :: Changeset.t
@@ -40,4 +44,9 @@ defmodule CodeCorps.Accounts.Changesets do
4044
end
4145
end
4246
defp ensure_email_without_overwriting(%Changeset{} = changeset, _params), do: changeset
47+
48+
defp unique_github_constraint(struct) do
49+
struct
50+
|> Changeset.unique_constraint(:github_id, message: dgettext("errors", "account is already connected to someone else"))
51+
end
4352
end

lib/code_corps/github/user.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ defmodule CodeCorps.GitHub.User do
3333
do
3434
user |> do_connect(user_payload, access_token)
3535
else
36+
{:ok, %{"error" => _} = error} -> handle_oauth_error(error)
3637
{:error, error} -> {:error, error}
3738
end
3839
end
3940

4041
@spec do_connect(User.t, map, String.t) :: {:ok, User.t} | {:error, Changeset.t}
4142
defp do_connect(%User{} = user, %{} = user_payload, access_token)
4243
when is_binary(access_token) do
44+
4345
Accounts.update_from_github_oauth(user, user_payload, access_token)
4446
end
4547

@@ -53,4 +55,8 @@ defmodule CodeCorps.GitHub.User do
5355
{:error, error} -> {:error, error}
5456
end
5557
end
58+
59+
defp handle_oauth_error(%{"error_description" => message, "error_uri" => documentation_url}) do
60+
{:error, GitHub.APIError.new({401, %{"message" => message, "documentation_url" => documentation_url}})}
61+
end
5662
end

lib/code_corps_web/controllers/fallback_controller.ex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ defmodule CodeCorpsWeb.FallbackController do
1818
def call(%Conn{} = conn, {:error, %Changeset{} = changeset}) do
1919
conn
2020
|> put_status(:unprocessable_entity)
21-
|> render(:errors, data: changeset)
21+
|> render(CodeCorpsWeb.ChangesetView, "422.json", changeset: changeset)
2222
end
2323
def call(%Conn{} = conn, {:error, :not_authorized}) do
2424
conn
@@ -41,4 +41,10 @@ defmodule CodeCorpsWeb.FallbackController do
4141
|> put_status(500)
4242
|> render(CodeCorpsWeb.ErrorView, "500.json", message: "An unknown error occurred with Stripe's API.")
4343
end
44+
def call(%Conn{} = conn, {:error, %CodeCorps.GitHub.APIError{message: message}}) do
45+
Logger.info message
46+
conn
47+
|> put_status(500)
48+
|> render(CodeCorpsWeb.ErrorView, "github-error.json", message: message)
49+
end
4450
end

lib/code_corps_web/controllers/user_controller.ex

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@ defmodule CodeCorpsWeb.UserController do
4949
with {:ok, user} <- GitHub.User.connect(current_user, code, state)
5050
do
5151
conn |> render("show.json-api", data: user)
52-
else
53-
{:error, _error} ->
54-
conn
55-
|> put_status(:internal_server_error)
56-
|> render(CodeCorpsWeb.ErrorView, "500.json-api")
5752
end
5853
end
5954

lib/code_corps_web/templates/layout/app.html.eex

Lines changed: 0 additions & 35 deletions
This file was deleted.

lib/code_corps_web/templates/page/index.html.eex

Lines changed: 0 additions & 36 deletions
This file was deleted.

lib/code_corps_web/views/changeset_view.ex

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
defmodule CodeCorpsWeb.ChangesetView do
22
use CodeCorpsWeb, :view
3+
use JaSerializer.PhoenixView
4+
5+
import CodeCorpsWeb.Gettext
36

47
alias Ecto.Changeset
8+
alias JaSerializer.Formatter.Utils
59

610
@doc """
711
Traverses and translates changeset errors.
812
913
See `Ecto.Changeset.traverse_errors/2` and
1014
`CodeCorpsWeb.ErrorHelpers.translate_error/1` for more details.
1115
"""
12-
def translate_errors(changeset) do
16+
def translate_errors(%Ecto.Changeset{} = changeset) do
1317
errors =
1418
changeset
1519
|> Changeset.traverse_errors(&translate_error/1)
16-
|> format_errors
20+
|> format_errors()
1721
errors
1822
end
1923

@@ -32,18 +36,30 @@ defmodule CodeCorpsWeb.ChangesetView do
3236

3337
def create_error(attribute, message) do
3438
%{
35-
id: "VALIDATION_ERROR",
39+
detail: format_detail(attribute, message),
40+
title: message,
3641
source: %{
3742
pointer: "data/attributes/#{attribute}"
3843
},
39-
detail: message,
40-
status: 422
44+
status: "422"
4145
}
4246
end
4347

44-
def render("error.json-api", %{changeset: changeset}) do
48+
def render("422.json", %{changeset: changeset}) do
4549
# When encoded, the changeset returns its errors
4650
# as a JSON object. So we just pass it forward.
47-
%{errors: translate_errors(changeset)}
51+
%{
52+
errors: translate_errors(changeset),
53+
jsonapi: %{
54+
version: "1.0"
55+
}
56+
}
4857
end
58+
59+
defp format_detail(attribute, message) do
60+
"#{attribute |> Utils.humanize |> translate_attribute} #{message}"
61+
end
62+
63+
defp translate_attribute("Github"), do: dgettext("errors", "Github")
64+
defp translate_attribute(attribute), do: attribute
4965
end

lib/code_corps_web/views/error_view.ex

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@ defmodule CodeCorpsWeb.ErrorView do
22
use CodeCorpsWeb, :view
33
use JaSerializer.PhoenixView
44

5-
def render("stripe-400.json-api", _assigns) do
5+
def render("404.json-api", _assigns) do
66
%{
7-
id: "INVALID_GRANT",
8-
title: "This authorization code has already been used. All tokens issued with this code have been revoked.",
9-
status: 400
7+
title: "404 Not Found",
8+
detail: "404 Not Found",
9+
status: "404"
1010
}
1111
|> JaSerializer.ErrorSerializer.format
1212
end
1313

14-
def render("404.json-api", _assigns) do
14+
def render("500.json-api", _assigns) do
1515
%{
16-
id: "NOT_FOUND",
17-
title: "404 Resource not found",
18-
status: 404
16+
title: "500 Internal Server Error",
17+
detail: "500 Internal Server Error",
18+
status: "500"
1919
}
2020
|> JaSerializer.ErrorSerializer.format
2121
end
2222

23-
def render("500.json-api", _assigns) do
23+
def render("github-error.json", %{message: message}) do
2424
%{
25-
id: "INTERNAL_SERVER_ERROR",
26-
title: "500 Internal server error",
27-
status: 500
25+
title: "GitHub API error",
26+
detail: message,
27+
status: "500"
2828
}
2929
|> JaSerializer.ErrorSerializer.format
3030
end

priv/gettext/default.pot

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## This file is a PO Template file.
2+
##
3+
## `msgid`s here are often extracted from source code.
4+
## Add new translations manually only if they're dynamic
5+
## translations that can't be statically extracted.
6+
##
7+
## Run `mix gettext.extract` to bring this file up to
8+
## date. Leave `msgstr`s empty as changing them here as no
9+
## effect: edit them in PO (`.po`) files instead.
10+
msgid ""
11+
msgstr ""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## `msgid`s in this file come from POT (.pot) files.
2+
##
3+
## Do not add, change, or remove `msgid`s manually here as
4+
## they're tied to the ones in the corresponding POT file
5+
## (with the same domain).
6+
##
7+
## Use `mix gettext.extract --merge` or `mix gettext.merge`
8+
## to merge POT files into PO files.
9+
msgid ""
10+
msgstr ""
11+
"Language: en\n"

0 commit comments

Comments
 (0)