Files
Sojan JoseandGitHub eae9841eb4 fix: restore token access to account APIs (#15088)
Token-authenticated requests to Agent Bots, Labels, and affected Captain
endpoints return normal responses again. The regression was caused by
duplicate `current_account` callbacks in subclasses moving account
resolution behind the API entitlement check, leaving `Current.account`
unset.

## Closes

- https://linear.app/chatwoot/issue/CW-7641/5xx-errors-in-agent-bot-apis

## How to reproduce

1. Send `GET /api/v1/accounts/:account_id/agent_bots` with a valid
administrator API access token.
2. Observe a `500` from `validate_token_api_access` because
`Current.account` is `nil`.
3. With this change, account resolution runs in the base-controller
order and the request succeeds.

## What changed

- Removed redundant `current_account` callbacks from account-scoped
controllers that already inherit the callback from
`Api::V1::Accounts::BaseController`.
- Kept the standalone direct-upload controller callback unchanged.
- Added regression coverage for administrator API-token access to Agent
Bots.
2026-07-20 15:28:33 -07:00

141 lines
4.3 KiB
Ruby

class Api::V1::Accounts::Captain::DocumentsController < Api::V1::Accounts::BaseController
before_action -> { check_authorization(Captain::Assistant) }
before_action :set_current_page, only: [:index]
before_action :set_documents, except: [:create]
before_action :set_document, only: [:show, :destroy, :sync]
before_action :set_assistant, only: [:create]
RESULTS_PER_PAGE = 25
def index
@documents = filtered_documents
@documents_count = @documents.count
@sync_interval_hours = current_sync_interval&.in_hours&.to_i
@documents = with_responses_count(@documents).page(@current_page).per(RESULTS_PER_PAGE)
end
def show; end
def create
return render_could_not_create_error('Missing Assistant') if @assistant.nil?
@document = @assistant.documents.build(document_params)
@document.save!
rescue Captain::Document::LimitExceededError => e
render_could_not_create_error(e.message)
rescue ActiveRecord::RecordInvalid => e
render_could_not_create_error(e.record.errors.full_messages.join(', '))
end
def sync
return render_could_not_create_error(I18n.t('captain.documents.sync_not_supported_for_pdf')) unless @document.syncable?
return render_could_not_create_error(I18n.t('captain.documents.sync_only_available_documents')) unless @document.available?
@document.update!(
sync_status: :syncing,
sync_step: nil,
last_sync_error_code: nil,
last_sync_attempted_at: Time.current
)
Captain::Documents::PerformSyncJob.perform_later(@document)
head :accepted
end
def destroy
@document.destroy
head :no_content
end
private
def set_documents
@documents = Current.account.captain_documents.with_attached_pdf_file.includes(:assistant)
end
def filtered_documents
documents = @documents
documents = documents.where(assistant_id: permitted_params[:assistant_id]) if permitted_params[:assistant_id].present?
documents = apply_source_filter(documents, permitted_params[:source])
documents = apply_filter(documents, permitted_params[:filter])
documents = apply_search(documents, permitted_params[:search_key])
apply_sort(documents, permitted_params[:sort])
end
def with_responses_count(scope)
scope.left_joins(:responses)
.select('captain_documents.*, COUNT(captain_assistant_responses.id) AS responses_count')
.group('captain_documents.id')
end
def set_document
@document = @documents.find(permitted_params[:id])
end
def set_assistant
@assistant = Current.account.captain_assistants.find_by(id: document_params[:assistant_id])
end
def set_current_page
@current_page = permitted_params[:page] || 1
end
def permitted_params
params.permit(:assistant_id, :page, :id, :account_id, :filter, :source, :sort, :search_key)
end
def apply_source_filter(scope, source)
case source
when 'web' then scope.syncable
when 'pdf' then scope.pdf_documents
else scope
end
end
def apply_filter(scope, filter)
case filter
when 'stale' then stale_documents(scope.syncable)
when 'synced' then up_to_date_documents(scope.syncable)
when 'syncing' then scope.syncable.sync_in_progress
when 'failed' then scope.syncable.sync_failed
else scope
end
end
def apply_search(scope, search_key)
return scope if search_key.blank?
query = "%#{ActiveRecord::Base.sanitize_sql_like(search_key)}%"
scope.where('captain_documents.name ILIKE :query OR captain_documents.external_link ILIKE :query', query: query)
end
def apply_sort(scope, sort)
case sort
when 'recently_created' then scope.order(created_at: :desc)
else scope.order(updated_at: :desc)
end
end
def stale_documents(scope)
return scope.none unless current_sync_interval
scope.sync_synced.where(Captain::Document.arel_table[:last_synced_at].lt(current_sync_interval.ago))
end
def up_to_date_documents(scope)
documents = scope.sync_synced
return documents unless current_sync_interval
documents.where(Captain::Document.arel_table[:last_synced_at].gteq(current_sync_interval.ago))
end
def current_sync_interval
return @current_sync_interval if defined?(@current_sync_interval)
@current_sync_interval = Current.account.captain_document_sync_interval
end
def document_params
params.require(:document).permit(:name, :external_link, :assistant_id, :pdf_file)
end
end