151 lines
4.8 KiB
Elixir
151 lines
4.8 KiB
Elixir
defmodule Clacks.Timeline do
|
|
alias Clacks.{Repo, Actor, Activity, User, Notification}
|
|
import Clacks.Paginator
|
|
import Ecto.Query
|
|
|
|
@public "https://www.w3.org/ns/activitystreams#Public"
|
|
@timeline_types ["Create", "Announce"]
|
|
@notification_types ["Create", "Announce", "Like", "Follow"]
|
|
|
|
@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))
|
|
|> Activity.preload_object()
|
|
|> join_with_announced_or_liked()
|
|
|> 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))
|
|
|> Activity.preload_object()
|
|
|> join_with_announced_or_liked()
|
|
|> 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))
|
|
|> Activity.preload_object()
|
|
|> join_with_actors()
|
|
|> join_with_announced_or_liked()
|
|
|> select(
|
|
[activity, _object, actor, announced, announced_actor],
|
|
{activity, actor, {announced, announced_actor}}
|
|
)
|
|
|> Repo.all()
|
|
end
|
|
|
|
@spec notifications(actor :: Actor.t(), params :: map()) :: [
|
|
{:follow, activity :: Activity.t(), actor :: Actor.t()}
|
|
| {:mention, activity :: Activity.t(), actor :: Actor.t()}
|
|
| {:announce, announce :: Activity.t(), announce_actor :: Actor.t(),
|
|
activity :: Activity.t(), actor :: Actor.t()}
|
|
| {:like, like :: Activity.t(), like_actor :: Actor.t(), activity :: Activity.t(),
|
|
actor :: Actor.t()}
|
|
]
|
|
def notifications(actor, params) do
|
|
Notification
|
|
|> where([n], n.user_id == ^actor.user_id)
|
|
|> join(:inner, [n], activity in Activity, on: activity.id == n.activity_id)
|
|
|> join(:inner, [n, activity], actor in Actor, on: activity.actor == actor.ap_id)
|
|
|> join(:left, [n, activity, actor], other in Activity,
|
|
on:
|
|
n.type in ["announce", "like"] and fragment("?->>'type'", other.data) == "Create" and
|
|
fragment("?->>'object'", activity.data) == fragment("?->'object'->>'id'", other.data)
|
|
)
|
|
|> paginate(params)
|
|
|> limit(^Map.get(params, "limit", 20))
|
|
|> select(
|
|
[notification, activity, actor, original_activity],
|
|
{notification, activity, actor, original_activity}
|
|
)
|
|
|> Repo.all()
|
|
|> Enum.map(fn {notification, activity, actor, original_activity} ->
|
|
{String.to_existing_atom(notification.type), activity, actor, original_activity}
|
|
end)
|
|
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_announced_or_liked(query) do
|
|
query
|
|
|> join(:left, [a], other in Activity,
|
|
on:
|
|
fragment("?->>'type'", a.data) in ["Announce", "Like"] 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
|