clacks/lib/clacks/timeline.ex

117 lines
3.2 KiB
Elixir

defmodule Clacks.Timeline do
alias Clacks.{Repo, Actor, Activity, User}
import Clacks.Paginator
import Ecto.Query
@public "https://www.w3.org/ns/activitystreams#Public"
@timeline_types ["Create", "Announce"]
@spec actor_timeline(
actor :: Actor.t(),
params :: map(),
only_public :: boolean()
) :: [
{Activity.t(), Actor.t(), {Activity.t() | nil, Actor.t() | nil}}
]
def actor_timeline(actor, params, only_public \\ true) do
Activity
|> restrict_to_actor(actor.ap_id)
|> restrict_to_types(@timeline_types)
|> restrict_to_public(only_public)
|> paginate(params)
|> limit(^Map.get(params, "limit", 20))
|> join_with_announces()
|> select(
[activity, announced, announced_actor],
{activity, {announced, announced_actor}}
)
|> Repo.all()
end
@spec home_timeline(user :: User.t(), params :: map()) :: [
{Activity.t(), Actor.t(), {Activity.t() | nil, Actor.t() | nil}}
]
def home_timeline(user, params) do
user =
case user.actor do
%Ecto.Association.NotLoaded{} ->
Repo.preload(user, :actor)
_ ->
user
end
Activity
|> join_with_actors()
|> where(
[activity, actor],
fragment("?->>'actor'", activity.data) == ^user.actor.ap_id or
^user.actor.ap_id in actor.followers
)
|> restrict_to_types(@timeline_types)
|> paginate(params)
|> limit(^Map.get(params, "limit", 20))
|> join_with_announces()
|> select(
[activity, actor, announced, announced_actor],
{activity, actor, {announced, announced_actor}}
)
|> Repo.all()
end
@spec local_timeline(params :: map()) :: [
{Activity.t(), Actor.t(), {Activity.t() | nil, Actor.t() | nil}}
]
def local_timeline(params) do
Activity
|> where([a], a.local)
|> restrict_to_public(true)
|> restrict_to_types(@timeline_types)
|> paginate(params)
|> limit(^Map.get(params, "limit", 20))
|> join_with_actors()
|> join_with_announces()
|> select(
[activity, actor, announced, announced_actor],
{activity, actor, {announced, announced_actor}}
)
|> Repo.all()
end
defp restrict_to_actor(query, actor_id) do
where(query, [a], fragment("?->>'actor'", a.data) == ^actor_id)
end
defp restrict_to_types(query, types) do
where(query, [a], fragment("?->>'type'", a.data) in ^types)
end
defp restrict_to_public(query, true) do
where(
query,
[a],
fragment("?->'to' \\? ?", a.data, @public) or fragment("?->'cc' \\? ?", a.data, @public)
)
end
defp restrict_to_public(query, false), do: query
defp join_with_actors(query) do
query
|> join(:left, [o], a in Actor, on: a.ap_id == fragment("?->>'actor'", o.data))
end
defp join_with_announces(query) do
query
|> join(:left, [a], other in Activity,
on:
fragment("?->>'type'", a.data) == "Announce" and
fragment("?->>'type'", other.data) == "Create" and
fragment("?->>'object'", a.data) == fragment("?->'object'->>'id'", other.data)
)
|> join(:left, [a, ..., announced], actor in Actor,
on: actor.ap_id == fragment("?->>'actor'", announced.data)
)
end
end