diff --git a/lib/code_corps/messages/messages.ex b/lib/code_corps/messages/messages.ex index 96fc80ac0..4ef76e617 100644 --- a/lib/code_corps/messages/messages.ex +++ b/lib/code_corps/messages/messages.ex @@ -53,6 +53,10 @@ defmodule CodeCorps.Messages do Conversation |> Repo.get(id) end + def update_conversation(conversation, params) do + conversation |> Conversation.update_changeset(params) |> Repo.update + end + @doc ~S""" Gets a `CodeCorps.ConversationPart` record """ diff --git a/lib/code_corps/model/conversation.ex b/lib/code_corps/model/conversation.ex index bfd11810e..cdbb155f3 100644 --- a/lib/code_corps/model/conversation.ex +++ b/lib/code_corps/model/conversation.ex @@ -26,4 +26,14 @@ defmodule CodeCorps.Conversation do timestamps() end + + def update_changeset(struct, %{"status" => status} = params) do + struct + |> cast(params, [:status]) + |> validate_inclusion(:status, statuses()) + end + + defp statuses do + ~w{ open closed } + end end diff --git a/lib/code_corps/policy/conversation.ex b/lib/code_corps/policy/conversation.ex index b192d9bd4..3bc7dfe7b 100644 --- a/lib/code_corps/policy/conversation.ex +++ b/lib/code_corps/policy/conversation.ex @@ -41,4 +41,10 @@ defmodule CodeCorps.Policy.Conversation do conversation |> get_message() |> get_project() |> administered_by?(user) end def show?(_, _), do: false + + def update?(%User{admin: true}, _conversation), do: true + def update?(%User{} = user, %Conversation{} = conversation) do + conversation |> get_message() |> get_project() |> administered_by?(user) + end + def update?(_, _), do: false end diff --git a/lib/code_corps/policy/policy.ex b/lib/code_corps/policy/policy.ex index 81be8c44d..7c94df8de 100644 --- a/lib/code_corps/policy/policy.ex +++ b/lib/code_corps/policy/policy.ex @@ -75,6 +75,7 @@ defmodule CodeCorps.Policy do # Conversation defp can?(%User{} = current_user, :show, %Conversation{} = conversation, %{}), do: Policy.Conversation.show?(current_user, conversation) + defp can?(%User{} = current_user, :update, %Conversation{} = conversation, %{}), do: Policy.Conversation.update?(current_user, conversation) # ConversationPart defp can?(%User{} = current_user, :create, %ConversationPart{}, %{} = params), do: Policy.ConversationPart.create?(current_user, params) diff --git a/lib/code_corps_web/controllers/conversation_controller.ex b/lib/code_corps_web/controllers/conversation_controller.ex index f040998df..5d3b23686 100644 --- a/lib/code_corps_web/controllers/conversation_controller.ex +++ b/lib/code_corps_web/controllers/conversation_controller.ex @@ -29,6 +29,18 @@ defmodule CodeCorpsWeb.ConversationController do end end + @spec update(Conn.t, map) :: Conn.t + def update(%Conn{} = conn, %{"id" => id} = params) do + with %Conversation{} = conversation <- Messages.get_conversation(id) |> preload(), + %User{} = current_user <- conn |> CodeCorps.Guardian.Plug.current_resource, + {:ok, :authorized} <- current_user |> Policy.authorize(:update, conversation), + {:ok, %Conversation{} = updated_conversation} <- conversation |> Messages.update_conversation(params) + do + + conn |> render("show.json-api", data: updated_conversation) + end + end + @preloads [:conversation_parts, :message, :user] def preload(data) do diff --git a/lib/code_corps_web/router.ex b/lib/code_corps_web/router.ex index 4f1183a0d..9f8761238 100644 --- a/lib/code_corps_web/router.ex +++ b/lib/code_corps_web/router.ex @@ -71,7 +71,7 @@ defmodule CodeCorpsWeb.Router do resources "/categories", CategoryController, only: [:create, :update] resources "/comments", CommentController, only: [:create, :update] - resources "/conversations", ConversationController, only: [:index, :show] + resources "/conversations", ConversationController, only: [:index, :show, :update] resources "/conversation-parts", ConversationPartController, only: [:index, :show, :create] resources "/donation-goals", DonationGoalController, only: [:create, :update, :delete] post "/oauth/github", UserController, :github_oauth diff --git a/test/lib/code_corps/policy/conversation_test.exs b/test/lib/code_corps/policy/conversation_test.exs index a06336352..f61d5a7b4 100644 --- a/test/lib/code_corps/policy/conversation_test.exs +++ b/test/lib/code_corps/policy/conversation_test.exs @@ -1,7 +1,7 @@ defmodule CodeCorps.Policy.ConversationTest do use CodeCorps.PolicyCase - import CodeCorps.Policy.Conversation, only: [scope: 2, show?: 2] + import CodeCorps.Policy.Conversation, only: [scope: 2, show?: 2, update?: 2] alias CodeCorps.{Conversation, Repo} @@ -113,4 +113,46 @@ defmodule CodeCorps.Policy.ConversationTest do assert show?(user, conversation) end end + + describe "update?" do + test "returns true when user is admin" do + user = insert(:user, admin: true) + message = insert(:message) + conversation = insert(:conversation, message: message, user: user) + + assert update?(user, conversation) + end + + test "returns false when user is a pending project member" do + %{project: project, user: user} = insert(:project_user, role: "pending") + message = insert(:message, project: project) + conversation = insert(:conversation, message: message) + + refute update?(user, conversation) + end + + test "returns false when user is a project contributor" do + %{project: project, user: user} = insert(:project_user, role: "contributor") + message = insert(:message, project: project) + conversation = insert(:conversation, message: message) + + refute update?(user, conversation) + end + + test "returns true when user is a project admin" do + %{project: project, user: user} = insert(:project_user, role: "admin") + message = insert(:message, project: project) + conversation = insert(:conversation, message: message) + + assert update?(user, conversation) + end + + test "returns true when user is project owner" do + %{project: project, user: user} = insert(:project_user, role: "owner") + message = insert(:message, project: project) + conversation = insert(:conversation, message: message) + + assert update?(user, conversation) + end + end end diff --git a/test/lib/code_corps_web/controllers/conversation_controller_test.exs b/test/lib/code_corps_web/controllers/conversation_controller_test.exs index 66bba91eb..36c89af8b 100644 --- a/test/lib/code_corps_web/controllers/conversation_controller_test.exs +++ b/test/lib/code_corps_web/controllers/conversation_controller_test.exs @@ -65,4 +65,39 @@ defmodule CodeCorpsWeb.ConversationControllerTest do assert conn |> request_show(conversation) |> json_response(403) end end + + describe "update" do + @tag authenticated: :admin + test "updates and renders chosen resource when data is valid", %{conn: conn, current_user: user} do + %{project: project} = insert(:project_user, role: "admin", user: user) + message_on_user_administered_project = insert(:message, project: project) + conversation_on_user_administered_project = + insert(:conversation, message: message_on_user_administered_project) + + data = + conn + |> request_update(conversation_on_user_administered_project, %{status: "closed"}) + |> json_response(200) + |> Map.get("data") + + assert data["attributes"]["status"] == "closed" + end + + @tag authenticated: :admin + test "renders 422 when data is invalid", %{conn: conn, current_user: current_user} do + conversation = insert(:conversation, user: current_user) + assert conn |> request_update(conversation, %{status: "wat"}) |> json_response(422) + end + + test "renders 401 when unauthenticated", %{conn: conn} do + assert conn |> request_update |> json_response(401) + end + + @tag :authenticated + test "does not update resource and renders 403 when not authorized", %{conn: conn} do + user = insert(:user) + insert(:conversation, user: user) + assert conn |> request_update() |> json_response(403) + end + end end