mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-30 14:57:59 +01:00
Merge pull request #328 from philomena-dev/notifications-v3
New notifications tables
This commit is contained in:
commit
fe59b046f7
47 changed files with 1273 additions and 542 deletions
|
@ -8,10 +8,10 @@ defmodule Philomena.Channels do
|
||||||
|
|
||||||
alias Philomena.Channels.AutomaticUpdater
|
alias Philomena.Channels.AutomaticUpdater
|
||||||
alias Philomena.Channels.Channel
|
alias Philomena.Channels.Channel
|
||||||
|
alias Philomena.Notifications
|
||||||
alias Philomena.Tags
|
alias Philomena.Tags
|
||||||
|
|
||||||
use Philomena.Subscriptions,
|
use Philomena.Subscriptions,
|
||||||
actor_types: ~w(Channel LivestreamChannel),
|
|
||||||
id_name: :channel_id
|
id_name: :channel_id
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -139,4 +139,18 @@ defmodule Philomena.Channels do
|
||||||
def change_channel(%Channel{} = channel) do
|
def change_channel(%Channel{} = channel) do
|
||||||
Channel.changeset(channel, %{})
|
Channel.changeset(channel, %{})
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -79,22 +79,7 @@ defmodule Philomena.Comments do
|
||||||
|> Repo.preload(:image)
|
|> Repo.preload(:image)
|
||||||
|> Map.fetch!(:image)
|
|> Map.fetch!(:image)
|
||||||
|
|
||||||
subscriptions =
|
Notifications.create_image_comment_notification(image, comment)
|
||||||
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"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -9,7 +9,6 @@ defmodule Philomena.Forums do
|
||||||
alias Philomena.Forums.Forum
|
alias Philomena.Forums.Forum
|
||||||
|
|
||||||
use Philomena.Subscriptions,
|
use Philomena.Subscriptions,
|
||||||
actor_types: ~w(Forum),
|
|
||||||
id_name: :forum_id
|
id_name: :forum_id
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -19,7 +19,6 @@ defmodule Philomena.Galleries do
|
||||||
alias Philomena.Images
|
alias Philomena.Images
|
||||||
|
|
||||||
use Philomena.Subscriptions,
|
use Philomena.Subscriptions,
|
||||||
actor_types: ~w(Gallery),
|
|
||||||
id_name: :gallery_id
|
id_name: :gallery_id
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -269,25 +268,10 @@ defmodule Philomena.Galleries do
|
||||||
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Galleries", [gallery.id, image.id]])
|
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Galleries", [gallery.id, image.id]])
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_notify([gallery_id, image_id]) do
|
def perform_notify([gallery_id, _image_id]) do
|
||||||
gallery = get_gallery!(gallery_id)
|
gallery = get_gallery!(gallery_id)
|
||||||
|
|
||||||
subscriptions =
|
Notifications.create_gallery_image_notification(gallery)
|
||||||
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"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reorder_gallery(gallery, image_ids) do
|
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(%{order_position_asc: true}), do: [asc: :position]
|
||||||
defp position_order(_gallery), do: [desc: :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
|
end
|
||||||
|
|
|
@ -22,7 +22,8 @@ defmodule Philomena.Images do
|
||||||
alias Philomena.IndexWorker
|
alias Philomena.IndexWorker
|
||||||
alias Philomena.ImageFeatures.ImageFeature
|
alias Philomena.ImageFeatures.ImageFeature
|
||||||
alias Philomena.SourceChanges.SourceChange
|
alias Philomena.SourceChanges.SourceChange
|
||||||
alias Philomena.Notifications.Notification
|
alias Philomena.Notifications.ImageCommentNotification
|
||||||
|
alias Philomena.Notifications.ImageMergeNotification
|
||||||
alias Philomena.NotificationWorker
|
alias Philomena.NotificationWorker
|
||||||
alias Philomena.TagChanges.Limits
|
alias Philomena.TagChanges.Limits
|
||||||
alias Philomena.TagChanges.TagChange
|
alias Philomena.TagChanges.TagChange
|
||||||
|
@ -38,7 +39,6 @@ defmodule Philomena.Images do
|
||||||
alias Philomena.Users.User
|
alias Philomena.Users.User
|
||||||
|
|
||||||
use Philomena.Subscriptions,
|
use Philomena.Subscriptions,
|
||||||
actor_types: ~w(Image),
|
|
||||||
id_name: :image_id
|
id_name: :image_id
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -905,12 +905,17 @@ defmodule Philomena.Images do
|
||||||
|
|
||||||
Repo.insert_all(Subscription, subscriptions, on_conflict: :nothing)
|
Repo.insert_all(Subscription, subscriptions, on_conflict: :nothing)
|
||||||
|
|
||||||
{count, nil} =
|
{comment_notification_count, nil} =
|
||||||
Notification
|
ImageCommentNotification
|
||||||
|> where(actor_type: "Image", actor_id: ^source.id)
|
|> where(image_id: ^source.id)
|
||||||
|> Repo.delete_all()
|
|> 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
|
end
|
||||||
|
|
||||||
def migrate_sources(source, target) do
|
def migrate_sources(source, target) do
|
||||||
|
@ -930,23 +935,24 @@ defmodule Philomena.Images do
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_notify([source_id, target_id]) do
|
def perform_notify([source_id, target_id]) do
|
||||||
|
source = get_image!(source_id)
|
||||||
target = get_image!(target_id)
|
target = get_image!(target_id)
|
||||||
|
|
||||||
subscriptions =
|
Notifications.create_image_merge_notification(target, source)
|
||||||
target
|
end
|
||||||
|> Repo.preload(:subscriptions)
|
|
||||||
|> Map.fetch!(:subscriptions)
|
|
||||||
|
|
||||||
Notifications.notify(
|
@doc """
|
||||||
nil,
|
Removes all image notifications for a given image and user.
|
||||||
subscriptions,
|
|
||||||
%{
|
## Examples
|
||||||
actor_id: target.id,
|
|
||||||
actor_type: "Image",
|
iex> clear_image_notification(image, user)
|
||||||
actor_child_id: nil,
|
:ok
|
||||||
actor_child_type: nil,
|
|
||||||
action: "merged ##{source_id} into"
|
"""
|
||||||
}
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,277 +4,262 @@ defmodule Philomena.Notifications do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
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.Category
|
||||||
alias Philomena.Notifications.Notification
|
alias Philomena.Notifications.Creator
|
||||||
alias Philomena.Notifications.UnreadNotification
|
|
||||||
alias Philomena.Polymorphic
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of unread notifications of the given type.
|
Return the count of all currently unread notifications for the user in all categories.
|
||||||
|
|
||||||
The set of valid types is `t:Philomena.Notifications.Category.t/0`.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> unread_notifications_for_user_and_type(user, :image_comment, ...)
|
iex> total_unread_notification_count(user)
|
||||||
[%Notification{}, ...]
|
15
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def unread_notifications_for_user_and_type(user, type, pagination) do
|
def total_unread_notification_count(user) do
|
||||||
notifications =
|
Category.total_unread_notification_count(user)
|
||||||
user
|
|
||||||
|> unread_query_for_type(type)
|
|
||||||
|> Repo.paginate(pagination)
|
|
||||||
|
|
||||||
put_in(notifications.entries, load_associations(notifications.entries))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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.
|
unread notification currently existing.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> unread_notifications_for_user(user)
|
iex> unread_notifications_for_user(user, page_size: 10)
|
||||||
[
|
%{
|
||||||
forum_topic: [%Notification{...}, ...],
|
channel_live: [],
|
||||||
forum_post: [%Notification{...}, ...],
|
forum_post: [%ForumPostNotification{...}, ...],
|
||||||
image_comment: [%Notification{...}, ...]
|
forum_topic: [%ForumTopicNotification{...}, ...],
|
||||||
]
|
gallery_image: [],
|
||||||
|
image_comment: [%ImageCommentNotification{...}, ...],
|
||||||
|
image_merge: []
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def unread_notifications_for_user(user, n) do
|
def unread_notifications_for_user(user, pagination) do
|
||||||
Category.types()
|
Category.unread_notifications_for_user(user, pagination)
|
||||||
|> 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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp unread_query_for_type(user, type) do
|
@doc """
|
||||||
from n in Category.query_for_type(type),
|
Returns paginated unread notifications for the user, given the category.
|
||||||
join: un in UnreadNotification,
|
|
||||||
on: un.notification_id == n.id,
|
## Examples
|
||||||
where: un.user_id == ^user.id,
|
|
||||||
order_by: [desc: :updated_at]
|
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
|
end
|
||||||
|
|
||||||
defp union_all_queries([query | rest]) do
|
@doc """
|
||||||
Enum.reduce(rest, query, fn q, acc -> union_all(acc, ^q) end)
|
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
|
end
|
||||||
|
|
||||||
defp load_associations(notifications) do
|
@doc """
|
||||||
Polymorphic.load_polymorphic(
|
Creates a forum post notification, returning the number of affected users.
|
||||||
notifications,
|
|
||||||
actor: [actor_id: :actor_type],
|
## Examples
|
||||||
actor_child: [actor_child_id: :actor_child_type]
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single notification.
|
Creates a forum topic notification, returning the number of affected users.
|
||||||
|
|
||||||
Raises `Ecto.NoResultsError` if the Notification does not exist.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> get_notification!(123)
|
iex> create_forum_topic_notification(topic)
|
||||||
%Notification{}
|
{:ok, 2}
|
||||||
|
|
||||||
iex> get_notification!(456)
|
|
||||||
** (Ecto.NoResultsError)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def get_notification!(id), do: Repo.get!(Notification, id)
|
def create_forum_topic_notification(topic) do
|
||||||
|
Creator.create_single(ForumSubscription, ForumTopicNotification, :topic_id, topic)
|
||||||
@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()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates a notification.
|
Creates a gallery image notification, returning the number of affected users.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> update_notification(notification, %{field: new_value})
|
iex> create_gallery_image_notification(gallery)
|
||||||
{:ok, %Notification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
iex> update_notification(notification, %{field: bad_value})
|
|
||||||
{:error, %Ecto.Changeset{}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def update_notification(%Notification{} = notification, attrs) do
|
def create_gallery_image_notification(gallery) do
|
||||||
notification
|
Creator.create_single(GallerySubscription, GalleryImageNotification, :gallery_id, gallery)
|
||||||
|> Notification.changeset(attrs)
|
|
||||||
|> Repo.insert_or_update()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a Notification.
|
Creates an image comment notification, returning the number of affected users.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> delete_notification(notification)
|
iex> create_image_comment_notification(image, comment)
|
||||||
{:ok, %Notification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
iex> delete_notification(notification)
|
|
||||||
{:error, %Ecto.Changeset{}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def delete_notification(%Notification{} = notification) do
|
def create_image_comment_notification(image, comment) do
|
||||||
Repo.delete(notification)
|
Creator.create_double(
|
||||||
|
ImageSubscription,
|
||||||
|
ImageCommentNotification,
|
||||||
|
:image_id,
|
||||||
|
image,
|
||||||
|
:comment_id,
|
||||||
|
comment
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking notification changes.
|
Creates an image merge notification, returning the number of affected users.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> change_notification(notification)
|
iex> create_image_merge_notification(target, source)
|
||||||
%Ecto.Changeset{source: %Notification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def change_notification(%Notification{} = notification) do
|
def create_image_merge_notification(target, source) do
|
||||||
Notification.changeset(notification, %{})
|
Creator.create_double(
|
||||||
end
|
ImageSubscription,
|
||||||
|
ImageMergeNotification,
|
||||||
def count_unread_notifications(user) do
|
:target_id,
|
||||||
UnreadNotification
|
target,
|
||||||
|> where(user_id: ^user.id)
|
:source_id,
|
||||||
|> Repo.aggregate(:count, :notification_id)
|
source
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a unread_notification.
|
Removes the channel live notification for a given channel and user, returning
|
||||||
|
the number of affected users.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> create_unread_notification(%{field: value})
|
iex> clear_channel_live_notification(channel, user)
|
||||||
{:ok, %UnreadNotification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
iex> create_unread_notification(%{field: bad_value})
|
|
||||||
{:error, %Ecto.Changeset{}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def create_unread_notification(attrs \\ %{}) do
|
def clear_channel_live_notification(channel, user) do
|
||||||
%UnreadNotification{}
|
ChannelLiveNotification
|
||||||
|> UnreadNotification.changeset(attrs)
|
|> where(channel_id: ^channel.id)
|
||||||
|> Repo.insert()
|
|> Creator.clear(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates a unread_notification.
|
Removes the forum post notification for a given topic and user, returning
|
||||||
|
the number of affected notifications.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> update_unread_notification(unread_notification, %{field: new_value})
|
iex> clear_forum_post_notification(topic, user)
|
||||||
{:ok, %UnreadNotification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
iex> update_unread_notification(unread_notification, %{field: bad_value})
|
|
||||||
{:error, %Ecto.Changeset{}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def update_unread_notification(%UnreadNotification{} = unread_notification, attrs) do
|
def clear_forum_post_notification(topic, user) do
|
||||||
unread_notification
|
ForumPostNotification
|
||||||
|> UnreadNotification.changeset(attrs)
|
|> where(topic_id: ^topic.id)
|
||||||
|> Repo.update()
|
|> Creator.clear(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a UnreadNotification.
|
Removes the forum topic notification for a given topic and user, returning
|
||||||
|
the number of affected notifications.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> delete_unread_notification(unread_notification)
|
iex> clear_forum_topic_notification(topic, user)
|
||||||
{:ok, %UnreadNotification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
iex> delete_unread_notification(unread_notification)
|
|
||||||
{:error, %Ecto.Changeset{}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def delete_unread_notification(actor_type, actor_id, user) do
|
def clear_forum_topic_notification(topic, user) do
|
||||||
notification =
|
ForumTopicNotification
|
||||||
Notification
|
|> where(topic_id: ^topic.id)
|
||||||
|> where(actor_type: ^actor_type, actor_id: ^actor_id)
|
|> Creator.clear(user)
|
||||||
|> Repo.one()
|
|
||||||
|
|
||||||
if notification do
|
|
||||||
UnreadNotification
|
|
||||||
|> where(notification_id: ^notification.id, user_id: ^user.id)
|
|
||||||
|> Repo.delete_all()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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
|
## Examples
|
||||||
|
|
||||||
iex> change_unread_notification(unread_notification)
|
iex> clear_gallery_image_notification(topic, user)
|
||||||
%Ecto.Changeset{source: %UnreadNotification{}}
|
{:ok, 2}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def change_unread_notification(%UnreadNotification{} = unread_notification) do
|
def clear_gallery_image_notification(gallery, user) do
|
||||||
UnreadNotification.changeset(unread_notification, %{})
|
GalleryImageNotification
|
||||||
|
|> where(gallery_id: ^gallery.id)
|
||||||
|
|> Creator.clear(user)
|
||||||
end
|
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
|
## Examples
|
||||||
# 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))
|
|
||||||
|
|
||||||
_ ->
|
iex> clear_gallery_image_notification(topic, user)
|
||||||
subscriptions
|
{:ok, 2}
|
||||||
end
|
|
||||||
|
|
||||||
Repo.transaction(fn ->
|
"""
|
||||||
notification =
|
def clear_image_comment_notification(image, user) do
|
||||||
Notification
|
ImageCommentNotification
|
||||||
|> Repo.get_by(actor_id: params.actor_id, actor_type: params.actor_type)
|
|> where(image_id: ^image.id)
|
||||||
|
|> Creator.clear(user)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, notification} =
|
@doc """
|
||||||
(notification || %Notification{})
|
Removes the image merge notification for a given image and user, returning
|
||||||
|> update_notification(params)
|
the number of affected notifications.
|
||||||
|
|
||||||
# Insert the notification to any watchers who do not have it
|
## Examples
|
||||||
unreads =
|
|
||||||
subscriptions
|
|
||||||
|> Enum.map(&%{user_id: &1.user_id, notification_id: notification.id})
|
|
||||||
|
|
||||||
UnreadNotification
|
iex> clear_image_merge_notification(topic, user)
|
||||||
|> Repo.insert_all(unreads, on_conflict: :nothing)
|
{:ok, 2}
|
||||||
end)
|
|
||||||
|
"""
|
||||||
|
def clear_image_merge_notification(image, user) do
|
||||||
|
ImageMergeNotification
|
||||||
|
|> where(target_id: ^image.id)
|
||||||
|
|> Creator.clear(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
defmodule Philomena.Notifications.Category do
|
defmodule Philomena.Notifications.Category do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Notification category determination.
|
Notification category querying.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
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 ::
|
@type t ::
|
||||||
:channel_live
|
:channel_live
|
||||||
|
@ -15,79 +22,145 @@ defmodule Philomena.Notifications.Category do
|
||||||
| :image_merge
|
| :image_merge
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Return a list of all supported types.
|
Return a list of all supported categories.
|
||||||
"""
|
"""
|
||||||
def types do
|
def categories do
|
||||||
[
|
[
|
||||||
:channel_live,
|
:channel_live,
|
||||||
|
:forum_post,
|
||||||
:forum_topic,
|
:forum_topic,
|
||||||
:gallery_image,
|
:gallery_image,
|
||||||
:image_comment,
|
:image_comment,
|
||||||
:image_merge,
|
:image_merge
|
||||||
:forum_post
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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
|
def total_unread_notification_count(user) do
|
||||||
case {n.actor_type, n.actor_child_type} do
|
categories()
|
||||||
{"Channel", _} ->
|
|> Enum.map(fn category ->
|
||||||
:channel_live
|
category
|
||||||
|
|> query_for_category_and_user(user)
|
||||||
|
|> exclude(:preload)
|
||||||
|
|> select([_], %{one: 1})
|
||||||
|
end)
|
||||||
|
|> union_all_queries()
|
||||||
|
|> Repo.aggregate(:count)
|
||||||
|
end
|
||||||
|
|
||||||
{"Gallery", _} ->
|
defp union_all_queries([query | rest]) do
|
||||||
:gallery_image
|
Enum.reduce(rest, query, fn q, acc -> union_all(acc, ^q) end)
|
||||||
|
end
|
||||||
|
|
||||||
{"Image", "Comment"} ->
|
@doc """
|
||||||
:image_comment
|
Gather up and return the top N notifications for the user, for each category of
|
||||||
|
unread notification currently existing.
|
||||||
|
|
||||||
{"Image", _} ->
|
## Examples
|
||||||
:image_merge
|
|
||||||
|
|
||||||
{"Topic", "Post"} ->
|
iex> unread_notifications_for_user(user, page_size: 10)
|
||||||
if n.action == "posted a new reply in" do
|
%{
|
||||||
:forum_post
|
channel_live: [],
|
||||||
else
|
forum_post: [%ForumPostNotification{...}, ...],
|
||||||
:forum_topic
|
forum_topic: [%ForumTopicNotification{...}, ...],
|
||||||
end
|
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
|
||||||
|
|
||||||
|
"""
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `m:Ecto.Query` that finds notifications for the given type.
|
Returns an `m:Ecto.Query` that finds unread notifications for the given category,
|
||||||
|
for the given user, with preloads applied.
|
||||||
|
|
||||||
|
## 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_type(type) do
|
def query_for_category_and_user(category, user) do
|
||||||
base = from(n in Notification)
|
query =
|
||||||
|
case category do
|
||||||
|
:channel_live ->
|
||||||
|
from(n in ChannelLiveNotification, preload: :channel)
|
||||||
|
|
||||||
case type do
|
:gallery_image ->
|
||||||
:channel_live ->
|
from(n in GalleryImageNotification, preload: [gallery: :creator])
|
||||||
where(base, [n], n.actor_type == "Channel")
|
|
||||||
|
|
||||||
:gallery_image ->
|
:image_comment ->
|
||||||
where(base, [n], n.actor_type == "Gallery")
|
from(n in ImageCommentNotification,
|
||||||
|
preload: [image: [:sources, tags: :aliases], comment: :user]
|
||||||
|
)
|
||||||
|
|
||||||
:image_comment ->
|
:image_merge ->
|
||||||
where(base, [n], n.actor_type == "Image" and n.actor_child_type == "Comment")
|
from(n in ImageMergeNotification,
|
||||||
|
preload: [:source, target: [:sources, tags: :aliases]]
|
||||||
|
)
|
||||||
|
|
||||||
:image_merge ->
|
:forum_topic ->
|
||||||
where(base, [n], n.actor_type == "Image" and is_nil(n.actor_child_type))
|
from(n in ForumTopicNotification, preload: [topic: [:forum, :user]])
|
||||||
|
|
||||||
:forum_topic ->
|
:forum_post ->
|
||||||
where(
|
from(n in ForumPostNotification, preload: [topic: :forum, post: :user])
|
||||||
base,
|
end
|
||||||
[n],
|
|
||||||
n.actor_type == "Topic" and n.actor_child_type == "Post" and
|
|
||||||
n.action != "posted a new reply in"
|
|
||||||
)
|
|
||||||
|
|
||||||
:forum_post ->
|
where(query, user_id: ^user.id)
|
||||||
where(
|
|
||||||
base,
|
|
||||||
[n],
|
|
||||||
n.actor_type == "Topic" and n.actor_child_type == "Post" and
|
|
||||||
n.action == "posted a new reply in"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
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)
|
|> Repo.preload(:topic)
|
||||||
|> Map.fetch!(:topic)
|
|> Map.fetch!(:topic)
|
||||||
|
|
||||||
subscriptions =
|
Notifications.create_forum_post_notification(topic, post)
|
||||||
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"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -2,35 +2,26 @@ defmodule Philomena.Subscriptions do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Common subscription logic.
|
Common subscription logic.
|
||||||
|
|
||||||
`use Philomena.Subscriptions` requires the following properties:
|
`use Philomena.Subscriptions` requires the following option:
|
||||||
|
|
||||||
- `:actor_types`
|
|
||||||
This is the "actor_type" in the notifications table.
|
|
||||||
For `Philomena.Images`, this would be `["Image"]`.
|
|
||||||
|
|
||||||
- `:id_name`
|
- `:id_name`
|
||||||
This is the name of the object field in the subscription table.
|
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:
|
The following functions and documentation are produced in the calling module:
|
||||||
- `subscribed?/2`
|
- `subscribed?/2`
|
||||||
- `subscriptions/2`
|
- `subscriptions/2`
|
||||||
- `create_subscription/2`
|
- `create_subscription/2`
|
||||||
- `delete_subscription/2`
|
- `delete_subscription/2`
|
||||||
- `clear_notification/2`
|
|
||||||
- `maybe_subscribe_on/4`
|
- `maybe_subscribe_on/4`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Ecto.Multi
|
alias Ecto.Multi
|
||||||
|
|
||||||
alias Philomena.Notifications
|
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
|
|
||||||
defmacro __using__(opts) do
|
defmacro __using__(opts) do
|
||||||
# For Philomena.Images, this yields ["Image"]
|
|
||||||
actor_types = Keyword.fetch!(opts, :actor_types)
|
|
||||||
|
|
||||||
# For Philomena.Images, this yields :image_id
|
# For Philomena.Images, this yields :image_id
|
||||||
field_name = Keyword.fetch!(opts, :id_name)
|
field_name = Keyword.fetch!(opts, :id_name)
|
||||||
|
|
||||||
|
@ -109,8 +100,6 @@ defmodule Philomena.Subscriptions do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def delete_subscription(object, user) do
|
def delete_subscription(object, user) do
|
||||||
clear_notification(object, user)
|
|
||||||
|
|
||||||
Philomena.Subscriptions.delete_subscription(
|
Philomena.Subscriptions.delete_subscription(
|
||||||
unquote(subscription_module),
|
unquote(subscription_module),
|
||||||
unquote(field_name),
|
unquote(field_name),
|
||||||
|
@ -119,23 +108,6 @@ defmodule Philomena.Subscriptions do
|
||||||
)
|
)
|
||||||
end
|
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 """
|
@doc """
|
||||||
Creates a subscription inside the `m:Ecto.Multi` flow if `user` is not nil
|
Creates a subscription inside the `m:Ecto.Multi` flow if `user` is not nil
|
||||||
and `field` in `user` is `true`.
|
and `field` in `user` is `true`.
|
||||||
|
@ -199,14 +171,6 @@ defmodule Philomena.Subscriptions do
|
||||||
|> Repo.delete()
|
|> Repo.delete()
|
||||||
end
|
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
|
@doc false
|
||||||
def maybe_subscribe_on(multi, module, change_name, user, field)
|
def maybe_subscribe_on(multi, module, change_name, user, field)
|
||||||
when field in [:watch_on_reply, :watch_on_upload, :watch_on_new_topic] do
|
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
|
alias Philomena.NotificationWorker
|
||||||
|
|
||||||
use Philomena.Subscriptions,
|
use Philomena.Subscriptions,
|
||||||
actor_types: ~w(Topic),
|
|
||||||
id_name: :topic_id
|
id_name: :topic_id
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -91,31 +90,10 @@ defmodule Philomena.Topics do
|
||||||
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Topics", [topic.id, post.id]])
|
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Topics", [topic.id, post.id]])
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_notify([topic_id, post_id]) do
|
def perform_notify([topic_id, _post_id]) do
|
||||||
topic = get_topic!(topic_id)
|
topic = get_topic!(topic_id)
|
||||||
post = Posts.get_post!(post_id)
|
|
||||||
|
|
||||||
forum =
|
Notifications.create_forum_topic_notification(topic)
|
||||||
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}"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -242,4 +220,19 @@ defmodule Philomena.Topics do
|
||||||
|> Topic.title_changeset(attrs)
|
|> Topic.title_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -12,7 +12,6 @@ defmodule Philomena.Users.User do
|
||||||
alias Philomena.Filters.Filter
|
alias Philomena.Filters.Filter
|
||||||
alias Philomena.ArtistLinks.ArtistLink
|
alias Philomena.ArtistLinks.ArtistLink
|
||||||
alias Philomena.Badges
|
alias Philomena.Badges
|
||||||
alias Philomena.Notifications.UnreadNotification
|
|
||||||
alias Philomena.Galleries.Gallery
|
alias Philomena.Galleries.Gallery
|
||||||
alias Philomena.Users.User
|
alias Philomena.Users.User
|
||||||
alias Philomena.Commissions.Commission
|
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 :public_links, ArtistLink, where: [public: true, aasm_state: "verified"]
|
||||||
has_many :galleries, Gallery, foreign_key: :creator_id
|
has_many :galleries, Gallery, foreign_key: :creator_id
|
||||||
has_many :awards, Badges.Award
|
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 :linked_tags, through: [:verified_links, :tag]
|
||||||
has_many :user_ips, UserIp
|
has_many :user_ips, UserIp
|
||||||
has_many :user_fingerprints, UserFingerprint
|
has_many :user_fingerprints, UserFingerprint
|
||||||
|
|
|
@ -11,7 +11,7 @@ defmodule PhilomenaWeb.Channel.ReadController do
|
||||||
channel = conn.assigns.channel
|
channel = conn.assigns.channel
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
Channels.clear_notification(channel, user)
|
Channels.clear_channel_notification(channel, user)
|
||||||
|
|
||||||
send_resp(conn, :ok, "")
|
send_resp(conn, :ok, "")
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,7 +37,7 @@ defmodule PhilomenaWeb.ChannelController do
|
||||||
channel = conn.assigns.channel
|
channel = conn.assigns.channel
|
||||||
user = conn.assigns.current_user
|
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))
|
redirect(conn, external: channel_url(channel))
|
||||||
end
|
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
|
gallery = conn.assigns.gallery
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
Galleries.clear_notification(gallery, user)
|
Galleries.clear_gallery_notification(gallery, user)
|
||||||
|
|
||||||
send_resp(conn, :ok, "")
|
send_resp(conn, :ok, "")
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,7 +80,7 @@ defmodule PhilomenaWeb.GalleryController do
|
||||||
|
|
||||||
gallery_json = Jason.encode!(Enum.map(gallery_images, &elem(&1, 0).id))
|
gallery_json = Jason.encode!(Enum.map(gallery_images, &elem(&1, 0).id))
|
||||||
|
|
||||||
Galleries.clear_notification(gallery, user)
|
Galleries.clear_gallery_notification(gallery, user)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> NotificationCountPlug.call([])
|
|> NotificationCountPlug.call([])
|
||||||
|
|
|
@ -11,7 +11,7 @@ defmodule PhilomenaWeb.Image.ReadController do
|
||||||
image = conn.assigns.image
|
image = conn.assigns.image
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
Images.clear_notification(image, user)
|
Images.clear_image_notification(image, user)
|
||||||
|
|
||||||
send_resp(conn, :ok, "")
|
send_resp(conn, :ok, "")
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,7 +56,7 @@ defmodule PhilomenaWeb.ImageController do
|
||||||
image = conn.assigns.image
|
image = conn.assigns.image
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
Images.clear_notification(image, user)
|
Images.clear_image_notification(image, user)
|
||||||
|
|
||||||
# Update the notification ticker in the header
|
# Update the notification ticker in the header
|
||||||
conn = NotificationCountPlug.call(conn)
|
conn = NotificationCountPlug.call(conn)
|
||||||
|
|
|
@ -4,19 +4,19 @@ defmodule PhilomenaWeb.Notification.CategoryController do
|
||||||
alias Philomena.Notifications
|
alias Philomena.Notifications
|
||||||
|
|
||||||
def show(conn, params) do
|
def show(conn, params) do
|
||||||
type = category(params)
|
category_param = category(params)
|
||||||
|
|
||||||
notifications =
|
notifications =
|
||||||
Notifications.unread_notifications_for_user_and_type(
|
Notifications.unread_notifications_for_user_and_category(
|
||||||
conn.assigns.current_user,
|
conn.assigns.current_user,
|
||||||
type,
|
category_param,
|
||||||
conn.assigns.scrivener
|
conn.assigns.scrivener
|
||||||
)
|
)
|
||||||
|
|
||||||
render(conn, "show.html",
|
render(conn, "show.html",
|
||||||
title: "Notification Area",
|
title: "Notification Area",
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
type: type
|
category: category_param
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,11 @@ defmodule PhilomenaWeb.NotificationController do
|
||||||
alias Philomena.Notifications
|
alias Philomena.Notifications
|
||||||
|
|
||||||
def index(conn, _params) do
|
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)
|
render(conn, "index.html", title: "Notification Area", notifications: notifications)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ defmodule PhilomenaWeb.Topic.ReadController do
|
||||||
def create(conn, _params) do
|
def create(conn, _params) do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
Topics.clear_notification(conn.assigns.topic, user)
|
Topics.clear_topic_notification(conn.assigns.topic, user)
|
||||||
|
|
||||||
send_resp(conn, :ok, "")
|
send_resp(conn, :ok, "")
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@ defmodule PhilomenaWeb.TopicController do
|
||||||
|
|
||||||
alias PhilomenaWeb.NotificationCountPlug
|
alias PhilomenaWeb.NotificationCountPlug
|
||||||
alias Philomena.{Forums.Forum, Topics.Topic, Posts.Post, Polls.Poll, PollOptions.PollOption}
|
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 Philomena.PollVotes
|
||||||
alias PhilomenaWeb.MarkdownRenderer
|
alias PhilomenaWeb.MarkdownRenderer
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
|
@ -34,8 +34,7 @@ defmodule PhilomenaWeb.TopicController do
|
||||||
|
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
Topics.clear_notification(topic, user)
|
Topics.clear_topic_notification(topic, user)
|
||||||
Forums.clear_notification(forum, user)
|
|
||||||
|
|
||||||
# Update the notification ticker in the header
|
# Update the notification ticker in the header
|
||||||
conn = NotificationCountPlug.call(conn)
|
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, nil), do: conn
|
||||||
|
|
||||||
defp maybe_assign_notifications(conn, user) do
|
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)
|
Conn.assign(conn, :notification_count, notifications)
|
||||||
end
|
end
|
||||||
|
|
|
@ -263,8 +263,6 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/subscription", Forum.SubscriptionController,
|
resources "/subscription", Forum.SubscriptionController,
|
||||||
only: [:create, :delete],
|
only: [:create, :delete],
|
||||||
singleton: true
|
singleton: true
|
||||||
|
|
||||||
resources "/read", Forum.ReadController, only: [:create], singleton: true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
resources "/profiles", ProfileController, only: [] do
|
resources "/profiles", ProfileController, only: [] do
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
.flex.flex--centered.flex__grow
|
.flex.flex--centered.flex__grow
|
||||||
div
|
div
|
||||||
strong>
|
strong>
|
||||||
= link @notification.actor.title, to: ~p"/channels/#{@notification.actor}"
|
= link @notification.channel.title, to: ~p"/channels/#{@notification.channel}"
|
||||||
=<> @notification.action
|
' went live
|
||||||
|
|
||||||
=> pretty_time @notification.updated_at
|
=> pretty_time @notification.updated_at
|
||||||
|
|
||||||
.flex.flex--centered.flex--no-wrap
|
.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
|
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
|
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
|
.flex.flex--centered.flex__grow
|
||||||
div
|
div
|
||||||
=> render PhilomenaWeb.UserAttributionView, "_user.html", object: %{user: @notification.actor.creator}, conn: @conn
|
=> render PhilomenaWeb.UserAttributionView, "_user.html", object: %{user: gallery.creator}, conn: @conn
|
||||||
=> @notification.action
|
' added images to
|
||||||
|
|
||||||
strong>
|
strong>
|
||||||
= link @notification.actor.title, to: ~p"/galleries/#{@notification.actor}"
|
= link gallery.title, to: ~p"/galleries/#{gallery}"
|
||||||
|
|
||||||
=> pretty_time @notification.updated_at
|
=> pretty_time @notification.updated_at
|
||||||
|
|
||||||
.flex.flex--centered.flex--no-wrap
|
.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
|
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
|
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
|
.flex.flex--centered.flex__grow
|
||||||
div
|
div
|
||||||
= if @notification.actor_child do
|
' Someone
|
||||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @notification.actor_child, conn: @conn
|
| merged #
|
||||||
- else
|
= source.id
|
||||||
' Someone
|
' into
|
||||||
=> @notification.action
|
|
||||||
|
|
||||||
strong>
|
strong>
|
||||||
= link "##{@notification.actor_id}", to: ~p"/images/#{@notification.actor}" <> "#comments"
|
= link "##{target.id}", to: ~p"/images/#{target}" <> "#comments"
|
||||||
|
|
||||||
=> pretty_time @notification.updated_at
|
=> pretty_time @notification.updated_at
|
||||||
|
|
||||||
.flex.flex--centered.flex--no-wrap
|
.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
|
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
|
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
|
- topic = @notification.topic
|
||||||
- post = @notification.actor_child
|
- forum = topic.forum
|
||||||
|
|
||||||
.flex.flex--centered.flex__grow
|
.flex.flex--centered.flex__grow
|
||||||
div
|
div
|
||||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: post, conn: @conn
|
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: topic, conn: @conn
|
||||||
=> @notification.action
|
' posted a new topic titled
|
||||||
|
|
||||||
strong>
|
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
|
=> pretty_time @notification.updated_at
|
||||||
|
|
||||||
.flex.flex--centered.flex--no-wrap
|
.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
|
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
|
i.fa.fa-bell-slash
|
||||||
|
|
|
@ -2,18 +2,19 @@ h1 Notification Area
|
||||||
.walloftext
|
.walloftext
|
||||||
= cond do
|
= cond do
|
||||||
- Enum.any?(@notifications) ->
|
- 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
|
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @notifications, route: route, conn: @conn
|
||||||
|
|
||||||
.block.notification-type-block
|
.block.notification-type-block
|
||||||
.block__header
|
.block__header
|
||||||
span.block__header__title = name_of_type(@type)
|
span.block__header__title = name_of_category(@category)
|
||||||
.block__header.block__header__sub
|
.block__header.block__header__sub
|
||||||
= pagination
|
= pagination
|
||||||
|
|
||||||
div
|
div
|
||||||
= for notification <- @notifications do
|
= 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
|
.block__header.block__header--light
|
||||||
= pagination
|
= pagination
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
h1 Notification Area
|
h1 Notification Area
|
||||||
.walloftext
|
.walloftext
|
||||||
= cond do
|
= for {category, notifications} <- @notifications, Enum.any?(notifications) do
|
||||||
- Enum.any?(@notifications) ->
|
.block.notification-type-block
|
||||||
= for {type, notifications} <- @notifications do
|
.block__header
|
||||||
.block.notification-type-block
|
span.block__header__title = name_of_category(category)
|
||||||
.block__header
|
|
||||||
span.block__header__title = name_of_type(type)
|
|
||||||
|
|
||||||
div
|
div
|
||||||
= for notification <- notifications do
|
= 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
|
.block__header.block__header--light
|
||||||
a href=~p"/notifications/categories/#{type}"
|
a href=~p"/notifications/categories/#{category}"
|
||||||
| View category
|
| View category (
|
||||||
|
= notifications.total_entries
|
||||||
|
| )
|
||||||
|
|
||||||
- true ->
|
p
|
||||||
p
|
' To get notifications on new comments and forum posts, click the
|
||||||
' 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.
|
||||||
' 'Subscribe' button in the bar at the top of an image or forum topic.
|
' You'll get notifications here for any new posts or comments.
|
||||||
' You'll get notifications here for any new posts or comments.
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule PhilomenaWeb.Notification.CategoryView do
|
defmodule PhilomenaWeb.Notification.CategoryView do
|
||||||
use PhilomenaWeb, :view
|
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
|
end
|
||||||
|
|
|
@ -2,20 +2,20 @@ defmodule PhilomenaWeb.NotificationView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
@template_paths %{
|
@template_paths %{
|
||||||
"Channel" => "_channel.html",
|
"channel_live" => "_channel.html",
|
||||||
"Forum" => "_forum.html",
|
"forum_post" => "_post.html",
|
||||||
"Gallery" => "_gallery.html",
|
"forum_topic" => "_topic.html",
|
||||||
"Image" => "_image.html",
|
"gallery_image" => "_gallery.html",
|
||||||
"LivestreamChannel" => "_channel.html",
|
"image_comment" => "_comment.html",
|
||||||
"Topic" => "_topic.html"
|
"image_merge" => "_image.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
def notification_template_path(actor_type) do
|
def notification_template_path(category) do
|
||||||
@template_paths[actor_type]
|
@template_paths[to_string(category)]
|
||||||
end
|
end
|
||||||
|
|
||||||
def name_of_type(notification_type) do
|
def name_of_category(category) do
|
||||||
case notification_type do
|
case category do
|
||||||
:channel_live ->
|
:channel_live ->
|
||||||
"Live channels"
|
"Live channels"
|
||||||
|
|
||||||
|
|
109
priv/repo/migrations/20240728191353_new_notifications.exs
Normal file
109
priv/repo/migrations/20240728191353_new_notifications.exs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
defmodule Philomena.Repo.Migrations.NewNotifications do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@categories [
|
||||||
|
channel_live: [channels: :channel_id],
|
||||||
|
forum_post: [topics: :topic_id, posts: :post_id],
|
||||||
|
forum_topic: [topics: :topic_id],
|
||||||
|
gallery_image: [galleries: :gallery_id],
|
||||||
|
image_comment: [images: :image_id, comments: :comment_id],
|
||||||
|
image_merge: [images: :target_id, images: :source_id]
|
||||||
|
]
|
||||||
|
|
||||||
|
def up do
|
||||||
|
for {category, refs} <- @categories do
|
||||||
|
create table("#{category}_notifications", primary_key: false) do
|
||||||
|
for {target_table_name, reference_name} <- refs do
|
||||||
|
add reference_name, references(target_table_name, on_delete: :delete_all), null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||||
|
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||||
|
add :read, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
{_primary_table_name, primary_ref_name} = hd(refs)
|
||||||
|
create index("#{category}_notifications", [:user_id, primary_ref_name], unique: true)
|
||||||
|
create index("#{category}_notifications", [:user_id, "updated_at desc"])
|
||||||
|
create index("#{category}_notifications", [:user_id, :read])
|
||||||
|
|
||||||
|
for {_target_table_name, reference_name} <- refs do
|
||||||
|
create index("#{category}_notifications", [reference_name])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
insert_statements =
|
||||||
|
"""
|
||||||
|
insert into channel_live_notifications (channel_id, user_id, created_at, updated_at)
|
||||||
|
select n.actor_id, un.user_id, n.created_at, n.updated_at
|
||||||
|
from unread_notifications un
|
||||||
|
join notifications n on un.notification_id = n.id
|
||||||
|
where n.actor_type = 'Channel'
|
||||||
|
and exists(select 1 from channels c where c.id = n.actor_id)
|
||||||
|
and exists(select 1 from users u where u.id = un.user_id);
|
||||||
|
|
||||||
|
insert into forum_post_notifications (topic_id, post_id, user_id, created_at, updated_at)
|
||||||
|
select n.actor_id, n.actor_child_id, un.user_id, n.created_at, n.updated_at
|
||||||
|
from unread_notifications un
|
||||||
|
join notifications n on un.notification_id = n.id
|
||||||
|
where n.actor_type = 'Topic'
|
||||||
|
and n.actor_child_type = 'Post'
|
||||||
|
and n.action = 'posted a new reply in'
|
||||||
|
and exists(select 1 from topics t where t.id = n.actor_id)
|
||||||
|
and exists(select 1 from posts p where p.id = n.actor_child_id)
|
||||||
|
and exists(select 1 from users u where u.id = un.user_id);
|
||||||
|
|
||||||
|
insert into forum_topic_notifications (topic_id, user_id, created_at, updated_at)
|
||||||
|
select n.actor_id, un.user_id, n.created_at, n.updated_at
|
||||||
|
from unread_notifications un
|
||||||
|
join notifications n on un.notification_id = n.id
|
||||||
|
where n.actor_type = 'Topic'
|
||||||
|
and n.actor_child_type = 'Post'
|
||||||
|
and n.action <> 'posted a new reply in'
|
||||||
|
and exists(select 1 from topics t where t.id = n.actor_id)
|
||||||
|
and exists(select 1 from users u where u.id = un.user_id);
|
||||||
|
|
||||||
|
insert into gallery_image_notifications (gallery_id, user_id, created_at, updated_at)
|
||||||
|
select n.actor_id, un.user_id, n.created_at, n.updated_at
|
||||||
|
from unread_notifications un
|
||||||
|
join notifications n on un.notification_id = n.id
|
||||||
|
where n.actor_type = 'Gallery'
|
||||||
|
and exists(select 1 from galleries g where g.id = n.actor_id)
|
||||||
|
and exists(select 1 from users u where u.id = un.user_id);
|
||||||
|
|
||||||
|
insert into image_comment_notifications (image_id, comment_id, user_id, created_at, updated_at)
|
||||||
|
select n.actor_id, n.actor_child_id, un.user_id, n.created_at, n.updated_at
|
||||||
|
from unread_notifications un
|
||||||
|
join notifications n on un.notification_id = n.id
|
||||||
|
where n.actor_type = 'Image'
|
||||||
|
and n.actor_child_type = 'Comment'
|
||||||
|
and exists(select 1 from images i where i.id = n.actor_id)
|
||||||
|
and exists(select 1 from comments c where c.id = n.actor_child_id)
|
||||||
|
and exists(select 1 from users u where u.id = un.user_id);
|
||||||
|
|
||||||
|
insert into image_merge_notifications (target_id, source_id, user_id, created_at, updated_at)
|
||||||
|
select n.actor_id, regexp_replace(n.action, '[a-z#]+', '', 'g')::bigint, un.user_id, n.created_at, n.updated_at
|
||||||
|
from unread_notifications un
|
||||||
|
join notifications n on un.notification_id = n.id
|
||||||
|
where n.actor_type = 'Image'
|
||||||
|
and n.actor_child_type is null
|
||||||
|
and exists(select 1 from images i where i.id = n.actor_id)
|
||||||
|
and exists(select 1 from images i where i.id = regexp_replace(n.action, '[a-z#]+', '', 'g')::integer)
|
||||||
|
and exists(select 1 from users u where u.id = un.user_id);
|
||||||
|
"""
|
||||||
|
|
||||||
|
# These statements should not be run by the migration in production.
|
||||||
|
# Run them manually in psql instead.
|
||||||
|
if System.get_env("MIX_ENV") != "prod" do
|
||||||
|
for stmt <- String.split(insert_statements, "\n\n") do
|
||||||
|
execute(stmt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
for {category, _refs} <- @categories do
|
||||||
|
drop table("#{category}_notifications")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -198,6 +198,19 @@ CREATE SEQUENCE public.badges_id_seq
|
||||||
ALTER SEQUENCE public.badges_id_seq OWNED BY public.badges.id;
|
ALTER SEQUENCE public.badges_id_seq OWNED BY public.badges.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.channel_live_notifications (
|
||||||
|
channel_id bigint NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
created_at timestamp(0) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(0) without time zone NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: channel_subscriptions; Type: TABLE; Schema: public; Owner: -
|
-- Name: channel_subscriptions; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -620,6 +633,20 @@ CREATE SEQUENCE public.fingerprint_bans_id_seq
|
||||||
ALTER SEQUENCE public.fingerprint_bans_id_seq OWNED BY public.fingerprint_bans.id;
|
ALTER SEQUENCE public.fingerprint_bans_id_seq OWNED BY public.fingerprint_bans.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.forum_post_notifications (
|
||||||
|
topic_id bigint NOT NULL,
|
||||||
|
post_id bigint NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
created_at timestamp(0) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(0) without time zone NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: forum_subscriptions; Type: TABLE; Schema: public; Owner: -
|
-- Name: forum_subscriptions; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -630,6 +657,19 @@ CREATE TABLE public.forum_subscriptions (
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.forum_topic_notifications (
|
||||||
|
topic_id bigint NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
created_at timestamp(0) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(0) without time zone NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: forums; Type: TABLE; Schema: public; Owner: -
|
-- Name: forums; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -709,6 +749,19 @@ CREATE SEQUENCE public.galleries_id_seq
|
||||||
ALTER SEQUENCE public.galleries_id_seq OWNED BY public.galleries.id;
|
ALTER SEQUENCE public.galleries_id_seq OWNED BY public.galleries.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.gallery_image_notifications (
|
||||||
|
gallery_id bigint NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
created_at timestamp(0) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(0) without time zone NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: gallery_interactions; Type: TABLE; Schema: public; Owner: -
|
-- Name: gallery_interactions; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -750,6 +803,20 @@ CREATE TABLE public.gallery_subscriptions (
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.image_comment_notifications (
|
||||||
|
image_id bigint NOT NULL,
|
||||||
|
comment_id bigint NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
created_at timestamp(0) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(0) without time zone NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: image_faves; Type: TABLE; Schema: public; Owner: -
|
-- Name: image_faves; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -837,6 +904,20 @@ CREATE SEQUENCE public.image_intensities_id_seq
|
||||||
ALTER SEQUENCE public.image_intensities_id_seq OWNED BY public.image_intensities.id;
|
ALTER SEQUENCE public.image_intensities_id_seq OWNED BY public.image_intensities.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.image_merge_notifications (
|
||||||
|
target_id bigint NOT NULL,
|
||||||
|
source_id bigint NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
created_at timestamp(0) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(0) without time zone NOT NULL,
|
||||||
|
read boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: image_sources; Type: TABLE; Schema: public; Owner: -
|
-- Name: image_sources; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -2894,6 +2975,160 @@ ALTER TABLE ONLY public.versions
|
||||||
ADD CONSTRAINT versions_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT versions_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications_channel_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX channel_live_notifications_channel_id_index ON public.channel_live_notifications USING btree (channel_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications_user_id_channel_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX channel_live_notifications_user_id_channel_id_index ON public.channel_live_notifications USING btree (user_id, channel_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications_user_id_read_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX channel_live_notifications_user_id_read_index ON public.channel_live_notifications USING btree (user_id, read);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications_user_id_updated_at_desc_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX channel_live_notifications_user_id_updated_at_desc_index ON public.channel_live_notifications USING btree (user_id, updated_at DESC);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications_post_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_post_notifications_post_id_index ON public.forum_post_notifications USING btree (post_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications_topic_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_post_notifications_topic_id_index ON public.forum_post_notifications USING btree (topic_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications_user_id_read_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_post_notifications_user_id_read_index ON public.forum_post_notifications USING btree (user_id, read);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications_user_id_topic_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX forum_post_notifications_user_id_topic_id_index ON public.forum_post_notifications USING btree (user_id, topic_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications_user_id_updated_at_desc_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_post_notifications_user_id_updated_at_desc_index ON public.forum_post_notifications USING btree (user_id, updated_at DESC);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications_topic_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_topic_notifications_topic_id_index ON public.forum_topic_notifications USING btree (topic_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications_user_id_read_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_topic_notifications_user_id_read_index ON public.forum_topic_notifications USING btree (user_id, read);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications_user_id_topic_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX forum_topic_notifications_user_id_topic_id_index ON public.forum_topic_notifications USING btree (user_id, topic_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications_user_id_updated_at_desc_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX forum_topic_notifications_user_id_updated_at_desc_index ON public.forum_topic_notifications USING btree (user_id, updated_at DESC);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications_gallery_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX gallery_image_notifications_gallery_id_index ON public.gallery_image_notifications USING btree (gallery_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications_user_id_gallery_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX gallery_image_notifications_user_id_gallery_id_index ON public.gallery_image_notifications USING btree (user_id, gallery_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications_user_id_read_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX gallery_image_notifications_user_id_read_index ON public.gallery_image_notifications USING btree (user_id, read);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications_user_id_updated_at_desc_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX gallery_image_notifications_user_id_updated_at_desc_index ON public.gallery_image_notifications USING btree (user_id, updated_at DESC);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications_comment_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_comment_notifications_comment_id_index ON public.image_comment_notifications USING btree (comment_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications_image_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_comment_notifications_image_id_index ON public.image_comment_notifications USING btree (image_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications_user_id_image_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX image_comment_notifications_user_id_image_id_index ON public.image_comment_notifications USING btree (user_id, image_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications_user_id_read_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_comment_notifications_user_id_read_index ON public.image_comment_notifications USING btree (user_id, read);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications_user_id_updated_at_desc_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_comment_notifications_user_id_updated_at_desc_index ON public.image_comment_notifications USING btree (user_id, updated_at DESC);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: image_intensities_index; Type: INDEX; Schema: public; Owner: -
|
-- Name: image_intensities_index; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -2901,6 +3136,41 @@ ALTER TABLE ONLY public.versions
|
||||||
CREATE INDEX image_intensities_index ON public.image_intensities USING btree (nw, ne, sw, se);
|
CREATE INDEX image_intensities_index ON public.image_intensities USING btree (nw, ne, sw, se);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications_source_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_merge_notifications_source_id_index ON public.image_merge_notifications USING btree (source_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications_target_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_merge_notifications_target_id_index ON public.image_merge_notifications USING btree (target_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications_user_id_read_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_merge_notifications_user_id_read_index ON public.image_merge_notifications USING btree (user_id, read);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications_user_id_target_id_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX image_merge_notifications_user_id_target_id_index ON public.image_merge_notifications USING btree (user_id, target_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications_user_id_updated_at_desc_index; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX image_merge_notifications_user_id_updated_at_desc_index ON public.image_merge_notifications USING btree (user_id, updated_at DESC);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: image_sources_image_id_source_index; Type: INDEX; Schema: public; Owner: -
|
-- Name: image_sources_image_id_source_index; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -4175,6 +4445,22 @@ CREATE UNIQUE INDEX user_tokens_context_token_index ON public.user_tokens USING
|
||||||
CREATE INDEX user_tokens_user_id_index ON public.user_tokens USING btree (user_id);
|
CREATE INDEX user_tokens_user_id_index ON public.user_tokens USING btree (user_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications channel_live_notifications_channel_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.channel_live_notifications
|
||||||
|
ADD CONSTRAINT channel_live_notifications_channel_id_fkey FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: channel_live_notifications channel_live_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.channel_live_notifications
|
||||||
|
ADD CONSTRAINT channel_live_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: channels fk_rails_021c624081; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: channels fk_rails_021c624081; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -4967,6 +5253,110 @@ ALTER TABLE ONLY public.gallery_subscriptions
|
||||||
ADD CONSTRAINT fk_rails_fa77f3cebe FOREIGN KEY (gallery_id) REFERENCES public.galleries(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
ADD CONSTRAINT fk_rails_fa77f3cebe FOREIGN KEY (gallery_id) REFERENCES public.galleries(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications forum_post_notifications_post_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.forum_post_notifications
|
||||||
|
ADD CONSTRAINT forum_post_notifications_post_id_fkey FOREIGN KEY (post_id) REFERENCES public.posts(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications forum_post_notifications_topic_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.forum_post_notifications
|
||||||
|
ADD CONSTRAINT forum_post_notifications_topic_id_fkey FOREIGN KEY (topic_id) REFERENCES public.topics(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_post_notifications forum_post_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.forum_post_notifications
|
||||||
|
ADD CONSTRAINT forum_post_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications forum_topic_notifications_topic_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.forum_topic_notifications
|
||||||
|
ADD CONSTRAINT forum_topic_notifications_topic_id_fkey FOREIGN KEY (topic_id) REFERENCES public.topics(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: forum_topic_notifications forum_topic_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.forum_topic_notifications
|
||||||
|
ADD CONSTRAINT forum_topic_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications gallery_image_notifications_gallery_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.gallery_image_notifications
|
||||||
|
ADD CONSTRAINT gallery_image_notifications_gallery_id_fkey FOREIGN KEY (gallery_id) REFERENCES public.galleries(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: gallery_image_notifications gallery_image_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.gallery_image_notifications
|
||||||
|
ADD CONSTRAINT gallery_image_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications image_comment_notifications_comment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.image_comment_notifications
|
||||||
|
ADD CONSTRAINT image_comment_notifications_comment_id_fkey FOREIGN KEY (comment_id) REFERENCES public.comments(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications image_comment_notifications_image_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.image_comment_notifications
|
||||||
|
ADD CONSTRAINT image_comment_notifications_image_id_fkey FOREIGN KEY (image_id) REFERENCES public.images(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_comment_notifications image_comment_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.image_comment_notifications
|
||||||
|
ADD CONSTRAINT image_comment_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications image_merge_notifications_source_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.image_merge_notifications
|
||||||
|
ADD CONSTRAINT image_merge_notifications_source_id_fkey FOREIGN KEY (source_id) REFERENCES public.images(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications image_merge_notifications_target_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.image_merge_notifications
|
||||||
|
ADD CONSTRAINT image_merge_notifications_target_id_fkey FOREIGN KEY (target_id) REFERENCES public.images(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: image_merge_notifications image_merge_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.image_merge_notifications
|
||||||
|
ADD CONSTRAINT image_merge_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: image_sources image_sources_image_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: image_sources image_sources_image_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -5056,3 +5446,4 @@ INSERT INTO public."schema_migrations" (version) VALUES (20211107130226);
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20211219194836);
|
INSERT INTO public."schema_migrations" (version) VALUES (20211219194836);
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20220321173359);
|
INSERT INTO public."schema_migrations" (version) VALUES (20220321173359);
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20240723122759);
|
INSERT INTO public."schema_migrations" (version) VALUES (20240723122759);
|
||||||
|
INSERT INTO public."schema_migrations" (version) VALUES (20240728191353);
|
||||||
|
|
Loading…
Reference in a new issue