firefly 0.4.0.1 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +14 -0
- data/VERSION +1 -1
- data/firefly.gemspec +2 -3
- data/lib/firefly.rb +1 -0
- data/lib/firefly/server.rb +18 -10
- data/lib/firefly/url.rb +18 -1
- data/spec/firefly/api_spec.rb +41 -6
- data/spec/firefly/url_spec.rb +31 -1
- data/views/index.haml +26 -0
- metadata +2 -4
- data/API.md +0 -86
data/HISTORY
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
= 0.4.1 / 2010-04-30
|
2
|
+
|
3
|
+
* 2010-04-30 - Normalize URLs before shortening them. This prevents false duplicates. [ariejan]
|
4
|
+
|
5
|
+
* 2010-04-30 - Validate URLs to be valid HTTP or HTTPS, don't accept others. [ariejan]
|
6
|
+
|
7
|
+
* 2010-04-30 - Don't ask for the API key after shortening a URL with the bookmarklet. [ariejan]
|
8
|
+
|
9
|
+
* 2010-04-15 - Show the highlighted URL separately. Closes #7. [ariejan]
|
10
|
+
|
11
|
+
= 0.4.0.1 / 2010-04-14
|
12
|
+
|
13
|
+
* 2010-04-14 - Correct file permission of `public/images/twitter.png`. [ariejan]
|
14
|
+
|
1
15
|
= 0.4.0 / 2010-04-14
|
2
16
|
|
3
17
|
* 2010-04-14 - Added button to quickly tweet a URL. Closes #3 [ariejan]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.1
|
data/firefly.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{firefly}
|
8
|
-
s.version = "0.4.
|
8
|
+
s.version = "0.4.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ariejan de Vroom"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-30}
|
13
13
|
s.description = %q{FireFly is a simple URL shortner for personal use. It's powered by Sinatra and can be run with any Rack-capable web server.}
|
14
14
|
s.email = %q{ariejan@ariejan.net}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -18,7 +18,6 @@ Gem::Specification.new do |s|
|
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".gitignore",
|
21
|
-
"API.md",
|
22
21
|
"HISTORY",
|
23
22
|
"LICENSE",
|
24
23
|
"README.md",
|
data/lib/firefly.rb
CHANGED
data/lib/firefly/server.rb
CHANGED
@@ -53,8 +53,13 @@ module Firefly
|
|
53
53
|
|
54
54
|
if !url.nil? && url != ""
|
55
55
|
ff_url = Firefly::Url.shorten(url)
|
56
|
-
|
57
|
-
|
56
|
+
if !ff_url.nil?
|
57
|
+
code = ff_url.code
|
58
|
+
result = "http://#{config[:hostname]}/#{code}"
|
59
|
+
else
|
60
|
+
code = nil
|
61
|
+
result = "ERROR: The URL you posted is invalid."
|
62
|
+
end
|
58
63
|
end
|
59
64
|
|
60
65
|
return code, result
|
@@ -69,6 +74,12 @@ module Firefly
|
|
69
74
|
def tweet(url)
|
70
75
|
config[:tweet].gsub('%short_url%', url)
|
71
76
|
end
|
77
|
+
|
78
|
+
def store_api_key(key)
|
79
|
+
if key == config[:api_key]
|
80
|
+
set_api_cookie(config[:api_key])
|
81
|
+
end
|
82
|
+
end
|
72
83
|
end
|
73
84
|
|
74
85
|
before do
|
@@ -79,19 +90,14 @@ module Firefly
|
|
79
90
|
end
|
80
91
|
|
81
92
|
get '/' do
|
82
|
-
@highlight
|
93
|
+
@highlight = Firefly::Url.first(:code => params[:highlight])
|
94
|
+
@error = params[:highlight] == "error"
|
83
95
|
@urls = Firefly::Url.all(:limit => config[:recent_urls], :order => [ :created_at.desc ])
|
84
96
|
haml :index
|
85
97
|
end
|
86
98
|
|
87
99
|
post '/api/set' do
|
88
|
-
|
89
|
-
puts "MATCH!"
|
90
|
-
set_api_cookie(config[:api_key])
|
91
|
-
else
|
92
|
-
puts "NOT MATCH"
|
93
|
-
end
|
94
|
-
|
100
|
+
store_api_key(params[:api_key])
|
95
101
|
redirect '/'
|
96
102
|
end
|
97
103
|
|
@@ -106,6 +112,8 @@ module Firefly
|
|
106
112
|
@result ||= "Invalid URL specified."
|
107
113
|
|
108
114
|
if params[:visual]
|
115
|
+
store_api_key(params[:api_key])
|
116
|
+
@code ||= "error"
|
109
117
|
redirect "/?highlight=#{@code}"
|
110
118
|
else
|
111
119
|
@result
|
data/lib/firefly/url.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
module Firefly
|
2
2
|
class Url
|
3
3
|
include DataMapper::Resource
|
4
|
-
|
4
|
+
|
5
|
+
VALID_URL_REGEX = /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix
|
6
|
+
|
5
7
|
property :id, Serial
|
6
8
|
property :url, String, :index => true, :length => 255
|
7
9
|
property :code, String, :index => true, :length => 16
|
@@ -15,9 +17,24 @@ module Firefly
|
|
15
17
|
|
16
18
|
# Shorten a long_url and return a new FireFly::Url
|
17
19
|
def self.shorten(long_url)
|
20
|
+
return nil unless valid_url?(long_url)
|
21
|
+
|
22
|
+
long_url = normalize_url(long_url)
|
23
|
+
|
18
24
|
the_url = Firefly::Url.first(:url => long_url) || Firefly::Url.create(:url => long_url)
|
19
25
|
the_url.update(:code => Firefly::Base62.encode(the_url.id.to_i)) if the_url.code.nil?
|
20
26
|
the_url
|
21
27
|
end
|
28
|
+
|
29
|
+
private
|
30
|
+
# Normalize the URL
|
31
|
+
def self.normalize_url(url)
|
32
|
+
URI.parse(url).normalize.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Validates the URL to be a valid http or https one.
|
36
|
+
def self.valid_url?(url)
|
37
|
+
url.match(Firefly::Url::VALID_URL_REGEX)
|
38
|
+
end
|
22
39
|
end
|
23
40
|
end
|
data/spec/firefly/api_spec.rb
CHANGED
@@ -10,18 +10,53 @@ describe "API" do
|
|
10
10
|
[:post, :get].each do |verb|
|
11
11
|
describe "adding a URL by #{verb.to_s.upcase}" do
|
12
12
|
it "should be okay adding a new URL" do
|
13
|
-
self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
|
13
|
+
self.send verb, '/api/add', :url => 'http://example.org/', :api_key => 'test'
|
14
14
|
last_response.should be_ok
|
15
15
|
end
|
16
16
|
|
17
17
|
it "should return the shortened URL" do
|
18
|
-
self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
|
19
|
-
|
20
|
-
url = Firefly::Url.first(:url => "http://example.org")
|
18
|
+
self.send verb, '/api/add', :url => 'http://example.org/', :api_key => 'test'
|
19
|
+
url = Firefly::Url.first(:url => "http://example.org/")
|
21
20
|
|
22
21
|
last_response.body.should eql("http://test.host/#{url.code}")
|
23
22
|
end
|
24
23
|
|
24
|
+
it "should show an error when shortening an invalid URL" do
|
25
|
+
self.send verb, '/api/add', :url => 'ftp://example.org', :api_key => 'test'
|
26
|
+
|
27
|
+
last_response.body.should match("The URL you posted is invalid")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should show an error when shortening an invalid URL in visual mode" do
|
31
|
+
self.send verb, '/api/add', :url => 'ftp://example.org', :api_key => 'test', :visual => "1"
|
32
|
+
follow_redirect!
|
33
|
+
|
34
|
+
last_response.body.should match("The URL you posted is invalid")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should redirect to the highlighted URL when visual is enabled" do
|
38
|
+
self.send verb, '/api/add', :url => 'http://example.org/', :api_key => 'test', :visual => "1"
|
39
|
+
follow_redirect!
|
40
|
+
|
41
|
+
last_request.path.should eql("/")
|
42
|
+
last_request.should be_get
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should store the API key in the session with visual enabled" do
|
46
|
+
self.send verb, '/api/add', :url => 'http://example.org/', :api_key => 'test', :visual => "1"
|
47
|
+
follow_redirect!
|
48
|
+
|
49
|
+
last_response.body.should_not match(/API Key/)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should highlight the shortened URL" do
|
53
|
+
self.send verb, '/api/add', :url => 'http://example.org/', :api_key => 'test', :visual => "1"
|
54
|
+
url = Firefly::Url.first(:url => "http://example.org/")
|
55
|
+
follow_redirect!
|
56
|
+
|
57
|
+
last_request.query_string.should match(/highlight=#{url.code}/)
|
58
|
+
end
|
59
|
+
|
25
60
|
it "should return a 401 on a wrong API key" do
|
26
61
|
self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'false'
|
27
62
|
last_response.status.should eql(401)
|
@@ -41,8 +76,8 @@ describe "API" do
|
|
41
76
|
}.should_not change(Firefly::Url, :count).by(1)
|
42
77
|
end
|
43
78
|
end
|
44
|
-
end
|
45
|
-
|
79
|
+
end
|
80
|
+
|
46
81
|
describe "getting information" do
|
47
82
|
before(:each) do
|
48
83
|
@created_at = Time.now
|
data/spec/firefly/url_spec.rb
CHANGED
@@ -3,7 +3,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
3
3
|
describe "Url" do
|
4
4
|
|
5
5
|
describe "shortening" do
|
6
|
-
|
7
6
|
it "should generate a code after create" do
|
8
7
|
url = Firefly::Url.shorten("http://example.com/")
|
9
8
|
Firefly::Url.first(:url => "http://example.com/").code.should_not be_nil
|
@@ -26,6 +25,37 @@ describe "Url" do
|
|
26
25
|
Firefly::Url.shorten("http://example.com/")
|
27
26
|
}.should_not change(Firefly::Url, :count)
|
28
27
|
end
|
28
|
+
|
29
|
+
it "should normalize urls correctly" do
|
30
|
+
# Note the trailing '/'
|
31
|
+
Firefly::Url.shorten("http://example.com/")
|
32
|
+
lambda {
|
33
|
+
Firefly::Url.shorten("http://example.com")
|
34
|
+
}.should_not change(Firefly::Url, :count)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "long url validation" do
|
39
|
+
[ "http://ariejan.net",
|
40
|
+
"https://ariejan.net",
|
41
|
+
"http://ariejan.net/page/1",
|
42
|
+
"http://ariejan.net/page/1?q=x&p=123",
|
43
|
+
"http://ariejan.net:8080/"
|
44
|
+
].each do |url|
|
45
|
+
it "should accept #{url}" do
|
46
|
+
Firefly::Url.shorten(url).should_not be_nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
[ "ftp://ariejan.net",
|
51
|
+
"irc://freenode.org/rails",
|
52
|
+
"skype:adevroom",
|
53
|
+
"ariejan.net",
|
54
|
+
].each do |url|
|
55
|
+
it "should not accept #{url}" do
|
56
|
+
Firefly::Url.shorten(url).should be_nil
|
57
|
+
end
|
58
|
+
end
|
29
59
|
end
|
30
60
|
|
31
61
|
describe "clicking" do
|
data/views/index.haml
CHANGED
@@ -17,7 +17,33 @@
|
|
17
17
|
%input.big_url{ :type => 'text', :name => 'url', :id => 'url', :size => 50, :autocomplete => "off", :spellcheck => 'false' }
|
18
18
|
%p
|
19
19
|
%input.big_url{ :type => 'submit', :name => 'submit', :id => 'submit', :value => "Make it short!" }
|
20
|
+
|
21
|
+
- if @error
|
22
|
+
%h2 Whoops!
|
23
|
+
|
24
|
+
%p
|
25
|
+
The URL you posted is invalid. Please post a valid HTTP url, including the protocol (http://) prefix.
|
20
26
|
|
27
|
+
- if @highlight
|
28
|
+
%h2 Your short URL
|
29
|
+
|
30
|
+
%table
|
31
|
+
%tr
|
32
|
+
%td.label Short URL
|
33
|
+
%td.label Full URL
|
34
|
+
%td.label Clicks
|
35
|
+
%td.label Shortened at
|
36
|
+
%td.label
|
37
|
+
%tr.highlighted
|
38
|
+
%td.value
|
39
|
+
%input{ :type => "text", :value => short_url(@highlight), :class => 'short_url', :size => 21 }
|
40
|
+
%td.value.fill <a href="#{@highlight.url}">#{@highlight.url}</a>
|
41
|
+
%td.value.center= @highlight.clicks
|
42
|
+
%td.value= @highlight.created_at.strftime("%Y-%m-%d %H:%M")
|
43
|
+
%td.value
|
44
|
+
%a{ :href => "http://twitter.com/home?status=#{tweet(short_url(@highlight))}" }
|
45
|
+
%img.twitter{ :src => "/images/twitter.png", :width => "16", :height => "16" }
|
46
|
+
|
21
47
|
%h2 Recently shortened
|
22
48
|
|
23
49
|
%table
|
metadata
CHANGED
@@ -5,9 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 4
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
version: 0.4.
|
9
|
+
version: 0.4.1
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Ariejan de Vroom
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-30 00:00:00 +02:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -127,7 +126,6 @@ extra_rdoc_files:
|
|
127
126
|
- README.md
|
128
127
|
files:
|
129
128
|
- .gitignore
|
130
|
-
- API.md
|
131
129
|
- HISTORY
|
132
130
|
- LICENSE
|
133
131
|
- README.md
|
data/API.md
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
# FireFly API
|
2
|
-
|
3
|
-
FireFly exposes its data via an Application Programming Interface (API), so developers can interact in a programmatic way with the FireFly website. This document is the official reference for that functionality. The current API version is 1.0
|
4
|
-
|
5
|
-
## Request / Response
|
6
|
-
|
7
|
-
FireFly responds in JSON.
|
8
|
-
|
9
|
-
A normal response may look like this:
|
10
|
-
|
11
|
-
{
|
12
|
-
"status_code": 200,
|
13
|
-
"data": {
|
14
|
-
"url": "http://example.com/cmeH01",
|
15
|
-
"hash": "cmeH01",
|
16
|
-
"long_url": "http://ariejan.net/",
|
17
|
-
"new_hash": 0
|
18
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
**Status codes**
|
22
|
-
|
23
|
-
FireFly sends back a `status_code`:
|
24
|
-
|
25
|
-
* 200 - OK
|
26
|
-
* 401 - Permission denied - wrong API key
|
27
|
-
* 404 - Hash or short URL not found (only for `expand` and `clicks`)
|
28
|
-
* 500 - Something bad happened.
|
29
|
-
|
30
|
-
## /v1/shorten
|
31
|
-
|
32
|
-
Shorten a long url to a short one.
|
33
|
-
|
34
|
-
GET /v1/shorten
|
35
|
-
|
36
|
-
**Parameters**
|
37
|
-
|
38
|
-
* `api_key` - Your API key, used for authentication
|
39
|
-
* `long_url` - The long URL. E.g. 'http://ariejan.net/'
|
40
|
-
|
41
|
-
**Output**
|
42
|
-
|
43
|
-
* `new_hash` - Is 1 if it's the first time the `long_url` is shortened, 0 otherwise.
|
44
|
-
* `url` - The short URL
|
45
|
-
* `hash` - The FireFly hash, this is a unique value
|
46
|
-
* `long_url` - Echos back the `long_url` that was shortened
|
47
|
-
|
48
|
-
## /v1/expand
|
49
|
-
|
50
|
-
Expand a short URL or hash to the long url.
|
51
|
-
|
52
|
-
GET /v1/expand
|
53
|
-
|
54
|
-
**Parameters**
|
55
|
-
|
56
|
-
* `short_url` - A valid short URL
|
57
|
-
* `hash` - A valid hash
|
58
|
-
* `api_key` - Your API key
|
59
|
-
|
60
|
-
_Note that either `short_url` or `hash` must be specified_
|
61
|
-
|
62
|
-
**Output**
|
63
|
-
|
64
|
-
* `short_url` - The short URL
|
65
|
-
* `hash` - The FireFly hash
|
66
|
-
* `long_url` - The original long URL
|
67
|
-
|
68
|
-
## /v1/clicks
|
69
|
-
|
70
|
-
Request the number of clicks of a specific short URL or hash.
|
71
|
-
|
72
|
-
GET /v1/clicks
|
73
|
-
|
74
|
-
**Parameters**
|
75
|
-
|
76
|
-
* `api_key` - Your API key
|
77
|
-
* `short_url` - A short URL
|
78
|
-
* `hash` - A hash
|
79
|
-
|
80
|
-
_Note that either `short_url` or `hash` must be specified_
|
81
|
-
|
82
|
-
**Output**
|
83
|
-
|
84
|
-
* `short_url` - The short URL
|
85
|
-
* `hash` - The FireFly hash
|
86
|
-
* `clicks` - The total number of clicks on this link
|