firefly 0.4.0.1 → 0.4.1

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/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