diff --git a/app/controllers/public/api/v1/inboxes/conversations_controller.rb b/app/controllers/public/api/v1/inboxes/conversations_controller.rb index 671863bdc..4e3b5dca9 100644 --- a/app/controllers/public/api/v1/inboxes/conversations_controller.rb +++ b/app/controllers/public/api/v1/inboxes/conversations_controller.rb @@ -1,15 +1,31 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::InboxesController include Events::Types - before_action :set_conversation, only: [:toggle_typing, :update_last_seen] + before_action :set_conversation, only: [:toggle_typing, :update_last_seen, :show, :toggle_status] def index @conversations = @contact_inbox.hmac_verified? ? @contact.conversations : @contact_inbox.conversations end + def show; end + def create @conversation = create_conversation end + def toggle_status + # Check if the conversation is already resolved to prevent redundant operations + return if @conversation.resolved? + + # Assign the conversation's contact as the resolver + # This step attributes the resolution action to the contact involved in the conversation + # If this assignment is not made, the system implicitly becomes the resolver by default + Current.contact = @conversation.contact + + # Update the conversation's status to 'resolved' to reflect its closure + @conversation.status = :resolved + @conversation.save! + end + def toggle_typing case params[:typing_status] when 'on' @@ -30,7 +46,11 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox private def set_conversation - @conversation = @contact_inbox.contact.conversations.find_by!(display_id: params[:id]) + @conversation = if @contact_inbox.hmac_verified? + @contact_inbox.contact.conversations.find_by!(display_id: params[:id]) + else + @contact_inbox.conversations.find_by!(display_id: params[:id]) + end end def create_conversation diff --git a/app/views/public/api/v1/inboxes/conversations/show.json.jbuilder b/app/views/public/api/v1/inboxes/conversations/show.json.jbuilder new file mode 100644 index 000000000..1b9588465 --- /dev/null +++ b/app/views/public/api/v1/inboxes/conversations/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'public/api/v1/models/conversation', formats: [:json], resource: @conversation diff --git a/app/views/public/api/v1/inboxes/conversations/toggle_status.json.jbuilder b/app/views/public/api/v1/inboxes/conversations/toggle_status.json.jbuilder new file mode 100644 index 000000000..1b9588465 --- /dev/null +++ b/app/views/public/api/v1/inboxes/conversations/toggle_status.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'public/api/v1/models/conversation', formats: [:json], resource: @conversation diff --git a/config/routes.rb b/config/routes.rb index 8c4d4124b..1ee74145f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -366,8 +366,9 @@ Rails.application.routes.draw do resources :inboxes do scope module: :inboxes do resources :contacts, only: [:create, :show, :update] do - resources :conversations, only: [:index, :create] do + resources :conversations, only: [:index, :create, :show] do member do + post :toggle_status post :toggle_typing post :update_last_seen end diff --git a/spec/controllers/public/api/v1/inbox/conversations_controller_spec.rb b/spec/controllers/public/api/v1/inbox/conversations_controller_spec.rb index d54f26272..2b2bdefc3 100644 --- a/spec/controllers/public/api/v1/inbox/conversations_controller_spec.rb +++ b/spec/controllers/public/api/v1/inbox/conversations_controller_spec.rb @@ -36,6 +36,45 @@ RSpec.describe 'Public Inbox Contact Conversations API', type: :request do end end + describe 'GET /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}' do + it 'returns the conversation that the contact has access to' do + conversation = create(:conversation, contact_inbox: contact_inbox) + create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'message-1') + create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'message-2') + + get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}" + + expect(response).to have_http_status(:success) + data = response.parsed_body + expect(data['id']).to eq(conversation.display_id) + expect(data['messages']).to be_a(Array) + expect(data['messages'].length).to eq(conversation.messages.count) + expect(data['messages'].pluck('content')).to include(conversation.messages.first.content) + end + end + + describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/toggle_status' do + it 'resolves the conversation' do + conversation = create(:conversation, contact_inbox: contact_inbox) + display_id = conversation.display_id + + post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{display_id}/toggle_status" + + expect(response).to have_http_status(:success) + expect(conversation.reload).to be_resolved + end + + it 'does not resolve a conversation that is already resolved' do + conversation = create(:conversation, contact_inbox: contact_inbox, status: :resolved) + display_id = conversation.display_id + + post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{display_id}/toggle_status" + + expect(response).to have_http_status(:success) + expect(Conversation.where(id: conversation.id, status: :resolved).count).to eq(1) + end + end + describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations' do it 'creates a conversation for that contact' do post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations" diff --git a/swagger/paths/index.yml b/swagger/paths/index.yml index d58439647..2c6dd3f87 100644 --- a/swagger/paths/index.yml +++ b/swagger/paths/index.yml @@ -94,7 +94,6 @@ patch: $ref: ./public/inboxes/contacts/update.yml - /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations: parameters: - $ref: '#/parameters/public_inbox_identifier' @@ -104,6 +103,38 @@ get: $ref: ./public/inboxes/conversations/index.yml +/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}: + parameters: + - $ref: '#/parameters/public_inbox_identifier' + - $ref: '#/parameters/public_contact_identifier' + - $ref: '#/parameters/conversation_id' + get: + $ref: ./public/inboxes/conversations/show.yml + +/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_status: + parameters: + - $ref: '#/parameters/public_inbox_identifier' + - $ref: '#/parameters/public_contact_identifier' + - $ref: '#/parameters/conversation_id' + post: + $ref: ./public/inboxes/conversations/toggle_status.yml + +/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_typing: + parameters: + - $ref: '#/parameters/public_inbox_identifier' + - $ref: '#/parameters/public_contact_identifier' + - $ref: '#/parameters/conversation_id' + post: + $ref: ./public/inboxes/conversations/toggle_typing.yml + +/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/update_last_seen: + parameters: + - $ref: '#/parameters/public_inbox_identifier' + - $ref: '#/parameters/public_contact_identifier' + - $ref: '#/parameters/conversation_id' + post: + $ref: ./public/inboxes/conversations/update_last_seen.yml + /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages: parameters: - $ref: '#/parameters/public_inbox_identifier' diff --git a/swagger/paths/public/inboxes/conversations/show.yml b/swagger/paths/public/inboxes/conversations/show.yml new file mode 100644 index 000000000..2ec6caa63 --- /dev/null +++ b/swagger/paths/public/inboxes/conversations/show.yml @@ -0,0 +1,14 @@ +tags: + - Conversations API +operationId: get-single-conversation +summary: Get a single conversation +description: Retrieves the details of a specific conversation +responses: + 200: + description: Success + schema: + $ref: '#/definitions/public_conversation' + 401: + description: Unauthorized + 404: + description: Conversation not found diff --git a/swagger/paths/public/inboxes/conversations/toggle_status.yml b/swagger/paths/public/inboxes/conversations/toggle_status.yml new file mode 100644 index 000000000..cac1c3d8d --- /dev/null +++ b/swagger/paths/public/inboxes/conversations/toggle_status.yml @@ -0,0 +1,14 @@ +tags: + - Conversations API +operationId: resolve-conversation +summary: Resolve a conversation +description: Marks a conversation as resolved +responses: + 200: + description: Conversation resolved successfully + schema: + $ref: '#/definitions/public_conversation' + 401: + description: Unauthorized + 404: + description: Conversation not found diff --git a/swagger/paths/public/inboxes/conversations/toggle_typing.yml b/swagger/paths/public/inboxes/conversations/toggle_typing.yml new file mode 100644 index 000000000..af01c77f6 --- /dev/null +++ b/swagger/paths/public/inboxes/conversations/toggle_typing.yml @@ -0,0 +1,18 @@ +tags: + - Conversations API +operationId: toggle-typing-status +summary: Toggle typing status +description: Toggles the typing status in a conversation +parameters: + - name: typing_status + in: query + required: true + type: string + description: Typing status, either 'on' or 'off' +responses: + 200: + description: Typing status toggled successfully + 401: + description: Unauthorized + 404: + description: Conversation not found diff --git a/swagger/paths/public/inboxes/conversations/update_last_seen.yml b/swagger/paths/public/inboxes/conversations/update_last_seen.yml new file mode 100644 index 000000000..4e56aa11d --- /dev/null +++ b/swagger/paths/public/inboxes/conversations/update_last_seen.yml @@ -0,0 +1,12 @@ +tags: + - Conversations API +operationId: update-last-seen +summary: Update last seen +description: Updates the last seen time of the contact in a conversation +responses: + 200: + description: Last seen updated successfully + 401: + description: Unauthorized + 404: + description: Conversation not found diff --git a/swagger/swagger.json b/swagger/swagger.json index 4859a785f..dde7c5f01 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -894,6 +894,149 @@ } } }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}": { + "parameters": [ + { + "$ref": "#/parameters/public_inbox_identifier" + }, + { + "$ref": "#/parameters/public_contact_identifier" + }, + { + "$ref": "#/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Conversations API" + ], + "operationId": "get-single-conversation", + "summary": "Get a single conversation", + "description": "Retrieves the details of a specific conversation", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/public_conversation" + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Conversation not found" + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_status": { + "parameters": [ + { + "$ref": "#/parameters/public_inbox_identifier" + }, + { + "$ref": "#/parameters/public_contact_identifier" + }, + { + "$ref": "#/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "resolve-conversation", + "summary": "Resolve a conversation", + "description": "Marks a conversation as resolved", + "responses": { + "200": { + "description": "Conversation resolved successfully", + "schema": { + "$ref": "#/definitions/public_conversation" + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Conversation not found" + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_typing": { + "parameters": [ + { + "$ref": "#/parameters/public_inbox_identifier" + }, + { + "$ref": "#/parameters/public_contact_identifier" + }, + { + "$ref": "#/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "toggle-typing-status", + "summary": "Toggle typing status", + "description": "Toggles the typing status in a conversation", + "parameters": [ + { + "name": "typing_status", + "in": "query", + "required": true, + "type": "string", + "description": "Typing status, either 'on' or 'off'" + } + ], + "responses": { + "200": { + "description": "Typing status toggled successfully" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Conversation not found" + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/update_last_seen": { + "parameters": [ + { + "$ref": "#/parameters/public_inbox_identifier" + }, + { + "$ref": "#/parameters/public_contact_identifier" + }, + { + "$ref": "#/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "update-last-seen", + "summary": "Update last seen", + "description": "Updates the last seen time of the contact in a conversation", + "responses": { + "200": { + "description": "Last seen updated successfully" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Conversation not found" + } + } + } + }, "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages": { "parameters": [ {