defmodule Claper.Posts do @moduledoc """ The Posts context. """ import Ecto.Query, warn: false alias Claper.Repo alias Claper.Posts.Post @doc """ Get event posts """ def list_posts(event_id, preload \\ []) do from(p in Post, join: e in Claper.Events.Event, on: p.event_id == e.id, select: p, where: e.uuid == ^event_id, order_by: [asc: p.id] ) |> Repo.all() |> Repo.preload(preload) end @doc """ Get event posts which are questions """ def list_questions(event_id, preload \\ [], sort \\ :date) do query = from(p in Post, join: e in assoc(p, :event), where: e.uuid == ^event_id and like(p.body, "%?%") ) query = case sort do :likes -> from(p in query, order_by: [desc: p.like_count]) _ -> from(p in query, order_by: [asc: p.id]) end query |> Repo.all() |> Repo.preload(preload) end @doc """ Get only the pinned event posts. """ def list_pinned_posts(event_id, preload \\ []) do from(p in Post, join: e in Claper.Events.Event, on: p.event_id == e.id, select: p, # Only pinned posts where: e.uuid == ^event_id and p.pinned == true, order_by: [asc: p.id] ) |> Repo.all() |> Repo.preload(preload) end @doc """ Get only the unpinned event posts. """ def list_unpinned_posts(event_id, preload \\ []) do from(p in Post, join: e in Claper.Events.Event, on: p.event_id == e.id, select: p, # Only unpinned posts where: e.uuid == ^event_id and p.pinned == false, order_by: [asc: p.id] ) |> Repo.all() |> Repo.preload(preload) end def reacted_posts(event_id, user_id, icon) when is_number(user_id) do from(reaction in Claper.Posts.Reaction, join: post in Claper.Posts.Post, on: reaction.post_id == post.id, where: reaction.icon == ^icon and reaction.user_id == ^user_id and post.event_id == ^event_id, distinct: true, select: reaction.post_id ) |> Repo.all() end def reacted_posts(event_id, attendee_identifier, icon) do from(reaction in Claper.Posts.Reaction, join: post in Claper.Posts.Post, on: reaction.post_id == post.id, where: reaction.icon == ^icon and reaction.attendee_identifier == ^attendee_identifier and post.event_id == ^event_id, distinct: true, select: reaction.post_id ) |> Repo.all() end @doc """ Gets a single post. Raises `Ecto.NoResultsError` if the Post does not exist. ## Examples iex> get_post!("123e4567-e89b-12d3-a456-426614174000") %Post{} iex> get_post!("123e4567-e89b-12d3-a456-426614174123") ** (Ecto.NoResultsError) """ def get_post!(id, preload \\ []), do: Repo.get_by!(Post, uuid: id) |> Repo.preload(preload) @doc """ Creates a post. ## Examples iex> create_post(event, %{field: value}) {:ok, %Post{}} iex> create_post(event, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_post(event, attrs) do %Post{} |> Map.put(:event, event) |> Post.changeset(attrs) |> Repo.insert(returning: [:uuid]) |> broadcast(:post_created) end @doc """ Updates a post. ## Examples iex> update_post(post, %{field: new_value}) {:ok, %Post{}} iex> update_post(post, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_post(%Post{} = post, attrs) do changeset = Post.changeset(post, attrs) result = changeset |> Repo.update() result |> broadcast(:post_updated) end @doc """ Pins or unpins a post based on its current state. ## Examples iex> toggle_pin_post(post) {:ok, %Post{}} iex> toggle_pin_post(invalid_post) {:error, %Ecto.Changeset{}} """ def toggle_pin_post(%Post{} = post) do # Toggling the pinned state new_pinned_state = not post.pinned changeset = Post.changeset(post, %{pinned: new_pinned_state}) result = changeset |> Repo.update() # Broadcast the appropriate message based on the new state broadcast_message = if new_pinned_state do :post_pinned else :post_unpinned end result |> broadcast(broadcast_message) end @doc """ Deletes a post. ## Examples iex> delete_post(post) {:ok, %Post{}} iex> delete_post(post) {:error, %Ecto.Changeset{}} """ def delete_post(%Post{} = post) do post |> Repo.delete() |> broadcast(:post_deleted) end def delete_all_posts(:attendee_identifier, attendee_identifier, event) do posts = from(post in Claper.Posts.Post, where: post.attendee_identifier == ^attendee_identifier and post.event_id == ^event.id ) |> Repo.all() for post <- posts do delete_post(%{post | event: event}) end end def delete_all_posts(:user_id, user_id, event) do posts = from(post in Claper.Posts.Post, where: post.user_id == ^user_id and post.event_id == ^event.id ) |> Repo.all() for post <- posts do delete_post(%{post | event: event}) end end alias Claper.Posts.{Reaction, Post} @doc """ Gets a single reaction. Raises `Ecto.NoResultsError` if the Reaction does not exist. ## Examples iex> get_reaction!(123) %Reaction{} iex> get_reaction!(456) ** (Ecto.NoResultsError) """ def get_reaction!(id), do: Repo.get!(Reaction, id) @doc """ Creates a reaction. ## Examples iex> create_reaction(%{field: value}) {:ok, %Reaction{}} iex> create_reaction(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_reaction(%{post: nil} = attrs), do: create_reaction(%{attrs | post: %Post{}}) def create_reaction(%{post: post} = attrs) do case %Reaction{} |> Map.put(:post_id, post.id) |> Reaction.changeset(attrs) |> Repo.insert() do {:ok, reaction} -> broadcast({:ok, post}, :reaction_added) {:ok, reaction} {:error, changeset} -> {:error, changeset} end end @doc """ Deletes a reaction. ## Examples iex> delete_reaction(reaction) {:ok, %Reaction{}} iex> delete_reaction(reaction) {:error, %Ecto.Changeset{}} """ def delete_reaction(%{user_id: user_id, post: post, icon: icon} = _params) when is_integer(user_id) do with reaction <- Repo.get_by!(Reaction, post_id: post.id, user_id: user_id, icon: icon) do Repo.delete(reaction) broadcast({:ok, post}, :reaction_removed) end end def delete_reaction( %{attendee_identifier: attendee_identifier, post: post, icon: icon} = _params ) do with reaction <- Repo.get_by!(Reaction, post_id: post.id, attendee_identifier: attendee_identifier, icon: icon ) do Repo.delete(reaction) broadcast({:ok, post}, :reaction_removed) end end defp broadcast({:error, _reason} = error, _event), do: error defp broadcast({:ok, post}, event) do Phoenix.PubSub.broadcast(Claper.PubSub, "event:#{post.event.uuid}", {event, post}) {:ok, post} end end