defmodule Algora.Ads do @moduledoc """ The Ads context. """ import Ecto.Query, warn: false alias Algora.Repo alias Algora.Ads.{Ad, Visit, Impression, Events, ContentMetrics, Appearance, ProductReview} @pubsub Algora.PubSub def display_duration, do: :timer.minutes(2) def rotation_interval, do: :timer.minutes(30) def unsubscribe_to_ads() do Phoenix.PubSub.unsubscribe(@pubsub, topic()) end def subscribe_to_ads() do Phoenix.PubSub.subscribe(@pubsub, topic()) end defp topic(), do: "ads" defp broadcast!(topic, msg) do Phoenix.PubSub.broadcast!(@pubsub, topic, {__MODULE__, msg}) end def broadcast_ad_created!(ad) do broadcast!(topic(), %Events.AdCreated{ad: ad}) end def broadcast_ad_updated!(ad) do broadcast!(topic(), %Events.AdUpdated{ad: ad}) end def broadcast_ad_deleted!(ad) do broadcast!(topic(), %Events.AdDeleted{ad: ad}) end @doc """ Returns the list of ads. ## Examples iex> list_ads() [%Ad{}, ...] """ def list_ads do Ad |> order_by(asc: :id) |> Repo.all() end def list_active_ads do Ad |> where(verified: true, status: :active) |> order_by(asc: :id) |> Repo.all() end @doc """ Gets a single ad. Raises `Ecto.NoResultsError` if the Ad does not exist. ## Examples iex> get_ad!(123) %Ad{} iex> get_ad!(456) ** (Ecto.NoResultsError) """ def get_ad!(id), do: Repo.get!(Ad, id) def get_ad_by_slug!(slug) do Repo.get_by!(Ad, slug: slug) end @doc """ Creates a ad. ## Examples iex> create_ad(%{field: value}) {:ok, %Ad{}} iex> create_ad(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_ad(attrs \\ %{}) do %Ad{status: :active, verified: true} |> Ad.changeset(attrs) |> Repo.insert() end @doc """ Updates a ad. ## Examples iex> update_ad(ad, %{field: new_value}) {:ok, %Ad{}} iex> update_ad(ad, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_ad(%Ad{} = ad, attrs) do ad |> Ad.changeset(attrs) |> Repo.update() end @doc """ Deletes a ad. ## Examples iex> delete_ad(ad) {:ok, %Ad{}} iex> delete_ad(ad) {:error, %Ecto.Changeset{}} """ def delete_ad(%Ad{} = ad) do Repo.delete(ad) end @doc """ Returns an `%Ecto.Changeset{}` for tracking ad changes. ## Examples iex> change_ad(ad) %Ecto.Changeset{data: %Ad{}} """ def change_ad(%Ad{} = ad, attrs \\ %{}) do Ad.changeset(ad, attrs) end def track_visit(attrs \\ %{}) do %Visit{} |> Visit.changeset(attrs) |> Repo.insert() end def track_impressions(attrs \\ %{}) do %Impression{} |> Impression.changeset(attrs) |> Repo.insert() end def get_current_index([]) do 0 end def get_current_index(ads) do :os.system_time(:millisecond) |> div(rotation_interval()) |> rem(length(ads)) end def rotate_ads(ads, index \\ nil) do index = index || get_current_index(ads) Enum.concat(Enum.drop(ads, index), Enum.take(ads, index)) end def next_slot(time \\ DateTime.utc_now()) do time |> DateTime.truncate(:millisecond) |> DateTime.add(ms_until_next_slot(time), :millisecond) end def time_until_next_slot(time \\ DateTime.utc_now()) do DateTime.diff(next_slot(time), time, :millisecond) end defp ms_until_next_slot(time) do rotation_interval() - rem(DateTime.to_unix(time, :millisecond), rotation_interval()) end def list_content_metrics do Repo.all(ContentMetrics) |> Repo.preload(video: [:appearances, :product_reviews]) end def create_content_metrics(attrs \\ %{}) do %ContentMetrics{} |> ContentMetrics.changeset(attrs) |> Repo.insert() end def change_content_metrics(%ContentMetrics{} = content_metrics, attrs \\ %{}) do ContentMetrics.changeset(content_metrics, attrs) end @doc """ Gets a single content_metrics. Raises `Ecto.NoResultsError` if the ContentMetrics does not exist. ## Examples iex> get_content_metrics!(123) %ContentMetrics{} iex> get_content_metrics!(456) ** (Ecto.NoResultsError) """ def get_content_metrics!(id) do Repo.get!(ContentMetrics, id) end @doc """ Updates a content_metrics. ## Examples iex> update_content_metrics(content_metrics, %{field: new_value}) {:ok, %ContentMetrics{}} iex> update_content_metrics(content_metrics, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_content_metrics(%ContentMetrics{} = content_metrics, attrs) do content_metrics |> ContentMetrics.changeset(attrs) |> Repo.update() end def create_appearance(attrs \\ %{}) do %Appearance{} |> Appearance.changeset(attrs) |> Repo.insert() end def change_appearance(%Appearance{} = appearance, attrs \\ %{}) do Appearance.changeset(appearance, attrs) end def create_product_review(attrs \\ %{}) do %ProductReview{} |> ProductReview.changeset(attrs) |> Repo.insert() end def change_product_review(%ProductReview{} = product_review, attrs \\ %{}) do ProductReview.changeset(product_review, attrs) end def list_appearances(ad) do Appearance |> where(ad_id: ^ad.id) |> preload(video: :user) |> Repo.all() end def list_product_reviews(ad) do ProductReview |> where(ad_id: ^ad.id) |> preload(video: [:user, :content_metrics]) |> Repo.all() end def list_content_metrics(appearances) do video_ids = Enum.map(appearances, & &1.video_id) ContentMetrics |> where([cm], cm.video_id in ^video_ids) |> Repo.all() end end