Add 'tags' to event model. Add a dataimport system - currently for MaxMind zip files

This commit is contained in:
Dan Milne
2025-11-11 10:31:36 +11:00
parent 772fae7e8b
commit 26216da9ca
34 changed files with 3580 additions and 14 deletions

View File

@@ -25,6 +25,10 @@ class Event < ApplicationRecord
# Serialize segment IDs as array for easy manipulation in Railssqit
serialize :request_segment_ids, type: Array, coder: JSON
# Tags are stored as JSON arrays with PostgreSQL jsonb type
# This provides direct array access and efficient indexing
attribute :tags, :json, default: -> { [] }
validates :event_id, presence: true, uniqueness: true
validates :timestamp, presence: true
@@ -36,6 +40,21 @@ class Event < ApplicationRecord
scope :allowed, -> { where(waf_action: :allow) }
scope :rate_limited, -> { where(waf_action: 'rate_limit') }
# Tag-based filtering scopes using PostgreSQL array operators
scope :with_tag, ->(tag) { where("tags @> ARRAY[?]", tag.to_s) }
scope :with_any_tags, ->(tags) {
return none if tags.blank?
tag_array = Array(tags).map(&:to_s)
where("tags && ARRAY[?]", tag_array)
}
scope :with_all_tags, ->(tags) {
return none if tags.blank?
tag_array = Array(tags).map(&:to_s)
where("tags @> ARRAY[?]", tag_array)
}
# Network-based filtering scopes
scope :by_company, ->(company) {
joins("JOIN network_ranges ON events.ip_address <<= network_ranges.network")
@@ -234,7 +253,8 @@ class Event < ApplicationRecord
end
def tags
payload&.dig("tags") || {}
# Use the dedicated tags column (array), fallback to payload during transition
super.presence || (payload&.dig("tags") || [])
end
def headers
@@ -281,6 +301,25 @@ class Event < ApplicationRecord
URI.parse(request_url).hostname rescue nil
end
# Tag helper methods
def add_tag(tag)
tag_str = tag.to_s
self.tags = (tags + [tag_str]).uniq unless tags.include?(tag_str)
end
def remove_tag(tag)
tag_str = tag.to_s
self.tags = tags - [tag_str] if tags.include?(tag_str)
end
def has_tag?(tag)
tags.include?(tag.to_s)
end
def tag_list
tags.join(', ')
end
# Normalize headers to lower case keys during import phase
def normalize_headers(headers)
return {} unless headers.is_a?(Hash)