short 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|