diff --git a/backend/lib/peach/ticket.ex b/backend/lib/peach/ticket.ex index 29626b7..1d26efb 100644 --- a/backend/lib/peach/ticket.ex +++ b/backend/lib/peach/ticket.ex @@ -7,8 +7,8 @@ defmodule Peach.Ticket do schema "tickets" do field :owner, :string - field :balance, :integer - field :tier_id, :id + + belongs_to :ticket_tier, Peach.TicketTier timestamps(type: :utc_datetime) end @@ -16,8 +16,8 @@ defmodule Peach.Ticket do @doc false def changeset(ticket, attrs) do ticket - |> cast(attrs, [:owner, :balance, :tier_id]) - |> validate_required([:owner, :balance, :tier_id]) - |> validate_length(:owner, max: 66) + |> cast(attrs, [:owner]) + |> validate_required([:owner]) + |> validate_format(:owner, ~r/^0x[0-9a-fA-F]{1,64}$/) end end diff --git a/backend/lib/peach/tickets.ex b/backend/lib/peach/tickets.ex new file mode 100644 index 0000000..f8a4d03 --- /dev/null +++ b/backend/lib/peach/tickets.ex @@ -0,0 +1,18 @@ +defmodule Peach.Tickets do + @moduledoc """ + Manages the tickets for the peach app + """ + alias Peach.Repo + alias Peach.Ticket + import Ecto.Query + + def list_tickets_with_event_by_owner(owner_address) do + Repo.all( + from t in Ticket, + where: t.owner == ^owner_address, + join: tier in assoc(t, :ticket_tier), + join: event in assoc(tier, :event), + preload: [ticket_tier: {tier, event: event}] + ) + end +end diff --git a/backend/lib/peach_web/controllers/ticket_controller.ex b/backend/lib/peach_web/controllers/ticket_controller.ex new file mode 100644 index 0000000..6070749 --- /dev/null +++ b/backend/lib/peach_web/controllers/ticket_controller.ex @@ -0,0 +1,39 @@ +defmodule PeachWeb.TicketController do + use PeachWeb, :controller + alias Peach.Tickets + + def get_tickets_with_event_by_address(conn, %{"address" => address}) do + # Fetch the tickets with preloaded ticket_tier and event associations + tickets = Tickets.list_tickets_with_event_by_owner(address) + + # Group tickets by event and then by tier_id within each event + events_with_tickets = + tickets + |> Enum.group_by(fn ticket -> ticket.ticket_tier.event end) + |> Enum.map(fn {event, tickets} -> + # Group tickets by tier within each event + tickets_by_tier = + tickets + |> Enum.group_by(fn ticket -> ticket.ticket_tier end) + |> Enum.map(fn {tier, tickets} -> + %{ + "tier_id" => tier.id, + "name" => tier.name, + "description" => tier.description, + "ticket_ids" => Enum.map(tickets, & &1.id) |> Enum.sort() + } + end) + + %{ + "name" => event.name, + "location" => event.location, + "date" => event.date, + "cover" => event.cover, + "tickets" => tickets_by_tier + } + end) + + # Wrap the result in a top-level map with "events" key + json(conn, %{events: events_with_tickets}) + end +end diff --git a/backend/lib/peach_web/router.ex b/backend/lib/peach_web/router.ex index 4926cae..71651a7 100644 --- a/backend/lib/peach_web/router.ex +++ b/backend/lib/peach_web/router.ex @@ -13,6 +13,7 @@ defmodule PeachWeb.Router do patch "/events/:id/location", EventController, :update_event_location patch "/events/:id/cover", EventController, :update_event_cover patch "/events/:id/treasury", EventController, :update_event_treasury + get "/tickets/:address", TicketController, :get_tickets_with_event_by_address end # Enable LiveDashboard and Swoosh mailbox preview in development diff --git a/backend/priv/repo/migrations/20241009142013_create_tickets.exs b/backend/priv/repo/migrations/20241009142013_create_tickets.exs index 367e2a8..fa7ffa5 100644 --- a/backend/priv/repo/migrations/20241009142013_create_tickets.exs +++ b/backend/priv/repo/migrations/20241009142013_create_tickets.exs @@ -4,12 +4,11 @@ defmodule Peach.Repo.Migrations.CreateTickets do def change do create table(:tickets) do add :owner, :string - add :balance, :integer - add :tier_id, references(:ticket_tiers, on_delete: :nothing) + add :ticket_tier_id, references(:ticket_tiers, on_delete: :nothing) timestamps(type: :utc_datetime) end - create index(:tickets, [:tier_id]) + create index(:tickets, [:ticket_tier_id]) end end diff --git a/backend/test/peach_web/controllers/ticket_controller_test.exs b/backend/test/peach_web/controllers/ticket_controller_test.exs new file mode 100644 index 0000000..c1c291e --- /dev/null +++ b/backend/test/peach_web/controllers/ticket_controller_test.exs @@ -0,0 +1,105 @@ +defmodule PeachWeb.TicketControllerTest do + use PeachWeb.ConnCase, async: true + alias Peach.{Event, Repo, Ticket, TicketTier} + + setup do + # Create an event + event = + Repo.insert!(%Event{ + name: "Blockchain Conference", + date: ~N[2024-11-10 00:00:00], + description: "A blockchain event", + location: "San Francisco, CA", + cover: "https://example.com/cover.jpg", + treasury: "0x1234567890abcdef1234567890abcdef12345678" + }) + + # Create ticket tiers associated with the event + vip_tier = + Repo.insert!(%TicketTier{ + name: "VIP", + description: "Access to VIP sessions", + max_supply: 100, + event_id: event.id + }) + + standard_tier = + Repo.insert!(%TicketTier{ + name: "Standard", + description: "General admission", + max_supply: 200, + event_id: event.id + }) + + # Create tickets for the user address associated with the tiers + vip_ticket = + Repo.insert!(%Ticket{ + owner: "0xdead", + # Using tier_id from the association + ticket_tier_id: vip_tier.id + }) + + # Create tickets for the user address associated with the tiers + vip_ticket = + Repo.insert!(%Ticket{ + owner: "0xdead", + # Using tier_id from the association + ticket_tier_id: vip_tier.id + }) + + standard_ticket = + Repo.insert!(%Ticket{ + owner: "0xdead", + ticket_tier_id: standard_tier.id + }) + + # Make the created data available for all tests + {:ok, event: event, vip_ticket: vip_ticket, standard_ticket: standard_ticket} + end + + test "returns events with tickets grouped by event for a given owner", %{ + conn: conn, + vip_ticket: vip_ticket, + standard_ticket: standard_ticket + } do + # Send the GET request to the endpoint + conn = get(conn, "/api/tickets/0xdead") + + # Expected JSON structure + expected_response = %{ + "events" => [ + %{ + "name" => "Blockchain Conference", + "location" => "San Francisco, CA", + "date" => "2024-11-10T00:00:00", + "cover" => "https://example.com/cover.jpg", + "tickets" => [ + %{ + "tier_id" => vip_ticket.ticket_tier_id, + "name" => "VIP", + "description" => "Access to VIP sessions", + "ticket_ids" => [1, 2] + }, + %{ + "tier_id" => standard_ticket.ticket_tier_id, + "name" => "Standard", + "description" => "General admission", + "ticket_ids" => [3] + } + ] + } + ] + } + + # Assert that the response matches the expected JSON structure + assert json_response(conn, 200) == expected_response + end + + test "returns an empty events list if no tickets are found for the address", %{conn: conn} do + # Send the GET request to the endpoint with an address that has no tickets + conn = get(conn, "/api/tickets/0xnonexistentaddress") + + # Assert that the response is an empty list under "events" + assert json_response(conn, 200) == %{"events" => []} + end +end diff --git a/backend/test/support/data_case.ex b/backend/test/support/data_case.ex index 12907cc..f5eec96 100644 --- a/backend/test/support/data_case.ex +++ b/backend/test/support/data_case.ex @@ -40,6 +40,8 @@ defmodule Peach.DataCase do pid = Sandbox.start_owner!(Peach.Repo, shared: not tags[:async]) # Reset the sequence for the `events` table before each test Peach.Repo.query!("ALTER SEQUENCE events_id_seq RESTART WITH 1") + Peach.Repo.query!("ALTER SEQUENCE tickets_id_seq RESTART WITH 1") + Peach.Repo.query!("ALTER SEQUENCE ticket_tiers_id_seq RESTART WITH 1") on_exit(fn -> Sandbox.stop_owner(pid) end) end