From f7d4c41d072e064f668711a3e5512bf37ec49129 Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Sun, 19 Apr 2026 01:41:09 -0300 Subject: [PATCH] feat(captain-memory): add MemoriesController with index/update/destroy/bulk_destroy --- config/routes.rb | 16 +++++- .../accounts/contacts/memories_controller.rb | 45 +++++++++++++++++ .../contacts/memories_controller_spec.rb | 50 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 enterprise/app/controllers/api/v1/accounts/contacts/memories_controller.rb create mode 100644 spec/enterprise/controllers/api/v1/accounts/contacts/memories_controller_spec.rb diff --git a/config/routes.rb b/config/routes.rb index d279b0a6a..a683dcc04 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,6 +83,9 @@ Rails.application.routes.draw do end member do get :pix + post :cancel + post :mark_as_paid + post :regenerate_pix end end resource :tasks, only: [], controller: 'tasks' do @@ -101,7 +104,11 @@ Rails.application.routes.draw do end end namespace :reports do - resource :operational, only: [:show], controller: 'reports/operational' + resource :operational, only: [:show], controller: :operational + resource :executive, only: [:show], controller: :executive do + get :drilldown + post :deliver + end resources :insights, only: [:index, :show] do post :generate, on: :collection end @@ -218,6 +225,13 @@ Rails.application.routes.draw do resources :labels, only: [:create, :index] resources :notes post :call, on: :member, to: 'calls#create' if ChatwootApp.enterprise? + if ChatwootApp.enterprise? + resources :memories, only: %i[index update destroy], controller: 'memories' do + collection do + delete :bulk_destroy, path: '' + end + end + end end end resources :csat_survey_responses, only: [:index] do diff --git a/enterprise/app/controllers/api/v1/accounts/contacts/memories_controller.rb b/enterprise/app/controllers/api/v1/accounts/contacts/memories_controller.rb new file mode 100644 index 000000000..347a69850 --- /dev/null +++ b/enterprise/app/controllers/api/v1/accounts/contacts/memories_controller.rb @@ -0,0 +1,45 @@ +class Api::V1::Accounts::Contacts::MemoriesController < Api::V1::Accounts::BaseController + before_action :set_contact + before_action :set_memory, only: %i[update destroy] + + def index + @memories = Captain::ContactMemory.active.for_contact(@contact.id).order(created_at: :desc) + authorize(@memories.first || Captain::ContactMemory.new(account: @contact.account)) if @memories.any? + render json: { data: @memories.as_json(except: :embedding) } + end + + def update + authorize(@memory) + @memory.update!(memory_params) + Captain::ContactMemories::UpdateEmbeddingJob.perform_later(@memory.id) if @memory.saved_change_to_content? + render json: @memory.as_json(except: :embedding) + end + + def destroy + authorize(@memory) + @memory.soft_delete! + head :no_content + end + + def bulk_destroy + authorize(Captain::ContactMemory.new(account: @contact.account)) + # rubocop:disable Rails/SkipsModelValidations + Captain::ContactMemory.active.for_contact(@contact.id).update_all(deleted_at: Time.current, updated_at: Time.current) + # rubocop:enable Rails/SkipsModelValidations + head :no_content + end + + private + + def set_contact + @contact = Current.account.contacts.find(params[:contact_id]) + end + + def set_memory + @memory = Captain::ContactMemory.for_contact(@contact.id).find(params[:id]) + end + + def memory_params + params.permit(:content, :memory_type, :scope) + end +end diff --git a/spec/enterprise/controllers/api/v1/accounts/contacts/memories_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/contacts/memories_controller_spec.rb new file mode 100644 index 000000000..e37ca2485 --- /dev/null +++ b/spec/enterprise/controllers/api/v1/accounts/contacts/memories_controller_spec.rb @@ -0,0 +1,50 @@ +require 'rails_helper' + +RSpec.describe Api::V1::Accounts::Contacts::MemoriesController, type: :request do + let(:account) { create(:account) } + let(:admin) { create(:user, account: account, role: :administrator) } + let(:contact) { create(:contact, account: account) } + let!(:memory) { create(:captain_contact_memory, account: account, contact: contact) } + + describe 'GET #index' do + it 'returns memories for the contact' do + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/memories", + headers: admin.create_new_auth_token + + expect(response).to have_http_status(:ok) + expect(response.parsed_body['data'].size).to eq(1) + end + end + + describe 'PATCH #update' do + it 'updates content and re-embeds' do + expect do + patch "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/memories/#{memory.id}", + params: { content: 'updated content' }, + headers: admin.create_new_auth_token + end.to have_enqueued_job(Captain::ContactMemories::UpdateEmbeddingJob) + + expect(memory.reload.content).to eq('updated content') + end + end + + describe 'DELETE #destroy' do + it 'soft-deletes' do + delete "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/memories/#{memory.id}", + headers: admin.create_new_auth_token + + expect(memory.reload.deleted_at).not_to be_nil + end + end + + describe 'DELETE collection (forget all)' do + it 'soft-deletes all memories for the contact' do + create_list(:captain_contact_memory, 3, account: account, contact: contact) + + delete "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/memories", + headers: admin.create_new_auth_token + + expect(Captain::ContactMemory.for_contact(contact.id).where(deleted_at: nil).count).to eq(0) + end + end +end