defmodule Ami.Notification do use Ecto.Schema import Ecto.{ Query, Changeset } alias Ami.{ User, Repo, Share, Community, CoursePeriod, Enrollment, Like, Comment, Post, Lesson } schema "notifications" do field(:category, :string) field(:title, :string) field(:body, :string) field(:icon, :string) field(:unread, :boolean, default: true) field(:broadcasted, :boolean) field(:created_at, :utc_datetime) field(:updated_at, :utc_datetime) belongs_to(:user, User) end def unread do __MODULE__ |> Ecto.Query.where(unread: true) end def unread(query) do query |> Ecto.Query.where(unread: true) end def or_unread(query) do query |> Ecto.Query.or_where(unread: true) end def read do __MODULE__ |> Ecto.Query.where(unread: false) end def read(query) do query |> Ecto.Query.where(unread: false) end def for_user(user_id) do __MODULE__ |> Ecto.Query.where(user_id: ^user_id) end def for_user(query, user_id) do query |> Ecto.Query.where(user_id: ^user_id) end def with_limit(limit) do __MODULE__ |> Ecto.Query.limit(^limit) end def with_limit(query, limit) do query |> Ecto.Query.limit(^limit) end def order_by_creation_date(query) do query |> Ecto.Query.order_by(desc: :created_at) end def exclude_ids(query, ids) do from(n in query, where: n.id not in ^ids) end def for_user_navbar(user_id, limit \\ 5) do query = for_user(user_id) |> unread() |> with_limit(limit) |> order_by_creation_date() Repo.all(query) end def unread_count_for_user(user_id) do from( n in __MODULE__, where: n.unread == true and n.user_id == ^user_id, select: count(n.id) ) |> Repo.one() end def latest_activities_for(user) do from( n in __MODULE__, where: n.user_id == ^user.id, where: n.category in ["Post", "Comment", "Course", "Community"], order_by: [desc: :created_at], limit: 5 ) |> Repo.all() end def clear_old_notifications_for_user(user_id, time_in_month \\ 1) do from( n in __MODULE__, where: n.user_id == ^user_id, where: n.created_at <= ^Timex.shift(Timex.now(), months: -time_in_month) ) |> Repo.delete_all() end def send(notification) do AmiWeb.Endpoint.broadcast!("notification:#{notification.user_id}", "new_notification", %{ notification_id: notification.id }) notification end def create(%{} = params) do %__MODULE__{} |> cast(params, [:user_id, :category, :title, :icon, :body]) |> cast(%{created_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}, [ :created_at, :updated_at ]) |> validate_required([:user_id, :body]) |> Repo.insert() end def notify_of_post_sharing(%{ post_id: post_id, message: message, recipient_id: "#all", user_id: user_id, community: community, socket: socket }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) Community.community_members(community) |> Enum.each(fn recipient -> post_url = "/my_resources/#{recipient.id}/community/#{community.id}##{post_id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, socket) notification = %{ user_id: recipient.id, category: "Post", title: "Share", icon: profile_pic_url, body: """ #{User.user_name(author)} thinks you would like this Post in #{community.title}. “#{ message }“. You can see and comment on the post here. . """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Post", post_id, user_id) end) share_count = share_count_for(post_id, "Post") {:ok, share_count, post_id} end def notify_of_post_sharing(%{ post_id: post_id, message: message, recipient_id: recipient_id, user_id: user_id, community: community, socket: socket }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) recipient = Repo.one(from(u in User, where: u.uuid == ^recipient_id)) post_url = "/my_resources/#{recipient.id}/community/#{community.id}##{post_id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, socket) notification = %{ user_id: recipient.id, category: "Post", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Post in #{community.title}. “#{ message }“. You can see and comment on the post here. . """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Post", post_id, user_id) share_count = share_count_for(post_id, "Post") {:ok, share_count, post_id} end def notify_of_post_sharing(%{ post_id: post_id, message: message, recipient_id: "#all", user_id: user_id, course_period_id: course_period_id, lesson: lesson, socket: socket }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) course_period = Repo.get(CoursePeriod, course_period_id) |> Repo.preload(:course) recipients = CoursePeriod.students_for(course_period) |> Enum.map(fn enr -> enr.user end) Enum.map(recipients, fn recipient -> enrollment = Repo.one( from( e in Enrollment, where: e.user_id == ^recipient.id and e.course_period_id == ^course_period_id ) ) post_url = "/course/#{recipient.id}/#{course_period.course_id}/#{enrollment.id}/show?lesson_id=#{ lesson.id }" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, socket) notification = %{ user_id: recipient.id, category: "Post", title: "Share", icon: profile_pic_url, body: """ #{User.user_name(author)} thinks you would like this Post in #{ course_period.course.title }, lesson: #{lesson.title}. “#{message}“. You can see and comment on the post here. . """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Post", post_id, user_id) end) share_count = share_count_for(post_id, "Post") {:ok, share_count, post_id} end def notify_of_post_sharing(%{ post_id: post_id, message: message, recipient_id: recipient_id, user_id: user_id, course_period_id: course_period_id, lesson: lesson, socket: socket }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) course_period = Repo.get(CoursePeriod, course_period_id) |> Repo.preload(:course) recipient = Ami.Repo.one(from(u in Ami.User, where: u.uuid == ^recipient_id)) enrollment = Repo.one( from( e in Enrollment, where: e.user_id == ^recipient.id and e.course_period_id == ^course_period_id ) ) post_url = "/course/#{recipient.id}/#{course_period.course_id}/#{enrollment.id}/show?lesson_id=#{ lesson.id }" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, socket) notification = %{ user_id: recipient.id, category: "Post", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Post in #{ course_period.course.title }, lesson: #{lesson.title}. “#{message}“. You can see and comment on the post here. . """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Post", post_id, user_id) share_count = share_count_for(post_id, "Post") {:ok, share_count, post_id} end def notify_of_liking(%Like{likeable_type: "Post"} = like, %{ recipient_id: recipient_id, context: %Community{} = community, socket: socket }) do post_url = "/my_resources/#{recipient_id}/community/#{community.id}##{like.likeable_id}" profile_pic_url = AmiWeb.LayoutView.user_image(like.user.profile, socket) %{ user_id: recipient_id, category: "Post", title: "Like", icon: profile_pic_url, body: "#{User.user_name(like.user)} liked your post in #{ community.title }" } |> create_and_send() end def notify_of_liking(%Like{likeable_type: "Post"} = like, %{ recipient_id: recipient_id, context: %Lesson{} = lesson, socket: socket }) do post = Repo.get(Post, like.likeable_id) |> Repo.preload(course_period: :course) enrollment = Repo.one( from( e in Enrollment, where: e.user_id == ^recipient_id and e.course_period_id == ^post.course_period_id ) ) post_url = "/course/#{recipient_id}/#{post.course_period.course_id}/#{enrollment.id}/show?lesson_id=#{ lesson.id }" profile_pic_url = AmiWeb.LayoutView.user_image(like.user.profile, socket) %{ user_id: recipient_id, category: "Post", title: "Like", icon: profile_pic_url, body: "#{User.user_name(like.user)} liked your post in #{ post.course_period.course.title } course - lesson: #{lesson.title}" } |> create_and_send() end def notify_of_liking(%Like{likeable_type: "Comment"} = like, %{ recipient_id: recipient_id, context: %Community{} = community, socket: socket }) do profile_pic_url = AmiWeb.LayoutView.user_image(like.user.profile, socket) %{ user_id: recipient_id, category: "Comment", title: "Like", icon: profile_pic_url, body: "#{User.user_name(like.user)} liked your comment in #{community.title} community wall." } |> create_and_send() end def notify_of_liking(%Like{likeable_type: "Comment"} = like, %{ recipient_id: recipient_id, context: %Lesson{} = lesson, socket: socket }) do profile_pic_url = AmiWeb.LayoutView.user_image(like.user.profile, socket) %{ user_id: recipient_id, category: "Comment", title: "Like", icon: profile_pic_url, body: "#{User.user_name(like.user)} liked your comment in #{lesson.title} lesson." } |> create_and_send() end def notify_of_commenting(%Comment{commentable_type: "Post"} = comment, %{ recipient_id: recipient_id, context: %Community{} = community, socket: socket }) do profile_pic_url = AmiWeb.LayoutView.user_image(comment.user.profile, socket) post_url = "/my_resources/#{recipient_id}/community/#{community.id}##{comment.commentable_id}" %{ user_id: recipient_id, category: "Post", title: "Comment", icon: profile_pic_url, body: "#{User.user_name(comment.user)} has commented on your post in #{community.title} community wall - reply now!" } |> create_and_send() end def notify_of_commenting(%Comment{commentable_type: "Post"} = comment, %{ recipient_id: recipient_id, context: %Lesson{} = lesson, socket: socket }) do profile_pic_url = AmiWeb.LayoutView.user_image(comment.user.profile, socket) post = Repo.get(Post, comment.commentable_id) |> Repo.preload(course_period: :course) %{ user_id: recipient_id, category: "Post", title: "Comment", icon: profile_pic_url, body: "#{User.user_name(comment.user)} has commented on your post in #{ post.course_period.course.title } course - lesson: #{lesson.title}" } |> create_and_send() end def notify_of_tool_sharing(%{ tool_id: tool_id, message: message, recipient_id: "#all", user_id: user_id, community: community, conn: conn }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) Community.community_members(community) |> Enum.each(fn recipient -> tool_url = "/my_resources/#{recipient.id}/tool/#{tool_id}?community_id=#{community.id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, conn) notification = %{ user_id: recipient.id, category: "Tool", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Tool in #{community.title}. “#{ message }“. You can see the tool here. """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Tool", tool_id, user_id) end) share_count = share_count_for(tool_id, "Tool") {:ok, share_count, tool_id} end def notify_of_tool_sharing(%{ tool_id: tool_id, message: message, recipient_id: recipient_id, user_id: user_id, community: %Community{} = community, conn: conn }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) recipient = Repo.one(from(u in User, where: u.uuid == ^recipient_id)) tool_url = "/my_resources/#{recipient.id}/tool/#{tool_id}?community_id=#{community.id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, conn) notification = %{ user_id: recipient.id, category: "Tool", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Tool in #{community.title}. “#{ message }“. You can see the tool here. """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Tool", tool_id, user_id) share_count = share_count_for(tool_id, "Tool") {:ok, share_count, tool_id} end def notify_of_tool_sharing(%{ tool_id: tool_id, message: message, user_id: user_id, recipient_id: recipient_id, conn: conn }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) recipient = Repo.one(from(u in User, where: u.uuid == ^recipient_id)) tool_url = "/my_resources/#{recipient.id}/tool/#{tool_id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, conn) notification = %{ user_id: recipient.id, category: "Tool", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Tool. “#{message}“. You can see the tool here. """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("Tool", tool_id, user_id) share_count = share_count_for(tool_id, "Tool") {:ok, share_count, tool_id} end def notify_of_toolkit_sharing(%{ toolkit_id: toolkit_id, message: message, recipient_id: "#all", user_id: user_id, community: community, conn: conn }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) Community.community_members(community) |> Enum.each(fn recipient -> toolkit_url = "/my_resources/#{recipient.id}/toolkit/#{toolkit_id}?community_id=#{community.id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, conn) notification = %{ user_id: recipient.id, category: "Toolkit", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Toolkit in #{community.title}. “#{ message }“. You can check it here. """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("ToolKit", toolkit_id, user_id) end) share_count = share_count_for(toolkit_id, "ToolKit") {:ok, share_count, toolkit_id} end def notify_of_toolkit_sharing(%{ toolkit_id: toolkit_id, message: message, recipient_id: recipient_id, user_id: user_id, community: community, conn: conn }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) recipient = Repo.one(from(u in User, where: u.uuid == ^recipient_id)) toolkit_url = "/my_resources/#{recipient.id}/toolkit/#{toolkit_id}?community_id=#{community.id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, conn) notification = %{ user_id: recipient.id, category: "Toolkit", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Toolkit in #{community.title}. “#{ message }“. You can check it here. """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("ToolKit", toolkit_id, user_id) share_count = share_count_for(toolkit_id, "ToolKit") {:ok, share_count, toolkit_id} end def notify_of_toolkit_sharing(%{ toolkit_id: toolkit_id, message: message, recipient_id: recipient_id, user_id: user_id, conn: conn }) do author = Repo.get(User, user_id) |> Repo.preload(:profile) recipient = Repo.one(from(u in User, where: u.uuid == ^recipient_id)) toolkit_url = "/my_resources/#{recipient.id}/toolkit/#{toolkit_id}" profile_pic_url = AmiWeb.LayoutView.user_image(author.profile, conn) notification = %{ user_id: recipient.id, category: "Toolkit", title: "Share", icon: profile_pic_url, body: """ #{Ami.User.user_name(author)} thinks you would like this Toolkit. “#{message}“. You can check it here. """ } |> create_and_send() if %__MODULE__{} = notification, do: create_share("ToolKit", toolkit_id, user_id) share_count = share_count_for(toolkit_id, "ToolKit") {:ok, share_count, toolkit_id} end def create_and_send(notification_params) do case __MODULE__.create(notification_params) do {:ok, notification} -> __MODULE__.send(notification) {:error, changeset} -> {:error, changeset} end end def create_and_send?(notification_params) do beginning_of_day = Timex.beginning_of_day(DateTime.utc_now()) end_of_day = Timex.end_of_day(DateTime.utc_now()) count = Ami.Repo.one( from( n in __MODULE__, where: n.user_id == ^notification_params[:user_id], where: n.category == ^notification_params[:category], where: n.title == ^notification_params[:title], where: n.body == ^notification_params[:body], where: n.created_at >= ^beginning_of_day and n.created_at <= ^end_of_day, select: count(n.id) ) ) if count == 0, do: create_and_send(notification_params) end defp share_count_for(id, type) do Repo.one( from( s in Ami.Share, where: s.shareable_type == ^type and s.shareable_id == ^id, select: count(s.id) ) ) end defp create_share(type, resource_id, user_id) do Share.create_share(%{ shareable_type: type, shareable_id: resource_id, user_id: user_id, created_at: DateTime.utc_now(), updated_at: DateTime.utc_now() }) end end