firefly 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/API.md ADDED
@@ -0,0 +1,86 @@
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
data/HISTORY CHANGED
@@ -1,6 +1,15 @@
1
- = HEAD
1
+ = 0.3.0 / 2010-04-13
2
+
3
+ * 2010-04-13 - Added recent urls to dashboard [ariejan]
4
+ - Added bookmarklet [ariejan]
5
+ - Split up Firefly codebase for better testing and maintainability [ariejan]
6
+
7
+ * 2010-04-02 - Added Ruby 1.9.x compatibility. [ariejan]
8
+
9
+ = 0.2.0 / 2010-03-29
2
10
 
3
11
  * 2010-03-29 - Added some GUI sugar at '/'. [ariejan]
12
+
4
13
  * 2010-03-29 - Added /api/info/:code for basic stats on the specified URL. [ariejan]
5
14
 
6
15
  * 2010-03-29 - Keep count of the number of visits to a given URL. [ariejan]
@@ -11,4 +20,4 @@
11
20
 
12
21
  = 0.1 / 2010-03-28
13
22
 
14
- * First official release [ariejan]
23
+ * First official release [ariejan]
data/README.md CHANGED
@@ -32,6 +32,8 @@ Next you can start your web server. You may try thin:
32
32
 
33
33
  thin start -R config.ru
34
34
 
35
+ Now visit `http://:hostname/` and enter your `:api_key`. Happy URL shortening!
36
+
35
37
  # Configuration
36
38
 
37
39
  All configuration is done in `config.ru`.
@@ -46,6 +48,12 @@ It's possible to use all kinds of backends with DataMapper. Sqlite3 and MySQL ha
46
48
 
47
49
  # Usage
48
50
 
51
+ Simply visit `http://:hostname/` and enter your `:api_key`. You can now shorten URLs.
52
+
53
+ ## Advanced usage
54
+
55
+ You may also use the API to automate URL shortening. Here's how.
56
+
49
57
  Adding a URL is done by doing a simple POST request that includes the URL and your API key.
50
58
 
51
59
  curl -d "url=http://ariejan.net" -d "api_key=test" http://localhost:3000/api/add
@@ -72,34 +80,40 @@ After you restart Terminal.app (or at least reload the `.profile` file) you can
72
80
  * [Source][2]
73
81
  * [Issue tracker][3]
74
82
 
83
+ [2]: http://github.com/ariejan/firefly
84
+ [3]: http://github.com/ariejan/firefly/issues
85
+
75
86
  Feel free to fork Firefly and create patches for it. Here are some basic instructions:
76
87
 
77
- * Fork the project.
78
- * Make your feature addition or bug fix.
79
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
80
- * Commit, do not mess with Rakefile, VERSION, or HISTORY. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
81
- * Send me a pull request. Bonus points for topic branches.
88
+ * [Fork][4] Firefly
89
+ * Create a topic branch - `git checkout -b my_branch`
90
+ * Push to your branch - `git push origin my_branch`
91
+ * Create an [Issue][5] with a link to your branch
92
+ * That's it!
82
93
 
94
+ [4]: http://help.github.com/forking/
95
+ [5]: http://github.com/ariejan/firefly/issues
96
+
83
97
  # License
84
98
 
85
- Copyright (c) 2009 Ariejan de Vroom
86
-
87
- Permission is hereby granted, free of charge, to any person obtaining
88
- a copy of this software and associated documentation files (the
89
- "Software"), to deal in the Software without restriction, including
90
- without limitation the rights to use, copy, modify, merge, publish,
91
- distribute, sublicense, and/or sell copies of the Software, and to
92
- permit persons to whom the Software is furnished to do so, subject to
93
- the following conditions:
94
-
95
- The above copyright notice and this permission notice shall be
96
- included in all copies or substantial portions of the Software.
97
-
98
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
99
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
100
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
101
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
102
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
103
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
104
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
99
+ Copyright (c) 2009 Ariejan de Vroom
100
+
101
+ Permission is hereby granted, free of charge, to any person obtaining
102
+ a copy of this software and associated documentation files (the
103
+ "Software"), to deal in the Software without restriction, including
104
+ without limitation the rights to use, copy, modify, merge, publish,
105
+ distribute, sublicense, and/or sell copies of the Software, and to
106
+ permit persons to whom the Software is furnished to do so, subject to
107
+ the following conditions:
108
+
109
+ The above copyright notice and this permission notice shall be
110
+ included in all copies or substantial portions of the Software.
111
+
112
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
113
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
114
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
115
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
116
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
117
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
118
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
105
119
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/config.ru.example CHANGED
@@ -10,6 +10,9 @@ app = Firefly::Server.new do
10
10
  # Use Sqlite3 by default
11
11
  set :database, "sqlite3://#{Dir.pwd}/firefly.sqlite3"
12
12
 
13
+ # Set number of recent urls to show
14
+ set :recent_urls, 10
15
+
13
16
  # You can use MySQL as well.
14
17
  # Make sure to install the do_mysql gem:
15
18
  # sudo gem install do_mysql
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.2.0"
8
+ s.version = "0.3.0"
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-03-29}
12
+ s.date = %q{2010-04-13}
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,6 +18,7 @@ Gem::Specification.new do |s|
18
18
  ]
19
19
  s.files = [
20
20
  ".gitignore",
21
+ "API.md",
21
22
  "HISTORY",
22
23
  "LICENSE",
23
24
  "README.md",
@@ -35,7 +36,10 @@ Gem::Specification.new do |s|
35
36
  "public/jquery-1.4.2.min.js",
36
37
  "public/reset.css",
37
38
  "public/style.css",
38
- "spec/firefly_spec.rb",
39
+ "spec/firefly/api_spec.rb",
40
+ "spec/firefly/base62_spec.rb",
41
+ "spec/firefly/server_spec.rb",
42
+ "spec/firefly/url_spec.rb",
39
43
  "spec/spec.opts",
40
44
  "spec/spec_helper.rb",
41
45
  "views/index.haml",
@@ -49,7 +53,10 @@ Gem::Specification.new do |s|
49
53
  s.rubygems_version = %q{1.3.6}
50
54
  s.summary = %q{FireFly is a simple URL shortner for personal use}
51
55
  s.test_files = [
52
- "spec/firefly_spec.rb",
56
+ "spec/firefly/api_spec.rb",
57
+ "spec/firefly/base62_spec.rb",
58
+ "spec/firefly/server_spec.rb",
59
+ "spec/firefly/url_spec.rb",
53
60
  "spec/spec_helper.rb"
54
61
  ]
55
62
 
@@ -11,7 +11,7 @@ module Firefly
11
11
  s << CHARS[rem]
12
12
  end
13
13
  s << CHARS[value]
14
- s.reverse.to_s
14
+ s.reverse.join("")
15
15
  end
16
16
 
17
17
  def self.decode(str)
@@ -2,9 +2,10 @@ module Firefly
2
2
  class Config < Hash
3
3
 
4
4
  DEFAULTS = {
5
- :hostname => "localhost:3000",
6
- :api_key => "test",
7
- :database => "sqlite3://#{Dir.pwd}/firefly_#{ENV['RACK_ENV']}.sqlite3"
5
+ :hostname => "localhost:3000",
6
+ :api_key => "test",
7
+ :database => "sqlite3://#{Dir.pwd}/firefly_#{ENV['RACK_ENV']}.sqlite3",
8
+ :recent_urls => 25
8
9
  }
9
10
 
10
11
  def initialize obj
@@ -44,11 +44,16 @@ module Firefly
44
44
  end
45
45
  end
46
46
 
47
+ def short_url(url)
48
+ "http://#{config[:hostname]}/#{url.code}"
49
+ end
50
+
47
51
  def generate_short_url(url = nil)
48
- result, code = nil, nil
52
+ code, result = nil, nil
49
53
 
50
54
  if !url.nil? && url != ""
51
- code = Firefly::Url.encode(url)
55
+ ff_url = Firefly::Url.shorten(url)
56
+ code = ff_url.code
52
57
  result = "http://#{config[:hostname]}/#{code}"
53
58
  end
54
59
 
@@ -62,6 +67,7 @@ module Firefly
62
67
  end
63
68
 
64
69
  get '/' do
70
+ @urls = Firefly::Url.all(:limit => config[:recent_urls], :order => [ :created_at.desc ])
65
71
  haml :index
66
72
  end
67
73
 
@@ -76,12 +82,13 @@ module Firefly
76
82
  redirect '/'
77
83
  end
78
84
 
85
+ # GET /add?url=http://ariejan.net&api_key=test
79
86
  # POST /add?url=http://ariejan.net&api_key=test
80
87
  #
81
88
  # Returns the shortened URL
82
- post '/api/add' do
89
+ api_add = lambda {
83
90
  validate_api_permission
84
- @url = params[:url]
91
+ @url = params[:url]
85
92
  @code, @result = generate_short_url(@url)
86
93
  @result ||= "Invalid URL specified."
87
94
 
@@ -90,7 +97,10 @@ module Firefly
90
97
  else
91
98
  @result
92
99
  end
93
- end
100
+ }
101
+
102
+ get '/api/add', &api_add
103
+ post '/api/add', &api_add
94
104
 
95
105
  # GET /b3d+
96
106
  #
@@ -98,24 +108,29 @@ module Firefly
98
108
  get '/api/info/:code' do
99
109
  validate_api_permission
100
110
 
101
- @url = Firefly::Url.decode(params[:code])
102
- @short_url = "http://#{config[:hostname]}/#{@url.code}"
111
+ @url = Firefly::Url.first(:code => params[:code])
103
112
 
104
- haml :info
113
+ if @url.nil?
114
+ status 404
115
+ "Sorry, that code is unknown."
116
+ else
117
+ @short_url = "http://#{config[:hostname]}/#{@url.code}"
118
+ haml :info
119
+ end
105
120
  end
106
121
 
107
122
  # GET /b3d
108
123
  #
109
124
  # Redirect to the shortened URL
110
125
  get '/:code' do
111
- url = Firefly::Url.decode(params[:code])
126
+ @url = Firefly::Url.first(:code => params[:code])
112
127
 
113
- if url.nil?
128
+ if @url.nil?
114
129
  status 404
115
130
  "Sorry, that code is unknown."
116
131
  else
117
- url.visit!
118
- redirect url.url, 301
132
+ @url.register_click!
133
+ redirect @url.url, 301
119
134
  end
120
135
  end
121
136
 
data/lib/firefly/url.rb CHANGED
@@ -4,32 +4,20 @@ module Firefly
4
4
 
5
5
  property :id, Serial
6
6
  property :url, String, :index => true, :length => 255
7
- property :code, String, :index => true, :length => 255
8
- property :visits, Integer, :default => 0
7
+ property :code, String, :index => true, :length => 16
8
+ property :clicks, Integer, :default => 0
9
9
  property :created_at, DateTime, :default => Time.now
10
10
 
11
11
  # Increase the visits counter by 1
12
- def visit!
13
- self.update(:visits => self.visits + 1)
12
+ def register_click!
13
+ self.update(:clicks => self.clicks + 1)
14
14
  end
15
15
 
16
- # Encode a URL and return the encoded ID
17
- def self.encode(url)
18
-
19
- @result = self.first(:url => url)
20
-
21
- if @result.nil?
22
- @result = self.create(:url => url)
23
- @result.update(:code => Firefly::Base62.encode(@result.id.to_i))
24
- end
25
-
26
- return @result.code
27
- end
28
-
29
- # Decode a code to the original URL
30
- def self.decode(code)
31
- @result = Firefly::Url.first(:code => code)
32
- return @result.nil? ? nil : @result
33
- end
16
+ # Shorten a long_url and return a new FireFly::Url
17
+ def self.shorten(long_url)
18
+ the_url = Firefly::Url.first(:url => long_url) || Firefly::Url.create(:url => long_url)
19
+ the_url.update(:code => Firefly::Base62.encode(the_url.id.to_i)) if the_url.code.nil?
20
+ the_url
21
+ end
34
22
  end
35
23
  end
data/public/style.css CHANGED
@@ -73,3 +73,12 @@ body { padding:0; margin:0; }
73
73
  #main p.pagination a.more { float:right;}
74
74
 
75
75
  #main form.clear-failed {float:right; margin-top:-10px;}
76
+
77
+ #main input.big_url { font-size: 1.8em; padding: 0.2em; }
78
+
79
+ #main h2 { color: #ce1212;}
80
+
81
+ #main .sidebox { float: right; width: 320px; }
82
+
83
+ #main .the_form { margin-bottom: 30px; }
84
+ #main table tr td.label { font-weight: bold; }
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "API" do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ @@app
8
+ end
9
+
10
+ [:post, :get].each do |verb|
11
+ describe "adding a URL by #{verb.to_s.upcase}" do
12
+ it "should be okay adding a new URL" do
13
+ self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
14
+ last_response.should be_ok
15
+ end
16
+
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")
21
+
22
+ last_response.body.should eql("http://test.host/#{url.code}")
23
+ end
24
+
25
+ it "should return a 401 on a wrong API key" do
26
+ self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'false'
27
+ last_response.status.should eql(401)
28
+ end
29
+
30
+ it "should create a new Firefly::Url" do
31
+ lambda {
32
+ self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
33
+ }.should change(Firefly::Url, :count).by(1)
34
+ end
35
+
36
+ it "should not create the same Firefly::Url twice" do
37
+ self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
38
+
39
+ lambda {
40
+ self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
41
+ }.should_not change(Firefly::Url, :count).by(1)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "getting information" do
47
+ before(:each) do
48
+ @created_at = Time.now
49
+ @url = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha', :clicks => 69, :created_at => @created_at)
50
+ end
51
+
52
+ it "should work" do
53
+ get '/api/info/alpha', :api_key => "test"
54
+ last_response.should be_ok
55
+ end
56
+
57
+ it "should show the click count" do
58
+ get '/api/info/alpha', :api_key => "test"
59
+ last_response.body.should match(/69/)
60
+ end
61
+
62
+ it "should show the short URL" do
63
+ get '/api/info/alpha', :api_key => "test"
64
+ last_response.body.should match(/alpha/)
65
+ end
66
+
67
+ it "should show the shortened at time" do
68
+ get '/api/info/alpha', :api_key => "test"
69
+ last_response.body.should match(/#{@created_at.strftime("%Y-%m-%d %H:%M")}/)
70
+ end
71
+
72
+ it "should show the full URL" do
73
+ get '/api/info/alpha', :api_key => "test"
74
+ last_response.body.should match(/http:\/\/example.com\/123/)
75
+ end
76
+
77
+ it "should validate API permissions" do
78
+ get '/api/info/alpha', :api_key => false
79
+ last_response.status.should be(401)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Base62 encoding/decoding" do
4
+ [
5
+ [ 1, "1"],
6
+ [ 10, "a"],
7
+ [ 61, "Z"],
8
+ [ 62, "10"],
9
+ [ 63, "11"],
10
+ [ 124, "20"],
11
+ [200000000, "dxb8s"]
12
+ ].each do |input, output|
13
+ it "should encode #{input} correctly to #{output}" do
14
+ Firefly::Base62.encode(input).should eql(output)
15
+ end
16
+
17
+ it "should decode correctly" do
18
+ Firefly::Base62.decode(output).should eql(input)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Firefly" do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ @@app
8
+ end
9
+
10
+ describe "/" do
11
+ it "should respond ok" do
12
+ get '/'
13
+ last_response.should be_ok
14
+ end
15
+ end
16
+
17
+ describe "redirecting" do
18
+ it "should redirect to the original URL" do
19
+ fake = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
20
+
21
+ get '/alpha'
22
+ follow_redirect!
23
+
24
+ last_request.url.should eql('http://example.com/123')
25
+ end
26
+
27
+ it "should increase the visits counter" do
28
+ fake = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
29
+ Firefly::Url.should_receive(:first).and_return(fake)
30
+
31
+ lambda {
32
+ get '/alpha'
33
+ }.should change(fake, :clicks).by(1)
34
+ end
35
+
36
+ it "should redirect with a 301 Permanent redirect" do
37
+ fake = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
38
+
39
+ get '/alpha'
40
+
41
+ last_response.status.should eql(301)
42
+ end
43
+
44
+ it "should throw a 404 when the code is unknown" do
45
+ get '/some_random_code_that_does_not_exist'
46
+
47
+ last_response.status.should eql(404)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Url" do
4
+
5
+ describe "shortening" do
6
+
7
+ it "should generate a code after create" do
8
+ url = Firefly::Url.shorten("http://example.com/")
9
+ Firefly::Url.first(:url => "http://example.com/").code.should_not be_nil
10
+ end
11
+
12
+ it "should set a clicks count of 0 for newly shortened urls" do
13
+ url = Firefly::Url.shorten("http://example.com/")
14
+ Firefly::Url.first(:url => "http://example.com/").clicks.should eql(0)
15
+ end
16
+
17
+ it "should create a new Firefly::Url with a new long_url" do
18
+ lambda {
19
+ Firefly::Url.shorten("http://example.com/")
20
+ }.should change(Firefly::Url, :count).by(1)
21
+ end
22
+
23
+ it "should return an existing Firefly::Url if the long_url exists" do
24
+ Firefly::Url.shorten("http://example.com/")
25
+ lambda {
26
+ Firefly::Url.shorten("http://example.com/")
27
+ }.should_not change(Firefly::Url, :count)
28
+ end
29
+ end
30
+
31
+ describe "clicking" do
32
+ before(:each) do
33
+ Firefly::Url.create(
34
+ :url => 'http://example.com/123',
35
+ :code => 'alpha',
36
+ :clicks => 69
37
+ )
38
+ @url = Firefly::Url.first(:code => 'alpha')
39
+ end
40
+
41
+ it "should increase the click count" do
42
+ lambda {
43
+ @url.register_click!
44
+ }.should change(@url, :clicks).by(1)
45
+ end
46
+ end
47
+ end
data/views/index.haml CHANGED
@@ -1,14 +1,39 @@
1
1
  - if @authenticated
2
- %h1 How about shortening a URL?
2
+
3
+ .sidebox
4
+ %h2 Bookmarklet
3
5
 
4
- %form{ :action => '/api/add', :method => 'post' }
5
- %input{ :type => "hidden", :name => "visual", :value => "1" }
6
6
  %p
7
- %label{ :for => 'url' } Long and ugly URL
8
- %input{ :type => 'text', :name => 'url', :id => 'url' }
7
+ Drag the following link to your bookmarks. Click it to shorten the URL to the page you're currently viewing.
9
8
  %p
10
- %input{ :type => 'submit', :name => 'submit', :id => 'submit', :value => "Shorten this!" }
11
-
9
+ %a{ :href => "javascript:var%20d=document,w=window,enc=encodeURIComponent,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),s2=((s.toString()=='')?s:('%22'+enc(s)+'%22')),f='http://#{@config[:hostname]}/api/add',l=d.location,p='?visual=1&api_key=#{@config[:api_key]}&url='+enc(l.href),u=f+p;try{if(!/^(.*\.)?tumblrzzz[^.]*$/.test(l.host))throw(0);tstbklt();}catch(z){a%20=function(){if(!w.open(u))l.href=u;};if(/Firefox/.test(navigator.userAgent))setTimeout(a,0);else%20a();}void(0)" } Shorten with #{@config[:hostname]}
10
+
11
+ %h1 How about shortening a URL?
12
+
13
+ .the_form
14
+ %form{ :action => '/api/add', :method => 'post' }
15
+ %input{ :type => "hidden", :name => "visual", :value => "1" }
16
+ %p
17
+ %input.big_url{ :type => 'text', :name => 'url', :id => 'url', :size => 50 }
18
+ %p
19
+ %input.big_url{ :type => 'submit', :name => 'submit', :id => 'submit', :value => "Make it short!" }
20
+
21
+ %h2 Recently shortened
22
+
23
+ %table
24
+ %tr
25
+ %td.label Short URL
26
+ %td.label Full URL
27
+ %td.label Clicks
28
+ %td.label Shortened at
29
+ - @urls.each do |url|
30
+ %tr
31
+ %td.value #{short_url(url)}
32
+ %td.value <a href="#{url.url}">#{url.url}</a>
33
+ %td.value= url.clicks
34
+ %td.value= url.created_at.strftime("%Y-%m-%d %H:%M")
35
+
36
+
12
37
  - else
13
38
  %h1 Please enter your API key
14
39
 
data/views/info.haml CHANGED
@@ -1,15 +1,18 @@
1
+ %p.link
2
+ %a{ :href => "/" } &laquo; Back
3
+
1
4
  %h1 http://#{@config[:hostname]}/#{@url.code}
2
5
 
3
6
  %table
4
7
  %tr
5
8
  %td.label Short URL
6
- %td.value <a href="#{@short_url}">#{@short_url}</a>
9
+ %td.value #{@short_url}
7
10
  %tr
8
11
  %td.label Full URL
9
12
  %td.value <a href="#{@url.url}">#{@url.url}</a>
10
13
  %tr
11
14
  %td.label Clicks
12
- %td.value= @url.visits
15
+ %td.value= @url.clicks
13
16
  %tr
14
17
  %td.label Shortened at
15
18
  %td.value= @url.created_at.strftime("%Y-%m-%d %H:%M")
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
7
+ - 3
8
8
  - 0
9
- version: 0.2.0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ariejan de Vroom
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-29 00:00:00 +02:00
17
+ date: 2010-04-13 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -126,6 +126,7 @@ extra_rdoc_files:
126
126
  - README.md
127
127
  files:
128
128
  - .gitignore
129
+ - API.md
129
130
  - HISTORY
130
131
  - LICENSE
131
132
  - README.md
@@ -143,7 +144,10 @@ files:
143
144
  - public/jquery-1.4.2.min.js
144
145
  - public/reset.css
145
146
  - public/style.css
146
- - spec/firefly_spec.rb
147
+ - spec/firefly/api_spec.rb
148
+ - spec/firefly/base62_spec.rb
149
+ - spec/firefly/server_spec.rb
150
+ - spec/firefly/url_spec.rb
147
151
  - spec/spec.opts
148
152
  - spec/spec_helper.rb
149
153
  - views/index.haml
@@ -180,5 +184,8 @@ signing_key:
180
184
  specification_version: 3
181
185
  summary: FireFly is a simple URL shortner for personal use
182
186
  test_files:
183
- - spec/firefly_spec.rb
187
+ - spec/firefly/api_spec.rb
188
+ - spec/firefly/base62_spec.rb
189
+ - spec/firefly/server_spec.rb
190
+ - spec/firefly/url_spec.rb
184
191
  - spec/spec_helper.rb
data/spec/firefly_spec.rb DELETED
@@ -1,163 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- require File.dirname(__FILE__) + '/spec_helper'
4
-
5
- describe "Firefly" do
6
- include Rack::Test::Methods
7
-
8
- def app
9
- @@app
10
- end
11
-
12
- describe "/" do
13
- it "should respond ok" do
14
- get '/'
15
- last_response.should be_ok
16
- end
17
- end
18
-
19
- describe "Url" do
20
- before(:each) do
21
- @url = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
22
- end
23
-
24
- it "should set a created_at timestamp" do
25
- @url.created_at.should_not be_nil
26
- end
27
-
28
- it "should have a visit count of 0 by default" do
29
- @url.visits.should eql(0)
30
- end
31
-
32
- it "should increase visits when calling visit!" do
33
- lambda {
34
- @url.visit!
35
- }.should change(@url, :visits).by(1)
36
- end
37
- end
38
-
39
- describe "adding a URL" do
40
- it "should be okay adding a new URL" do
41
- post '/api/add', :url => 'http://example.org', :api_key => 'test'
42
- last_response.should be_ok
43
- end
44
-
45
- it "should return the shortened URL" do
46
- post '/api/add', :url => 'http://example.org', :api_key => 'test'
47
-
48
- url = Firefly::Url.first(:url => "http://example.org")
49
-
50
- last_response.body.should eql("http://test.host/#{url.code}")
51
- end
52
-
53
- it "should return a 401 on a wrong API key" do
54
- post '/api/add', :url => 'http://example.org', :api_key => 'false'
55
- last_response.status.should eql(401)
56
- end
57
-
58
- it "should create a new Firefly::Url" do
59
- lambda {
60
- post '/api/add', :url => 'http://example.org', :api_key => 'test'
61
- }.should change(Firefly::Url, :count).by(1)
62
- end
63
-
64
- it "should not create the same Firefly::Url twice" do
65
- post '/api/add', :url => 'http://example.org', :api_key => 'test'
66
-
67
- lambda {
68
- post '/api/add', :url => 'http://example.org', :api_key => 'test'
69
- }.should_not change(Firefly::Url, :count).by(1)
70
- end
71
- end
72
-
73
- describe "Base62 encoding/decoding" do
74
- [
75
- [ 1, "1"],
76
- [ 10, "a"],
77
- [ 61, "Z"],
78
- [ 62, "10"],
79
- [ 63, "11"],
80
- [ 124, "20"],
81
- [200000000, "dxb8s"]
82
- ].each do |input, output|
83
- it "should encode #{input} correctly to #{output}" do
84
- Firefly::Base62.encode(input).should eql(output)
85
- end
86
-
87
- it "should decode correctly" do
88
- Firefly::Base62.decode(output).should eql(input)
89
- end
90
- end
91
- end
92
-
93
- describe "getting information" do
94
- before(:each) do
95
- @created_at = Time.now
96
- @url = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha', :visits => 69, :created_at => @created_at)
97
- end
98
-
99
- it "should work" do
100
- get '/api/info/alpha', :api_key => "test"
101
- last_response.should be_ok
102
- end
103
-
104
- it "should show the visit count" do
105
- get '/api/info/alpha', :api_key => "test"
106
- last_response.body.should match(/69/)
107
- end
108
-
109
- it "should show the short URL" do
110
- get '/api/info/alpha', :api_key => "test"
111
- last_response.body.should match(/alpha/)
112
- end
113
-
114
- it "should show the shortened at time" do
115
- get '/api/info/alpha', :api_key => "test"
116
- last_response.body.should match(/#{@created_at.strftime("%Y-%m-%d %H:%M")}/)
117
- end
118
-
119
- it "should show the full URL" do
120
- get '/api/info/alpha', :api_key => "test"
121
- last_response.body.should match(/http:\/\/example.com\/123/)
122
- end
123
-
124
- it "should validate API permissions" do
125
- get '/api/info/alpha', :api_key => false
126
- last_response.status.should be(401)
127
- end
128
- end
129
-
130
- describe "redirecting" do
131
- it "should redirect to the original URL" do
132
- fake = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
133
-
134
- get '/alpha'
135
- follow_redirect!
136
-
137
- last_request.url.should eql('http://example.com/123')
138
- end
139
-
140
- it "should increase the visits counter" do
141
- fake = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
142
- Firefly::Url.should_receive(:decode).and_return(fake)
143
-
144
- lambda {
145
- get '/alpha'
146
- }.should change(fake, :visits).by(1)
147
- end
148
-
149
- it "should redirect with a 301 Permanent redirect" do
150
- fake = Firefly::Url.create(:url => 'http://example.com/123', :code => 'alpha')
151
-
152
- get '/alpha'
153
-
154
- last_response.status.should eql(301)
155
- end
156
-
157
- it "should throw a 404 when the code is unknown" do
158
- get '/some_random_code_that_does_not_exist'
159
-
160
- last_response.status.should eql(404)
161
- end
162
- end
163
- end