feat: country ip lookup

This commit is contained in:
sjdonado
2025-03-16 11:37:29 +01:00
parent ece74226d4
commit 8cab7a51ad
8 changed files with 139 additions and 17 deletions
+19 -11
View File
@@ -1,9 +1,11 @@
require "uuid"
require "user_agent_parser"
UserAgent.load_regexes(File.read("data/regexes.yaml"))
require "../lib/controller.cr"
require "../lib/ip_lookup"
UserAgent.load_regexes(File.read("data/regexes.yaml"))
IpLookup.load_mmdb("data/GeoLite2-Country.mmdb")
module App::Controllers::Link
class Create < App::Lib::BaseController
@@ -51,12 +53,10 @@ module App::Controllers::Link
link = Database.get_by(Link, slug: slug)
raise App::NotFoundException.new(env) if !link
client_ip = env.request.remote_address.try &.to_s || "Unknown"
remote_address = env.request.remote_address.try &.to_s
user_agent_str = env.request.headers["User-Agent"]? || "Unknown"
user_agent = user_agent_str != "Unknown" ? UserAgent.new(user_agent_str) : nil
language_header = env.request.headers["Accept-Language"]? || "Unknown"
language = language_header.split(',').first.split(';').first
referer = env.request.headers["Referer"]?
client_ip = IpLookup.extract_ip(remote_address) || "Unknown"
env.response.status_code = 301
env.response.headers["Location"] = link.url!
@@ -65,14 +65,22 @@ module App::Controllers::Link
env.response.headers["X-Forwarded-User-Agent"] = user_agent_str
spawn do
ip_lookup = client_ip != "Unknown" ? IpLookup.new(client_ip) : nil
country = ip_lookup.try &.country.try &.code
user_agent = user_agent_str != "Unknown" ? UserAgent.new(user_agent_str) : nil
source = env.params.query["utm_source"]? || "Direct"
referer_host = env.request.headers["Referer"]?.try { |r| begin URI.parse(r).host rescue r end } || source
click = Click.new
click.id = UUID.v4.to_s
click.link = link
click.language = language
click.country = country
click.user_agent = user_agent_str
click.browser = user_agent ? user_agent.family : "Unknown"
click.os = user_agent ? (user_agent.os.try &.family || "Unknown") : "Unknown"
click.referer = referer ? URI.parse(referer).host : "Unknown"
click.browser = user_agent.try &.family
click.os = user_agent.try &.os.try &.family
click.referer = referer_host
changeset = Database.insert(click)
if changeset.errors.any?
+54
View File
@@ -0,0 +1,54 @@
require "maxminddb"
class IpLookup
@@instance : MaxMindDB::Reader? = nil
record Country, code : String? = nil, name : String? = nil
getter ip : String
getter country : Country?
def self.load_mmdb(mmdb_file_path : String)
@@instance = MaxMindDB.open(mmdb_file_path)
end
def initialize(ip_address : String)
@ip = ip_address
@country = nil
return if @@instance.nil? || ip_address == "Unknown" || ip_address.empty?
begin
lookup = @@instance.not_nil!.get(ip_address)
country_code = lookup["country"]?.try &.["iso_code"]?.try &.as_s
country_name = lookup["country"]?.try &.["names"]?.try &.["en"]?.try &.as_s
if country_code || country_name
@country = Country.new(
code: country_code,
name: country_name
)
end
rescue ex
# Silently handle lookup errors
Log.error { "IP lookup failed: #{ex.message}" }
end
end
def self.extract_ip(address_string : String?) : String?
return nil if address_string.nil?
if address_string.includes?('[') # IPv6 with port: [2001:db8::1]:8080
address_string.split(']').first.sub('[', '\'')
elsif address_string.includes?(':')
if address_string.count(':') > 1 # IPv6 without port
address_string
else # IPv4 with port: 192.168.1.1:8080
address_string.split(':').first
end
else # Address without port
address_string
end
end
end
+1 -1
View File
@@ -13,6 +13,6 @@ module App::Models
belongs_to :link, Link
end
validate_required [:user_agent, :country, :referer]
validate_required [:user_agent, :referer]
end
end