diff --git a/.iex.exs b/.iex.exs index 2d0eae8..41a5b9e 100644 --- a/.iex.exs +++ b/.iex.exs @@ -2,6 +2,8 @@ IEx.configure(inspect: [charlists: false]) alias Flix.Accounts alias Flix.Accounts.User +alias Flix.Accounts.UserToken +alias Flix.Accounts.UserNotifier alias Flix.Catalogs alias Flix.Catalogs.Characterization diff --git a/.tool-versions b/.tool-versions index 85f8b33..6b61424 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 24.3.2 -elixir 1.13.3-otp-24 +erlang 24.3.3 +elixir 1.13.4-otp-24 diff --git a/README.md b/README.md index 5be49e9..5b8049b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The purpose of this project is to implement an application where fans can commen - Erlang 24.3.2 or newer -- Node >= 14.19.0 and <= 15.0 +- Node >= 14.19.1 and <= 15.0 - Phoenix 1.6.6 or newer diff --git a/config/config.exs b/config/config.exs index 961cf22..791e0ce 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,19 +48,19 @@ config :flix, Flix.Mailer, adapter: Bamboo.SendGridAdapter, api_key: System.get_env("SENDGRID_API_KEY") -# Configure mix_test_watch -# if Mix.env == :dev do -# config :mix_test_watch, -# clear: true, -# tasks: [ -# "test", -# "credo", -# ] -# end +# Configure Mix Test Watch +if Mix.env == :dev do + config :mix_test_watch, + clear: true, + tasks: [ + "test", + "credo" + ] +end # Configure Absinthe SDL/JSON code generation. -config :absinthe, - schema: FlixWeb.Graphql.Schema +# config :absinthe, +# schema: FlixWeb.GraphQL.Schema # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/dev/support/seeds.ex b/dev/support/seeds.ex new file mode 100644 index 0000000..45a3a30 --- /dev/null +++ b/dev/support/seeds.ex @@ -0,0 +1,325 @@ +defmodule Flix.Seeds do + import Ecto.Query + + alias Flix.Accounts + alias Flix.Accounts.User + alias Flix.Accounts.UserToken + alias Flix.Catalogs + alias Flix.Catalogs.Genre + alias Flix.Repo + + def run do + # reset database + + reset() + + # create users + + create_users() + + # create genres + + create_genres() + + # create movies + + create_movies() + end + + defp reset() do + Flix.Repo.delete_all("movies") + Flix.Repo.delete_all("genres") + Flix.Repo.delete_all("users") + Flix.Repo.delete_all("users") + Flix.Repo.delete_all("users_tokens") + end + + defp create_users() do + user_data = [ + %{ + name: "Conrad Taylor", + username: "conradwt", + email: "conradwt@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Chief", + username: "chief", + email: "chief@example", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Cyborg", + username: "cyborg", + email: "cyborg@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Crazy Jane", + username: "jane", + email: "jane@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Larry Trainor", + username: "larryt", + email: "larryt@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Rita Farr", + username: "ritaf", + email: "ritaf@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Cliff Steele", + username: "cliffs", + email: "cliffs@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + }, + %{ + name: "Negative Man", + username: "negativem", + email: "negativem@example.com", + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc" + } + ] + + Enum.each(user_data, fn data -> + {:ok, user} = Accounts.register_user(data) + + Ecto.Multi.new() + |> Ecto.Multi.update(:user, User.confirm_changeset(user)) + |> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"])) + end) + end + + defp create_genres() do + genre_data = ["Action", "Adventure", "Comedy", "Drama", "Sci-Fi"] + + Enum.each(genre_data, fn data -> + Catalogs.create_genre!(%{name: data}) + end) + end + + defp get_genres() do + query = + from(g in Genre, + where: g.name in ["Action", "Adventure", "Sci-Fi"], + order_by: g.name, + select: g.id) + + Repo.all(query) + end + + defp create_movies() do + movie_data = [ + %{ + description: + "After the devastating events of Avengers: Infinity War, the universe is in ruins. With the help of remaining allies, the Avengers assemble once more in order to undo Thanos' actions and restore order to the universe.", + director: "Anthony Russo", + duration: "181 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/avengers-end-game.png" + }, + rating: "PG-13", + released_on: "2019-04-26", + title: "My Avengers: Endgame", + total_gross: 1_223_641_414 + }, + %{ + description: + "Carol Danvers becomes one of the universe's most powerful heroes when Earth is caught in the middle of a galactic war between two alien races.", + director: "Anna Boden", + duration: "124 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/captain-marvel.png" + }, + rating: "PG-13", + released_on: "2019-03-08", + title: "Captain Marvel", + total_gross: 1_110_662_849 + }, + %{ + description: + "T'Challa, heir to the hidden but advanced kingdom of Wakanda, must step forward to lead his people into a new future and must confront a challenger from his country's past.", + director: "Ryan Coogler", + duration: "134 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/black-panther.png" + }, + rating: "PG-13", + released_on: "2018-02-16", + title: "Black Panther", + total_gross: 1_346_913_161 + }, + %{ + description: + "The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe.", + director: "Anthony Russo", + duration: "149 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/avengers-infinity-war.png" + }, + rating: "PG-13", + released_on: "2018-04-27", + title: "Avengers: Infinity War", + total_gross: 2_048_359_754 + }, + %{ + description: + "Reckless test pilot Hal Jordan is granted an alien ring that bestows him with otherworldly powers that inducts him into an intergalactic police force, the Green Lantern Corps.", + director: "Martin Campbell", + duration: "114 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/green-lantern.png" + }, + rating: "PG-13", + released_on: "2011-06-17", + title: "Green Lantern", + total_gross: 219_851_172 + }, + %{ + description: + "Four young outsiders teleport to an alternate and dangerous universe which alters their physical form in shocking ways. The four must learn to harness their new abilities and work together to save Earth from a former friend turned enemy.", + director: "Josh Trank", + duration: "100 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/fantastic-four.png" + }, + rating: "PG-13", + released_on: "2015-08-07", + title: "Fantastic Four", + total_gross: 168_257_860 + }, + %{ + description: + "When wealthy industrialist Tony Stark is forced to build an armored suit after a life-threatening incident, he ultimately decides to use its technology to fight against evil.", + director: "Jon Favreau", + duration: "126 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/ironman.png" + }, + rating: "PG-13", + released_on: "2008-05-02", + title: "Iron Man", + total_gross: 585_366_247 + }, + %{ + description: + "An alien orphan is sent from his dying planet to Earth, where he grows up to become his adoptive home's first and greatest super-hero.", + director: "Richard Donner", + duration: "143 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/superman.png" + }, + rating: "PG", + released_on: "1978-12-15", + title: "Superman", + total_gross: 300_451_603 + }, + %{ + description: + "When bitten by a genetically modified spider, a nerdy, shy, and awkward high school student gains spider-like abilities that he eventually must use to fight evil as a superhero after tragedy + befalls his family.", + director: "Sam Raimi", + duration: "121 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/spiderman.png" + }, + rating: "PG-13", + released_on: "2002-05-03", + title: "Spider-Man", + total_gross: 825_025_036 + }, + %{ + description: + "The Dark Knight of Gotham City begins his war on crime with his first major enemy being the clownishly homicidal Joker.", + director: "Tim Burton", + duration: "126 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/batman.png" + }, + rating: "PG-13", + released_on: "1989-06-23", + title: "Batman", + total_gross: 411_348_924 + }, + %{ + description: + "Patience Philips seems destined to spend her life apologizing for taking up space. Despite her artistic ability she has a more than respectable career as a graphic designer.", + director: "Jean-Christophe 'Pitof' Comar", + duration: "101 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/catwoman.png" + }, + rating: "PG-13", + released_on: "2004-07-23", + title: "Catwoman", + total_gross: 82_102_379 + }, + %{ + description: + "When a pilot crashes and tells of conflict in the outside world, Diana, an Amazonian warrior in training, leaves home to fight a war, discovering her full powers and true destiny.", + director: "Patty Jenkins", + duration: "141 min", + genres: get_genres(), + main_image: %Plug.Upload{ + content_type: "image/png", + filename: "avengers-end-game.png", + path: "#{File.cwd!()}/assets/static/images/wonder-woman.png" + }, + rating: "PG-13", + released_on: "2017-06-02", + title: "Wonder Woman", + total_gross: 821_847_012 + } + ] + + Enum.each(movie_data, fn data -> + Catalogs.create_movie(data) + end) + end +end diff --git a/elixir_buildpack.config b/elixir_buildpack.config index 0a901d4..a0d1275 100644 --- a/elixir_buildpack.config +++ b/elixir_buildpack.config @@ -1,5 +1,5 @@ # Elixir version -elixir_version=1.13.3 +elixir_version=1.13.4 # Erlang version -erlang_version=24.3.2 +erlang_version=24.3.3 diff --git a/lib/flix/accounts.ex b/lib/flix/accounts.ex index 248fe27..1658590 100644 --- a/lib/flix/accounts.ex +++ b/lib/flix/accounts.ex @@ -59,6 +59,29 @@ defmodule Flix.Accounts do if User.valid_password?(user, password), do: user end + @doc """ + Gets a user by email and password. + + ## Examples + + iex> authenticate("foo@example.com", "correct_password") + {:ok, %User{}} + + iex> authenticate("foo@example.com", "invalid_password") + :error + + """ + def authenticate(email, password) + when is_binary(email) and is_binary(password) do + + with user <- Repo.get_by(User, email: email), + true <- User.valid_password?(user, password) do + {:ok, user} + else + _ -> :error + end + end + @doc """ Gets a single user. diff --git a/lib/flix/accounts/user.ex b/lib/flix/accounts/user.ex index cfd1755..dca5bf7 100644 --- a/lib/flix/accounts/user.ex +++ b/lib/flix/accounts/user.ex @@ -45,19 +45,13 @@ defmodule Flix.Accounts.User do """ def registration_changeset(user, attrs, opts \\ []) do user - |> cast(attrs, [:admin, :email, :name, :password, :username]) - |> validate_admin() + |> cast(attrs, [:email, :name, :password, :username]) |> validate_email() |> validate_password(opts) |> validate_name |> validate_username() end - defp validate_admin(changeset) do - changeset - |> validate_required([:admin]) - end - defp validate_name(changeset) do changeset |> validate_required([:name]) diff --git a/lib/flix/application.ex b/lib/flix/application.ex index c87c15a..cd4d1f6 100644 --- a/lib/flix/application.ex +++ b/lib/flix/application.ex @@ -12,11 +12,12 @@ defmodule Flix.Application do # Start the Telemetry supervisor FlixWeb.Telemetry, # Start the PubSub system - {Phoenix.PubSub, name: Flix.PubSub}, + {Phoenix.PubSub, [name: Flix.PubSub, adapter: Phoenix.PubSub.PG2]}, # Start the Endpoint (http/https) - FlixWeb.Endpoint + FlixWeb.Endpoint, # Start a worker by calling: Flix.Worker.start_link(arg) # {Flix.Worker, arg} + {Absinthe.Subscription, FlixWeb.Endpoint} ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/flix/catalogs.ex b/lib/flix/catalogs.ex index 56def3e..6cc8e69 100644 --- a/lib/flix/catalogs.ex +++ b/lib/flix/catalogs.ex @@ -72,7 +72,7 @@ defmodule Flix.Catalogs do """ def create_movie(attrs \\ %{}) do Ecto.Multi.new() - |> Ecto.Multi.insert(:movie_info, Movie.changeset(%Movie{}, attrs)) + |> Ecto.Multi.insert(:movie, Movie.changeset(%Movie{}, attrs)) |> Ecto.Multi.update(:movie_poster, &Movie.poster_changeset(&1.movie, attrs)) |> Repo.transaction() end @@ -203,8 +203,6 @@ defmodule Flix.Catalogs do """ def create_review(attrs \\ %{}) do - IO.inspect(attrs, label: "create_review") - %Review{} |> Review.changeset(attrs) |> Repo.insert() diff --git a/lib/flix_web/auth_token.ex b/lib/flix_web/auth_token.ex new file mode 100644 index 0000000..260fe9e --- /dev/null +++ b/lib/flix_web/auth_token.ex @@ -0,0 +1,21 @@ +defmodule FlixWeb.AuthToken do + @user_salt "user auth salt" + + @doc """ + Encodes the given `user` id and signs it, returning a token + clients can use as identification when using the API. + """ + def sign(user) do + Phoenix.Token.sign(FlixWeb.Endpoint, @user_salt, %{id: user.id}) + end + + @doc """ + Decodes the original data from the given `token` and + verifies its integrity. + """ + def verify(token) do + Phoenix.Token.verify(FlixWeb.Endpoint, @user_salt, token, [ + max_age: 365 * 24 * 3600 + ]) + end +end diff --git a/lib/flix_web/channels/user_socket.ex b/lib/flix_web/channels/user_socket.ex index 1a59ede..cc84f80 100644 --- a/lib/flix_web/channels/user_socket.ex +++ b/lib/flix_web/channels/user_socket.ex @@ -1,5 +1,6 @@ -defmodule FlixWeb.UserSocket do +defmodule FlixWeb.Channels.UserSocket do use Phoenix.Socket + use Absinthe.Phoenix.Socket, schema: FlixWeb.GraphQL.Schema ## Channels # channel "room:*", FlixWeb.RoomChannel diff --git a/lib/flix_web/controllers/movie_controller.ex b/lib/flix_web/controllers/movie_controller.ex index bbe6682..7b69d63 100644 --- a/lib/flix_web/controllers/movie_controller.ex +++ b/lib/flix_web/controllers/movie_controller.ex @@ -31,10 +31,10 @@ defmodule FlixWeb.MovieController do def create(conn, %{"movie" => movie_params}) do case Catalogs.create_movie(movie_params) do - {:ok, %{movie_info: movie_info, movie_poster: _movie_poster}} -> + {:ok, %{movie: movie, movie_poster: _movie_poster}} -> conn |> put_flash(:notice, "Movie created successfully.") - |> redirect(to: Routes.movie_path(conn, :show, movie_info)) + |> redirect(to: Routes.movie_path(conn, :show, movie)) {:error, :movie_info, changeset, _changes_so_far} -> conn diff --git a/lib/flix_web/endpoint.ex b/lib/flix_web/endpoint.ex index 7ac53d4..4424884 100644 --- a/lib/flix_web/endpoint.ex +++ b/lib/flix_web/endpoint.ex @@ -1,5 +1,6 @@ defmodule FlixWeb.Endpoint do use Phoenix.Endpoint, otp_app: :flix + use Absinthe.Phoenix.Endpoint # The session will be stored in the cookie and signed, # this means its contents can be read but not tampered with. @@ -10,10 +11,9 @@ defmodule FlixWeb.Endpoint do signing_salt: "NFzKYqak" ] - socket("/socket", FlixWeb.UserSocket, + socket "/socket", FlixWeb.Channels.UserSocket, websocket: [timeout: 45_000], longpoll: false - ) socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]) diff --git a/lib/flix_web/graphql/changeset_errors.ex b/lib/flix_web/graphql/changeset_errors.ex new file mode 100644 index 0000000..59df053 --- /dev/null +++ b/lib/flix_web/graphql/changeset_errors.ex @@ -0,0 +1,15 @@ +defmodule FlixWeb.GraphQL.ChangesetErrors do + @doc """ + Traverses the changeset errors and returns a map of + error messages. For example: + + %{title: ["can't be blank"], released_on: ["can't be blank"]} + """ + def error_details(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + end +end diff --git a/lib/flix_web/graphql/middleware/authenticate.ex b/lib/flix_web/graphql/middleware/authenticate.ex new file mode 100644 index 0000000..5c93dc9 --- /dev/null +++ b/lib/flix_web/graphql/middleware/authenticate.ex @@ -0,0 +1,14 @@ +defmodule FlixWeb.GraphQL.Middleware.Authenticate do + @behaviour Absinthe.Middleware + + def call(resolution, _config) do + case resolution.context do + %{current_user: _} -> + resolution + + _ -> + resolution + |> Absinthe.Resolution.put_result({:error, "You must sign in first!"}) + end + end +end diff --git a/lib/flix_web/graphql/resolvers/accounts.ex b/lib/flix_web/graphql/resolvers/accounts.ex new file mode 100644 index 0000000..77b148a --- /dev/null +++ b/lib/flix_web/graphql/resolvers/accounts.ex @@ -0,0 +1,38 @@ +defmodule FlixWeb.GraphQL.Resolvers.Accounts do + alias Flix.Accounts + alias FlixWeb.GraphQL.ChangesetErrors + + def signin(_parent, %{email: email, password: password}, _resolution) do + case Accounts.authenticate(email, password) do + :error -> + {:error, "Whoops, invalid credentials!"} + + {:ok, user} -> + token = FlixWeb.AuthToken.sign(user) + {:ok, %{user: user, token: token}} + end + end + + def signup(_parent, args, _resolution) do + case Accounts.register_user(args) do + {:error, changeset} -> + { + :error, + message: "Could not create account", + details: ChangesetErrors.error_details(changeset) + } + + {:ok, user} -> + token = FlixWeb.AuthToken.sign(user) + {:ok, %{user: user, token: token}} + end + end + + def me(_parent, _args, %{context: %{current_user: user}}) do + {:ok, user} + end + + def me(_parent, _args, _resolution) do + {:ok, nil} + end +end diff --git a/lib/flix_web/graphql/resolvers/genre_resolver.ex b/lib/flix_web/graphql/resolvers/genre_resolver.ex index 1a9c549..4fde250 100644 --- a/lib/flix_web/graphql/resolvers/genre_resolver.ex +++ b/lib/flix_web/graphql/resolvers/genre_resolver.ex @@ -1,4 +1,4 @@ -defmodule FlixWeb.Graphql.Resolvers.GenreResolver do +defmodule FlixWeb.GraphQL.Resolvers.GenreResolver do alias Flix.Catalogs def list_genres(%Flix.Catalogs.Movie{} = movie, _args, _resolution) do diff --git a/lib/flix_web/graphql/resolvers/movie_resolver.ex b/lib/flix_web/graphql/resolvers/movie_resolver.ex index 262f6f4..4d58d97 100644 --- a/lib/flix_web/graphql/resolvers/movie_resolver.ex +++ b/lib/flix_web/graphql/resolvers/movie_resolver.ex @@ -1,4 +1,4 @@ -defmodule FlixWeb.Graphql.Resolvers.MovieResolver do +defmodule FlixWeb.GraphQL.Resolvers.MovieResolver do alias Flix.Catalogs alias Flix.Catalogs.Movie alias Flix.Repo diff --git a/lib/flix_web/graphql/resolvers/review_resolver.ex b/lib/flix_web/graphql/resolvers/review_resolver.ex index 1c06091..eb3b385 100644 --- a/lib/flix_web/graphql/resolvers/review_resolver.ex +++ b/lib/flix_web/graphql/resolvers/review_resolver.ex @@ -1,4 +1,4 @@ -defmodule FlixWeb.Graphql.Resolvers.ReviewResolver do +defmodule FlixWeb.GraphQL.Resolvers.ReviewResolver do alias Flix.Catalogs def list_reviews(%Flix.Catalogs.Movie{} = movie, _args, _resolution) do diff --git a/lib/flix_web/graphql/resolvers/user_resolver.ex b/lib/flix_web/graphql/resolvers/user_resolver.ex index 65087cb..815e04e 100644 --- a/lib/flix_web/graphql/resolvers/user_resolver.ex +++ b/lib/flix_web/graphql/resolvers/user_resolver.ex @@ -1,16 +1,16 @@ -defmodule FlixWeb.Graphql.Resolvers.UserResolver do +defmodule FlixWeb.GraphQL.Resolvers.UserResolver do alias Flix.Accounts alias Flix.Accounts.User alias Flix.Repo - def get_user(_root, %{id: id}, _info) do + def get_user(_parent, %{id: id}, _resolution) do case User |> Repo.get(id) do nil -> {:error, "User id \"#{id}\" not found"} user -> {:ok, user} end end - def list_users(_root, _args, _info) do + def list_users(_parent, _args, _resolution) do {:ok, Accounts.list_users()} end end diff --git a/lib/flix_web/graphql/schema.ex b/lib/flix_web/graphql/schema.ex index 1c9a40b..9162ae0 100644 --- a/lib/flix_web/graphql/schema.ex +++ b/lib/flix_web/graphql/schema.ex @@ -1,14 +1,19 @@ -defmodule FlixWeb.Graphql.Schema do +defmodule FlixWeb.GraphQL.Schema do use Absinthe.Schema - import_types(FlixWeb.Graphql.Types.{ + import_types Absinthe.Type.Custom + + import_types(FlixWeb.GraphQL.Types.Custom.UUID4) + + import_types(FlixWeb.GraphQL.Types.{ Genre, Movie, Review, + Session, User }) - import_types(FlixWeb.Graphql.Schemas.Queries.{ + import_types(FlixWeb.GraphQL.Schemas.Queries.{ Movie, User }) @@ -17,4 +22,12 @@ defmodule FlixWeb.Graphql.Schema do import_fields(:movie_queries) import_fields(:user_queries) end + + import_types(FlixWeb.GraphQL.Schemas.Mutations.{ + Accounts + }) + + mutation do + import_fields(:user_mutations) + end end diff --git a/lib/flix_web/graphql/schemas/mutations/accounts.ex b/lib/flix_web/graphql/schemas/mutations/accounts.ex new file mode 100644 index 0000000..41b6b3f --- /dev/null +++ b/lib/flix_web/graphql/schemas/mutations/accounts.ex @@ -0,0 +1,23 @@ +defmodule FlixWeb.GraphQL.Schemas.Mutations.Accounts do + use Absinthe.Schema.Notation + + alias FlixWeb.GraphQL.Resolvers + + object :user_mutations do + @desc "Create a user account" + field :signup, :session do + arg :name, non_null(:string) + arg :email, non_null(:string) + arg :username, non_null(:string) + arg :password, non_null(:string) + resolve &Resolvers.Accounts.signup/3 + end + + @desc "Sign in a user" + field :signin, :session do + arg :email, non_null(:string) + arg :password, non_null(:string) + resolve &Resolvers.Accounts.signin/3 + end + end +end diff --git a/lib/flix_web/graphql/schemas/mutations/catalogs.ex b/lib/flix_web/graphql/schemas/mutations/catalogs.ex new file mode 100644 index 0000000..8425be6 --- /dev/null +++ b/lib/flix_web/graphql/schemas/mutations/catalogs.ex @@ -0,0 +1,2 @@ +defmodule FlixWeb.GraphQL.Schemas.Mutations.Catalogs do +end diff --git a/lib/flix_web/graphql/schemas/queries/movie.ex b/lib/flix_web/graphql/schemas/queries/movie.ex index 7bc127c..702b084 100644 --- a/lib/flix_web/graphql/schemas/queries/movie.ex +++ b/lib/flix_web/graphql/schemas/queries/movie.ex @@ -1,21 +1,21 @@ -defmodule FlixWeb.Graphql.Schemas.Queries.Movie do +defmodule FlixWeb.GraphQL.Schemas.Queries.Movie do use Absinthe.Schema.Notation - alias FlixWeb.Graphql.Resolvers.MovieResolver + alias FlixWeb.GraphQL.Resolvers object :movie_queries do @desc "get a single movie" field :movie, :movie do arg :id, non_null(:id) - resolve(&MovieResolver.get_movie/3) + resolve(&Resolvers.MovieResolver.get_movie/3) end @desc "list all movies" field :movies, list_of(:movie) do arg(:filter, :string, default_value: nil) - resolve(&MovieResolver.list_movies/3) + resolve(&Resolvers.MovieResolver.list_movies/3) end end end diff --git a/lib/flix_web/graphql/schemas/queries/user.ex b/lib/flix_web/graphql/schemas/queries/user.ex index 6a984f6..fb828e1 100644 --- a/lib/flix_web/graphql/schemas/queries/user.ex +++ b/lib/flix_web/graphql/schemas/queries/user.ex @@ -1,7 +1,7 @@ -defmodule FlixWeb.Graphql.Schemas.Queries.User do +defmodule FlixWeb.GraphQL.Schemas.Queries.User do use Absinthe.Schema.Notation - alias FlixWeb.Graphql.Resolvers.UserResolver + alias FlixWeb.GraphQL.Resolvers.UserResolver object :user_queries do @desc "get a single user" diff --git a/lib/flix_web/graphql/types/custom/uuid4.ex b/lib/flix_web/graphql/types/custom/uuid4.ex new file mode 100644 index 0000000..91414af --- /dev/null +++ b/lib/flix_web/graphql/types/custom/uuid4.ex @@ -0,0 +1,36 @@ +defmodule FlixWeb.GraphQL.Types.Custom.UUID4 do + @moduledoc """ + The UUID4 scalar type allows UUID4 compliant strings to be passed in and out. + Requires `{ :ecto, ">= 0.0.0" }` package: https://github.com/elixir-ecto/ecto + """ + use Absinthe.Schema.Notation + + alias Ecto.UUID + + scalar :uuid4, name: "UUID4" do + description(""" + The `UUID4` scalar type represents UUID4 compliant string data, represented as UTF-8 + character sequences. The UUID4 type is most often used to represent unique + human-readable ID strings. + """) + + serialize(&encode/1) + parse(&decode/1) + end + + @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error + @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil} + defp decode(%Absinthe.Blueprint.Input.String{value: value}) do + UUID.cast(value) + end + + defp decode(%Absinthe.Blueprint.Input.Null{}) do + {:ok, nil} + end + + defp decode(_) do + :error + end + + defp encode(value), do: value +end diff --git a/lib/flix_web/graphql/types/genre.ex b/lib/flix_web/graphql/types/genre.ex index 1dcf960..909e184 100644 --- a/lib/flix_web/graphql/types/genre.ex +++ b/lib/flix_web/graphql/types/genre.ex @@ -1,4 +1,4 @@ -defmodule FlixWeb.Graphql.Types.Genre do +defmodule FlixWeb.GraphQL.Types.Genre do use Absinthe.Schema.Notation @desc "a genre" diff --git a/lib/flix_web/graphql/types/movie.ex b/lib/flix_web/graphql/types/movie.ex index 7fe2ec1..179c760 100644 --- a/lib/flix_web/graphql/types/movie.ex +++ b/lib/flix_web/graphql/types/movie.ex @@ -1,9 +1,7 @@ -defmodule FlixWeb.Graphql.Types.Movie do +defmodule FlixWeb.GraphQL.Types.Movie do use Absinthe.Schema.Notation - import_types(Absinthe.Type.Custom) - - alias FlixWeb.Graphql.Resolvers + alias FlixWeb.GraphQL.Resolvers @desc "a movie" object :movie do diff --git a/lib/flix_web/graphql/types/review.ex b/lib/flix_web/graphql/types/review.ex index d4e99ad..0129ff1 100644 --- a/lib/flix_web/graphql/types/review.ex +++ b/lib/flix_web/graphql/types/review.ex @@ -1,4 +1,4 @@ -defmodule FlixWeb.Graphql.Types.Review do +defmodule FlixWeb.GraphQL.Types.Review do use Absinthe.Schema.Notation @desc "a review" diff --git a/lib/flix_web/graphql/types/session.ex b/lib/flix_web/graphql/types/session.ex new file mode 100644 index 0000000..4df7ac4 --- /dev/null +++ b/lib/flix_web/graphql/types/session.ex @@ -0,0 +1,8 @@ +defmodule FlixWeb.GraphQL.Types.Session do + use Absinthe.Schema.Notation + + object :session do + field :user, non_null(:user) + field :token, non_null(:string) + end +end diff --git a/lib/flix_web/graphql/types/user.ex b/lib/flix_web/graphql/types/user.ex index e659539..1ee7c15 100644 --- a/lib/flix_web/graphql/types/user.ex +++ b/lib/flix_web/graphql/types/user.ex @@ -1,4 +1,4 @@ -defmodule FlixWeb.Graphql.Types.User do +defmodule FlixWeb.GraphQL.Types.User do use Absinthe.Schema.Notation alias Flix.Accounts.User diff --git a/lib/flix_web/plugs/set_current_user.ex b/lib/flix_web/plugs/set_current_user.ex new file mode 100644 index 0000000..c9c9fd2 --- /dev/null +++ b/lib/flix_web/plugs/set_current_user.ex @@ -0,0 +1,32 @@ +# Adds an Absinthe execution context to the Phoenix connection. +# If a valid auth token is in the request header, the corresponding +# user is added to the context which is then available to all +# resolvers. Otherwise, the context is empty. +# +# This plug runs prior to `Absinthe.Plug` in the `:api` router +# so that the context is set up and `Absinthe.Plug` can extract +# the context from the connection. + +defmodule FlixWeb.Plugs.SetCurrentUser do + @behaviour Plug + + import Plug.Conn + + alias Flix.Accounts + + def init(opts), do: opts + + def call(conn, _) do + context = build_context(conn) + Absinthe.Plug.put_options(conn, context: context) + end + + defp build_context(conn) do + with ["Bearer " <> token] <- get_req_header(conn, "authorization"), + {:ok, user} <- Accounts.get_user_by_session_token(token) do + %{current_user: user} + else + _ -> %{} + end + end +end diff --git a/lib/flix_web/router.ex b/lib/flix_web/router.ex index 1036926..7309af0 100644 --- a/lib/flix_web/router.ex +++ b/lib/flix_web/router.ex @@ -14,6 +14,7 @@ defmodule FlixWeb.Router do pipeline :api do plug :accepts, ["json"] + plug FlixWeb.Plugs.SetCurrentUser end scope "/" do @@ -22,14 +23,14 @@ defmodule FlixWeb.Router do if Mix.env() in [:dev, :test] do forward "/graphiql", Absinthe.Plug.GraphiQL, - schema: FlixWeb.Graphql.Schema, - json_codec: Jason, - interface: :playground + schema: FlixWeb.GraphQL.Schema, + socket: FlixWeb.Channels.UserSocket, + json_codec: Jason end - forward "/graphql", + forward "/api", Absinthe.Plug, - schema: FlixWeb.Graphql.Schema + schema: FlixWeb.GraphQL.Schema end scope "/", FlixWeb do diff --git a/mix.exs b/mix.exs index 3ea9c9b..7acb335 100644 --- a/mix.exs +++ b/mix.exs @@ -5,12 +5,15 @@ defmodule Flix.MixProject do [ app: :flix, version: "0.1.0", - elixir: "~> 1.13.3", + elixir: "~> 1.13.4", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:gettext] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + preferred_cli_env: [ + "test.watch": :test + ] ] end @@ -20,12 +23,17 @@ defmodule Flix.MixProject do def application do [ mod: {Flix.Application, []}, - extra_applications: [:logger, :runtime_tools, :phoenix_html_simplified_helpers] + extra_applications: [ + :logger, + :phoenix_html_simplified_helpers, + :runtime_tools + ] ] end # Specifies which paths to compile per environment. defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(:dev), do: ["lib", "dev/support"] defp elixirc_paths(_), do: ["lib"] # Specifies your project dependencies. @@ -46,9 +54,10 @@ defmodule Flix.MixProject do {:telemetry_metrics, "~> 0.6.1"}, {:telemetry_poller, "~> 1.0"}, {:gettext, "~> 0.18.2"}, - {:jason, "~> 1.2.2"}, + {:jason, "~> 1.3.0"}, {:plug_cowboy, "~> 2.5.2"}, {:absinthe_plug, "~> 1.5.8"}, + {:absinthe_phoenix, "~> 2.0.2"}, {:cors_plug, "~> 2.0.3"}, {:bcrypt_elixir, "~> 2.0"}, {:number, "~> 1.0"}, @@ -63,9 +72,9 @@ defmodule Flix.MixProject do {:ex_parameterize, "~> 1.0"}, {:bamboo, "~> 2.1.0"}, {:bamboo_phoenix, "~> 1.0"}, - {:credo, "~> 1.5", only: [:dev, :test], runtime: false} - # , - # {:mix_test_watch, "~> 1.1"} + {:credo, "~> 1.6.4", only: [:dev, :test], runtime: false}, + {:mix_test_watch, "~> 1.1.0", only: [:dev, :test], runtime: false}, + {:ex_machina, "~> 2.7.0", only: :test} ] end diff --git a/mix.lock b/mix.lock index 25ad428..3dc2222 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "absinthe": {:hex, :absinthe, "1.7.0", "36819e7b1fd5046c9c734f27fe7e564aed3bda59f0354c37cd2df88fd32dd014", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "566a5b5519afc9b29c4d367f0c6768162de3ec03e9bf9916f9dc2bcbe7c09643"}, + "absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.2", "e607b438db900049b9b3760f8ecd0591017a46122fffed7057bf6989020992b5", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d36918925c380dc7d2ed7d039c9a3b4182ec36723f7417a68745ade5aab22f8d"}, "absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"}, "bamboo": {:hex, :bamboo, "2.1.0", "3c58f862efd74fa8c8d48a410ac592b41f7d24785e828566f7a0af549269ddc3", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0ad2623b9a1d2dc06dcf289b59df9ebc522f49f3a21971ec87a8fce04e6d33e"}, "bamboo_phoenix": {:hex, :bamboo_phoenix, "1.0.0", "f3cc591ffb163ed0bf935d256f1f4645cd870cf436545601215745fb9cc9953f", [:mix], [{:bamboo, ">= 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.3.0", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "6db88fbb26019c84a47994bb2bd879c0887c29ce6c559bc6385fd54eb8b37dee"}, @@ -23,14 +24,15 @@ "esbuild": {:hex, :esbuild, "0.4.0", "9f17db148aead4cf1e6e6a584214357287a93407b5fb51a031f122b61385d4c2", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "b61e4e6b92ffe45e4ee4755a22de6211a67c67987dc02afb35a425a0add1d447"}, "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.3.2", "92a63b72d763b488510626d528775b26831f5c82b066a63a3128054b7a09de28", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "b235b27131409bcc293c343bf39f1fbdd32892aa237b3f13752e914dc2979960"}, + "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_parameterize": {:hex, :ex_parameterize, "1.0.0", "03153ce953872886c21f7fca89e1416ad05d96c4def5c684136ecb418e49dc23", [:mix], [], "hexpm", "24fea3625bf3fbe1ffda0136738ac73b92070cffea312709a538410db0b10448"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "floki": {:hex, :floki, "0.32.0", "f915dc15258bc997d49be1f5ef7d3992f8834d6f5695270acad17b41f5bcc8e2", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "1c5a91cae1fd8931c26a4826b5e2372c284813904c8bacb468b5de39c7ececbd"}, + "floki": {:hex, :floki, "0.32.1", "dfe3b8db3b793939c264e6f785bca01753d17318d144bd44b407fb3493acaa87", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "d4b91c713e4a784a3f7b1e3cc016eefc619f6b1c3898464222867cafd3c681a3"}, "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, @@ -38,25 +40,25 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "number": {:hex, :number, "1.0.3", "932c8a2d478a181c624138958ca88a78070332191b8061717270d939778c9857", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "dd397bbc096b2ca965a6a430126cc9cf7b9ef7421130def69bcf572232ca0f18"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "phoenix": {:hex, :phoenix, "1.6.6", "281c8ce8dccc9f60607346b72cdfc597c3dde134dd9df28dff08282f0b751754", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "807bd646e64cd9dc83db016199715faba72758e6db1de0707eef0a2da4924364"}, + "phoenix": {:hex, :phoenix, "1.6.7", "f1de32418bbbcd471f4fe74d3860ee9c8e8c6c36a0ec173be8ff468a5d72ac90", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b354a4f11d9a2f3a380fb731042dae064f22d7aed8c7e7c024a2459f12994aad"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, "phoenix_html_simplified_helpers": {:hex, :phoenix_html_simplified_helpers, "2.1.0", "252c80df9a5bf8c8312b8fff57bc9735fcc07c599a2e825967d8b919b89a1eae", [:mix], [{:ecto, "~> 3.0 or ~> 2.2 or ~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, ">= 0.11.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:timex, "~> 3.4 or ~> 3.3 or ~> 3.2", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "5444ec916abfaa72e38fd57974d7ceb18a08433bbfc2c1094f0a5a5064c03165"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.7", "05a42377075868a678d446361effba80cefef19ab98941c01a7a4c7560b29121", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25eaf41028eb351b90d4f69671874643a09944098fefd0d01d442f40a6091b6f"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.9", "36b5aa812bc3ccd64c9630f6b3234d9ea21105493237e927aae19d0ba758f0db", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f7ebc3e0ba0c5f6b6996ed6c901ddbfdaba59a6d09b569e7cb2f2f7d693b4455"}, "phoenix_mtm": {:git, "https://github.com/adam12/phoenix_mtm", "4f508e3fdd427e6cf1271c8e0f78dbdb90bb6a9b", []}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, - "plug": {:hex, :plug, "1.13.4", "addb6e125347226e3b11489e23d22a60f7ab74786befb86c14f94fb5f23ca9a4", [:mix], [{:mime, "~> 1.0 or ~> 2.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.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "06114c1f2a334212fe3ae567dbb3b1d29fd492c1a09783d52f3d489c1a6f4cf2"}, + "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.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.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "postgrex": {:hex, :postgrex, "0.15.13", "7794e697481799aee8982688c261901de493eb64451feee6ea58207d7266d54a", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "3ffb76e1a97cfefe5c6a95632a27ffb67f28871c9741fb585f9d1c3cd2af70f1"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.2", "4729f997286811fabdd8288f8474e0840a76573051062f066c4b597e76f14f9f", [:mix], [], "hexpm", "6894e68a120f454534d99045ea3325f7740ea71260bc315f82e29731d570a6e8"}, - "swoosh": {:hex, :swoosh, "1.6.3", "598d3f07641004bedb3eede40057760ae18be1073cff72f079ca1e1fc9cd97b9", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "81ff9d7c7c4005a57465a7eb712edd71db51829aef94c8a34c30c5b9e9964adf"}, - "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, + "swoosh": {:hex, :swoosh, "1.6.4", "ce3a4bf3e5276fd114178ebc5ed072ee0c177a7b3a09e5992aa005778ac143c2", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad4c8b534812433730b6241a1d9df38b1da75fdfa340f51887a31d7e9343fffe"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, diff --git a/phoenix_static_buildpack.config b/phoenix_static_buildpack.config index 3359867..b2cb3d0 100644 --- a/phoenix_static_buildpack.config +++ b/phoenix_static_buildpack.config @@ -1,2 +1,2 @@ # Node.js version -node_version=14.19.0 +node_version=14.19.1 diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 36b6a8b..af2409b 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -18,314 +18,4 @@ # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) -import Ecto.Query - -alias Flix.Accounts -alias Flix.Catalogs -alias Flix.Catalogs.Genre -alias Flix.Repo - -# clear database - -Flix.Repo.delete_all("movies") -Flix.Repo.delete_all("genres") -Flix.Repo.delete_all("users") - -# create users - -user_data = [ - %{ - name: "Conrad Taylor", - username: "conradwt", - email: "conradwt@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Chief", - username: "chief", - email: "chief@example", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Cyborg", - username: "cyborg", - email: "cyborg@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Crazy Jane", - username: "jane", - email: "jane@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Larry Trainor", - username: "larryt", - email: "larryt@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Rita Farr", - username: "ritaf", - email: "ritaf@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Cliff Steele", - username: "cliffs", - email: "cliffs@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - }, - %{ - name: "Negative Man", - username: "negativem", - email: "negativem@example.com", - password: "1qaz2wsx3edc", - password_confirmation: "1qaz2wsx3edc", - admin: true, - confirmed_at: NaiveDateTime.utc_now() - } -] - -Enum.each(user_data, fn data -> - Accounts.register_user(data) -end) - -# create genres - -genre_data = ["Action", "Adventure", "Comedy", "Drama", "Sci-Fi"] - -Enum.each(genre_data, fn data -> - Catalogs.create_genre!(%{name: data}) -end) - -query = - from(g in Genre, - where: g.name in ["Action", "Adventure", "Sci-Fi"], - order_by: g.name, - select: g.id) - -get_genres = Repo.all(query) - -# create movies - -movie_data = [ - %{ - description: - "After the devastating events of Avengers: Infinity War, the universe is in ruins. With the help of remaining allies, the Avengers assemble once more in order to undo Thanos' actions and restore order to the universe.", - director: "Anthony Russo", - duration: "181 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/avengers-end-game.png" - }, - rating: "PG-13", - released_on: "2019-04-26", - title: "My Avengers: Endgame", - total_gross: 1_223_641_414 - }, - %{ - description: - "Carol Danvers becomes one of the universe's most powerful heroes when Earth is caught in the middle of a galactic war between two alien races.", - director: "Anna Boden", - duration: "124 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/captain-marvel.png" - }, - rating: "PG-13", - released_on: "2019-03-08", - title: "Captain Marvel", - total_gross: 1_110_662_849 - }, - %{ - description: - "T'Challa, heir to the hidden but advanced kingdom of Wakanda, must step forward to lead his people into a new future and must confront a challenger from his country's past.", - director: "Ryan Coogler", - duration: "134 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/black-panther.png" - }, - rating: "PG-13", - released_on: "2018-02-16", - title: "Black Panther", - total_gross: 1_346_913_161 - }, - %{ - description: - "The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe.", - director: "Anthony Russo", - duration: "149 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/avengers-infinity-war.png" - }, - rating: "PG-13", - released_on: "2018-04-27", - title: "Avengers: Infinity War", - total_gross: 2_048_359_754 - }, - %{ - description: - "Reckless test pilot Hal Jordan is granted an alien ring that bestows him with otherworldly powers that inducts him into an intergalactic police force, the Green Lantern Corps.", - director: "Martin Campbell", - duration: "114 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/green-lantern.png" - }, - rating: "PG-13", - released_on: "2011-06-17", - title: "Green Lantern", - total_gross: 219_851_172 - }, - %{ - description: - "Four young outsiders teleport to an alternate and dangerous universe which alters their physical form in shocking ways. The four must learn to harness their new abilities and work together to save Earth from a former friend turned enemy.", - director: "Josh Trank", - duration: "100 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/fantastic-four.png" - }, - rating: "PG-13", - released_on: "2015-08-07", - title: "Fantastic Four", - total_gross: 168_257_860 - }, - %{ - description: - "When wealthy industrialist Tony Stark is forced to build an armored suit after a life-threatening incident, he ultimately decides to use its technology to fight against evil.", - director: "Jon Favreau", - duration: "126 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/ironman.png" - }, - rating: "PG-13", - released_on: "2008-05-02", - title: "Iron Man", - total_gross: 585_366_247 - }, - %{ - description: - "An alien orphan is sent from his dying planet to Earth, where he grows up to become his adoptive home's first and greatest super-hero.", - director: "Richard Donner", - duration: "143 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/superman.png" - }, - rating: "PG", - released_on: "1978-12-15", - title: "Superman", - total_gross: 300_451_603 - }, - %{ - description: - "When bitten by a genetically modified spider, a nerdy, shy, and awkward high school student gains spider-like abilities that he eventually must use to fight evil as a superhero after tragedy - befalls his family.", - director: "Sam Raimi", - duration: "121 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/spiderman.png" - }, - rating: "PG-13", - released_on: "2002-05-03", - title: "Spider-Man", - total_gross: 825_025_036 - }, - %{ - description: - "The Dark Knight of Gotham City begins his war on crime with his first major enemy being the clownishly homicidal Joker.", - director: "Tim Burton", - duration: "126 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/batman.png" - }, - rating: "PG-13", - released_on: "1989-06-23", - title: "Batman", - total_gross: 411_348_924 - }, - %{ - description: - "Patience Philips seems destined to spend her life apologizing for taking up space. Despite her artistic ability she has a more than respectable career as a graphic designer.", - director: "Jean-Christophe 'Pitof' Comar", - duration: "101 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/catwoman.png" - }, - rating: "PG-13", - released_on: "2004-07-23", - title: "Catwoman", - total_gross: 82_102_379 - }, - %{ - description: - "When a pilot crashes and tells of conflict in the outside world, Diana, an Amazonian warrior in training, leaves home to fight a war, discovering her full powers and true destiny.", - director: "Patty Jenkins", - duration: "141 min", - genres: get_genres, - main_image: %Plug.Upload{ - content_type: "image/png", - filename: "avengers-end-game.png", - path: "#{File.cwd!()}/assets/static/images/wonder-woman.png" - }, - rating: "PG-13", - released_on: "2017-06-02", - title: "Wonder Woman", - total_gross: 821_847_012 - } -] - -Enum.each(movie_data, fn data -> - Catalogs.create_movie(data) -end) +Flix.Seeds.run() diff --git a/test/flix_web/graphql/schemas/mutations/accounts_test.exs b/test/flix_web/graphql/schemas/mutations/accounts_test.exs new file mode 100644 index 0000000..8e0b60a --- /dev/null +++ b/test/flix_web/graphql/schemas/mutations/accounts_test.exs @@ -0,0 +1,46 @@ +defmodule FlixWeb.GraphQL.Schemas.Mutations.AccountsTest do + use FlixWeb.ConnCase, async: true + + alias Flix.Support.Factory + + describe "new session with valid arguments" do + test "create session" do + user = Factory.create_user() + + operation = """ + mutation Signin($email: String!) { + signin( + email: $email, + password: "1qaz2wsx3edc" + ) + { + token + user { + id + email + } + } + } + """ + + response = + post( + build_conn(), + "/api", + query: operation, + variables: %{"email" => user.email} + ) + + assert %{ + "data" => %{ + "signin" => %{ + "token" => token, + "user" => _user_data + } + } + } = json_response(response, 200) + + assert {:ok, %{id: user.id}} == FlixWeb.AuthToken.verify(token) + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex new file mode 100644 index 0000000..26d512a --- /dev/null +++ b/test/support/factory.ex @@ -0,0 +1,27 @@ +defmodule Flix.Support.Factory do + use ExMachina.Ecto, repo: Flix.Repo + + alias Flix.Accounts + alias Flix.Accounts.User + alias Flix.Accounts.UserToken + + def create_user() do + int = :erlang.unique_integer([:positive, :monotonic]) + + params = %{ + name: "User #{int}", + email: sequence(:email, &"email#{&1}@example.com"), + password: "1qaz2wsx3edc", + password_confirmation: "1qaz2wsx3edc", + username: sequence(:username, &"username#{&1}") + } + + {:ok, user} = Accounts.register_user(params) + + Ecto.Multi.new() + |> Ecto.Multi.update(:user, User.confirm_changeset(user)) + |> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"])) + + user + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index a7d55e5..dff698f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,2 +1,3 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Flix.Repo, :manual) +{:ok, _} = Application.ensure_all_started(:ex_machina)