mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
Frontend changes
This commit is contained in:
parent
6ac5230b2e
commit
f48a8fc165
45 changed files with 773 additions and 542 deletions
|
@ -8,10 +8,10 @@ defmodule Philomena.Channels do
|
|||
|
||||
alias Philomena.Channels.AutomaticUpdater
|
||||
alias Philomena.Channels.Channel
|
||||
alias Philomena.Notifications
|
||||
alias Philomena.Tags
|
||||
|
||||
use Philomena.Subscriptions,
|
||||
actor_types: ~w(Channel LivestreamChannel),
|
||||
id_name: :channel_id
|
||||
|
||||
@doc """
|
||||
|
@ -139,4 +139,18 @@ defmodule Philomena.Channels do
|
|||
def change_channel(%Channel{} = channel) do
|
||||
Channel.changeset(channel, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes all channel notifications for a given channel and user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> clear_channel_notification(channel, user)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def clear_channel_notification(%Channel{} = channel, user) do
|
||||
Notifications.clear_channel_live_notification(channel, user)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -79,22 +79,7 @@ defmodule Philomena.Comments do
|
|||
|> Repo.preload(:image)
|
||||
|> Map.fetch!(:image)
|
||||
|
||||
subscriptions =
|
||||
image
|
||||
|> Repo.preload(:subscriptions)
|
||||
|> Map.fetch!(:subscriptions)
|
||||
|
||||
Notifications.notify(
|
||||
comment,
|
||||
subscriptions,
|
||||
%{
|
||||
actor_id: image.id,
|
||||
actor_type: "Image",
|
||||
actor_child_id: comment.id,
|
||||
actor_child_type: "Comment",
|
||||
action: "commented on"
|
||||
}
|
||||
)
|
||||
Notifications.create_image_comment_notification(image, comment)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -9,7 +9,6 @@ defmodule Philomena.Forums do
|
|||
alias Philomena.Forums.Forum
|
||||
|
||||
use Philomena.Subscriptions,
|
||||
actor_types: ~w(Forum),
|
||||
id_name: :forum_id
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -19,7 +19,6 @@ defmodule Philomena.Galleries do
|
|||
alias Philomena.Images
|
||||
|
||||
use Philomena.Subscriptions,
|
||||
actor_types: ~w(Gallery),
|
||||
id_name: :gallery_id
|
||||
|
||||
@doc """
|
||||
|
@ -269,25 +268,10 @@ defmodule Philomena.Galleries do
|
|||
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Galleries", [gallery.id, image.id]])
|
||||
end
|
||||
|
||||
def perform_notify([gallery_id, image_id]) do
|
||||
def perform_notify([gallery_id, _image_id]) do
|
||||
gallery = get_gallery!(gallery_id)
|
||||
|
||||
subscriptions =
|
||||
gallery
|
||||
|> Repo.preload(:subscriptions)
|
||||
|> Map.fetch!(:subscriptions)
|
||||
|
||||
Notifications.notify(
|
||||
gallery,
|
||||
subscriptions,
|
||||
%{
|
||||
actor_id: gallery.id,
|
||||
actor_type: "Gallery",
|
||||
actor_child_id: image_id,
|
||||
actor_child_type: "Image",
|
||||
action: "added images to"
|
||||
}
|
||||
)
|
||||
Notifications.create_gallery_image_notification(gallery)
|
||||
end
|
||||
|
||||
def reorder_gallery(gallery, image_ids) do
|
||||
|
@ -360,4 +344,18 @@ defmodule Philomena.Galleries do
|
|||
|
||||
defp position_order(%{order_position_asc: true}), do: [asc: :position]
|
||||
defp position_order(_gallery), do: [desc: :position]
|
||||
|
||||
@doc """
|
||||
Removes all gallery notifications for a given gallery and user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> clear_gallery_notification(gallery, user)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def clear_gallery_notification(%Gallery{} = gallery, user) do
|
||||
Notifications.clear_gallery_image_notification(gallery, user)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,8 @@ defmodule Philomena.Images do
|
|||
alias Philomena.IndexWorker
|
||||
alias Philomena.ImageFeatures.ImageFeature
|
||||
alias Philomena.SourceChanges.SourceChange
|
||||
alias Philomena.Notifications.Notification
|
||||
alias Philomena.Notifications.ImageCommentNotification
|
||||
alias Philomena.Notifications.ImageMergeNotification
|
||||
alias Philomena.NotificationWorker
|
||||
alias Philomena.TagChanges.Limits
|
||||
alias Philomena.TagChanges.TagChange
|
||||
|
@ -38,7 +39,6 @@ defmodule Philomena.Images do
|
|||
alias Philomena.Users.User
|
||||
|
||||
use Philomena.Subscriptions,
|
||||
actor_types: ~w(Image),
|
||||
id_name: :image_id
|
||||
|
||||
@doc """
|
||||
|
@ -905,12 +905,17 @@ defmodule Philomena.Images do
|
|||
|
||||
Repo.insert_all(Subscription, subscriptions, on_conflict: :nothing)
|
||||
|
||||
{count, nil} =
|
||||
Notification
|
||||
|> where(actor_type: "Image", actor_id: ^source.id)
|
||||
|> Repo.delete_all()
|
||||
{comment_notification_count, nil} =
|
||||
ImageCommentNotification
|
||||
|> where(image_id: ^source.id)
|
||||
|> Repo.update_all(set: [image_id: target.id])
|
||||
|
||||
{:ok, count}
|
||||
{merge_notification_count, nil} =
|
||||
ImageMergeNotification
|
||||
|> where(image_id: ^source.id)
|
||||
|> Repo.update_all(set: [image_id: target.id])
|
||||
|
||||
{:ok, {comment_notification_count, merge_notification_count}}
|
||||
end
|
||||
|
||||
def migrate_sources(source, target) do
|
||||
|
@ -930,23 +935,24 @@ defmodule Philomena.Images do
|
|||
end
|
||||
|
||||
def perform_notify([source_id, target_id]) do
|
||||
source = get_image!(source_id)
|
||||
target = get_image!(target_id)
|
||||
|
||||
subscriptions =
|
||||
target
|
||||
|> Repo.preload(:subscriptions)
|
||||
|> Map.fetch!(:subscriptions)
|
||||
Notifications.create_image_merge_notification(target, source)
|
||||
end
|
||||
|
||||
Notifications.notify(
|
||||
nil,
|
||||
subscriptions,
|
||||
%{
|
||||
actor_id: target.id,
|
||||
actor_type: "Image",
|
||||
actor_child_id: nil,
|
||||
actor_child_type: nil,
|
||||
action: "merged ##{source_id} into"
|
||||
}
|
||||
)
|
||||
@doc """
|
||||
Removes all image notifications for a given image and user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> clear_image_notification(image, user)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def clear_image_notification(%Image{} = image, user) do
|
||||
Notifications.clear_image_comment_notification(image, user)
|
||||
Notifications.clear_image_merge_notification(image, user)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,277 +4,262 @@ defmodule Philomena.Notifications do
|
|||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Channels.Subscription, as: ChannelSubscription
|
||||
alias Philomena.Forums.Subscription, as: ForumSubscription
|
||||
alias Philomena.Galleries.Subscription, as: GallerySubscription
|
||||
alias Philomena.Images.Subscription, as: ImageSubscription
|
||||
alias Philomena.Topics.Subscription, as: TopicSubscription
|
||||
|
||||
alias Philomena.Notifications.ChannelLiveNotification
|
||||
alias Philomena.Notifications.ForumPostNotification
|
||||
alias Philomena.Notifications.ForumTopicNotification
|
||||
alias Philomena.Notifications.GalleryImageNotification
|
||||
alias Philomena.Notifications.ImageCommentNotification
|
||||
alias Philomena.Notifications.ImageMergeNotification
|
||||
|
||||
alias Philomena.Notifications.Category
|
||||
alias Philomena.Notifications.Notification
|
||||
alias Philomena.Notifications.UnreadNotification
|
||||
alias Philomena.Polymorphic
|
||||
alias Philomena.Notifications.Creator
|
||||
|
||||
@doc """
|
||||
Returns the list of unread notifications of the given type.
|
||||
|
||||
The set of valid types is `t:Philomena.Notifications.Category.t/0`.
|
||||
Return the count of all currently unread notifications for the user in all categories.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unread_notifications_for_user_and_type(user, :image_comment, ...)
|
||||
[%Notification{}, ...]
|
||||
iex> total_unread_notification_count(user)
|
||||
15
|
||||
|
||||
"""
|
||||
def unread_notifications_for_user_and_type(user, type, pagination) do
|
||||
notifications =
|
||||
user
|
||||
|> unread_query_for_type(type)
|
||||
|> Repo.paginate(pagination)
|
||||
|
||||
put_in(notifications.entries, load_associations(notifications.entries))
|
||||
def total_unread_notification_count(user) do
|
||||
Category.total_unread_notification_count(user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gather up and return the top N notifications for the user, for each type of
|
||||
Gather up and return the top N notifications for the user, for each category of
|
||||
unread notification currently existing.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unread_notifications_for_user(user)
|
||||
[
|
||||
forum_topic: [%Notification{...}, ...],
|
||||
forum_post: [%Notification{...}, ...],
|
||||
image_comment: [%Notification{...}, ...]
|
||||
]
|
||||
iex> unread_notifications_for_user(user, page_size: 10)
|
||||
%{
|
||||
channel_live: [],
|
||||
forum_post: [%ForumPostNotification{...}, ...],
|
||||
forum_topic: [%ForumTopicNotification{...}, ...],
|
||||
gallery_image: [],
|
||||
image_comment: [%ImageCommentNotification{...}, ...],
|
||||
image_merge: []
|
||||
}
|
||||
|
||||
"""
|
||||
def unread_notifications_for_user(user, n) do
|
||||
Category.types()
|
||||
|> Enum.map(fn type ->
|
||||
q =
|
||||
user
|
||||
|> unread_query_for_type(type)
|
||||
|> limit(^n)
|
||||
|
||||
# Use a subquery to ensure the order by is applied to the
|
||||
# subquery results only, and not the main query results
|
||||
from(n in subquery(q))
|
||||
end)
|
||||
|> union_all_queries()
|
||||
|> Repo.all()
|
||||
|> load_associations()
|
||||
|> Enum.group_by(&Category.notification_type/1)
|
||||
|> Enum.sort_by(fn {k, _v} -> k end)
|
||||
def unread_notifications_for_user(user, pagination) do
|
||||
Category.unread_notifications_for_user(user, pagination)
|
||||
end
|
||||
|
||||
defp unread_query_for_type(user, type) do
|
||||
from n in Category.query_for_type(type),
|
||||
join: un in UnreadNotification,
|
||||
on: un.notification_id == n.id,
|
||||
where: un.user_id == ^user.id,
|
||||
order_by: [desc: :updated_at]
|
||||
@doc """
|
||||
Returns paginated unread notifications for the user, given the category.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unread_notifications_for_user_and_category(user, :image_comment)
|
||||
[%ImageCommentNotification{...}]
|
||||
|
||||
"""
|
||||
def unread_notifications_for_user_and_category(user, category, pagination) do
|
||||
Category.unread_notifications_for_user_and_category(user, category, pagination)
|
||||
end
|
||||
|
||||
defp union_all_queries([query | rest]) do
|
||||
Enum.reduce(rest, query, fn q, acc -> union_all(acc, ^q) end)
|
||||
@doc """
|
||||
Creates a channel live notification, returning the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_channel_live_notification(channel)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def create_channel_live_notification(channel) do
|
||||
Creator.create_single(ChannelSubscription, ChannelLiveNotification, :channel_id, channel)
|
||||
end
|
||||
|
||||
defp load_associations(notifications) do
|
||||
Polymorphic.load_polymorphic(
|
||||
notifications,
|
||||
actor: [actor_id: :actor_type],
|
||||
actor_child: [actor_child_id: :actor_child_type]
|
||||
@doc """
|
||||
Creates a forum post notification, returning the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_forum_post_notification(topic, post)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def create_forum_post_notification(topic, post) do
|
||||
Creator.create_double(
|
||||
TopicSubscription,
|
||||
ForumPostNotification,
|
||||
:topic_id,
|
||||
topic,
|
||||
:post_id,
|
||||
post
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single notification.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Notification does not exist.
|
||||
Creates a forum topic notification, returning the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_notification!(123)
|
||||
%Notification{}
|
||||
|
||||
iex> get_notification!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
iex> create_forum_topic_notification(topic)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def get_notification!(id), do: Repo.get!(Notification, id)
|
||||
|
||||
@doc """
|
||||
Creates a notification.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_notification(%{field: value})
|
||||
{:ok, %Notification{}}
|
||||
|
||||
iex> create_notification(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_notification(attrs \\ %{}) do
|
||||
%Notification{}
|
||||
|> Notification.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
def create_forum_topic_notification(topic) do
|
||||
Creator.create_single(ForumSubscription, ForumTopicNotification, :topic_id, topic)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a notification.
|
||||
Creates a gallery image notification, returning the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_notification(notification, %{field: new_value})
|
||||
{:ok, %Notification{}}
|
||||
|
||||
iex> update_notification(notification, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
iex> create_gallery_image_notification(gallery)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def update_notification(%Notification{} = notification, attrs) do
|
||||
notification
|
||||
|> Notification.changeset(attrs)
|
||||
|> Repo.insert_or_update()
|
||||
def create_gallery_image_notification(gallery) do
|
||||
Creator.create_single(GallerySubscription, GalleryImageNotification, :gallery_id, gallery)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a Notification.
|
||||
Creates an image comment notification, returning the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_notification(notification)
|
||||
{:ok, %Notification{}}
|
||||
|
||||
iex> delete_notification(notification)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
iex> create_image_comment_notification(image, comment)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def delete_notification(%Notification{} = notification) do
|
||||
Repo.delete(notification)
|
||||
def create_image_comment_notification(image, comment) do
|
||||
Creator.create_double(
|
||||
ImageSubscription,
|
||||
ImageCommentNotification,
|
||||
:image_id,
|
||||
image,
|
||||
:comment_id,
|
||||
comment
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking notification changes.
|
||||
Creates an image merge notification, returning the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_notification(notification)
|
||||
%Ecto.Changeset{source: %Notification{}}
|
||||
iex> create_image_merge_notification(target, source)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def change_notification(%Notification{} = notification) do
|
||||
Notification.changeset(notification, %{})
|
||||
end
|
||||
|
||||
def count_unread_notifications(user) do
|
||||
UnreadNotification
|
||||
|> where(user_id: ^user.id)
|
||||
|> Repo.aggregate(:count, :notification_id)
|
||||
def create_image_merge_notification(target, source) do
|
||||
Creator.create_double(
|
||||
ImageSubscription,
|
||||
ImageMergeNotification,
|
||||
:target_id,
|
||||
target,
|
||||
:source_id,
|
||||
source
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a unread_notification.
|
||||
Removes the channel live notification for a given channel and user, returning
|
||||
the number of affected users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_unread_notification(%{field: value})
|
||||
{:ok, %UnreadNotification{}}
|
||||
|
||||
iex> create_unread_notification(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
iex> clear_channel_live_notification(channel, user)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def create_unread_notification(attrs \\ %{}) do
|
||||
%UnreadNotification{}
|
||||
|> UnreadNotification.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
def clear_channel_live_notification(channel, user) do
|
||||
ChannelLiveNotification
|
||||
|> where(channel_id: ^channel.id)
|
||||
|> Creator.clear(user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a unread_notification.
|
||||
Removes the forum post notification for a given topic and user, returning
|
||||
the number of affected notifications.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_unread_notification(unread_notification, %{field: new_value})
|
||||
{:ok, %UnreadNotification{}}
|
||||
|
||||
iex> update_unread_notification(unread_notification, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
iex> clear_forum_post_notification(topic, user)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def update_unread_notification(%UnreadNotification{} = unread_notification, attrs) do
|
||||
unread_notification
|
||||
|> UnreadNotification.changeset(attrs)
|
||||
|> Repo.update()
|
||||
def clear_forum_post_notification(topic, user) do
|
||||
ForumPostNotification
|
||||
|> where(topic_id: ^topic.id)
|
||||
|> Creator.clear(user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a UnreadNotification.
|
||||
Removes the forum topic notification for a given topic and user, returning
|
||||
the number of affected notifications.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_unread_notification(unread_notification)
|
||||
{:ok, %UnreadNotification{}}
|
||||
|
||||
iex> delete_unread_notification(unread_notification)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
iex> clear_forum_topic_notification(topic, user)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def delete_unread_notification(actor_type, actor_id, user) do
|
||||
notification =
|
||||
Notification
|
||||
|> where(actor_type: ^actor_type, actor_id: ^actor_id)
|
||||
|> Repo.one()
|
||||
|
||||
if notification do
|
||||
UnreadNotification
|
||||
|> where(notification_id: ^notification.id, user_id: ^user.id)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
def clear_forum_topic_notification(topic, user) do
|
||||
ForumTopicNotification
|
||||
|> where(topic_id: ^topic.id)
|
||||
|> Creator.clear(user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking unread_notification changes.
|
||||
Removes the gallery image notification for a given gallery and user, returning
|
||||
the number of affected notifications.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_unread_notification(unread_notification)
|
||||
%Ecto.Changeset{source: %UnreadNotification{}}
|
||||
iex> clear_gallery_image_notification(topic, user)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def change_unread_notification(%UnreadNotification{} = unread_notification) do
|
||||
UnreadNotification.changeset(unread_notification, %{})
|
||||
def clear_gallery_image_notification(gallery, user) do
|
||||
GalleryImageNotification
|
||||
|> where(gallery_id: ^gallery.id)
|
||||
|> Creator.clear(user)
|
||||
end
|
||||
|
||||
def notify(_actor_child, [], _params), do: nil
|
||||
@doc """
|
||||
Removes the image comment notification for a given image and user, returning
|
||||
the number of affected notifications.
|
||||
|
||||
def notify(actor_child, subscriptions, params) do
|
||||
# Don't push to the user that created the notification
|
||||
subscriptions =
|
||||
case actor_child do
|
||||
%{user_id: id} ->
|
||||
subscriptions
|
||||
|> Enum.reject(&(&1.user_id == id))
|
||||
## Examples
|
||||
|
||||
_ ->
|
||||
subscriptions
|
||||
iex> clear_gallery_image_notification(topic, user)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def clear_image_comment_notification(image, user) do
|
||||
ImageCommentNotification
|
||||
|> where(image_id: ^image.id)
|
||||
|> Creator.clear(user)
|
||||
end
|
||||
|
||||
Repo.transaction(fn ->
|
||||
notification =
|
||||
Notification
|
||||
|> Repo.get_by(actor_id: params.actor_id, actor_type: params.actor_type)
|
||||
@doc """
|
||||
Removes the image merge notification for a given image and user, returning
|
||||
the number of affected notifications.
|
||||
|
||||
{:ok, notification} =
|
||||
(notification || %Notification{})
|
||||
|> update_notification(params)
|
||||
## Examples
|
||||
|
||||
# Insert the notification to any watchers who do not have it
|
||||
unreads =
|
||||
subscriptions
|
||||
|> Enum.map(&%{user_id: &1.user_id, notification_id: notification.id})
|
||||
iex> clear_image_merge_notification(topic, user)
|
||||
{:ok, 2}
|
||||
|
||||
UnreadNotification
|
||||
|> Repo.insert_all(unreads, on_conflict: :nothing)
|
||||
end)
|
||||
"""
|
||||
def clear_image_merge_notification(image, user) do
|
||||
ImageMergeNotification
|
||||
|> where(target_id: ^image.id)
|
||||
|> Creator.clear(user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
defmodule Philomena.Notifications.Category do
|
||||
@moduledoc """
|
||||
Notification category determination.
|
||||
Notification category querying.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Philomena.Notifications.Notification
|
||||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Notifications.ChannelLiveNotification
|
||||
alias Philomena.Notifications.ForumPostNotification
|
||||
alias Philomena.Notifications.ForumTopicNotification
|
||||
alias Philomena.Notifications.GalleryImageNotification
|
||||
alias Philomena.Notifications.ImageCommentNotification
|
||||
alias Philomena.Notifications.ImageMergeNotification
|
||||
|
||||
@type t ::
|
||||
:channel_live
|
||||
|
@ -15,79 +22,145 @@ defmodule Philomena.Notifications.Category do
|
|||
| :image_merge
|
||||
|
||||
@doc """
|
||||
Return a list of all supported types.
|
||||
Return a list of all supported categories.
|
||||
"""
|
||||
def types do
|
||||
def categories do
|
||||
[
|
||||
:channel_live,
|
||||
:forum_post,
|
||||
:forum_topic,
|
||||
:gallery_image,
|
||||
:image_comment,
|
||||
:image_merge,
|
||||
:forum_post
|
||||
:image_merge
|
||||
]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determine the type of a `m:Philomena.Notifications.Notification`.
|
||||
Return the count of all currently unread notifications for the user in all categories.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> total_unread_notification_count(user)
|
||||
15
|
||||
|
||||
"""
|
||||
def notification_type(n) do
|
||||
case {n.actor_type, n.actor_child_type} do
|
||||
{"Channel", _} ->
|
||||
:channel_live
|
||||
def total_unread_notification_count(user) do
|
||||
categories()
|
||||
|> Enum.map(fn category ->
|
||||
category
|
||||
|> query_for_category_and_user(user)
|
||||
|> exclude(:preload)
|
||||
|> select([_], %{one: 1})
|
||||
end)
|
||||
|> union_all_queries()
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
{"Gallery", _} ->
|
||||
:gallery_image
|
||||
defp union_all_queries([query | rest]) do
|
||||
Enum.reduce(rest, query, fn q, acc -> union_all(acc, ^q) end)
|
||||
end
|
||||
|
||||
{"Image", "Comment"} ->
|
||||
@doc """
|
||||
Gather up and return the top N notifications for the user, for each category of
|
||||
unread notification currently existing.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unread_notifications_for_user(user, page_size: 10)
|
||||
%{
|
||||
channel_live: [],
|
||||
forum_post: [%ForumPostNotification{...}, ...],
|
||||
forum_topic: [%ForumTopicNotification{...}, ...],
|
||||
gallery_image: [],
|
||||
image_comment: [%ImageCommentNotification{...}, ...],
|
||||
image_merge: []
|
||||
}
|
||||
|
||||
"""
|
||||
def unread_notifications_for_user(user, pagination) do
|
||||
Map.new(categories(), fn category ->
|
||||
results =
|
||||
category
|
||||
|> query_for_category_and_user(user)
|
||||
|> order_by(desc: :updated_at)
|
||||
|> Repo.paginate(pagination)
|
||||
|
||||
{category, results}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns paginated unread notifications for the user, given the category.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unread_notifications_for_user_and_category(user, :image_comment)
|
||||
[%ImageCommentNotification{...}]
|
||||
|
||||
"""
|
||||
def unread_notifications_for_user_and_category(user, category, pagination) do
|
||||
category
|
||||
|> query_for_category_and_user(user)
|
||||
|> order_by(desc: :updated_at)
|
||||
|> Repo.paginate(pagination)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determine the category of a notification.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> notification_category(%ImageCommentNotification{})
|
||||
:image_comment
|
||||
|
||||
{"Image", _} ->
|
||||
:image_merge
|
||||
|
||||
{"Topic", "Post"} ->
|
||||
if n.action == "posted a new reply in" do
|
||||
:forum_post
|
||||
else
|
||||
:forum_topic
|
||||
end
|
||||
"""
|
||||
def notification_category(n) do
|
||||
case n.__struct__ do
|
||||
ChannelLiveNotification -> :channel_live
|
||||
GalleryImageNotification -> :gallery_image
|
||||
ImageCommentNotification -> :image_comment
|
||||
ImageMergeNotification -> :image_merge
|
||||
ForumPostNotification -> :forum_post
|
||||
ForumTopicNotification -> :forum_topic
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `m:Ecto.Query` that finds notifications for the given type.
|
||||
"""
|
||||
def query_for_type(type) do
|
||||
base = from(n in Notification)
|
||||
Returns an `m:Ecto.Query` that finds unread notifications for the given category,
|
||||
for the given user, with preloads applied.
|
||||
|
||||
case type do
|
||||
## Examples
|
||||
|
||||
iex> query_for_category_and_user(:channel_live, user)
|
||||
#Ecto.Query<from c0 in ChannelLiveNotification, where: c0.user_id == ^1, preload: [:channel]>
|
||||
|
||||
"""
|
||||
def query_for_category_and_user(category, user) do
|
||||
query =
|
||||
case category do
|
||||
:channel_live ->
|
||||
where(base, [n], n.actor_type == "Channel")
|
||||
from(n in ChannelLiveNotification, preload: :channel)
|
||||
|
||||
:gallery_image ->
|
||||
where(base, [n], n.actor_type == "Gallery")
|
||||
from(n in GalleryImageNotification, preload: [gallery: :creator])
|
||||
|
||||
:image_comment ->
|
||||
where(base, [n], n.actor_type == "Image" and n.actor_child_type == "Comment")
|
||||
from(n in ImageCommentNotification,
|
||||
preload: [image: [:sources, tags: :aliases], comment: :user]
|
||||
)
|
||||
|
||||
:image_merge ->
|
||||
where(base, [n], n.actor_type == "Image" and is_nil(n.actor_child_type))
|
||||
from(n in ImageMergeNotification,
|
||||
preload: [:source, target: [:sources, tags: :aliases]]
|
||||
)
|
||||
|
||||
:forum_topic ->
|
||||
where(
|
||||
base,
|
||||
[n],
|
||||
n.actor_type == "Topic" and n.actor_child_type == "Post" and
|
||||
n.action != "posted a new reply in"
|
||||
)
|
||||
from(n in ForumTopicNotification, preload: [topic: [:forum, :user]])
|
||||
|
||||
:forum_post ->
|
||||
where(
|
||||
base,
|
||||
[n],
|
||||
n.actor_type == "Topic" and n.actor_child_type == "Post" and
|
||||
n.action == "posted a new reply in"
|
||||
)
|
||||
end
|
||||
from(n in ForumPostNotification, preload: [topic: :forum, post: :user])
|
||||
end
|
||||
|
||||
where(query, user_id: ^user.id)
|
||||
end
|
||||
end
|
||||
|
|
25
lib/philomena/notifications/channel_live_notification.ex
Normal file
25
lib/philomena/notifications/channel_live_notification.ex
Normal file
|
@ -0,0 +1,25 @@
|
|||
defmodule Philomena.Notifications.ChannelLiveNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Channels.Channel
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "channel_live_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :channel, Channel, primary_key: true
|
||||
|
||||
field :read, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(channel_live_notification, attrs) do
|
||||
channel_live_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
123
lib/philomena/notifications/creator.ex
Normal file
123
lib/philomena/notifications/creator.ex
Normal file
|
@ -0,0 +1,123 @@
|
|||
defmodule Philomena.Notifications.Creator do
|
||||
@moduledoc """
|
||||
Internal notifications creation logic.
|
||||
|
||||
Supports two formats for notification creation:
|
||||
- Key-only (`create_single/4`): The object's id is the only other component inserted.
|
||||
- Non-key (`create_double/6`): The object's id plus another object's id are inserted.
|
||||
|
||||
See the respective documentation for each function for more details.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Philomena.Repo
|
||||
|
||||
@doc """
|
||||
Propagate notifications for a notification table type containing a single reference column.
|
||||
|
||||
The single reference column (`name`, `object`) is also part of the unique key for the table,
|
||||
and is used to select which object to act on.
|
||||
|
||||
Returns `{:ok, count}`, where `count` is the number of affected rows.
|
||||
|
||||
## Example
|
||||
|
||||
iex> create_single(GallerySubscription, GalleryImageNotification, :gallery_id, gallery)
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def create_single(subscription, notification, name, object) do
|
||||
subscription
|
||||
|> create_notification_query(name, object)
|
||||
|> create_notification(notification, name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Propagate notifications for a notification table type containing two reference columns.
|
||||
|
||||
The first reference column (`name1`, `object1`) is also part of the unique key for the table,
|
||||
and is used to select which object to act on.
|
||||
|
||||
Returns `{:ok, count}`, where `count` is the number of affected rows.
|
||||
|
||||
## Example
|
||||
|
||||
iex> create_double(
|
||||
...> ImageSubscription,
|
||||
...> ImageCommentNotification,
|
||||
...> :image_id,
|
||||
...> image,
|
||||
...> :comment_id,
|
||||
...> comment
|
||||
...> )
|
||||
{:ok, 2}
|
||||
|
||||
"""
|
||||
def create_double(subscription, notification, name1, object1, name2, object2) do
|
||||
subscription
|
||||
|> create_notification_query(name1, object1, name2, object2)
|
||||
|> create_notification(notification, name1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Clear all unread notifications using the given query.
|
||||
|
||||
Returns `{:ok, count}`, where `count` is the number of affected rows.
|
||||
"""
|
||||
def clear(query, user) do
|
||||
if user do
|
||||
{count, nil} =
|
||||
query
|
||||
|> where(user_id: ^user.id)
|
||||
|> Repo.delete_all()
|
||||
|
||||
{:ok, count}
|
||||
else
|
||||
{:ok, 0}
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: the following cannot be accomplished with a single query expression
|
||||
# due to this Ecto bug: https://github.com/elixir-ecto/ecto/issues/4430
|
||||
|
||||
defp create_notification_query(subscription, name, object) do
|
||||
now = DateTime.utc_now(:second)
|
||||
|
||||
from s in subscription,
|
||||
where: field(s, ^name) == ^object.id,
|
||||
select: %{
|
||||
^name => type(^object.id, :integer),
|
||||
user_id: s.user_id,
|
||||
created_at: ^now,
|
||||
updated_at: ^now,
|
||||
read: false
|
||||
}
|
||||
end
|
||||
|
||||
defp create_notification_query(subscription, name1, object1, name2, object2) do
|
||||
now = DateTime.utc_now(:second)
|
||||
|
||||
from s in subscription,
|
||||
where: field(s, ^name1) == ^object1.id,
|
||||
select: %{
|
||||
^name1 => type(^object1.id, :integer),
|
||||
^name2 => type(^object2.id, :integer),
|
||||
user_id: s.user_id,
|
||||
created_at: ^now,
|
||||
updated_at: ^now,
|
||||
read: false
|
||||
}
|
||||
end
|
||||
|
||||
defp create_notification(query, notification, name) do
|
||||
{count, nil} =
|
||||
Repo.insert_all(
|
||||
notification,
|
||||
query,
|
||||
on_conflict: {:replace_all_except, [:created_at]},
|
||||
conflict_target: [name, :user_id]
|
||||
)
|
||||
|
||||
{:ok, count}
|
||||
end
|
||||
end
|
27
lib/philomena/notifications/forum_post_notification.ex
Normal file
27
lib/philomena/notifications/forum_post_notification.ex
Normal file
|
@ -0,0 +1,27 @@
|
|||
defmodule Philomena.Notifications.ForumPostNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Topics.Topic
|
||||
alias Philomena.Posts.Post
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "forum_post_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :topic, Topic, primary_key: true
|
||||
belongs_to :post, Post
|
||||
|
||||
field :read, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(forum_post_notification, attrs) do
|
||||
forum_post_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
25
lib/philomena/notifications/forum_topic_notification.ex
Normal file
25
lib/philomena/notifications/forum_topic_notification.ex
Normal file
|
@ -0,0 +1,25 @@
|
|||
defmodule Philomena.Notifications.ForumTopicNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Topics.Topic
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "forum_topic_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :topic, Topic, primary_key: true
|
||||
|
||||
field :read, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(forum_topic_notification, attrs) do
|
||||
forum_topic_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
25
lib/philomena/notifications/gallery_image_notification.ex
Normal file
25
lib/philomena/notifications/gallery_image_notification.ex
Normal file
|
@ -0,0 +1,25 @@
|
|||
defmodule Philomena.Notifications.GalleryImageNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Galleries.Gallery
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "gallery_image_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :gallery, Gallery, primary_key: true
|
||||
|
||||
field :read, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(gallery_image_notification, attrs) do
|
||||
gallery_image_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
27
lib/philomena/notifications/image_comment_notification.ex
Normal file
27
lib/philomena/notifications/image_comment_notification.ex
Normal file
|
@ -0,0 +1,27 @@
|
|||
defmodule Philomena.Notifications.ImageCommentNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Images.Image
|
||||
alias Philomena.Comments.Comment
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "image_comment_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :image, Image, primary_key: true
|
||||
belongs_to :comment, Comment
|
||||
|
||||
field :read, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(image_comment_notification, attrs) do
|
||||
image_comment_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
26
lib/philomena/notifications/image_merge_notification.ex
Normal file
26
lib/philomena/notifications/image_merge_notification.ex
Normal file
|
@ -0,0 +1,26 @@
|
|||
defmodule Philomena.Notifications.ImageMergeNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Images.Image
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "image_merge_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :target, Image, primary_key: true
|
||||
belongs_to :source, Image
|
||||
|
||||
field :read, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(image_merge_notification, attrs) do
|
||||
image_merge_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
defmodule Philomena.Notifications.Notification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "notifications" do
|
||||
field :action, :string
|
||||
|
||||
# fixme: rails polymorphic relation
|
||||
field :actor_id, :integer
|
||||
field :actor_type, :string
|
||||
field :actor_child_id, :integer
|
||||
field :actor_child_type, :string
|
||||
|
||||
field :actor, :any, virtual: true
|
||||
field :actor_child, :any, virtual: true
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(notification, attrs) do
|
||||
notification
|
||||
|> cast(attrs, [:actor_id, :actor_type, :actor_child_id, :actor_child_type, :action])
|
||||
|> validate_required([:actor_id, :actor_type, :action])
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
defmodule Philomena.Notifications.UnreadNotification do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Notifications.Notification
|
||||
|
||||
@primary_key false
|
||||
|
||||
schema "unread_notifications" do
|
||||
belongs_to :user, User, primary_key: true
|
||||
belongs_to :notification, Notification, primary_key: true
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(unread_notification, attrs) do
|
||||
unread_notification
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
|
@ -128,22 +128,7 @@ defmodule Philomena.Posts do
|
|||
|> Repo.preload(:topic)
|
||||
|> Map.fetch!(:topic)
|
||||
|
||||
subscriptions =
|
||||
topic
|
||||
|> Repo.preload(:subscriptions)
|
||||
|> Map.fetch!(:subscriptions)
|
||||
|
||||
Notifications.notify(
|
||||
post,
|
||||
subscriptions,
|
||||
%{
|
||||
actor_id: topic.id,
|
||||
actor_type: "Topic",
|
||||
actor_child_id: post.id,
|
||||
actor_child_type: "Post",
|
||||
action: "posted a new reply in"
|
||||
}
|
||||
)
|
||||
Notifications.create_forum_post_notification(topic, post)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -2,35 +2,26 @@ defmodule Philomena.Subscriptions do
|
|||
@moduledoc """
|
||||
Common subscription logic.
|
||||
|
||||
`use Philomena.Subscriptions` requires the following properties:
|
||||
|
||||
- `:actor_types`
|
||||
This is the "actor_type" in the notifications table.
|
||||
For `Philomena.Images`, this would be `["Image"]`.
|
||||
`use Philomena.Subscriptions` requires the following option:
|
||||
|
||||
- `:id_name`
|
||||
This is the name of the object field in the subscription table.
|
||||
For `Philomena.Images`, this would be `:image_id`.
|
||||
For `m:Philomena.Images`, this would be `:image_id`.
|
||||
|
||||
The following functions and documentation are produced in the calling module:
|
||||
- `subscribed?/2`
|
||||
- `subscriptions/2`
|
||||
- `create_subscription/2`
|
||||
- `delete_subscription/2`
|
||||
- `clear_notification/2`
|
||||
- `maybe_subscribe_on/4`
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Ecto.Multi
|
||||
|
||||
alias Philomena.Notifications
|
||||
alias Philomena.Repo
|
||||
|
||||
defmacro __using__(opts) do
|
||||
# For Philomena.Images, this yields ["Image"]
|
||||
actor_types = Keyword.fetch!(opts, :actor_types)
|
||||
|
||||
# For Philomena.Images, this yields :image_id
|
||||
field_name = Keyword.fetch!(opts, :id_name)
|
||||
|
||||
|
@ -109,8 +100,6 @@ defmodule Philomena.Subscriptions do
|
|||
|
||||
"""
|
||||
def delete_subscription(object, user) do
|
||||
clear_notification(object, user)
|
||||
|
||||
Philomena.Subscriptions.delete_subscription(
|
||||
unquote(subscription_module),
|
||||
unquote(field_name),
|
||||
|
@ -119,23 +108,6 @@ defmodule Philomena.Subscriptions do
|
|||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes any active notifications for a subscription.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> clear_notification(object, user)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def clear_notification(object, user) do
|
||||
for type <- unquote(actor_types) do
|
||||
Philomena.Subscriptions.clear_notification(type, object, user)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a subscription inside the `m:Ecto.Multi` flow if `user` is not nil
|
||||
and `field` in `user` is `true`.
|
||||
|
@ -199,14 +171,6 @@ defmodule Philomena.Subscriptions do
|
|||
|> Repo.delete()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def clear_notification(type, object, user) do
|
||||
case user do
|
||||
nil -> nil
|
||||
_ -> Notifications.delete_unread_notification(type, object.id, user)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def maybe_subscribe_on(multi, module, change_name, user, field)
|
||||
when field in [:watch_on_reply, :watch_on_upload, :watch_on_new_topic] do
|
||||
|
|
|
@ -14,7 +14,6 @@ defmodule Philomena.Topics do
|
|||
alias Philomena.NotificationWorker
|
||||
|
||||
use Philomena.Subscriptions,
|
||||
actor_types: ~w(Topic),
|
||||
id_name: :topic_id
|
||||
|
||||
@doc """
|
||||
|
@ -91,31 +90,10 @@ defmodule Philomena.Topics do
|
|||
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Topics", [topic.id, post.id]])
|
||||
end
|
||||
|
||||
def perform_notify([topic_id, post_id]) do
|
||||
def perform_notify([topic_id, _post_id]) do
|
||||
topic = get_topic!(topic_id)
|
||||
post = Posts.get_post!(post_id)
|
||||
|
||||
forum =
|
||||
topic
|
||||
|> Repo.preload(:forum)
|
||||
|> Map.fetch!(:forum)
|
||||
|
||||
subscriptions =
|
||||
forum
|
||||
|> Repo.preload(:subscriptions)
|
||||
|> Map.fetch!(:subscriptions)
|
||||
|
||||
Notifications.notify(
|
||||
post,
|
||||
subscriptions,
|
||||
%{
|
||||
actor_id: topic.id,
|
||||
actor_type: "Topic",
|
||||
actor_child_id: post.id,
|
||||
actor_child_type: "Post",
|
||||
action: "posted a new topic in #{forum.name}"
|
||||
}
|
||||
)
|
||||
Notifications.create_forum_topic_notification(topic)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -242,4 +220,19 @@ defmodule Philomena.Topics do
|
|||
|> Topic.title_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes all topic notifications for a given topic and user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> clear_topic_notification(topic, user)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def clear_topic_notification(%Topic{} = topic, user) do
|
||||
Notifications.clear_forum_post_notification(topic, user)
|
||||
Notifications.clear_forum_topic_notification(topic, user)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,6 @@ defmodule Philomena.Users.User do
|
|||
alias Philomena.Filters.Filter
|
||||
alias Philomena.ArtistLinks.ArtistLink
|
||||
alias Philomena.Badges
|
||||
alias Philomena.Notifications.UnreadNotification
|
||||
alias Philomena.Galleries.Gallery
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Commissions.Commission
|
||||
|
@ -30,8 +29,6 @@ defmodule Philomena.Users.User do
|
|||
has_many :public_links, ArtistLink, where: [public: true, aasm_state: "verified"]
|
||||
has_many :galleries, Gallery, foreign_key: :creator_id
|
||||
has_many :awards, Badges.Award
|
||||
has_many :unread_notifications, UnreadNotification
|
||||
has_many :notifications, through: [:unread_notifications, :notification]
|
||||
has_many :linked_tags, through: [:verified_links, :tag]
|
||||
has_many :user_ips, UserIp
|
||||
has_many :user_fingerprints, UserFingerprint
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule PhilomenaWeb.Channel.ReadController do
|
|||
channel = conn.assigns.channel
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Channels.clear_notification(channel, user)
|
||||
Channels.clear_channel_notification(channel, user)
|
||||
|
||||
send_resp(conn, :ok, "")
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ defmodule PhilomenaWeb.ChannelController do
|
|||
channel = conn.assigns.channel
|
||||
user = conn.assigns.current_user
|
||||
|
||||
if user, do: Channels.clear_notification(channel, user)
|
||||
Channels.clear_channel_notification(channel, user)
|
||||
|
||||
redirect(conn, external: channel_url(channel))
|
||||
end
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
defmodule PhilomenaWeb.Forum.ReadController do
|
||||
import Plug.Conn
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Forums.Forum
|
||||
alias Philomena.Forums
|
||||
|
||||
plug :load_resource,
|
||||
model: Forum,
|
||||
id_name: "forum_id",
|
||||
id_field: "short_name",
|
||||
persisted: true
|
||||
|
||||
def create(conn, _params) do
|
||||
forum = conn.assigns.forum
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Forums.clear_notification(forum, user)
|
||||
|
||||
send_resp(conn, :ok, "")
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@ defmodule PhilomenaWeb.Gallery.ReadController do
|
|||
gallery = conn.assigns.gallery
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Galleries.clear_notification(gallery, user)
|
||||
Galleries.clear_gallery_notification(gallery, user)
|
||||
|
||||
send_resp(conn, :ok, "")
|
||||
end
|
||||
|
|
|
@ -80,7 +80,7 @@ defmodule PhilomenaWeb.GalleryController do
|
|||
|
||||
gallery_json = Jason.encode!(Enum.map(gallery_images, &elem(&1, 0).id))
|
||||
|
||||
Galleries.clear_notification(gallery, user)
|
||||
Galleries.clear_gallery_notification(gallery, user)
|
||||
|
||||
conn
|
||||
|> NotificationCountPlug.call([])
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule PhilomenaWeb.Image.ReadController do
|
|||
image = conn.assigns.image
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Images.clear_notification(image, user)
|
||||
Images.clear_image_notification(image, user)
|
||||
|
||||
send_resp(conn, :ok, "")
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ defmodule PhilomenaWeb.ImageController do
|
|||
image = conn.assigns.image
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Images.clear_notification(image, user)
|
||||
Images.clear_image_notification(image, user)
|
||||
|
||||
# Update the notification ticker in the header
|
||||
conn = NotificationCountPlug.call(conn)
|
||||
|
|
|
@ -4,19 +4,19 @@ defmodule PhilomenaWeb.Notification.CategoryController do
|
|||
alias Philomena.Notifications
|
||||
|
||||
def show(conn, params) do
|
||||
type = category(params)
|
||||
category_param = category(params)
|
||||
|
||||
notifications =
|
||||
Notifications.unread_notifications_for_user_and_type(
|
||||
Notifications.unread_notifications_for_user_and_category(
|
||||
conn.assigns.current_user,
|
||||
type,
|
||||
category_param,
|
||||
conn.assigns.scrivener
|
||||
)
|
||||
|
||||
render(conn, "show.html",
|
||||
title: "Notification Area",
|
||||
notifications: notifications,
|
||||
type: type
|
||||
category: category_param
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,11 @@ defmodule PhilomenaWeb.NotificationController do
|
|||
alias Philomena.Notifications
|
||||
|
||||
def index(conn, _params) do
|
||||
notifications = Notifications.unread_notifications_for_user(conn.assigns.current_user, 15)
|
||||
notifications =
|
||||
Notifications.unread_notifications_for_user(
|
||||
conn.assigns.current_user,
|
||||
page_size: 10
|
||||
)
|
||||
|
||||
render(conn, "index.html", title: "Notification Area", notifications: notifications)
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ defmodule PhilomenaWeb.Topic.ReadController do
|
|||
def create(conn, _params) do
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Topics.clear_notification(conn.assigns.topic, user)
|
||||
Topics.clear_topic_notification(conn.assigns.topic, user)
|
||||
|
||||
send_resp(conn, :ok, "")
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule PhilomenaWeb.TopicController do
|
|||
|
||||
alias PhilomenaWeb.NotificationCountPlug
|
||||
alias Philomena.{Forums.Forum, Topics.Topic, Posts.Post, Polls.Poll, PollOptions.PollOption}
|
||||
alias Philomena.{Forums, Topics, Polls, Posts}
|
||||
alias Philomena.{Topics, Polls, Posts}
|
||||
alias Philomena.PollVotes
|
||||
alias PhilomenaWeb.MarkdownRenderer
|
||||
alias Philomena.Repo
|
||||
|
@ -34,8 +34,7 @@ defmodule PhilomenaWeb.TopicController do
|
|||
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Topics.clear_notification(topic, user)
|
||||
Forums.clear_notification(forum, user)
|
||||
Topics.clear_topic_notification(topic, user)
|
||||
|
||||
# Update the notification ticker in the header
|
||||
conn = NotificationCountPlug.call(conn)
|
||||
|
|
|
@ -32,7 +32,7 @@ defmodule PhilomenaWeb.NotificationCountPlug do
|
|||
defp maybe_assign_notifications(conn, nil), do: conn
|
||||
|
||||
defp maybe_assign_notifications(conn, user) do
|
||||
notifications = Notifications.count_unread_notifications(user)
|
||||
notifications = Notifications.total_unread_notification_count(user)
|
||||
|
||||
Conn.assign(conn, :notification_count, notifications)
|
||||
end
|
||||
|
|
|
@ -263,8 +263,6 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/subscription", Forum.SubscriptionController,
|
||||
only: [:create, :delete],
|
||||
singleton: true
|
||||
|
||||
resources "/read", Forum.ReadController, only: [:create], singleton: true
|
||||
end
|
||||
|
||||
resources "/profiles", ProfileController, only: [] do
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
strong>
|
||||
= link @notification.actor.title, to: ~p"/channels/#{@notification.actor}"
|
||||
=<> @notification.action
|
||||
= link @notification.channel.title, to: ~p"/channels/#{@notification.channel}"
|
||||
' went live
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/channels/#{@notification.actor}/read" data-method="post" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button.button--separate-right title="Delete" href=~p"/channels/#{@notification.channel}/read" data-method="post" data-remote="true"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/channels/#{@notification.actor}/subscription" data-method="delete" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button title="Unsubscribe" href=~p"/channels/#{@notification.channel}/subscription" data-method="delete" data-remote="true"
|
||||
i.fa.fa-bell-slash
|
22
lib/philomena_web/templates/notification/_comment.html.slime
Normal file
22
lib/philomena_web/templates/notification/_comment.html.slime
Normal file
|
@ -0,0 +1,22 @@
|
|||
- comment = @notification.comment
|
||||
- image = @notification.image
|
||||
|
||||
.flex.flex--centered.flex__fixed.thumb-tiny-container.spacing-right
|
||||
= render PhilomenaWeb.ImageView, "_image_container.html", image: image, size: :thumb_tiny, conn: @conn
|
||||
|
||||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: comment, conn: @conn
|
||||
' commented on
|
||||
|
||||
strong>
|
||||
= link "##{image.id}", to: ~p"/images/#{image}" <> "#comments"
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/images/#{image}/read" data-method="post" data-remote="true"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/images/#{image}/subscription" data-method="delete" data-remote="true"
|
||||
i.fa.fa-bell-slash
|
|
@ -1,25 +0,0 @@
|
|||
- forum = @notification.actor
|
||||
- topic = @notification.actor_child
|
||||
|
||||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: topic, conn: @conn
|
||||
=> @notification.action
|
||||
|
||||
' titled
|
||||
|
||||
strong>
|
||||
= link topic.title, to: ~p"/forums/#{forum}/topics/#{topic}"
|
||||
|
||||
' in
|
||||
|
||||
=> link forum.name, to: ~p"/forums/#{forum}"
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/forums/#{forum}/read" data-method="post" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/forums/#{forum}/subscription" data-method="delete" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
i.fa.fa-bell-slash
|
|
@ -1,16 +1,18 @@
|
|||
- gallery = @notification.gallery
|
||||
|
||||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
=> render PhilomenaWeb.UserAttributionView, "_user.html", object: %{user: @notification.actor.creator}, conn: @conn
|
||||
=> @notification.action
|
||||
=> render PhilomenaWeb.UserAttributionView, "_user.html", object: %{user: gallery.creator}, conn: @conn
|
||||
' added images to
|
||||
|
||||
strong>
|
||||
= link @notification.actor.title, to: ~p"/galleries/#{@notification.actor}"
|
||||
= link gallery.title, to: ~p"/galleries/#{gallery}"
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/galleries/#{@notification.actor}/read" data-method="post" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button.button--separate-right title="Delete" href=~p"/galleries/#{gallery}/read" data-method="post" data-remote="true"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/galleries/#{@notification.actor}/subscription" data-method="delete" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button title="Unsubscribe" href=~p"/galleries/#{gallery}/subscription" data-method="delete" data-remote="true"
|
||||
i.fa.fa-bell-slash
|
|
@ -1,19 +1,24 @@
|
|||
- target = @notification.target
|
||||
- source = @notification.source
|
||||
|
||||
.flex.flex--centered.flex__fixed.thumb-tiny-container.spacing-right
|
||||
= render PhilomenaWeb.ImageView, "_image_container.html", image: target, size: :thumb_tiny, conn: @conn
|
||||
|
||||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
= if @notification.actor_child do
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @notification.actor_child, conn: @conn
|
||||
- else
|
||||
' Someone
|
||||
=> @notification.action
|
||||
| merged #
|
||||
= source.id
|
||||
' into
|
||||
|
||||
strong>
|
||||
= link "##{@notification.actor_id}", to: ~p"/images/#{@notification.actor}" <> "#comments"
|
||||
= link "##{target.id}", to: ~p"/images/#{target}" <> "#comments"
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/images/#{@notification.actor}/read" data-method="post" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button.button--separate-right title="Delete" href=~p"/images/#{target}/read" data-method="post" data-remote="true"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/images/#{@notification.actor}/subscription" data-method="delete" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button title="Unsubscribe" href=~p"/images/#{target}/subscription" data-method="delete" data-remote="true"
|
||||
i.fa.fa-bell-slash
|
|
@ -1,7 +0,0 @@
|
|||
= if @notification.actor do
|
||||
.block.block--fixed.flex.notification id="notification-#{@notification.id}"
|
||||
= if @notification.actor_type == "Image" and @notification.actor do
|
||||
.flex.flex--centered.flex__fixed.thumb-tiny-container.spacing-right
|
||||
= render PhilomenaWeb.ImageView, "_image_container.html", image: @notification.actor, size: :thumb_tiny, conn: @conn
|
||||
|
||||
=> render PhilomenaWeb.NotificationView, notification_template_path(@notification.actor_type), notification: @notification, conn: @conn
|
19
lib/philomena_web/templates/notification/_post.html.slime
Normal file
19
lib/philomena_web/templates/notification/_post.html.slime
Normal file
|
@ -0,0 +1,19 @@
|
|||
- topic = @notification.topic
|
||||
- post = @notification.post
|
||||
|
||||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: post, conn: @conn
|
||||
' posted a new reply in
|
||||
|
||||
strong>
|
||||
= link topic.title, to: ~p"/forums/#{topic.forum}/topics/#{topic}?#{[post_id: post.id]}" <> "#post_#{post.id}"
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/forums/#{topic.forum}/topics/#{topic}/read" data-method="post" data-remote="true"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/forums/#{topic.forum}/topics/#{topic}/subscription" data-method="delete" data-remote="true"
|
||||
i.fa.fa-bell-slash
|
|
@ -1,19 +1,23 @@
|
|||
- topic = @notification.actor
|
||||
- post = @notification.actor_child
|
||||
- topic = @notification.topic
|
||||
- forum = topic.forum
|
||||
|
||||
.flex.flex--centered.flex__grow
|
||||
div
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: post, conn: @conn
|
||||
=> @notification.action
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: topic, conn: @conn
|
||||
' posted a new topic titled
|
||||
|
||||
strong>
|
||||
= link topic.title, to: ~p"/forums/#{topic.forum}/topics/#{topic}?#{[post_id: post.id]}" <> "#post_#{post.id}"
|
||||
= link topic.title, to: ~p"/forums/#{forum}/topics/#{topic}"
|
||||
|
||||
' in
|
||||
|
||||
=> link forum.name, to: ~p"/forums/#{forum}"
|
||||
|
||||
=> pretty_time @notification.updated_at
|
||||
|
||||
.flex.flex--centered.flex--no-wrap
|
||||
a.button.button--separate-right title="Delete" href=~p"/forums/#{topic.forum}/topics/#{topic}/read" data-method="post" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button.button--separate-right title="Delete" href=~p"/forums/#{forum}/topics/#{topic}/read" data-method="post" data-remote="true"
|
||||
i.fa.fa-trash
|
||||
|
||||
a.button title="Unsubscribe" href=~p"/forums/#{topic.forum}/topics/#{topic}/subscription" data-method="delete" data-remote="true" data-fetchcomplete-hide="#notification-#{@notification.id}"
|
||||
a.button title="Unsubscribe" href=~p"/forums/#{forum}/subscription" data-method="delete" data-remote="true"
|
||||
i.fa.fa-bell-slash
|
|
@ -2,18 +2,19 @@ h1 Notification Area
|
|||
.walloftext
|
||||
= cond do
|
||||
- Enum.any?(@notifications) ->
|
||||
- route = fn p -> ~p"/notifications/categories/#{@type}?#{p}" end
|
||||
- route = fn p -> ~p"/notifications/categories/#{@category}?#{p}" end
|
||||
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @notifications, route: route, conn: @conn
|
||||
|
||||
.block.notification-type-block
|
||||
.block__header
|
||||
span.block__header__title = name_of_type(@type)
|
||||
span.block__header__title = name_of_category(@category)
|
||||
.block__header.block__header__sub
|
||||
= pagination
|
||||
|
||||
div
|
||||
= for notification <- @notifications do
|
||||
= render PhilomenaWeb.NotificationView, "_notification.html", notification: notification, conn: @conn
|
||||
.block.block--fixed.flex.notification
|
||||
= render PhilomenaWeb.NotificationView, notification_template_path(@category), notification: notification, conn: @conn
|
||||
|
||||
.block__header.block__header--light
|
||||
= pagination
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
h1 Notification Area
|
||||
.walloftext
|
||||
= cond do
|
||||
- Enum.any?(@notifications) ->
|
||||
= for {type, notifications} <- @notifications do
|
||||
= for {category, notifications} <- @notifications, Enum.any?(notifications) do
|
||||
.block.notification-type-block
|
||||
.block__header
|
||||
span.block__header__title = name_of_type(type)
|
||||
span.block__header__title = name_of_category(category)
|
||||
|
||||
div
|
||||
= for notification <- notifications do
|
||||
= render PhilomenaWeb.NotificationView, "_notification.html", notification: notification, conn: @conn
|
||||
.block.block--fixed.flex.notification
|
||||
= render PhilomenaWeb.NotificationView, notification_template_path(category), notification: notification, conn: @conn
|
||||
|
||||
.block__header.block__header--light
|
||||
a href=~p"/notifications/categories/#{type}"
|
||||
| View category
|
||||
a href=~p"/notifications/categories/#{category}"
|
||||
| View category (
|
||||
= notifications.total_entries
|
||||
| )
|
||||
|
||||
- true ->
|
||||
p
|
||||
' To get notifications on new comments and forum posts, click the
|
||||
' 'Subscribe' button in the bar at the top of an image or forum topic.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
defmodule PhilomenaWeb.Notification.CategoryView do
|
||||
use PhilomenaWeb, :view
|
||||
|
||||
defdelegate name_of_type(type), to: PhilomenaWeb.NotificationView
|
||||
defdelegate name_of_category(category), to: PhilomenaWeb.NotificationView
|
||||
defdelegate notification_template_path(category), to: PhilomenaWeb.NotificationView
|
||||
end
|
||||
|
|
|
@ -2,20 +2,20 @@ defmodule PhilomenaWeb.NotificationView do
|
|||
use PhilomenaWeb, :view
|
||||
|
||||
@template_paths %{
|
||||
"Channel" => "_channel.html",
|
||||
"Forum" => "_forum.html",
|
||||
"Gallery" => "_gallery.html",
|
||||
"Image" => "_image.html",
|
||||
"LivestreamChannel" => "_channel.html",
|
||||
"Topic" => "_topic.html"
|
||||
"channel_live" => "_channel.html",
|
||||
"forum_post" => "_post.html",
|
||||
"forum_topic" => "_topic.html",
|
||||
"gallery_image" => "_gallery.html",
|
||||
"image_comment" => "_comment.html",
|
||||
"image_merge" => "_image.html"
|
||||
}
|
||||
|
||||
def notification_template_path(actor_type) do
|
||||
@template_paths[actor_type]
|
||||
def notification_template_path(category) do
|
||||
@template_paths[to_string(category)]
|
||||
end
|
||||
|
||||
def name_of_type(notification_type) do
|
||||
case notification_type do
|
||||
def name_of_category(category) do
|
||||
case category do
|
||||
:channel_live ->
|
||||
"Live channels"
|
||||
|
||||
|
|
Loading…
Reference in a new issue