├── priv
├── repo
│ ├── migrations
│ │ ├── .formatter.exs
│ │ └── 20191227155621_create_users.exs
│ └── seeds.exs
└── gettext
│ ├── en
│ └── LC_MESSAGES
│ │ └── errors.po
│ └── errors.pot
├── test
├── test_helper.exs
├── api_ecommerce_web
│ ├── views
│ │ └── error_view_test.exs
│ └── controllers
│ │ └── user_controller_test.exs
├── support
│ ├── channel_case.ex
│ ├── conn_case.ex
│ └── data_case.ex
└── api_ecommerce
│ └── auth_test.exs
├── lib
├── api_ecommerce
│ ├── ecto_enums.ex
│ ├── repo.ex
│ ├── application.ex
│ ├── auth
│ │ └── user.ex
│ └── auth.ex
├── api_ecommerce.ex
├── api_ecommerce_web
│ ├── auth_error_handler.ex
│ ├── auth_pipeline.ex
│ ├── router.ex
│ ├── views
│ │ ├── changeset_view.ex
│ │ ├── error_view.ex
│ │ ├── user_view.ex
│ │ └── error_helpers.ex
│ ├── controllers
│ │ ├── fallback_controller.ex
│ │ └── user_controller.ex
│ ├── gettext.ex
│ ├── channels
│ │ └── user_socket.ex
│ └── endpoint.ex
├── guardian.ex
└── api_ecommerce_web.ex
├── .idea
├── .gitignore
├── libraries
│ ├── geo.xml
│ ├── jsx.xml
│ ├── x509.xml
│ ├── binpp.xml
│ ├── credo.xml
│ ├── ex_doc.xml
│ ├── jsone.xml
│ ├── ojson.xml
│ ├── poison.xml
│ ├── benchee.xml
│ ├── dialyxir.xml
│ ├── dialyze.xml
│ ├── earmark.xml
│ ├── hackney.xml
│ ├── inch_ex.xml
│ ├── kadabra.xml
│ ├── libdecaf.xml
│ ├── libsodium.xml
│ ├── mariaex.xml
│ ├── excoveralls.xml
│ ├── benchee_json.xml
│ ├── phoenix_html.xml
│ ├── websocket_client.xml
│ ├── cowboy.xml
│ ├── cowlib.xml
│ ├── ranch.xml
│ ├── base64url.xml
│ ├── telemetry.xml
│ ├── postgrex.xml
│ ├── ecto.xml
│ ├── jason.xml
│ ├── myxql.xml
│ ├── decimal.xml
│ ├── ecto_sql.xml
│ ├── guardian.xml
│ ├── ecto_enum.xml
│ ├── connection.xml
│ ├── elixir_make.xml
│ ├── plug_cowboy.xml
│ ├── plug_crypto.xml
│ ├── phoenix_ecto.xml
│ ├── db_connection.xml
│ ├── phoenix_pubsub.xml
│ ├── plug.xml
│ ├── mime.xml
│ ├── gettext.xml
│ ├── phoenix.xml
│ ├── comeonin.xml
│ ├── jose.xml
│ └── bcrypt_elixir.xml
├── dictionaries
│ └── luisl.xml
├── vcs.xml
├── modules.xml
├── misc.xml
├── dataSources.xml
└── api-ecommerce.iml
├── .formatter.exs
├── config
├── test.exs
├── config.exs
├── prod.secret.exs
├── dev.exs
└── prod.exs
├── README.md
├── .gitignore
├── mix.exs
└── mix.lock
/priv/repo/migrations/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:ecto_sql],
3 | inputs: ["*.exs"]
4 | ]
5 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 | Ecto.Adapters.SQL.Sandbox.mode(ApiEcommerce.Repo, :manual)
3 |
--------------------------------------------------------------------------------
/lib/api_ecommerce/ecto_enums.ex:
--------------------------------------------------------------------------------
1 | import EctoEnum
2 | defenum StatusEnum, active: 0, inactive: 1, deleted: 2
3 | defenum RoleEnum, member: 0, admin: 1
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
3 | # Datasource local storage ignored files
4 | /dataSources/
5 | /dataSources.local.xml
6 |
--------------------------------------------------------------------------------
/lib/api_ecommerce/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Repo do
2 | use Ecto.Repo,
3 | otp_app: :api_ecommerce,
4 | adapter: Ecto.Adapters.MyXQL
5 | end
6 |
--------------------------------------------------------------------------------
/.idea/libraries/geo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/jsx.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/x509.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/binpp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/credo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/ex_doc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/jsone.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/ojson.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/poison.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/benchee.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/dialyxir.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/dialyze.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/earmark.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/hackney.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/inch_ex.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/kadabra.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/libdecaf.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/libsodium.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/mariaex.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/excoveralls.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:ecto, :phoenix],
3 | inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
4 | subdirectories: ["priv/*/migrations"]
5 | ]
6 |
--------------------------------------------------------------------------------
/.idea/libraries/benchee_json.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/phoenix_html.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/websocket_client.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/dictionaries/luisl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ecommerce
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/cowboy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/libraries/cowlib.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/libraries/ranch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/libraries/base64url.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/libraries/telemetry.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/api_ecommerce.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce do
2 | @moduledoc """
3 | ApiEcommerce keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/libraries/postgrex.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/auth_error_handler.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.AuthErrorHandler do
2 | import Plug.Conn
3 |
4 | @behaviour Guardian.Plug.ErrorHandler
5 |
6 | @impl Guardian.Plug.ErrorHandler
7 | def auth_error(conn, {type, _reason}, _opts) do
8 | conn
9 | |> put_resp_content_type("application/json")
10 | |> send_resp(401, '')
11 | end
12 |
13 | end
--------------------------------------------------------------------------------
/.idea/libraries/ecto.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/jason.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/myxql.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/priv/repo/seeds.exs:
--------------------------------------------------------------------------------
1 | # Script for populating the database. You can run it as:
2 | #
3 | # mix run priv/repo/seeds.exs
4 | #
5 | # Inside the script, you can read and write to any of your
6 | # repositories directly:
7 | #
8 | # ApiEcommerce.Repo.insert!(%ApiEcommerce.SomeSchema{})
9 | #
10 | # We recommend using the bang functions (`insert!`, `update!`
11 | # and so on) as they will fail if something goes wrong.
12 |
--------------------------------------------------------------------------------
/.idea/libraries/decimal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/ecto_sql.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/guardian.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/auth_pipeline.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Guardian.AuthPipeline do
2 | use Guardian.Plug.Pipeline, otp_app: :api_ecommerce,
3 | module: ApiEcommerce.Guardian,
4 | error_handler: ApiEcommerce.AuthErrorHandler
5 |
6 | plug Guardian.Plug.VerifyHeader, realm: "Bearer"
7 | plug Guardian.Plug.EnsureAuthenticated
8 | plug Guardian.Plug.LoadResource
9 | end
--------------------------------------------------------------------------------
/.idea/libraries/ecto_enum.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/connection.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/elixir_make.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/plug_cowboy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/plug_crypto.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/phoenix_ecto.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/db_connection.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/phoenix_pubsub.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/plug.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/libraries/mime.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/libraries/gettext.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/libraries/phoenix.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/libraries/comeonin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/libraries/jose.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/dataSources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mysql.8
6 | true
7 | com.mysql.cj.jdbc.Driver
8 | jdbc:mysql://localhost:3306
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/api_ecommerce_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.ErrorViewTest do
2 | use ApiEcommerceWeb.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.json" do
8 | assert render(ApiEcommerceWeb.ErrorView, "404.json", []) == %{errors: %{detail: "Not Found"}}
9 | end
10 |
11 | test "renders 500.json" do
12 | assert render(ApiEcommerceWeb.ErrorView, "500.json", []) ==
13 | %{errors: %{detail: "Internal Server Error"}}
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/guardian.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Guardian do
2 | use Guardian, otp_app: :api_ecommerce
3 |
4 | def subject_for_token(user, _claims) do
5 | sub = to_string(user.id)
6 | {:ok, sub}
7 | end
8 |
9 | def subject_for_token(_, _) do
10 | {:error, :reason_for_error}
11 | end
12 |
13 | def resource_from_claims(claims) do
14 | id = claims["sub"]
15 | resource = ApiEcommerce.Auth.get_user!(id)
16 | {:ok, resource}
17 | end
18 |
19 | def resource_from_claims(_claims) do
20 | {:error, :reason_for_error}
21 | end
22 | end
--------------------------------------------------------------------------------
/.idea/libraries/bcrypt_elixir.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # Configure your database
4 | config :api_ecommerce, ApiEcommerce.Repo,
5 | username: "admin",
6 | password: "admin",
7 | database: "api_ecommerce_test",
8 | hostname: "localhost",
9 | pool: Ecto.Adapters.SQL.Sandbox
10 |
11 | # We don't run a server during test. If one is required,
12 | # you can enable the server option below.
13 | config :api_ecommerce, ApiEcommerceWeb.Endpoint,
14 | http: [port: 4002],
15 | server: false
16 |
17 | # Print only warnings and errors during test
18 | config :logger, level: :warn
19 |
20 | config :bcrypt_elixir, :log_rounds, 4
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.Router do
2 | use ApiEcommerceWeb, :router
3 |
4 | alias ApiEcommerce.Guardian
5 |
6 | pipeline :api do
7 | plug :accepts, ["json"]
8 | end
9 |
10 | pipeline :auth do
11 | plug Guardian.AuthPipeline
12 | end
13 |
14 | scope "/v1", ApiEcommerceWeb do
15 | pipe_through :api
16 |
17 | post "/users/sign_in", UserController, :sign_in
18 | post "/users/sign_up", UserController, :create
19 | end
20 |
21 | scope "/v1", ApiEcommerceWeb do
22 | pipe_through [:api, :auth]
23 |
24 | resources "/users", UserController, except: [:new, :create, :edit]
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/views/changeset_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.ChangesetView do
2 | use ApiEcommerceWeb, :view
3 |
4 | @doc """
5 | Traverses and translates changeset errors.
6 |
7 | See `Ecto.Changeset.traverse_errors/2` and
8 | `ApiEcommerceWeb.ErrorHelpers.translate_error/1` for more details.
9 | """
10 | def translate_errors(changeset) do
11 | Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
12 | end
13 |
14 | def render("error.json", %{changeset: changeset}) do
15 | # When encoded, the changeset returns its errors
16 | # as a JSON object. So we just pass it forward.
17 | %{errors: translate_errors(changeset)}
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/controllers/fallback_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.FallbackController do
2 | @moduledoc """
3 | Translates controller action results into valid `Plug.Conn` responses.
4 |
5 | See `Phoenix.Controller.action_fallback/1` for more details.
6 | """
7 | use ApiEcommerceWeb, :controller
8 |
9 | def call(conn, {:error, :not_found}) do
10 | conn
11 | |> put_status(:not_found)
12 | |> put_view(ApiEcommerceWeb.ErrorView)
13 | |> render(:"404")
14 | end
15 |
16 | def call(conn, {:error, %Ecto.Changeset{}}) do
17 | conn
18 | |> put_status(:unprocessable_entity)
19 | |> put_view(ApiEcommerceWeb.ErrorView)
20 | |> render(:"422")
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ApiEcommerce
2 |
3 | To start your Phoenix server:
4 |
5 | * Install dependencies with `mix deps.get`
6 | * Create and migrate your database with `mix ecto.setup`
7 | * Start Phoenix endpoint with `mix phx.server`
8 |
9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
10 |
11 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
12 |
13 | ## Learn more
14 |
15 | * Official website: http://www.phoenixframework.org/
16 | * Guides: https://hexdocs.pm/phoenix/overview.html
17 | * Docs: https://hexdocs.pm/phoenix
18 | * Mailing list: http://groups.google.com/group/phoenix-talk
19 | * Source: https://github.com/phoenixframework/phoenix
20 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.ErrorView do
2 | use ApiEcommerceWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.json", _assigns) do
7 | # %{errors: %{detail: "Internal Server Error"}}
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.json" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
15 | end
16 |
17 | def render("401.json", %{message: message}) do
18 | %{errors: %{detail: message}}
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import ApiEcommerceWeb.Gettext
9 |
10 | # Simple translation
11 | gettext("Here is the string to translate")
12 |
13 | # Plural translation
14 | ngettext("Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3)
17 |
18 | # Domain-based translation
19 | dgettext("errors", "Here is the error message to translate")
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :api_ecommerce
24 | end
25 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20191227155621_create_users.exs:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Repo.Migrations.CreateUsers do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:users, primary_key: false) do
6 | add :id, :binary_id, primary_key: true
7 | add :name, :string
8 | add :email, :string, null: false
9 | add :status, :tinyint, null: false, default: 0
10 | add :role, :tinyint, null: false, default: 0
11 | add :password_hash, :string
12 | add :recovery_token, :string
13 | add :recovery_token_created_at, :naive_datetime
14 |
15 | timestamps()
16 | end
17 |
18 | create index(:users, [:id])
19 | create index(:users, [:status])
20 | create index(:users, [:role])
21 | create unique_index(:users, [:recovery_token])
22 | create unique_index(:users, [:email])
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | api_ecommerce-*.tar
24 |
25 | # Since we are building assets from assets/,
26 | # we ignore priv/static. You may want to comment
27 | # this depending on your deployment strategy.
28 | /priv/static/
29 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/views/user_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.UserView do
2 | use ApiEcommerceWeb, :view
3 | alias ApiEcommerceWeb.UserView
4 |
5 | def render("index.json", %{users: users}) do
6 | %{data: render_many(users, UserView, "user.json")}
7 | end
8 |
9 | def render("show.json", %{user: user}) do
10 | %{data: render_one(user, UserView, "user.json")}
11 | end
12 |
13 | def render("sign_in.json", %{user: user, token: token}) do
14 | %{data: Map.merge(render_one(user, UserView, "user.json"), %{token: token})}
15 | end
16 |
17 | def render("sign_up.json", %{user: user, token: token}) do
18 | %{data: Map.merge(render_one(user, UserView, "user.json"), %{token: token})}
19 | end
20 |
21 | def render("user.json", %{user: user}) do
22 | %{
23 | id: user.id,
24 | email: user.email,
25 | role: user.role,
26 | status: user.status
27 | }
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build common data structures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | # The default endpoint for testing
24 | @endpoint ApiEcommerceWeb.Endpoint
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ApiEcommerce.Repo)
30 |
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(ApiEcommerce.Repo, {:shared, self()})
33 | end
34 |
35 | :ok
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/api_ecommerce/application.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | def start(_type, _args) do
9 | # List all child processes to be supervised
10 | children = [
11 | # Start the Ecto repository
12 | ApiEcommerce.Repo,
13 | # Start the PubSub system
14 | {Phoenix.PubSub, name: MyApp.PubSub},
15 | # Start the endpoint when the application starts
16 | ApiEcommerceWeb.Endpoint
17 | # Starts a worker by calling: ApiEcommerce.Worker.start_link(arg)
18 | # {ApiEcommerce.Worker, arg},
19 | ]
20 |
21 | # See https://hexdocs.pm/elixir/Supervisor.html
22 | # for other strategies and supported options
23 | opts = [strategy: :one_for_one, name: ApiEcommerce.Supervisor]
24 | Supervisor.start_link(children, opts)
25 | end
26 |
27 | # Tell Phoenix to update the endpoint configuration
28 | # whenever the application is updated.
29 | def config_change(changed, _new, removed) do
30 | ApiEcommerceWeb.Endpoint.config_change(changed, removed)
31 | :ok
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", ApiEcommerceWeb.RoomChannel
6 |
7 | # Socket params are passed from the client and can
8 | # be used to verify and authenticate a user. After
9 | # verification, you can put default assigns into
10 | # the socket that will be set for all channels, ie
11 | #
12 | # {:ok, assign(socket, :user_id, verified_user_id)}
13 | #
14 | # To deny connection, return `:error`.
15 | #
16 | # See `Phoenix.Token` documentation for examples in
17 | # performing token verification on connect.
18 | def connect(_params, socket, _connect_info) do
19 | {:ok, socket}
20 | end
21 |
22 | # Socket id's are topics that allow you to identify all sockets for a given user:
23 | #
24 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
25 | #
26 | # Would allow you to broadcast a "disconnect" event and terminate
27 | # all active sockets and channels for a given user:
28 | #
29 | # ApiEcommerceWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
30 | #
31 | # Returning `nil` makes this socket anonymous.
32 | def id(_socket), do: nil
33 | end
34 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | use Mix.Config
9 |
10 | config :api_ecommerce,
11 | ecto_repos: [ApiEcommerce.Repo],
12 | generators: [binary_id: true]
13 |
14 | # Configures the endpoint
15 | config :api_ecommerce, ApiEcommerceWeb.Endpoint,
16 | url: [host: "localhost"],
17 | secret_key_base: "xpSGNOCTXLgEQy1K1X+Q9aGfZZXx5Y37FxaXsUgrqf/zKk4KZwpPDCK0nWlUNrOh",
18 | render_errors: [view: ApiEcommerceWeb.ErrorView, accepts: ~w(json)],
19 | pubsub_server: MyApp.PubSub
20 |
21 | config :api_ecommerce, ApiEcommerce.Guardian,
22 | issuer: "api_ecommerce",
23 | secret_key: "RFl88y/O+rrGqRzWwdTGpsF68o07jhz60tfUnh0mTKhkGpeGo3Adzc8+xDuxXyd5"
24 |
25 | # Configures Elixir's Logger
26 | config :logger, :console,
27 | format: "$time $metadata[$level] $message\n",
28 | metadata: [:request_id]
29 |
30 | # Use Jason for JSON parsing in Phoenix
31 | config :phoenix, :json_library, Jason
32 |
33 | # Import environment specific config. This must remain at the bottom
34 | # of this file so it overrides the configuration defined above.
35 | import_config "#{Mix.env()}.exs"
36 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | @doc """
7 | Translates an error message using gettext.
8 | """
9 | def translate_error({msg, opts}) do
10 | # When using gettext, we typically pass the strings we want
11 | # to translate as a static argument:
12 | #
13 | # # Translate "is invalid" in the "errors" domain
14 | # dgettext("errors", "is invalid")
15 | #
16 | # # Translate the number of files with plural rules
17 | # dngettext("errors", "1 file", "%{count} files", count)
18 | #
19 | # Because the error messages we show in our forms and APIs
20 | # are defined inside Ecto, we need to translate them dynamically.
21 | # This requires us to call the Gettext module passing our gettext
22 | # backend as first argument.
23 | #
24 | # Note we use the "errors" domain, which means translations
25 | # should be written to the errors.po file. The :count option is
26 | # set by Ecto and indicates we should also apply plural rules.
27 | if count = opts[:count] do
28 | Gettext.dngettext(ApiEcommerceWeb.Gettext, "errors", msg, msg, count, opts)
29 | else
30 | Gettext.dgettext(ApiEcommerceWeb.Gettext, "errors", msg, opts)
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/config/prod.secret.exs:
--------------------------------------------------------------------------------
1 | # In this file, we load production configuration and secrets
2 | # from environment variables. You can also hardcode secrets,
3 | # although such is generally not recommended and you have to
4 | # remember to add this file to your .gitignore.
5 | use Mix.Config
6 |
7 | database_url =
8 | System.get_env("DATABASE_URL") ||
9 | raise """
10 | environment variable DATABASE_URL is missing.
11 | For example: ecto://USER:PASS@HOST/DATABASE
12 | """
13 |
14 | config :api_ecommerce, ApiEcommerce.Repo,
15 | # ssl: true,
16 | url: database_url,
17 | pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
18 |
19 | secret_key_base =
20 | System.get_env("SECRET_KEY_BASE") ||
21 | raise """
22 | environment variable SECRET_KEY_BASE is missing.
23 | You can generate one by calling: mix phx.gen.secret
24 | """
25 |
26 | config :api_ecommerce, ApiEcommerceWeb.Endpoint,
27 | http: [:inet6, port: String.to_integer(System.get_env("PORT") || "4000")],
28 | secret_key_base: secret_key_base
29 |
30 | # ## Using releases (Elixir v1.9+)
31 | #
32 | # If you are doing OTP releases, you need to instruct Phoenix
33 | # to start each relevant endpoint:
34 | #
35 | # config :api_ecommerce, ApiEcommerceWeb.Endpoint, server: true
36 | #
37 | # Then you can assemble a release by calling `mix release`.
38 | # See `mix help release` for more information.
39 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common data structures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | we enable the SQL sandbox, so changes done to the database
12 | are reverted at the end of every test. If you are using
13 | PostgreSQL, you can even run database tests asynchronously
14 | by setting `use ApiEcommerceWeb.ConnCase, async: true`, although
15 | this option is not recommendded for other databases.
16 | """
17 |
18 | use ExUnit.CaseTemplate
19 |
20 | using do
21 | quote do
22 | # Import conveniences for testing with connections
23 | import Plug.Conn
24 | import Phoenix.ConnTest
25 | alias ApiEcommerceWeb.Router.Helpers, as: Routes
26 |
27 | # The default endpoint for testing
28 | @endpoint ApiEcommerceWeb.Endpoint
29 | end
30 | end
31 |
32 | setup tags do
33 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ApiEcommerce.Repo)
34 |
35 | unless tags[:async] do
36 | Ecto.Adapters.SQL.Sandbox.mode(ApiEcommerce.Repo, {:shared, self()})
37 | end
38 |
39 | {:ok, conn: Phoenix.ConnTest.build_conn()}
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :api_ecommerce
3 |
4 | socket "/socket", ApiEcommerceWeb.UserSocket,
5 | websocket: true,
6 | longpoll: false
7 |
8 | # Serve at "/" the static files from "priv/static" directory.
9 | #
10 | # You should set gzip to true if you are running phx.digest
11 | # when deploying your static files in production.
12 | plug Plug.Static,
13 | at: "/",
14 | from: :api_ecommerce,
15 | gzip: false,
16 | only: ~w(css fonts images js favicon.ico robots.txt)
17 |
18 | # Code reloading can be explicitly enabled under the
19 | # :code_reloader configuration of your endpoint.
20 | if code_reloading? do
21 | plug Phoenix.CodeReloader
22 | end
23 |
24 | plug Plug.RequestId
25 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
26 |
27 | plug Plug.Parsers,
28 | parsers: [:urlencoded, :multipart, :json],
29 | pass: ["*/*"],
30 | json_decoder: Phoenix.json_library()
31 |
32 | plug Plug.MethodOverride
33 | plug Plug.Head
34 |
35 | # The session will be stored in the cookie and signed,
36 | # this means its contents can be read but not tampered with.
37 | # Set :encryption_salt if you would also like to encrypt it.
38 | plug Plug.Session,
39 | store: :cookie,
40 | key: "_api_ecommerce_key",
41 | signing_salt: "RUXh0u9W"
42 |
43 | plug ApiEcommerceWeb.Router
44 | end
45 |
--------------------------------------------------------------------------------
/lib/api_ecommerce/auth/user.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Auth.User do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | @primary_key {:id, :binary_id, autogenerate: true}
6 | @foreign_key_type :binary_id
7 | schema "users" do
8 | field :name, :string
9 | field :email, :string
10 | field :role, RoleEnum, default: :member
11 | field :status, StatusEnum, default: :active
12 | field :recovery_token, :string
13 | field :recovery_token_created_at, :naive_datetime
14 | field :password_hash, :string
15 | field :password, :string, virtual: true
16 | field :password_confirmation, :string, virtual: true
17 |
18 | timestamps()
19 | end
20 |
21 | @doc false
22 | def changeset(user, attrs) do
23 | user
24 | |> cast(attrs, [:name, :email, :status, :role, :password, :password_confirmation])
25 | |> validate_required([:email, :status, :role, :password, :password_confirmation])
26 | |> validate_format(:email, ~r/@/)
27 | |> validate_length(:password, min: 6)
28 | |> validate_confirmation(:password) # Check that password === password_confirmation
29 | |> unique_constraint(:email)
30 | |> put_password_hash()
31 | end
32 |
33 | defp put_password_hash(%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset) do
34 | changeset
35 | |> change(Bcrypt.add_hash(password))
36 | |> change(%{password_confirmation: nil})
37 | end
38 |
39 | defp put_password_hash(changeset) do
40 | changeset
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/test/support/data_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.DataCase do
2 | @moduledoc """
3 | This module defines the setup for tests requiring
4 | access to the application's data layer.
5 |
6 | You may define functions here to be used as helpers in
7 | your tests.
8 |
9 | Finally, if the test case interacts with the database,
10 | we enable the SQL sandbox, so changes done to the database
11 | are reverted at the end of every test. If you are using
12 | PostgreSQL, you can even run database tests asynchronously
13 | by setting `use ApiEcommerceWeb.DataCase, async: true`, although
14 | this option is not recommendded for other databases.
15 | """
16 |
17 | use ExUnit.CaseTemplate
18 |
19 | using do
20 | quote do
21 | alias ApiEcommerce.Repo
22 |
23 | import Ecto
24 | import Ecto.Changeset
25 | import Ecto.Query
26 | import ApiEcommerce.DataCase
27 | end
28 | end
29 |
30 | setup tags do
31 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ApiEcommerce.Repo)
32 |
33 | unless tags[:async] do
34 | Ecto.Adapters.SQL.Sandbox.mode(ApiEcommerce.Repo, {:shared, self()})
35 | end
36 |
37 | :ok
38 | end
39 |
40 | @doc """
41 | A helper that transforms changeset errors into a map of messages.
42 |
43 | assert {:error, changeset} = Accounts.create_user(%{password: "short"})
44 | assert "password is too short" in errors_on(changeset).password
45 | assert %{password: ["password is too short"]} = errors_on(changeset)
46 |
47 | """
48 | def errors_on(changeset) do
49 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
50 | Regex.replace(~r"%{(\w+)}", message, fn _, key ->
51 | opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
52 | end)
53 | end)
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use ApiEcommerceWeb, :controller
9 | use ApiEcommerceWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: ApiEcommerceWeb
23 |
24 | import Plug.Conn
25 | import ApiEcommerceWeb.Gettext
26 | alias ApiEcommerceWeb.Router.Helpers, as: Routes
27 | end
28 | end
29 |
30 | def view do
31 | quote do
32 | use Phoenix.View,
33 | root: "lib/api_ecommerce_web/templates",
34 | namespace: ApiEcommerceWeb
35 |
36 | # Import convenience functions from controllers
37 | import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
38 |
39 | import ApiEcommerceWeb.ErrorHelpers
40 | import ApiEcommerceWeb.Gettext
41 | alias ApiEcommerceWeb.Router.Helpers, as: Routes
42 | end
43 | end
44 |
45 | def router do
46 | quote do
47 | use Phoenix.Router
48 | import Plug.Conn
49 | import Phoenix.Controller
50 | end
51 | end
52 |
53 | def channel do
54 | quote do
55 | use Phoenix.Channel
56 | import ApiEcommerceWeb.Gettext
57 | end
58 | end
59 |
60 | @doc """
61 | When used, dispatch to the appropriate controller/view/etc.
62 | """
63 | defmacro __using__(which) when is_atom(which) do
64 | apply(__MODULE__, which, [])
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # Configure your database
4 | config :api_ecommerce, ApiEcommerce.Repo,
5 | username: "admin",
6 | password: "admin",
7 | database: "api_ecommerce_dev",
8 | hostname: "localhost",
9 | show_sensitive_data_on_connection_error: true,
10 | pool_size: 10
11 |
12 | # For development, we disable any cache and enable
13 | # debugging and code reloading.
14 | #
15 | # The watchers configuration can be used to run external
16 | # watchers to your application. For example, we use it
17 | # with webpack to recompile .js and .css sources.
18 | config :api_ecommerce, ApiEcommerceWeb.Endpoint,
19 | http: [port: 4000],
20 | debug_errors: true,
21 | code_reloader: true,
22 | check_origin: false,
23 | watchers: []
24 |
25 | # ## SSL Support
26 | #
27 | # In order to use HTTPS in development, a self-signed
28 | # certificate can be generated by running the following
29 | # Mix task:
30 | #
31 | # mix phx.gen.cert
32 | #
33 | # Note that this task requires Erlang/OTP 20 or later.
34 | # Run `mix help phx.gen.cert` for more information.
35 | #
36 | # The `http:` config above can be replaced with:
37 | #
38 | # https: [
39 | # port: 4001,
40 | # cipher_suite: :strong,
41 | # keyfile: "priv/cert/selfsigned_key.pem",
42 | # certfile: "priv/cert/selfsigned.pem"
43 | # ],
44 | #
45 | # If desired, both `http:` and `https:` keys can be
46 | # configured to run both http and https servers on
47 | # different ports.
48 |
49 | # Do not include metadata nor timestamps in development logs
50 | config :logger, :console, format: "[$level] $message\n"
51 |
52 | # Set a higher stacktrace during development. Avoid configuring such
53 | # in production as building large stacktraces may be expensive.
54 | config :phoenix, :stacktrace_depth, 20
55 |
56 | # Initialize plugs at runtime for faster development compilation
57 | config :phoenix, :plug_init_mode, :runtime
58 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :api_ecommerce,
7 | version: "0.1.0",
8 | elixir: "~> 1.5",
9 | elixirc_paths: elixirc_paths(Mix.env()),
10 | compilers: [:phoenix, :gettext] ++ Mix.compilers(),
11 | start_permanent: Mix.env() == :prod,
12 | aliases: aliases(),
13 | deps: deps()
14 | ]
15 | end
16 |
17 | # Configuration for the OTP application.
18 | #
19 | # Type `mix help compile.app` for more information.
20 | def application do
21 | [
22 | mod: {ApiEcommerce.Application, []},
23 | extra_applications: [:logger, :runtime_tools]
24 | ]
25 | end
26 |
27 | # Specifies which paths to compile per environment.
28 | defp elixirc_paths(:test), do: ["lib", "test/support"]
29 | defp elixirc_paths(_), do: ["lib"]
30 |
31 | # Specifies your project dependencies.
32 | #
33 | # Type `mix help deps` for examples and options.
34 | defp deps do
35 | [
36 | {:phoenix, "~> 1.5.4"},
37 | {:phoenix_pubsub, "~> 2.0"},
38 | {:phoenix_ecto, "~> 4.0"},
39 | {:ecto_sql, "~> 3.4.5"},
40 | {:myxql, ">= 0.0.0"},
41 | {:gettext, "~> 0.18.0"},
42 | {:jason, "~> 1.2.1"},
43 | {:plug_cowboy, "~> 2.3.0"},
44 | {:bcrypt_elixir, "~> 2.2.0"},
45 | {:guardian, "~> 2.1.1"},
46 | {:ecto_enum, "~> 1.4"}
47 | ]
48 | end
49 |
50 | # Aliases are shortcuts or tasks specific to the current project.
51 | # For example, to create, migrate and run the seeds file at once:
52 | #
53 | # $ mix ecto.setup
54 | #
55 | # See the documentation for `Mix` for more info on aliases.
56 | defp aliases do
57 | [
58 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
59 | "ecto.reset": ["ecto.drop", "ecto.setup"],
60 | test: ["ecto.create --quiet", "ecto.migrate", "test"]
61 | ]
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/api_ecommerce_web/controllers/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.UserController do
2 | use ApiEcommerceWeb, :controller
3 |
4 | alias ApiEcommerce.Auth
5 | alias ApiEcommerce.Auth.User
6 | alias ApiEcommerce.Guardian
7 |
8 | action_fallback ApiEcommerceWeb.FallbackController
9 |
10 | def index(conn, _params) do
11 | users = Auth.list_users()
12 | render(conn, "index.json", users: users)
13 | end
14 |
15 | def create(conn, %{"user" => user_params}) do
16 | with {:ok, %User{} = user} <- Auth.create_user(user_params),
17 | {:ok, token, _claims} <- Guardian.encode_and_sign(user) do
18 | conn
19 | |> put_status(:created)
20 | |> put_resp_header("location", Routes.user_path(conn, :show, user))
21 | |> render("sign_up.json", user: user, token: token)
22 | end
23 | end
24 |
25 | def show(conn, %{"id" => id}) do
26 | user = Auth.get_user!(id)
27 | render(conn, "show.json", user: user)
28 | end
29 |
30 | def update(conn, %{"id" => id, "user" => user_params}) do
31 | user = Auth.get_user!(id)
32 |
33 | with {:ok, %User{} = user} <- Auth.update_user(user, user_params) do
34 | render(conn, "show.json", user: user)
35 | end
36 | end
37 |
38 | def delete(conn, %{"id" => id}) do
39 | user = Auth.get_user!(id)
40 |
41 | with {:ok, %User{}} <- Auth.delete_user(user) do
42 | send_resp(conn, :no_content, "")
43 | end
44 | end
45 |
46 | def sign_in(conn, %{"email" => email, "password" => password}) do
47 | case ApiEcommerce.Auth.authenticate_user(email, password) do
48 | {:ok, user, token} ->
49 | conn
50 | |> put_status(:ok)
51 | |> put_view(ApiEcommerceWeb.UserView)
52 | |> render("sign_in.json", user: user, token: token)
53 |
54 | {:error, message} ->
55 | conn
56 | |> put_status(:unauthorized)
57 | |> put_view(ApiEcommerceWeb.ErrorView)
58 | |> render("401.json", message: message)
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, don't forget to configure the url host
4 | # to something meaningful, Phoenix uses this information
5 | # when generating URLs.
6 | #
7 | # Note we also include the path to a cache manifest
8 | # containing the digested version of static files. This
9 | # manifest is generated by the `mix phx.digest` task,
10 | # which you should run after static files are built and
11 | # before starting your production server.
12 | config :api_ecommerce, ApiEcommerceWeb.Endpoint,
13 | url: [host: "example.com", port: 80],
14 | cache_static_manifest: "priv/static/cache_manifest.json"
15 |
16 | # Do not print debug messages in production
17 | config :logger, level: :info
18 |
19 | # ## SSL Support
20 | #
21 | # To get SSL working, you will need to add the `https` key
22 | # to the previous section and set your `:url` port to 443:
23 | #
24 | # config :api_ecommerce, ApiEcommerceWeb.Endpoint,
25 | # ...
26 | # url: [host: "example.com", port: 443],
27 | # https: [
28 | # :inet6,
29 | # port: 443,
30 | # cipher_suite: :strong,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
33 | # ]
34 | #
35 | # The `cipher_suite` is set to `:strong` to support only the
36 | # latest and more secure SSL ciphers. This means old browsers
37 | # and clients may not be supported. You can set it to
38 | # `:compatible` for wider support.
39 | #
40 | # `:keyfile` and `:certfile` expect an absolute path to the key
41 | # and cert in disk or a relative path inside priv, for example
42 | # "priv/ssl/server.key". For all supported SSL configuration
43 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
44 | #
45 | # We also recommend setting `force_ssl` in your endpoint, ensuring
46 | # no data is ever sent via http, always redirecting to https:
47 | #
48 | # config :api_ecommerce, ApiEcommerceWeb.Endpoint,
49 | # force_ssl: [hsts: true]
50 | #
51 | # Check `Plug.SSL` for all available options in `force_ssl`.
52 |
53 | # Finally import the config/prod.secret.exs which loads secrets
54 | # and configuration from environment variables.
55 | import_config "prod.secret.exs"
56 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
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"
12 |
13 | ## From Ecto.Changeset.cast/4
14 | msgid "can't be blank"
15 | msgstr ""
16 |
17 | ## From Ecto.Changeset.unique_constraint/3
18 | msgid "has already been taken"
19 | msgstr ""
20 |
21 | ## From Ecto.Changeset.put_change/3
22 | msgid "is invalid"
23 | msgstr ""
24 |
25 | ## From Ecto.Changeset.validate_acceptance/3
26 | msgid "must be accepted"
27 | msgstr ""
28 |
29 | ## From Ecto.Changeset.validate_format/3
30 | msgid "has invalid format"
31 | msgstr ""
32 |
33 | ## From Ecto.Changeset.validate_subset/3
34 | msgid "has an invalid entry"
35 | msgstr ""
36 |
37 | ## From Ecto.Changeset.validate_exclusion/3
38 | msgid "is reserved"
39 | msgstr ""
40 |
41 | ## From Ecto.Changeset.validate_confirmation/3
42 | msgid "does not match confirmation"
43 | msgstr ""
44 |
45 | ## From Ecto.Changeset.no_assoc_constraint/3
46 | msgid "is still associated with this entry"
47 | msgstr ""
48 |
49 | msgid "are still associated with this entry"
50 | msgstr ""
51 |
52 | ## From Ecto.Changeset.validate_length/3
53 | msgid "should be %{count} character(s)"
54 | msgid_plural "should be %{count} character(s)"
55 | msgstr[0] ""
56 | msgstr[1] ""
57 |
58 | msgid "should have %{count} item(s)"
59 | msgid_plural "should have %{count} item(s)"
60 | msgstr[0] ""
61 | msgstr[1] ""
62 |
63 | msgid "should be at least %{count} character(s)"
64 | msgid_plural "should be at least %{count} character(s)"
65 | msgstr[0] ""
66 | msgstr[1] ""
67 |
68 | msgid "should have at least %{count} item(s)"
69 | msgid_plural "should have at least %{count} item(s)"
70 | msgstr[0] ""
71 | msgstr[1] ""
72 |
73 | msgid "should be at most %{count} character(s)"
74 | msgid_plural "should be at most %{count} character(s)"
75 | msgstr[0] ""
76 | msgstr[1] ""
77 |
78 | msgid "should have at most %{count} item(s)"
79 | msgid_plural "should have at most %{count} item(s)"
80 | msgstr[0] ""
81 | msgstr[1] ""
82 |
83 | ## From Ecto.Changeset.validate_number/3
84 | msgid "must be less than %{number}"
85 | msgstr ""
86 |
87 | msgid "must be greater than %{number}"
88 | msgstr ""
89 |
90 | msgid "must be less than or equal to %{number}"
91 | msgstr ""
92 |
93 | msgid "must be greater than or equal to %{number}"
94 | msgstr ""
95 |
96 | msgid "must be equal to %{number}"
97 | msgstr ""
98 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This 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 has no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 | ## From Ecto.Changeset.cast/4
12 | msgid "can't be blank"
13 | msgstr ""
14 |
15 | ## From Ecto.Changeset.unique_constraint/3
16 | msgid "has already been taken"
17 | msgstr ""
18 |
19 | ## From Ecto.Changeset.put_change/3
20 | msgid "is invalid"
21 | msgstr ""
22 |
23 | ## From Ecto.Changeset.validate_acceptance/3
24 | msgid "must be accepted"
25 | msgstr ""
26 |
27 | ## From Ecto.Changeset.validate_format/3
28 | msgid "has invalid format"
29 | msgstr ""
30 |
31 | ## From Ecto.Changeset.validate_subset/3
32 | msgid "has an invalid entry"
33 | msgstr ""
34 |
35 | ## From Ecto.Changeset.validate_exclusion/3
36 | msgid "is reserved"
37 | msgstr ""
38 |
39 | ## From Ecto.Changeset.validate_confirmation/3
40 | msgid "does not match confirmation"
41 | msgstr ""
42 |
43 | ## From Ecto.Changeset.no_assoc_constraint/3
44 | msgid "is still associated with this entry"
45 | msgstr ""
46 |
47 | msgid "are still associated with this entry"
48 | msgstr ""
49 |
50 | ## From Ecto.Changeset.validate_length/3
51 | msgid "should be %{count} character(s)"
52 | msgid_plural "should be %{count} character(s)"
53 | msgstr[0] ""
54 | msgstr[1] ""
55 |
56 | msgid "should have %{count} item(s)"
57 | msgid_plural "should have %{count} item(s)"
58 | msgstr[0] ""
59 | msgstr[1] ""
60 |
61 | msgid "should be at least %{count} character(s)"
62 | msgid_plural "should be at least %{count} character(s)"
63 | msgstr[0] ""
64 | msgstr[1] ""
65 |
66 | msgid "should have at least %{count} item(s)"
67 | msgid_plural "should have at least %{count} item(s)"
68 | msgstr[0] ""
69 | msgstr[1] ""
70 |
71 | msgid "should be at most %{count} character(s)"
72 | msgid_plural "should be at most %{count} character(s)"
73 | msgstr[0] ""
74 | msgstr[1] ""
75 |
76 | msgid "should have at most %{count} item(s)"
77 | msgid_plural "should have at most %{count} item(s)"
78 | msgstr[0] ""
79 | msgstr[1] ""
80 |
81 | ## From Ecto.Changeset.validate_number/3
82 | msgid "must be less than %{number}"
83 | msgstr ""
84 |
85 | msgid "must be greater than %{number}"
86 | msgstr ""
87 |
88 | msgid "must be less than or equal to %{number}"
89 | msgstr ""
90 |
91 | msgid "must be greater than or equal to %{number}"
92 | msgstr ""
93 |
94 | msgid "must be equal to %{number}"
95 | msgstr ""
96 |
--------------------------------------------------------------------------------
/lib/api_ecommerce/auth.ex:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.Auth do
2 | @moduledoc """
3 | The Auth context.
4 | """
5 |
6 | import Ecto.Query, warn: false
7 |
8 | alias ApiEcommerce.Repo
9 | alias ApiEcommerce.Auth.User
10 | alias ApiEcommerce.Guardian
11 |
12 | @doc """
13 | Returns the list of users.
14 |
15 | ## Examples
16 |
17 | iex> list_users()
18 | [%User{}, ...]
19 |
20 | """
21 | def list_users do
22 | Repo.all(User)
23 | end
24 |
25 | @doc """
26 | Gets a single user.
27 |
28 | Raises `Ecto.NoResultsError` if the User does not exist.
29 |
30 | ## Examples
31 |
32 | iex> get_user!(123)
33 | %User{}
34 |
35 | iex> get_user!(456)
36 | ** (Ecto.NoResultsError)
37 |
38 | """
39 | def get_user!(id), do: Repo.get!(User, id)
40 |
41 | @doc """
42 | Creates a user.
43 |
44 | ## Examples
45 |
46 | iex> create_user(%{field: value})
47 | {:ok, %User{}}
48 |
49 | iex> create_user(%{field: bad_value})
50 | {:error, %Ecto.Changeset{}}
51 |
52 | """
53 | def create_user(attrs \\ %{}) do
54 | %User{}
55 | |> User.changeset(attrs)
56 | |> Repo.insert()
57 | end
58 |
59 | @doc """
60 | Updates a user.
61 |
62 | ## Examples
63 |
64 | iex> update_user(user, %{field: new_value})
65 | {:ok, %User{}}
66 |
67 | iex> update_user(user, %{field: bad_value})
68 | {:error, %Ecto.Changeset{}}
69 |
70 | """
71 | def update_user(%User{} = user, attrs) do
72 | user
73 | |> User.changeset(attrs)
74 | |> Repo.update()
75 | end
76 |
77 | @doc """
78 | Deletes a User.
79 |
80 | ## Examples
81 |
82 | iex> delete_user(user)
83 | {:ok, %User{}}
84 |
85 | iex> delete_user(user)
86 | {:error, %Ecto.Changeset{}}
87 |
88 | """
89 | def delete_user(%User{} = user) do
90 | Repo.delete(user)
91 | end
92 |
93 | @doc """
94 | Returns an `%Ecto.Changeset{}` for tracking user changes.
95 |
96 | ## Examples
97 |
98 | iex> change_user(user)
99 | %Ecto.Changeset{source: %User{}}
100 |
101 | """
102 | def change_user(%User{} = user) do
103 | User.changeset(user, %{})
104 | end
105 |
106 | def authenticate_user(email, password) do
107 | query = from(u in User, where: u.email == ^email)
108 | query
109 | |> Repo.one()
110 | |> verify_password(password)
111 | |> gen_token()
112 | end
113 |
114 | defp verify_password(nil, _) do
115 | Bcrypt.no_user_verify()
116 | {:error, "Wrong username or password"}
117 | end
118 |
119 | defp verify_password(user, password) do
120 | if Bcrypt.verify_pass(password, user.password_hash) do
121 | user
122 | else
123 | {:error, "Wrong username or password"}
124 | end
125 | end
126 |
127 | defp gen_token(%User{} = user) do
128 | case Guardian.encode_and_sign(user) do
129 | {:ok, token, _claims} -> {:ok, user, token}
130 | _ -> {:error, :unauthorized}
131 | end
132 | end
133 |
134 | defp gen_token(error) do
135 | error
136 | end
137 | end
138 |
--------------------------------------------------------------------------------
/test/api_ecommerce/auth_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerce.AuthTest do
2 | use ApiEcommerce.DataCase
3 |
4 | alias ApiEcommerce.Auth
5 |
6 | describe "users" do
7 | alias ApiEcommerce.Auth.User
8 |
9 | @valid_attrs %{
10 | name: "some name",
11 | email: "some@email",
12 | status: :active,
13 | role: :member,
14 | password: "some password",
15 | password_confirmation: "some password"
16 | }
17 | @update_attrs %{
18 | name: "some updated name",
19 | email: "some@updated.email",
20 | status: :deleted,
21 | role: :admin,
22 | password: "some updated password",
23 | password_confirmation: "some updated password"
24 | }
25 | @invalid_attrs %{email: nil, status: nil, password: nil}
26 |
27 | def user_fixture(attrs \\ %{}) do
28 | {:ok, user} =
29 | attrs
30 | |> Enum.into(@valid_attrs)
31 | |> Auth.create_user()
32 |
33 | user
34 | end
35 |
36 | test "list_users/0 returns all users" do
37 | user = user_fixture()
38 | assert Auth.list_users() |> Enum.map(fn x -> x.email end) |> to_string =~ user.email
39 | end
40 |
41 | test "get_user!/1 returns the user with given id" do
42 | user = user_fixture()
43 | assert Auth.get_user!(user.id).id == user.id
44 | assert Auth.get_user!(user.id).email == user.email
45 | end
46 |
47 | test "create_user/1 with valid data creates a user" do
48 | assert {:ok, %User{} = user} = Auth.create_user(@valid_attrs)
49 | assert user.name == @valid_attrs.name
50 | assert user.email == @valid_attrs.email
51 | assert user.status == @valid_attrs.status
52 | assert user.role == @valid_attrs.role
53 | assert Bcrypt.verify_pass(@valid_attrs.password, user.password_hash)
54 | end
55 |
56 | test "create_user/1 with invalid data returns error changeset" do
57 | assert {:error, %Ecto.Changeset{}} = Auth.create_user(@invalid_attrs)
58 | end
59 |
60 | test "update_user/2 with valid data updates the user" do
61 | user = user_fixture()
62 | assert {:ok, %User{} = user} = Auth.update_user(user, @update_attrs)
63 | assert user.name == @update_attrs.name
64 | assert user.email == @update_attrs.email
65 | assert user.status == @update_attrs.status
66 | assert user.role == @update_attrs.role
67 | assert Bcrypt.verify_pass(@update_attrs.password, user.password_hash)
68 | end
69 |
70 | test "update_user/2 with invalid data returns error changeset" do
71 | user = user_fixture()
72 | assert {:error, %Ecto.Changeset{}} = Auth.update_user(user, @invalid_attrs)
73 | user1 = Auth.get_user!(user.id)
74 | assert user.email == user1.email
75 | assert user.status == user1.status
76 | assert Bcrypt.verify_pass(@valid_attrs.password, user.password_hash)
77 | end
78 |
79 | test "delete_user/1 deletes the user" do
80 | user = user_fixture()
81 | assert {:ok, %User{}} = Auth.delete_user(user)
82 | assert_raise Ecto.NoResultsError, fn -> Auth.get_user!(user.id) end
83 | end
84 |
85 | test "change_user/1 returns a user changeset" do
86 | user = user_fixture()
87 | assert %Ecto.Changeset{} = Auth.change_user(user)
88 | end
89 |
90 | test "authenticate_user/2 authenticates the user" do
91 | user = user_fixture()
92 | assert {:error, "Wrong username or password"} = Auth.authenticate_user("wrong email", "")
93 |
94 | assert {:ok, authenticated_user, token} =
95 | Auth.authenticate_user(user.email, @valid_attrs.password)
96 |
97 | assert %{user | password: nil, password_confirmation: nil} == authenticated_user
98 | end
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/test/api_ecommerce_web/controllers/user_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ApiEcommerceWeb.UserControllerTest do
2 | use ApiEcommerceWeb.ConnCase
3 |
4 | alias ApiEcommerce.Auth
5 | alias ApiEcommerce.Auth.User
6 | alias ApiEcommerce.Guardian
7 |
8 | @create_attrs %{
9 | email: "some@email",
10 | password: "some password",
11 | password_confirmation: "some password"
12 | }
13 | @update_attrs %{
14 | email: "some@updated.email",
15 | password: "some updated password",
16 | password_confirmation: "some updated password"
17 | }
18 | @invalid_attrs %{email: nil, is_active: nil, password: nil}
19 | @current_user_attrs %{
20 | email: "some_current@user.email",
21 | password: "some current user password",
22 | password_confirmation: "some current user password"
23 | }
24 |
25 | def fixture(:user) do
26 | {:ok, user} = Auth.create_user(@create_attrs)
27 | user
28 | end
29 |
30 | def fixture(:current_user) do
31 | {:ok, current_user} = Auth.create_user(@current_user_attrs)
32 | current_user
33 | end
34 |
35 | setup %{conn: conn} do
36 | {:ok, conn: conn, current_user: current_user} = setup_current_user(conn)
37 | {:ok, token, _} = Guardian.encode_and_sign(current_user)
38 | {:ok, conn: put_req_header(conn, "accept", "application/json"), current_user: current_user, token: token}
39 | end
40 |
41 | describe "index" do
42 | test "lists all users", %{conn: conn, current_user: current_user, token: token} do
43 |
44 | conn = conn
45 | |> put_req_header("authorization", "bearer: " <> token)
46 | |> get(Routes.user_path(conn, :index))
47 |
48 | assert json_response(conn, 200)["data"] == [
49 | %{
50 | "id" => current_user.id,
51 | "email" => current_user.email,
52 | "status" => current_user.status |> Atom.to_string(),
53 | "role" => current_user.role |> Atom.to_string()
54 | }
55 | ]
56 | end
57 | end
58 |
59 | describe "create user" do
60 | test "renders user when data is valid", %{conn: conn, token: token} do
61 | request1 = post(conn, Routes.user_path(conn, :create), user: @create_attrs)
62 | assert %{"id" => id} = json_response(request1, 201)["data"]
63 |
64 | request2 = conn
65 | |> put_req_header("authorization", "bearer: " <> token)
66 | |> get(Routes.user_path(conn, :show, id))
67 |
68 | assert json_response(request2, 200)["data"] == %{
69 | "id" => id,
70 | "email" => @create_attrs.email,
71 | "status" => "active",
72 | "role" => "member"
73 | }
74 | end
75 |
76 | test "renders errors when data is invalid", %{conn: conn} do
77 | conn = post(conn, Routes.user_path(conn, :create), user: @invalid_attrs)
78 | assert json_response(conn, 422)["errors"] != %{}
79 | end
80 | end
81 |
82 | describe "update user" do
83 | setup [:create_user]
84 |
85 | test "renders user when data is valid", %{conn: conn, user: %User{id: id} = user, token: token} do
86 | request1 = conn
87 | |> put_req_header("authorization", "bearer: " <> token)
88 | |> put(Routes.user_path(conn, :update, user), user: @update_attrs)
89 |
90 | assert %{"id" => ^id} = json_response(request1, 200)["data"]
91 |
92 | request2 = conn
93 | |> put_req_header("authorization", "bearer: " <> token)
94 | |> get(Routes.user_path(conn, :show, id))
95 |
96 | assert json_response(request2, 200)["data"] == %{
97 | "id" => id,
98 | "email" => @update_attrs.email,
99 | "status" => user.status |> Atom.to_string(),
100 | "role" => user.role |> Atom.to_string()
101 | }
102 | end
103 |
104 | test "renders errors when data is invalid", %{conn: conn, user: user, token: token} do
105 | conn = conn
106 | |> put_req_header("authorization", "bearer: " <> token)
107 | |> put(Routes.user_path(conn, :update, user), user: @invalid_attrs)
108 | assert json_response(conn, 422)["errors"] != %{}
109 | end
110 | end
111 |
112 | describe "delete user" do
113 | setup [:create_user]
114 |
115 | test "deletes chosen user", %{conn: conn, user: user, token: token} do
116 | conn = conn
117 | |> put_req_header("authorization", "bearer: " <> token)
118 | |> delete(Routes.user_path(conn, :delete, user))
119 | assert response(conn, 204)
120 |
121 | assert_error_sent 404, fn ->
122 | get(conn, Routes.user_path(conn, :show, user))
123 | end
124 | end
125 | end
126 |
127 | describe "sign_in user" do
128 | test "renders user when user credentials are good", %{conn: conn, current_user: current_user} do
129 | conn =
130 | post(
131 | conn,
132 | Routes.user_path(conn, :sign_in, %{email: current_user.email, password: @current_user_attrs.password})
133 | )
134 |
135 | assert json_response(conn, 200)["data"]["id"] == current_user.id
136 | assert json_response(conn, 200)["data"]["email"] == current_user.email
137 | assert json_response(conn, 200)["data"]["status"] == current_user.status |> Atom.to_string()
138 | assert json_response(conn, 200)["data"]["role"] == current_user.role |> Atom.to_string()
139 | assert {:ok, claims} = Guardian.decode_and_verify(json_response(conn, 200)["data"]["token"])
140 | end
141 |
142 | test "renders errors when user credentials are bad", %{conn: conn} do
143 | conn =
144 | post(conn, Routes.user_path(conn, :sign_in, %{email: "non-existent email", password: ""}))
145 |
146 | assert json_response(conn, 401)["errors"] == %{"detail" => "Wrong username or password"}
147 | end
148 | end
149 |
150 | defp create_user(_) do
151 | user = fixture(:user)
152 | {:ok, user: user}
153 | end
154 |
155 | defp setup_current_user(conn) do
156 | current_user = fixture(:current_user)
157 |
158 | {
159 | :ok,
160 | conn: conn,
161 | current_user: current_user
162 | }
163 | end
164 | end
165 |
--------------------------------------------------------------------------------
/.idea/api-ecommerce.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
3 | "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"},
4 | "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
5 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
6 | "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
7 | "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
8 | "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
9 | "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
10 | "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
11 | "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
12 | "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
13 | "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
14 | "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
15 | "guardian": {:hex, :guardian, "2.1.1", "1f02b349f6ba765647cc834036a8d76fa4bd65605342fe3a031df3c99d0d411a", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "189b87ba7ce6b40d6ba029138098b96ffc4ae78f229f5b39539b9141af8bf0f8"},
16 | "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
17 | "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
18 | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
19 | "myxql": {:hex, :myxql, "0.4.1", "92a02822598d0e819cafa01d745b586e4e0adb6f30223cd817d22d26675d7026", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d9cc26bd71a33d92d42a7fb2e70e4864b03719dac5a497d7b8a8c1883eee729"},
20 | "phoenix": {:hex, :phoenix, "1.5.4", "0fca9ce7e960f9498d6315e41fcd0c80bfa6fbeb5fa3255b830c67fdfb7e703f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4e516d131fde87b568abd62e1b14aa07ba7d5edfd230bab4e25cc9dedbb39135"},
21 | "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
22 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
23 | "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
24 | "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"},
25 | "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
26 | "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
27 | "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
28 | "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
29 | }
30 |
--------------------------------------------------------------------------------