mirror of
https://github.com/ClaperCo/Claper.git
synced 2026-09-02 03:59:46 +02:00
Add event-scoped retrieval functions for embeds, forms, polls, posts, and quizzes
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
- Fix stored XSS vulnerability in custom embed iframes via input sanitization with attribute whitelisting
|
- Fix stored XSS vulnerability in custom embed iframes via input sanitization with attribute whitelisting
|
||||||
- Fix XSS vulnerability in URL link formatting by escaping user-submitted URLs
|
- Fix XSS vulnerability in URL link formatting by escaping user-submitted URLs
|
||||||
- Fix IDOR on form export endpoint by adding authorization check
|
- Fix IDOR on form export endpoint by adding authorization check
|
||||||
|
- Fix cross-event IDOR on polls, quizzes, forms, embeds, and posts by enforcing event-scoped resource access in context layer
|
||||||
- Fix atom exhaustion DoS by replacing `String.to_atom/1` on user input with explicit whitelists (8 locations)
|
- Fix atom exhaustion DoS by replacing `String.to_atom/1` on user input with explicit whitelists (8 locations)
|
||||||
- Add rate limiting on authentication endpoints using Hammer 7.0
|
- Add rate limiting on authentication endpoints using Hammer 7.0
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,23 @@ defmodule Claper.Embeds do
|
|||||||
def get_embed!(id, preload \\ []),
|
def get_embed!(id, preload \\ []),
|
||||||
do: Repo.get!(Embed, id) |> Repo.preload(preload)
|
do: Repo.get!(Embed, id) |> Repo.preload(preload)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single embed scoped to the given event.
|
||||||
|
|
||||||
|
Returns `nil` if the embed does not exist or does not belong to the event.
|
||||||
|
"""
|
||||||
|
def get_embed_for_event(id, event_id, preload \\ []) do
|
||||||
|
from(e in Embed,
|
||||||
|
join: pf in assoc(e, :presentation_file),
|
||||||
|
where: e.id == ^id and pf.event_id == ^event_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil -> nil
|
||||||
|
embed -> Repo.preload(embed, preload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single embed for a given position.
|
Gets a single embed for a given position.
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,23 @@ defmodule Claper.Forms do
|
|||||||
def get_form!(id, preload \\ []),
|
def get_form!(id, preload \\ []),
|
||||||
do: Repo.get!(Form, id) |> Repo.preload(preload)
|
do: Repo.get!(Form, id) |> Repo.preload(preload)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single form scoped to the given event.
|
||||||
|
|
||||||
|
Returns `nil` if the form does not exist or does not belong to the event.
|
||||||
|
"""
|
||||||
|
def get_form_for_event(id, event_id, preload \\ []) do
|
||||||
|
from(f in Form,
|
||||||
|
join: pf in assoc(f, :presentation_file),
|
||||||
|
where: f.id == ^id and pf.event_id == ^event_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil -> nil
|
||||||
|
form -> Repo.preload(form, preload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single form for a given position.
|
Gets a single form for a given position.
|
||||||
|
|
||||||
@@ -280,6 +297,20 @@ defmodule Claper.Forms do
|
|||||||
def get_form_submit_by_id!(id, preload \\ []),
|
def get_form_submit_by_id!(id, preload \\ []),
|
||||||
do: Repo.get_by!(FormSubmit, id: id) |> Repo.preload(preload)
|
do: Repo.get_by!(FormSubmit, id: id) |> Repo.preload(preload)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single FormSubmit scoped to the given event.
|
||||||
|
|
||||||
|
Returns `nil` if the FormSubmit does not exist or does not belong to the event.
|
||||||
|
"""
|
||||||
|
def get_form_submit_for_event(id, event_id) do
|
||||||
|
from(fs in FormSubmit,
|
||||||
|
join: f in assoc(fs, :form),
|
||||||
|
join: pf in assoc(f, :presentation_file),
|
||||||
|
where: fs.id == ^id and pf.event_id == ^event_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates or update a FormSubmit.
|
Creates or update a FormSubmit.
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,34 @@ defmodule Claper.Polls do
|
|||||||
)
|
)
|
||||||
|> set_percentages()
|
|> set_percentages()
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single poll scoped to the given event.
|
||||||
|
|
||||||
|
Returns `nil` if the poll does not exist or does not belong to the event.
|
||||||
|
"""
|
||||||
|
def get_poll_for_event(id, event_id) do
|
||||||
|
from(p in Poll,
|
||||||
|
join: pf in assoc(p, :presentation_file),
|
||||||
|
where: p.id == ^id and pf.event_id == ^event_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
poll ->
|
||||||
|
poll
|
||||||
|
|> Repo.preload(
|
||||||
|
poll_opts:
|
||||||
|
from(
|
||||||
|
o in PollOpt,
|
||||||
|
order_by: [asc: o.id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> set_percentages()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single poll for a given position.
|
Gets a single poll for a given position.
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,22 @@ defmodule Claper.Posts do
|
|||||||
"""
|
"""
|
||||||
def get_post!(id, preload \\ []), do: Repo.get_by!(Post, uuid: id) |> Repo.preload(preload)
|
def get_post!(id, preload \\ []), do: Repo.get_by!(Post, uuid: id) |> Repo.preload(preload)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single post scoped to the given event.
|
||||||
|
|
||||||
|
Returns `nil` if the post does not exist or does not belong to the event.
|
||||||
|
"""
|
||||||
|
def get_post_for_event(uuid, event_id, preload \\ []) do
|
||||||
|
from(p in Post,
|
||||||
|
where: p.uuid == ^uuid and p.event_id == ^event_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil -> nil
|
||||||
|
post -> Repo.preload(post, preload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a post.
|
Creates a post.
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,23 @@ defmodule Claper.Quizzes do
|
|||||||
|> set_percentages()
|
|> set_percentages()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single quiz scoped to the given event.
|
||||||
|
|
||||||
|
Returns `nil` if the quiz does not exist or does not belong to the event.
|
||||||
|
"""
|
||||||
|
def get_quiz_for_event(id, event_id, preload \\ []) do
|
||||||
|
from(q in Quiz,
|
||||||
|
join: pf in assoc(q, :presentation_file),
|
||||||
|
where: q.id == ^id and pf.event_id == ^event_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil -> nil
|
||||||
|
quiz -> quiz |> Repo.preload(preload) |> set_percentages()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single quiz for a given position.
|
Gets a single quiz for a given position.
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,14 @@ defmodule ClaperWeb.EmbedLive.FormComponent do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", %{"id" => id}, socket) do
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
embed = Embeds.get_embed!(id)
|
case Embeds.get_embed_for_event(id, socket.assigns.presentation_file.event_id) do
|
||||||
{:ok, _} = Embeds.delete_embed(socket.assigns.event_uuid, embed)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
embed ->
|
||||||
|
{:ok, _} = Embeds.delete_embed(socket.assigns.event_uuid, embed)
|
||||||
|
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
|
|
||||||
defp leader?(_socket, _event), do: false
|
defp leader?(_socket, _event), do: false
|
||||||
|
|
||||||
|
defp event_id(%{assigns: %{event: event}}), do: event.id
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info(%{event: "presence_diff"}, %{assigns: %{event: event}} = socket) do
|
def handle_info(%{event: "presence_diff"}, %{assigns: %{event: event}} = socket) do
|
||||||
attendees = Presence.list("event:#{event.uuid}")
|
attendees = Presence.list("event:#{event.uuid}")
|
||||||
@@ -329,127 +331,175 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("poll-set-active", %{"id" => id}, socket) do
|
def handle_event("poll-set-active", %{"id" => id}, socket) do
|
||||||
with poll <- Polls.get_poll!(id), :ok <- Claper.Interactions.enable_interaction(poll) do
|
case Polls.get_poll_for_event(id, event_id(socket)) do
|
||||||
Phoenix.PubSub.broadcast(
|
nil ->
|
||||||
Claper.PubSub,
|
{:noreply, socket}
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, poll}
|
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply,
|
poll ->
|
||||||
socket
|
with :ok <- Claper.Interactions.enable_interaction(poll) do
|
||||||
|> assign(:current_interaction, poll)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, poll}
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, poll)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("form-set-active", %{"id" => id}, socket) do
|
def handle_event("form-set-active", %{"id" => id}, socket) do
|
||||||
with form <- Forms.get_form!(id), :ok <- Claper.Interactions.enable_interaction(form) do
|
case Forms.get_form_for_event(id, event_id(socket)) do
|
||||||
Phoenix.PubSub.broadcast(
|
nil ->
|
||||||
Claper.PubSub,
|
{:noreply, socket}
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, form}
|
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply,
|
form ->
|
||||||
socket
|
with :ok <- Claper.Interactions.enable_interaction(form) do
|
||||||
|> assign(:current_interaction, form)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, form}
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, form)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("embed-set-active", %{"id" => id}, socket) do
|
def handle_event("embed-set-active", %{"id" => id}, socket) do
|
||||||
with embed <- Embeds.get_embed!(id), :ok <- Claper.Interactions.enable_interaction(embed) do
|
case Embeds.get_embed_for_event(id, event_id(socket)) do
|
||||||
Phoenix.PubSub.broadcast(
|
nil ->
|
||||||
Claper.PubSub,
|
{:noreply, socket}
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, embed}
|
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply,
|
embed ->
|
||||||
socket
|
with :ok <- Claper.Interactions.enable_interaction(embed) do
|
||||||
|> assign(:current_interaction, embed)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, embed}
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, embed)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("poll-set-inactive", %{"id" => id}, socket) do
|
def handle_event("poll-set-inactive", %{"id" => id}, socket) do
|
||||||
with poll <- Polls.get_poll!(id), {:ok, _} <- Claper.Interactions.disable_interaction(poll) do
|
case Polls.get_poll_for_event(id, event_id(socket)) do
|
||||||
Phoenix.PubSub.broadcast(
|
nil ->
|
||||||
Claper.PubSub,
|
{:noreply, socket}
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, nil}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply,
|
poll ->
|
||||||
socket
|
with {:ok, _} <- Claper.Interactions.disable_interaction(poll) do
|
||||||
|> assign(:current_interaction, nil)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, nil}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, nil)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("form-set-inactive", %{"id" => id}, socket) do
|
def handle_event("form-set-inactive", %{"id" => id}, socket) do
|
||||||
with form <- Forms.get_form!(id), {:ok, _} <- Claper.Interactions.disable_interaction(form) do
|
case Forms.get_form_for_event(id, event_id(socket)) do
|
||||||
Phoenix.PubSub.broadcast(
|
nil ->
|
||||||
Claper.PubSub,
|
{:noreply, socket}
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, nil}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply,
|
form ->
|
||||||
socket
|
with {:ok, _} <- Claper.Interactions.disable_interaction(form) do
|
||||||
|> assign(:current_interaction, nil)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, nil}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, nil)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("embed-set-inactive", %{"id" => id}, socket) do
|
def handle_event("embed-set-inactive", %{"id" => id}, socket) do
|
||||||
with embed <- Embeds.get_embed!(id),
|
case Embeds.get_embed_for_event(id, event_id(socket)) do
|
||||||
{:ok, _} <- Claper.Interactions.disable_interaction(embed) do
|
nil ->
|
||||||
Phoenix.PubSub.broadcast(
|
{:noreply, socket}
|
||||||
Claper.PubSub,
|
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, nil}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply,
|
embed ->
|
||||||
socket
|
with {:ok, _} <- Claper.Interactions.disable_interaction(embed) do
|
||||||
|> assign(:current_interaction, nil)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, nil}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, nil)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("quiz-set-active", %{"id" => id}, socket) do
|
def handle_event("quiz-set-active", %{"id" => id}, socket) do
|
||||||
with quiz <- Quizzes.get_quiz!(id, [:quiz_questions, quiz_questions: :quiz_question_opts]),
|
case Quizzes.get_quiz_for_event(id, event_id(socket), [
|
||||||
:ok <- Claper.Interactions.enable_interaction(quiz) do
|
:quiz_questions,
|
||||||
Phoenix.PubSub.broadcast(
|
quiz_questions: :quiz_question_opts
|
||||||
Claper.PubSub,
|
]) do
|
||||||
"event:#{socket.assigns.event.uuid}",
|
nil ->
|
||||||
{:current_interaction, quiz}
|
{:noreply, socket}
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply,
|
quiz ->
|
||||||
socket
|
with :ok <- Claper.Interactions.enable_interaction(quiz) do
|
||||||
|> assign(:current_interaction, quiz)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, quiz}
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, quiz)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("quiz-set-inactive", %{"id" => id}, socket) do
|
def handle_event("quiz-set-inactive", %{"id" => id}, socket) do
|
||||||
with quiz <- Quizzes.get_quiz!(id),
|
case Quizzes.get_quiz_for_event(id, event_id(socket)) do
|
||||||
{:ok, _} <- Claper.Interactions.disable_interaction(quiz) do
|
nil ->
|
||||||
Phoenix.PubSub.broadcast(
|
{:noreply, socket}
|
||||||
Claper.PubSub,
|
|
||||||
"event:#{socket.assigns.event.uuid}",
|
|
||||||
{:current_interaction, nil}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply,
|
quiz ->
|
||||||
socket
|
with {:ok, _} <- Claper.Interactions.disable_interaction(quiz) do
|
||||||
|> assign(:current_interaction, nil)
|
Phoenix.PubSub.broadcast(
|
||||||
|> interactions_at_position(socket.assigns.state.position)}
|
Claper.PubSub,
|
||||||
|
"event:#{socket.assigns.event.uuid}",
|
||||||
|
{:current_interaction, nil}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:current_interaction, nil)
|
||||||
|
|> interactions_at_position(socket.assigns.state.position)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -465,8 +515,10 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("pin", %{"id" => id}, socket) do
|
def handle_event("pin", %{"id" => id}, socket) do
|
||||||
post = Claper.Posts.get_post!(id, [:event])
|
case Claper.Posts.get_post_for_event(id, event_id(socket), [:event]) do
|
||||||
pin(post, socket)
|
nil -> {:noreply, socket}
|
||||||
|
post -> pin(post, socket)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -669,21 +721,26 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", %{"id" => id}, socket) do
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
post = Claper.Posts.get_post!(id, [:event])
|
case Claper.Posts.get_post_for_event(id, event_id(socket), [:event]) do
|
||||||
{:ok, _} = Claper.Posts.delete_post(post)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
updated_socket =
|
post ->
|
||||||
if post.pinned do
|
{:ok, _} = Claper.Posts.delete_post(post)
|
||||||
stream(socket, :pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid),
|
|
||||||
reset: true
|
|
||||||
)
|
|
||||||
|
|
||||||
stream(socket, :posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
updated_socket =
|
||||||
else
|
if post.pinned do
|
||||||
stream(socket, :posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
stream(socket, :pinned_posts, list_pinned_posts(socket, socket.assigns.event.uuid),
|
||||||
end
|
reset: true
|
||||||
|
)
|
||||||
|
|
||||||
{:noreply, updated_socket}
|
stream(socket, :posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
||||||
|
else
|
||||||
|
stream(socket, :posts, list_all_posts(socket, socket.assigns.event.uuid), reset: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply, updated_socket}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -697,16 +754,21 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete-form-submit", %{"event-id" => event_id, "id" => id}, socket) do
|
def handle_event("delete-form-submit", %{"id" => id}, socket) do
|
||||||
form = Claper.Forms.get_form_submit_by_id!(id)
|
case Claper.Forms.get_form_submit_for_event(id, event_id(socket)) do
|
||||||
{:ok, _} = Claper.Forms.delete_form_submit(event_id, form)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply,
|
form_submit ->
|
||||||
assign(
|
{:ok, _} = Claper.Forms.delete_form_submit(socket.assigns.event.uuid, form_submit)
|
||||||
socket,
|
|
||||||
:form_submits,
|
{:noreply,
|
||||||
list_form_submits(socket, socket.assigns.event.presentation_file.id)
|
assign(
|
||||||
)}
|
socket,
|
||||||
|
:form_submits,
|
||||||
|
list_form_submits(socket, socket.assigns.event.presentation_file.id)
|
||||||
|
)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -762,18 +824,26 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete-poll", %{"id" => id}, socket) do
|
def handle_event("delete-poll", %{"id" => id}, socket) do
|
||||||
poll = Polls.get_poll!(id)
|
case Polls.get_poll_for_event(id, event_id(socket)) do
|
||||||
{:ok, _} = Polls.delete_poll(socket.assigns.event.uuid, poll)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply, socket}
|
poll ->
|
||||||
|
{:ok, _} = Polls.delete_poll(socket.assigns.event.uuid, poll)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete-quiz", %{"id" => id}, socket) do
|
def handle_event("delete-quiz", %{"id" => id}, socket) do
|
||||||
quiz = Quizzes.get_quiz!(id)
|
case Quizzes.get_quiz_for_event(id, event_id(socket)) do
|
||||||
{:ok, _} = Quizzes.delete_quiz(socket.assigns.event.uuid, quiz)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply, socket}
|
quiz ->
|
||||||
|
{:ok, _} = Quizzes.delete_quiz(socket.assigns.event.uuid, quiz)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -826,13 +896,19 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :edit_poll, %{"id" => id}) do
|
defp apply_action(socket, :edit_poll, %{"id" => id}) do
|
||||||
poll = Polls.get_poll!(id)
|
case Polls.get_poll_for_event(id, event_id(socket)) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("Resource not found"))
|
||||||
|
|> push_navigate(to: ~p"/e/#{socket.assigns.event.code}/manage")
|
||||||
|
|
||||||
socket
|
poll ->
|
||||||
|> assign(:create, "poll")
|
socket
|
||||||
|> assign(:interaction_modal, true)
|
|> assign(:create, "poll")
|
||||||
|> assign(:create_action, :edit)
|
|> assign(:interaction_modal, true)
|
||||||
|> assign(:poll, poll)
|
|> assign(:create_action, :edit)
|
||||||
|
|> assign(:poll, poll)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :add_form, _params) do
|
defp apply_action(socket, :add_form, _params) do
|
||||||
@@ -859,23 +935,35 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :edit_form, %{"id" => id}) do
|
defp apply_action(socket, :edit_form, %{"id" => id}) do
|
||||||
form = Forms.get_form!(id)
|
case Forms.get_form_for_event(id, event_id(socket)) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("Resource not found"))
|
||||||
|
|> push_navigate(to: ~p"/e/#{socket.assigns.event.code}/manage")
|
||||||
|
|
||||||
socket
|
form ->
|
||||||
|> assign(:create, "form")
|
socket
|
||||||
|> assign(:interaction_modal, true)
|
|> assign(:create, "form")
|
||||||
|> assign(:create_action, :edit)
|
|> assign(:interaction_modal, true)
|
||||||
|> assign(:form, form)
|
|> assign(:create_action, :edit)
|
||||||
|
|> assign(:form, form)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :edit_embed, %{"id" => id}) do
|
defp apply_action(socket, :edit_embed, %{"id" => id}) do
|
||||||
embed = Embeds.get_embed!(id)
|
case Embeds.get_embed_for_event(id, event_id(socket)) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("Resource not found"))
|
||||||
|
|> push_navigate(to: ~p"/e/#{socket.assigns.event.code}/manage")
|
||||||
|
|
||||||
socket
|
embed ->
|
||||||
|> assign(:create, "embed")
|
socket
|
||||||
|> assign(:interaction_modal, true)
|
|> assign(:create, "embed")
|
||||||
|> assign(:create_action, :edit)
|
|> assign(:interaction_modal, true)
|
||||||
|> assign(:embed, embed)
|
|> assign(:create_action, :edit)
|
||||||
|
|> assign(:embed, embed)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :add_quiz, _params) do
|
defp apply_action(socket, :add_quiz, _params) do
|
||||||
@@ -900,13 +988,22 @@ defmodule ClaperWeb.EventLive.Manage do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :edit_quiz, %{"id" => id}) do
|
defp apply_action(socket, :edit_quiz, %{"id" => id}) do
|
||||||
quiz = Quizzes.get_quiz!(id, [:quiz_questions, quiz_questions: :quiz_question_opts])
|
case Quizzes.get_quiz_for_event(id, event_id(socket), [
|
||||||
|
:quiz_questions,
|
||||||
|
quiz_questions: :quiz_question_opts
|
||||||
|
]) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("Resource not found"))
|
||||||
|
|> push_navigate(to: ~p"/e/#{socket.assigns.event.code}/manage")
|
||||||
|
|
||||||
socket
|
quiz ->
|
||||||
|> assign(:create, "quiz")
|
socket
|
||||||
|> assign(:interaction_modal, true)
|
|> assign(:create, "quiz")
|
||||||
|> assign(:create_action, :edit)
|
|> assign(:interaction_modal, true)
|
||||||
|> assign(:quiz, quiz)
|
|> assign(:create_action, :edit)
|
||||||
|
|> assign(:quiz, quiz)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp pin(post, socket) do
|
defp pin(post, socket) do
|
||||||
|
|||||||
@@ -1398,7 +1398,6 @@
|
|||||||
to: "#",
|
to: "#",
|
||||||
phx_click: "delete-form-submit",
|
phx_click: "delete-form-submit",
|
||||||
phx_value_id: submission.id,
|
phx_value_id: submission.id,
|
||||||
phx_value_event_id: @event.uuid,
|
|
||||||
data: [confirm: gettext("This cannot be undone, confirm ?")]
|
data: [confirm: gettext("This cannot be undone, confirm ?")]
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ defmodule ClaperWeb.FormLive.FormComponent do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", %{"id" => id}, socket) do
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
form = Forms.get_form!(id)
|
case Forms.get_form_for_event(id, socket.assigns.presentation_file.event_id) do
|
||||||
{:ok, _} = Forms.delete_form(socket.assigns.event_uuid, form)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
form ->
|
||||||
|
{:ok, _} = Forms.delete_form(socket.assigns.event_uuid, form)
|
||||||
|
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ defmodule ClaperWeb.PollLive.FormComponent do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", %{"id" => id}, socket) do
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
poll = Polls.get_poll!(id)
|
case Polls.get_poll_for_event(id, socket.assigns.presentation_file.event_id) do
|
||||||
{:ok, _} = Polls.delete_poll(socket.assigns.event_uuid, poll)
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
poll ->
|
||||||
|
{:ok, _} = Polls.delete_poll(socket.assigns.event_uuid, poll)
|
||||||
|
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|||||||
@@ -17,10 +17,17 @@ defmodule ClaperWeb.QuizLive.QuizComponent do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("delete", %{"id" => id}, socket) do
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
quiz = Quizzes.get_quiz!(id, [:quiz_questions, quiz_questions: :quiz_question_opts])
|
case Quizzes.get_quiz_for_event(id, socket.assigns.event.id, [
|
||||||
{:ok, _} = Quizzes.delete_quiz(socket.assigns.event.uuid, quiz)
|
:quiz_questions,
|
||||||
|
quiz_questions: :quiz_question_opts
|
||||||
|
]) do
|
||||||
|
nil ->
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
quiz ->
|
||||||
|
{:ok, _} = Quizzes.delete_quiz(socket.assigns.event.uuid, quiz)
|
||||||
|
{:noreply, socket |> push_navigate(to: socket.assigns.return_to)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|||||||
@@ -138,5 +138,26 @@ defmodule Claper.EmbedsTest do
|
|||||||
embed = embed_fixture(%{presentation_file_id: presentation_file.id})
|
embed = embed_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
assert %Ecto.Changeset{} = Embeds.change_embed(embed)
|
assert %Ecto.Changeset{} = Embeds.change_embed(embed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get_embed_for_event/3 returns embed when it belongs to the event" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
embed = embed_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
|
|
||||||
|
fetched_embed = Embeds.get_embed_for_event(embed.id, presentation_file.event_id)
|
||||||
|
assert fetched_embed.id == embed.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_embed_for_event/3 returns nil when embed belongs to a different event" do
|
||||||
|
presentation_file_a = presentation_file_fixture()
|
||||||
|
presentation_file_b = presentation_file_fixture()
|
||||||
|
embed = embed_fixture(%{presentation_file_id: presentation_file_a.id})
|
||||||
|
|
||||||
|
assert is_nil(Embeds.get_embed_for_event(embed.id, presentation_file_b.event_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_embed_for_event/3 returns nil for nonexistent embed id" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
assert is_nil(Embeds.get_embed_for_event(-1, presentation_file.event_id))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -102,10 +102,63 @@ defmodule Claper.FormsTest do
|
|||||||
form = form_fixture(%{presentation_file_id: presentation_file.id})
|
form = form_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
assert %Ecto.Changeset{} = Forms.change_form(form)
|
assert %Ecto.Changeset{} = Forms.change_form(form)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get_form_for_event/3 returns form when it belongs to the event" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
form = form_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
|
|
||||||
|
fetched_form = Forms.get_form_for_event(form.id, presentation_file.event_id)
|
||||||
|
assert fetched_form.id == form.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_form_for_event/3 returns nil when form belongs to a different event" do
|
||||||
|
presentation_file_a = presentation_file_fixture()
|
||||||
|
presentation_file_b = presentation_file_fixture()
|
||||||
|
form = form_fixture(%{presentation_file_id: presentation_file_a.id})
|
||||||
|
|
||||||
|
assert is_nil(Forms.get_form_for_event(form.id, presentation_file_b.event_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_form_for_event/3 returns nil for nonexistent form id" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
assert is_nil(Forms.get_form_for_event(-1, presentation_file.event_id))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "form_submits" do
|
describe "form_submits" do
|
||||||
import Claper.{FormsFixtures, PresentationsFixtures}
|
import Claper.{FormsFixtures, PresentationsFixtures, AccountsFixtures}
|
||||||
|
|
||||||
|
test "get_form_submit_for_event/2 returns form submit when it belongs to the event" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
form = form_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
|
user = user_fixture()
|
||||||
|
|
||||||
|
{:ok, form_submit} =
|
||||||
|
Forms.create_form_submit(%{
|
||||||
|
form_id: form.id,
|
||||||
|
user_id: user.id,
|
||||||
|
response: %{"Name" => "Test"}
|
||||||
|
})
|
||||||
|
|
||||||
|
fetched = Forms.get_form_submit_for_event(form_submit.id, presentation_file.event_id)
|
||||||
|
assert fetched.id == form_submit.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_form_submit_for_event/2 returns nil when form submit belongs to a different event" do
|
||||||
|
presentation_file_a = presentation_file_fixture()
|
||||||
|
presentation_file_b = presentation_file_fixture()
|
||||||
|
form = form_fixture(%{presentation_file_id: presentation_file_a.id})
|
||||||
|
user = user_fixture()
|
||||||
|
|
||||||
|
{:ok, form_submit} =
|
||||||
|
Forms.create_form_submit(%{
|
||||||
|
form_id: form.id,
|
||||||
|
user_id: user.id,
|
||||||
|
response: %{"Name" => "Test"}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert is_nil(Forms.get_form_submit_for_event(form_submit.id, presentation_file_b.event_id))
|
||||||
|
end
|
||||||
|
|
||||||
test "get_form_submit/2 returns the form_submit with given id and user id" do
|
test "get_form_submit/2 returns the form_submit with given id and user id" do
|
||||||
form_submit = form_submit_fixture()
|
form_submit = form_submit_fixture()
|
||||||
|
|||||||
@@ -106,6 +106,27 @@ defmodule Claper.PollsTest do
|
|||||||
poll = poll_fixture(%{presentation_file_id: presentation_file.id})
|
poll = poll_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
assert %Ecto.Changeset{} = Polls.change_poll(poll)
|
assert %Ecto.Changeset{} = Polls.change_poll(poll)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get_poll_for_event/2 returns poll when it belongs to the event" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
poll = poll_fixture(%{presentation_file_id: presentation_file.id})
|
||||||
|
|
||||||
|
fetched_poll = Polls.get_poll_for_event(poll.id, presentation_file.event_id)
|
||||||
|
assert fetched_poll.id == poll.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_poll_for_event/2 returns nil when poll belongs to a different event" do
|
||||||
|
presentation_file_a = presentation_file_fixture()
|
||||||
|
presentation_file_b = presentation_file_fixture()
|
||||||
|
poll = poll_fixture(%{presentation_file_id: presentation_file_a.id})
|
||||||
|
|
||||||
|
assert is_nil(Polls.get_poll_for_event(poll.id, presentation_file_b.event_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_poll_for_event/2 returns nil for nonexistent poll id" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
assert is_nil(Polls.get_poll_for_event(-1, presentation_file.event_id))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "poll_opts" do
|
describe "poll_opts" do
|
||||||
|
|||||||
@@ -52,6 +52,27 @@ defmodule Claper.PostsTest do
|
|||||||
assert {:ok, %Post{}} = Posts.delete_post(post)
|
assert {:ok, %Post{}} = Posts.delete_post(post)
|
||||||
assert_raise Ecto.NoResultsError, fn -> Posts.get_post!(post.uuid) end
|
assert_raise Ecto.NoResultsError, fn -> Posts.get_post!(post.uuid) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get_post_for_event/3 returns post when it belongs to the event" do
|
||||||
|
event = event_fixture()
|
||||||
|
post = post_fixture(%{event: event}, [:event])
|
||||||
|
|
||||||
|
fetched_post = Posts.get_post_for_event(post.uuid, event.id, [:event])
|
||||||
|
assert fetched_post.id == post.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_post_for_event/3 returns nil when post belongs to a different event" do
|
||||||
|
event_a = event_fixture()
|
||||||
|
event_b = event_fixture()
|
||||||
|
post = post_fixture(%{event: event_a})
|
||||||
|
|
||||||
|
assert is_nil(Posts.get_post_for_event(post.uuid, event_b.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_post_for_event/3 returns nil for nonexistent post uuid" do
|
||||||
|
event = event_fixture()
|
||||||
|
assert is_nil(Posts.get_post_for_event(Ecto.UUID.generate(), event.id))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "reactions" do
|
describe "reactions" do
|
||||||
|
|||||||
@@ -196,6 +196,27 @@ defmodule Claper.QuizzesTest do
|
|||||||
assert length(responses) == 1
|
assert length(responses) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get_quiz_for_event/3 returns quiz when it belongs to the event" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
quiz = quiz_fixture(%{presentation_file: presentation_file})
|
||||||
|
|
||||||
|
fetched_quiz = Quizzes.get_quiz_for_event(quiz.id, presentation_file.event_id)
|
||||||
|
assert fetched_quiz.id == quiz.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_quiz_for_event/3 returns nil when quiz belongs to a different event" do
|
||||||
|
presentation_file_a = presentation_file_fixture()
|
||||||
|
presentation_file_b = presentation_file_fixture()
|
||||||
|
quiz = quiz_fixture(%{presentation_file: presentation_file_a})
|
||||||
|
|
||||||
|
assert is_nil(Quizzes.get_quiz_for_event(quiz.id, presentation_file_b.event_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_quiz_for_event/3 returns nil for nonexistent quiz id" do
|
||||||
|
presentation_file = presentation_file_fixture()
|
||||||
|
assert is_nil(Quizzes.get_quiz_for_event(-1, presentation_file.event_id))
|
||||||
|
end
|
||||||
|
|
||||||
test "submit_quiz/4 with user and duplicate opts deduplicates by id" do
|
test "submit_quiz/4 with user and duplicate opts deduplicates by id" do
|
||||||
quiz = quiz_fixture()
|
quiz = quiz_fixture()
|
||||||
user = user_fixture()
|
user = user_fixture()
|
||||||
|
|||||||
Reference in New Issue
Block a user