Class: Api::EndpointHandler

Inherits:
Core::Service show all
Defined in:
app/middleware/api/endpoint_handler.rb

Overview

Sinatra application which provides routing for the V1 API Automatically generates routes from the files listed under `app/api/endpoints` used in RootService which in turn gets mounted in `config/routes.rb`

Constant Summary collapse

ACTIONS_TO_HTTP_VERBS =
{
  create: :post,
  read: :get,
  update: :put,
  delete: :delete
}

Constants inherited from Core::Service

Core::Service::API_VERSION

Instance Attribute Summary

Attributes inherited from Core::Service

#command

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Core::Service

after_all_actions, #api_path, api_version_path, before_all_actions, #handle_not_found!, #redirect_to, #redirect_to_multiple_locations

Class Method Details

.file_action(action, http_method) ⇒ Object


89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'app/middleware/api/endpoint_handler.rb', line 89

def file_action(action, http_method)
  send(http_method, %r{/([\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})(?:/([^/]+(?:/[^/]+)*))?}, file_requested: true) do
    report('file') do
      uuid_in_url, parts = params[:captures][0], params[:captures][1].try(:split, '/') || []
      uuid = Uuid.find_by(external_id: uuid_in_url) or raise ActiveRecord::RecordNotFound, 'UUID does not exist'

      file_through = return_file(request, action, parts) do |request|
        request.io     = lookup_for_class(uuid.resource.class) { |e| raise e }
        request.target = request.io.eager_loading_for(uuid.resource.class).include_uuid.find(uuid.resource_id)
      end
      uuid.resource.__send__(file_through) { |file| send_file file.path, filename: file.filename }
    end
  end
end

.file_addition(action, http_method) ⇒ Object


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'app/middleware/api/endpoint_handler.rb', line 25

def file_addition(action, http_method)
  send(http_method, %r{/([\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})(?:/([^/]+(?:/[^/]+)*))?}, file_attatched: true) do
    raise Core::Service::ContentFiltering::InvalidRequestedContentTypeOnFile if request.acceptable_media_types.prioritize(registered_mimetypes).present?

    report('file') do
      filename = /filename="([^"]*)"/.match(request.env['HTTP_CONTENT_DISPOSITION']).try(:[], 1) || 'unnamed_file'
      begin
        file = Tempfile.new(filename)
        file.binmode
        file.unlink
        file.write(request.body.read)
        # Be kind...
        file.rewind
        request.body.rewind
        uuid_in_url, parts = params[:captures][0], params[:captures][1].try(:split, '/') || []
        uuid = Uuid.find_by(external_id: uuid_in_url) or raise ActiveRecord::RecordNotFound, 'UUID does not exist'
        handle_request(:instance, request, action, parts) do |request|
          request.io     = lookup_for_class(uuid.resource.class) { |e| raise e }
          request.target = request.io.eager_loading_for(uuid.resource.class).include_uuid.find(uuid.resource_id)
          request.file = file
          request.filename = filename
        end
      ensure
        file.close!
      end
    end
  end
end

.file_attatched(bool) ⇒ Object


19
20
21
22
23
# File 'app/middleware/api/endpoint_handler.rb', line 19

def file_attatched(bool)
  condition do
    registered_mimetypes.include?(request.content_type) == bool
  end
end

.file_model_action(_action, http_method) ⇒ Object


81
82
83
84
85
86
87
# File 'app/middleware/api/endpoint_handler.rb', line 81

def file_model_action(_action, http_method)
  send(http_method, %r{/([^\d/][^/]+(?:/[^/]+){0,2})}, file_requested: true) do
    report('model') do
      raise Core::Service::ContentFiltering::InvalidRequestedContentType
    end
  end
end

.file_model_addition(action, http_method) ⇒ Object


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'app/middleware/api/endpoint_handler.rb', line 54

def file_model_addition(action, http_method)
  send(http_method, %r{/([^\d/][^/]+(?:/[^/]+){0,2})}, file_attatched: true) do
    raise Core::Service::ContentFiltering::InvalidRequestedContentType if request.acceptable_media_types.prioritize(registered_mimetypes).present?

    report('model') do
      filename = /filename="([^"]*)"/.match(request.env['HTTP_CONTENT_DISPOSITION']).try(:[], 1) || 'unnamed_file'
      begin
        file = Tempfile.new(filename)
        file.write(request.body.read)
        # Be kind...
        file.rewind
        request.body.rewind
        determine_model_from_parts(*params[:captures].join.split('/')) do |model, parts|
          handle_request(:model, request, action, parts) do |request|
            request.io     = lookup_for_class(model) { |_| nil }
            request.target = model
            request.file = file
            request.filename = filename
          end
        end
      ensure
        file.close!
      end
    end
  end
end

.file_requested(bool) ⇒ Object

We can't use the built in provides, as the accepted mimetimes are fixed when the route is set up.


13
14
15
16
17
# File 'app/middleware/api/endpoint_handler.rb', line 13

def file_requested(bool)
  condition do
    request.acceptable_media_types.prioritize(registered_mimetypes).present? == bool
  end
end

.instance_action(action, http_method) ⇒ Object


104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/middleware/api/endpoint_handler.rb', line 104

def instance_action(action, http_method)
  send(http_method, %r{/([\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})(?:/([^/]+(?:/[^/]+)*))?}, file_attatched: false, file_requested: false) do
    report('instance') do
      uuid_in_url, parts = params[:captures][0], params[:captures][1].try(:split, '/') || []
      uuid = Uuid.find_by(external_id: uuid_in_url) or raise ActiveRecord::RecordNotFound, 'UUID does not exist'
      handle_request(:instance, request, action, parts) do |request|
        request.io     = lookup_for_class(uuid.resource.class) { |e| raise e }
        request.target = request.io.eager_loading_for(uuid.resource.class).include_uuid.find(uuid.resource_id)
      end
    end
  end
end

.model_action(action, http_method) ⇒ Object


117
118
119
120
121
122
123
124
125
126
127
128
# File 'app/middleware/api/endpoint_handler.rb', line 117

def model_action(action, http_method)
  send(http_method, %r{/([^\d/][^/]+(?:/[^/]+){0,2})}, file_attatched: false, file_requested: false) do
    report('model') do
      determine_model_from_parts(*params[:captures].join.split('/')) do |model, parts|
        handle_request(:model, request, action, parts) do |request|
          request.io     = lookup_for_class(model) { |_| nil }
          request.target = model
        end
      end
    end
  end
end

.register_mimetype(mimetype) ⇒ Object


130
131
132
133
# File 'app/middleware/api/endpoint_handler.rb', line 130

def register_mimetype(mimetype)
  @registered_mimetypes ||= []
  @registered_mimetypes.push(mimetype).uniq!
end

.registered_mimetypesObject


8
9
10
# File 'app/middleware/api/endpoint_handler.rb', line 8

def registered_mimetypes
  @registered_mimetypes || []
end

Instance Method Details

#handle_request(handler, http_request, action, parts) ⇒ Object


176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'app/middleware/api/endpoint_handler.rb', line 176

def handle_request(handler, http_request, action, parts)
  endpoint_lookup, io_lookup =
    case handler
    when :instance then [:endpoint_for_object, :lookup_for_object]
    when :model    then [:endpoint_for_class,  :lookup_for_class]
    else raise StandardError, "Unexpected handler #{handler.inspect}"
    end

  request =
    ::Core::Service::Request.new(requested_url = http_request.fullpath) do |request|
      request.service = self
      request.path    = parts
      request.json    = @json
      Rails.logger.info("API[payload]: #{@json}")
      yield(request)
    end

  endpoint = send(endpoint_lookup, request.target)
  Rails.logger.info("API[endpoint]: #{handler}: #{requested_url} handled by #{endpoint.inspect}")
  body(request.send(handler, action, endpoint))
end

#registered_mimetypesObject


136
137
138
# File 'app/middleware/api/endpoint_handler.rb', line 136

def registered_mimetypes
  self.class.registered_mimetypes
end

#return_file(http_request, action, parts) ⇒ Object


198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/middleware/api/endpoint_handler.rb', line 198

def return_file(http_request, action, parts)
  request =
    ::Core::Service::Request.new(requested_url = http_request.fullpath) do |request|
      request.service = self
      request.path    = parts
      request.json    = @json
      yield(request)
    end

  endpoint = endpoint_for_object(request.target)
  file_through = request.instance(action, endpoint).handled_by.file_through(request_accepted)
  raise Core::Service::ContentFiltering::InvalidRequestedContentType if file_through.nil?

  Rails.logger.info("API[endpoint]: File: #{requested_url} handled by #{endpoint.inspect}")
  file_through
end