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 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.0.1
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.0.1"
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-14}
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
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'open-uri'
2
3
  require 'sinatra'
3
4
  require 'dm-core'
4
5
  require 'dm-aggregates'
@@ -53,8 +53,13 @@ module Firefly
53
53
 
54
54
  if !url.nil? && url != ""
55
55
  ff_url = Firefly::Url.shorten(url)
56
- code = ff_url.code
57
- result = "http://#{config[:hostname]}/#{code}"
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 ||= params[: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
- if params[:api_key] == config[:api_key]
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
@@ -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
@@ -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.0.1
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-14 00:00:00 +02:00
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