short 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +14 -0
- data/bin/short +3 -0
- data/lib/shortener/configuration.rb +38 -6
- data/lib/shortener/server/Gemfile +2 -0
- data/lib/shortener/server/Gemfile.lock +5 -0
- data/lib/shortener/server/api/v1.rb +76 -0
- data/lib/shortener/server/auth.rb +117 -0
- data/lib/shortener/server/brief.rb +189 -0
- data/lib/shortener/server/config.ru.template +44 -2
- data/lib/shortener/server/helpers.rb +84 -0
- data/lib/shortener/server/pbkdf2.rb +192 -0
- data/lib/shortener/server/public/css/bootstrap-responsive.min.css +9 -3
- data/lib/shortener/server/public/css/bootstrap.min.css +9 -610
- data/lib/shortener/server/public/css/controls.png +0 -0
- data/lib/shortener/server/public/css/loading.gif +0 -0
- data/lib/shortener/server/public/css/shortener.css +23 -2
- data/lib/shortener/server/public/js/bootstrap.min.js +6 -1
- data/lib/shortener/server/public/js/site.js +2 -2
- data/lib/shortener/server/user.rb +131 -0
- data/lib/shortener/server/views.rb +55 -0
- data/lib/shortener/server/views/add.haml +8 -8
- data/lib/shortener/server/views/display.haml +1 -1
- data/lib/shortener/server/views/index.haml +28 -12
- data/lib/shortener/server/views/layout.haml +38 -12
- data/lib/shortener/server/views/s3/layout.haml +1 -1
- data/lib/shortener/server/views/u/edit.haml +40 -0
- data/lib/shortener/server/views/u/login.haml +13 -0
- data/lib/shortener/server/views/upload.haml +9 -9
- data/lib/shortener/server/warden.rb +29 -0
- data/lib/shortener/short.rb +2 -2
- data/lib/shortener/tasks/heroku.rb +16 -10
- data/lib/shortener/version.rb +1 -1
- metadata +22 -12
- data/lib/shortener/server.rb +0 -350
- data/lib/shortener/server/public/flash/clippy.swf +0 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
.row
|
3
|
+
%form{action: '/api/v1/u/login', method: 'post'}
|
4
|
+
.control-group
|
5
|
+
%label.control-label{for: 'user[username]'} Username:
|
6
|
+
.controls
|
7
|
+
%input{type: 'text', name: 'user[username]', class: 'text'}
|
8
|
+
.control-group
|
9
|
+
%label.control-label{for: 'user[password]'} Password:
|
10
|
+
.controls
|
11
|
+
%input{type: 'password', name: 'user[password]', class: 'password'}
|
12
|
+
|
13
|
+
%input{type: 'submit', value: 'Login', class: 'btn primary'}
|
@@ -1,7 +1,7 @@
|
|
1
|
-
%script{src: "js/swfupload.js"}
|
2
|
-
%script{src: "js/jquery-swfupload-min.js"}
|
1
|
+
%script{src: "/js/swfupload.js"}
|
2
|
+
%script{src: "/js/jquery-swfupload-min.js"}
|
3
3
|
.row
|
4
|
-
%form.form-horizontal.span6{id: 'upload-form', action: '/upload', method: 'post'}
|
4
|
+
%form.form-horizontal.span6{id: 'upload-form', action: '/api/v1/upload', method: 'post'}
|
5
5
|
.control-group
|
6
6
|
%label.control-label Select File:
|
7
7
|
.controls
|
@@ -13,16 +13,16 @@
|
|
13
13
|
%label.control-label Display Type:
|
14
14
|
.controls
|
15
15
|
%label.radio.inline
|
16
|
-
%input{name: 'shortener[type]', type: 'radio', value: 'video'}
|
16
|
+
%input{name: 'shortener[type]', type: 'radio', value: 'video'}
|
17
17
|
Video
|
18
18
|
%label.radio.inline
|
19
|
-
%input{name: 'shortener[type]', type: 'radio', value: 'audio'}
|
19
|
+
%input{name: 'shortener[type]', type: 'radio', value: 'audio'}
|
20
20
|
Audio
|
21
21
|
%label.radio.inline
|
22
|
-
%input{name: 'shortener[type]', type: 'radio', value: 'image'}
|
22
|
+
%input{name: 'shortener[type]', type: 'radio', value: 'image'}
|
23
23
|
Image
|
24
24
|
%label.radio.inline
|
25
|
-
%input{name: 'shortener[type]', type: 'radio', value: 'download'}
|
25
|
+
%input{name: 'shortener[type]', type: 'radio', value: 'download'}
|
26
26
|
Download
|
27
27
|
.control-group.hideable
|
28
28
|
%label.control-label{for: 'shorterner[name]'} Name
|
@@ -45,7 +45,7 @@
|
|
45
45
|
$(function(){
|
46
46
|
$('#swfupload-control').swfupload({
|
47
47
|
// Backend Settings
|
48
|
-
upload_url: "#{@upload_url}",
|
48
|
+
upload_url: "#{@upload_url}",
|
49
49
|
http_success : [ 200, 201, 204 ], // FOR AWS
|
50
50
|
|
51
51
|
// File Upload Settings
|
@@ -84,7 +84,7 @@
|
|
84
84
|
.bind('uploadSuccess', function(event, file, serverData){
|
85
85
|
var values = $('#upload-form').serialize();
|
86
86
|
values += '&shortener%5bfile_name%5d=' + file.name;
|
87
|
-
$.post('/upload
|
87
|
+
$.post('/api/v1/upload', values, function(data){
|
88
88
|
$('#display-up-new-shorts').append(data.html)
|
89
89
|
});
|
90
90
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
Warden::Manager.serialize_into_session{|user| user.token }
|
3
|
+
Warden::Manager.serialize_from_session{|token| User.get_by_token(token) }
|
4
|
+
|
5
|
+
Warden::Manager.before_failure do |env,opts|
|
6
|
+
env['REQUEST_METHOD'] = "POST"
|
7
|
+
end
|
8
|
+
|
9
|
+
Warden::Strategies.add(:password) do
|
10
|
+
def valid?
|
11
|
+
params['user']["username"] || params['user']["password"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate!
|
15
|
+
u = User.authenticate!(params['user']["username"], params['user']["password"])
|
16
|
+
u ? success!(u) : fail!('Could not log in')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Warden::Strategies.add(:token) do
|
21
|
+
def valid?
|
22
|
+
params.has_key?('token')
|
23
|
+
end
|
24
|
+
|
25
|
+
def authenticate!
|
26
|
+
u = User.get_by_token(params['token'])
|
27
|
+
u ? success!(u) : fail!('Could not log in')
|
28
|
+
end
|
29
|
+
end
|
data/lib/shortener/short.rb
CHANGED
@@ -11,7 +11,7 @@ class Shortener
|
|
11
11
|
|
12
12
|
# set a shortened url
|
13
13
|
def shorten(url, conf = nil)
|
14
|
-
opts = {
|
14
|
+
opts = {"shortener[url]" => "#{url}"}
|
15
15
|
response = request(:post, :add, conf, opts)
|
16
16
|
if response.is_a?(Net::HTTPOK)
|
17
17
|
return Short.new(response.body, conf)
|
@@ -40,7 +40,7 @@ class Shortener
|
|
40
40
|
|
41
41
|
# delete a short
|
42
42
|
def delete(short, conf = nil)
|
43
|
-
response = request(:
|
43
|
+
response = request(:post, :delete, conf, {id: short})
|
44
44
|
Short.new(response.body, conf)
|
45
45
|
end
|
46
46
|
|
@@ -64,10 +64,10 @@ namespace :short do
|
|
64
64
|
desc "Build a Heroku Ready Git repo"
|
65
65
|
task :build do
|
66
66
|
FileUtils.mkdir(File.join(Dir.pwd, 'shortener-heroku')) unless $existing_repo
|
67
|
-
[:'server', :'server/public', :'server/views', :'server/views/s3',
|
67
|
+
[:'server', :'server/public', :'server/views', :'server/views/s3',
|
68
68
|
:'server/public/flash', :'server/public/js', :'server/public/css',
|
69
|
-
:'server/public/skin', :'server/public/images',
|
70
|
-
:'server/public/skin/blue.monday'].each do |f|
|
69
|
+
:'server/public/skin', :'server/public/images', :'server/views/u',
|
70
|
+
:'server/public/skin/blue.monday', 'server/api'].each do |f|
|
71
71
|
unless File.exist?(_file(f))
|
72
72
|
puts "creating #{_file(f)}" if ENV['VERBOSE']
|
73
73
|
FileUtils.mkdir(_file(f))
|
@@ -76,8 +76,8 @@ namespace :short do
|
|
76
76
|
|
77
77
|
['server', 'server/public', 'server/views', :'server/views/s3',
|
78
78
|
:'server/public/flash', :'server/public/images', :'server/public/skin',
|
79
|
-
:'server/public/skin/blue.monday', :'server/public/js',
|
80
|
-
:'server/public/css'].each do |end_point|
|
79
|
+
:'server/public/skin/blue.monday', :'server/public/js', :'server/views/u',
|
80
|
+
:'server/public/css', 'server/api'].each do |end_point|
|
81
81
|
Dir["#{$gem_dir}/#{end_point}/**"].each do |f|
|
82
82
|
next if File.directory?(f)
|
83
83
|
end_point = _file(:"#{_ep(f)}")
|
@@ -85,9 +85,9 @@ namespace :short do
|
|
85
85
|
FileUtils.cp(f, end_point)
|
86
86
|
end
|
87
87
|
end
|
88
|
-
_s, _e = gem_file('server.rb'), _file(:'main.rb')
|
89
|
-
_l(:copying, _s, _e)
|
90
|
-
FileUtils.cp(_s, _e)
|
88
|
+
#_s, _e = gem_file('server.rb'), _file(:'main.rb')
|
89
|
+
#_l(:copying, _s, _e)
|
90
|
+
#FileUtils.cp(_s, _e)
|
91
91
|
_s, _e = gem_file('configuration.rb'), _file(:'configuration.rb')
|
92
92
|
_l(:copying, _s, _e)
|
93
93
|
FileUtils.cp(_s, _e)
|
@@ -117,6 +117,7 @@ namespace :short do
|
|
117
117
|
recursively_remove_files(Dir.pwd)
|
118
118
|
else
|
119
119
|
puts "working directory not clean, stash or commit your changes"
|
120
|
+
exit
|
120
121
|
end
|
121
122
|
$existing_repo = true
|
122
123
|
Rake::Task[:'short:heroku:build'].execute
|
@@ -124,16 +125,21 @@ namespace :short do
|
|
124
125
|
|
125
126
|
desc "config a Heroku app the way we need it. Optionally set APPNAME to set heroku app name"
|
126
127
|
task :config do
|
127
|
-
require 'shortener'
|
128
128
|
$name = ENV['APPNAME'] || "shner-#{`whoami`.chomp}"
|
129
129
|
cmd = Dir.pwd =~ /heroku$/ ? "" : "cd heroku && "
|
130
130
|
cmd += "heroku create #{$name}"
|
131
131
|
cmd += " && heroku addons:add redistogo:nano"
|
132
|
-
cmd += " && heroku config:add #{Shortener::Configuration.new.to_params}"
|
133
132
|
cmd += " && heroku addons:add custom_domains:basic"
|
133
|
+
Rake::Task[:update_config].execute
|
134
134
|
sh cmd
|
135
135
|
end
|
136
136
|
|
137
|
+
desc "Update Heroku env vars with Shortener::Config dump"
|
138
|
+
task :update_config do
|
139
|
+
require 'shortener'
|
140
|
+
sh "heroku config:add #{Shortener::Configuration.new.to_params}"
|
141
|
+
end
|
142
|
+
|
137
143
|
desc "Push to Heroku"
|
138
144
|
task :push do
|
139
145
|
cmd = Dir.pwd =~ /heroku$/ ? "" : "cd heroku && "
|
data/lib/shortener/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: short
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
16
|
-
requirement: &
|
16
|
+
requirement: &70284680213020 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70284680213020
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: redis-namespace
|
27
|
-
requirement: &
|
27
|
+
requirement: &70284680212600 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70284680212600
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: haml
|
38
|
-
requirement: &
|
38
|
+
requirement: &70284680212140 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70284680212140
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: turn
|
49
|
-
requirement: &
|
49
|
+
requirement: &70284680211720 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70284680211720
|
58
58
|
description: A (hopefully) easy and handy deployable APIable way to shorten links.
|
59
59
|
email:
|
60
60
|
- jake@jakewilkins.com
|
@@ -71,16 +71,21 @@ files:
|
|
71
71
|
- config.ru
|
72
72
|
- lib/shortener.rb
|
73
73
|
- lib/shortener/configuration.rb
|
74
|
-
- lib/shortener/server.rb
|
75
74
|
- lib/shortener/server/Gemfile
|
76
75
|
- lib/shortener/server/Gemfile.lock
|
76
|
+
- lib/shortener/server/api/v1.rb
|
77
|
+
- lib/shortener/server/auth.rb
|
78
|
+
- lib/shortener/server/brief.rb
|
77
79
|
- lib/shortener/server/config.ru.template
|
80
|
+
- lib/shortener/server/helpers.rb
|
81
|
+
- lib/shortener/server/pbkdf2.rb
|
78
82
|
- lib/shortener/server/public/css/bootstrap-responsive.min.css
|
79
83
|
- lib/shortener/server/public/css/bootstrap.min.css
|
80
84
|
- lib/shortener/server/public/css/colorbox.css
|
85
|
+
- lib/shortener/server/public/css/controls.png
|
86
|
+
- lib/shortener/server/public/css/loading.gif
|
81
87
|
- lib/shortener/server/public/css/shortener.css
|
82
88
|
- lib/shortener/server/public/flash/Jplayer.swf
|
83
|
-
- lib/shortener/server/public/flash/clippy.swf
|
84
89
|
- lib/shortener/server/public/flash/swfupload.swf
|
85
90
|
- lib/shortener/server/public/images/XPButtonUploadText_61x22.png
|
86
91
|
- lib/shortener/server/public/images/delete-icon.png
|
@@ -98,6 +103,8 @@ files:
|
|
98
103
|
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.jpg
|
99
104
|
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.seeking.gif
|
100
105
|
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.video.play.png
|
106
|
+
- lib/shortener/server/user.rb
|
107
|
+
- lib/shortener/server/views.rb
|
101
108
|
- lib/shortener/server/views/add.haml
|
102
109
|
- lib/shortener/server/views/display.haml
|
103
110
|
- lib/shortener/server/views/index.haml
|
@@ -106,7 +113,10 @@ files:
|
|
106
113
|
- lib/shortener/server/views/s3/image.haml
|
107
114
|
- lib/shortener/server/views/s3/layout.haml
|
108
115
|
- lib/shortener/server/views/s3/video.haml
|
116
|
+
- lib/shortener/server/views/u/edit.haml
|
117
|
+
- lib/shortener/server/views/u/login.haml
|
109
118
|
- lib/shortener/server/views/upload.haml
|
119
|
+
- lib/shortener/server/warden.rb
|
110
120
|
- lib/shortener/short.rb
|
111
121
|
- lib/shortener/tasks/heroku.rb
|
112
122
|
- lib/shortener/version.rb
|
data/lib/shortener/server.rb
DELETED
@@ -1,350 +0,0 @@
|
|
1
|
-
require 'sinatra'
|
2
|
-
require 'redis-namespace'
|
3
|
-
require 'uri'
|
4
|
-
require 'json'
|
5
|
-
require 'haml'
|
6
|
-
require 'digest/sha1'
|
7
|
-
require 'base64'
|
8
|
-
require File.join(File.dirname(__FILE__), 'configuration')
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class Shortener
|
13
|
-
class Server < Sinatra::Base
|
14
|
-
dir = File.expand_path(File.dirname(__FILE__))
|
15
|
-
set :root, File.join(dir, 'server')
|
16
|
-
set :public_folder, File.join(dir, 'server', 'public')
|
17
|
-
|
18
|
-
configure do
|
19
|
-
$conf = Shortener::Configuration.new
|
20
|
-
uri = $conf.redistogo_url
|
21
|
-
_redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
|
22
|
-
$redis = Redis::Namespace.new(:shortener, redis: _redis)
|
23
|
-
end
|
24
|
-
|
25
|
-
set(:s3_available) {|v| condition {$conf.s3_available == v}}
|
26
|
-
|
27
|
-
helpers do
|
28
|
-
|
29
|
-
def bad! message
|
30
|
-
halt 412, {}, message
|
31
|
-
end
|
32
|
-
|
33
|
-
def nope!(message = 'No luck.')
|
34
|
-
halt 404, {}, message
|
35
|
-
end
|
36
|
-
|
37
|
-
def base_url
|
38
|
-
@base_url ||= "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}"
|
39
|
-
end
|
40
|
-
|
41
|
-
def clippy(text, bgcolor='#FFFFFF')
|
42
|
-
html = <<-EOF
|
43
|
-
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
|
44
|
-
width="110"
|
45
|
-
height="25"
|
46
|
-
id="clippy" >
|
47
|
-
<param name="movie" value="/flash/clippy.swf"/>
|
48
|
-
<param name="allowScriptAccess" value="always" />
|
49
|
-
<param name="quality" value="high" />
|
50
|
-
<param name="scale" value="noscale" />
|
51
|
-
<param NAME="FlashVars" value="text=#{text}">
|
52
|
-
<param name="bgcolor" value="#{bgcolor}">
|
53
|
-
<embed src="/flash/clippy.swf"
|
54
|
-
width="110"
|
55
|
-
height="14"
|
56
|
-
name="clippy"
|
57
|
-
quality="high"
|
58
|
-
allowScriptAccess="always"
|
59
|
-
type="application/x-shockwave-flash"
|
60
|
-
pluginspage="http://www.macromedia.com/go/getflashplayer"
|
61
|
-
FlashVars="text=#{text}"
|
62
|
-
bgcolor="#{bgcolor}"
|
63
|
-
/>
|
64
|
-
</object>
|
65
|
-
EOF
|
66
|
-
end
|
67
|
-
|
68
|
-
def set_or_fetch_url(params)
|
69
|
-
bad! 'Missing url.' unless url = params['url']
|
70
|
-
bad! 'Bad URL' unless params['url'] =~ /(^http|^www)/
|
71
|
-
url = "http://#{url}" unless /^http/i =~ url
|
72
|
-
bad! 'Bad URL' unless (url = URI.parse(url)) && /^http/ =~ url.scheme
|
73
|
-
|
74
|
-
%w(max-clicks expire desired-short allow-override).each do |k|
|
75
|
-
params[k] = false if params[k].nil? || params[k].empty?
|
76
|
-
end
|
77
|
-
|
78
|
-
unless params['max-clicks'] || params['expire'] || params['desired-short']
|
79
|
-
data = check_cache(url)
|
80
|
-
end
|
81
|
-
data ||= shorten(url, params)
|
82
|
-
|
83
|
-
data
|
84
|
-
end
|
85
|
-
|
86
|
-
def set_upload_short(params)
|
87
|
-
bad! 'Missing content type.' unless type = params['type']
|
88
|
-
key = generate_short
|
89
|
-
fname = params['file_name'].gsub(' ', '+')
|
90
|
-
sha = Digest::SHA1.hexdigest(fname)
|
91
|
-
hash_key = "data:#{sha}:#{key}"
|
92
|
-
url = "https://s3.amazonaws.com/#{$conf.s3_bucket}/#{$conf.s3_key_prefix}/#{fname}"
|
93
|
-
ext = File.extname(fname)[1..-1]
|
94
|
-
data = {'url' => url, 's3' => true, 'shortened' => key,
|
95
|
-
'extension' => ext, 'set_count' => 1}
|
96
|
-
data = params.merge(data)
|
97
|
-
|
98
|
-
$redis.set(key, sha)
|
99
|
-
$redis.hmset(hash_key, *arrayify_hash(data))
|
100
|
-
|
101
|
-
data
|
102
|
-
end
|
103
|
-
|
104
|
-
def shorten(url, options = {})
|
105
|
-
|
106
|
-
unless options['desired-short']
|
107
|
-
key = generate_short
|
108
|
-
else
|
109
|
-
check = $redis.get(options['desired-short'])
|
110
|
-
if check # it's already taken
|
111
|
-
check_key = "data:#{check}:#{options['desired-short']}"
|
112
|
-
prev_set = $redis.hgetall(check_key)
|
113
|
-
|
114
|
-
# if we don't expire or have max clicks and previously set key
|
115
|
-
# doesn't expire or have max clicks we can go ahead and use it
|
116
|
-
# without any further setup.
|
117
|
-
unless options['expire'] || options['max-clicks']
|
118
|
-
if (!prev_set['max-clicks'] && !prev_set['expire'] &&
|
119
|
-
(prev_set['url'] == url.to_s))
|
120
|
-
$redis.hincrby(check_key, 'set_count', 1)
|
121
|
-
return prev_set
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
if prev_set['max-clicks'].to_i < prev_set['clicks'].to_i
|
126
|
-
# previous key is no longer valid, we can assign to it
|
127
|
-
key = options['desired-short']
|
128
|
-
else
|
129
|
-
bad! 'Name is already taken. Use Allow override' unless options['allow-override'] == 'true'
|
130
|
-
key = generate_short
|
131
|
-
end
|
132
|
-
|
133
|
-
else
|
134
|
-
key = options['desired-short']
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
sha = Digest::SHA1.hexdigest(url.to_s)
|
139
|
-
$redis.set(key, sha)
|
140
|
-
|
141
|
-
hsh_data = {'shortened' => key, 'url' => url, 'set_count' => 1}
|
142
|
-
hsh_data['max-clicks'] = options['max-clicks'].to_i if options['max-clicks']
|
143
|
-
|
144
|
-
if options['expire'] # set expire time if specified
|
145
|
-
ttl = options['expire'].to_i
|
146
|
-
ttl_key = "expire:#{sha}:#{key}"
|
147
|
-
$redis.set(ttl_key, "#{sha}:#{key}")
|
148
|
-
$redis.expire(ttl_key, ttl)
|
149
|
-
hsh_data[:expire] = ttl_key
|
150
|
-
end
|
151
|
-
$redis.hmset("data:#{sha}:#{key}", *arrayify_hash(hsh_data))
|
152
|
-
|
153
|
-
hsh_data
|
154
|
-
end
|
155
|
-
|
156
|
-
def check_cache(url)
|
157
|
-
sha = Digest::SHA1.hexdigest(url.to_s)
|
158
|
-
|
159
|
-
$redis.keys("data:#{sha}:*").each do |key|
|
160
|
-
short = $redis.hgetall(key)
|
161
|
-
unless short == {} || short['expire'] || short['max-clicks']
|
162
|
-
$redis.hincrby(key, 'set_count', 1)
|
163
|
-
return short
|
164
|
-
end
|
165
|
-
end
|
166
|
-
nil
|
167
|
-
end
|
168
|
-
|
169
|
-
def delete_short(id)
|
170
|
-
puts "deleting #{id}"
|
171
|
-
puts sha = $redis.get(id)
|
172
|
-
unless sha.nil?
|
173
|
-
$redis.multi do
|
174
|
-
$redis.del "data:#{sha}:#{id}"
|
175
|
-
$redis.del "expire:#{sha}:#{id}"
|
176
|
-
$redis.del id
|
177
|
-
end
|
178
|
-
true
|
179
|
-
else
|
180
|
-
false
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def generate_short
|
185
|
-
begin
|
186
|
-
o = [('a'..'z'),('A'..'Z'),(0..9)].map{|i| i.to_a}.flatten;
|
187
|
-
key = (0..4).map{ o[rand(o.length)] }.join;
|
188
|
-
puts "testing #{key}"
|
189
|
-
end while !$redis.get(key).nil?
|
190
|
-
key
|
191
|
-
end
|
192
|
-
|
193
|
-
def ttl_display(ttl)
|
194
|
-
if ttl == -1
|
195
|
-
ret = 'expired'
|
196
|
-
elsif ttl == nil
|
197
|
-
ret = '∞'
|
198
|
-
else
|
199
|
-
ret = ttl
|
200
|
-
end
|
201
|
-
ret
|
202
|
-
end
|
203
|
-
|
204
|
-
def arrayify_hash(hsh)
|
205
|
-
hsh.keys.map {|k| [k, hsh[k]] }.flatten
|
206
|
-
end
|
207
|
-
|
208
|
-
def boxify_class(int, boxify_classes = '', nonbox_classes = '', other_classes = '')
|
209
|
-
str = if @boxify
|
210
|
-
"#{other_classes} #{boxify_classes}"
|
211
|
-
else
|
212
|
-
"#{other_classes} #{nonbox_classes}"
|
213
|
-
end
|
214
|
-
str = str + " offset#{int}" unless !int.nil? && @boxify
|
215
|
-
str
|
216
|
-
end
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
before do
|
221
|
-
params
|
222
|
-
end
|
223
|
-
|
224
|
-
get '/' do
|
225
|
-
redirect $conf.default_url
|
226
|
-
end
|
227
|
-
|
228
|
-
get '/add' do
|
229
|
-
@boxify = !params['boxify'].nil?
|
230
|
-
haml :add, layout: !@boxify
|
231
|
-
end
|
232
|
-
|
233
|
-
get '/index.?:format?' do
|
234
|
-
@shortens = Array.new
|
235
|
-
$redis.keys('data:*').each do |key|
|
236
|
-
short = $redis.hgetall(key)
|
237
|
-
short['expire'] = $redis.ttl(short['expire']) if short.has_key?('expire')
|
238
|
-
@shortens << short
|
239
|
-
puts " url for #{key[-5..-1]} => #{short['url']}"
|
240
|
-
end
|
241
|
-
if params[:format] == 'json'
|
242
|
-
content_type :json
|
243
|
-
return @shortens.to_json
|
244
|
-
end
|
245
|
-
haml :index
|
246
|
-
end
|
247
|
-
|
248
|
-
get '/delete/:id.:format' do |id, format|
|
249
|
-
status = delete_short(id)
|
250
|
-
if format == 'json'
|
251
|
-
nope! "Short not found: #{id}" if status == false
|
252
|
-
content_type :json
|
253
|
-
{success: status, shortened: id}.to_json
|
254
|
-
else
|
255
|
-
redirect :index
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
get '/upload', s3_available: true do
|
260
|
-
policy = $conf.s3_policy
|
261
|
-
signature = $conf.s3_signature(policy)
|
262
|
-
|
263
|
-
@post = {
|
264
|
-
"key" => "#{$conf.s3_key_prefix}/${filename}",
|
265
|
-
"AWSAccessKeyId" => "#{$conf.s3_access_key_id}",
|
266
|
-
"acl" => "#{$conf.s3_default_acl}",
|
267
|
-
"policy" => "#{policy}",
|
268
|
-
"signature" => "#{signature}",
|
269
|
-
"success_action_status" => "201"
|
270
|
-
}
|
271
|
-
|
272
|
-
@upload_url = "http://#{$conf.s3_bucket}.s3.amazonaws.com/"
|
273
|
-
@boxify = !params['boxify'].nil?
|
274
|
-
haml :upload, layout: !@boxify
|
275
|
-
end
|
276
|
-
|
277
|
-
#get '/:id.?:format?' do
|
278
|
-
get %r{\/([a-z0-9]{3,})(\.[a-z]{3,}){0,1}}i do
|
279
|
-
id = params[:captures].first
|
280
|
-
sha = $redis.get(id)
|
281
|
-
if sha.nil?
|
282
|
-
if (params[:captures].last == '.json')
|
283
|
-
nope! "Short not found: #{id}"
|
284
|
-
else
|
285
|
-
puts "redirecting to default url"
|
286
|
-
redirect $conf.default_url
|
287
|
-
end
|
288
|
-
else
|
289
|
-
key = "data:#{sha}:#{id}"
|
290
|
-
short = $redis.hgetall(key)
|
291
|
-
not_expired = short.has_key?('expire') ? $redis.get(short['expire']) : true
|
292
|
-
not_maxed = !(short['click_count'].to_i >= short['max-clicks'].to_i)
|
293
|
-
short.has_key?('max-clicks') ? not_maxed : not_maxed = true
|
294
|
-
if params[:captures].last == '.json'
|
295
|
-
ret = short.merge({expired: not_expired.nil? , maxed: !not_maxed})
|
296
|
-
content_type :json
|
297
|
-
return ret.to_json
|
298
|
-
else
|
299
|
-
$redis.hincrby(key, 'click_count', 1) if not_expired && not_maxed
|
300
|
-
if not_expired
|
301
|
-
unless short['s3'] == 'true' && !(short['type'] == 'download')
|
302
|
-
if not_maxed
|
303
|
-
puts "redirecting #{id} to #{url}"
|
304
|
-
redirect short['url']
|
305
|
-
end # => max clicks check
|
306
|
-
else
|
307
|
-
@short = short
|
308
|
-
puts "rendering view for s3 content. #{id} => #{short['url']}"
|
309
|
-
return haml(:"s3/#{short['type']}", layout: :'s3/layout')
|
310
|
-
end # => it's S3 and needs displaying.
|
311
|
-
end # => expired check
|
312
|
-
end # => format
|
313
|
-
end
|
314
|
-
puts "redirecting to default url"
|
315
|
-
redirect $conf.default_url
|
316
|
-
end
|
317
|
-
|
318
|
-
post '/upload.?:format?' do |format|
|
319
|
-
@data = set_upload_short(params['shortener'])
|
320
|
-
puts "set #{@data['shortened']} to #{params['shortener']['file_name']}"
|
321
|
-
@url = "#{base_url}/#{@data['shortened']}"
|
322
|
-
if format == 'json'
|
323
|
-
content_type :json
|
324
|
-
@data.merge({html: haml(:display, layout: false)}).to_json
|
325
|
-
else
|
326
|
-
redirect :index
|
327
|
-
end
|
328
|
-
end
|
329
|
-
|
330
|
-
post '/add.?:format?' do |format|
|
331
|
-
begin
|
332
|
-
# TODO figure out why the fuck these are parsing from Net::HTTP
|
333
|
-
params['shortener'] = JSON.parse(params['shortener']) if params['shortener'].is_a?(String)
|
334
|
-
rescue Exception => boom
|
335
|
-
# essentally, params = params
|
336
|
-
end
|
337
|
-
|
338
|
-
@data = set_or_fetch_url(params["shortener"])
|
339
|
-
@url = "#{base_url}/#{@data['shortened']}"
|
340
|
-
puts "set #{@url} to #{params['shortener']['url']}"
|
341
|
-
if format == 'json'
|
342
|
-
content_type :json
|
343
|
-
@data.merge({success: true, html: haml(:display, :layout => false)}).to_json
|
344
|
-
else
|
345
|
-
haml :display
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
end # => Server
|
350
|
-
end # => Shortener
|