@@ -9,7 +9,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
|
||||
alias Mobilizon.{Actors, Config, Crypto, Mention, Share}
|
||||
alias Mobilizon.Actors.{ActorOpenness, ActorType, ActorVisibility, Follower, Member}
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.{Event, FeedToken}
|
||||
alias Mobilizon.Media.File
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
@@ -27,6 +27,9 @@ defmodule Mobilizon.Actors.Actor do
|
||||
following_url: String.t(),
|
||||
followers_url: String.t(),
|
||||
shared_inbox_url: String.t(),
|
||||
resources_url: String.t(),
|
||||
posts_url: String.t(),
|
||||
events_url: String.t(),
|
||||
type: ActorType.t(),
|
||||
name: String.t(),
|
||||
domain: String.t(),
|
||||
@@ -62,6 +65,10 @@ defmodule Mobilizon.Actors.Actor do
|
||||
:shared_inbox_url,
|
||||
:following_url,
|
||||
:followers_url,
|
||||
:posts_url,
|
||||
:events_url,
|
||||
:todos_url,
|
||||
:discussions_url,
|
||||
:type,
|
||||
:name,
|
||||
:domain,
|
||||
@@ -96,6 +103,10 @@ defmodule Mobilizon.Actors.Actor do
|
||||
:followers_url,
|
||||
:members_url,
|
||||
:resources_url,
|
||||
:posts_url,
|
||||
:todos_url,
|
||||
:events_url,
|
||||
:discussions_url,
|
||||
:name,
|
||||
:summary,
|
||||
:manually_approves_followers,
|
||||
@@ -117,6 +128,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
|
||||
schema "actors" do
|
||||
field(:url, :string)
|
||||
|
||||
field(:outbox_url, :string)
|
||||
field(:inbox_url, :string)
|
||||
field(:following_url, :string)
|
||||
@@ -124,7 +136,11 @@ defmodule Mobilizon.Actors.Actor do
|
||||
field(:shared_inbox_url, :string)
|
||||
field(:members_url, :string)
|
||||
field(:resources_url, :string)
|
||||
field(:posts_url, :string)
|
||||
field(:events_url, :string)
|
||||
field(:todos_url, :string)
|
||||
field(:discussions_url, :string)
|
||||
|
||||
field(:type, ActorType, default: :Person)
|
||||
field(:name, :string)
|
||||
field(:domain, :string, default: nil)
|
||||
@@ -344,7 +360,8 @@ defmodule Mobilizon.Actors.Actor do
|
||||
def build_url("relay", :page, _args),
|
||||
do: Endpoint |> Routes.activity_pub_url(:relay) |> URI.decode()
|
||||
|
||||
def build_url(preferred_username, endpoint, args) when endpoint in [:page, :resources] do
|
||||
def build_url(preferred_username, endpoint, args)
|
||||
when endpoint in [:page, :resources, :posts, :discussions, :events, :todos] do
|
||||
endpoint = if endpoint == :page, do: :actor, else: endpoint
|
||||
|
||||
Endpoint
|
||||
@@ -353,7 +370,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
end
|
||||
|
||||
def build_url(preferred_username, endpoint, args)
|
||||
when endpoint in [:outbox, :following, :followers, :members, :todos] do
|
||||
when endpoint in [:outbox, :following, :followers, :members] do
|
||||
Endpoint
|
||||
|> Routes.activity_pub_url(endpoint, preferred_username, args)
|
||||
|> URI.decode()
|
||||
|
||||
@@ -55,6 +55,8 @@ defmodule Mobilizon.Actors do
|
||||
|
||||
@public_visibility [:public, :unlisted]
|
||||
@administrator_roles [:creator, :administrator]
|
||||
@moderator_roles [:moderator] ++ @administrator_roles
|
||||
@member_roles [:member] ++ @moderator_roles
|
||||
@actor_preloads [:user, :organized_events, :comments]
|
||||
|
||||
@doc """
|
||||
@@ -118,6 +120,17 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
New function to replace `Mobilizon.Actors.get_actor_by_url/1` with
|
||||
better signature
|
||||
"""
|
||||
@spec get_actor_by_url_2(String.t(), boolean) :: Actor.t() | nil
|
||||
def get_actor_by_url_2(url, preload \\ false) do
|
||||
Actor
|
||||
|> Repo.get_by(url: url)
|
||||
|> preload_followers(preload)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an actor by its URL (ActivityPub ID). The `:preload` option allows to
|
||||
preload the followers relation.
|
||||
@@ -181,9 +194,17 @@ defmodule Mobilizon.Actors do
|
||||
"""
|
||||
@spec create_actor(map) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_actor(attrs \\ %{}) do
|
||||
%Actor{}
|
||||
|> Actor.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
type = Map.get(attrs, :type, :Person)
|
||||
|
||||
case type do
|
||||
:Person ->
|
||||
%Actor{}
|
||||
|> Actor.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
|
||||
:Group ->
|
||||
create_group(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -238,7 +259,8 @@ defmodule Mobilizon.Actors do
|
||||
name: name,
|
||||
summary: summary,
|
||||
avatar: transform_media_file(avatar),
|
||||
banner: transform_media_file(banner)
|
||||
banner: transform_media_file(banner),
|
||||
last_refreshed_at: DateTime.utc_now()
|
||||
]
|
||||
],
|
||||
conflict_target: [:url]
|
||||
@@ -285,6 +307,7 @@ defmodule Mobilizon.Actors do
|
||||
"""
|
||||
@spec perform(atom(), Actor.t()) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
|
||||
def perform(:delete_actor, %Actor{} = actor, options \\ @delete_actor_default_options) do
|
||||
Logger.info("Going to delete actor #{actor.url}")
|
||||
actor = Repo.preload(actor, @actor_preloads)
|
||||
|
||||
delete_actor_options = Keyword.merge(@delete_actor_default_options, options)
|
||||
@@ -306,10 +329,18 @@ defmodule Mobilizon.Actors do
|
||||
case Repo.transaction(multi) do
|
||||
{:ok, %{actor: %Actor{} = actor}} ->
|
||||
{:ok, true} = Cachex.del(:activity_pub, "actor_#{actor.preferred_username}")
|
||||
Logger.info("Deleted actor #{actor.url}")
|
||||
{:ok, actor}
|
||||
|
||||
{:error, remove, error, _} when remove in [:remove_banner, :remove_avatar] ->
|
||||
Logger.error("Error while deleting actor's banner or avatar")
|
||||
Logger.error(inspect(error, pretty: true))
|
||||
{:error, error}
|
||||
|
||||
err ->
|
||||
Logger.error("Unknown error while deleting actor")
|
||||
Logger.error(inspect(err, pretty: true))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -438,23 +469,47 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_local_group_by_url(String.t()) :: Actor.t()
|
||||
def get_local_group_by_url(group_url) do
|
||||
group_query()
|
||||
|> where([q], q.url == ^group_url and is_nil(q.domain))
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec get_group_by_members_url(String.t()) :: Actor.t()
|
||||
def get_group_by_members_url(members_url) do
|
||||
group_query()
|
||||
|> where([q], q.members_url == ^members_url)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a group.
|
||||
|
||||
If the group is local, creates an admin actor as well from `creator_actor_id`.
|
||||
"""
|
||||
@spec create_group(map) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_group(attrs \\ %{}) do
|
||||
with {:ok, %{insert_group: %Actor{} = group, add_admin_member: %Member{} = _admin_member}} <-
|
||||
Multi.new()
|
||||
|> Multi.insert(:insert_group, Actor.group_creation_changeset(%Actor{}, attrs))
|
||||
|> Multi.insert(:add_admin_member, fn %{insert_group: group} ->
|
||||
Member.changeset(%Member{}, %{
|
||||
parent_id: group.id,
|
||||
actor_id: attrs.creator_actor_id,
|
||||
role: :administrator
|
||||
})
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
{:ok, group}
|
||||
local = Map.get(attrs, :local, true)
|
||||
|
||||
if local do
|
||||
with {:ok, %{insert_group: %Actor{} = group, add_admin_member: %Member{} = _admin_member}} <-
|
||||
Multi.new()
|
||||
|> Multi.insert(:insert_group, Actor.group_creation_changeset(%Actor{}, attrs))
|
||||
|> Multi.insert(:add_admin_member, fn %{insert_group: group} ->
|
||||
Member.changeset(%Member{}, %{
|
||||
parent_id: group.id,
|
||||
actor_id: attrs.creator_actor_id,
|
||||
role: :administrator
|
||||
})
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
{:ok, group}
|
||||
end
|
||||
else
|
||||
%Actor{}
|
||||
|> Actor.group_creation_changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -532,12 +587,7 @@ defmodule Mobilizon.Actors do
|
||||
def is_member?(actor_id, parent_id) do
|
||||
match?(
|
||||
{:ok, %Member{}},
|
||||
get_member(actor_id, parent_id, [
|
||||
:member,
|
||||
:moderator,
|
||||
:administrator,
|
||||
:creator
|
||||
])
|
||||
get_member(actor_id, parent_id, @member_roles)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -552,6 +602,20 @@ defmodule Mobilizon.Actors do
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec get_single_group_member_actor(integer() | String.t()) :: Actor.t() | nil
|
||||
def get_single_group_member_actor(group_id) do
|
||||
Member
|
||||
|> where(
|
||||
[m],
|
||||
m.parent_id == ^group_id and m.role in [^:member, ^:moderator, ^:administrator, ^:creator]
|
||||
)
|
||||
|> join(:inner, [m], a in Actor, on: m.actor_id == a.id)
|
||||
|> where([_m, a], is_nil(a.domain))
|
||||
|> limit(1)
|
||||
|> select([_m, a], a)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a member.
|
||||
"""
|
||||
@@ -616,25 +680,26 @@ defmodule Mobilizon.Actors do
|
||||
@doc """
|
||||
Returns the list of members for a group.
|
||||
"""
|
||||
@spec list_members_for_group(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
def list_members_for_group(%Actor{id: group_id, type: :Group}, page \\ nil, limit \\ nil) do
|
||||
group_id
|
||||
|> members_for_group_query()
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@spec list_external_members_for_group(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
def list_external_members_for_group(
|
||||
@spec list_members_for_group(Actor.t(), list(atom()), integer | nil, integer | nil) :: Page.t()
|
||||
def list_members_for_group(
|
||||
%Actor{id: group_id, type: :Group},
|
||||
roles \\ [],
|
||||
page \\ nil,
|
||||
limit \\ nil
|
||||
) do
|
||||
group_id
|
||||
|> members_for_group_query()
|
||||
|> filter_external()
|
||||
|> filter_member_role(roles)
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@spec list_external_actors_members_for_group(Actor.t()) :: list(Actor.t())
|
||||
def list_external_actors_members_for_group(%Actor{id: group_id, type: :Group}) do
|
||||
group_id
|
||||
|> group_external_member_actor_query()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of administrator members for a group.
|
||||
"""
|
||||
@@ -1141,6 +1206,26 @@ defmodule Mobilizon.Actors do
|
||||
)
|
||||
end
|
||||
|
||||
@spec group_external_member_actor_query(integer()) :: Ecto.Query.t()
|
||||
defp group_external_member_actor_query(group_id) do
|
||||
Member
|
||||
|> where([m], m.parent_id == ^group_id)
|
||||
|> join(:inner, [m], a in Actor, on: m.actor_id == a.id)
|
||||
|> where([_m, a], not is_nil(a.domain))
|
||||
|> select([_m, a], a)
|
||||
end
|
||||
|
||||
@spec filter_member_role(Ecto.Query.t(), list(atom()) | atom()) :: Ecto.Query.t()
|
||||
def filter_member_role(query, []), do: query
|
||||
|
||||
def filter_member_role(query, roles) when is_list(roles) do
|
||||
where(query, [m], m.role in ^roles)
|
||||
end
|
||||
|
||||
def filter_member_role(query, role) when is_atom(role) do
|
||||
from(m in query, where: m.role == ^role)
|
||||
end
|
||||
|
||||
@spec administrator_members_for_group_query(integer | String.t()) :: Ecto.Query.t()
|
||||
defp administrator_members_for_group_query(group_id) do
|
||||
from(
|
||||
@@ -1296,13 +1381,22 @@ defmodule Mobilizon.Actors do
|
||||
defp preload_followers(actor, true), do: Repo.preload(actor, [:followers])
|
||||
defp preload_followers(actor, false), do: actor
|
||||
|
||||
defp delete_actor_organized_events(%Actor{organized_events: organized_events}) do
|
||||
defp delete_actor_organized_events(%Actor{organized_events: organized_events} = actor) do
|
||||
res =
|
||||
Enum.map(organized_events, fn event ->
|
||||
event =
|
||||
Repo.preload(event, [:organizer_actor, :participants, :picture, :mentions, :comments])
|
||||
Repo.preload(event, [
|
||||
:organizer_actor,
|
||||
:participants,
|
||||
:picture,
|
||||
:mentions,
|
||||
:comments,
|
||||
:attributed_to,
|
||||
:tags,
|
||||
:physical_address
|
||||
])
|
||||
|
||||
ActivityPub.delete(event, false)
|
||||
ActivityPub.delete(event, actor, false)
|
||||
end)
|
||||
|
||||
if Enum.all?(res, fn {status, _, _} -> status == :ok end) do
|
||||
@@ -1312,13 +1406,21 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_actor_empty_comments(%Actor{comments: comments}) do
|
||||
defp delete_actor_empty_comments(%Actor{comments: comments} = actor) do
|
||||
res =
|
||||
Enum.map(comments, fn comment ->
|
||||
comment =
|
||||
Repo.preload(comment, [:actor, :mentions, :event, :in_reply_to_comment, :origin_comment])
|
||||
Repo.preload(comment, [
|
||||
:actor,
|
||||
:mentions,
|
||||
:event,
|
||||
:in_reply_to_comment,
|
||||
:origin_comment,
|
||||
:attributed_to,
|
||||
:tags
|
||||
])
|
||||
|
||||
ActivityPub.delete(comment, false)
|
||||
ActivityPub.delete(comment, actor, false)
|
||||
end)
|
||||
|
||||
if Enum.all?(res, fn {status, _, _} -> status == :ok end) do
|
||||
|
||||
@@ -119,7 +119,7 @@ defmodule Mobilizon.Config do
|
||||
|
||||
@spec instance_user_agent :: String.t()
|
||||
def instance_user_agent,
|
||||
do: "#{instance_name()} #{instance_hostname()} - Mobilizon #{instance_version()}"
|
||||
do: "#{instance_hostname()} - Mobilizon #{instance_version()}"
|
||||
|
||||
@spec instance_federating :: String.t()
|
||||
def instance_federating, do: instance_config()[:federating]
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
defmodule Mobilizon.Conversations.Conversation.TitleSlug do
|
||||
@moduledoc """
|
||||
Module to generate the slug for conversations
|
||||
"""
|
||||
use EctoAutoslugField.Slug, from: :title, to: :slug
|
||||
end
|
||||
|
||||
defmodule Mobilizon.Conversations.Conversation do
|
||||
@moduledoc """
|
||||
Represents a conversation
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Conversations.Conversation.TitleSlug
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
creator: Actor.t(),
|
||||
actor: Actor.t(),
|
||||
title: String.t(),
|
||||
slug: String.t(),
|
||||
last_comment: Comment.t(),
|
||||
comments: list(Comment.t())
|
||||
}
|
||||
|
||||
@required_attrs [:actor_id, :creator_id, :title, :last_comment_id]
|
||||
@optional_attrs []
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
schema "conversations" do
|
||||
field(:title, :string)
|
||||
field(:slug, TitleSlug.Type)
|
||||
belongs_to(:creator, Actor)
|
||||
belongs_to(:actor, Actor)
|
||||
belongs_to(:last_comment, Comment)
|
||||
has_many(:comments, Comment, foreign_key: :conversation_id)
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||
def changeset(%__MODULE__{} = conversation, attrs) do
|
||||
conversation
|
||||
|> cast(attrs, @attrs)
|
||||
|> validate_required(@required_attrs)
|
||||
|> TitleSlug.maybe_generate_slug()
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Mobilizon.Conversations.Comment do
|
||||
defmodule Mobilizon.Discussions.Comment do
|
||||
@moduledoc """
|
||||
Represents an actor comment (for instance on an event or on a group).
|
||||
"""
|
||||
@@ -8,7 +8,7 @@ defmodule Mobilizon.Conversations.Comment do
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.{Comment, CommentVisibility, Conversation}
|
||||
alias Mobilizon.Discussions.{Comment, CommentVisibility, Discussion}
|
||||
alias Mobilizon.Events.{Event, Tag}
|
||||
alias Mobilizon.Mention
|
||||
|
||||
@@ -42,7 +42,7 @@ defmodule Mobilizon.Conversations.Comment do
|
||||
:attributed_to_id,
|
||||
:deleted_at,
|
||||
:local,
|
||||
:conversation_id
|
||||
:discussion_id
|
||||
]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@@ -60,7 +60,7 @@ defmodule Mobilizon.Conversations.Comment do
|
||||
belongs_to(:event, Event, foreign_key: :event_id)
|
||||
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
|
||||
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
||||
belongs_to(:conversation, Conversation)
|
||||
belongs_to(:discussion, Discussion, type: :binary_id)
|
||||
has_many(:replies, Comment, foreign_key: :in_reply_to_comment_id)
|
||||
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
||||
has_many(:mentions, Mention)
|
||||
@@ -69,7 +69,7 @@ defmodule Mobilizon.Conversations.Comment do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the id of the first comment in the conversation.
|
||||
Returns the id of the first comment in the discussion.
|
||||
"""
|
||||
@spec get_thread_id(t) :: integer
|
||||
def get_thread_id(%__MODULE__{id: id, origin_comment_id: origin_comment_id}) do
|
||||
@@ -98,6 +98,7 @@ defmodule Mobilizon.Conversations.Comment do
|
||||
|> change()
|
||||
|> put_change(:text, nil)
|
||||
|> put_change(:actor_id, nil)
|
||||
|> put_change(:discussion_id, nil)
|
||||
|> put_change(:deleted_at, DateTime.utc_now() |> DateTime.truncate(:second))
|
||||
end
|
||||
|
||||
102
lib/mobilizon/discussions/discussion.ex
Normal file
102
lib/mobilizon/discussions/discussion.ex
Normal file
@@ -0,0 +1,102 @@
|
||||
defmodule Mobilizon.Discussions.Discussion.TitleSlug do
|
||||
@moduledoc """
|
||||
Module to generate the slug for discussions
|
||||
"""
|
||||
use EctoAutoslugField.Slug, from: [:title, :id], to: :slug
|
||||
|
||||
def build_slug([title, id], %Ecto.Changeset{valid?: true}) do
|
||||
[title, ShortUUID.encode!(id)]
|
||||
|> Enum.join("-")
|
||||
|> Slugger.slugify()
|
||||
end
|
||||
|
||||
def build_slug(_sources, %Ecto.Changeset{valid?: false}), do: ""
|
||||
end
|
||||
|
||||
defmodule Mobilizon.Discussions.Discussion do
|
||||
@moduledoc """
|
||||
Represents a discussion
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Discussions.Discussion.TitleSlug
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
creator: Actor.t(),
|
||||
actor: Actor.t(),
|
||||
title: String.t(),
|
||||
url: String.t(),
|
||||
slug: String.t(),
|
||||
last_comment: Comment.t(),
|
||||
comments: list(Comment.t())
|
||||
}
|
||||
|
||||
@required_attrs [:actor_id, :creator_id, :title, :last_comment_id, :url, :id]
|
||||
@optional_attrs []
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
||||
|
||||
schema "discussions" do
|
||||
field(:title, :string)
|
||||
field(:slug, TitleSlug.Type)
|
||||
field(:url, :string)
|
||||
belongs_to(:creator, Actor)
|
||||
belongs_to(:actor, Actor)
|
||||
belongs_to(:last_comment, Comment)
|
||||
has_many(:comments, Comment, foreign_key: :discussion_id)
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||
def changeset(%__MODULE__{} = discussion, attrs) do
|
||||
discussion
|
||||
|> cast(attrs, @attrs)
|
||||
|> maybe_generate_id()
|
||||
|> validate_required([:title, :id])
|
||||
|> TitleSlug.maybe_generate_slug()
|
||||
|> TitleSlug.unique_constraint()
|
||||
|> maybe_generate_url()
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
|
||||
defp maybe_generate_id(%Ecto.Changeset{} = changeset) do
|
||||
case fetch_field(changeset, :id) do
|
||||
res when res in [:error, {:data, nil}] ->
|
||||
put_change(changeset, :id, Ecto.UUID.generate())
|
||||
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp maybe_generate_url(%Ecto.Changeset{} = changeset) do
|
||||
with res when res in [:error, {:data, nil}] <- fetch_field(changeset, :url),
|
||||
{changes, slug} when changes in [:changes, :data] <-
|
||||
fetch_field(changeset, :slug),
|
||||
{_changes, actor_id} <-
|
||||
fetch_field(changeset, :actor_id),
|
||||
%Actor{preferred_username: preferred_username} <-
|
||||
Actors.get_actor(actor_id),
|
||||
url <- generate_url(preferred_username, slug) do
|
||||
put_change(changeset, :url, url)
|
||||
else
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec generate_url(String.t(), String.t()) :: String.t()
|
||||
defp generate_url(preferred_username, slug),
|
||||
do: Routes.page_url(Endpoint, :discussion, preferred_username, slug)
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Mobilizon.Conversations do
|
||||
defmodule Mobilizon.Discussions do
|
||||
@moduledoc """
|
||||
The conversations context
|
||||
The discussions context
|
||||
"""
|
||||
|
||||
import EctoEnum
|
||||
@@ -9,7 +9,7 @@ defmodule Mobilizon.Conversations do
|
||||
alias Ecto.Changeset
|
||||
alias Ecto.Multi
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.{Comment, Conversation}
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Storage.{Page, Repo}
|
||||
|
||||
defenum(
|
||||
@@ -42,10 +42,11 @@ defmodule Mobilizon.Conversations do
|
||||
:origin_comment,
|
||||
:replies,
|
||||
:tags,
|
||||
:mentions
|
||||
:mentions,
|
||||
:discussion
|
||||
]
|
||||
|
||||
@conversation_preloads [
|
||||
@discussion_preloads [
|
||||
:last_comment,
|
||||
:comments,
|
||||
:creator,
|
||||
@@ -231,21 +232,11 @@ defmodule Mobilizon.Conversations do
|
||||
@doc """
|
||||
Returns the list of public comments for the actor.
|
||||
"""
|
||||
@spec list_public_comments_for_actor(Actor.t(), integer | nil, integer | nil) ::
|
||||
{:ok, [Comment.t()], integer}
|
||||
@spec list_public_comments_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
def list_public_comments_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do
|
||||
comments =
|
||||
actor_id
|
||||
|> public_comments_for_actor_query()
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
|
||||
count_comments =
|
||||
actor_id
|
||||
|> count_comments_query()
|
||||
|> Repo.one()
|
||||
|
||||
{:ok, comments, count_comments}
|
||||
actor_id
|
||||
|> public_comments_for_actor_query()
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -263,10 +254,10 @@ defmodule Mobilizon.Conversations do
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_comments_for_conversation(integer, integer | nil, integer | nil) :: Page.t()
|
||||
def get_comments_for_conversation(conversation_id, page \\ nil, limit \\ nil) do
|
||||
@spec get_comments_for_discussion(integer, integer | nil, integer | nil) :: Page.t()
|
||||
def get_comments_for_discussion(discussion_id, page \\ nil, limit \\ nil) do
|
||||
Comment
|
||||
|> where([c], c.conversation_id == ^conversation_id)
|
||||
|> where([c], c.discussion_id == ^discussion_id)
|
||||
|> order_by(asc: :inserted_at)
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
@@ -277,80 +268,114 @@ defmodule Mobilizon.Conversations do
|
||||
@spec count_local_comments :: integer
|
||||
def count_local_comments, do: Repo.one(count_local_comments_query())
|
||||
|
||||
def get_conversation(conversation_id) do
|
||||
Conversation
|
||||
|> Repo.get(conversation_id)
|
||||
|> Repo.preload(@conversation_preloads)
|
||||
def get_discussion(discussion_id) do
|
||||
Discussion
|
||||
|> Repo.get(discussion_id)
|
||||
|> Repo.preload(@discussion_preloads)
|
||||
end
|
||||
|
||||
@spec find_conversations_for_actor(integer, integer | nil, integer | nil) :: Page.t()
|
||||
def find_conversations_for_actor(actor_id, page \\ nil, limit \\ nil) do
|
||||
Conversation
|
||||
@spec get_discussion_by_url(String.t() | nil) :: Discussion.t() | nil
|
||||
def get_discussion_by_url(nil), do: nil
|
||||
|
||||
def get_discussion_by_url(discussion_url) do
|
||||
Discussion
|
||||
|> Repo.get_by(url: discussion_url)
|
||||
|> Repo.preload(@discussion_preloads)
|
||||
end
|
||||
|
||||
def get_discussion_by_slug(discussion_slug) do
|
||||
Discussion
|
||||
|> Repo.get_by(slug: discussion_slug)
|
||||
|> Repo.preload(@discussion_preloads)
|
||||
end
|
||||
|
||||
@spec find_discussions_for_actor(integer, integer | nil, integer | nil) :: Page.t()
|
||||
def find_discussions_for_actor(actor_id, page \\ nil, limit \\ nil) do
|
||||
Discussion
|
||||
|> where([c], c.actor_id == ^actor_id)
|
||||
|> preload(^@conversation_preloads)
|
||||
|> preload(^@discussion_preloads)
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a conversation.
|
||||
Creates a discussion.
|
||||
"""
|
||||
@spec create_conversation(map) :: {:ok, Comment.t()} | {:error, Changeset.t()}
|
||||
def create_conversation(attrs \\ %{}) do
|
||||
with {:ok, %{comment: %Comment{} = _comment, conversation: %Conversation{} = conversation}} <-
|
||||
@spec create_discussion(map) :: {:ok, Comment.t()} | {:error, Changeset.t()}
|
||||
def create_discussion(attrs \\ %{}) do
|
||||
with {:ok, %{comment: %Comment{} = _comment, discussion: %Discussion{} = discussion}} <-
|
||||
Multi.new()
|
||||
|> Multi.insert(
|
||||
:comment,
|
||||
Comment.changeset(%Comment{}, Map.merge(attrs, %{actor_id: attrs.creator_id}))
|
||||
Comment.changeset(
|
||||
%Comment{},
|
||||
Map.merge(attrs, %{actor_id: attrs.creator_id, attributed_to_id: attrs.actor_id})
|
||||
)
|
||||
)
|
||||
|> Multi.insert(:conversation, fn %{comment: %Comment{id: comment_id}} ->
|
||||
Conversation.changeset(
|
||||
%Conversation{},
|
||||
|> Multi.insert(:discussion, fn %{comment: %Comment{id: comment_id}} ->
|
||||
Discussion.changeset(
|
||||
%Discussion{},
|
||||
Map.merge(attrs, %{last_comment_id: comment_id})
|
||||
)
|
||||
end)
|
||||
|> Multi.update(:comment_conversation, fn %{
|
||||
comment: %Comment{} = comment,
|
||||
conversation: %Conversation{
|
||||
id: conversation_id
|
||||
}
|
||||
} ->
|
||||
Changeset.change(comment, %{conversation_id: conversation_id})
|
||||
|> Multi.update(:comment_discussion, fn %{
|
||||
comment: %Comment{} = comment,
|
||||
discussion: %Discussion{
|
||||
id: discussion_id,
|
||||
url: discussion_url
|
||||
}
|
||||
} ->
|
||||
Changeset.change(comment, %{discussion_id: discussion_id, url: discussion_url})
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
{:ok, conversation}
|
||||
{:ok, discussion}
|
||||
end
|
||||
end
|
||||
|
||||
def reply_to_conversation(%Conversation{id: conversation_id} = conversation, attrs \\ %{}) do
|
||||
with {:ok, %{comment: %Comment{} = comment, conversation: %Conversation{} = conversation}} <-
|
||||
def reply_to_discussion(%Discussion{id: discussion_id} = discussion, attrs \\ %{}) do
|
||||
with {:ok, %{comment: %Comment{} = comment, discussion: %Discussion{} = discussion}} <-
|
||||
Multi.new()
|
||||
|> Multi.insert(
|
||||
:comment,
|
||||
Comment.changeset(%Comment{}, Map.merge(attrs, %{conversation_id: conversation_id}))
|
||||
Comment.changeset(
|
||||
%Comment{},
|
||||
Map.merge(attrs, %{
|
||||
discussion_id: discussion_id,
|
||||
actor_id: Map.get(attrs, :creator_id, attrs.actor_id)
|
||||
})
|
||||
)
|
||||
)
|
||||
|> Multi.update(:conversation, fn %{comment: %Comment{id: comment_id}} ->
|
||||
Conversation.changeset(
|
||||
conversation,
|
||||
|> Multi.update(:discussion, fn %{comment: %Comment{id: comment_id}} ->
|
||||
Discussion.changeset(
|
||||
discussion,
|
||||
%{last_comment_id: comment_id}
|
||||
)
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
# For some reason conversation is not updated
|
||||
{:ok, Map.put(conversation, :last_comment, comment)}
|
||||
# Discussion is not updated
|
||||
{:ok, Map.put(discussion, :last_comment, comment)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a conversation. Only their title for now.
|
||||
Update a discussion. Only their title for now.
|
||||
"""
|
||||
@spec update_conversation(Conversation.t(), map()) ::
|
||||
{:ok, Conversation.t()} | {:error, Changeset.t()}
|
||||
def update_conversation(%Conversation{} = conversation, attrs \\ %{}) do
|
||||
conversation
|
||||
|> Conversation.changeset(attrs)
|
||||
@spec update_discussion(Discussion.t(), map()) ::
|
||||
{:ok, Discussion.t()} | {:error, Changeset.t()}
|
||||
def update_discussion(%Discussion{} = discussion, attrs \\ %{}) do
|
||||
discussion
|
||||
|> Discussion.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete a discussion.
|
||||
"""
|
||||
@spec delete_discussion(Discussion.t()) :: {:ok, Discussion.t()} | {:error, Changeset.t()}
|
||||
def delete_discussion(%Discussion{} = discussion) do
|
||||
discussion
|
||||
|> Repo.delete()
|
||||
end
|
||||
|
||||
defp public_comments_for_actor_query(actor_id) do
|
||||
Comment
|
||||
|> where([c], c.actor_id == ^actor_id and c.visibility in ^@public_visibility)
|
||||
@@ -365,11 +390,6 @@ defmodule Mobilizon.Conversations do
|
||||
|> preload_for_comment()
|
||||
end
|
||||
|
||||
@spec count_comments_query(integer) :: Ecto.Query.t()
|
||||
defp count_comments_query(actor_id) do
|
||||
from(c in Comment, select: count(c.id), where: c.actor_id == ^actor_id)
|
||||
end
|
||||
|
||||
@spec count_local_comments_query :: Ecto.Query.t()
|
||||
defp count_local_comments_query do
|
||||
from(
|
||||
@@ -382,6 +402,6 @@ defmodule Mobilizon.Conversations do
|
||||
@spec preload_for_comment(Ecto.Query.t()) :: Ecto.Query.t()
|
||||
defp preload_for_comment(query), do: preload(query, ^@comment_preloads)
|
||||
|
||||
# @spec preload_for_conversation(Ecto.Query.t()) :: Ecto.Query.t()
|
||||
# defp preload_for_conversation(query), do: preload(query, ^@conversation_preloads)
|
||||
# @spec preload_for_discussion(Ecto.Query.t()) :: Ecto.Query.t()
|
||||
# defp preload_for_discussion(query), do: preload(query, ^@discussion_preloads)
|
||||
end
|
||||
@@ -13,7 +13,7 @@ defmodule Mobilizon.Events.Event do
|
||||
alias Mobilizon.{Addresses, Events, Media, Mention}
|
||||
alias Mobilizon.Addresses.Address
|
||||
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Discussions.Comment
|
||||
|
||||
alias Mobilizon.Events.{
|
||||
EventOptions,
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Conversations.CommentModeration
|
||||
alias Mobilizon.Discussions.CommentModeration
|
||||
|
||||
alias Mobilizon.Events.{
|
||||
EventOffer,
|
||||
|
||||
@@ -380,24 +380,19 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Lists public events for the actor, with all associations loaded.
|
||||
"""
|
||||
@spec list_public_events_for_actor(Actor.t(), integer | nil, integer | nil) ::
|
||||
{:ok, [Event.t()], integer}
|
||||
def list_public_events_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do
|
||||
events =
|
||||
actor_id
|
||||
|> event_for_actor_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> preload_for_event()
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
@spec list_public_events_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
def list_public_events_for_actor(actor, page \\ nil, limit \\ nil)
|
||||
|
||||
events_count =
|
||||
actor_id
|
||||
|> count_events_for_actor_query()
|
||||
|> Repo.one()
|
||||
def list_public_events_for_actor(%Actor{type: :Group} = group, page, limit),
|
||||
do: list_organized_events_for_group(group, page, limit)
|
||||
|
||||
{:ok, events, events_count}
|
||||
def list_public_events_for_actor(%Actor{id: actor_id}, page, limit) do
|
||||
actor_id
|
||||
|> event_for_actor_query()
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> preload_for_event()
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@spec list_organized_events_for_actor(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
@@ -1321,15 +1316,6 @@ defmodule Mobilizon.Events do
|
||||
)
|
||||
end
|
||||
|
||||
@spec count_events_for_actor_query(integer | String.t()) :: Ecto.Query.t()
|
||||
defp count_events_for_actor_query(actor_id) do
|
||||
from(
|
||||
e in Event,
|
||||
select: count(e.id),
|
||||
where: e.organizer_actor_id == ^actor_id
|
||||
)
|
||||
end
|
||||
|
||||
@spec count_local_events_query :: Ecto.Query.t()
|
||||
defp count_local_events_query do
|
||||
from(e in Event, select: count(e.id), where: e.local == ^true)
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule Mobilizon.Events.Participant do
|
||||
url: String.t(),
|
||||
event: Event.t(),
|
||||
actor: Actor.t(),
|
||||
metadata: Map.t()
|
||||
metadata: map()
|
||||
}
|
||||
|
||||
@required_attrs [:url, :role, :event_id, :actor_id]
|
||||
|
||||
@@ -6,7 +6,7 @@ defmodule Mobilizon.Mention do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
|
||||
137
lib/mobilizon/posts/post.ex
Normal file
137
lib/mobilizon/posts/post.ex
Normal file
@@ -0,0 +1,137 @@
|
||||
defmodule Mobilizon.Posts.Post.TitleSlug do
|
||||
@moduledoc """
|
||||
Module to generate the slug for posts
|
||||
"""
|
||||
use EctoAutoslugField.Slug, from: [:title, :id], to: :slug
|
||||
|
||||
def build_slug([title, id], %Ecto.Changeset{valid?: true}) do
|
||||
[title, ShortUUID.encode!(id)]
|
||||
|> Enum.join("-")
|
||||
|> Slugger.slugify()
|
||||
end
|
||||
|
||||
def build_slug(_sources, %Ecto.Changeset{valid?: false}), do: ""
|
||||
end
|
||||
|
||||
defmodule Mobilizon.Posts.Post do
|
||||
@moduledoc """
|
||||
Module that represent Posts published by groups
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Ecto.Changeset
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.Tag
|
||||
alias Mobilizon.Media.Picture
|
||||
alias Mobilizon.Posts.Post.TitleSlug
|
||||
alias Mobilizon.Posts.PostVisibility
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
url: String.t(),
|
||||
local: boolean,
|
||||
slug: String.t(),
|
||||
body: String.t(),
|
||||
title: String.t(),
|
||||
draft: boolean,
|
||||
visibility: PostVisibility.t(),
|
||||
publish_at: DateTime.t(),
|
||||
author: Actor.t(),
|
||||
attributed_to: Actor.t(),
|
||||
picture: Picture.t(),
|
||||
tags: [Tag.t()]
|
||||
}
|
||||
|
||||
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
||||
|
||||
schema "posts" do
|
||||
field(:body, :string)
|
||||
field(:draft, :boolean, default: false)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:slug, TitleSlug.Type)
|
||||
field(:title, :string)
|
||||
field(:url, :string)
|
||||
field(:publish_at, :utc_datetime)
|
||||
field(:visibility, PostVisibility, default_value: :public)
|
||||
belongs_to(:author, Actor)
|
||||
belongs_to(:attributed_to, Actor)
|
||||
belongs_to(:picture, Picture, on_replace: :update)
|
||||
many_to_many(:tags, Tag, join_through: "posts_tags", on_replace: :delete)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_attrs [
|
||||
:id,
|
||||
:title,
|
||||
:body,
|
||||
:draft,
|
||||
:slug,
|
||||
:url,
|
||||
:author_id,
|
||||
:attributed_to_id
|
||||
]
|
||||
@optional_attrs [:picture_id, :local, :publish_at, :visibility]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@doc false
|
||||
def changeset(%__MODULE__{} = post, attrs) do
|
||||
post
|
||||
|> cast(attrs, @attrs)
|
||||
|> maybe_generate_id()
|
||||
|> put_tags(attrs)
|
||||
|> maybe_put_publish_date()
|
||||
# Validate ID and title here because they're needed for slug
|
||||
|> validate_required([:id, :title])
|
||||
|> TitleSlug.maybe_generate_slug()
|
||||
|> TitleSlug.unique_constraint()
|
||||
|> maybe_generate_url()
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
|
||||
defp maybe_generate_id(%Ecto.Changeset{} = changeset) do
|
||||
case fetch_field(changeset, :id) do
|
||||
res when res in [:error, {:data, nil}] ->
|
||||
put_change(changeset, :id, Ecto.UUID.generate())
|
||||
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp maybe_generate_url(%Ecto.Changeset{} = changeset) do
|
||||
with res when res in [:error, {:data, nil}] <- fetch_field(changeset, :url),
|
||||
{changes, id_and_slug} when changes in [:changes, :data] <-
|
||||
fetch_field(changeset, :slug),
|
||||
url <- generate_url(id_and_slug) do
|
||||
put_change(changeset, :url, url)
|
||||
else
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec generate_url(String.t()) :: String.t()
|
||||
defp generate_url(id_and_slug), do: Routes.page_url(Endpoint, :post, id_and_slug)
|
||||
|
||||
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||
defp put_tags(changeset, %{"tags" => tags}),
|
||||
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||
|
||||
defp put_tags(changeset, %{tags: tags}),
|
||||
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||
|
||||
defp put_tags(changeset, _), do: changeset
|
||||
|
||||
defp process_tag(tag), do: Tag.changeset(%Tag{}, tag)
|
||||
|
||||
defp maybe_put_publish_date(%Changeset{} = changeset) do
|
||||
publish_at =
|
||||
if get_field(changeset, :draft, true) == false,
|
||||
do: DateTime.utc_now() |> DateTime.truncate(:second),
|
||||
else: nil
|
||||
|
||||
put_change(changeset, :publish_at, publish_at)
|
||||
end
|
||||
end
|
||||
135
lib/mobilizon/posts/posts.ex
Normal file
135
lib/mobilizon/posts/posts.ex
Normal file
@@ -0,0 +1,135 @@
|
||||
defmodule Mobilizon.Posts do
|
||||
@moduledoc """
|
||||
The Posts context.
|
||||
"""
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.Tag
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Storage.{Page, Repo}
|
||||
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
|
||||
@post_preloads [:author, :attributed_to, :picture]
|
||||
|
||||
import EctoEnum
|
||||
|
||||
defenum(PostVisibility, :post_visibility, [
|
||||
:public,
|
||||
:unlisted,
|
||||
:restricted,
|
||||
:private
|
||||
])
|
||||
|
||||
@doc """
|
||||
Returns the list of recent posts for a group
|
||||
"""
|
||||
@spec get_posts_for_group(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
def get_posts_for_group(%Actor{id: group_id}, page \\ nil, limit \\ nil) do
|
||||
group_id
|
||||
|> do_get_posts_for_group()
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@spec get_public_posts_for_group(Actor.t(), integer | nil, integer | nil) :: Page.t()
|
||||
def get_public_posts_for_group(%Actor{id: group_id}, page \\ nil, limit \\ nil) do
|
||||
group_id
|
||||
|> do_get_posts_for_group()
|
||||
|> where([p], p.visibility == ^:public and not p.draft)
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
def do_get_posts_for_group(group_id) do
|
||||
Post
|
||||
|> where(attributed_to_id: ^group_id)
|
||||
|> order_by(desc: :inserted_at)
|
||||
|> preload([p], [:author, :attributed_to, :picture])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a post by it's ID
|
||||
"""
|
||||
@spec get_post(integer | String.t()) :: Post.t() | nil
|
||||
def get_post(nil), do: nil
|
||||
def get_post(id), do: Repo.get(Post, id)
|
||||
|
||||
@spec get_post_with_preloads(integer | String.t()) :: Post.t() | nil
|
||||
def get_post_with_preloads(id) do
|
||||
Post
|
||||
|> Repo.get(id)
|
||||
|> Repo.preload(@post_preloads)
|
||||
end
|
||||
|
||||
@spec get_post_by_slug(String.t()) :: Post.t() | nil
|
||||
def get_post_by_slug(nil), do: nil
|
||||
def get_post_by_slug(slug), do: Repo.get_by(Post, slug: slug)
|
||||
|
||||
@spec get_post_by_slug_with_preloads(String.t()) :: Post.t() | nil
|
||||
def get_post_by_slug_with_preloads(slug) do
|
||||
Post
|
||||
|> Repo.get_by(slug: slug)
|
||||
|> Repo.preload(@post_preloads)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a post by it's URL
|
||||
"""
|
||||
@spec get_post_by_url(String.t()) :: Post.t() | nil
|
||||
def get_post_by_url(url), do: Repo.get_by(Post, url: url)
|
||||
|
||||
@spec get_post_by_url_with_preloads(String.t()) :: Post.t() | nil
|
||||
def get_post_by_url_with_preloads(url) do
|
||||
Post
|
||||
|> Repo.get_by(url: url)
|
||||
|> Repo.preload(@post_preloads)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a post.
|
||||
"""
|
||||
@spec create_post(map) :: {:ok, Post.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_post(attrs \\ %{}) do
|
||||
%Post{}
|
||||
|> Post.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a post.
|
||||
"""
|
||||
@spec update_post(Post.t(), map) :: {:ok, Post.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_post(%Post{} = post, attrs) do
|
||||
post
|
||||
|> Repo.preload(:tags)
|
||||
|> Post.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a post
|
||||
"""
|
||||
@spec delete_post(Post.t()) :: {:ok, Post.t()} | {:error, Ecto.Changeset.t()}
|
||||
def delete_post(%Post{} = post), do: Repo.delete(post)
|
||||
|
||||
@doc """
|
||||
Returns the list of tags for the post.
|
||||
"""
|
||||
@spec list_tags_for_post(integer | String.t()) :: [Tag.t()]
|
||||
def list_tags_for_post(post_id) do
|
||||
{:ok, uuid} = Ecto.UUID.dump(post_id)
|
||||
|
||||
uuid
|
||||
|> tags_for_post_query()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec tags_for_post_query(integer) :: Ecto.Query.t()
|
||||
defp tags_for_post_query(post_id) do
|
||||
from(
|
||||
t in Tag,
|
||||
join: p in "posts_tags",
|
||||
on: t.id == p.tag_id,
|
||||
where: p.post_id == ^post_id
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ defmodule Mobilizon.Reports.Report do
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Reports.{Note, ReportStatus}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ defmodule Mobilizon.Resources do
|
||||
Resource
|
||||
|> where(actor_id: ^group_id)
|
||||
|> order_by(desc: :updated_at)
|
||||
|> preload([r], [:actor, :creator])
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@@ -55,6 +56,7 @@ defmodule Mobilizon.Resources do
|
||||
Resource
|
||||
|> where([r], r.parent_id == ^resource_id)
|
||||
|> order_by(asc: :type)
|
||||
|> preload([r], [:actor, :creator])
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ defmodule Mobilizon.Todos do
|
||||
TodoList
|
||||
|> where(actor_id: ^group_id)
|
||||
|> order_by(desc: :updated_at)
|
||||
|> preload([:actor])
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
|
||||
@@ -7,6 +7,15 @@ defmodule Mobilizon.Users.Setting do
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Users.{NotificationPendingNotificationDelay, User}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
timezone: String.t(),
|
||||
notification_on_day: boolean,
|
||||
notification_each_week: boolean,
|
||||
notification_before_event: boolean,
|
||||
notification_pending_participation: NotificationPendingNotificationDelay.t(),
|
||||
user: User.t()
|
||||
}
|
||||
|
||||
@required_attrs [:user_id]
|
||||
|
||||
@optional_attrs [
|
||||
|
||||
Reference in New Issue
Block a user