Last active
December 22, 2024 18:40
-
-
Save mayel/880d98c7d70343f45515260267db6f76 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Bonfire.Social.Feeds.Presets do | |
@moduledoc """ | |
Preset feed definitions | |
""" | |
use Bonfire.Common.E | |
import Bonfire.Common.Utils | |
alias Bonfire.Common.Config | |
alias Bonfire.Common.Types | |
@type filter_params :: %{ | |
feed_name: String.t() | nil, | |
feed_ids: String.t() | nil, | |
activity_types: list(String.t()) | nil, | |
# TODO: rename to exclude_activity_types | |
exclude_verbs: list(String.t()) | nil, | |
object_types: list(String.t()) | nil, | |
media_types: list(String.t()) | nil, | |
subjects: list(String.t()) | nil, | |
objects: list(String.t()) | nil, | |
creators: list(String.t()) | nil, | |
tags: list(String.t()) | nil, | |
time_limit: integer() | nil, | |
sort_by: atom() | nil, | |
sort_order: :asc | :desc | nil | |
} | |
def feed_presets do | |
Config.get([__MODULE__, :preload_presets], %{ | |
my: %{ | |
description: "Activities of people I follow", | |
filters: %{feed_name: :my}, | |
current_user_required: true | |
}, | |
explore: %{ | |
description: "All activities", | |
filters: %{feed_name: :explore}, | |
exclude_verbs: [:like] | |
}, | |
local: %{ | |
description: "Local instance activities", | |
filters: %{feed_name: :local}, | |
exclude_verbs: [:like] | |
}, | |
remote: %{ | |
description: "Remote/Fediverse activities", | |
filters: %{feed_name: :remote}, | |
exclude_verbs: [:like] | |
}, | |
notifications: %{ | |
description: "Notifications for me", | |
filters: %{feed_name: :notifications}, | |
current_user_required: true | |
}, | |
messages: %{ | |
description: "Messages for me", | |
filters: %{feed_name: :messages}, | |
current_user_required: true | |
}, | |
# User interaction feeds | |
liked_by_me: %{ | |
description: "Posts I've liked", | |
filters: %{activity_types: :like, subjects: :me}, | |
parameterized: true | |
}, | |
my_bookmarks: %{ | |
description: "Posts I've bookmarked", | |
filters: %{activity_types: :bookmark, subjects: :me}, | |
current_user_required: true, | |
parameterized: true | |
}, | |
my_requests: %{ | |
description: "Pending requests for me", | |
filters: %{feed_name: :notifications, activity_types: :request}, | |
current_user_required: true | |
}, | |
# User-specific feeds | |
user_activities: %{ | |
description: "A specific user's activities", | |
# $username is replaced at runtime | |
filters: %{subjects: :by}, | |
parameterized: true | |
}, | |
user_followers: %{ | |
description: "Followers of a specific user", | |
filters: %{object_types: :follow, objects: :by}, | |
parameterized: true | |
}, | |
user_following: %{ | |
description: "Users followed by a specific user", | |
filters: %{activity_types: :follow, subjects: :by}, | |
parameterized: true | |
}, | |
user_posts: %{ | |
description: "Posts by a specific user", | |
filters: %{creators: :by, object_types: :post}, | |
parameterized: true | |
}, | |
# user_publications: %{ | |
# description: "Publications by a specific user", | |
# filters: %{creators: :by, media_types: :publication}, | |
# parameterized: true | |
# }, | |
# Content type feeds | |
# publications: %{ | |
# description: "All known publications", | |
# filters: %{media_types: :publication]} | |
# }, | |
images: %{ | |
description: "All known images", | |
filters: %{media_types: "image"} | |
}, | |
# Hashtag feeds | |
hashtag: %{ | |
description: "Activities with a specific hashtag", | |
filters: %{tags: :hashtag}, | |
parameterized: true | |
}, | |
mentions: %{ | |
description: "Activities with a specific @ mention", | |
filters: %{tags: :mentioned}, | |
parameterized: true | |
}, | |
# Moderation feeds | |
flagged_by_me: %{ | |
description: "Content I've flagged", | |
filters: %{activity_types: :flag, subjects: :me}, | |
parameterized: true, | |
current_user_required: true | |
}, | |
flagged_content: %{ | |
description: "Content flagged by anyone (mods only)", | |
filters: %{activity_types: :flag}, | |
current_user_required: true, | |
mod_required: true | |
}, | |
# Combined filters examples | |
trending_discussions: %{ | |
description: "Popular discussions from the last 7 days", | |
filters: %{ | |
time_limit: 7, | |
sort_by: :num_replies, | |
sort_order: :desc | |
} | |
}, | |
local_media: %{ | |
description: "Media from local instance", | |
filters: %{ | |
feed_name: :local, | |
media_types: "*" | |
} | |
} | |
}) | |
end | |
@doc """ | |
Gets an aliased feed's filters by name, with optional parameters. | |
## Examples | |
# 1: Retrieve a preset feed without parameters | |
iex> preset_feed_filters("local", []) | |
{:ok, %{feed_name: :local}} | |
# 1: Retrieve a preset feed without parameters | |
iex> preset_feed_filters(:local, []) | |
{:ok, %{feed_name: :local}} | |
# 2: Retrieve a preset feed with parameters | |
iex> preset_feed_filters("user_activities", [by: "alice"]) | |
{:ok, %{subjects: "alice"}} | |
# 3: Feed not found (error case) | |
iex> preset_feed_filters("unknown_feed", []) | |
{:error, :not_found} | |
# 4: Preset feed with parameterized filters | |
iex> preset_feed_filters("liked_by_me", current_user: %{id: "alice"}) | |
{:ok, %{activity_types: :like, subjects: %{id: "alice"}}} | |
# 5: Feed with `current_user_required` should check for current user | |
iex> preset_feed_filters("messages", current_user: %{id: "alice"}) | |
{:ok, %{feed_name: :messages}} | |
# 6: Feed with `current_user_required` and no current user | |
iex> preset_feed_filters("messages", []) | |
** (Bonfire.Fail.Auth) You need to log in first. | |
# 7: Custom feed with additional parameters | |
iex> preset_feed_filters("user_followers", [by: "alice"]) | |
{:ok, %{object_types: :follow, objects: "alice"}} | |
""" | |
@spec preset_feed_filters(String.t(), map()) :: {:ok, filter_params()} | {:error, atom()} | |
def preset_feed_filters(name, opts \\ []) do | |
case feed_definition_if_permitted(name, opts) do | |
{:error, e} -> | |
{:error, e} | |
{:ok, %{parameterized: true, filters: filters}} -> | |
{:ok, parameterize_filters(filters, opts)} | |
{:ok, %{filters: filters}} -> | |
{:ok, filters} | |
end | |
end | |
defp feed_definition_if_permitted(name, opts) when is_atom(name) do | |
case feed_presets()[name] do | |
nil -> | |
{:error, :not_found} | |
# %{admin_required: true} = alias when not user.is_admin -> | |
# {:error, :unauthorized} # TODO | |
# %{mod_required: true} = alias when not user.is_moderator -> | |
# {:error, :unauthorized} # TODO | |
%{current_user_required: true} = feed_def -> | |
if current_user_required!(opts), do: {:ok, feed_def} | |
feed_def -> | |
{:ok, feed_def} | |
end | |
end | |
defp feed_definition_if_permitted(name, opts) do | |
case Types.maybe_to_atom!(name) do | |
nil -> | |
{:error, :not_found} | |
name -> | |
feed_definition_if_permitted(name, opts) | |
end | |
end | |
@doc """ | |
Parameterizes the filters by replacing parameterized values with values from `opts`. | |
## Examples | |
# 1: Parameterizing a simple filter | |
iex> parameterize_filters(%{subjects: [:me]}, current_user: %{id: "alice"}) | |
%{subjects: [%{id: "alice"}]} | |
# 2: Parameterizing multiple filters | |
iex> parameterize_filters(%{subjects: :me, tags: [:hashtag]}, current_user: %{id: "alice"}, hashtag: "elixir") | |
%{subjects: %{id: "alice"}, tags: ["elixir"]} | |
# 3: Parameterizing with undefined options | |
iex> parameterize_filters(%{subjects: :me}, current_user: nil) | |
%{subjects: nil} | |
# 4: Handling filters that don't require parameterization | |
iex> parameterize_filters(%{activity_types: ["like"]}, current_user: "bob") | |
%{activity_types: ["like"]} | |
""" | |
def parameterize_filters(filters, opts) do | |
filters | |
|> Enum.map(fn | |
{k, v} when is_list(v) -> | |
{k, Enum.map(v, &replace_parameters(&1, opts))} | |
{k, v} -> | |
{k, replace_parameters(v, opts)} | |
end) | |
|> Enum.into(%{}) | |
end | |
@doc """ | |
Replaces parameters in the filter value with the actual values from `opts`. | |
## Examples | |
# 1: Replacing a `me` parameter with the current user | |
iex> replace_parameters(:me, current_user: %{id: "alice"}) | |
%{id: "alice"} | |
# 2: Replacing a `:current_user` parameter with the current user only if available | |
iex> replace_parameters(:current_user, current_user: nil) | |
nil | |
# 3: Failing with `:current_user_required` parameter if we have no current user | |
iex> replace_parameters(:current_user_required, current_user: nil) | |
** (Bonfire.Fail.Auth) You need to log in first. | |
# 4: Handling a parameter that is not in the opts | |
iex> replace_parameters(:unknown, current_user: "bob") | |
:unknown | |
""" | |
def replace_parameters(:current_user, opts) do | |
current_user(opts) | |
end | |
def replace_parameters(:current_user_required, opts) do | |
current_user_required!(opts) | |
end | |
def replace_parameters(:me, opts) do | |
current_user(opts) | |
end | |
def replace_parameters(value, opts) do | |
ed(opts, value, value) | |
end | |
def replace_parameters(value, _params), do: value | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment