useless 0.0.6
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/.gitignore +3 -0
- data/DOC.html +28 -0
- data/Gemfile +2 -0
- data/README.md +23 -0
- data/Rakefile +2 -0
- data/assets/application.css +134 -0
- data/lib/useless/rack/base/doc.rb +12 -0
- data/lib/useless/rack/base/files.rb +31 -0
- data/lib/useless/rack/base.rb +20 -0
- data/lib/useless/rack/doc.rb +35 -0
- data/lib/useless/rack/fs.rb +25 -0
- data/lib/useless/rack/logger.rb +50 -0
- data/lib/useless/rack/middleware/assets.rb +38 -0
- data/lib/useless/rack/middleware/authentication/access_token.rb +41 -0
- data/lib/useless/rack/middleware/authentication/query_string.rb +19 -0
- data/lib/useless/rack/middleware/authentication/request_header.rb +19 -0
- data/lib/useless/rack/middleware/doc.rb +17 -0
- data/lib/useless/rack/middleware/exceptions.rb +41 -0
- data/lib/useless/rack/middleware/fs.rb +17 -0
- data/lib/useless/rack/middleware/mongo.rb +18 -0
- data/lib/useless/rack/mongo.rb +17 -0
- data/lib/useless/rack.rb +69 -0
- data/lib/useless/version.rb +3 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/assets/muffin-milk.jpg +0 -0
- data/spec/useless/rack/base/files_spec.rb +41 -0
- data/spec/useless/rack/fs_spec.rb +22 -0
- data/spec/useless/rack/logger_spec.rb +86 -0
- data/spec/useless/rack/middleware/authentication/query_string_spec.rb +45 -0
- data/spec/useless/rack/middleware/authentication/request_header_spec.rb +46 -0
- data/spec/useless/rack/middleware/exceptions_spec.rb +46 -0
- data/spec/useless/rack/mongo_spec.rb +62 -0
- data/useless.gemspec +27 -0
- metadata +199 -0
data/.gitignore
ADDED
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
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,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,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
|
data/lib/useless/rack.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|