etna 0.1.11 → 0.1.16

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b95bd70a5a778deaaeb05408e95dd043dfd41914993986d770cf8c29f6ba54b
4
- data.tar.gz: 9e2d68c4fcb362108ce3ebf203cf7e88fba5045e6b87164c13f7feb4a9811e17
3
+ metadata.gz: 345d1790412dd359129f41586677ac2ac1a53c55897768d5d695e45ac828dcb6
4
+ data.tar.gz: ad2a6121a6659fa1352268b1545b434a9a7777531e6a8bdea8a74d6d779f54f8
5
5
  SHA512:
6
- metadata.gz: dfae596fc44742b0fd087cabd93ca8a2132f8501662cab18a2c46e86d33db9a84852ff061299bf5e694f70da1a62ed1b0057e6523799cec19b031fec3cd171ae
7
- data.tar.gz: 2146b65d50ea6c9b610a762c3043748113a79c3f603fe0366f3519c81314f36983d0ea59d542e4a512ea01f8e0862a3e1f403884b755d077e75de53c5dae72a9
6
+ metadata.gz: 3994385c74ff519195d80cae05e87dc5bd57755efd55ef20e26fdcb5b658c0ae45da22c62a7a87e77805e8d74eed7a7fe44440f4c5cd766dcf8d4b1f9ed46598
7
+ data.tar.gz: e0b39e27d1b5d2aab03aeb5d7118ca6c7b908e94778fe37aee9f83deee1fac3191a399bca95ca5c147f10261092974d2d40fd22ee44862e50506f763d6c982d8
@@ -5,6 +5,7 @@
5
5
 
6
6
  require_relative './sign_service'
7
7
  require 'singleton'
8
+ require 'rollbar'
8
9
 
9
10
  module Etna::Application
10
11
  def self.included(other)
@@ -31,6 +32,12 @@ module Etna::Application
31
32
 
32
33
  def configure(opts)
33
34
  @config = opts
35
+
36
+ if (rollbar_config = config(:rollbar)) && rollbar_config[:access_token]
37
+ Rollbar.configure do |config|
38
+ config.access_token = rollbar_config[:access_token]
39
+ end
40
+ end
34
41
  end
35
42
 
36
43
  def setup_logger
@@ -40,10 +47,9 @@ module Etna::Application
40
47
  # Number of old copies of the log to keep.
41
48
  config(:log_copies) || 5,
42
49
  # How large the log can get before overturning.
43
- config(:log_size) || 1048576
50
+ config(:log_size) || 1048576,
44
51
  )
45
52
  log_level = (config(:log_level) || 'warn').upcase.to_sym
46
-
47
53
  @logger.level = Logger.const_defined?(log_level) ? Logger.const_get(log_level) : Logger::WARN
48
54
  end
49
55
 
@@ -93,7 +93,7 @@ module Etna
93
93
  return false unless headers = etna_param(request, :headers)
94
94
 
95
95
  headers = headers.split(/,/).map do |header|
96
- [ header.to_sym, etna_param(request, header) ]
96
+ [ header.to_sym, params(request)[header.to_sym] || etna_param(request, header) ]
97
97
  end.to_h
98
98
 
99
99
  # Now expect the standard headers
@@ -4,13 +4,15 @@ require 'singleton'
4
4
 
5
5
  module Etna
6
6
  class Client
7
- def initialize(host, token)
7
+ def initialize(host, token, routes_available: true)
8
8
  @host = host.sub(%r!/$!,'')
9
9
  @token = token
10
10
 
11
- set_routes
12
11
 
13
- define_route_helpers
12
+ if routes_available
13
+ set_routes
14
+ define_route_helpers
15
+ end
14
16
  end
15
17
 
16
18
  attr_reader :routes
@@ -19,6 +21,29 @@ module Etna
19
21
  Etna::Route.path(route[:route], params)
20
22
  end
21
23
 
24
+ def multipart_post(endpoint, content, &block)
25
+ uri = request_uri(endpoint)
26
+ multipart = Net::HTTP::Post::Multipart.new uri.path, content
27
+ multipart.add_field('Authorization', "Etna #{@token}")
28
+ request(uri, multipart, &block)
29
+ end
30
+
31
+ def post(endpoint, params={}, &block)
32
+ body_request(Net::HTTP::Post, endpoint, params, &block)
33
+ end
34
+
35
+ def get(endpoint, params={}, &block)
36
+ query_request(Net::HTTP::Get, endpoint, params, &block)
37
+ end
38
+
39
+ def options(endpoint, params={}, &block)
40
+ query_request(Net::HTTP::Options, endpoint, params, &block)
41
+ end
42
+
43
+ def delete(endpoint, params={}, &block)
44
+ body_request(Net::HTTP::Delete, endpoint, params, &block)
45
+ end
46
+
22
47
  private
23
48
 
24
49
  def set_routes
@@ -60,32 +85,10 @@ module Etna
60
85
  end
61
86
  end
62
87
 
63
- def multipart_post(endpoint, content, &block)
64
- uri = request_uri(endpoint)
65
- multipart = Net::HTTP::Post::Multipart.new uri.path, content
66
- multipart.add_field('Authorization', "Etna #{@token}")
67
- request(uri, multipart, &block)
68
- end
69
-
70
- def post(endpoint, params={}, &block)
71
- body_request(Net::HTTP::Post, endpoint, params, &block)
72
- end
73
-
74
- def get(endpoint, params={}, &block)
75
- query_request(Net::HTTP::Get, endpoint, params, &block)
76
- end
77
-
78
- def options(endpoint, params={}, &block)
79
- query_request(Net::HTTP::Options, endpoint, params, &block)
80
- end
81
-
82
- def delete(endpoint, params={}, &block)
83
- body_request(Net::HTTP::Delete, endpoint, params, &block)
84
- end
85
88
 
86
89
  def body_request(type, endpoint, params={}, &block)
87
90
  uri = request_uri(endpoint)
88
- req = type.new(uri.path,request_params)
91
+ req = type.new(uri.request_uri,request_params)
89
92
  req.body = params.to_json
90
93
  request(uri, req, &block)
91
94
  end
@@ -122,9 +125,9 @@ module Etna
122
125
  def json_error(body)
123
126
  msg = JSON.parse(body, symbolize_names: true)
124
127
  if (msg.has_key?(:errors) && msg[:errors].is_a?(Array))
125
- return msg[:errors].join(', ')
128
+ return JSON.generate(msg[:errors])
126
129
  elsif msg.has_key?(:error)
127
- return msg[:error]
130
+ return JSON.generate(msg[:error])
128
131
  end
129
132
  end
130
133
 
@@ -0,0 +1,2 @@
1
+ require_relative './magma/client'
2
+ require_relative './magma/models'
@@ -0,0 +1,65 @@
1
+ require 'net/http/persistent'
2
+ require 'net/http/post/multipart'
3
+ require 'singleton'
4
+ require_relative '../../client'
5
+ require_relative './models'
6
+
7
+ module Etna
8
+ module Clients
9
+ class Magma
10
+ def initialize(host:, token:)
11
+ raise 'Magma client configuration is missing host.' unless host
12
+ raise 'Magma client configuration is missing token.' unless token
13
+ @etna_client = ::Etna::Client.new(host, token, routes_available: false)
14
+ end
15
+
16
+ # This endpoint returns models and records by name:
17
+ # e.g. params:
18
+ # {
19
+ # model_name: "model_one", # or "all"
20
+ # record_names: [ "rn1", "rn2" ], # or "all",
21
+ # attribute_names: "all"
22
+ # }
23
+ def retrieve(retrieval_request)
24
+ json = nil
25
+ @etna_client.post('/retrieve', retrieval_request) do |res|
26
+ json = JSON.parse(res.body)
27
+ end
28
+
29
+ RetrievalResponse.new(json)
30
+ end
31
+
32
+ # This 'query' end point is used to fetch data by graph query
33
+ # See question.rb for more detail
34
+ def query(query_request)
35
+ json = nil
36
+ @etna_client.post('/query', query_request) do |res|
37
+ json = JSON.parse(res.body)
38
+ end
39
+
40
+ QueryResponse.new(json)
41
+ end
42
+
43
+ # Post revisions to Magma records
44
+ # { model_name: { record_name: { attribute1: 1, attribute2: 2 } } } }
45
+ # data can also be a File or IO stream
46
+ def update(update_request)
47
+ json = nil
48
+ @etna_client.multipart_post('/update', update_request.encode_multipart_content) do |res|
49
+ json = JSON.parse(res.body)
50
+ end
51
+
52
+ UpdateResponse.new(json)
53
+ end
54
+
55
+ def update_model(update_model_request)
56
+ json = nil
57
+ @etna_client.post('/update_model', update_model_request) do |res|
58
+ json = JSON.parse(res.body)
59
+ end
60
+
61
+ UpdateModelResponse.new(json)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,265 @@
1
+ require_relative '../../json_serializable_struct'
2
+ require_relative '../../multipart_serializable_nested_hash'
3
+ require_relative '../../directed_graph'
4
+
5
+ # TODO: In the near future, I'd like to transition to specifying apis via SWAGGER and generating model stubs from the
6
+ # common definitions. For nowe I've written them out by hand here.
7
+ module Etna
8
+ module Clients
9
+ class Magma
10
+ class RetrievalRequest < Struct.new(:model_name, :attribute_names, :record_names, :project_name, keyword_init: true)
11
+ include JsonSerializableStruct
12
+
13
+ def initialize(**params)
14
+ super({model_name: 'all', attribute_names: 'all', record_names: []}.update(params))
15
+ end
16
+ end
17
+
18
+ class QueryRequest < Struct.new(:query, :project_name, keyword_init: true)
19
+ include JsonSerializableStruct
20
+ end
21
+
22
+ class UpdateRequest < Struct.new(:revisions, :project_name, keyword_init: true)
23
+ include JsonSerializableStruct
24
+ include MultipartSerializableNestedHash
25
+
26
+ def initialize(**params)
27
+ super({revisions: {}}.update(params))
28
+ end
29
+
30
+ def update_revision(model_name, record_name, **attrs)
31
+ revision = revisions[model_name] ||= {}
32
+ record = revision[record_name] ||= {}
33
+ record.update(attrs)
34
+ end
35
+ end
36
+
37
+ class UpdateModelRequest < Struct.new(:project_name, :actions, keyword_init: true)
38
+ include JsonSerializableStruct
39
+
40
+ def initialize(**params)
41
+ super({actions: []}.update(params))
42
+ end
43
+
44
+ def add_action(action)
45
+ actions << action
46
+ end
47
+ end
48
+
49
+ class AddAttributeAction < Struct.new(:action_name, :model_name, :attribute_name, :type, :description, :display_name, :format_hint, :hidden, :index, :link_model_name, :read_only, :restricted, :unique, :validation, keyword_init: true)
50
+ include JsonSerializableStruct
51
+ def initialize(**args)
52
+ super({action_name: 'add_attribute'}.update(args))
53
+ end
54
+ end
55
+
56
+ class AttributeValidation < Struct.new(:type, :value, :begin, :end, keyword_init: true)
57
+ include JsonSerializableStruct
58
+ end
59
+
60
+ class AttributeValidationType < String
61
+ REGEXP = AttributeValidationType.new("Regexp")
62
+ ARRAY = AttributeValidationType.new("Array")
63
+ RANGE = AttributeValidationType.new("Range")
64
+ end
65
+
66
+ class RetrievalResponse
67
+ attr_reader :raw
68
+
69
+ def initialize(raw = {})
70
+ @raw = raw
71
+ end
72
+
73
+ def models
74
+ Models.new(raw['models'])
75
+ end
76
+ end
77
+
78
+ class UpdateModelResponse < RetrievalResponse
79
+ end
80
+
81
+ class QueryResponse
82
+ attr_reader :raw
83
+
84
+ def initialize(raw = {})
85
+ @raw = raw
86
+ end
87
+
88
+ def answer
89
+ raw['answer']
90
+ end
91
+
92
+ def format
93
+ raw['format']
94
+ end
95
+
96
+ def type
97
+ raw['type']
98
+ end
99
+ end
100
+
101
+ class UpdateResponse < RetrievalResponse
102
+ end
103
+
104
+ class Models
105
+ attr_reader :raw
106
+
107
+ def initialize(raw = {})
108
+ @raw = raw
109
+ end
110
+
111
+ def model_keys
112
+ raw.keys
113
+ end
114
+
115
+ def model(model_key)
116
+ Model.new(raw[model_key])
117
+ end
118
+
119
+ def to_directed_graph(include_casual_links=false)
120
+ graph = ::DirectedGraph.new
121
+
122
+ model_keys.each do |model_name|
123
+ graph.add_connection(model(model_name).template.parent, model_name)
124
+
125
+ if include_casual_links
126
+ attributes = model(model_name).template.attributes
127
+ attributes.attribute_keys.each do |attribute_name|
128
+ attribute = attributes.attribute(attribute_name)
129
+
130
+ linked_model_name = attribute.link_model_name
131
+ if linked_model_name
132
+ if attribute.attribute_type == AttributeType::PARENT
133
+ graph.add_connection(linked_model_name, model_name)
134
+ elsif attribute.attribute_type == AttributeType::COLLECTION
135
+ graph.add_connection(model_name, linked_model_name)
136
+ elsif attribute.attribute_type == AttributeType::CHILD
137
+ graph.add_connection(model_name, linked_model_name)
138
+ elsif attribute.attribute_type == AttributeType::LINK
139
+ graph.add_connection(model_name, linked_model_name)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ graph
147
+ end
148
+ end
149
+
150
+ class Model
151
+ attr_reader :raw
152
+
153
+ def initialize(raw = {})
154
+ @raw = raw
155
+ end
156
+
157
+ def documents
158
+ Documents.new(raw['documents'])
159
+ end
160
+
161
+ def template
162
+ Template.new(raw['template'])
163
+ end
164
+ end
165
+
166
+ class Documents
167
+ attr_reader :raw
168
+
169
+ def initialize(raw = {})
170
+ @raw = raw
171
+ end
172
+
173
+ def document_keys
174
+ raw.keys
175
+ end
176
+
177
+ def document(document_key)
178
+ raw[document_key]
179
+ end
180
+ end
181
+
182
+ class Template
183
+ attr_reader :raw
184
+
185
+ def initialize(raw = {})
186
+ @raw = raw
187
+ end
188
+
189
+ def name
190
+ raw['name'] || ""
191
+ end
192
+
193
+ def identifier
194
+ raw['identifier'] || ""
195
+ end
196
+
197
+ def parent
198
+ raw['parent']
199
+ end
200
+
201
+ def attributes
202
+ Attributes.new(raw['attributes'])
203
+ end
204
+ end
205
+
206
+ class Attributes
207
+ attr_reader :raw
208
+
209
+ def initialize(raw = {})
210
+ @raw = raw
211
+ end
212
+
213
+ def attribute_keys
214
+ raw.keys
215
+ end
216
+
217
+ def attribute(attribute_key)
218
+ Attribute.new(raw[attribute_key])
219
+ end
220
+ end
221
+
222
+ class Attribute
223
+ attr_reader :raw
224
+
225
+ def initialize(raw = {})
226
+ @raw = raw
227
+ end
228
+
229
+ def name
230
+ @raw['name'] || ""
231
+ end
232
+
233
+ def attribute_name
234
+ @raw['attribute_name'] || ""
235
+ end
236
+
237
+ def attribute_type
238
+ @raw['attribute_type'] && AttributeType.new(@raw['attribute_type'])
239
+ end
240
+
241
+ def link_model_name
242
+ raw['link_model_name']
243
+ end
244
+ end
245
+
246
+ class AttributeType < String
247
+ STRING = AttributeType.new("string")
248
+ DATE_TIME = AttributeType.new("date_time")
249
+ BOOLEAN = AttributeType.new("boolean")
250
+ CHILD = AttributeType.new("child")
251
+ COLLECTION = AttributeType.new("collection")
252
+ FILE = AttributeType.new("file")
253
+ FLOAT = AttributeType.new("float")
254
+ IDENTIFIER = AttributeType.new("identifier")
255
+ IMAGE = AttributeType.new("image")
256
+ INTEGER = AttributeType.new("integer")
257
+ LINK = AttributeType.new("link")
258
+ MATCH = AttributeType.new("match")
259
+ MATRIX = AttributeType.new("matrix")
260
+ PARENT = AttributeType.new("parent")
261
+ TABLE = AttributeType.new("table")
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,2 @@
1
+ require_relative './metis/client'
2
+ require_relative './metis/models'
@@ -0,0 +1,37 @@
1
+ require 'net/http/persistent'
2
+ require 'net/http/post/multipart'
3
+ require 'singleton'
4
+ require_relative '../../client'
5
+ require_relative './models'
6
+ require 'pry'
7
+ module Etna
8
+ module Clients
9
+ class Metis
10
+ def initialize(host:, token:)
11
+ raise 'Metis client configuration is missing host.' unless host
12
+ raise 'Metis client configuration is missing token.' unless token
13
+ @etna_client = ::Etna::Client.new(host, token)
14
+ end
15
+
16
+ def list_all_folders(list_all_folders_request)
17
+ FoldersResponse.new(
18
+ @etna_client.folder_list_all_folders(list_all_folders_request.to_h))
19
+ end
20
+
21
+ def list_folder(list_folder_request)
22
+ FoldersAndFilesResponse.new(
23
+ @etna_client.folder_list(list_folder_request.to_h))
24
+ end
25
+
26
+ def rename_folder(rename_folder_request)
27
+ FoldersResponse.new(
28
+ @etna_client.folder_rename(rename_folder_request.to_h))
29
+ end
30
+
31
+ def create_folder(create_folder_request)
32
+ FoldersResponse.new(
33
+ @etna_client.folder_create(create_folder_request.to_h))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,142 @@
1
+ require_relative '../../json_serializable_struct'
2
+
3
+
4
+ module Etna
5
+ module Clients
6
+ class Metis
7
+ class ListFoldersRequest < Struct.new(:project_name, :bucket_name, :offset, :limit, keyword_init: true)
8
+ include JsonSerializableStruct
9
+
10
+ def initialize(**params)
11
+ super({}.update(params))
12
+ end
13
+
14
+ def to_h
15
+ # The :project_name comes in from Polyphemus as a symbol value,
16
+ # we need to make sure it's a string because it's going
17
+ # in the URL.
18
+ super().compact.transform_values(&:to_s)
19
+ end
20
+ end
21
+
22
+ class RenameFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, :new_bucket_name, :new_folder_path, keyword_init: true)
23
+ include JsonSerializableStruct
24
+
25
+ def initialize(**params)
26
+ super({}.update(params))
27
+ end
28
+
29
+ def to_h
30
+ # The :project_name comes in from Polyphemus as a symbol value,
31
+ # we need to make sure it's a string because it's going
32
+ # in the URL.
33
+ super().compact.transform_values(&:to_s)
34
+ end
35
+ end
36
+
37
+ class ListFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
38
+ include JsonSerializableStruct
39
+
40
+ def initialize(**params)
41
+ super({}.update(params))
42
+ end
43
+
44
+ def to_h
45
+ # The :project_name comes in from Polyphemus as a symbol value,
46
+ # we need to make sure it's a string because it's going
47
+ # in the URL.
48
+ super().compact.transform_values(&:to_s)
49
+ end
50
+ end
51
+
52
+ class CreateFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
53
+ include JsonSerializableStruct
54
+
55
+ def initialize(**params)
56
+ super({}.update(params))
57
+ end
58
+
59
+ def to_h
60
+ # The :project_name comes in from Polyphemus as a symbol value,
61
+ # we need to make sure it's a string because it's going
62
+ # in the URL.
63
+ super().compact.transform_values(&:to_s)
64
+ end
65
+ end
66
+
67
+ class FoldersResponse
68
+ attr_reader :raw
69
+
70
+ def initialize(raw = {})
71
+ @raw = raw
72
+ end
73
+
74
+ def folders
75
+ Folders.new(raw[:folders])
76
+ end
77
+ end
78
+
79
+ class FoldersAndFilesResponse < FoldersResponse
80
+ def files
81
+ Files.new(raw[:files])
82
+ end
83
+ end
84
+
85
+ class Files
86
+ attr_reader :raw
87
+
88
+ def initialize(raw = {})
89
+ @raw = raw
90
+ end
91
+
92
+ def all
93
+ raw.map { |file| File.new(file) }
94
+ end
95
+ end
96
+
97
+ class Folders
98
+ attr_reader :raw
99
+
100
+ def initialize(raw = {})
101
+ @raw = raw
102
+ end
103
+
104
+ def all
105
+ raw.map { |folder| Folder.new(folder) }
106
+ end
107
+ end
108
+
109
+ class File
110
+ attr_reader :raw
111
+
112
+ def initialize(raw = {})
113
+ @raw = raw
114
+ end
115
+
116
+ def file_path
117
+ raw[:file_path]
118
+ end
119
+
120
+ def file_name
121
+ raw[:file_name]
122
+ end
123
+ end
124
+
125
+ class Folder
126
+ attr_reader :raw
127
+
128
+ def initialize(raw = {})
129
+ @raw = raw
130
+ end
131
+
132
+ def folder_path
133
+ raw[:folder_path]
134
+ end
135
+
136
+ def bucket_name
137
+ raw[:bucket_name]
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,3 +1,5 @@
1
+ require 'rollbar'
2
+
1
3
  module Etna
2
4
  class Command
3
5
  class << self
@@ -15,12 +17,18 @@ module Etna
15
17
  # To be overridden during inheritance.
16
18
  def execute
17
19
  raise 'Command is not implemented'
20
+ rescue => e
21
+ Rollbar.error(e)
22
+ raise
18
23
  end
19
24
 
20
25
  # To be overridden during inheritance, to e.g. connect to a database.
21
26
  # Should be called with super by inheriting method.
22
27
  def setup(config)
23
28
  Etna::Application.find(self.class).configure(config)
29
+ rescue => e
30
+ Rollbar.error(e)
31
+ raise
24
32
  end
25
33
  end
26
34
  end
@@ -0,0 +1,56 @@
1
+ class DirectedGraph
2
+ def initialize
3
+ @children = {}
4
+ @parents = {}
5
+ end
6
+
7
+ attr_reader :children
8
+ attr_reader :parents
9
+
10
+ def add_connection(parent, child)
11
+ children = @children[parent] ||= {}
12
+ child_children = @children[child] ||= {}
13
+
14
+ children[child] = child_children
15
+
16
+ parents = @parents[child] ||= {}
17
+ parent_parents = @parents[parent] ||= {}
18
+ parents[parent] = parent_parents
19
+ end
20
+
21
+ def descendants(parent)
22
+ seen = Set.new
23
+
24
+ seen.add(parent)
25
+ queue = @children[parent].keys.dup
26
+ parent_queue = @parents[parent].keys.dup
27
+
28
+ # Because this is not an acyclical graph, the definition of descendants needs to be stronger;
29
+ # here we believe that any path that would move through --any-- parent to this child would not be considered
30
+ # descendant, so we first find all those parents and mark them as 'seen' so that they are not traveled.
31
+ while next_parent = parent_queue.pop
32
+ next if seen.include?(next_parent)
33
+ seen.add(next_parent)
34
+ parent_queue.push(*@parents[next_parent].keys)
35
+ end
36
+
37
+ queue = queue.nil? ? [] : queue.dup
38
+ paths = {}
39
+
40
+ while child = queue.pop
41
+ next if seen.include? child
42
+ seen.add(child)
43
+ path = (paths[child] ||= [parent])
44
+
45
+ @children[child].keys.each do |child_child|
46
+ queue.push child_child
47
+
48
+ unless paths.include? child_child
49
+ paths[child_child] = path + [child]
50
+ end
51
+ end
52
+ end
53
+
54
+ paths
55
+ end
56
+ end
@@ -60,6 +60,7 @@ module Etna
60
60
  end
61
61
 
62
62
  def valid_id?
63
+ return false if !@application.config(:hmac_keys)
63
64
  @application.config(:hmac_keys).key?(@id)
64
65
  end
65
66
 
@@ -78,7 +79,7 @@ module Etna
78
79
  # these are set as headers or params
79
80
  @nonce,
80
81
  @id,
81
- @headers.map{|l| l.join('=')}.join(';'),
82
+ @headers.to_json,
82
83
  @expiration,
83
84
  ].join("\n")
84
85
  end
@@ -0,0 +1,34 @@
1
+ module Etna
2
+ module JsonSerializableStruct
3
+ def self.included(cls)
4
+ cls.instance_eval do
5
+ def self.as_json(v)
6
+ if v.respond_to? :as_json
7
+ return v.as_json
8
+ end
9
+
10
+ if v.is_a? Hash
11
+ return v.map { |k, v| [k, as_json(v)] }.to_h
12
+ end
13
+
14
+ if v.class.include? Enumerable
15
+ return v.map { |v| as_json(v) }
16
+ end
17
+
18
+ v
19
+ end
20
+ end
21
+ end
22
+
23
+ def as_json
24
+ members.map do |k|
25
+ v = self.class.as_json(send(k))
26
+ [k, v]
27
+ end.to_h.delete_if { |k, v| v.nil? }
28
+ end
29
+
30
+ def to_json
31
+ as_json.to_json
32
+ end
33
+ end
34
+ end
@@ -1,10 +1,10 @@
1
1
  require 'logger'
2
+ require 'rollbar'
2
3
 
3
4
  module Etna
4
5
  class Logger < ::Logger
5
6
  def initialize(log_dev, age, size)
6
7
  super
7
-
8
8
  self.formatter = proc do |severity, datetime, progname, msg|
9
9
  format(severity, datetime, progname, msg)
10
10
  end
@@ -14,11 +14,28 @@ module Etna
14
14
  "#{severity}:#{datetime.iso8601} #{msg}\n"
15
15
  end
16
16
 
17
+ def warn(msg, &block)
18
+ super
19
+ Rollbar.warn(msg)
20
+ end
21
+
22
+ def error(msg, &block)
23
+ super
24
+ Rollbar.error(msg)
25
+ end
26
+
27
+ def fatal(msg, &block)
28
+ super
29
+ Rollbar.error(msg)
30
+ end
31
+
17
32
  def log_error(e)
18
33
  error(e.message)
19
34
  e.backtrace.each do |trace|
20
35
  error(trace)
21
36
  end
37
+
38
+ Rollbar.error(e)
22
39
  end
23
40
 
24
41
  def log_request(request)
@@ -0,0 +1,45 @@
1
+ module Etna
2
+ module MultipartSerializableNestedHash
3
+ def self.included(cls)
4
+ cls.instance_eval do
5
+ def self.encode_multipart_pairs(value, base_key, is_root, &block)
6
+ if value.is_a? Hash
7
+ value.each do |k, v|
8
+ encode_multipart_pairs(v, is_root ? k : "#{base_key}[#{k}]", false, &block)
9
+ end
10
+ elsif value.is_a? Array
11
+ value.each_with_index do |v, i|
12
+ # This is necessary to ensure that arrays of hashes that have hetergenous keys still get parsed correctly
13
+ # Since the only way to indicate a new entry in the array of hashes is by re-using a key that existed in
14
+ # the previous hash.
15
+ if v.is_a? Hash
16
+ encode_multipart_pairs(i, "#{base_key}[][_idx]", false, &block)
17
+ end
18
+
19
+ encode_multipart_pairs(v, "#{base_key}[]", false, &block)
20
+ end
21
+ else
22
+ raise "base_key cannot be empty for a scalar value!" if base_key.length == 0
23
+ yield [base_key, value.respond_to?(:read) ? UploadIO.new(value, 'application/octet-stream') : value.to_s]
24
+ end
25
+ end
26
+
27
+
28
+ def self.encode_multipart_content(value, base_key = '', is_root = true)
29
+ result = []
30
+ self.encode_multipart_pairs(value, base_key, is_root) { |pair| result << pair }
31
+ result
32
+ end
33
+ end
34
+ end
35
+
36
+ def encode_multipart_content(base_key = '')
37
+ value = self
38
+ if value.respond_to? :as_json
39
+ value = value.as_json
40
+ end
41
+
42
+ self.class.encode_multipart_content(value, base_key)
43
+ end
44
+ end
45
+ end
@@ -23,7 +23,7 @@ module Etna
23
23
  )
24
24
  when %r{multipart/form-data}i
25
25
  params.update(
26
- Rack::Multipart.parse_multipart(env)
26
+ Rack::Multipart.parse_multipart(env) || {}
27
27
  )
28
28
  end
29
29
  # Always parse the params that are url-encoded.
@@ -66,6 +66,9 @@ module Etna
66
66
  end
67
67
 
68
68
  [404, {}, ["There is no such path '#{request.path}'"]]
69
+ rescue => e
70
+ application.logger.log_error(e)
71
+ raise
69
72
  end
70
73
 
71
74
  def initialize
@@ -74,7 +74,7 @@ module Etna
74
74
  end
75
75
  class TestHmac < Hmac
76
76
  def valid?
77
- @test_signature == 'valid'
77
+ @test_signature == 'valid' || super
78
78
  end
79
79
  end
80
80
  end
@@ -7,7 +7,9 @@ module Etna
7
7
  }
8
8
 
9
9
  def initialize params, token=nil
10
- @first, @last, @email, @encoded_permissions = params.values_at(:first, :last, :email, :perm)
10
+ @first, @last, @email, @encoded_permissions, encoded_flags = params.values_at(:first, :last, :email, :perm, :flags)
11
+
12
+ @flags = encoded_flags&.split(/;/) || []
11
13
  @token = token unless !token
12
14
  raise ArgumentError, "No email given!" unless @email
13
15
  end
@@ -30,6 +32,10 @@ module Etna
30
32
  end.inject([],:+).to_h
31
33
  end
32
34
 
35
+ def has_flag?(flag)
36
+ @flags.include?(flag)
37
+ end
38
+
33
39
  def name
34
40
  "#{first} #{last}"
35
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etna
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saurabh Asthana
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-23 00:00:00.000000000 Z
11
+ date: 2020-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rollbar
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: See summary
70
84
  email: Saurabh.Asthana@ucsf.edu
71
85
  executables: []
@@ -76,14 +90,23 @@ files:
76
90
  - lib/etna/application.rb
77
91
  - lib/etna/auth.rb
78
92
  - lib/etna/client.rb
93
+ - lib/etna/clients/magma.rb
94
+ - lib/etna/clients/magma/client.rb
95
+ - lib/etna/clients/magma/models.rb
96
+ - lib/etna/clients/metis.rb
97
+ - lib/etna/clients/metis/client.rb
98
+ - lib/etna/clients/metis/models.rb
79
99
  - lib/etna/command.rb
80
100
  - lib/etna/controller.rb
81
101
  - lib/etna/cross_origin.rb
82
102
  - lib/etna/describe_routes.rb
103
+ - lib/etna/directed_graph.rb
83
104
  - lib/etna/errors.rb
84
105
  - lib/etna/ext.rb
85
106
  - lib/etna/hmac.rb
107
+ - lib/etna/json_serializable_struct.rb
86
108
  - lib/etna/logger.rb
109
+ - lib/etna/multipart_serializable_nested_hash.rb
87
110
  - lib/etna/parse_body.rb
88
111
  - lib/etna/route.rb
89
112
  - lib/etna/server.rb