refactor: replace ClickController class with struct

This commit is contained in:
sjdonado
2025-03-20 20:42:51 +01:00
parent f2b63c00a3
commit 4ae6ef39d5
4 changed files with 76 additions and 65 deletions
+37 -34
View File
@@ -4,52 +4,55 @@ UserAgent.load_regexes(File.read("data/uap_core_regexes.yaml"))
IpLookup.load_mmdb("data/GeoLite2-Country.mmdb")
module App::Controllers
class ClickController
struct ClickController
include App::Models
include App::Lib
include App::Services
def initialize(@env : HTTP::Server::Context); end
def self.redirect_handler
->(env : HTTP::Server::Context) {
slug = env.params.url["slug"]
def redirect
slug = @env.params.url["slug"]
link_data = nil
Database.raw_query("SELECT id, url FROM links WHERE slug = (?) LIMIT 1", slug) do |result|
if result.move_next
link_data = {result.read(Int64), result.read(String)}
link_data = nil
Database.raw_query("SELECT id, url FROM links WHERE slug = (?) LIMIT 1", slug) do |result|
if result.move_next
link_data = {result.read(Int64), result.read(String)}
end
end
end
raise App::NotFoundException.new(@env) unless link_data
raise App::NotFoundException.new(env) unless link_data
link_id, url = link_data
client_ip = IpLookup.ip_from_address(@env.request.headers["Cf-Connecting-Ip"]? || @env.request.remote_address.to_s)
link_id, url = link_data
client_ip = IpLookup.ip_from_address(env.request.headers["Cf-Connecting-Ip"]? || env.request.remote_address.to_s)
@env.response.status_code = 301
@env.response.headers["Location"] = url
@env.response.headers["X-Forwarded-For"] = client_ip.to_s
@env.response.headers["Connection"] = "close"
spawn do
begin
user_agent_str = env.request.headers["User-Agent"]?
referer = env.request.headers["Referer"]?.try { |r| URI.parse(r).host rescue r } || env.params.query["utm_source"]? || "Direct"
spawn do
begin
user_agent_str = @env.request.headers["User-Agent"]?
referer = @env.request.headers["Referer"]?.try { |r| URI.parse(r).host rescue r } || @env.params.query["utm_source"]? || "Direct"
ua_parser = user_agent_str ? UserAgent.new(user_agent_str) : nil
ua_parser = user_agent_str ? UserAgent.new(user_agent_str) : nil
click = App::Models::Click.new
click.link_id = link_id
click.country = client_ip ? IpLookup.new(client_ip).try(&.country.try(&.code)) : nil
click.user_agent = ua_parser.to_s
click.browser = ua_parser.try(&.family)
click.os = ua_parser.try(&.os.try(&.family))
click.referer = referer
click = App::Models::Click.new
click.link_id = link_id
click.country = client_ip ? IpLookup.new(client_ip).try(&.country.try(&.code)) : nil
click.user_agent = ua_parser.to_s
click.browser = ua_parser.try(&.family)
click.os = ua_parser.try(&.os.try(&.family))
click.referer = referer
Database.insert(click)
rescue ex
Log.error { "Click tracking error: #{ex.message}" }
Database.insert(click)
rescue ex
Log.error { "Click tracking error: #{ex.message}" }
end
end
end
env.response.status_code = 301
env.response.headers.add("Location", url)
env.response.headers.add("X-Forwarded-For", client_ip.to_s)
env.response.print ""
env.response.close
return
}
end
end
end
-9
View File
@@ -55,12 +55,3 @@ module App
end
end
end
error 500 do |env|
App::InternalServerErrorException.new(env)
""
end
error 404 do |env|
""
end
+36 -17
View File
@@ -1,26 +1,40 @@
require "./controllers/**"
module App
# CORS handling middleware
before_all do |env|
if env.request.path.starts_with?("/api/")
env.response.headers["Access-Control-Allow-Origin"] = "*"
env.response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
env.response.headers["Access-Control-Allow-Headers"] = "Content-Type, Accept, Origin, X-Api-Key"
require "kemal"
class CORSHandler < Kemal::Handler
exclude ["/:slug"]
def initialize(
@allow_origin = "*",
@allow_methods = "GET, POST, PUT, DELETE, OPTIONS",
@allow_headers = "Content-Type, Accept, Origin, X-Api-Key"
)
end
def call(context)
return call_next(context) if exclude_match?(context)
context.response.headers["Access-Control-Allow-Origin"] = @allow_origin
context.response.headers["Access-Control-Allow-Methods"] = @allow_methods
context.response.headers["Access-Control-Allow-Headers"] = @allow_headers
# If this is a preflight OPTIONS request, we return immediately with 200
if context.request.method == "OPTIONS"
context.response.status_code = 200
context.response.content_type = "text/plain"
context.response.print("")
return context
end
end
# Error handling middleware
error 404 do |env|
{error: "Not Found"}.to_json
end
error 500 do |env|
{error: "Internal Server Error"}.to_json
call_next(context)
end
end
get "/:slug" do |env|
Controllers::ClickController.new(env).redirect
end
add_handler CORSHandler.new
module App
get "/:slug", &App::Controllers::ClickController.redirect_handler
# Namespace /api
get "/api/ping" do |env|
@@ -50,4 +64,9 @@ module App
delete "/api/links/:id" do |env|
Controllers::LinkController.new(env).delete
end
error 500 do |env|
App::InternalServerErrorException.new(env)
""
end
end