Skip to content

Commit

Permalink
Merge branch '9-product-api' into 'master'
Browse files Browse the repository at this point in the history
Resolve "Product API"

Closes elixirmoney#9

See merge request !4
  • Loading branch information
Muhammad Surya committed Jul 30, 2016
2 parents 50ea1b1 + e98a7e6 commit a2db60a
Show file tree
Hide file tree
Showing 36 changed files with 846 additions and 98 deletions.
4 changes: 1 addition & 3 deletions .gitlab-ci.yml
@@ -1,4 +1,4 @@
image: trenpixster/elixir:latest
image: fs02/elixir-stack:latest
services:
- postgres:latest

Expand All @@ -9,8 +9,6 @@ variables:
POSTGRES_PASSWORD: postgres

before_script:
- apt-get update
- apt-get install -y imagemagick
- cp config/test.ci.exs config/test.exs
- mix deps.get
- mix ecto.create
Expand Down
9 changes: 9 additions & 0 deletions config/config.exs
Expand Up @@ -32,6 +32,13 @@ config :guardian, Guardian,
secret_key: "ODPxS_)D0-eDE_AKSD_ASODkasdpqwdokw[awdQA12]",
serializer: Makancuy.GuardianSerializer

# Configure Money
config :money,
default_currency: :IDR,
separator: ".",
delimeter: ",",
symbol: true

# Configure ExAdmin
config :ex_admin,
repo: Makancuy.Repo,
Expand All @@ -44,7 +51,9 @@ config :ex_admin,
Makancuy.ExAdmin.User,
Makancuy.ExAdmin.Profile,
Makancuy.ExAdmin.Restaurant,
Makancuy.ExAdmin.Product,
Makancuy.ExAdmin.Cuisine,
Makancuy.ExAdmin.Category,
Makancuy.ExAdmin.RestaurantManager
]

Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Expand Up @@ -44,7 +44,8 @@ defmodule Makancuy.Mixfile do
{:comeonin, "~> 1.0"},
{:arc, "~> 0.5.2"},
{:arc_ecto, "~> 0.4.2"},
{:ex_admin, github: "smpallen99/ex_admin"},
{:money, github: "Fs02/money"},
{:ex_admin, github: "smpallen99/ex_admin", override: true},
{:ex_machina, "~> 1.0", only: :test, github: "Fs02/ex_machina"},
{:cowboy, "~> 1.0"}]
end
Expand Down
19 changes: 10 additions & 9 deletions mix.lock
@@ -1,35 +1,36 @@
%{"arc": {:hex, :arc, "0.5.3", "2bc1b4ce8c1064ae12015f5612f3e3853d354859905bb34088b3579bf34a519d", [:mix], [{:httpoison, "~> 0.7", [hex: :httpoison, optional: true]}, {:poison, "~> 1.2 or ~> 2.0", [hex: :poison, optional: true]}, {:ex_aws, "~> 0.4.10 or ~> 0.5.0", [hex: :ex_aws, optional: true]}]},
"arc_ecto": {:hex, :arc_ecto, "0.4.2", "9f7fd7da4dfa8d16292bb6a49a65971b12c663cb4c9711674d6302d6272f9e52", [:mix], [{:ecto, "~> 2.0.0-rc.3", [hex: :ecto, optional: false]}, {:arc, "~> 0.2", [hex: :arc, optional: false]}]},
%{"arc": {:hex, :arc, "0.5.3", "2bc1b4ce8c1064ae12015f5612f3e3853d354859905bb34088b3579bf34a519d", [:mix], [{:ex_aws, "~> 0.4.10 or ~> 0.5.0", [hex: :ex_aws, optional: true]}, {:httpoison, "~> 0.7", [hex: :httpoison, optional: true]}, {:poison, "~> 1.2 or ~> 2.0", [hex: :poison, optional: true]}]},
"arc_ecto": {:hex, :arc_ecto, "0.4.2", "9f7fd7da4dfa8d16292bb6a49a65971b12c663cb4c9711674d6302d6272f9e52", [:mix], [{:arc, "~> 0.2", [hex: :arc, optional: false]}, {:ecto, "~> 2.0.0-rc.3", [hex: :ecto, optional: false]}]},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], []},
"comeonin": {:hex, :comeonin, "1.6.0", "65d3217bf171feb2600ac639089d1316c5438407a30dfbe9f465d75500db2171", [:mix, :make, :make], [{:comeonin_i18n, "~> 0.1", [hex: :comeonin_i18n, optional: true]}]},
"connection": {:hex, :connection, "1.0.3", "3145f7416be3df248a4935f24e3221dc467c1e3a158d62015b35bd54da365786", [:mix], []},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
"csvlixir": {:hex, :csvlixir, "1.0.0", "e9fd30abfca2d312390060e86bb7ec52487c813824dcccad45bb13e85ecad6b1", [:mix], []},
"db_connection": {:hex, :db_connection, "1.0.0-rc.3", "d9ceb670fe300271140af46d357b669983cd16bc0d01206d7d3222dde56cf038", [:mix], [{:sbroker, "~> 1.0.0-beta.3", [hex: :sbroker, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:connection, "~> 1.0.2", [hex: :connection, optional: false]}]},
"db_connection": {:hex, :db_connection, "1.0.0-rc.3", "d9ceb670fe300271140af46d357b669983cd16bc0d01206d7d3222dde56cf038", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0.0-beta.3", [hex: :sbroker, optional: true]}]},
"decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], []},
"ecto": {:hex, :ecto, "2.0.2", "b02331c1f20bbe944dbd33c8ecd8f1ccffecc02e344c4471a891baf3a25f5406", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:db_connection, "~> 1.0-rc.2", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}]},
"ecto": {:hex, :ecto, "2.0.2", "b02331c1f20bbe944dbd33c8ecd8f1ccffecc02e344c4471a891baf3a25f5406", [:mix], [{:db_connection, "~> 1.0-rc.2", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]},
"ex_admin": {:git, "https://github.com/smpallen99/ex_admin.git", "8047c91aa2c217b51de9e06e0804b7e90813e28d", []},
"ex_machina": {:git, "https://github.com/Fs02/ex_machina.git", "1c2a50cd661aa39c24b3b99fe3573324ee1a730c", []},
"ex_queb": {:hex, :ex_queb, "0.1.2", "a7bea897c490875a716042675cec4013b5075de083c26ec1b7f0e0fb5144f5a1", [:mix], [{:ecto, "~> 1.1", [hex: :ecto, optional: false]}]},
"exactor": {:hex, :exactor, "2.2.0", "2a7418b82d974fe8276edd62c1facf4a9dc06339cdf11b5dcd10359e107fe5c3", [:mix], []},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
"gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], []},
"guardian": {:hex, :guardian, "0.12.0", "ab1f0a1ab0cd8f4f9c8cca6e28d61136ca682684cf0f82e55a50e8061be7575a", [:mix], [{:uuid, ">=1.1.1", [hex: :uuid, optional: false]}, {:poison, ">= 1.3.0", [hex: :poison, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}, {:jose, "~> 1.6", [hex: :jose, optional: false]}]},
"guardian": {:hex, :guardian, "0.12.0", "ab1f0a1ab0cd8f4f9c8cca6e28d61136ca682684cf0f82e55a50e8061be7575a", [:mix], [{:jose, "~> 1.6", [hex: :jose, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}, {:poison, ">= 1.3.0", [hex: :poison, optional: false]}, {:uuid, ">=1.1.1", [hex: :uuid, optional: false]}]},
"inflex": {:hex, :inflex, "1.7.0", "4466a34b7d8e871d8164619ba0f3b8410ec782e900f0ae1d3d27a5875a29532e", [:mix], []},
"jose": {:hex, :jose, "1.7.5", "f19f890f7aaa1261230af691e2f1e0fc60bf44bce7d34c86022d0efa8875628a", [:mix, :rebar], [{:base64url, "~> 0.0.1", [hex: :base64url, optional: false]}]},
"phoenix": {:hex, :phoenix, "1.2.0", "1bdeb99c254f4c534cdf98fd201dede682297ccc62fcac5d57a2627c3b6681fb", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
"money": {:git, "https://github.com/Fs02/money.git", "04eb26d98951e1a3d6d27ce26bd20f1c2458448d", []},
"phoenix": {:hex, :phoenix, "1.2.0", "1bdeb99c254f4c534cdf98fd201dede682297ccc62fcac5d57a2627c3b6681fb", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"phoenix_ecto": {:hex, :phoenix_ecto, "3.0.0", "b947aaf03d076f5b1448f87828f22fb7710478ee38455c67cc3fe8e9a4dfd015", [:mix], [{:ecto, "~> 2.0.0-rc", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.6", [hex: :phoenix_html, optional: true]}]},
"phoenix_html": {:hex, :phoenix_html, "2.6.0", "b9f7e091eb3d908586d9634596478fb9e577ee033d76f4ff327a745569bdd2d8", [:mix], [{:plug, "~> 0.13 or ~> 1.0", [hex: :plug, optional: false]}]},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.5", "829218c4152ba1e9848e2bf8e161fcde6b4ec679a516259442561d21fde68d0b", [:mix], [{:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}, {:fs, "~> 0.9.1", [hex: :fs, optional: false]}]},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.5", "829218c4152ba1e9848e2bf8e161fcde6b4ec679a516259442561d21fde68d0b", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.0", "c31af4be22afeeebfaf246592778c8c840e5a1ddc7ca87610c41ccfb160c2c57", [:mix], []},
"plug": {:hex, :plug, "1.1.6", "8927e4028433fcb859e000b9389ee9c37c80eb28378eeeea31b0273350bf668b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}]},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},
"postgrex": {:hex, :postgrex, "0.11.2", "139755c1359d3c5c6d6e8b1ea72556d39e2746f61c6ddfb442813c91f53487e8", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.0-rc", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]},
"ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []},
"scrivener": {:hex, :scrivener, "2.0.0", "38e59a1c37e989c40a22c019da245a86b257182aee45a074292c9038fab39678", [:mix], []},
"scrivener_ecto": {:hex, :scrivener_ecto, "1.0.0", "66a0c4cf722e965cd4fc8b5ad48c09409c19a5a125bf2435d2f3fb18d0d72f50", [:mix], [{:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:scrivener, "~> 2.0", [hex: :scrivener, optional: false]}]},
"scrivener_headers": {:hex, :scrivener_headers, "2.0.0", "6e7c417d6ed3ae55bf40727dc8220a6c5b581c96f8941800ed8d874c53061af0", [:mix], [{:scrivener, "~> 2.0", [hex: :scrivener, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: true]}]},
"scrivener_ecto": {:hex, :scrivener_ecto, "1.0.0", "66a0c4cf722e965cd4fc8b5ad48c09409c19a5a125bf2435d2f3fb18d0d72f50", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:scrivener, "~> 2.0", [hex: :scrivener, optional: false]}]},
"scrivener_headers": {:hex, :scrivener_headers, "2.0.0", "6e7c417d6ed3ae55bf40727dc8220a6c5b581c96f8941800ed8d874c53061af0", [:mix], [{:plug, "~> 1.1", [hex: :plug, optional: true]}, {:scrivener, "~> 2.0", [hex: :scrivener, optional: false]}]},
"uuid": {:hex, :uuid, "1.1.4", "36c7734e4c8e357f2f67ba57fb61799d60c20a7f817b104896cca64b857e3686", [:mix], []},
"xain": {:hex, :xain, "0.6.0", "5b61cfe3ffc17904759ee30a699f9e0b1aefd943e996ee4cafea76e5b2f59e3a", [:mix], []}}
17 changes: 17 additions & 0 deletions priv/repo/migrations/20160724165506_create_api_product.exs
@@ -0,0 +1,17 @@
defmodule Makancuy.Repo.Migrations.CreateApi.Product do
use Ecto.Migration

def change do
create table(:products, primary_key: false) do
add :id, :binary_id, primary_key: true
add :name, :string
add :description, :string
add :price, :integer
add :image, :string
add :restaurant_id, references(:restaurants, type: :binary_id), null: false

timestamps()
end

end
end
13 changes: 13 additions & 0 deletions priv/repo/migrations/20160725175534_create_category.exs
@@ -0,0 +1,13 @@
defmodule Makancuy.Repo.Migrations.CreateCategory do
use Ecto.Migration

def change do
create table(:categories, primary_key: false) do
add :id, :binary_id, primary_key: true
add :name, :string

timestamps()
end

end
end
@@ -0,0 +1,9 @@
defmodule Makancuy.Repo.Migrations.AddCategoryToProduct do
use Ecto.Migration

def change do
alter table(:products) do
add :category_id, references(:categories, type: :binary_id), null: false
end
end
end
26 changes: 26 additions & 0 deletions test/controllers/api/category_controller_test.exs
@@ -0,0 +1,26 @@
defmodule Makancuy.Api.CategoryControllerTest do
use Makancuy.ConnCase
import Makancuy.Factory

setup %{conn: conn} do
{:ok, conn: put_req_header(conn, "accept", "application/json")}
end

test "lists all entries on index", %{conn: conn} do
conn = get conn, api_category_path(conn, :index)
assert json_response(conn, 200) == []
end

test "shows chosen resource", %{conn: conn} do
category = insert(:category)
conn = get conn, api_category_path(conn, :show, category)
assert json_response(conn, 200) == %{"id" => category.id,
"name" => category.name}
end

test "renders page not found when id is nonexistent", %{conn: conn} do
assert_error_sent 404, fn ->
get conn, api_category_path(conn, :show, Ecto.UUID.generate)
end
end
end
142 changes: 142 additions & 0 deletions test/controllers/api/product_controller_test.exs
@@ -0,0 +1,142 @@
defmodule Makancuy.Api.ProductControllerTest do
use Makancuy.ConnCase
import Makancuy.Factory

alias Makancuy.Product

setup %{conn: conn} do
user = insert(:user)
restaurant = insert(:restaurant)
insert(:restaurant_manager, user: user, restaurant: restaurant)
{:ok, jwt, _full_claims} = Guardian.encode_and_sign(user)

{
:ok,
conn: put_req_header(conn, "accept", "application/json"),
user: user,
restaurant: restaurant,
jwt: jwt
}
end

test "lists all entries on index", %{conn: conn, restaurant: restaurant} do
conn = get conn, api_product_path(conn, :index, restaurant_id: restaurant.id)
assert json_response(conn, 200) == []
end

test "shows chosen resource", %{conn: conn, restaurant: restaurant} do
product = insert(:product, restaurant_id: restaurant.id)

conn = get conn, api_product_path(conn, :show, product)
assert json_response(conn, 200) == %{"id" => product.id,
"name" => product.name,
"description" => product.description,
"price" => product.price |> to_string,
"image" => %{
"small" => Makancuy.Image.url({product.image, product}, :small),
"medium" => Makancuy.Image.url({product.image, product}, :medium),
"large" => Makancuy.Image.url({product.image, product}, :large)
},
"category" => %{
"id" => product.category_id
},
"restaurant" => %{
"id" => restaurant.id
}
}
end

test "renders page not found when id is nonexistent", %{conn: conn} do
assert_error_sent 404, fn ->
get conn, api_product_path(conn, :show, Ecto.UUID.generate)
end
end

test "creates and renders resource when data is valid", %{conn: conn, jwt: jwt} do
category = insert(:category)
product_params = params_for(:product, category_id: category.id)

conn =
conn
|> put_req_header("authorization", "Bearer" <> " " <> jwt)
|> post(api_product_path(conn, :create), product_params)

assert json_response(conn, 201)["id"]
assert Repo.get_by(Product, product_params)
end

test "does not create resource and renders errors when data is invalid", %{conn: conn, jwt: jwt} do
conn =
conn
|> put_req_header("authorization", "Bearer" <> " " <> jwt)
|> post(api_product_path(conn, :create), %{})
assert json_response(conn, 422)["errors"] != %{}
end

test "create request autnehtication when not signed in", %{conn: conn} do
category = insert(:category)
product_params = params_for(:product, category_id: category.id)

conn = post conn, api_product_path(conn, :create), product_params
assert json_response(conn, 401)["message"] == "authentication required"
end

test "create renders not found when no restaurant associated", %{conn: conn} do
user = insert(:user)

{:ok, jwt, _full_claims} = Guardian.encode_and_sign(user)

conn =
conn
|> put_req_header("authorization", "Bearer" <> " " <> jwt)
|> post(api_product_path(conn, :create), %{})
assert json_response(conn, 404)["message"] == "no restaurant registered"
end

test "updates and renders chosen resource when data is valid", %{conn: conn, jwt: jwt, restaurant: restaurant} do
product = insert(:product, restaurant_id: restaurant.id)

new_product_params = params_for(:product)

conn =
conn
|> put_req_header("authorization", "Bearer" <> " " <> jwt)
|> put(api_product_path(conn, :update, product), new_product_params)
assert json_response(conn, 200)["id"]
assert Repo.get_by(Product, new_product_params)
end

test "deletes chosen resource", %{conn: conn, jwt: jwt, restaurant: restaurant} do
product = insert(:product, restaurant: restaurant)

conn =
conn
|> put_req_header("authorization", "Bearer" <> " " <> jwt)
|> delete(api_product_path(conn, :delete, product), %{})

assert response(conn, 204)
refute Repo.get(Product, product.id)
end

test "upload image", %{conn: conn, restaurant: restaurant, jwt: jwt} do
product = insert(:product, restaurant: restaurant)
upload = %Plug.Upload{path: "test/fixtures/image.jpg", filename: "image.jpg", content_type: "image/jpeg"}

conn =
conn
|> put_req_header("authorization", "Bearer" <> " " <> jwt)
|> put(api_product_upload_path(conn, :image_upload, product), %{filedata: upload})

assert json_response(conn, 201)["id"]
end

test "does not upload cover when not authenticated", %{conn: conn} do
upload = %Plug.Upload{path: "/test/fixtures/image.jpg", filename: "image.jpg", content_type: "image/jpeg"}

conn =
conn
|> put(api_restaurant_upload_path(conn, :cover_upload), %{filedata: upload})

assert json_response(conn, 401)["message"] == "authentication required"
end
end

0 comments on commit a2db60a

Please sign in to comment.