require "redis/namespace" require "new_base_60" require "sinatra" require "uri" url = ENV.values_at("OSO_REDIS_URL", "REDISTOGO_URL").compact.first url = URI.parse url || "redis://localhost:6379" $redis = Redis::Namespace.new :oso, redis: Redis.new(host: url.host, password: url.password, port: url.port) helpers do def bad! message halt 412, {}, message end def nope! halt 404, {}, "No luck." end def save short, url, limit, life longkey = "long:#{url}" shortkey = "short:#{short}" limitkey = "#{shortkey}:limit" $redis.multi do $redis.set limitkey, limit if limit $redis.set longkey, short $redis.set shortkey, url end if life $redis.expire limitkey, limit if limit $redis.expire longkey, life $redis.expire shortkey, life end end def shorturl short request.path_info = "/#{short}" request.url end end get "/" do IO.read "#{settings.public}/index.html" end get "/stats" do @count = $redis.get(:counter).to_i @hits = $redis.get(:hits).to_i @misses = $redis.get(:misses).to_i @byhits = Hash[*$redis.zrevrange("by:hits", 0, 10, :with_scores => true)] @bytimes = Hash[*$redis.zrevrange("by:time", 0, 10, :with_scores => true)] [@byhits, @bytimes].each do |h| h.each { |k, v| h[k] = { long: $redis.get("short:#{k}"), score: v } } end @title = "Stats" erb :stats end post "/" do bad! "Missing url." unless url = params[:url] url = "http://#{url}" unless /^http/i =~ url bad! "Malformed url." unless (u = URI.parse url) && /^http/ =~ u.scheme life = params[:life].to_i if params[:life] limit = params[:limit].to_i if params[:limit] short = params[:name] if params[:name] && /[-a-z0-9]+/i =~ params[:name] bad! "Name is already taken." if short && $redis.exists("short:#{short}") if !short && existing = $redis.get("long:#{url}") halt 201, {}, shorturl(existing) end short ||= $redis.incr(:counter).to_sxg save short, url, limit, life [201, {}, shorturl(short)] end get "/:short" do |short| long = $redis.get "short:#{short}" $redis.incr(:misses) and nope! unless long limited = $redis.exists("short:#{short}:limit") && $redis.decr("short:#{short}:limit") < 0 if limited %W(long:#{long} short:#{short} short:#{short}:limit).each do |key| $redis.del key end nope! end $redis.incr :hits $redis.zincrby "by:hits", 1, short $redis.zadd "by:time", Time.now.utc.to_i, short redirect long end