Wrote this because I thought it would be cool. Wanted to make it as small and simple as possible.Took 4 hours because DataMapper was being weird and I don't do a lot of Ruby (or much programming in general these days ;_;).
Anyway, the code:
# linkshorten.rb
# Sinatra app to shorten urls.
# Date: 6th October 2014
# Author: Fur
# DataMapper stuff taken from: https://github.com/tomaszj/sinatra-datamapper-sample/blob/master/app.rb
# TODO:
# Deduplicate argument validation (but where to put the functions?).
require 'rubygems'
require 'uri'
require 'sinatra'
require 'data_mapper'
require 'json'
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/links.db")
class ShortenedUrl
include DataMapper::Resource
property :id, Serial
property :url, String, :required => true
end
DataMapper.finalize.auto_upgrade!
class Base32
# Base32 encode a number.
def self.encode(number)
number.to_s(32)
end
# Decode a base32-encoded string
def self.decode(str)
str.to_i(32)
end
# Is str encoded in base32?
def self.is_encoded?(str)
str =~ /[a-z0-9]+/
end
end
class LinkShortener
# Shortens a link and returns its code
def self.shorten(url)
if url !~ URI::regexp
throw ArgumentError, "url must be a valid uri"
end
Base32.encode(ShortenedUrl.first_or_create(url: url).id)
end
# Gets the long url for a short url code
def self.expand(code)
if !Base32.is_encoded?(code)
throw ArgumentError, "code must be a valid base32 string"
end
ShortenedUrl.get(Base32.decode(code)).url
end
# Gets the short url (like domain.tld/aaaa3) for a code and the route which will redirect short urls to their long ones.
def self.code_to_short_url(code, base_url)
(base_url.end_with?('/') ? base_url : base_url + '/') + code
end
def self.valid_code?(code)
Base32.is_encoded?(code) && ShortenedUrl.all(:id => Base32.decode(code)).count > 0
end
end
class Main < Sinatra::Base
set :environment, :production
set :show_exceptions, false
post '/shorten' do
if params[:url] !~ URI::regexp
JSON.generate({:error => "url is not valid"})
else
code = LinkShortener.shorten params[:url]
JSON.generate({:shortenedUrl => LinkShortener.code_to_short_url(code, request.base_url)})
end
end
get '/:code' do
if !LinkShortener.valid_code?(params[:code])
"Invalid url"
else
redirect LinkShortener.expand(params[:code]), 302
end
end
end
Main.run!
Dependency installation (on Ubuntu at least):
sudo gem install sinatra
sudo apt-get install libsqlite3-dev
sudo apt-get install ruby1.9.1-dev # Sometimes data_mapper fails to build native modules. This fixes it.
sudo gem install data_mapper
CHANGELOG:
Reorganised code
Removed code attribute in JSON output.
Now uses dynamic JSON generator for readability reasons.
Changed wording of exceptions a little
Fixed an exception where trying to follow an invalid short url would cause an exception becaused by a forgotten else.
Cleaned up the design a bit
Redirect now redirectes with HTTP code 302.
Cleaned TODO