More use of tags - drop add_header action -> allow + headers+tags
This commit is contained in:
@@ -34,7 +34,8 @@ class EventDdb
|
||||
SQL
|
||||
|
||||
# Convert to hash like ActiveRecord .group.count returns
|
||||
result.to_a.to_h { |row| [row["waf_action"], row["count"]] }
|
||||
# DuckDB returns arrays: [waf_action, count]
|
||||
result.to_a.to_h { |row| [row[0], row[1]] }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in breakdown_by_action: #{e.message}"
|
||||
@@ -54,7 +55,8 @@ class EventDdb
|
||||
SQL
|
||||
|
||||
# Return array of [country, count] tuples like ActiveRecord
|
||||
result.to_a.map { |row| [row["country"], row["count"]] }
|
||||
# DuckDB returns arrays: [country, count]
|
||||
result.to_a.map { |row| [row[0], row[1]] }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in top_countries: #{e.message}"
|
||||
@@ -73,7 +75,8 @@ class EventDdb
|
||||
LIMIT ?
|
||||
SQL
|
||||
|
||||
result.to_a.map { |row| [row["ip_address"], row["count"]] }
|
||||
# DuckDB returns arrays: [ip_address, count]
|
||||
result.to_a.map { |row| [row[0], row[1]] }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in top_blocked_ips: #{e.message}"
|
||||
@@ -94,7 +97,8 @@ class EventDdb
|
||||
SQL
|
||||
|
||||
# Convert to hash with Time keys like ActiveRecord
|
||||
result.to_a.to_h { |row| [row["hour"], row["count"]] }
|
||||
# DuckDB returns arrays: [hour, count]
|
||||
result.to_a.to_h { |row| [row[0], row[1]] }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in hourly_timeline: #{e.message}"
|
||||
@@ -495,5 +499,128 @@ class EventDdb
|
||||
Rails.logger.error "[EventDdb] Error in suspicious_patterns: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
# Bot traffic analysis - breakdown of bot vs human traffic
|
||||
def bot_traffic_breakdown(start_time)
|
||||
service.with_connection do |conn|
|
||||
result = conn.query(<<~SQL, start_time)
|
||||
SELECT
|
||||
is_bot,
|
||||
COUNT(*) as event_count,
|
||||
COUNT(DISTINCT ip_address) as unique_ips
|
||||
FROM events
|
||||
WHERE timestamp >= ?
|
||||
GROUP BY is_bot
|
||||
SQL
|
||||
|
||||
# Convert to hash: is_bot => { event_count, unique_ips }
|
||||
# DuckDB returns arrays: [is_bot, event_count, unique_ips]
|
||||
result.to_a.to_h do |row|
|
||||
[
|
||||
row[0] ? "bot" : "human", # row[0] = is_bot
|
||||
{
|
||||
"event_count" => row[1], # row[1] = event_count
|
||||
"unique_ips" => row[2] # row[2] = unique_ips
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in bot_traffic_breakdown: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
# Count human traffic (non-bot) since timestamp
|
||||
def human_traffic_count(start_time)
|
||||
service.with_connection do |conn|
|
||||
result = conn.query(<<~SQL, start_time)
|
||||
SELECT COUNT(*) as count
|
||||
FROM events
|
||||
WHERE timestamp >= ? AND is_bot = false
|
||||
SQL
|
||||
|
||||
result.first&.first || 0
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in human_traffic_count: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
# Count bot traffic since timestamp
|
||||
def bot_traffic_count(start_time)
|
||||
service.with_connection do |conn|
|
||||
result = conn.query(<<~SQL, start_time)
|
||||
SELECT COUNT(*) as count
|
||||
FROM events
|
||||
WHERE timestamp >= ? AND is_bot = true
|
||||
SQL
|
||||
|
||||
result.first&.first || 0
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in bot_traffic_count: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
# Top bot user agents
|
||||
def top_bot_user_agents(start_time, limit = 20)
|
||||
service.with_connection do |conn|
|
||||
result = conn.query(<<~SQL, start_time, limit)
|
||||
SELECT
|
||||
user_agent,
|
||||
COUNT(*) as event_count,
|
||||
COUNT(DISTINCT ip_address) as unique_ips
|
||||
FROM events
|
||||
WHERE timestamp >= ? AND is_bot = true AND user_agent IS NOT NULL
|
||||
GROUP BY user_agent
|
||||
ORDER BY event_count DESC
|
||||
LIMIT ?
|
||||
SQL
|
||||
|
||||
# DuckDB returns arrays: [user_agent, event_count, unique_ips]
|
||||
result.to_a.map do |row|
|
||||
{
|
||||
user_agent: row[0], # row[0] = user_agent
|
||||
event_count: row[1], # row[1] = event_count
|
||||
unique_ips: row[2] # row[2] = unique_ips
|
||||
}
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in top_bot_user_agents: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
# Bot traffic timeline (hourly breakdown)
|
||||
def bot_traffic_timeline(start_time, end_time)
|
||||
service.with_connection do |conn|
|
||||
result = conn.query(<<~SQL, start_time, end_time)
|
||||
SELECT
|
||||
DATE_TRUNC('hour', timestamp) as hour,
|
||||
SUM(CASE WHEN is_bot = true THEN 1 ELSE 0 END) as bot_count,
|
||||
SUM(CASE WHEN is_bot = false THEN 1 ELSE 0 END) as human_count
|
||||
FROM events
|
||||
WHERE timestamp >= ? AND timestamp < ?
|
||||
GROUP BY hour
|
||||
ORDER BY hour
|
||||
SQL
|
||||
|
||||
# Convert to hash with Time keys
|
||||
# DuckDB returns arrays: [hour, bot_count, human_count]
|
||||
result.to_a.to_h do |row|
|
||||
[
|
||||
row[0], # row[0] = hour
|
||||
{
|
||||
"bot_count" => row[1], # row[1] = bot_count
|
||||
"human_count" => row[2], # row[2] = human_count
|
||||
"total" => row[1] + row[2]
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[EventDdb] Error in bot_traffic_timeline: #{e.message}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user