firefly 0.2.0 → 0.3.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/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