Chore: Inbox Members API improvements (#3008)

- New Inbox Member APIs
- Return JSON errors for Platform APIs
This commit is contained in:
Sojan Jose 2021-09-14 11:55:02 +05:30 committed by GitHub
parent ccd0dc39ad
commit 22d1c8baf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 767 additions and 131 deletions

View file

@ -1,26 +1,40 @@
class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseController
before_action :fetch_inbox, only: [:create, :show]
before_action :current_agents_ids, only: [:create]
before_action :fetch_inbox
before_action :current_agents_ids, only: [:update]
def create
authorize @inbox, :create?
begin
# update also done via same action
update_agents_list
head :ok
rescue StandardError => e
Rails.logger.debug { "Rescued: #{e.inspect}" }
render_could_not_create_error('Could not add agents to inbox')
ActiveRecord::Base.transaction do
params[:user_ids].map { |user_id| @inbox.add_member(user_id) }
end
fetch_updated_agents
end
def show
authorize @inbox, :show?
@agents = Current.account.users.where(id: @inbox.members.select(:user_id))
fetch_updated_agents
end
def update
authorize @inbox, :update?
update_agents_list
fetch_updated_agents
end
def destroy
authorize @inbox, :destroy?
ActiveRecord::Base.transaction do
params[:user_ids].map { |user_id| @inbox.remove_member(user_id) }
end
head :ok
end
private
def fetch_updated_agents
@agents = Current.account.users.where(id: @inbox.members.select(:user_id))
end
def update_agents_list
# get all the user_ids which the inbox currently has as members.
# get the list of user_ids from params

View file

@ -43,7 +43,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
channel_attributes = get_channel_attributes(@inbox.channel_type)
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
@inbox.channel.update!(permitted_params(channel_attributes)[:channel]) if permitted_params(channel_attributes)[:channel].present?
update_channel_feature_flags
end

View file

@ -1,5 +1,6 @@
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
include RequestExceptionHandler
include Pundit
include SwitchLocale
@ -9,22 +10,8 @@ class ApplicationController < ActionController::Base
around_action :switch_locale
around_action :handle_with_exception, unless: :devise_controller?
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
private
def handle_with_exception
yield
rescue ActiveRecord::RecordNotFound => e
Sentry.capture_exception(e)
render_not_found_error('Resource could not be found')
rescue Pundit::NotAuthorizedError
render_unauthorized('You are not authorized to do this action')
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.reset
end
def set_current_user
@user ||= current_user
Current.user = @user
@ -34,32 +21,6 @@ class ApplicationController < ActionController::Base
@subscription ||= Current.account.subscription
end
def render_unauthorized(message)
render json: { error: message }, status: :unauthorized
end
def render_not_found_error(message)
render json: { error: message }, status: :not_found
end
def render_could_not_create_error(message)
render json: { error: message }, status: :unprocessable_entity
end
def render_internal_server_error(message)
render json: { error: message }, status: :internal_server_error
end
def render_record_invalid(exception)
render json: {
message: exception.record.errors.full_messages.join(', ')
}, status: :unprocessable_entity
end
def render_error_response(exception)
render json: exception.to_hash, status: exception.http_status
end
def pundit_user
{
user: Current.user,

View file

@ -0,0 +1,47 @@
module RequestExceptionHandler
extend ActiveSupport::Concern
included do
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
end
private
def handle_with_exception
yield
rescue ActiveRecord::RecordNotFound => e
Sentry.capture_exception(e)
render_not_found_error('Resource could not be found')
rescue Pundit::NotAuthorizedError
render_unauthorized('You are not authorized to do this action')
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.reset
end
def render_unauthorized(message)
render json: { error: message }, status: :unauthorized
end
def render_not_found_error(message)
render json: { error: message }, status: :not_found
end
def render_could_not_create_error(message)
render json: { error: message }, status: :unprocessable_entity
end
def render_internal_server_error(message)
render json: { error: message }, status: :internal_server_error
end
def render_record_invalid(exception)
render json: {
message: exception.record.errors.full_messages.join(', ')
}, status: :unprocessable_entity
end
def render_error_response(exception)
render json: exception.to_hash, status: exception.http_status
end
end

View file

@ -7,8 +7,8 @@ class Platform::Api::V1::UsersController < PlatformController
def create
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
@resource.confirm
@resource.save!
@resource.confirm
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
end

View file

@ -1,4 +1,6 @@
class PlatformController < ActionController::API
include RequestExceptionHandler
before_action :ensure_access_token
before_action :set_platform_app
before_action :set_resource, only: [:update, :show, :destroy]

View file

@ -1,5 +1,6 @@
# TODO: we should switch to ActionController::API for the base classes
# One of the specs is failing when I tried doing that, lets revisit in future
class PublicController < ActionController::Base
include RequestExceptionHandler
skip_before_action :verify_authenticity_token
end

View file

@ -6,8 +6,8 @@ class InboxMembers extends ApiClient {
super('inbox_members', { accountScoped: true });
}
create({ inboxId, agentList }) {
return axios.post(this.url, {
update({ inboxId, agentList }) {
return axios.patch(this.url, {
inbox_id: inboxId,
user_ids: agentList,
});

View file

@ -5,7 +5,7 @@ export const actions = {
return InboxMembersAPI.show(inboxId);
},
create(_, { inboxId, agentList }) {
return InboxMembersAPI.create({ inboxId, agentList });
return InboxMembersAPI.update({ inboxId, agentList });
},
};

View file

@ -62,7 +62,7 @@ class Inbox < ApplicationRecord
end
def remove_member(user_id)
member = inbox_members.find_by(user_id: user_id)
member = inbox_members.find_by!(user_id: user_id)
member.try(:destroy)
end

View file

@ -0,0 +1,5 @@
json.payload do
json.array! @agents do |agent|
json.partial! 'api/v1/models/agent.json.jbuilder', resource: agent
end
end

View file

@ -0,0 +1,5 @@
json.payload do
json.array! @agents do |agent|
json.partial! 'api/v1/models/agent.json.jbuilder', resource: agent
end
end

View file

@ -104,7 +104,12 @@ Rails.application.routes.draw do
post :set_agent_bot, on: :member
delete :avatar, on: :member
end
resources :inbox_members, only: [:create, :show], param: :inbox_id
resources :inbox_members, only: [:create, :show], param: :inbox_id do
collection do
delete :destroy
patch :update
end
end
resources :labels, only: [:index, :show, :create, :update, :destroy]
resources :notifications, only: [:index, :update] do

View file

@ -4,82 +4,12 @@ RSpec.describe 'Inbox Member API', type: :request do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
describe 'POST /api/v1/accounts/{account.id}/inbox_members' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/inbox_members"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: agent, inbox: inbox)
end
it 'returns unauthorized' do
params = { inbox_id: inbox.id, user_ids: [agent.id] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: agent.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated user with access to inbox' do
let(:administrator) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
it 'modifies inbox members' do
params = { inbox_id: inbox.id, user_ids: [agent.id] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:success)
expect(inbox.inbox_members&.count).to eq(1)
expect(inbox.inbox_members&.first&.user).to eq(agent)
end
it 'renders not found when inbox not found' do
params = { inbox_id: nil, user_ids: [agent.id] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:not_found)
end
it 'renders error on invalid params' do
params = { inbox_id: inbox.id, user_ids: ['invalid'] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.body).to include('Could not add agents to inbox')
end
end
end
describe 'GET /api/v1/accounts/{account.id}/inbox_members/:id' do
let(:inbox_member) { create(:inbox_member, inbox: inbox) }
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
get "/api/v1/accounts/#{account.id}/inbox_members/#{inbox_member.id}"
get "/api/v1/accounts/#{account.id}/inbox_members/#{inbox.id}"
expect(response).to have_http_status(:unauthorized)
end
@ -112,4 +42,242 @@ RSpec.describe 'Inbox Member API', type: :request do
end
end
end
describe 'POST /api/v1/accounts/{account.id}/inbox_members' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/inbox_members"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: agent, inbox: inbox)
end
it 'returns unauthorized' do
params = { inbox_id: inbox.id, user_ids: [agent.id] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: agent.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an administrator' do
let(:administrator) { create(:user, account: account, role: :administrator) }
let(:old_agent) { create(:user, account: account, role: :agent) }
let(:agent_to_add) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: old_agent, inbox: inbox)
end
it 'add inbox members' do
params = { inbox_id: inbox.id, user_ids: [agent_to_add.id] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:success)
expect(inbox.inbox_members&.count).to eq(2)
expect(inbox.inbox_members&.second&.user).to eq(agent_to_add)
end
it 'renders not found when inbox not found' do
params = { inbox_id: nil, user_ids: [agent_to_add.id] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:not_found)
end
it 'renders error on invalid params' do
params = { inbox_id: inbox.id, user_ids: ['invalid'] }
post "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.body).to include('User must exist')
end
end
end
describe 'PATCH /api/v1/accounts/{account.id}/inbox_members' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
patch "/api/v1/accounts/#{account.id}/inbox_members"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: agent, inbox: inbox)
end
it 'returns unauthorized' do
params = { inbox_id: inbox.id, user_ids: [agent.id] }
patch "/api/v1/accounts/#{account.id}/inbox_members",
headers: agent.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an administrator' do
let(:administrator) { create(:user, account: account, role: :administrator) }
let(:old_agent) { create(:user, account: account, role: :agent) }
let(:agent_to_add) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: old_agent, inbox: inbox)
end
it 'modifies inbox members' do
params = { inbox_id: inbox.id, user_ids: [agent_to_add.id] }
patch "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:success)
expect(inbox.inbox_members&.count).to eq(1)
expect(inbox.inbox_members&.first&.user).to eq(agent_to_add)
end
it 'renders not found when inbox not found' do
params = { inbox_id: nil, user_ids: [agent_to_add.id] }
patch "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:not_found)
end
it 'renders error on invalid params' do
params = { inbox_id: inbox.id, user_ids: ['invalid'] }
patch "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.body).to include('User must exist')
end
end
end
describe 'DELETE /api/v1/accounts/{account.id}/inbox_members' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
delete "/api/v1/accounts/#{account.id}/inbox_members"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: agent, inbox: inbox)
end
it 'returns unauthorized' do
params = { inbox_id: inbox.id, user_ids: [agent.id] }
delete "/api/v1/accounts/#{account.id}/inbox_members",
headers: agent.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an administrator' do
let(:administrator) { create(:user, account: account, role: :administrator) }
let(:old_agent) { create(:user, account: account, role: :agent) }
let(:agent_to_delete) { create(:user, account: account, role: :agent) }
let(:non_member_agent) { create(:user, account: account, role: :agent) }
before do
create(:inbox_member, user: old_agent, inbox: inbox)
create(:inbox_member, user: agent_to_delete, inbox: inbox)
end
it 'deletes inbox members' do
params = { inbox_id: inbox.id, user_ids: [agent_to_delete.id] }
delete "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:success)
expect(inbox.inbox_members&.count).to eq(1)
end
it 'renders not found when inbox not found' do
params = { inbox_id: nil, user_ids: [agent_to_delete.id] }
delete "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:not_found)
end
it 'renders error on invalid params' do
params = { inbox_id: inbox.id, user_ids: ['invalid'] }
delete "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:not_found)
expect(response.body).to include('Resource could not be found')
end
it 'renders error on non member params' do
params = { inbox_id: inbox.id, user_ids: [non_member_agent.id] }
delete "/api/v1/accounts/#{account.id}/inbox_members",
headers: administrator.create_new_auth_token,
params: params,
as: :json
expect(response).to have_http_status(:not_found)
expect(response.body).to include('Resource could not be found')
end
end
end
end

View file

@ -20,6 +20,8 @@ message:
$ref: ./resource/message.yml
user:
$ref: ./resource/user.yml
agent:
$ref: ./resource/agent.yml
inbox:
$ref: ./resource/inbox.yml
agent_bot:

View file

@ -0,0 +1,25 @@
type: object
properties:
id:
type: number
uid:
type: string
name:
type: string
available_name:
type: string
display_name:
type: string
email:
type: string
account_id:
type: number
role:
type: string
enum: ['agent', 'administrator']
confirmed:
type: boolean
custom_attributes:
type: object
description: Available for users who are created through platform APIs and has custom attributes associated.

View file

@ -0,0 +1,6 @@
in: path
name: inbox_id
schema:
type: integer
required: true
description: The ID of the Inbox

View file

@ -7,6 +7,9 @@ agent_bot_id:
team_id:
$ref: ./team_id.yml
inbox_id:
$ref: ./inbox_id.yml
hook_id:
$ref: ./hook_id.yml

View file

@ -0,0 +1,36 @@
tags:
- Inbox
operationId: add-new-agent-to-inbox
summary: Add a New Agent
description: Add a new Agent to Inbox
security:
- userApiKey: []
parameters:
- name: data
in: body
required: true
schema:
type: object
properties:
inbox_id:
type: string
description: The ID of the inbox
required: true
user_ids:
type: array
description: IDs of users to be added to the inbox
required: true
responses:
200:
description: Success
schema:
type: array
description: 'Array of all active agents'
items:
$ref: '#/definitions/agent'
404:
description: Inbox not found
403:
description: Access denied
422:
description: User must exist

View file

@ -0,0 +1,31 @@
tags:
- Inbox
operationId: delete-agent-in-inbox
summary: Remove an Agent from Inbox
description: Remove an Agent from Inbox
security:
- userApiKey: []
parameters:
- name: data
in: body
required: true
schema:
type: object
properties:
inbox_id:
type: string
description: The ID of the inbox
required: true
user_ids:
type: array
description: IDs of users to be deleted from the inbox
required: true
responses:
200:
description: Success
404:
description: Inbox not found
403:
description: Access denied
422:
description: User must exist

View file

@ -0,0 +1,21 @@
tags:
- Inbox
operationId: get-inbox-members
summary: List Agents in Inbox
description: Get Details of Agents in an Inbox
security:
- userApiKey: []
parameters:
- $ref: '#/parameters/inbox_id'
responses:
200:
description: Success
schema:
type: array
description: 'Array of all active agents'
items:
$ref: '#/definitions/agent'
404:
description: Inbox not found
403:
description: Access denied

View file

@ -0,0 +1,36 @@
tags:
- Inbox
operationId: update-agents-in-inbox
summary: Update Agents in Inbox
description: All agents execept the one passed in params will be removed
security:
- userApiKey: []
parameters:
- name: data
in: body
required: true
schema:
type: object
properties:
inbox_id:
type: string
description: The ID of the inbox
required: true
user_ids:
type: array
description: IDs of users to be added to the inbox
required: true
responses:
200:
description: Success
schema:
type: array
description: 'Array of all active agents'
items:
$ref: '#/definitions/agent'
404:
description: Inbox not found
403:
description: Access denied
422:
description: User must exist

View file

@ -1,7 +1,7 @@
get:
tags:
- Inbox
operationId: GetInboxe
operationId: GetInbox
summary: Get an inbox
description: Get an inbox available in the current account
parameters:

View file

@ -209,6 +209,18 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
/api/v1/accounts/{account_id}/inboxes/{id}/set_agent_bot:
$ref: ./inboxes/set_agent_bot.yml
# Inbox Members
/api/v1/accounts/{account_id}/inbox_members:
get:
$ref: ./inboxes/inbox_members/show.yml
post:
$ref: ./inboxes/inbox_members/create.yml
patch:
$ref: ./inboxes/inbox_members/update.yml
delete:
$ref: ./inboxes/inbox_members/delete.yml
# Messages
/api/v1/accounts/{account_id}/conversations/{id}/messages:
@ -273,7 +285,7 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
### Custom Filters goes here
# Teams
# Custom Filters
/api/v1/accounts/{account_id}/custom_filters:
parameters:
- $ref: '#/parameters/account_id'

View file

@ -1920,7 +1920,7 @@
"tags": [
"Inbox"
],
"operationId": "GetInboxe",
"operationId": "GetInbox",
"summary": "Get an inbox",
"description": "Get an inbox available in the current account",
"parameters": [
@ -2198,6 +2198,213 @@
}
}
},
"/api/v1/accounts/{account_id}/inbox_members": {
"get": {
"tags": [
"Inbox"
],
"operationId": "get-inbox-members",
"summary": "List Agents in Inbox",
"description": "Get Details of Agents in an Inbox",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"$ref": "#/parameters/inbox_id"
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "array",
"description": "Array of all active agents",
"items": {
"$ref": "#/definitions/agent"
}
}
},
"404": {
"description": "Inbox not found"
},
"403": {
"description": "Access denied"
}
}
},
"post": {
"tags": [
"Inbox"
],
"operationId": "add-new-agent-to-inbox",
"summary": "Add a New Agent",
"description": "Add a new Agent to Inbox",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"inbox_id": {
"type": "string",
"description": "The ID of the inbox",
"required": true
},
"user_ids": {
"type": "array",
"description": "IDs of users to be added to the inbox",
"required": true
}
}
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "array",
"description": "Array of all active agents",
"items": {
"$ref": "#/definitions/agent"
}
}
},
"404": {
"description": "Inbox not found"
},
"403": {
"description": "Access denied"
},
"422": {
"description": "User must exist"
}
}
},
"patch": {
"tags": [
"Inbox"
],
"operationId": "update-agents-in-inbox",
"summary": "Update Agents in Inbox",
"description": "All agents execept the one passed in params will be removed",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"inbox_id": {
"type": "string",
"description": "The ID of the inbox",
"required": true
},
"user_ids": {
"type": "array",
"description": "IDs of users to be added to the inbox",
"required": true
}
}
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "array",
"description": "Array of all active agents",
"items": {
"$ref": "#/definitions/agent"
}
}
},
"404": {
"description": "Inbox not found"
},
"403": {
"description": "Access denied"
},
"422": {
"description": "User must exist"
}
}
},
"delete": {
"tags": [
"Inbox"
],
"operationId": "delete-agent-in-inbox",
"summary": "Remove an Agent from Inbox",
"description": "Remove an Agent from Inbox",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"inbox_id": {
"type": "string",
"description": "The ID of the inbox",
"required": true
},
"user_ids": {
"type": "array",
"description": "IDs of users to be deleted from the inbox",
"required": true
}
}
}
}
],
"responses": {
"200": {
"description": "Success"
},
"404": {
"description": "Inbox not found"
},
"403": {
"description": "Access denied"
},
"422": {
"description": "User must exist"
}
}
}
},
"/api/v1/accounts/{account_id}/conversations/{id}/messages": {
"post": {
"tags": [
@ -3193,6 +3400,46 @@
}
}
},
"agent": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"uid": {
"type": "string"
},
"name": {
"type": "string"
},
"available_name": {
"type": "string"
},
"display_name": {
"type": "string"
},
"email": {
"type": "string"
},
"account_id": {
"type": "number"
},
"role": {
"type": "string",
"enum": [
"agent",
"administrator"
]
},
"confirmed": {
"type": "boolean"
},
"custom_attributes": {
"type": "object",
"description": "Available for users who are created through platform APIs and has custom attributes associated."
}
}
},
"inbox": {
"type": "object",
"properties": {
@ -4128,6 +4375,15 @@
"required": true,
"description": "The ID of the team to be updated"
},
"inbox_id": {
"in": "path",
"name": "inbox_id",
"schema": {
"type": "integer"
},
"required": true,
"description": "The ID of the Inbox"
},
"hook_id": {
"in": "path",
"name": "hook_id",