defmodule Mobilizon.Instances do @moduledoc """ The instances context """ alias Ecto.Adapters.SQL alias Mobilizon.Actors.{Actor, Follower} alias Mobilizon.Instances.Instance alias Mobilizon.Storage.{Page, Repo} import Ecto.Query @is_null_fragment "CASE WHEN ? IS NULL THEN FALSE ELSE TRUE END" @spec instances(Keyword.t()) :: Page.t(Instance.t()) def instances(options) do page = Keyword.get(options, :page) limit = Keyword.get(options, :limit) order_by = Keyword.get(options, :order_by) direction = Keyword.get(options, :direction) filter_domain = Keyword.get(options, :filter_domain) # suspend_status = Keyword.get(options, :filter_suspend_status) follow_status = Keyword.get(options, :filter_follow_status) order_by_options = Keyword.new([{direction, order_by}]) subquery = Actor |> where( [a], a.preferred_username == "relay" and a.type == :Application and not is_nil(a.domain) ) |> join(:left, [a], f1 in Follower, on: f1.target_actor_id == a.id) |> join(:left, [a], f2 in Follower, on: f2.actor_id == a.id) |> select([a, f1, f2], %{ domain: a.domain, has_relay: fragment(@is_null_fragment, a.id), following: fragment(@is_null_fragment, f2.id), following_approved: f2.approved, follower: fragment(@is_null_fragment, f1.id), follower_approved: f1.approved }) query = Instance |> join(:left, [i], s in subquery(subquery), on: i.domain == s.domain) |> select([i, s], {i, s}) |> order_by(^order_by_options) query = if is_nil(filter_domain) or filter_domain == "" do query else where(query, [i], like(i.domain, ^"%#{filter_domain}%")) end query = case follow_status do :following -> where(query, [i, s], s.following == true) :followed -> where(query, [i, s], s.follower == true) :all -> query end %Page{elements: elements} = paged_instances = Page.build_page(query, page, limit, :domain) %Page{ paged_instances | elements: Enum.map(elements, &convert_instance_meta/1) } end @spec instance(String.t()) :: Instance.t() | nil def instance(domain) do Instance |> where(domain: ^domain) |> Repo.one() end @spec all_domains :: list(Instance.t()) def all_domains do Instance |> distinct(true) |> select([:domain]) |> Repo.all() end @spec refresh :: %{ :rows => nil | [[term()] | binary()], :num_rows => non_neg_integer(), optional(atom()) => any() } def refresh do SQL.query!(Repo, "REFRESH MATERIALIZED VIEW instances") end defp convert_instance_meta( {instance, %{ domain: _domain, follower: follower, follower_approved: follower_approved, following: following, following_approved: following_approved, has_relay: has_relay }} ) do instance |> Map.put(:follower_status, follow_status(following, following_approved)) |> Map.put(:followed_status, follow_status(follower, follower_approved)) |> Map.put(:has_relay, has_relay) end defp follow_status(true, true), do: :approved defp follow_status(true, false), do: :pending defp follow_status(false, _), do: :none defp follow_status(nil, _), do: :none end