class FetchIpapiDataJob < ApplicationJob queue_as :default # Fetches IPAPI enrichment data for a NetworkRange # @param network_range_id [Integer] ID of the tracking NetworkRange (usually /24) def perform(network_range_id:) tracking_network = NetworkRange.find_by(id: network_range_id) return unless tracking_network # Use the network address (first IP in range) as the representative IP sample_ip = tracking_network.network_address.split('/').first Rails.logger.info "Fetching IPAPI data for #{tracking_network.cidr} using IP #{sample_ip}" ipapi_data = Ipapi.lookup(sample_ip) if ipapi_data.present? && !ipapi_data.key?('error') # Process IPAPI data and create network ranges result = Ipapi.process_ipapi_data(ipapi_data, tracking_network) # Mark the tracking network as having been queried # Use the broadest CIDR returned for deduplication tracking_network.mark_ipapi_queried!(result[:broadest_cidr]) Rails.logger.info "Successfully fetched IPAPI data for #{tracking_network.cidr} (created #{result[:networks].length} networks)" # Broadcast to the tracking network broadcast_ipapi_update(tracking_network, ipapi_data) else Rails.logger.warn "IPAPI returned error for #{tracking_network.cidr}: #{ipapi_data}" # Still mark as queried to avoid retrying immediately tracking_network.mark_ipapi_queried!(tracking_network.cidr) end rescue => e Rails.logger.error "Failed to fetch IPAPI data for network_range #{network_range_id}: #{e.message}" Rails.logger.error e.backtrace.join("\n") ensure # Always clear the fetching status when done tracking_network&.clear_fetching_status!(:ipapi) end private def broadcast_ipapi_update(network_range, ipapi_data) # Broadcast to a stream specific to this network range Turbo::StreamsChannel.broadcast_replace_to( "network_range_#{network_range.id}", target: "ipapi_data_section", partial: "network_ranges/ipapi_data", locals: { ipapi_data: ipapi_data, network_range: network_range, parent_with_ipapi: nil, ipapi_loading: false } ) end end