├── .gitignore ├── README.md ├── config ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── lib ├── elixirGraphqlBoilerplate.ex ├── elixirGraphqlBoilerplate │ ├── application.ex │ └── repo.ex ├── elixirGraphqlBoilerplate_web.ex └── elixirGraphqlBoilerplate_web │ ├── channels │ └── user_socket.ex │ ├── endpoint.ex │ ├── gettext.ex │ ├── graphql │ ├── models │ │ └── user_models.ex │ ├── resolvers │ │ └── user_resolvers.ex │ ├── schema.ex │ └── types │ │ └── user_types.ex │ ├── router.ex │ └── views │ ├── error_helpers.ex │ └── error_view.ex ├── mix.exs ├── mix.lock ├── priv ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot └── repo │ ├── migrations │ └── 20180215174606_create_users.exs │ └── seeds.exs ├── scripts ├── create_database.sh ├── drop.database.sh └── update_schema.sh └── test ├── elixirGraphqlBoilerplate_web └── views │ └── error_view_test.exs ├── support ├── channel_case.ex ├── conn_case.ex └── data_case.ex └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | # App artifacts 2 | /_build 3 | /db 4 | /deps 5 | /*.ez 6 | 7 | # Generated on crash by the VM 8 | erl_crash.dump 9 | 10 | # Files matching config/*.secret.exs pattern contain sensitive 11 | # data and you should not commit them into version control. 12 | # 13 | # Alternatively, you may comment the line below and commit the 14 | # secrets files as long as you replace their contents by environment 15 | # variables. 16 | /config/*.secret.exs 17 | 18 | .elixir_ls/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ElixirGraphqlBoilerplate 2 | 3 | To start your Phoenix server: 4 | 5 | * Create database `sh scripts/create_database.sh` (verify that the settings are correct in devs.exs) 6 | * Install dependencies with `mix deps.get` 7 | * Create and migrate your database with `mix ecto.create && mix ecto.migrate` 8 | * Start Phoenix endpoint with `mix phx.server` 9 | 10 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 11 | 12 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment). 13 | 14 | ## Learn more 15 | 16 | * Official website: http://www.phoenixframework.org/ 17 | * Guides: http://phoenixframework.org/docs/overview 18 | * Docs: https://hexdocs.pm/phoenix 19 | * Mailing list: http://groups.google.com/group/phoenix-talk 20 | * Source: https://github.com/phoenixframework/phoenix 21 | -------------------------------------------------------------------------------- /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 | use Mix.Config 7 | 8 | # General application configuration 9 | config :elixirGraphqlBoilerplate, 10 | ecto_repos: [ElixirGraphqlBoilerplate.Repo] 11 | 12 | # Configures the endpoint 13 | config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, 14 | url: [host: "localhost"], 15 | secret_key_base: "MHBXalP2S1frSALmNuvjUMwTHFmZQkk3xkVnym7CbQ/xMJmtsvLUJ8/bviPHFBKe", 16 | render_errors: [view: ElixirGraphqlBoilerplateWeb.ErrorView, accepts: ~w(json)], 17 | pubsub: [name: ElixirGraphqlBoilerplate.PubSub, 18 | adapter: Phoenix.PubSub.PG2] 19 | 20 | # Configures Elixir's Logger 21 | config :logger, :console, 22 | format: "$time $metadata[$level] $message\n", 23 | metadata: [:request_id] 24 | 25 | # Import environment specific config. This must remain at the bottom 26 | # of this file so it overrides the configuration defined above. 27 | import_config "#{Mix.env}.exs" 28 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with brunch.io to recompile .js and .css sources. 9 | config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, 10 | http: [port: 4000], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: [] 15 | 16 | # ## SSL Support 17 | # 18 | # In order to use HTTPS in development, a self-signed 19 | # certificate can be generated by running the following 20 | # command from your terminal: 21 | # 22 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem 23 | # 24 | # The `http:` config above can be replaced with: 25 | # 26 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], 27 | # 28 | # If desired, both `http:` and `https:` keys can be 29 | # configured to run both http and https servers on 30 | # different ports. 31 | 32 | # Do not include metadata nor timestamps in development logs 33 | config :logger, :console, format: "[$level] $message\n" 34 | 35 | # Set a higher stacktrace during development. Avoid configuring such 36 | # in production as building large stacktraces may be expensive. 37 | config :phoenix, :stacktrace_depth, 20 38 | 39 | # Configure your database 40 | config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplate.Repo, 41 | adapter: Ecto.Adapters.Postgres, 42 | username: "postgres", 43 | password: "postgres", 44 | database: "elixirgraphqlboilerplate_dev", 45 | hostname: "localhost", 46 | pool_size: 10 47 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, we often load configuration from external 4 | # sources, such as your system environment. For this reason, 5 | # you won't find the :http configuration below, but set inside 6 | # ElixirGraphqlBoilerplateWeb.Endpoint.init/2 when load_from_system_env is 7 | # true. Any dynamic configuration should be done there. 8 | # 9 | # Don't forget to configure the url host to something meaningful, 10 | # Phoenix uses this information when generating URLs. 11 | # 12 | # Finally, we also include the path to a cache manifest 13 | # containing the digested version of static files. This 14 | # manifest is generated by the mix phx.digest task 15 | # which you typically run after static files are built. 16 | config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, 17 | load_from_system_env: true, 18 | url: [host: "example.com", port: 80], 19 | cache_static_manifest: "priv/static/cache_manifest.json" 20 | 21 | # Do not print debug messages in production 22 | config :logger, level: :info 23 | 24 | # ## SSL Support 25 | # 26 | # To get SSL working, you will need to add the `https` key 27 | # to the previous section and set your `:url` port to 443: 28 | # 29 | # config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, 30 | # ... 31 | # url: [host: "example.com", port: 443], 32 | # https: [:inet6, 33 | # port: 443, 34 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 35 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] 36 | # 37 | # Where those two env variables return an absolute path to 38 | # the key and cert in disk or a relative path inside priv, 39 | # for example "priv/ssl/server.key". 40 | # 41 | # We also recommend setting `force_ssl`, ensuring no data is 42 | # ever sent via http, always redirecting to https: 43 | # 44 | # config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, 45 | # force_ssl: [hsts: true] 46 | # 47 | # Check `Plug.SSL` for all available options in `force_ssl`. 48 | 49 | # ## Using releases 50 | # 51 | # If you are doing OTP releases, you need to instruct Phoenix 52 | # to start the server for all endpoints: 53 | # 54 | # config :phoenix, :serve_endpoints, true 55 | # 56 | # Alternatively, you can configure exactly which server to 57 | # start per endpoint: 58 | # 59 | # config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, server: true 60 | # 61 | 62 | # Finally import the config/prod.secret.exs 63 | # which should be versioned separately. 64 | import_config "prod.secret.exs" 65 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplateWeb.Endpoint, 6 | http: [port: 4001], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | 12 | # Configure your database 13 | config :elixirGraphqlBoilerplate, ElixirGraphqlBoilerplate.Repo, 14 | adapter: Ecto.Adapters.Postgres, 15 | username: "postgres", 16 | password: "postgres", 17 | database: "elixirgraphqlboilerplate_test", 18 | hostname: "localhost", 19 | pool: Ecto.Adapters.SQL.Sandbox 20 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate do 2 | @moduledoc """ 3 | ElixirGraphqlBoilerplate 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 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate/application.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Application do 2 | use Application 3 | 4 | # See https://hexdocs.pm/elixir/Application.html 5 | # for more information on OTP Applications 6 | def start(_type, _args) do 7 | import Supervisor.Spec 8 | 9 | # Define workers and child supervisors to be supervised 10 | children = [ 11 | # Start the Ecto repository 12 | supervisor(ElixirGraphqlBoilerplate.Repo, []), 13 | # Start the endpoint when the application starts 14 | supervisor(ElixirGraphqlBoilerplateWeb.Endpoint, []), 15 | # Start your own worker by calling: ElixirGraphqlBoilerplate.Worker.start_link(arg1, arg2, arg3) 16 | # worker(ElixirGraphqlBoilerplate.Worker, [arg1, arg2, arg3]), 17 | ] 18 | 19 | # See https://hexdocs.pm/elixir/Supervisor.html 20 | # for other strategies and supported options 21 | opts = [strategy: :one_for_one, name: ElixirGraphqlBoilerplate.Supervisor] 22 | Supervisor.start_link(children, opts) 23 | end 24 | 25 | # Tell Phoenix to update the endpoint configuration 26 | # whenever the application is updated. 27 | def config_change(changed, _new, removed) do 28 | ElixirGraphqlBoilerplateWeb.Endpoint.config_change(changed, removed) 29 | :ok 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Repo do 2 | use Ecto.Repo, otp_app: :elixirGraphqlBoilerplate 3 | 4 | @doc """ 5 | Dynamically loads the repository url from the 6 | DATABASE_URL environment variable. 7 | """ 8 | def init(_, opts) do 9 | {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb 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 ElixirGraphqlBoilerplateWeb, :controller 9 | use ElixirGraphqlBoilerplateWeb, :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: ElixirGraphqlBoilerplateWeb 23 | import Plug.Conn 24 | import ElixirGraphqlBoilerplateWeb.Router.Helpers 25 | import ElixirGraphqlBoilerplateWeb.Gettext 26 | end 27 | end 28 | 29 | def view do 30 | quote do 31 | use Phoenix.View, root: "lib/elixirGraphqlBoilerplate_web/templates", 32 | namespace: ElixirGraphqlBoilerplateWeb 33 | 34 | # Import convenience functions from controllers 35 | import Phoenix.Controller, only: [get_flash: 2, view_module: 1] 36 | 37 | import ElixirGraphqlBoilerplateWeb.Router.Helpers 38 | import ElixirGraphqlBoilerplateWeb.ErrorHelpers 39 | import ElixirGraphqlBoilerplateWeb.Gettext 40 | end 41 | end 42 | 43 | def router do 44 | quote do 45 | use Phoenix.Router 46 | import Plug.Conn 47 | import Phoenix.Controller 48 | end 49 | end 50 | 51 | def channel do 52 | quote do 53 | use Phoenix.Channel 54 | import ElixirGraphqlBoilerplateWeb.Gettext 55 | end 56 | end 57 | 58 | @doc """ 59 | When used, dispatch to the appropriate controller/view/etc. 60 | """ 61 | defmacro __using__(which) when is_atom(which) do 62 | apply(__MODULE__, which, []) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", ElixirGraphqlBoilerplateWeb.RoomChannel 6 | 7 | ## Transports 8 | transport :websocket, Phoenix.Transports.WebSocket 9 | # transport :longpoll, Phoenix.Transports.LongPoll 10 | 11 | # Socket params are passed from the client and can 12 | # be used to verify and authenticate a user. After 13 | # verification, you can put default assigns into 14 | # the socket that will be set for all channels, ie 15 | # 16 | # {:ok, assign(socket, :user_id, verified_user_id)} 17 | # 18 | # To deny connection, return `:error`. 19 | # 20 | # See `Phoenix.Token` documentation for examples in 21 | # performing token verification on connect. 22 | def connect(_params, socket) do 23 | {:ok, socket} 24 | end 25 | 26 | # Socket id's are topics that allow you to identify all sockets for a given user: 27 | # 28 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}" 29 | # 30 | # Would allow you to broadcast a "disconnect" event and terminate 31 | # all active sockets and channels for a given user: 32 | # 33 | # ElixirGraphqlBoilerplateWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 34 | # 35 | # Returning `nil` makes this socket anonymous. 36 | def id(_socket), do: nil 37 | end 38 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :elixirGraphqlBoilerplate 3 | 4 | socket "/socket", ElixirGraphqlBoilerplateWeb.UserSocket 5 | 6 | # Serve at "/" the static files from "priv/static" directory. 7 | # 8 | # You should set gzip to true if you are running phoenix.digest 9 | # when deploying your static files in production. 10 | plug Plug.Static, 11 | at: "/", from: :elixirGraphqlBoilerplate, gzip: false, 12 | only: ~w(css fonts images js favicon.ico robots.txt) 13 | 14 | # Code reloading can be explicitly enabled under the 15 | # :code_reloader configuration of your endpoint. 16 | if code_reloading? do 17 | plug Phoenix.CodeReloader 18 | end 19 | 20 | plug Plug.RequestId 21 | plug Plug.Logger 22 | 23 | plug Plug.Parsers, 24 | parsers: [:urlencoded, :multipart, :json], 25 | pass: ["*/*"], 26 | json_decoder: Poison 27 | 28 | plug Plug.MethodOverride 29 | plug Plug.Head 30 | 31 | # The session will be stored in the cookie and signed, 32 | # this means its contents can be read but not tampered with. 33 | # Set :encryption_salt if you would also like to encrypt it. 34 | plug Plug.Session, 35 | store: :cookie, 36 | key: "_elixirGraphqlBoilerplate_key", 37 | signing_salt: "onNvjTJu" 38 | 39 | plug ElixirGraphqlBoilerplateWeb.Router 40 | 41 | @doc """ 42 | Callback invoked for dynamically configuring the endpoint. 43 | 44 | It receives the endpoint configuration and checks if 45 | configuration should be loaded from the system environment. 46 | """ 47 | def init(_key, config) do 48 | if config[:load_from_system_env] do 49 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" 50 | {:ok, Keyword.put(config, :http, [:inet6, port: port])} 51 | else 52 | {:ok, config} 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.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 ElixirGraphqlBoilerplateWeb.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: :elixirGraphqlBoilerplate 24 | end 25 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/graphql/models/user_models.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Models.User do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias ElixirGraphqlBoilerplate.Models.User 5 | 6 | schema "users" do 7 | field(:active, :boolean, default: false) 8 | field(:email, :string) 9 | field(:first_name, :string) 10 | field(:last_name, :string) 11 | field(:password, :string) 12 | 13 | timestamps() 14 | end 15 | 16 | @doc false 17 | def changeset(%User{} = user, attrs) do 18 | user 19 | |> cast(attrs, [ 20 | :email, 21 | :first_name, 22 | :last_name, 23 | :password 24 | ]) 25 | |> validate_required([ 26 | :email, 27 | :first_name, 28 | :last_name, 29 | :password 30 | ]) 31 | |> validate_format(:email, ~r/@/) 32 | |> unique_constraint(:email) 33 | end 34 | end -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/graphql/resolvers/user_resolvers.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Resolvers.User do 2 | alias ElixirGraphqlBoilerplate.Repo 3 | alias ElixirGraphqlBoilerplate.Models.User 4 | 5 | def get(_parent, %{id: id}, _resolution) do 6 | case Repo.get(User, id) do 7 | nil -> 8 | {:error, "User ID #{id} not found"} 9 | 10 | user -> 11 | {:ok, user} 12 | end 13 | end 14 | 15 | def all(_params, _args, _resolution) do 16 | {:ok, Repo.all(User)} 17 | end 18 | 19 | def insert(_params, args, _resolution) do 20 | %User{} 21 | |> User.changeset(args) 22 | |> Repo.insert() 23 | end 24 | end -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/graphql/schema.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Schema do 2 | use Absinthe.Schema 3 | alias ElixirGraphqlBoilerplate.Resolvers 4 | 5 | import_types(ElixirGraphqlBoilerplate.Types.User) 6 | 7 | query do 8 | @desc "Get user" 9 | field :user, :user do 10 | arg(:id, non_null(:id)) 11 | resolve(&Resolvers.User.get/3) 12 | end 13 | 14 | @desc "Get all users" 15 | field :users, list_of(:user) do 16 | resolve(&Resolvers.User.all/3) 17 | end 18 | end 19 | 20 | mutation do 21 | @desc "Insert user" 22 | field :create_user, :user do 23 | arg(:active, non_null(:boolean)) 24 | arg(:email, non_null(:string)) 25 | arg(:first_name, non_null(:string)) 26 | arg(:last_name, non_null(:string)) 27 | arg(:password, non_null(:string)) 28 | 29 | resolve(&Resolvers.User.insert/3) 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/graphql/types/user_types.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Types.User do 2 | use Absinthe.Schema.Notation 3 | 4 | @desc "A user of the ElixirGraphqlBoilerplate" 5 | object :user do 6 | field(:id, :id) 7 | field(:active, :boolean) 8 | field(:email, :string) 9 | field(:first_name, :string) 10 | field(:last_name, :string) 11 | end 12 | end -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.Router do 2 | use ElixirGraphqlBoilerplateWeb, :router 3 | 4 | pipeline :graphql do 5 | plug(:accepts, ["json"]) 6 | end 7 | 8 | scope "/" do 9 | pipe_through(:graphql) 10 | 11 | forward("/graphql", Absinthe.Plug, schema: ElixirGraphqlBoilerplate.Schema) 12 | 13 | if Mix.env() == :dev do 14 | forward("/graphiql", Absinthe.Plug.GraphiQL, schema: ElixirGraphqlBoilerplate.Schema) 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.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 | # Because error messages were defined within Ecto, we must 11 | # call the Gettext module passing our Gettext backend. We 12 | # also use the "errors" domain as translations are placed 13 | # in the errors.po file. 14 | # Ecto will pass the :count keyword if the error message is 15 | # meant to be pluralized. 16 | # On your own code and templates, depending on whether you 17 | # need the message to be pluralized or not, this could be 18 | # written simply as: 19 | # 20 | # dngettext "errors", "1 file", "%{count} files", count 21 | # dgettext "errors", "is invalid" 22 | # 23 | if count = opts[:count] do 24 | Gettext.dngettext(ElixirGraphqlBoilerplateWeb.Gettext, "errors", msg, msg, count, opts) 25 | else 26 | Gettext.dgettext(ElixirGraphqlBoilerplateWeb.Gettext, "errors", msg, opts) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/elixirGraphqlBoilerplate_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.ErrorView do 2 | use ElixirGraphqlBoilerplateWeb, :view 3 | 4 | def render("404.json", _assigns) do 5 | %{errors: %{detail: "Page not found"}} 6 | end 7 | 8 | def render("500.json", _assigns) do 9 | %{errors: %{detail: "Internal server error"}} 10 | end 11 | 12 | # In case no render clause matches or no 13 | # template is found, let's render it as 500 14 | def template_not_found(_template, assigns) do 15 | render "500.json", assigns 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :elixirGraphqlBoilerplate, 7 | version: "0.0.1", 8 | elixir: "~> 1.4", 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: {ElixirGraphqlBoilerplate.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.3.0"}, 37 | {:phoenix_pubsub, "~> 1.0"}, 38 | {:phoenix_ecto, "~> 3.2"}, 39 | {:postgrex, ">= 0.0.0"}, 40 | {:gettext, "~> 0.11"}, 41 | {:cowboy, "~> 1.0"}, 42 | {:faker, "~> 0.9"}, 43 | 44 | # Absinthe 45 | {:absinthe_ecto, "~> 0.1.3"}, 46 | {:absinthe_plug, "~> 1.4.2"}, 47 | {:absinthe, "~> 1.4.5"} 48 | ] 49 | end 50 | 51 | # Aliases are shortcuts or tasks specific to the current project. 52 | # For example, to create, migrate and run the seeds file at once: 53 | # 54 | # $ mix ecto.setup 55 | # 56 | # See the documentation for `Mix` for more info on aliases. 57 | defp aliases do 58 | [ 59 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 60 | "ecto.reset": ["ecto.drop", "ecto.setup"], 61 | test: ["ecto.create --quiet", "ecto.migrate", "test"] 62 | ] 63 | end 64 | end -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "absinthe": {:hex, :absinthe, "1.4.7", "c92411307e01d0193f2eafc0d6e28273e476f66d5d20e031f879ce1fec0ad9e3", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, 3 | "absinthe_ecto": {:hex, :absinthe_ecto, "0.1.3", "420b68129e79fe4571a4838904ba03e282330d335da47729ad52ffd7b8c5fcb1", [:mix], [{:absinthe, "~> 1.3.0 or ~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "absinthe_plug": {:hex, :absinthe_plug, "1.4.2", "01bf16f0a637869bcc0a1919935f08ff853501004e7549ddaa3a7788deb48965", [:mix], [{:absinthe, "~> 1.4", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, 6 | "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, 8 | "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, 9 | "decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], [], "hexpm"}, 10 | "ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, 11 | "faker": {:hex, :faker, "0.9.0", "b22c55967fbd3413b9e5c121e59e75a553065587cf31e5aa2271b6fae2775cde", [:mix], [], "hexpm"}, 12 | "gettext": {:hex, :gettext, "0.14.1", "a666b6782fdb6ccb4736170ccb0928c0f773aa0e47c694d4b7d0338f724ff189", [:mix], [], "hexpm"}, 13 | "mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"}, 14 | "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, 15 | "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [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"}, 16 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, 17 | "plug": {:hex, :plug, "1.4.4", "279b547662272cd835a8ca089717201dd3be51bb4705354eaf1b0346744acc82", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, 18 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, 19 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, 20 | "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, 21 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, 22 | } 23 | -------------------------------------------------------------------------------- /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 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 | 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 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180215174606_create_users.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.Repo.Migrations.CreateUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users) do 6 | add(:active, :boolean, default: true, null: false) 7 | add(:email, :string) 8 | add(:first_name, :string) 9 | add(:last_name, :string) 10 | add(:password, :string) 11 | 12 | timestamps() 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /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 | # ElixirGraphqlBoilerplate.Repo.insert!(%ElixirGraphqlBoilerplate.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | 13 | alias ElixirGraphqlBoilerplate.Repo 14 | alias ElixirGraphqlBoilerplate.Models.User 15 | 16 | 1..100 17 | |> Enum.each(fn _ -> 18 | Repo.insert!(%User{ 19 | first_name: Faker.Name.first_name(), 20 | last_name: Faker.Name.last_name(), 21 | email: Faker.Internet.free_email(), 22 | password: Faker.String.base64() 23 | }) 24 | end) -------------------------------------------------------------------------------- /scripts/create_database.sh: -------------------------------------------------------------------------------- 1 | mix ecto.create 2 | mix ecto.migrate 3 | mix run priv/repo/seeds.exs -------------------------------------------------------------------------------- /scripts/drop.database.sh: -------------------------------------------------------------------------------- 1 | mix ecto.drop -------------------------------------------------------------------------------- /scripts/update_schema.sh: -------------------------------------------------------------------------------- 1 | mix absinthe.schema.json --schema ElixirGraphqlBoilerplate.Schema --pretty 2 | -------------------------------------------------------------------------------- /test/elixirGraphqlBoilerplate_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.ErrorViewTest do 2 | use ElixirGraphqlBoilerplateWeb.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(ElixirGraphqlBoilerplateWeb.ErrorView, "404.json", []) == 9 | %{errors: %{detail: "Page not found"}} 10 | end 11 | 12 | test "render 500.json" do 13 | assert render(ElixirGraphqlBoilerplateWeb.ErrorView, "500.json", []) == 14 | %{errors: %{detail: "Internal server error"}} 15 | end 16 | 17 | test "render any other" do 18 | assert render(ElixirGraphqlBoilerplateWeb.ErrorView, "505.json", []) == 19 | %{errors: %{detail: "Internal server error"}} 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.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 datastructures 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 ElixirGraphqlBoilerplateWeb.Endpoint 25 | end 26 | end 27 | 28 | 29 | setup tags do 30 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ElixirGraphqlBoilerplate.Repo) 31 | unless tags[:async] do 32 | Ecto.Adapters.SQL.Sandbox.mode(ElixirGraphqlBoilerplate.Repo, {:shared, self()}) 33 | end 34 | :ok 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplateWeb.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 datastructures 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 connections 21 | use Phoenix.ConnTest 22 | import ElixirGraphqlBoilerplateWeb.Router.Helpers 23 | 24 | # The default endpoint for testing 25 | @endpoint ElixirGraphqlBoilerplateWeb.Endpoint 26 | end 27 | end 28 | 29 | 30 | setup tags do 31 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ElixirGraphqlBoilerplate.Repo) 32 | unless tags[:async] do 33 | Ecto.Adapters.SQL.Sandbox.mode(ElixirGraphqlBoilerplate.Repo, {:shared, self()}) 34 | end 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirGraphqlBoilerplate.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 | it cannot be async. For this reason, every test runs 11 | inside a transaction which is reset at the beginning 12 | of the test unless the test case is marked as async. 13 | """ 14 | 15 | use ExUnit.CaseTemplate 16 | 17 | using do 18 | quote do 19 | alias ElixirGraphqlBoilerplate.Repo 20 | 21 | import Ecto 22 | import Ecto.Changeset 23 | import Ecto.Query 24 | import ElixirGraphqlBoilerplate.DataCase 25 | end 26 | end 27 | 28 | setup tags do 29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(ElixirGraphqlBoilerplate.Repo) 30 | 31 | unless tags[:async] do 32 | Ecto.Adapters.SQL.Sandbox.mode(ElixirGraphqlBoilerplate.Repo, {:shared, self()}) 33 | end 34 | 35 | :ok 36 | end 37 | 38 | @doc """ 39 | A helper that transform changeset errors to a map of messages. 40 | 41 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 42 | assert "password is too short" in errors_on(changeset).password 43 | assert %{password: ["password is too short"]} = errors_on(changeset) 44 | 45 | """ 46 | def errors_on(changeset) do 47 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 48 | Enum.reduce(opts, message, fn {key, value}, acc -> 49 | String.replace(acc, "%{#{key}}", to_string(value)) 50 | end) 51 | end) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | 3 | Ecto.Adapters.SQL.Sandbox.mode(ElixirGraphqlBoilerplate.Repo, :manual) 4 | 5 | --------------------------------------------------------------------------------