Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/auto bid #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,4 @@ workflows:
version: 2
build-deploy:
jobs:
- build
- deploy:
requires:
- build
# filters:
# branches:
# only: master
- build
24 changes: 20 additions & 4 deletions lib/rebay_api/user_item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ defmodule RebayApi.UserItem do
alias RebayApi.UserItem.Bid
alias RebayApi.Listings
alias RebayApi.Accounts
alias RebayApi.Listings.Item

def list_bids do
Repo.all(Bid)
end

def get_highest_bid(item_id) do
query = from bid in Bid, where: ^item_id == bid.item_id, order_by: [desc: bid.bid_price], limit: 1
bid = Repo.one(query)
def get_highest_bid_price(item_id) do
bid = get_highest_bid(item_id)
if (bid), do: bid.bid_price
end

def get_highest_bidder_user_id(item_id) do
bid = get_highest_bid(item_id)
if (bid), do: bid.user_id
end

defp get_highest_bid(item_id) do
query = from bid in Bid, where: ^item_id == bid.item_id, order_by: [desc: bid.bid_price], limit: 1
Repo.one(query)
end

def list_bids_by_item(item_uuid) do
bidItem = Listings.get_item!(item_uuid)
query = from bid in Bid, where: ^bidItem.id == bid.item_id
Expand All @@ -28,6 +36,14 @@ defmodule RebayApi.UserItem do
get_users_bids_by_items(user_item_ids, bidUser.id)
end

def get_auto_bids_by_item(item_id) do
item_highest_bid = get_highest_bid_price(item_id)
query = from bid in Bid,
where: ^item_id == bid.item_id and not is_nil(bid.max_bid_price) and bid.max_bid_price > ^item_highest_bid,
order_by: [desc: bid.updated_at]
Repo.all(query, preload: [:bid])
end

defp get_users_bids_by_items(user_item_ids, user_id) do
Enum.reduce user_item_ids, [], fn (item_id, acc) ->
item_bid = %{}
Expand Down
32 changes: 32 additions & 0 deletions lib/rebay_api_web/controllers/bid_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,45 @@ defmodule RebayApiWeb.BidController do
|> Map.put("uuid", Ecto.UUID.generate())

with {:ok, %Bid{} = bid} <- UserItem.create_bid(bid_params) do
handle_auto_bids(item.id)

conn
|> put_status(:created)
|> put_resp_header("location", Routes.item_bid_path(conn, :show, item.uuid, bid))
|> render("show.json", bid: bid)
end
end

defp handle_auto_bids(item_id) do
item_id
|> UserItem.get_auto_bids_by_item
|> run_auto_bids(item_id)
Copy link
Member

@tpetersen0308 tpetersen0308 Aug 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be nitpicky, but I think it makes for better readability to implement pipelines when the same message is being passed through. So passing in item_id to a function that returns a collection of bids that get passed to run_auto_bids doesn't read as well to me as something more like UserItem.get_auto_bids_by_item(item_id) |> run_auto_bids(item_id). Such a minor thing here, but I think it becomes more helpful with more complicated pipelines.


case length(item_id |> UserItem.get_auto_bids_by_item) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you reduce duplication and improve readability by assigning the collection of bids to a variable?

0 -> nil
1 -> nil
_ -> handle_auto_bids(item_id)
end
end

defp run_auto_bids(bids, item_id) do
bids
|> Enum.each(fn bid ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Elixir pattern-matching can be leveraged recursively to the same effect, if you want to do this more idiomatically than with the use of each.

last_bidders_id = UserItem.get_highest_bidder_user_id(item_id)
price_increment = 100
next_bid_price = UserItem.get_highest_bid_price(item_id) + price_increment
if bid.max_bid_price >= next_bid_price && bid.user_id != last_bidders_id do
new_bid = %{
user_id: bid.user_id,
item_id: item_id,
bid_price: next_bid_price,
uuid: Ecto.UUID.generate()
}
UserItem.create_bid(new_bid)
end
end)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-bidding functionality may be worth extracting to a separate module. The controller should really just be concerned with handling requests.


def show(conn, %{"uuid" => uuid}) do
bid = UserItem.get_bid!(uuid)
render(conn, "show.json", bid: bid)
Expand Down
4 changes: 1 addition & 3 deletions lib/rebay_api_web/controllers/session_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ defmodule RebayApiWeb.SessionController do
alias RebayApi.Repo
alias RebayApi.Accounts.User

@client_host Application.get_env(:rebay_api, :client_host)

def create(%{assigns: %{ueberauth_auth: auth}} = conn, %{"provider" => provider}) do
user_params = user_params(auth, provider)
changeset = User.changeset(%User{}, user_params)
Expand All @@ -17,7 +15,7 @@ defmodule RebayApiWeb.SessionController do
|> put_session(:id, auth.credentials.token)
|> put_session(:user_uuid, user.uuid)
|> put_resp_cookie("session_id", auth.credentials.token, [http_only: true])
|> redirect(external: "#{@client_host}/login/#{user.uuid}")
|> redirect(external: "#{Application.get_env(:rebay_api, :client_host)}/login/#{user.uuid}")
{:error, %Ecto.Changeset{} = changeset} ->
conn
|> put_view(RebayApiWeb.ChangesetView)
Expand Down
2 changes: 1 addition & 1 deletion lib/rebay_api_web/views/bid_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule RebayApiWeb.BidView do
item = Repo.get!(Item, bid.item_id)
{:ok, current_date} = DateTime.now("Etc/UTC")
has_ended = DateTime.compare(item.end_date, current_date) == :lt
highest_bid_price = UserItem.get_highest_bid(item.id)
highest_bid_price = UserItem.get_highest_bid_price(item.id)
has_ended && bid.bid_price == highest_bid_price
end
end
2 changes: 1 addition & 1 deletion lib/rebay_api_web/views/item_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ defmodule RebayApiWeb.ItemView do
item = Item
|> Repo.get_by!(uuid: item_uuid)

UserItem.get_highest_bid(item.id)
UserItem.get_highest_bid_price(item.id)
end
end
37 changes: 31 additions & 6 deletions test/rebay_api/user_item_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,50 @@ defmodule RebayApi.UserItemTest do
[bid1, bid2, bid3, bid4]
end

test "get_highest_bid/1 returns max bid number" do
test "get_highest_bid_price/1 returns max bid number" do
user = TestHelpers.user_fixture()
item = TestHelpers.item_fixture(%{user_id: user.id})

UserItem.create_bid(%{bid_price: 43, uuid: Ecto.UUID.generate(), user_id: user.id, item_id: item.id})
UserItem.create_bid(%{bid_price: 44, uuid: Ecto.UUID.generate(), user_id: user.id, item_id: item.id})
UserItem.create_bid(%{bid_price: 45, uuid: Ecto.UUID.generate(), user_id: user.id, item_id: item.id})
highestBid = UserItem.get_highest_bid(item.id);
highestBid = UserItem.get_highest_bid_price(item.id);
assert highestBid == 45
end
test "get_highest_bid/1 returns nil when there are no bids" do

test "get_highest_bid_price/1 returns nil when there are no bids" do
user = TestHelpers.user_fixture()
item = TestHelpers.item_fixture(%{user_id: user.id})
highestBid = UserItem.get_highest_bid(item.id);

highestBid = UserItem.get_highest_bid_price(item.id);
assert highestBid == nil
end

test "get_highest_bidder_user_id/1 returns max bid number" do
user = TestHelpers.user_fixture()
item = TestHelpers.item_fixture(%{user_id: user.id})

UserItem.create_bid(%{bid_price: 43, uuid: Ecto.UUID.generate(), user_id: user.id, item_id: item.id})
UserItem.create_bid(%{bid_price: 44, uuid: Ecto.UUID.generate(), user_id: user.id, item_id: item.id})
UserItem.create_bid(%{bid_price: 45, uuid: Ecto.UUID.generate(), user_id: user.id, item_id: item.id})
user_id = UserItem.get_highest_bidder_user_id(item.id);
assert user_id == user.id
end

test "get_auto_bids_by_item/1 returns the current auto bids for a given item uuid" do
item = TestHelpers.item_fixture(%{ price: 100 })
_bid1 = TestHelpers.bid_fixture(%{ max_bid_price: 500, bid_price: 200, item_id: item.id })
bid2 = TestHelpers.bid_fixture(%{ max_bid_price: 700, bid_price: 300, item_id: item.id })
_bid3 = TestHelpers.bid_fixture(%{ max_bid_price: 500, bid_price: 400, item_id: item.id })
_bid4 = TestHelpers.bid_fixture(%{ bid_price: 500, item_id: item.id })
bid5 = TestHelpers.bid_fixture(%{ max_bid_price: 800, bid_price: 600, item_id: item.id })

item2 = TestHelpers.item_fixture()
_bid6 = TestHelpers.bid_fixture(%{ max_bid_price: 30, bid_price: 25, item_id: item2.id })

assert UserItem.get_auto_bids_by_item(item.id) == [bid2, bid5]
end

test "list_bids/0 returns all bids" do
bid = TestHelpers.bid_fixture()
assert UserItem.list_bids() == [bid]
Expand Down
63 changes: 63 additions & 0 deletions test/rebay_api_web/controllers/bid_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,69 @@ defmodule RebayApiWeb.BidControllerTest do
end
end

describe "auto bids" do
test "triggers auto bids after a new bid creation", %{conn: conn} do
item_owner = TestHelpers.user_fixture()
item = TestHelpers.item_fixture(%{user_id: item_owner.id, price: 100})
user1 = TestHelpers.user_fixture()
user2 = TestHelpers.user_fixture()
user3 = TestHelpers.user_fixture()
bid_uuid = Ecto.UUID.generate()
item_owner_uuid = item_owner.uuid
item_uuid = item.uuid
TestHelpers.bid_fixture(%{ max_bid_price: 700, bid_price: 200, item_id: item.id, user_id: user1.id })
TestHelpers.bid_fixture(%{ max_bid_price: 900, bid_price: 300, item_id: item.id, user_id: user2.id })

conn = conn
|> TestHelpers.valid_session(user3)
|> post(Routes.item_bid_path(conn, :create, item.uuid), %{ bid_price: 400, uuid: bid_uuid })
assert %{"uuid" => uuid} = json_response(conn, 201)["data"]

conn = get(conn, Routes.user_item_path(conn, :show, item_owner_uuid, item_uuid))
assert %{
"category" => "some category",
"current_highest_bid" => 800,
"description" => "some description",
"end_date" => "2020-07-31T06:59:59Z",
"image" => "some image",
"price" => 100,
"title" => "some title",
"user_uuid" => item_owner_uuid,
"uuid" => item_uuid
} == json_response(conn, 200)["data"]
end

test "does not trigger bidding users auto bids after a new bid creation", %{conn: conn} do
item_owner = TestHelpers.user_fixture()
item = TestHelpers.item_fixture(%{user_id: item_owner.id, price: 100})
user1 = TestHelpers.user_fixture()
user2 = TestHelpers.user_fixture()
bid_uuid = Ecto.UUID.generate()
item_owner_uuid = item_owner.uuid
item_uuid = item.uuid
TestHelpers.bid_fixture(%{ max_bid_price: 600, bid_price: 200, item_id: item.id, user_id: user1.id })
TestHelpers.bid_fixture(%{ max_bid_price: 700, bid_price: 300, item_id: item.id, user_id: user2.id })

conn = conn
|> TestHelpers.valid_session(user1)
|> post(Routes.item_bid_path(conn, :create, item.uuid), %{ bid_price: 400, uuid: bid_uuid })
assert %{"uuid" => uuid} = json_response(conn, 201)["data"]

conn = get(conn, Routes.user_item_path(conn, :show, item_owner_uuid, item_uuid))
assert %{
"category" => "some category",
"current_highest_bid" => 700,
"description" => "some description",
"end_date" => "2020-07-31T06:59:59Z",
"image" => "some image",
"price" => 100,
"title" => "some title",
"user_uuid" => item_owner_uuid,
"uuid" => item_uuid
} == json_response(conn, 200)["data"]
end
end

defp create_bid(_) do
bid = fixture(:bid)
bid = Bid
Expand Down
16 changes: 8 additions & 8 deletions test/rebay_api_web/controllers/session_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ defmodule RebayApiWeb.SessionControllerTest do
alias RebayApi.Repo
alias RebayApi.Accounts.User
alias RebayApi.TestHelpers

test "redirects user to Google for authentication", %{conn: conn} do
conn = get conn, "/auth/google?scope=email%20profile"
assert redirected_to(conn, 302)
end

test "creates user from Google information", %{conn: conn} do
users = User |> Repo.all
assert Enum.count(users) == 0
Expand All @@ -17,8 +17,8 @@ defmodule RebayApiWeb.SessionControllerTest do
credentials: %{token: "fdsnoafhnoofh08h38h"},
info: %{
image: "some-image-url.foo",
email: "[email protected]",
first_name: "Travis",
email: "[email protected]",
first_name: "Travis",
provider: "google",
token: "foo",
},
Expand All @@ -30,7 +30,7 @@ defmodule RebayApiWeb.SessionControllerTest do

users = User |> Repo.all
[ user | _ ] = users

assert Enum.count(users) == 1
assert get_resp_header(conn, "location") == ["#{Application.get_env(:rebay_api, :client_host)}/login/#{user.uuid}"]
assert fetch_cookies(conn).cookies["session_id"] == "fdsnoafhnoofh08h38h"
Expand All @@ -53,8 +53,8 @@ defmodule RebayApiWeb.SessionControllerTest do
credentials: %{token: "fdsnoafhnoofh08h38h"},
info: %{
image: invalid_image,
email: "[email protected]",
first_name: "Travis",
email: "[email protected]",
first_name: "Travis",
provider: "google",
token: "foo",
},
Expand All @@ -67,4 +67,4 @@ defmodule RebayApiWeb.SessionControllerTest do
users = User |> Repo.all
assert Enum.count(users) == 0
end
end
end