fix(captain): allow agents to manage FAQs

This commit is contained in:
Codex CLI 2026-05-04 13:16:47 +00:00
parent d670c5644b
commit 689cc114f8
5 changed files with 128 additions and 24 deletions

View File

@ -1,6 +1,6 @@
class Api::V1::Accounts::Captain::AssistantResponsesController < Api::V1::Accounts::BaseController class Api::V1::Accounts::Captain::AssistantResponsesController < Api::V1::Accounts::BaseController
before_action :current_account before_action :current_account
before_action -> { check_authorization(Captain::Assistant) } before_action -> { check_authorization(Captain::AssistantResponse) }
before_action :set_current_page, only: [:index] before_action :set_current_page, only: [:index]
before_action :set_assistant, only: [:create] before_action :set_assistant, only: [:create]
@ -21,6 +21,7 @@ class Api::V1::Accounts::Captain::AssistantResponsesController < Api::V1::Accoun
@response = Current.account.captain_assistant_responses.new(response_params) @response = Current.account.captain_assistant_responses.new(response_params)
@response.documentable = Current.user @response.documentable = Current.user
@response.save! @response.save!
Captain::Llm::UpdateEmbeddingJob.perform_now(@response, "#{@response.question}: #{@response.answer}")
end end
def update def update
@ -28,7 +29,7 @@ class Api::V1::Accounts::Captain::AssistantResponsesController < Api::V1::Accoun
end end
def destroy def destroy
@response.destroy! @response.destroy
head :no_content head :no_content
end end

View File

@ -1,6 +1,6 @@
class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::BaseController class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::BaseController
before_action :current_account before_action :current_account
before_action -> { check_authorization(Captain::Assistant) } before_action -> { check_authorization(Captain::AssistantResponse) }
before_action :validate_params before_action :validate_params
before_action :type_matches? before_action :type_matches?
@ -19,7 +19,7 @@ class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::Bas
end end
def type_matches? def type_matches?
return false if MODEL_TYPE.include?(params[:type]) return if MODEL_TYPE.include?(params[:type])
render json: { success: false }, status: :unprocessable_entity render json: { success: false }, status: :unprocessable_entity
end end
@ -37,7 +37,7 @@ class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::Bas
case params[:fields][:status] case params[:fields][:status]
when 'approve' when 'approve'
responses.pending.update!(status: 'approved') responses.pending.update(status: 'approved')
responses responses
when 'delete' when 'delete'
responses.destroy_all responses.destroy_all

View File

@ -0,0 +1,33 @@
class Captain::AssistantResponsePolicy < ApplicationPolicy
def index?
@account_user.administrator? || @account_user.agent?
end
def show?
index?
end
def create?
manage?
end
def update?
manage?
end
def destroy?
manage?
end
private
def manage?
return true if @account_user.administrator?
if @account_user.custom_role.present?
return @account_user.custom_role.permissions.include?('knowledge_base_manage')
end
@account_user.agent?
end
end

View File

@ -6,6 +6,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
let(:document) { create(:captain_document, assistant: assistant, account: account) } let(:document) { create(:captain_document, assistant: assistant, account: account) }
let(:admin) { create(:user, account: account, role: :administrator) } let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) } let(:agent) { create(:user, account: account, role: :agent) }
let(:agent_with_custom_role) { create(:user, account: account, role: :agent) }
let(:another_assistant) { create(:captain_assistant, account: account) } let(:another_assistant) { create(:captain_assistant, account: account) }
let(:another_document) { create(:captain_document, account: account, assistant: assistant) } let(:another_document) { create(:captain_document, account: account, assistant: assistant) }
@ -179,6 +180,46 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
expect(json_response[:answer]).to eq('Test answer') expect(json_response[:answer]).to eq('Test answer')
end end
it 'creates a new response if the user is an agent' do
expect do
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
end.to change(Captain::AssistantResponse, :count).by(1)
expect(response).to have_http_status(:success)
expect(json_response[:question]).to eq('Test question?')
end
it 'creates a new response if the user has a custom role with knowledge base permission' do
custom_role = create(:custom_role, account: account, permissions: ['knowledge_base_manage'])
AccountUser.find_by!(account: account, user: agent_with_custom_role).update!(custom_role: custom_role)
expect do
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: valid_params,
headers: agent_with_custom_role.create_new_auth_token,
as: :json
end.to change(Captain::AssistantResponse, :count).by(1)
expect(response).to have_http_status(:success)
end
it 'does not create a response if the custom role lacks knowledge base permission' do
custom_role = create(:custom_role, account: account, permissions: ['conversation_manage'])
AccountUser.find_by!(account: account, user: agent_with_custom_role).update!(custom_role: custom_role)
expect do
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: valid_params,
headers: agent_with_custom_role.create_new_auth_token,
as: :json
end.not_to change(Captain::AssistantResponse, :count)
expect(response).to have_http_status(:forbidden)
end
context 'with invalid params' do context 'with invalid params' do
let(:invalid_params) do let(:invalid_params) do
{ {
@ -197,22 +238,6 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_entity)
end end
it 'returns unprocessable entity when question exceeds 255 characters' do
long_question = 'a' * 256
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: {
assistant_response: {
question: long_question,
answer: 'Test answer',
assistant_id: assistant.id
}
},
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end end
end end
@ -239,6 +264,16 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
expect(json_response[:answer]).to eq('Updated answer') expect(json_response[:answer]).to eq('Updated answer')
end end
it 'updates the response if the user is an agent' do
patch "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
params: update_params,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:question]).to eq('Updated question?')
end
context 'with invalid params' do context 'with invalid params' do
let(:invalid_params) do let(:invalid_params) do
{ {
@ -266,7 +301,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
it 'deletes the response' do it 'deletes the response' do
expect do expect do
delete "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}", delete "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
headers: admin.create_new_auth_token, headers: agent.create_new_auth_token,
as: :json as: :json
end.to change(Captain::AssistantResponse, :count).by(-1) end.to change(Captain::AssistantResponse, :count).by(-1)

View File

@ -5,6 +5,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
let(:assistant) { create(:captain_assistant, account: account) } let(:assistant) { create(:captain_assistant, account: account) }
let(:admin) { create(:user, account: account, role: :administrator) } let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) } let(:agent) { create(:user, account: account, role: :agent) }
let(:agent_with_custom_role) { create(:user, account: account, role: :agent) }
let!(:pending_responses) do let!(:pending_responses) do
create_list( create_list(
:captain_assistant_response, :captain_assistant_response,
@ -32,7 +33,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
it 'approves the responses and returns the updated records' do it 'approves the responses and returns the updated records' do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions", post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: valid_params, params: valid_params,
headers: admin.create_new_auth_token, headers: agent.create_new_auth_token,
as: :json as: :json
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@ -59,7 +60,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
expect do expect do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions", post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: delete_params, params: delete_params,
headers: admin.create_new_auth_token, headers: agent.create_new_auth_token,
as: :json as: :json
end.to change(Captain::AssistantResponse, :count).by(-2) end.to change(Captain::AssistantResponse, :count).by(-2)
@ -73,6 +74,40 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
end end
end end
context 'when the user has a custom role' do
let(:approve_params) do
{
type: 'AssistantResponse',
ids: pending_responses.map(&:id),
fields: { status: 'approve' }
}
end
it 'allows bulk actions with knowledge base permission' do
custom_role = create(:custom_role, account: account, permissions: ['knowledge_base_manage'])
AccountUser.find_by!(account: account, user: agent_with_custom_role).update!(custom_role: custom_role)
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: approve_params,
headers: agent_with_custom_role.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
end
it 'rejects bulk actions without knowledge base permission' do
custom_role = create(:custom_role, account: account, permissions: ['conversation_manage'])
AccountUser.find_by!(account: account, user: agent_with_custom_role).update!(custom_role: custom_role)
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: approve_params,
headers: agent_with_custom_role.create_new_auth_token,
as: :json
expect(response).to have_http_status(:forbidden)
end
end
context 'with invalid type' do context 'with invalid type' do
let(:invalid_params) do let(:invalid_params) do
{ {