From 3700a810c7870350d8373778415ef5340c0fb71b Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Tue, 29 Jul 2025 19:48:40 +0530 Subject: [PATCH] feat: job to delete orphaned custom domains --- .../jobs/enterprise/cloudflare_cleanup_job.rb | 39 +++++++++++++++++++ .../delete_custom_hostname_service.rb | 17 ++++++++ .../list_custom_hostnames_service.rb | 14 +++++++ 3 files changed, 70 insertions(+) create mode 100644 enterprise/app/jobs/enterprise/cloudflare_cleanup_job.rb create mode 100644 enterprise/app/services/cloudflare/delete_custom_hostname_service.rb create mode 100644 enterprise/app/services/cloudflare/list_custom_hostnames_service.rb diff --git a/enterprise/app/jobs/enterprise/cloudflare_cleanup_job.rb b/enterprise/app/jobs/enterprise/cloudflare_cleanup_job.rb new file mode 100644 index 000000000..4b944dd8b --- /dev/null +++ b/enterprise/app/jobs/enterprise/cloudflare_cleanup_job.rb @@ -0,0 +1,39 @@ +class Enterprise::CloudflareCleanupJob < ApplicationJob + queue_as :housekeeping + + def perform + return unless ChatwootApp.chatwoot_cloud? + + Rails.logger.info 'Starting Cloudflare custom hostname cleanup' + + result = Cloudflare::ListCustomHostnamesService.new.perform + + if result[:errors].present? + Rails.logger.error "Failed to fetch custom hostnames from Cloudflare: #{result[:errors]}" + return + end + + cloudflare_hostnames = result[:data] || [] + existing_domains = Portal.where.not(custom_domain: [nil, '']).pluck(:custom_domain) + + orphaned_hostnames = cloudflare_hostnames.reject do |hostname| + existing_domains.include?(hostname['hostname']) + end + + Rails.logger.info "Found #{orphaned_hostnames.size} orphaned custom hostnames to cleanup" + + orphaned_hostnames.each do |hostname| + cleanup_result = Cloudflare::DeleteCustomHostnameService.new( + hostname_id: hostname['id'] + ).perform + + if cleanup_result[:errors].present? + Rails.logger.error "Failed to delete hostname #{hostname['hostname']}: #{cleanup_result[:errors]}" + else + Rails.logger.info "Successfully deleted orphaned hostname: #{hostname['hostname']}" + end + end + + Rails.logger.info 'Completed Cloudflare custom hostname cleanup' + end +end diff --git a/enterprise/app/services/cloudflare/delete_custom_hostname_service.rb b/enterprise/app/services/cloudflare/delete_custom_hostname_service.rb new file mode 100644 index 000000000..a70d95b25 --- /dev/null +++ b/enterprise/app/services/cloudflare/delete_custom_hostname_service.rb @@ -0,0 +1,17 @@ +class Cloudflare::DeleteCustomHostnameService < Cloudflare::BaseCloudflareZoneService + pattr_initialize [:hostname_id!] + + def perform + return { errors: ['Cloudflare API token or zone ID not found'] } if api_token.blank? || zone_id.blank? + return { errors: ['Hostname ID is required'] } if @hostname_id.blank? + + response = HTTParty.delete( + "#{BASE_URI}/zones/#{zone_id}/custom_hostnames/#{@hostname_id}", + headers: headers + ) + + return { errors: response.parsed_response['errors'] } unless response.success? + + { success: true } + end +end diff --git a/enterprise/app/services/cloudflare/list_custom_hostnames_service.rb b/enterprise/app/services/cloudflare/list_custom_hostnames_service.rb new file mode 100644 index 000000000..c877e7925 --- /dev/null +++ b/enterprise/app/services/cloudflare/list_custom_hostnames_service.rb @@ -0,0 +1,14 @@ +class Cloudflare::ListCustomHostnamesService < Cloudflare::BaseCloudflareZoneService + def perform + return { errors: ['Cloudflare API token or zone ID not found'] } if api_token.blank? || zone_id.blank? + + response = HTTParty.get( + "#{BASE_URI}/zones/#{zone_id}/custom_hostnames", + headers: headers + ) + + return { errors: response.parsed_response['errors'] } unless response.success? + + { data: response.parsed_response['result'] } + end +end