vines-services 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/LICENSE +19 -0
  2. data/README +40 -0
  3. data/Rakefile +130 -0
  4. data/bin/vines-services +95 -0
  5. data/conf/config.rb +25 -0
  6. data/lib/vines/services/command/init.rb +209 -0
  7. data/lib/vines/services/command/restart.rb +14 -0
  8. data/lib/vines/services/command/start.rb +30 -0
  9. data/lib/vines/services/command/stop.rb +20 -0
  10. data/lib/vines/services/command/views.rb +26 -0
  11. data/lib/vines/services/component.rb +26 -0
  12. data/lib/vines/services/config.rb +105 -0
  13. data/lib/vines/services/connection.rb +120 -0
  14. data/lib/vines/services/controller/attributes_controller.rb +19 -0
  15. data/lib/vines/services/controller/base_controller.rb +99 -0
  16. data/lib/vines/services/controller/disco_info_controller.rb +61 -0
  17. data/lib/vines/services/controller/labels_controller.rb +17 -0
  18. data/lib/vines/services/controller/members_controller.rb +44 -0
  19. data/lib/vines/services/controller/messages_controller.rb +66 -0
  20. data/lib/vines/services/controller/probes_controller.rb +45 -0
  21. data/lib/vines/services/controller/services_controller.rb +81 -0
  22. data/lib/vines/services/controller/subscriptions_controller.rb +39 -0
  23. data/lib/vines/services/controller/systems_controller.rb +45 -0
  24. data/lib/vines/services/controller/transfers_controller.rb +58 -0
  25. data/lib/vines/services/controller/uploads_controller.rb +62 -0
  26. data/lib/vines/services/controller/users_controller.rb +127 -0
  27. data/lib/vines/services/core_ext/blather.rb +46 -0
  28. data/lib/vines/services/core_ext/couchrest.rb +33 -0
  29. data/lib/vines/services/indexer.rb +195 -0
  30. data/lib/vines/services/priority_queue.rb +94 -0
  31. data/lib/vines/services/roster.rb +70 -0
  32. data/lib/vines/services/storage/couchdb/fragment.rb +23 -0
  33. data/lib/vines/services/storage/couchdb/service.rb +170 -0
  34. data/lib/vines/services/storage/couchdb/system.rb +141 -0
  35. data/lib/vines/services/storage/couchdb/upload.rb +66 -0
  36. data/lib/vines/services/storage/couchdb/user.rb +137 -0
  37. data/lib/vines/services/storage/couchdb/vcard.rb +13 -0
  38. data/lib/vines/services/storage/couchdb.rb +157 -0
  39. data/lib/vines/services/storage.rb +33 -0
  40. data/lib/vines/services/throttle.rb +26 -0
  41. data/lib/vines/services/version.rb +7 -0
  42. data/lib/vines/services/vql/compiler.rb +94 -0
  43. data/lib/vines/services/vql/vql.citrus +115 -0
  44. data/lib/vines/services/vql/vql.rb +186 -0
  45. data/lib/vines/services.rb +71 -0
  46. data/test/config_test.rb +242 -0
  47. data/test/priority_queue_test.rb +23 -0
  48. data/test/storage/couchdb_test.rb +30 -0
  49. data/test/vql/compiler_test.rb +96 -0
  50. data/test/vql/vql_test.rb +233 -0
  51. data/web/coffeescripts/api.coffee +51 -0
  52. data/web/coffeescripts/commands.coffee +18 -0
  53. data/web/coffeescripts/files.coffee +315 -0
  54. data/web/coffeescripts/init.coffee +21 -0
  55. data/web/coffeescripts/services.coffee +356 -0
  56. data/web/coffeescripts/setup.coffee +503 -0
  57. data/web/coffeescripts/systems.coffee +371 -0
  58. data/web/images/default-service.png +0 -0
  59. data/web/images/linux.png +0 -0
  60. data/web/images/mac.png +0 -0
  61. data/web/images/run.png +0 -0
  62. data/web/images/windows.png +0 -0
  63. data/web/index.html +17 -0
  64. data/web/stylesheets/common.css +52 -0
  65. data/web/stylesheets/files.css +218 -0
  66. data/web/stylesheets/services.css +181 -0
  67. data/web/stylesheets/setup.css +117 -0
  68. data/web/stylesheets/systems.css +142 -0
  69. metadata +230 -0
@@ -0,0 +1,157 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ class Storage
6
+ class CouchDB < Storage
7
+ register :couchdb
8
+
9
+ module ClassMethods
10
+ # Override CouchRest::Model::Persistence::ClassMethods#build_from_database
11
+ # to instantiate the fully qualified model class name. We store the
12
+ # bare class name in doc['type'] (e.g. Service rather than
13
+ # Vines::Services::CouchModels::Service), so outside query processes
14
+ # don't need to know our class hierarchy.
15
+ def build_from_database(doc = {}, options = {}, &block)
16
+ src = doc[model_type_key]
17
+ base = (src.blank? || src == self.to_s) ? self : "Vines::Services::CouchModels::#{src}".constantize
18
+ base.new(doc, options.merge(:directly_set_attributes => true), &block)
19
+ end
20
+
21
+ # CouchRest::Model uses Class#to_s to determine design document names
22
+ # as well as the value of doc['type']. Strip off all module names to
23
+ # get clean design document URLs.
24
+ def to_s
25
+ self.name.split('::').last
26
+ end
27
+ end
28
+
29
+ %w[host port database tls username password index_dir].each do |name|
30
+ define_method(name) do |*args|
31
+ if args.first
32
+ @config[name.to_sym] = args.first
33
+ else
34
+ @config[name.to_sym]
35
+ end
36
+ end
37
+ end
38
+
39
+ def initialize(&block)
40
+ @config = {}
41
+ instance_eval(&block)
42
+ [:host, :port, :database, :index_dir].each do |key|
43
+ raise "Must provide #{key}" unless @config[key]
44
+ end
45
+
46
+ @config[:index_dir] = File.expand_path(@config[:index_dir])
47
+ unless File.directory?(@config[:index_dir]) && File.writable?(@config[:index_dir])
48
+ raise 'Must provide a writable index directory'
49
+ end
50
+
51
+ @url = url(@config)
52
+ init_couch_rest
53
+
54
+ db = "%s-%s-%s.db" % [host, port, database]
55
+ @index = Indexer[File.join(@config[:index_dir], db)]
56
+ end
57
+
58
+ def get(path, &callback)
59
+ http(path, :get, &callback)
60
+ end
61
+
62
+ def post(path, body, &callback)
63
+ http(path, :post, body, &callback)
64
+ end
65
+
66
+ def delete(path, &callback)
67
+ http(path, :get) do |doc|
68
+ if doc
69
+ http("#{path}?rev=#{doc['_rev']}", :delete, &callback)
70
+ else
71
+ yield
72
+ end
73
+ end
74
+ end
75
+
76
+ def save(doc, &callback)
77
+ http('', :post, doc.to_json, &callback)
78
+ end
79
+
80
+ def index(system)
81
+ @index << system.ohai
82
+ end
83
+
84
+ def query(sql, *params, &callback)
85
+ @index.find(sql, params, &callback)
86
+ end
87
+
88
+ def store_file(path)
89
+ file = CouchModels::Upload.find_by_name(File.basename(path))
90
+ unless file
91
+ File.delete(path)
92
+ return
93
+ end
94
+
95
+ http = EM::HttpRequest.new("#{@url}/#{file.id}/data?rev=#{file.rev}").put(
96
+ head: {'Content-Type' => 'application/octet-stream'},
97
+ file: path
98
+ )
99
+ http.callback {|chunk| yield }
100
+ end
101
+
102
+ def create_views
103
+ # FIXME Use views in CouchRest::Model classes to populate db
104
+ designs = {}
105
+
106
+ EM.run do
107
+ http('', :put) do # create db
108
+ designs.each do |name, views|
109
+ get("/_design/#{name}") do |doc|
110
+ doc ||= {"_id" => "_design/#{name}"}
111
+ doc['language'] = 'javascript'
112
+ doc['views'] = views
113
+ save(doc) { EM.stop }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def http(path, method, body=nil)
123
+ args = {}.tap do |opts|
124
+ opts[:head] = {'Content-Type' => 'application/json'}
125
+ opts[:body] = body if body
126
+ end
127
+
128
+ http = EM::HttpRequest.new("#{@url}#{path}").send(method, args)
129
+ http.errback { yield }
130
+ http.callback do
131
+ doc = if (200...300).include?(http.response_header.status)
132
+ JSON.parse(http.response) rescue nil
133
+ end
134
+ yield doc
135
+ end
136
+ end
137
+
138
+ def url(config)
139
+ scheme = config[:tls] ? 'https' : 'http'
140
+ user, password = config.values_at(:username, :password)
141
+ credentials = empty?(user, password) ? '' : "%s:%s@" % [user, password]
142
+ "%s://%s%s:%s/%s" % [scheme, credentials, *config.values_at(:host, :port, :database)]
143
+ end
144
+
145
+ def init_couch_rest
146
+ *url, _ = @url.split('/')
147
+ server = CouchRest::Server.new(url.join('/'))
148
+ CouchRest::Model::Base.database = server.database(database)
149
+ end
150
+
151
+ def escape(jid)
152
+ URI.escape(jid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ class Storage
6
+ include Vines::Log
7
+
8
+ @@nicks = {}
9
+
10
+ # Register a nickname that can be used in the config file to specify this
11
+ # storage implementation.
12
+ def self.register(name)
13
+ @@nicks[name.to_sym] = self
14
+ end
15
+
16
+ def self.from_name(name, &block)
17
+ klass = @@nicks[name.to_sym]
18
+ raise "#{name} storage class not found" unless klass
19
+ klass.new(&block)
20
+ end
21
+
22
+ private
23
+
24
+ # Return true if any of the arguments are nil or empty strings.
25
+ # For example:
26
+ # username, password = 'alice@wonderland.lit', ''
27
+ # empty?(username, password) #=> true
28
+ def empty?(*args)
29
+ args.flatten.any? {|arg| (arg || '').strip.empty? }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ # Send many outgoing stanzas to the server at a sustained rate that won't
6
+ # cause the server to shutdown the component's stream with a policy_violation
7
+ # error.
8
+ class Throttle
9
+ def initialize(stream, delay=0.1)
10
+ @stream, @delay = stream, delay
11
+ end
12
+
13
+ # Send the nodes to the server at a constant rate. The nodes are sent
14
+ # asynchronously, so this method returns immediately.
15
+ def async_send(nodes)
16
+ timer = EM::PeriodicTimer.new(@delay) do
17
+ if node = nodes.shift
18
+ @stream.write(node)
19
+ else
20
+ timer.cancel
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ module VQL
6
+ # Compiles Vines Query Language queries into JavaScript for CouchDB views
7
+ # and SQL for sqlite queries. The output of the compiler is a fully formed
8
+ # query that can be sent directly to the database.
9
+ class Compiler
10
+ Citrus.load(File.expand_path('../vql.citrus', __FILE__))
11
+
12
+ # Return the compiled CouchDB map function. Raise an exception on
13
+ # compilation failure. The exception's error message can be shown to
14
+ # the user to debug the syntax.
15
+ def to_js(code)
16
+ raise ArgumentError, 'code required' if code.nil? || code.strip.empty?
17
+ expr = VinesQL.parse(code.strip)
18
+ %Q{
19
+ function(doc) {
20
+ if (doc.type != 'System') return;
21
+ try {
22
+ var match = #{expr.js};
23
+ if (match) {
24
+ var name = doc['_id'].replace('system:', '');
25
+ var os = doc.ohai.kernel.os.toLowerCase().replace('gnu/', '');
26
+ emit(name, os);
27
+ }
28
+ } catch(e) {
29
+ log(e.message);
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ # Return the compiled CouchDB map function for all services. Raise an
36
+ # exception on compilation failure. The exception's error message can
37
+ # be shown to the user to debug the syntax.
38
+ def to_full_js(services)
39
+ maps = services.map do |service|
40
+ expr = VinesQL.parse(service.code.strip)
41
+ %Q{
42
+ try {
43
+ var match = #{expr.js};
44
+ if (match) {
45
+ emit([0, '#{service.id}'], {name: name, os: os});
46
+ emit([1, name], '#{service.id}');
47
+ }
48
+ } catch(e) {
49
+ log(e.message);
50
+ }
51
+ }
52
+ end
53
+
54
+ %Q{
55
+ function(doc) {
56
+ if (doc.type != 'System') return;
57
+ var name = doc['_id'].replace('system:', '');
58
+ var os = doc.ohai.kernel.os.toLowerCase().replace('gnu/', '');
59
+ #{maps.join}
60
+ }
61
+ }
62
+ end
63
+
64
+ # Return the compiled sqlite SQL query along with an array of parameter
65
+ # replacement values. Raise an exception on compilation failure. The
66
+ # exception's error message can be shown to the user to debug the syntax.
67
+ def to_sql(code)
68
+ raise ArgumentError, 'code required' if code.nil? || code.strip.empty?
69
+ expr = VinesQL.parse(code.strip)
70
+
71
+ keys, values = expr.params.partition.each_with_index {|p, ix| ix % 2 == 0 }
72
+ joins = keys.each_with_index.map do |k, ix|
73
+ "inner join attributes a%s on id=a%s.system_id and a%s.key=?" % [ix, ix, ix]
74
+ end
75
+
76
+ where = expr.sql.tap do |sql|
77
+ values.size.times do |ix|
78
+ sql.sub!(/(^|[^\.])value/, "\\1a#{ix}.value")
79
+ end
80
+ end
81
+
82
+ sql = %Q{
83
+ select name, os from systems
84
+ #{joins.join("\n")}
85
+ where #{where}
86
+ order by name
87
+ }
88
+
89
+ [sql, [keys, values].flatten]
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,115 @@
1
+ grammar VinesQL
2
+ rule expr
3
+ expr:(disjunctive | stmt)
4
+ end
5
+
6
+ rule disjunctive
7
+ (lhs:stmt 'or' ws rhs:expr) <Vines::Services::VQL::Or>
8
+ end
9
+
10
+ rule stmt
11
+ stmt:(conjunctive | term)
12
+ end
13
+
14
+ rule conjunctive
15
+ (lhs:term 'and' ws rhs:stmt) <Vines::Services::VQL::And>
16
+ end
17
+
18
+ rule term
19
+ is_not | is | ltgt | starts_with | ends_with | not_like | like | group
20
+ end
21
+
22
+ rule is
23
+ (lhs:member 'is' ws rhs:value) <Vines::Services::VQL::Is>
24
+ end
25
+
26
+ rule is_not
27
+ (lhs:member 'is' ws 'not' ws rhs:value) <Vines::Services::VQL::IsNot>
28
+ end
29
+
30
+ rule like
31
+ (lhs:member 'like' ws rhs:string) <Vines::Services::VQL::Like>
32
+ end
33
+
34
+ rule not_like
35
+ (lhs:member 'not' ws 'like' ws rhs:string) <Vines::Services::VQL::NotLike>
36
+ end
37
+
38
+ rule starts_with
39
+ (lhs:member 'starts' ws 'with' ws rhs:string) <Vines::Services::VQL::StartsWith>
40
+ end
41
+
42
+ rule ends_with
43
+ (lhs:member 'ends' ws 'with' ws rhs:string) <Vines::Services::VQL::EndsWith>
44
+ end
45
+
46
+ rule ltgt
47
+ (lhs:member op:([<>] '='?) ws rhs:number) <Vines::Services::VQL::LtGt>
48
+ end
49
+
50
+ rule value
51
+ string | number | keyword
52
+ end
53
+
54
+ rule group
55
+ (lparen expr rparen) <Vines::Services::VQL::Group>
56
+ end
57
+
58
+ rule member
59
+ (str:(ident ('.' ident)*) ws) <Vines::Services::VQL::Member>
60
+ end
61
+
62
+ rule ident
63
+ (alpha | '_') (alpha | '_' | digits)*
64
+ end
65
+
66
+ rule string
67
+ single_quoted | double_quoted
68
+ end
69
+
70
+ rule single_quoted
71
+ ("'" str:(~"'") "'" ws) <Vines::Services::VQL::SingleQuoted>
72
+ end
73
+
74
+ rule double_quoted
75
+ ('"' str:(~'"') '"' ws) <Vines::Services::VQL::DoubleQuoted>
76
+ end
77
+
78
+ rule keyword
79
+ (str:'null' ws) <Vines::Services::VQL::Null>
80
+ | (str:'true' ws) <Vines::Services::VQL::Terminal>
81
+ | (str:'false' ws) <Vines::Services::VQL::Terminal>
82
+ end
83
+
84
+ rule number
85
+ float | int
86
+ end
87
+
88
+ rule float
89
+ (str:('-'? digits '.' digits) ws) <Vines::Services::VQL::Terminal>
90
+ end
91
+
92
+ rule int
93
+ (str:('-'? digits) ws) <Vines::Services::VQL::Terminal>
94
+ end
95
+
96
+ rule digits
97
+ [0-9]+
98
+ end
99
+
100
+ rule alpha
101
+ [a-zA-Z]+
102
+ end
103
+
104
+ rule lparen
105
+ '(' ws
106
+ end
107
+
108
+ rule rparen
109
+ ')' ws
110
+ end
111
+
112
+ rule ws
113
+ [ \t\n\r]*
114
+ end
115
+ end
@@ -0,0 +1,186 @@
1
+ module Vines
2
+ module Services
3
+ # These modules give semantic meaning to the Vines Query Language Citrus
4
+ # parser. The query language is translated into JavaScript and SQL fragments
5
+ # for use in CouchDB views or sqlite queries. This file must be loaded before
6
+ # vql.citrus.
7
+ module VQL
8
+ module Or
9
+ def js
10
+ "%s || %s" % [lhs.js, rhs.js]
11
+ end
12
+ def sql
13
+ "%s or %s" % [lhs.sql, rhs.sql]
14
+ end
15
+ def params
16
+ [lhs.params, rhs.params].flatten
17
+ end
18
+ end
19
+
20
+ module And
21
+ def js
22
+ "%s && %s" % [lhs.js, rhs.js]
23
+ end
24
+ def sql
25
+ "%s and %s" % [lhs.sql, rhs.sql]
26
+ end
27
+ def params
28
+ [lhs.params, rhs.params].flatten
29
+ end
30
+ end
31
+
32
+ module Is
33
+ def js
34
+ "(%s === %s)" % [lhs.js, rhs.js]
35
+ end
36
+ def sql
37
+ case rhs
38
+ when Vines::Services::VQL::Null
39
+ "value is null"
40
+ else
41
+ "value=?"
42
+ end
43
+ end
44
+ def params
45
+ case rhs
46
+ when Vines::Services::VQL::Null
47
+ [lhs.sql]
48
+ else
49
+ [lhs.sql, rhs.sql]
50
+ end
51
+ end
52
+ end
53
+
54
+ module IsNot
55
+ def js
56
+ "(%s !== %s)" % [lhs.js, rhs.js]
57
+ end
58
+ def sql
59
+ case rhs
60
+ when Vines::Services::VQL::Null
61
+ "value is not null"
62
+ else
63
+ "value <> ?"
64
+ end
65
+ end
66
+ def params
67
+ case rhs
68
+ when Vines::Services::VQL::Null
69
+ [lhs.sql]
70
+ else
71
+ [lhs.sql, rhs.sql]
72
+ end
73
+ end
74
+ end
75
+
76
+ module Like
77
+ def js
78
+ "(%s.indexOf(%s) !== -1)" % [lhs.js, rhs.js]
79
+ end
80
+ def sql
81
+ "value like ?"
82
+ end
83
+ def params
84
+ [lhs.sql, "%#{rhs.sql}%"]
85
+ end
86
+ end
87
+
88
+ module NotLike
89
+ def js
90
+ "(%s.indexOf(%s) === -1)" % [lhs.js, rhs.js]
91
+ end
92
+ def sql
93
+ "value not like ?"
94
+ end
95
+ def params
96
+ [lhs.sql, "%#{rhs.sql}%"]
97
+ end
98
+ end
99
+
100
+ module StartsWith
101
+ def js
102
+ "(%s.lastIndexOf(%s, 0) === 0)" % [lhs.js, rhs.js]
103
+ end
104
+ def sql
105
+ "value like ?"
106
+ end
107
+ def params
108
+ [lhs.sql, "#{rhs.sql}%"]
109
+ end
110
+ end
111
+
112
+ module EndsWith
113
+ def js
114
+ "(%s.match(%s + '$'))" % [lhs.js, rhs.js]
115
+ end
116
+ def sql
117
+ "value like ?"
118
+ end
119
+ def params
120
+ [lhs.sql, "%#{rhs.sql}"]
121
+ end
122
+ end
123
+
124
+ module LtGt
125
+ def js
126
+ "(%s %s %s)" % [lhs.js, op, rhs.js]
127
+ end
128
+ def sql
129
+ "cast(value as number) %s ?" % op
130
+ end
131
+ def params
132
+ [lhs.sql, rhs.sql]
133
+ end
134
+ end
135
+
136
+ module Group
137
+ def js
138
+ "(%s)" % expr.js
139
+ end
140
+ def sql
141
+ "(%s)" % expr.sql
142
+ end
143
+ def params
144
+ expr.params
145
+ end
146
+ end
147
+
148
+ module Terminal
149
+ def js
150
+ str.to_s
151
+ end
152
+ def sql
153
+ str.to_s
154
+ end
155
+ def params
156
+ []
157
+ end
158
+ end
159
+
160
+ module Member
161
+ include Terminal
162
+ def js
163
+ "doc.ohai.#{str}"
164
+ end
165
+ end
166
+
167
+ module SingleQuoted
168
+ include Terminal
169
+ def js
170
+ "'%s'" % str
171
+ end
172
+ end
173
+
174
+ module DoubleQuoted
175
+ include Terminal
176
+ def js
177
+ '"%s"' % str
178
+ end
179
+ end
180
+
181
+ module Null
182
+ include Terminal
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: UTF-8
2
+
3
+ %w[
4
+ couchrest_model
5
+ logger
6
+ bcrypt
7
+ blather/client/client
8
+ cgi
9
+ citrus
10
+ em-http
11
+ fiber
12
+ fileutils
13
+ json
14
+ nokogiri
15
+ sqlite3
16
+ uri
17
+
18
+ vines/log
19
+ vines/daemon
20
+ vines/jid
21
+ vines/kit
22
+
23
+ vines/services/core_ext/blather
24
+ vines/services/core_ext/couchrest
25
+
26
+ vines/services/version
27
+ vines/services/connection
28
+ vines/services/config
29
+ vines/services/component
30
+ vines/services/priority_queue
31
+ vines/services/indexer
32
+ vines/services/throttle
33
+
34
+ vines/services/vql/vql
35
+ vines/services/vql/compiler
36
+
37
+ vines/services/command/init
38
+ vines/services/command/restart
39
+ vines/services/command/start
40
+ vines/services/command/stop
41
+ vines/services/command/views
42
+
43
+ vines/services/storage
44
+ vines/services/storage/couchdb
45
+ vines/services/storage/couchdb/fragment
46
+ vines/services/storage/couchdb/service
47
+ vines/services/storage/couchdb/system
48
+ vines/services/storage/couchdb/upload
49
+ vines/services/storage/couchdb/user
50
+ vines/services/storage/couchdb/vcard
51
+
52
+ vines/services/controller/base_controller
53
+ vines/services/controller/attributes_controller
54
+ vines/services/controller/disco_info_controller
55
+ vines/services/controller/uploads_controller
56
+ vines/services/controller/labels_controller
57
+ vines/services/controller/members_controller
58
+ vines/services/controller/messages_controller
59
+ vines/services/controller/probes_controller
60
+ vines/services/controller/services_controller
61
+ vines/services/controller/subscriptions_controller
62
+ vines/services/controller/systems_controller
63
+ vines/services/controller/transfers_controller
64
+ vines/services/controller/users_controller
65
+ ].each {|f| require f }
66
+
67
+ module Vines
68
+ module Services
69
+ class Forbidden < StandardError; end
70
+ end
71
+ end