firefly 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.sqlite3
2
+ sinatra.log
2
3
  config.ru
3
4
  pkg
4
5
  .rvmrc
data/HISTORY CHANGED
@@ -1,3 +1,9 @@
1
+ = HEAD
2
+
3
+ * 2010-08-10 - Added sorting of shortened URLs. Closes #12.
4
+ * 2010-08-10 - Added CSV, XML and YAML export of all shortened URLs. Closes #11.
5
+ * 2010-08-10 - Updated bookmarklet JavaScript to escape URL-unsafe charachters in the API key. Fixes #17
6
+
1
7
  = 0.4.4 / 2010-06-20
2
8
 
3
9
  * 2010-06-20 - Updated gem dependencies for DataMapper 1.0.0
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # FireFly
2
2
 
3
- FireFly is a simple URL shortener for personal use.
3
+ FireFly is a simple URL shortener for personal (or not so personal) use.
4
+
5
+ # Quick-Start (2 minutes) with Heroku
6
+
7
+ See the [screencast][1] or [written instructions][2] on how to setup Firefly within 2 minutes on [Heroku][3]
4
8
 
5
9
  # Installation
6
10
 
@@ -54,14 +58,12 @@ All configuration is done in `config.ru`.
54
58
 
55
59
  * `:hostname` sets the hostname to use for shortened URLs.
56
60
  * `:api_key` sets the API key. This key is required when posting new URLs
57
- * `:database` a database URI that [DataMapper][1] can understand.
61
+ * `:database` a database URI that [DataMapper][4] can understand.
58
62
  * `:recent_urls` sets the number of URLs to show in the overview. Default: 25.
59
63
  * `:tweet` set the template to use for tweets. Default: `"Check this out: %short_url%"`
60
64
 
61
65
  It's possible to use all kinds of backends with DataMapper. Sqlite3 and MySQL have been tested, but others may work as well.
62
66
 
63
- [1]: http://datamapper.org/
64
-
65
67
  # Usage
66
68
 
67
69
  Simply visit `http://:hostname/` and enter your `:api_key`. You can now shorten URLs.
@@ -93,22 +95,25 @@ After you restart Terminal.app (or at least reload the `.profile` file) you can
93
95
 
94
96
  # Bugs, Feature Requests, etc.
95
97
 
96
- * [Source][2]
97
- * [Issue tracker][3]
98
-
99
- [2]: http://github.com/ariejan/firefly
100
- [3]: http://github.com/ariejan/firefly/issues
98
+ * [Source][5]
99
+ * [Issue tracker][6]
101
100
 
102
101
  Feel free to fork Firefly and create patches for it. Here are some basic instructions:
103
102
 
104
- * [Fork][4] Firefly
103
+ * [Fork][7] Firefly
105
104
  * Create a topic branch - `git checkout -b my_branch`
106
105
  * Push to your branch - `git push origin my_branch`
107
- * Create an [Issue][5] with a link to your branch
106
+ * Create an [Issue][8] with a link to your branch
108
107
  * That's it!
109
108
 
110
- [4]: http://help.github.com/forking/
111
- [5]: http://github.com/ariejan/firefly/issues
109
+ [1]: http://ariejan.net/2010/07/12/screencast-firefly-url-shortener-in-less-than-25-minutes/
110
+ [2]: http://ariejan.net/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/
111
+ [3]: http://heroku.com
112
+ [4]: http://datamapper.org/
113
+ [5]: http://github.com/ariejan/firefly
114
+ [6]: http://github.com/ariejan/firefly/issues
115
+ [7]: http://help.github.com/forking/
116
+ [8]: http://github.com/ariejan/firefly/issues
112
117
 
113
118
  # License
114
119
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 0.4.5
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.4"
8
+ s.version = "0.4.5"
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-06-20}
12
+ s.date = %q{2010-08-10}
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 = [
@@ -36,10 +36,14 @@ Gem::Specification.new do |s|
36
36
  "public/jquery-1.4.2.min.js",
37
37
  "public/reset.css",
38
38
  "public/style.css",
39
+ "spec/files/export.csv",
40
+ "spec/files/export.xml",
41
+ "spec/files/export.yml",
39
42
  "spec/firefly/api_spec.rb",
40
43
  "spec/firefly/base62_spec.rb",
41
44
  "spec/firefly/server_spec.rb",
42
45
  "spec/firefly/url_spec.rb",
46
+ "spec/fixtures/urls.yml",
43
47
  "spec/spec.opts",
44
48
  "spec/spec_helper.rb",
45
49
  "views/index.haml",
data/lib/firefly.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'open-uri'
3
+ require 'cgi'
4
+ require 'yaml'
3
5
  require 'sinatra'
4
6
  require 'dm-core'
5
7
  require 'dm-migrations'
@@ -94,7 +94,12 @@ module Firefly
94
94
  get '/' do
95
95
  @highlight = Firefly::Url.first(:code => params[:highlight])
96
96
  @error = params[:highlight] == "error"
97
- @urls = Firefly::Url.all(:limit => config[:recent_urls], :order => [ :created_at.desc ])
97
+
98
+ sort_column = params[:s] || 'created_at'
99
+ sort_order = params[:d] || 'desc'
100
+
101
+ @urls = Firefly::Url.all(:limit => config[:recent_urls], :order => [ sort_column.to_sym.send(sort_order.to_sym) ] )
102
+
98
103
  haml :index
99
104
  end
100
105
 
@@ -142,7 +147,73 @@ module Firefly
142
147
  haml :info
143
148
  end
144
149
  end
145
-
150
+
151
+ # GET /api/export.csv
152
+ #
153
+ # Download a CSV file with all shortened URLs
154
+ get '/api/export.csv' do
155
+ validate_api_permission or return "Permission denied: Invalid API key"
156
+
157
+ @urls = Firefly::Url.all(:order => [ :created_at.asc ])
158
+
159
+ output = "\"Code\",\"Short URL\",\"Long URL\",\"Clicks\",\"Created at\"\n"
160
+ @urls.each do |url|
161
+ output += "\"#{url.code}\",\"#{short_url(url)}\",\"#{url.url}\",\"#{url.clicks}\",\"#{url.created_at.strftime('%Y-%m-%d %H:%M:%S')}\"\n"
162
+ end
163
+
164
+ attachment "firefly-export.csv"
165
+ content_type "text/csv"
166
+ output
167
+ end
168
+
169
+ # GET /api/export.xml
170
+ #
171
+ # Download a XML file with all shortened URLs
172
+ get '/api/export.xml' do
173
+ validate_api_permission or return "Permission denied: Invalid API key"
174
+
175
+ @urls = Firefly::Url.all(:order => [ :created_at.asc ])
176
+
177
+ # I know, manual XML creation is ugly, at least you don't need nokogiri
178
+ output = "<urls>\n"
179
+ @urls.each do |url|
180
+ output += " <url>\n"
181
+ output += " <code>#{url.code}</code>\n"
182
+ output += " <short_url>#{short_url(url)}</short_url>\n"
183
+ output += " <long_url>#{url.url}</long_url>\n"
184
+ output += " <clicks>#{url.clicks}</clicks>\n"
185
+ output += " <created_at>#{url.created_at.strftime('%Y-%m-%d %H:%M:%S')}</created_at>\n"
186
+ output += " </url>\n"
187
+ end
188
+ output += "</urls>\n"
189
+
190
+ attachment "firefly-export.xml"
191
+ content_type "text/xml"
192
+ output
193
+ end
194
+
195
+ # GET /api/export.yml
196
+ #
197
+ # Download a YAML file with all shortened URLs
198
+ get '/api/export.yml' do
199
+ validate_api_permission or return "Permission denied: Invalid API key"
200
+
201
+ @urls = Firefly::Url.all(:order => [ :created_at.asc ])
202
+
203
+ output = {}
204
+ @urls.each do |url|
205
+ output[url.code] = { 'code' => url.code,
206
+ 'short_url' => short_url(url),
207
+ 'long_url' => url.url,
208
+ 'clicks' => url.clicks,
209
+ 'created_at' => url.created_at.strftime('%Y-%m-%d %H:%M:%S') }
210
+ end
211
+
212
+ attachment "firefly-export.yml"
213
+ content_type "text/yaml"
214
+ YAML::dump(output)
215
+ end
216
+
146
217
  # GET /b3d
147
218
  #
148
219
  # Redirect to the shortened URL
data/public/style.css CHANGED
@@ -87,5 +87,8 @@ body { padding:0; margin:0; }
87
87
  #main table tr td.value.fill { width: 100%; white-space: normal; }
88
88
  #main table tr td.value.center { text-align: center; }
89
89
  #main table tr td.label { font-weight: bold; }
90
+ #main table tr td.label a { text-decoration: none; font-size: 11px; }
91
+ #main table tr td.label a.highlight { color: #f00; }
92
+
90
93
  #main table tr td input.short_url { border: 1px solid #CCC; color: #666; font-family: Monaco, 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 11px; height: 16px; padding: 3px 5px 2px; width: 160px; }
91
- #main table tr td img.twitter { border: 0; margin: 0; padding: 0; float: right; width: 16px; height: 16px; }
94
+ #main table tr td img.twitter { border: 0; margin: 0; padding: 0; float: right; width: 16px; height: 16px; }
@@ -0,0 +1,3 @@
1
+ "Code","Short URL","Long URL","Clicks","Created at"
2
+ "def","http://test.host/def","http://example.org/","456","2010-02-24 14:55:00"
3
+ "abc","http://test.host/abc","http://example.com/","123","2010-04-01 12:00:00"
@@ -0,0 +1,16 @@
1
+ <urls>
2
+ <url>
3
+ <code>def</code>
4
+ <short_url>http://test.host/def</short_url>
5
+ <long_url>http://example.org/</long_url>
6
+ <clicks>456</clicks>
7
+ <created_at>2010-02-24 14:55:00</created_at>
8
+ </url>
9
+ <url>
10
+ <code>abc</code>
11
+ <short_url>http://test.host/abc</short_url>
12
+ <long_url>http://example.com/</long_url>
13
+ <clicks>123</clicks>
14
+ <created_at>2010-04-01 12:00:00</created_at>
15
+ </url>
16
+ </urls>
@@ -0,0 +1,13 @@
1
+ ---
2
+ abc:
3
+ created_at: 2010-04-01 12:00:00
4
+ long_url: http://example.com/
5
+ short_url: http://test.host/abc
6
+ code: abc
7
+ clicks: 123
8
+ def:
9
+ created_at: 2010-02-24 14:55:00
10
+ long_url: http://example.org/
11
+ short_url: http://test.host/def
12
+ code: def
13
+ clicks: 456
@@ -69,7 +69,7 @@ describe "API" do
69
69
 
70
70
  it "should create a new Firefly::Url" do
71
71
  lambda {
72
- self.send verb, '/api/add', :url => 'http://example.org', :api_key => 'test'
72
+ self.send verb, '/api/add', :url => 'http://example.org/', :api_key => 'test'
73
73
  }.should change(Firefly::Url, :count).by(1)
74
74
  end
75
75
 
@@ -119,4 +119,40 @@ describe "API" do
119
119
  last_response.status.should be(401)
120
120
  end
121
121
  end
122
+
123
+ describe "exports" do
124
+ before(:each) do
125
+ load_fixtures
126
+ end
127
+
128
+ it "should export in CSV" do
129
+ get '/api/export.csv', :api_key => "test"
130
+ last_response.body.should eql(spec_file('export.csv'))
131
+ end
132
+
133
+ it "should export in XML" do
134
+ get '/api/export.xml', :api_key => "test"
135
+ last_response.body.should eql(spec_file('export.xml'))
136
+ end
137
+
138
+ it "should export in YAML" do
139
+ get '/api/export.yml', :api_key => "test"
140
+ last_response.body.should eql(spec_file('export.yml'))
141
+ end
142
+ end
143
+
144
+ describe "api key" do
145
+ def app
146
+ Firefly::Server.new do
147
+ set :hostname, "test.host"
148
+ set :api_key, "test#!"
149
+ set :database, "sqlite3://#{Dir.pwd}/firefly_test.sqlite3"
150
+ end
151
+ end
152
+
153
+ it "should be okay adding a new URL" do
154
+ self.send :get, '/api/add', :url => 'http://example.org/api_key_test', :api_key => 'test#!'
155
+ last_response.should be_ok
156
+ end
157
+ end
122
158
  end
@@ -0,0 +1,11 @@
1
+ one:
2
+ url: http://example.com/
3
+ code: abc
4
+ clicks: 123
5
+ created_at: 2010-04-01 12:00:00
6
+
7
+ two:
8
+ url: http://example.org/
9
+ code: def
10
+ clicks: 456
11
+ created_at: 2010-02-24 14:55:00
data/spec/spec_helper.rb CHANGED
@@ -6,6 +6,7 @@ require 'rack/test'
6
6
  require 'spec'
7
7
  require 'spec/autorun'
8
8
  require 'spec/interop/test'
9
+ require 'yaml'
9
10
 
10
11
  # set test environment
11
12
  set :environment, :test
@@ -37,4 +38,16 @@ Spec::Runner.configure do |config|
37
38
  r.adapter.push_transaction(transaction)
38
39
  end
39
40
  end
40
- end
41
+
42
+ # Loads the urls.yml fixtures.
43
+ def load_fixtures
44
+ Firefly::Url.destroy
45
+ urls = YAML::load(File.open('spec/fixtures/urls.yml'))
46
+ urls.each { |key, url| Firefly::Url.create(url) }
47
+ end
48
+
49
+ # Load a spec file and return its contents
50
+ def spec_file(filename)
51
+ File.open('spec/files/'+filename) { |f| f.read }
52
+ end
53
+ end
data/views/index.haml CHANGED
@@ -6,7 +6,7 @@
6
6
  %p
7
7
  Drag the following link to your bookmarks. Click it to shorten the URL to the page you're currently viewing.
8
8
  %p
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]}
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')),key=enc('#{@config[:api_key]}'),f='http://#{@config[:hostname]}/api/add',l=d.location,p='?visual=1&api_key='+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
10
 
11
11
  %h1 How about shortening a URL?
12
12
 
@@ -23,6 +23,8 @@
23
23
 
24
24
  %p
25
25
  The URL you posted is invalid. Please post a valid HTTP url, including the protocol (http://) prefix.
26
+
27
+ %p= @url
26
28
 
27
29
  - if @highlight
28
30
  %h2 Your short URL
@@ -48,10 +50,10 @@
48
50
 
49
51
  %table
50
52
  %tr
51
- %td.label Short URL
52
- %td.label Full URL
53
- %td.label Clicks
54
- %td.label Shortened at
53
+ %td.label(nowrap='nowrap') Short URL <a href="/?s=code&d=asc">▲</a> <a href="/?s=code&d=desc">▼</a>
54
+ %td.label(nowrap='nowrap') Full URL <a href="/?s=url&d=asc">▲</a> <a href="/?s=url&d=desc">▼</a>
55
+ %td.label(nowrap='nowrap') Clicks <a href="/?s=clicks&d=asc">▲</a> <a href="/?s=clicks&d=desc">▼</a>
56
+ %td.label(nowrap='nowrap') Shortened At <a href="/?s=created_at&d=asc">▲</a> <a href="/?s=created_at&d=desc">▼</a>
55
57
  %td.label &nbsp;
56
58
  - @urls.each do |url|
57
59
  %tr{ :class => is_highlighted?(url) ? 'highlighted' : '' }
@@ -69,6 +71,18 @@
69
71
  $('input.short_url').each(function(index) {
70
72
  $(this).mouseup(function() { $(this).select(); });
71
73
  });
74
+
75
+ if (window.location.search == "") {
76
+ var pathname = window.location.pathname + '?s=created_at&d=desc';
77
+ } else {
78
+ var pathname = window.location.pathname + window.location.search;
79
+ }
80
+
81
+ $('#main table tr td.label a').each(function(index) {
82
+ if ($(this).attr('href') == pathname) {
83
+ $(this).addClass('highlight');
84
+ }
85
+ });
72
86
  });
73
87
  - else
74
88
  %h1 Please enter your API key
@@ -81,4 +95,4 @@
81
95
  %label{ :for => 'api_key' } API Key
82
96
  %input{ :type => 'password', :name => 'api_key', :id => 'api_key' }
83
97
  %p
84
- %input{ :type => 'submit', :name => 'submit', :id => 'submit', :value => "Let me in" }
98
+ %input{ :type => 'submit', :name => 'submit', :id => 'submit', :value => "Let me in" }
data/views/layout.haml CHANGED
@@ -17,4 +17,4 @@
17
17
 
18
18
  #footer
19
19
  %p
20
- Powered by <a href="http://github.com/ariejan/firefly">Firefly</a> v#{Firefly::Version} | <a href="http://github.com/ariejan/firefly/tree/v#{Firefly::Version}">Source</a> | <a href="http://github.com/ariejan/firefly/issues">Issues</a>
20
+ Powered by <a href="http://github.com/ariejan/firefly">Firefly</a> v#{Firefly::Version} | <a href="http://github.com/ariejan/firefly/tree/v#{Firefly::Version}">Source</a> | <a href="http://github.com/ariejan/firefly/issues">Issues</a> | Export <a href="/api/export.csv">CSV</a>, <a href="/api/export.yml">YAML</a> or <a href="/api/export.xml">XML</a>
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firefly
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 4
10
- version: 0.4.4
9
+ - 5
10
+ version: 0.4.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ariejan de Vroom
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-20 00:00:00 +02:00
18
+ date: 2010-08-10 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -190,10 +190,14 @@ files:
190
190
  - public/jquery-1.4.2.min.js
191
191
  - public/reset.css
192
192
  - public/style.css
193
+ - spec/files/export.csv
194
+ - spec/files/export.xml
195
+ - spec/files/export.yml
193
196
  - spec/firefly/api_spec.rb
194
197
  - spec/firefly/base62_spec.rb
195
198
  - spec/firefly/server_spec.rb
196
199
  - spec/firefly/url_spec.rb
200
+ - spec/fixtures/urls.yml
197
201
  - spec/spec.opts
198
202
  - spec/spec_helper.rb
199
203
  - views/index.haml