code-corps / code-corps-api

Elixir/Phoenix API for Code Corps.
https://www.codecorps.org
MIT License
234 stars 86 forks source link

Add ConversationController index endpoint and scope #1288

Closed begedin closed 6 years ago

begedin commented 6 years ago

Problem

Our client /project/messages and /user/messages routes will be in fact fetching conversations.

That means the current :index endpoint being implemented in #1278 is over-implemented and par of the behavior will have to be moved into the new endpoint.

That being said, all of the work being done there will be transferable to the solution for this issue.

The ConversationController needs potentially just one, but likely two endpoints.

This issue is for the :index endpoint.

Subtasks

This is a custom query involving the message scope, in that, the user has access to all conversations belonging to messages they initiated, as well as all conversations belonging to them directly (meaning they were the target of a message sent by a project admin).

def scope(query, %User{id: id} = current_user) do
  scoped_message_ids = 
    Message 
    |> Policy.Message.scope(current_user)
    |> select([m], m.id)
    |> Repo.all

  query
  |> where(user_id: ^id)
  |> or_where([c], c.message_id in scoped_message_ids)
end

From https://github.com/code-corps/code-corps-api/issues/1234#issuecomment-350776870

the messages/conversations index page should, by default query conversations initiated by users with any amount of parts, as well as convs. initiated by projects with at least one part (so we don't render unreplied to messages sent out to multiple users)

This is a weird route/endpoint which in a way is indexing two schemas at once.

We'd have to scope the conversations using the message scope, apply project_id filter to that scope, then fetch conversations from those messages, which fulfil the "status" rule and potentially any additional filter.

# in CodeCorpsWeb.ConversationController

def index(conn, params) do
  Message
  |> Policy.Conversation.scope(current_user) 
  |> Messages.list_conversations(params)
end

# in CodeCorps.Messages

def list_conversations(scope, %{} = params) do
  scope
  |> ConversationQuery.project_filter(params)
  |> ConversationQuery.status_filter(params)
end

# in CodeCorps.Messages.ConversationQuery

def project_filter(query, %{"project_id" => project_id}) do
  query
  |> join(:left, [c], m in Message, c.messsage_id == m.id)
  |> where([_c, _m], m.project_id == ^project_id)
end
def status_filter(query, %{}), do: query

# "status" => "active" is what the client is querying for by default,
# and is the default query parameter for the route, so it isn't rendered in the URL
def status_filter(query, %{"status" => "active"}) do
  query
  |> where([initiated_by: "user"]) 
  |> or_where([initiated_by: "admin, part_count > 0])
end
def status_filter(query, %{}), do: query

References