├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── app.rb └── config.ru /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem "guillotine", "1.4.0" 4 | gem "redis" 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | addressable (2.2.8) 5 | guillotine (1.4.0) 6 | addressable (~> 2.2.6) 7 | sinatra (~> 1.2.6) 8 | rack (1.5.2) 9 | redis (3.0.1) 10 | sinatra (1.2.8) 11 | rack (~> 1.1) 12 | tilt (>= 1.2.2, < 2.0) 13 | tilt (1.3.4) 14 | 15 | PLATFORMS 16 | ruby 17 | 18 | DEPENDENCIES 19 | guillotine (= 1.4.0) 20 | redis 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Daniel Schauenberg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Katana [![Code Climate](https://codeclimate.com/github/mrtazz/katana.png)](https://codeclimate.com/github/mrtazz/katana) 2 | 3 | ## Overview 4 | Opinionated personal URL shortener which runs on [Heroku][1] and uses [Redis to 5 | go][2] as a backend. Shortening is done through the fabulous [Guillotine][3] 6 | engine and its Redis adapter. 7 | 8 | If you set `HTTP_USER` and `HTTP_PASS` all methods except `GETs` require basic 9 | authentication. 10 | 11 | If you set `ROOT_REDIRECTS_TO_URL` traffic that GETs '/' will get redirected there. 12 | 13 | ## Usage 14 | You can use it exactly as any other guillotine app: 15 | 16 | curl -X POST http://sho.rt --user foo:bar -i -F"url=http://github.com" -F"code=gh" 17 | 18 | ## Features 19 | - Authentication 20 | - Custom [Tweetbot][7] compatible endpoint 21 | 22 | ## Setup 23 | 24 | git clone git://github.com/mrtazz/katana.git 25 | cd katana 26 | heroku create 27 | heroku addons:add redistogo 28 | # or if you want to use Heroku redis 29 | heroku addons:create heroku-redis:hobby-dev 30 | heroku domains:add sho.rt 31 | git push heroku master 32 | # for authentication 33 | heroku config:add HTTP_USER="theuser" 34 | heroku config:add HTTP_PASS="thepass" 35 | # for redirection 36 | heroku config:add ROOT_REDIRECTS_TO_URL="http://www.yourlongerdomain.io" 37 | 38 | ### Tweetbot 39 | There is a custom endpoint which is compatible with how tweetbot expects custom 40 | URL shorteners to behave. Activate it by setting 41 | 42 | TWEETBOT_API=true 43 | 44 | in your environment variables. After that you can add URLs with a `GET` to 45 | 46 | http://sho.rt/api/create/?url=http://github.com 47 | 48 | Keep in mind that this endpoint is not authenticated. 49 | 50 | ## Thanks 51 | [@technoweenie][4] for the awesome guillotine and [@roidrage][5] for 52 | [s3itch][6] which somehow got me started wanting a personal URL shortener. 53 | 54 | [1]: http://heroku.com 55 | [2]: http://redistogo.com 56 | [3]: https://github.com/technoweenie/guillotine 57 | [4]: https://twitter.com/technoweenie 58 | [5]: https://twitter.com/roidrage 59 | [6]: https://github.com/mattmatt/s3itch 60 | [7]: http://tapbots.com/software/tweetbot/ 61 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require 'guillotine' 2 | require 'redis' 3 | 4 | module Katana 5 | class App < Guillotine::App 6 | # use redis adapter with redistogo 7 | uri = URI.parse(ENV["REDISTOGO_URL"] || ENV["REDISGREEN_URL"] || ENV["REDIS_URL"]) 8 | REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password) 9 | adapter = Guillotine::Adapters::RedisAdapter.new REDIS 10 | set :service => Guillotine::Service.new(adapter, :strip_query => false, 11 | :strip_anchor => false) 12 | 13 | # authenticate everything except GETs 14 | before do 15 | unless request.request_method == "GET" 16 | protected! 17 | end 18 | end 19 | 20 | get '/' do 21 | if ENV['ROOT_REDIRECTS_TO_URL'] 22 | redirect ENV['ROOT_REDIRECTS_TO_URL'] 23 | else 24 | "Shorten all the URLs" 25 | end 26 | end 27 | 28 | if ENV['TWEETBOT_API'] 29 | # experimental (unauthenticated) API endpoint for tweetbot 30 | get '/api/create/?' do 31 | status, head, body = settings.service.create(params[:url], params[:code]) 32 | 33 | if loc = head['Location'] 34 | "#{File.join("http://", request.host, loc)}" 35 | else 36 | 500 37 | end 38 | end 39 | end 40 | 41 | # helper methods 42 | helpers do 43 | 44 | # Private: helper method to protect URLs with Rack Basic Auth 45 | # 46 | # Throws 401 if authorization fails 47 | def protected! 48 | return unless ENV["HTTP_USER"] 49 | unless authorized? 50 | response['WWW-Authenticate'] = %(Basic realm="Restricted Area") 51 | throw(:halt, [401, "Not authorized\n"]) 52 | end 53 | end 54 | 55 | # Private: helper method to check if authorization parameters match the 56 | # set environment variables 57 | # 58 | # Returns true or false 59 | def authorized? 60 | @auth ||= Rack::Auth::Basic::Request.new(request.env) 61 | user = ENV["HTTP_USER"] 62 | pass = ENV["HTTP_PASS"] 63 | @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [user, pass] 64 | end 65 | end 66 | 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # config.ru 2 | require "rubygems" 3 | require File.expand_path("../app.rb", __FILE__) 4 | run Katana::App 5 | --------------------------------------------------------------------------------