diff --git a/app/admin/cdr/cdr_exports.rb b/app/admin/cdr/cdr_exports.rb index 4c711d72e..b5b6c7b57 100644 --- a/app/admin/cdr/cdr_exports.rb +++ b/app/admin/cdr/cdr_exports.rb @@ -10,6 +10,10 @@ link_to 'Download', { action: :download } if resource.completed? end + action_item(:delete_file, only: [:show]) do + link_to('Delete File', { action: :delete_file }, method: :delete) if resource.completed? + end + index do selectable_column id_column @@ -34,6 +38,12 @@ render body: nil end + member_action :delete_file, method: :delete do + resource.update!(status: CdrExport::STATUS_DELETED) + flash[:notice] = 'The file will be deleted in background!' + redirect_back fallback_location: root_path + end + controller do def build_new_resource build_params = resource_params[0].to_h diff --git a/app/jobs/worker/remove_cdr_export_file_job.rb b/app/jobs/worker/remove_cdr_export_file_job.rb new file mode 100644 index 000000000..cc7a77a20 --- /dev/null +++ b/app/jobs/worker/remove_cdr_export_file_job.rb @@ -0,0 +1,44 @@ +require 'net/http' + +module Worker + class RemoveCdrExportFileJob < ActiveJob::Base + class FileNotDeletedError < RuntimeError + def initialize(http_code) + @http_code = http_code + end + + def message + "File was not deleted! http code: #{@http_code}" + end + end + + queue_as 'cdr_export' + ALLOWED_HTTP_CODES = [200, 404].freeze + + def perform(cdr_export_id) + url = URI.parse(delete_url(cdr_export_id)) + req = Net::HTTP::Delete.new(url.to_s) + res = Net::HTTP.start( + url.host, + url.port, + use_ssl: url.scheme == 'https', + verify_mode: OpenSSL::SSL::VERIFY_NONE + ) do |http| + http.request(req) + end + http_code = res.code.to_i + unless ALLOWED_HTTP_CODES.include?(http_code) + raise FileNotDeletedError.new(http_code) + end + end + + private + + def delete_url(cdr_export_id) + [ + Rails.configuration.yeti_web.fetch('cdr_export').fetch('delete_url').chomp('/'), + "#{cdr_export_id}.csv" + ].join('/') + end + end +end diff --git a/app/models/cdr_export.rb b/app/models/cdr_export.rb index f8b3cb907..580a450d5 100644 --- a/app/models/cdr_export.rb +++ b/app/models/cdr_export.rb @@ -20,10 +20,12 @@ class CdrExport < Yeti::ActiveRecord STATUS_PENDING = 'Pending'.freeze STATUS_COMPLETED = 'Completed'.freeze STATUS_FAILED = 'Failed'.freeze + STATUS_DELETED = 'Deleted'.freeze STATUSES = [ STATUS_PENDING, STATUS_COMPLETED, - STATUS_FAILED + STATUS_FAILED, + STATUS_DELETED ].freeze #need for activeadmin form @@ -57,6 +59,10 @@ class CdrExport < Yeti::ActiveRecord Worker::CdrExportJob.perform_later(self.id) end + after_update if: proc { saved_change_to_attribute?(:status) && deleted? } do + Worker::RemoveCdrExportFileJob.perform_later(self.id) + end + alias_attribute :export_type, :type def export_sql @@ -67,6 +73,10 @@ def completed? status == STATUS_COMPLETED end + def deleted? + status == STATUS_DELETED + end + def self.allowed_filters [ 'time_start_lteq', diff --git a/app/resources/api/rest/admin/cdr/cdr_export_resource.rb b/app/resources/api/rest/admin/cdr/cdr_export_resource.rb index ba0887605..2de5551c8 100644 --- a/app/resources/api/rest/admin/cdr/cdr_export_resource.rb +++ b/app/resources/api/rest/admin/cdr/cdr_export_resource.rb @@ -1,5 +1,6 @@ class Api::Rest::Admin::Cdr::CdrExportResource < ::BaseResource model_name 'CdrExport' + model_hint model: CdrExport::Base, resource: self attributes :fields, :filters, @@ -11,4 +12,9 @@ class Api::Rest::Admin::Cdr::CdrExportResource < ::BaseResource def self.creatable_fields(_context) [:fields, :filters, :callback_url, :export_type] end + + def _remove + @model.update!(status: CdrExport::STATUS_DELETED) + :completed + end end diff --git a/config/routes.rb b/config/routes.rb index 95dc13d7e..4bd0e7a7a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -85,7 +85,7 @@ end jsonapi_resources :auth_logs, only: [:index, :show] do end - jsonapi_resources :cdr_exports, only: [:create] + jsonapi_resources :cdr_exports, only: [:create, :destroy] end namespace :billing do diff --git a/config/yeti_web.yml.distr b/config/yeti_web.yml.distr index 1d384acd4..6bcb26011 100644 --- a/config/yeti_web.yml.distr +++ b/config/yeti_web.yml.distr @@ -4,3 +4,4 @@ api: token_lifetime: 600 # jwt token lifetime in seconds, empty string means permanent tokens cdr_export: dir_path: "/tmp" + delete_url: http://localhost/cdrs_export/ diff --git a/spec/controllers/api/rest/admin/cdr/cdr_exports_controller_spec.rb b/spec/controllers/api/rest/admin/cdr/cdr_exports_controller_spec.rb index 030a8a923..e655dce37 100644 --- a/spec/controllers/api/rest/admin/cdr/cdr_exports_controller_spec.rb +++ b/spec/controllers/api/rest/admin/cdr/cdr_exports_controller_spec.rb @@ -11,6 +11,26 @@ request.headers['Authorization'] = auth_token end + describe 'DELETE destroy' do + subject { delete :destroy, params: { id: cdr_export.id } } + let(:cdr_export) do + FactoryGirl.create(:cdr_export, :completed) + end + + it 'http code should be 204' do + subject + expect(response).to have_http_status(204) + end + + it 'status should be changed to removed' do + expect { subject }.to change { cdr_export.reload.status }.from(CdrExport::STATUS_COMPLETED).to(CdrExport::STATUS_DELETED) + end + + it 'remove file job should be enqueued' do + expect { subject }.to have_enqueued_job(Worker::RemoveCdrExportFileJob).with(cdr_export.id) + end + end + describe 'POST create' do subject { post :create, params: payload } let(:payload) do diff --git a/spec/controllers/api/rest/admin/cdr/cdrs_controller_spec.rb b/spec/controllers/api/rest/admin/cdr/cdrs_controller_spec.rb index 81a9a7e8d..3fb3fe7e9 100644 --- a/spec/controllers/api/rest/admin/cdr/cdrs_controller_spec.rb +++ b/spec/controllers/api/rest/admin/cdr/cdrs_controller_spec.rb @@ -470,13 +470,13 @@ end end - describe 'PATCH create' do + describe 'PATCH update' do it 'PATCH should not be routable', type: :routing do expect(patch: '/api/rest/admin/cdr/cdrs/123').to_not be_routable end end - describe 'DELETE create' do + describe 'DELETE destroy' do it 'DELETE should not be routable', type: :routing do expect(delete: '/api/rest/admin/cdr/cdrs/123').to_not be_routable end diff --git a/spec/jobs/worker/remove_cdr_export_file_job_spec.rb b/spec/jobs/worker/remove_cdr_export_file_job_spec.rb new file mode 100644 index 000000000..99796f12d --- /dev/null +++ b/spec/jobs/worker/remove_cdr_export_file_job_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +RSpec.describe Worker::RemoveCdrExportFileJob, type: :job do + subject do + described_class.perform_now(cdr_export.id) + end + + let(:cdr_export) do + FactoryGirl.create(:cdr_export, :completed) + end + + let(:delete_url) do + [ + Rails.configuration.yeti_web.fetch('cdr_export').fetch('delete_url').chomp('/'), + "#{cdr_export.id}.csv" + ].join('/') + end + + let!(:http_mock) do + stub_request(:delete, delete_url).to_return(status: http_code) + end + + shared_examples :valid_deleting do + it 'http mock should be requested' do + subject + expect(http_mock).to have_been_requested.once + end + + it 'nothing should be raised' do + expect { subject }.not_to raise_error + end + end + + context 'when http code 200' do + let(:http_code) do + 200 + end + + include_examples :valid_deleting + end + + context 'when http code 404' do + let(:http_code) do + 404 + end + + include_examples :valid_deleting + end + + context 'when http code 400' do + let(:http_code) do + 400 + end + + it 'error should be raised' do + expect { subject }.to raise_error(Worker::RemoveCdrExportFileJob::FileNotDeletedError, 'File was not deleted! http code: 400') + end + end +end