useless 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ log/*.log
3
+ pkg/*
data/DOC.html ADDED
@@ -0,0 +1,28 @@
1
+ <h1 class="platform-title"><span class="the-name-of-the-website">useless.io</span> the useless platform</h1>
2
+
3
+ <p class="platform-description">a collection of APIs that are more or less useless.</p>
4
+
5
+ <h2>Platform Guidelines</h2>
6
+
7
+ <h3>Authentication</h3>
8
+
9
+ <p>Some endpoints require authentication. In order to do so, the client must
10
+ specify an <em>access token</em>. This can be done by using either the
11
+ <code>Authorization</code> request header field or the
12
+ <code>access_token</code> query string parameter. For example:</p>
13
+
14
+ <pre>
15
+ GET /photos/1 HTTP/1.1
16
+ Host: street-art.useless.io
17
+ Authorization: 7Fjfp0ZBr1KtDRbnfVdmIw
18
+ </pre>
19
+
20
+ <p>If an endpoint requires authentication and an access token is not specified
21
+ using one of these methods, a <code>401 Unauthorized</code> response will be returned. If
22
+ an invalid access token is ever specified, regardless of whether the endpoint
23
+ requires authentication, a <code>401 Unauthorized</code> response will also be returned.</p>
24
+
25
+ <h3>Dates</h3>
26
+
27
+ <p>All dates are UTC and will conform to the ISO 8601 format. If a request expects
28
+ date or time data, it should formatted in the same way. For example: <code>2011-11-13T21:23:47+00:00</code></p>
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ useless
2
+ =======
3
+
4
+ A lightweight Rack stack that forms the platform for useless.io.
5
+
6
+ useless's primary responsibility is to dispatch requests to the APIs
7
+ based upon subdomain. It also provides a minimal common interface that
8
+ includes:
9
+
10
+ 1. _authentication_ - if a request can be authenticated by a supported
11
+ strategy, the middleware will set the 'useless.user' environment key to the
12
+ associated user record.
13
+
14
+ 2. _persistence_ - the platform provides access to a Mongo driver instance
15
+ via the `Useless::Mongo` class. An instance is provided in the env via
16
+ 'useless.mongo'.
17
+
18
+ 3. _file storage_ - all files should be stored using the `Useless::FS` instance.
19
+ provided in the env by 'useless.fs'. Any file stored using `Useless::FS` is
20
+ assigned an ID and is served from the '/files/[ID]' endpoint.
21
+
22
+ 4. _logging_ - the platform sets up a logger appropriate for the environment.
23
+ It can be accessed using 'useless.logger' environment key.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
@@ -0,0 +1,134 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
4
+ font-size: 16px;
5
+ color: #333;
6
+ line-height: 21px;
7
+ font-weight: 200;
8
+ background-color: #EEE;
9
+ }
10
+
11
+ #container {
12
+ margin: 0 auto;
13
+ width: 760px;
14
+ padding: 3em 2em;
15
+ border-left: 1px solid #DDD;
16
+ border-right: 1px solid #DDD;
17
+ background-color: #FFF;
18
+ }
19
+
20
+ h1, h2, h3, h4, h5, h6 {
21
+ font-weight: normal;
22
+ }
23
+
24
+ h1 {
25
+ margin: 0 0 .25em;
26
+ }
27
+
28
+ h1.platform-title {
29
+ color: #999;
30
+ }
31
+
32
+ h1.platform-title span.the-name-of-the-website {
33
+ color: #333;
34
+ }
35
+
36
+ h2 {
37
+ border-bottom: 1px solid #DDD;
38
+ padding-bottom: 0.25em;
39
+ }
40
+
41
+ h3.endpoint, h3.endpoint pre {
42
+ margin-bottom: 0.25em;
43
+ }
44
+
45
+ h3 {
46
+ font-size: 1.25em;
47
+ margin-top: 4em;
48
+ }
49
+
50
+ h3:first-of-type {
51
+ margin-top: inherit;
52
+ }
53
+
54
+ h4 {
55
+ font-weight: bold;
56
+ margin-bottom: 0.25em;
57
+ }
58
+
59
+ p.api-description, p.platform-description {
60
+ margin-bottom: 3em;
61
+ }
62
+
63
+ p.platform-description {
64
+ margin-top: 0;
65
+ color: #AAA;
66
+ }
67
+
68
+ p.endpoint-description {
69
+ margin-top: 0;
70
+ }
71
+
72
+ code.base-api-url {
73
+ color: #777777;
74
+ display: block;
75
+ }
76
+
77
+ ul.attributes {
78
+ overflow: hidden;
79
+ *zoom: 1;
80
+ padding: 0;
81
+ margin: 0;
82
+ list-style: none;
83
+ margin-bottom: 2em;
84
+ }
85
+
86
+ ul.attributes li {
87
+ float: left;
88
+ padding: 3px 10px;
89
+ border: 1px solid #DDD;
90
+ background-color: #EEE;
91
+ font-size: 0.8em;
92
+ }
93
+
94
+ ul.attributes li.called-out {
95
+ border-color: #AAA;
96
+ background-color: lightYellow;
97
+ }
98
+
99
+ table {
100
+ width: 100%;
101
+ border: 1px solid #DDD;
102
+ border-spacing: 0;
103
+ }
104
+
105
+ table th {
106
+ font-weight: inherit;
107
+ }
108
+
109
+ table td, th {
110
+ border-right: 1px solid #DDD;
111
+ border-bottom: 1px solid #DDD;
112
+ padding: 5px;
113
+ }
114
+
115
+ table th {
116
+ text-align: left;
117
+ color: #999;
118
+ }
119
+
120
+ table td:last-child, table th:last-child {
121
+ border-right-width: 0;
122
+ }
123
+
124
+ table tbody tr:last-child td, table tbody tr:last-child th {
125
+ border-bottom-width: 0;
126
+ }
127
+
128
+ pre.example {
129
+ margin-top: 0;
130
+ padding: 1em;
131
+ border: 1px solid #DDD;
132
+ background-color: #EEE;
133
+ overflow: auto;
134
+ }
@@ -0,0 +1,12 @@
1
+ module Useless
2
+ class Rack
3
+ class Base
4
+ # Useless::Base::Doc just calls .serve on `useless.doc`
5
+ module Doc
6
+ def self.call(env)
7
+ env['useless.doc'].serve File.expand_path('../../../../DOC.html', __FILE__)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module Useless
2
+ class Rack
3
+ class Base
4
+ # Useless::Base::Files simply retrieves files from Useless::FS and serves
5
+ # them up.
6
+ class Files
7
+ # Provide a helper method for file URLs to keep things consistent.
8
+ def self.url_for(id)
9
+ "http://useless.io/files/#{id}"
10
+ end
11
+
12
+ def call(env)
13
+ # The file ID is everything after the initial '/' in the path
14
+ id = env['PATH_INFO'][1..-1]
15
+
16
+ # Retrieve the file from FS
17
+ file = env['useless.fs'].get(BSON::ObjectId(id))
18
+
19
+ # and serve it up with the associated content type
20
+ return [200, {'Content-Type' => file.content_type}, file.read]
21
+
22
+ # Two things can go wrong, and they'll both raise an error:
23
+ # * the specified ID is not a valid object ID
24
+ # * there is no file corresponding to the ID in the FS
25
+ rescue BSON::InvalidObjectId, Useless::Rack::FS::FileNotFound
26
+ [404, {'Content-Type' => 'text/plain'}, "File not found: #{id}"]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ module Useless
2
+ class Rack
3
+ # Useless::Base is the Rack endpoint for useless.io proper. It has two
4
+ # responsibilties: show documentation, and serve file from 'useless.fs'.
5
+ class Base
6
+ autoload :Doc, 'useless/rack/base/doc'
7
+ autoload :Files, 'useless/rack/base/files'
8
+
9
+ def call(env)
10
+ # Show platform documentation at root
11
+ app = ::Rack::URLMap.new '/' => Doc,
12
+
13
+ # serve files out of /files/* via the Files Rack endpoint.
14
+ '/files/' => Files.new
15
+
16
+ app.call(env)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ require 'erb'
2
+
3
+ module Useless
4
+ class Rack
5
+ # `Useless::Doc` is a module that handles serving documentation
6
+ module Doc
7
+ TEMPLATE = <<-BLOCK
8
+ <!DOCTYPE html>
9
+ <html>
10
+ <head>
11
+ <title>useless.io</title>
12
+ <link href="/application.css" media="screen" rel="stylesheet" type="text/css" />
13
+ </head>
14
+ <body>
15
+ <div id="container">
16
+ <%= body %>
17
+ </div>
18
+ </body>
19
+ </html>
20
+ BLOCK
21
+
22
+ def self.serve(file_or_path)
23
+ # Read the contents of the file or the file at the specified path.
24
+ body = file_or_path.is_a?(File) ?
25
+ file_or_path.read : File.read(file_or_path)
26
+
27
+ # Render the template,
28
+ response = ERB.new(TEMPLATE).result(binding)
29
+
30
+ # and return it as a Rack response.
31
+ [200, {'Content-Type' => 'text/html'}, [response]]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module Useless
2
+ class Rack
3
+ # Useless::FS is a minimal wrapper around Mongo::Grid.
4
+ class FS
5
+ class FileNotFound < StandardError; end
6
+
7
+ def initialize(mongo)
8
+ @mongo = mongo
9
+ end
10
+
11
+ def put(file, opts = {})
12
+ @mongo.grid.put(file, opts)
13
+ end
14
+
15
+ def get(id)
16
+ @mongo.grid.get(id)
17
+
18
+ # To preserve the abstraction, raise our own exception when a file isn't
19
+ # found
20
+ rescue ::Mongo::GridFileNotFound => e
21
+ raise FileNotFound, e.message
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ require 'logger'
2
+
3
+ module Useless
4
+ class Rack
5
+ # Useless::Logger is a wrapper around Logger for the purposes of decorating.
6
+ class Logger
7
+ attr_accessor :request_id
8
+
9
+ def initialize(io = $stdout)
10
+ @logger = ::Logger.new(io)
11
+ end
12
+
13
+ def level=(level)
14
+ @logger.level = level
15
+ end
16
+
17
+ def level
18
+ @logger.level
19
+ end
20
+
21
+ def fatal(message)
22
+ add ::Logger::FATAL, message
23
+ end
24
+
25
+ def error(message)
26
+ add ::Logger::ERROR, message
27
+ end
28
+
29
+ def warn(message)
30
+ add ::Logger::WARN, message
31
+ end
32
+
33
+ def info(message)
34
+ add ::Logger::INFO, message
35
+ end
36
+
37
+ def debug(message)
38
+ add ::Logger::DEBUG, message
39
+ end
40
+
41
+ private
42
+
43
+ def add(level, message)
44
+ formatted_message = message
45
+ formatted_message = "[#{@request_id}] #{formatted_message}" if @request_id
46
+ @logger.add level, formatted_message
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ # `Assets` maps requests to files in the assets directory.
5
+ class Assets
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ # If the path couldn't be a file in assets, just proxy the request.
12
+ unless env['PATH_INFO'] =~ /\/\w+/
13
+ return @app.call(env)
14
+ end
15
+
16
+ # The path is relative to the assets directory in the gem root
17
+ path = File.expand_path("../../../../assets#{env['PATH_INFO']}", __FILE__)
18
+
19
+ # If there's a corresponding file:
20
+ if File.exists?(path) and !File.directory?(path)
21
+ # Try to infer the type from the extension, resorting to 'plain'
22
+ # if we can't.
23
+ type = path[/\.(\w+)$/, 1] || 'plain'
24
+
25
+ # Read the file,
26
+ response = File.read(path)
27
+
28
+ # and serve it up
29
+ [200, {'Content-Type' => "text/#{type}"}, [response]]
30
+ else
31
+ # Otherwise, just pass the request along
32
+ @app.call(env)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ module Authentication
5
+ # The `Authentication::AccessToken` module defines the behavior for access-
6
+ # token-based authentication middleware. The middlewares are responsible
7
+ # only for providing the access token via the `#access_token_for_env`
8
+ # method.
9
+ module AccessToken
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ # If we don't already have a user set in the environment,
16
+ unless env['useless.user']
17
+ # check to see if an access token was specified.
18
+ if access_token = access_token_for_env(env)
19
+ # If so, and a corresponding user can be found,
20
+ if user = env['useless.mongo']['users'].find_one('access_token' => access_token)
21
+ # set 'useless.user' in the environment.
22
+ env['useless.user'] = user
23
+ else
24
+ # Otherwise, return a 401 Unauthorized.
25
+ return [401, {'Content-Type' => 'text/plain'}, ["Invalid access token: #{access_token}"]]
26
+ end
27
+ end
28
+ end
29
+
30
+ @app.call(env)
31
+ end
32
+
33
+ private
34
+
35
+ def access_token_for_env(env)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ module Authentication
5
+ # The `Authentication::QueryString` middleware attempt to retrieve the
6
+ # access token from the query string.
7
+ class QueryString
8
+ include AccessToken
9
+
10
+ private
11
+
12
+ def access_token_for_env(env)
13
+ ::Rack::Utils.parse_query(env['QUERY_STRING'])['access_token']
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ module Authentication
5
+ # The `Authentication::RequestHeader` middleware attempts to retrieve the
6
+ # access token from the `Authorization` request header.
7
+ class RequestHeader
8
+ include AccessToken
9
+
10
+ private
11
+
12
+ def access_token_for_env(env)
13
+ env['HTTP_AUTHORIZATION']
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ # `Doc` simply adds Useless::Doc instance to env as useless.doc'.
5
+ class Doc
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ env['useless.doc'] = Useless::Rack::Doc
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ # `Exceptions` is a Rack middleware that handles any exceptions raised by
5
+ # it's app. It has two responsibilities:
6
+ # 1. log the exception trace, if a logger is available, and
7
+ # 2. return a 500 response with the appropriate message
8
+ class Exceptions
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ @app.call(env)
15
+ rescue => exception
16
+ # First, format the exception trace
17
+ trace = exception.message + "\n" + exception.backtrace.join("\n")
18
+
19
+ # If a logger is available,
20
+ if env['useless.logger']
21
+ # log the trace as fatal.
22
+ env['useless.logger'].fatal trace
23
+ end
24
+
25
+ # Next, if the request is authenticated by an admin or we are in development or test,
26
+ if (env['useless.user'] and env['useless.user']['admin']) or
27
+ ENV['RACK_ENV'] == 'development'
28
+ # we will return the trace;
29
+ message = trace
30
+ else
31
+ # otherwise, return a generic message.
32
+ message = 'An internal server error occurred. Please try again later.'
33
+ end
34
+
35
+ # Finally, return the 500 error.
36
+ [500, {'Content-Type' => 'text/plain'}, [message]]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ # `FS` simply adds the Useless.fs instance to env as 'useless.fs'.
5
+ class FS
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ env['useless.fs'] = Useless::Rack.fs
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ module Useless
2
+ class Rack
3
+ module Middleware
4
+ # `Useless::Mongo` simply adds the Useless.mongo instance to env as
5
+ # 'useless.mongo'.
6
+ class Mongo
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env['useless.mongo'] = Useless::Rack.mongo
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ require 'low/mongo'
2
+
3
+ module Useless
4
+ class Rack
5
+ module Mongo
6
+ # Returns an instance of the appropriate `Low::Mongo` helper class based upon the
7
+ # `RACK_ENV` environment variable (or the `env` parameter, if specified).
8
+ def self.for_env(env = nil)
9
+ case (env || ENV['RACK_ENV'])
10
+ when 'test' then Low::Mongo::Local.new 'useless_test'
11
+ when 'production' then Low::Mongo::Remote.new ENV['MONGOLAB_URI']
12
+ else Low::Mongo::Local.new 'useless_development'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'low/rack/log_level'
4
+ require 'low/rack/rack_errors'
5
+ require 'low/rack/request_id'
6
+ require 'low/rack/request_logger'
7
+ require 'low/rack/subdomain_map'
8
+ require 'low/scoped_logger'
9
+
10
+ module Useless
11
+ class Rack
12
+ autoload :Base, 'useless/rack/base'
13
+ autoload :Doc, 'useless/rack/doc'
14
+ autoload :FS, 'useless/rack/fs'
15
+ autoload :Logger, 'useless/rack/logger'
16
+ autoload :Mongo, 'useless/rack/mongo'
17
+
18
+ module Middleware
19
+ autoload :Assets, 'useless/rack/middleware/assets'
20
+ autoload :Doc, 'useless/rack/middleware/doc'
21
+ autoload :Exceptions, 'useless/rack/middleware/exceptions'
22
+ autoload :FS, 'useless/rack/middleware/fs'
23
+ autoload :Mongo, 'useless/rack/middleware/mongo'
24
+
25
+ module Authentication
26
+ autoload :AccessToken, 'useless/rack/middleware/authentication/access_token'
27
+ autoload :QueryString, 'useless/rack/middleware/authentication/query_string'
28
+ autoload :RequestHeader, 'useless/rack/middleware/authentication/request_header'
29
+ end
30
+ end
31
+
32
+ def self.mongo
33
+ @mongo ||= Mongo.for_env
34
+ end
35
+
36
+ def self.fs
37
+ @fs ||= FS.new(mongo)
38
+ end
39
+
40
+ def initialize(map = nil, &block)
41
+ @map = map || {}
42
+ instance_eval &block if block_given?
43
+ end
44
+
45
+ def map(pair)
46
+ subdomain, app = pair.first
47
+ @map[subdomain.to_s] = app.respond_to?(:new) ? app.new : app
48
+ end
49
+
50
+ def call(env)
51
+ ::Rack::Builder.app do
52
+ use Middleware::Exceptions
53
+ use Low::Rack::RequestId
54
+ use Low::Rack::RackErrors
55
+ use Low::Rack::LogLevel
56
+ use Low::Rack::RequestLogger, key: 'useless.logger'
57
+ use ::Rack::CommonLogger
58
+ use Middleware::Assets
59
+ use Middleware::Mongo
60
+ use Middleware::FS
61
+ use Middleware::Doc
62
+ use Middleware::Authentication::QueryString
63
+ use Middleware::Authentication::RequestHeader
64
+
65
+ run Low::Rack::SubdomainMap.new(Base.new, @map)
66
+ end.call(env)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Useless
2
+ VERSION = '0.0.6'
3
+ end
@@ -0,0 +1,31 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ require File.dirname(__FILE__) + '/../lib/useless/rack'
3
+
4
+ require 'rack/mock'
5
+ require 'rack/test'
6
+
7
+ RSpec.configure do |config|
8
+ config.order = :rand
9
+
10
+ def clean_database
11
+ Useless::Rack.mongo.db.collections.each do |collection|
12
+ if !(collection.name =~ /^system\./)
13
+ collection.drop
14
+ end
15
+ end
16
+ end
17
+
18
+ config.before(:suite){ clean_database }
19
+
20
+ config.after(:each) do
21
+ clean_database
22
+
23
+ # After dropping collections, we need to force a new connection on the
24
+ # next request.
25
+ Useless::Rack.mongo.reset_connection!
26
+ end
27
+
28
+ def asset_path(path)
29
+ File.dirname(__FILE__) + '/support/assets/' + path
30
+ end
31
+ end
Binary file
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+
3
+ describe 'useless.io/files' do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Useless::Rack.new
8
+ end
9
+
10
+ describe 'GET /files/:id' do
11
+ it 'should return a file if one exists for the specified ID' do
12
+ file = File.open(asset_path('muffin-milk.jpg'))
13
+ id = Useless::Rack.fs.put(file)
14
+
15
+ get Useless::Rack::Base::Files.url_for(id)
16
+ last_response.should be_ok
17
+
18
+ # See spec/useless/fs_spec.rb
19
+ file.rewind
20
+ blob = file.read
21
+ blob.force_encoding('BINARY')
22
+ blob.should == last_response.body
23
+ end
24
+
25
+ it 'should return a 404 if the file does not exits' do
26
+ get Useless::Rack::Base::Files.url_for(BSON::ObjectId.new)
27
+ last_response.should be_not_found
28
+ end
29
+
30
+ it 'should return a 404 if the specified ID is invalid' do
31
+ get Useless::Rack::Base::Files.url_for('invalid-id')
32
+ last_response.should be_not_found
33
+ end
34
+ end
35
+
36
+ describe '.url_for' do
37
+ it 'should return the appropriate URL for the specified file ID' do
38
+ Useless::Rack::Base::Files.url_for('abc123').should == 'http://useless.io/files/abc123'
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Useless::Rack::FS do
4
+ it 'should allow for files to be added and retrieved via .put and .get' do
5
+ original_file = File.open(asset_path('muffin-milk.jpg'))
6
+ original_file_id = Useless::Rack.fs.put(original_file)
7
+ stored_file = Useless::Rack.fs.get(original_file_id)
8
+
9
+ # Useless::Rack::FS stores the file as an un-encoded binary blob, so in order to
10
+ # compare, we must strip the encoding, i.e. force a binary encoding.
11
+ original_file.rewind
12
+ original_blob = original_file.read
13
+ original_blob.force_encoding('BINARY')
14
+ original_blob.should == stored_file.read
15
+ end
16
+
17
+ describe '.get' do
18
+ it 'should raise Useless::FS::FileNotFound if the specified ID doesn\'t exist' do
19
+ -> { Useless::Rack.fs.get('nonexistant') }.should raise_error(Useless::Rack::FS::FileNotFound)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Useless::Rack::Logger do
4
+ describe '#fatal' do
5
+ it 'should emit a fatal message' do
6
+ io = StringIO.new
7
+ logger = Useless::Rack::Logger.new(io)
8
+ logger.fatal 'Oh noes!'
9
+
10
+ io.rewind; message = io.read
11
+ message.should =~ /FATAL/
12
+ message.should =~ /Oh noes!$/
13
+ end
14
+ end
15
+
16
+ describe '#error' do
17
+ it 'should emit an error message' do
18
+ io = StringIO.new
19
+ logger = Useless::Rack::Logger.new(io)
20
+ logger.error 'Wrong!'
21
+
22
+ io.rewind; message = io.read
23
+ message.should =~ /ERROR/
24
+ message.should =~ /Wrong!$/
25
+ end
26
+ end
27
+
28
+ describe '#warn' do
29
+ it 'should emit a warn message' do
30
+ io = StringIO.new
31
+ logger = Useless::Rack::Logger.new(io)
32
+ logger.warn 'Watchout!'
33
+
34
+ io.rewind; message = io.read
35
+ message.should =~ /WARN/
36
+ message.should =~ /Watchout!$/
37
+ end
38
+ end
39
+
40
+ describe '#info' do
41
+ it 'should emit an info message' do
42
+ io = StringIO.new
43
+ logger = Useless::Rack::Logger.new(io)
44
+ logger.info 'Such and such'
45
+
46
+ io.rewind; message = io.read
47
+ message.should =~ /INFO/
48
+ message.should =~ /Such and such$/
49
+ end
50
+ end
51
+
52
+ describe '#debug' do
53
+ it 'should emit a debug message' do
54
+ io = StringIO.new
55
+ logger = Useless::Rack::Logger.new(io)
56
+ logger.debug '101011'
57
+
58
+ io.rewind; message = io.read
59
+ message.should =~ /DEBUG/
60
+ message.should =~ /101011$/
61
+ end
62
+
63
+ it 'should emit nothing if the level is too high' do
64
+ io = StringIO.new
65
+ logger = Useless::Rack::Logger.new(io)
66
+ logger.level = ::Logger::INFO
67
+ logger.debug '101011'
68
+
69
+ io.rewind; message = io.read
70
+ message.should == ''
71
+ end
72
+ end
73
+
74
+ describe '#request_id' do
75
+ it 'should append the specified ID to all messages' do
76
+ io = StringIO.new
77
+ logger = Useless::Rack::Logger.new(io)
78
+ logger.request_id = 'abc123'
79
+ logger.info 'Yadda'
80
+
81
+ io.rewind; message = io.read
82
+ message.should =~ /abc123/
83
+ message.should =~ /Yadda$/
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/../../../../spec_helper'
2
+
3
+ describe Useless::Rack::Middleware::Authentication::QueryString do
4
+ def authenticated_app
5
+ app = lambda do |env|
6
+ body = env['useless.user'] ?
7
+ "user authenticated: #{env['useless.user']['handle']}" :
8
+ "unauthenticated"
9
+
10
+ [200, {'Content-Type' => 'text/plain'}, [body]]
11
+ end
12
+
13
+ query_string_app = Useless::Rack::Middleware::Authentication::QueryString.new(app)
14
+ Useless::Rack::Middleware::Mongo.new(query_string_app)
15
+ end
16
+
17
+ it 'should do nothing if `useless.user` is already set' do
18
+ Useless::Rack.mongo['users'].insert({'handle' => 'dph', 'access_token' => 'def456'})
19
+ res = Rack::MockRequest.new(authenticated_app).get('/resource?access_token=def456',
20
+ 'useless.user' => {'handle' => 'khy', 'access_token' => 'abc123'})
21
+ res.should be_ok
22
+ res.body.should == 'user authenticated: khy'
23
+ end
24
+
25
+ it 'should do nothing if no access token is specified' do
26
+ res = Rack::MockRequest.new(authenticated_app).get('/resource')
27
+ res.should be_ok
28
+ res.body.should == 'unauthenticated'
29
+ end
30
+
31
+ it 'should return a 401 Unauthenticated if an access token is specified, but
32
+ does not correspond to a user' do
33
+ res = Rack::MockRequest.new(authenticated_app).get('/resource?access_token=abc123')
34
+ res.status.should == 401
35
+ res.body.should == 'Invalid access token: abc123'
36
+ end
37
+
38
+ it 'should set `useless.user` in the environment if a user can be found for
39
+ the specified access token' do
40
+ Useless::Rack.mongo['users'].insert({'handle' => 'khy', 'access_token' => 'abc123'})
41
+ res = Rack::MockRequest.new(authenticated_app).get('/resource?access_token=abc123')
42
+ res.should be_ok
43
+ res.body.should == 'user authenticated: khy'
44
+ end
45
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/../../../../spec_helper'
2
+
3
+ describe Useless::Rack::Middleware::Authentication::RequestHeader do
4
+ def authenticated_app
5
+ app = lambda do |env|
6
+ body = env['useless.user'] ?
7
+ "user authenticated: #{env['useless.user']['handle']}" :
8
+ "unauthenticated"
9
+
10
+ [200, {'Content-Type' => 'text/plain'}, [body]]
11
+ end
12
+
13
+ request_header_app = Useless::Rack::Middleware::Authentication::RequestHeader.new(app)
14
+ Useless::Rack::Middleware::Mongo.new(request_header_app)
15
+ end
16
+
17
+ it 'should do nothing if `useless.user` is already set' do
18
+ Useless::Rack.mongo['users'].insert({'handle' => 'dph', 'access_token' => 'def456'})
19
+ res = Rack::MockRequest.new(authenticated_app).get('/resource',
20
+ 'Authorization' => 'def456',
21
+ 'useless.user' => {'handle' => 'khy', 'access_token' => 'abc123'})
22
+ res.should be_ok
23
+ res.body.should == 'user authenticated: khy'
24
+ end
25
+
26
+ it 'should do nothing if no access token is specified' do
27
+ res = Rack::MockRequest.new(authenticated_app).get('/resource')
28
+ res.should be_ok
29
+ res.body.should == 'unauthenticated'
30
+ end
31
+
32
+ it 'should return a 401 Unauthenticated if an access token is specified, but
33
+ does not correspond to a user' do
34
+ res = Rack::MockRequest.new(authenticated_app).get('/resource', 'HTTP_AUTHORIZATION' => 'abc123')
35
+ res.status.should == 401
36
+ res.body.should == 'Invalid access token: abc123'
37
+ end
38
+
39
+ it 'should set `useless.user` in the environment if a user can be found for
40
+ the specified access token' do
41
+ Useless::Rack.mongo['users'].insert({'handle' => 'khy', 'access_token' => 'abc123'})
42
+ res = Rack::MockRequest.new(authenticated_app).get('/resource', 'HTTP_AUTHORIZATION' => 'abc123')
43
+ res.should be_ok
44
+ res.body.should == 'user authenticated: khy'
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+
3
+ describe Useless::Rack::Middleware::Exceptions do
4
+ it 'should proxy transparently if nothing goes wrong in the main app' do
5
+ main_app = lambda do |env|
6
+ [200, {'Content-Type' => 'text/plain'}, ['No Problems!']]
7
+ end
8
+ exceptions_app = Useless::Rack::Middleware::Exceptions.new(main_app)
9
+
10
+ res = Rack::MockRequest.new(exceptions_app).get('http://useless.info')
11
+ res.should be_ok
12
+ res.body.should == 'No Problems!'
13
+ end
14
+
15
+ def broken_app
16
+ main_app = lambda do |env|
17
+ raise 'It\'s all broken!'
18
+ [200, {'Content-Type' => 'text/plain'}, ['No Problems!']]
19
+ end
20
+
21
+ Useless::Rack::Middleware::Exceptions.new(main_app)
22
+ end
23
+
24
+ it 'should return a 500 with a generic message if an exception is raised
25
+ in the main app and the request is not authenticated' do
26
+ res = Rack::MockRequest.new(broken_app).get('http://useless.info')
27
+ res.status.should == 500
28
+ res.body.should == 'An internal server error occurred. Please try again later.'
29
+ end
30
+
31
+ it 'should return a 500 with details of the exception if one is raised in
32
+ the main app and the request is authenticated by an admin user' do
33
+ res = Rack::MockRequest.new(broken_app).get('http://useless.info',
34
+ 'useless.user' => {'handle' => 'khy', 'admin' => true})
35
+ res.status.should == 500
36
+ res.body.should =~ /^It's all broken!/
37
+ end
38
+
39
+ it 'should return a 500 with a generic message if an exception is raised
40
+ in the main app and the request is authenticated by an non-admin user' do
41
+ res = Rack::MockRequest.new(broken_app).get('http://useless.info',
42
+ 'useless.user' => {'handle' => 'khy'})
43
+ res.status.should == 500
44
+ res.body.should == 'An internal server error occurred. Please try again later.'
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Useless::Rack::Mongo do
4
+ describe '.for_env' do
5
+ it 'should return a instance for \'useless_development\' if \'development\' is specified' do
6
+ mongo = Useless::Rack::Mongo.for_env('development')
7
+ mongo.database.should == 'useless_development'
8
+ end
9
+
10
+ it 'should return a instance for \'useless_development\' if RACK_ENV is \'development\'' do
11
+ original_rack_env = ENV['RACK_ENV']
12
+ begin
13
+ ENV['RACK_ENV'] = 'development'
14
+ mongo = Useless::Rack::Mongo.for_env
15
+ mongo.database.should == 'useless_development'
16
+ ensure
17
+ ENV['RACK_ENV'] = original_rack_env
18
+ end
19
+ end
20
+
21
+ it 'should return a instance for \'useless_test\' if \'test\' is specified' do
22
+ mongo = Useless::Rack::Mongo.for_env('test')
23
+ mongo.database.should == 'useless_test'
24
+ end
25
+
26
+ it 'should return a instance for \'useless_test\' if RACK_ENV is \'test\'' do
27
+ original_rack_env = ENV['RACK_ENV']
28
+ begin
29
+ ENV['RACK_ENV'] = 'test'
30
+ mongo = Useless::Rack::Mongo.for_env
31
+ mongo.database.should == 'useless_test'
32
+ ensure
33
+ ENV['RACK_ENV'] = original_rack_env
34
+ end
35
+ end
36
+
37
+ it 'should return a instance for the MONGOLAB_URI if \'production\' is specified' do
38
+ original_mongolab_uri = ENV['MONGOLAB_URI']
39
+ begin
40
+ ENV['MONGOLAB_URI'] = 'mongodb://mongo.com'
41
+ mongo = Useless::Rack::Mongo.for_env('production')
42
+ mongo.uri.should == 'mongodb://mongo.com'
43
+ ensure
44
+ ENV['MONGOLAB_URI'] = original_mongolab_uri
45
+ end
46
+ end
47
+
48
+ it 'should return a instance for the MONGOLAB_URL if RACK_ENV is \'production\'' do
49
+ original_rack_env = ENV['RACK_ENV']
50
+ original_mongolab_uri = ENV['MONGOLAB_URI']
51
+ begin
52
+ ENV['RACK_ENV'] = 'production'
53
+ ENV['MONGOLAB_URI'] = 'mongodb://mongo.com'
54
+ mongo = Useless::Rack::Mongo.for_env
55
+ mongo.uri.should == 'mongodb://mongo.com'
56
+ ensure
57
+ ENV['RACK_ENV'] = original_rack_env
58
+ ENV['MONGOLAB_URI'] = original_mongolab_uri
59
+ end
60
+ end
61
+ end
62
+ end
data/useless.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/useless/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'useless'
6
+ gem.version = Useless::VERSION
7
+ gem.summary = 'The useless.io Ruby library.'
8
+ gem.description = 'Library for building a useless.io-compliant APIs in Ruby.'
9
+
10
+ gem.authors = ['Kevin Hyland']
11
+ gem.email = ['khy@me.com']
12
+ gem.homepage = 'https://github.com/khy/useless.rb'
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^spec/})
17
+ gem.require_paths = ['lib']
18
+
19
+ gem.add_runtime_dependency 'rack'
20
+ gem.add_runtime_dependency 'low', '0.0.14'
21
+ gem.add_runtime_dependency 'mongo'
22
+ gem.add_runtime_dependency 'bson'
23
+ gem.add_runtime_dependency 'bson_ext'
24
+
25
+ gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'rack-test'
27
+ end
metadata ADDED
@@ -0,0 +1,199 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: useless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kevin Hyland
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: low
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.0.14
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.0.14
46
+ - !ruby/object:Gem::Dependency
47
+ name: mongo
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bson
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bson_ext
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rack-test
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Library for building a useless.io-compliant APIs in Ruby.
127
+ email:
128
+ - khy@me.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - DOC.html
135
+ - Gemfile
136
+ - README.md
137
+ - Rakefile
138
+ - assets/application.css
139
+ - lib/useless/rack.rb
140
+ - lib/useless/rack/base.rb
141
+ - lib/useless/rack/base/doc.rb
142
+ - lib/useless/rack/base/files.rb
143
+ - lib/useless/rack/doc.rb
144
+ - lib/useless/rack/fs.rb
145
+ - lib/useless/rack/logger.rb
146
+ - lib/useless/rack/middleware/assets.rb
147
+ - lib/useless/rack/middleware/authentication/access_token.rb
148
+ - lib/useless/rack/middleware/authentication/query_string.rb
149
+ - lib/useless/rack/middleware/authentication/request_header.rb
150
+ - lib/useless/rack/middleware/doc.rb
151
+ - lib/useless/rack/middleware/exceptions.rb
152
+ - lib/useless/rack/middleware/fs.rb
153
+ - lib/useless/rack/middleware/mongo.rb
154
+ - lib/useless/rack/mongo.rb
155
+ - lib/useless/version.rb
156
+ - spec/spec_helper.rb
157
+ - spec/support/assets/muffin-milk.jpg
158
+ - spec/useless/rack/base/files_spec.rb
159
+ - spec/useless/rack/fs_spec.rb
160
+ - spec/useless/rack/logger_spec.rb
161
+ - spec/useless/rack/middleware/authentication/query_string_spec.rb
162
+ - spec/useless/rack/middleware/authentication/request_header_spec.rb
163
+ - spec/useless/rack/middleware/exceptions_spec.rb
164
+ - spec/useless/rack/mongo_spec.rb
165
+ - useless.gemspec
166
+ homepage: https://github.com/khy/useless.rb
167
+ licenses: []
168
+ post_install_message:
169
+ rdoc_options: []
170
+ require_paths:
171
+ - lib
172
+ required_ruby_version: !ruby/object:Gem::Requirement
173
+ none: false
174
+ requirements:
175
+ - - ! '>='
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ required_rubygems_version: !ruby/object:Gem::Requirement
179
+ none: false
180
+ requirements:
181
+ - - ! '>='
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubyforge_project:
186
+ rubygems_version: 1.8.23
187
+ signing_key:
188
+ specification_version: 3
189
+ summary: The useless.io Ruby library.
190
+ test_files:
191
+ - spec/spec_helper.rb
192
+ - spec/support/assets/muffin-milk.jpg
193
+ - spec/useless/rack/base/files_spec.rb
194
+ - spec/useless/rack/fs_spec.rb
195
+ - spec/useless/rack/logger_spec.rb
196
+ - spec/useless/rack/middleware/authentication/query_string_spec.rb
197
+ - spec/useless/rack/middleware/authentication/request_header_spec.rb
198
+ - spec/useless/rack/middleware/exceptions_spec.rb
199
+ - spec/useless/rack/mongo_spec.rb