web-connect 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/Rakefile +3 -0
  4. data/lib/netfira/web_connect/components/checksum.rb +19 -0
  5. data/lib/netfira/web_connect/components/guid.rb +11 -0
  6. data/lib/netfira/web_connect/components/octa_word.rb +73 -0
  7. data/lib/netfira/web_connect/components.rb +1 -0
  8. data/lib/netfira/web_connect/configuration.rb +33 -0
  9. data/lib/netfira/web_connect/db_schema/20140514_support.rb +38 -0
  10. data/lib/netfira/web_connect/db_schema/20140515_alpha.rb +106 -0
  11. data/lib/netfira/web_connect/events.rb +14 -0
  12. data/lib/netfira/web_connect/migration/base_methods.rb +59 -0
  13. data/lib/netfira/web_connect/migration/refinements.rb +16 -0
  14. data/lib/netfira/web_connect/migration.rb +101 -0
  15. data/lib/netfira/web_connect/model/record/digests.rb +24 -0
  16. data/lib/netfira/web_connect/model/record/events.rb +35 -0
  17. data/lib/netfira/web_connect/model/record/file_record.rb +79 -0
  18. data/lib/netfira/web_connect/model/record/materialization.rb +59 -0
  19. data/lib/netfira/web_connect/model/record/relations.rb +51 -0
  20. data/lib/netfira/web_connect/model/record/sendable.rb +29 -0
  21. data/lib/netfira/web_connect/model/record/serializer.rb +56 -0
  22. data/lib/netfira/web_connect/model/record/translated_string.rb +42 -0
  23. data/lib/netfira/web_connect/model/record/translation.rb +58 -0
  24. data/lib/netfira/web_connect/model/record/translations.rb +63 -0
  25. data/lib/netfira/web_connect/model/record/tree.rb +13 -0
  26. data/lib/netfira/web_connect/model/record.rb +132 -0
  27. data/lib/netfira/web_connect/model/relation/events.rb +34 -0
  28. data/lib/netfira/web_connect/model/relation.rb +67 -0
  29. data/lib/netfira/web_connect/model/support.rb +13 -0
  30. data/lib/netfira/web_connect/model.rb +33 -0
  31. data/lib/netfira/web_connect/models/image.rb +48 -0
  32. data/lib/netfira/web_connect/models/order.rb +5 -0
  33. data/lib/netfira/web_connect/models/order_line.rb +13 -0
  34. data/lib/netfira/web_connect/models/support/setting.rb +31 -0
  35. data/lib/netfira/web_connect/models/support/shop/settings.rb +42 -0
  36. data/lib/netfira/web_connect/models/support/shop.rb +22 -0
  37. data/lib/netfira/web_connect/models/support/table.rb +5 -0
  38. data/lib/netfira/web_connect/models.rb +73 -0
  39. data/lib/netfira/web_connect/rack_app/action.rb +66 -0
  40. data/lib/netfira/web_connect/rack_app/action_helpers/data_types.rb +22 -0
  41. data/lib/netfira/web_connect/rack_app/action_helpers/env_importer.rb +59 -0
  42. data/lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb +23 -0
  43. data/lib/netfira/web_connect/rack_app/actions/version_1/not_supported.rb +11 -0
  44. data/lib/netfira/web_connect/rack_app/actions/version_8/checksums.rb +14 -0
  45. data/lib/netfira/web_connect/rack_app/actions/version_8/commit/records.rb +45 -0
  46. data/lib/netfira/web_connect/rack_app/actions/version_8/commit/relations.rb +48 -0
  47. data/lib/netfira/web_connect/rack_app/actions/version_8/commit.rb +33 -0
  48. data/lib/netfira/web_connect/rack_app/actions/version_8/files.rb +21 -0
  49. data/lib/netfira/web_connect/rack_app/actions/version_8/info.rb +18 -0
  50. data/lib/netfira/web_connect/rack_app/actions/version_8/settings.rb +79 -0
  51. data/lib/netfira/web_connect/rack_app/exceptions/http_exception.rb +44 -0
  52. data/lib/netfira/web_connect/rack_app/exceptions.rb +6 -0
  53. data/lib/netfira/web_connect/rack_app.rb +56 -0
  54. data/lib/netfira/web_connect/request_filter.rb +66 -0
  55. data/lib/netfira/web_connect/schema/table.rb +58 -0
  56. data/lib/netfira/web_connect/schema.rb +22 -0
  57. data/lib/netfira/web_connect/version.rb +5 -0
  58. data/lib/netfira/web_connect.rb +82 -0
  59. data/lib/netfira.rb +10 -0
  60. data/lib/web_connect.rb +1 -0
  61. metadata +246 -0
@@ -0,0 +1,42 @@
1
+ require 'forwardable'
2
+ require 'active_support/core_ext/object/deep_dup'
3
+
4
+ module Netfira::WebConnect
5
+ class Models::Shop::Settings
6
+ extend Forwardable
7
+
8
+ def initialize(proxy)
9
+ @proxy = proxy
10
+ @models = proxy.map{ |model| [model.key.to_sym, model] }.to_h
11
+ @hash = @models.map{ |key, model| [key, model.typed_value]}.to_h
12
+ end
13
+
14
+ def [](key)
15
+ @hash[key.to_sym]
16
+ end
17
+
18
+ def []=(key, value)
19
+ key = key.to_sym
20
+ model = @models[key] ||= @proxy.new(key: key.to_s)
21
+ model.typed_value = value
22
+ model.save!
23
+ @hash[key] = value
24
+ end
25
+
26
+ def to_h
27
+ @hash.deep_dup
28
+ end
29
+
30
+ def delete(key)
31
+ key = key.to_sym
32
+ return false unless @models[key]
33
+ @models[key].destroy!
34
+ @models.delete key
35
+ @hash.delete key
36
+ true
37
+ end
38
+
39
+ delegate [:count, :length, :size, :each, :map] => :@hash
40
+
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module Netfira::WebConnect
2
+ class Models::Shop < Model::Support
3
+
4
+ has_many :setting_models, class_name: Models::Setting.name, inverse_of: :shop
5
+
6
+ def settings
7
+ @settings ||= Settings.new(setting_models)
8
+ end
9
+
10
+ def reload
11
+ @settings = nil
12
+ super
13
+ end
14
+
15
+ def locale
16
+ settings[:locale] || Netfira::WebConnect.system_locale
17
+ end
18
+
19
+ end
20
+ end
21
+
22
+ require_relative 'shop/settings'
@@ -0,0 +1,5 @@
1
+ module Netfira::WebConnect
2
+ class Models::Table < Model::Support
3
+
4
+ end
5
+ end
@@ -0,0 +1,73 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Netfira::WebConnect
4
+ module Models
5
+
6
+ def self.materialize
7
+
8
+ # Scrap any existing definitions
9
+ Models.constants.each do |name|
10
+ klass = Models.const_get(name)
11
+ Models.__send__ :remove_const, name if klass < Model::Record || klass < Model::Relation
12
+ end
13
+
14
+ # Load pre-defined models
15
+ Dir[File.expand_path('../models/*.rb', __FILE__)].each { |p| load p }
16
+
17
+ table_names = []
18
+ tables_to_localize = []
19
+ tables_to_relate = []
20
+
21
+ Model.connection.tables.reject{|x| x == Netfira::WebConnect.schema_migrations_table_name }.each do |table_name|
22
+ unprefixed_table_name = unprefix_table_name(table_name)
23
+ if unprefixed_table_name.nil? or unprefixed_table_name[0] == '_'
24
+ next
25
+ elsif l10n_table_name? table_name
26
+ tables_to_localize << l10n_table_owner_name(unprefixed_table_name)
27
+ elsif unprefixed_table_name =~ /\A(.+)_to_(.+)\z/
28
+ tables_to_relate << [$1, $2]
29
+ else
30
+ table_names << unprefixed_table_name
31
+ end
32
+ end
33
+
34
+ table_props = Models::Table.all.map{ |model| [model.name, model] }.to_h
35
+
36
+ table_names.each do |name|
37
+ props = table_props[name]
38
+ next unless props
39
+ Model::Record.materialize name.camelize.singularize,
40
+ tables_to_localize.include?(name),
41
+ props
42
+ end
43
+
44
+ tables_to_relate.each do |names|
45
+ Model::Relation.materialize *names.map{ |x| x.camelize.singularize }
46
+ end
47
+
48
+ end
49
+
50
+ private
51
+
52
+ def self.unprefix_table_name(name)
53
+ return unless name.starts_with? Netfira::WebConnect.db_table_prefix.to_s
54
+ @prefix_range ||= (Netfira::WebConnect.db_table_prefix.length)..-1
55
+ name[@prefix_range]
56
+ end
57
+
58
+ def self.l10n_table_name?(name)
59
+ @l10n_suffix ||= Netfira::WebConnect.db_table_l10n_suffix.to_s
60
+ @l10n_suffix_range ||= (-@l10n_suffix.length)..-1
61
+ @l10n_suffix == name[@l10n_suffix_range]
62
+ end
63
+
64
+ def self.l10n_table_owner_name(name)
65
+ name[@l10n_table_name_range ||= 0..(-@l10n_suffix.length - 1)] if l10n_table_name? name
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ require_relative 'models/support/setting'
72
+ require_relative 'models/support/shop'
73
+ require_relative 'models/support/table'
@@ -0,0 +1,66 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ #todo needs comment
5
+ module Netfira::WebConnect
6
+ class RackApp::Action
7
+ include RackApp::Exceptions::HttpExceptions
8
+
9
+ def self.action_classes
10
+ @action_classes ||= find_action_classes
11
+ end
12
+
13
+ def self.latest_version
14
+ @latest_version ||= action_classes.keys.max
15
+ end
16
+
17
+ def self.create(action, version = nil)
18
+ version ||= latest_version
19
+ klass = nil
20
+ until klass or version < 1
21
+ klass = (action_classes[version] || {})[action] # todo needs explination or to be rewritten to be easily readable
22
+ version -= 1
23
+ end
24
+ klass and klass.new
25
+ end
26
+
27
+ def initialize
28
+ @headers = ActiveSupport::HashWithIndifferentAccess.new
29
+ end
30
+
31
+ private
32
+
33
+ def self.find_action_classes
34
+ action_classes = {}
35
+
36
+ # Loop through all constants in this class's namespace
37
+ constants.each do |constant|
38
+
39
+ # Match Version# constants (modules containing actions)
40
+ if constant =~ /\AVersion(\d+)\z/
41
+
42
+ # The module containing the actions
43
+ mod = const_get(constant)
44
+
45
+ # A hash of actions, e.g. :commit => Action::Version8::Commit
46
+ action_classes[$1.to_i] = Hash[mod.constants.map do |name|
47
+ mod.const_get(name)
48
+ end.select do |klass|
49
+ klass < self
50
+ end.map do |klass|
51
+ [klass.name.demodulize.underscore.to_sym, klass]
52
+ end]
53
+ end
54
+ end
55
+
56
+ action_classes
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ # Load action helpers
63
+ Dir[File.expand_path('../action_helpers/*.rb', __FILE__)].each{ |f| require f }
64
+
65
+ # Pre load all action classes in all versions
66
+ Dir[File.expand_path('../actions/version_*/*.rb', __FILE__)].each{ |f| require f }
@@ -0,0 +1,22 @@
1
+ module Netfira::WebConnect
2
+ class RackApp::Action
3
+
4
+ def class_for_record_type(type)
5
+ class_for_type type, Model::Record
6
+ end
7
+
8
+ def class_for_relation_type(type)
9
+ class_for_type type, Model::Relation
10
+ end
11
+
12
+ private
13
+
14
+ def class_for_type(type, superclass)
15
+ raise BadRequest, 'No data type specified' if type.nil?
16
+ klass = Models.const_defined?(type) && Models.const_get(type)
17
+ raise BadRequest, 'Invalid or unknown data type' unless klass && klass < superclass
18
+ klass
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,59 @@
1
+ module Netfira::WebConnect
2
+ class RackApp::Action
3
+
4
+ def import_env(env)
5
+ @env = env
6
+
7
+ # Parse the environment
8
+ request = Rack::Request.new env
9
+
10
+ # Authentication
11
+ authenticator = Netfira::WebConnect.authenticator
12
+ if authenticator.respond_to? :call
13
+ shop_name = env['HTTP_X_SHOP_NAME']
14
+ password = request['pw'] || env['HTTP_X_PASSWORD']
15
+
16
+ # Basic auth
17
+ auth = Rack::Auth::Basic::Request.new(env)
18
+ if auth.provided? && auth.basic?
19
+ shop_name ||= auth.username
20
+ password ||= auth.credentials[1]
21
+ end
22
+
23
+ result = authenticator.call shop_name, password
24
+ raise Unauthorized unless result
25
+ header :x_vary_password, result if String === result
26
+ @shop = Netfira::WebConnect::Models::Shop.find_or_create_by(name: shop_name)
27
+
28
+ elsif authenticator.nil?
29
+ @shop = Netfira::WebConnect.anonymous_shop
30
+ else
31
+ raise 'Authenticator is not callable'
32
+ end
33
+
34
+ # The request verb (PUT, GET, POST etc)
35
+ @verb = request.request_method.downcase.to_sym
36
+
37
+ # Query string
38
+ @query_string = request.GET
39
+
40
+ # The X-Timeout header
41
+ timeout = env['HTTP_X_TIMEOUT']
42
+ @timeout = timeout.to_i if timeout
43
+
44
+ # Path components
45
+ if env['PATH_INFO'] =~ /\A\/\d+\/[^\/]+\/(.+)\z/
46
+ @path = $1.split('/').map{ |x| Rack::Utils.unescape x }
47
+ end
48
+
49
+ # Input
50
+ if put? or post?
51
+ @input = request.body
52
+ @input = StringIO.new(@input.read.unpack('m').first) if env['CONTENT_ENCODING'] == 'base64'
53
+ @input = JSON.parse @input.read if request.media_type == 'application/json'
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ module Netfira::WebConnect
2
+ class RackApp::Action
3
+
4
+ attr_reader :timeout, :input, :path, :verb, :shop, :headers, :query_string, :env
5
+
6
+ # This method sets a response header, e.g.header 'Content-Type', 'text/plain'
7
+ def header(name, value)
8
+ name = name.to_s.split(/[- _]/).map(&:capitalize).join('-').to_sym
9
+ if value
10
+ headers[name] = value
11
+ else
12
+ headers.delete name
13
+ end
14
+ end
15
+
16
+ # These methods tell you if a given verb was used, e.g. post?
17
+ %w[get post put delete].each do |verb|
18
+ define_method(:"#{verb}?") { self.verb.to_s == verb }
19
+ end
20
+
21
+
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version1
3
+ %w[Commit Info Settings Checksums Files].each do |name|
4
+ const_set name, (Class.new(Action) do
5
+ def call
6
+ raise Exceptions::HttpExceptions::NotFound
7
+ end
8
+ end)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Checksums < Action
4
+ def call
5
+ raise MethodNotAllowed unless get?
6
+
7
+ klass = class_for_record_type(query_string['type'])
8
+ checksums = klass.where(shop_id: shop.id).map{ |record| [record.origin_id, record.digest]}.to_h
9
+
10
+ {checksums: checksums}
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,45 @@
1
+ module Netfira::WebConnect
2
+ module RackApp::Action::Version8
3
+ class Commit
4
+
5
+ private
6
+
7
+ def commit_records(records)
8
+ records.each do |class_name, attribute_sets|
9
+ klass = class_for_record_type(class_name)
10
+ attribute_sets.each do |attributes|
11
+ update_record klass, attributes
12
+ end
13
+ end
14
+ end
15
+
16
+ def update_record(klass, attributes)
17
+ origin_key = attributes[klass.origin_key.to_s.camelize(:lower)]
18
+ raise BadRequest, "#{klass.name.demodulize} sent with no #{klass.origin_key}" unless origin_key
19
+ record = klass.find_or_initialize_by_origin_id(shop, origin_key)
20
+ attributes.each do |key, value|
21
+ key = key.underscore
22
+ next if key == origin_key
23
+ if key == 'checksum' && klass < Model::Record::FileRecord
24
+ request_file klass, attributes['fileName'] if record.checksum != value
25
+ next
26
+ end
27
+ record.write_with_type_casting(key.to_sym, value) or raise BadRequest, "#{klass.name.demodulize.pluralize} don't have #{key} fields"
28
+ end
29
+ record.save or raise InternalServerError, record.errors
30
+ complete_record record
31
+ end
32
+
33
+ def complete_record(record)
34
+ list = complete[:records][record.class.name.demodulize] ||= {}
35
+ list[record.origin_id.to_s] = record.digest
36
+ end
37
+
38
+ def request_file(klass, file_name)
39
+ list = files_to_send[klass.name.demodulize] ||= []
40
+ list << file_name unless list.include? file_name
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Commit
4
+
5
+ private
6
+
7
+ def commit_relations(relations)
8
+ relations.each do |class_name, relation_sets|
9
+ klass = class_for_relation_type class_name.sub('&', 'To')
10
+ relation_sets.each do |relation|
11
+ update_relation klass, relation
12
+ complete_relation class_name, relation
13
+ end
14
+ end
15
+ end
16
+
17
+ def update_relation(klass, relation)
18
+
19
+ # Make a hash of models => IDs, e.g. {Product => '123', Image => 'file.jpg'}
20
+ origin_ids = [relation['a'], relation['b']].each_with_index.map do |origin_id, index|
21
+ [klass.related_classes[index], origin_id]
22
+ end.to_h
23
+
24
+ if relation['x']
25
+ relate_records origin_ids
26
+ else
27
+ unrelate_records origin_ids
28
+ end
29
+ end
30
+
31
+ def relate_records(origin_ids)
32
+ records = origin_ids.map{ |k, id| k.find_or_create_by_origin_id shop, id }
33
+ records[0] << records[1]
34
+ end
35
+
36
+ def unrelate_records(origin_ids)
37
+ records = origin_ids.map{ |k, id| k.find_by_origin_id shop, id }.reject(&:nil?)
38
+ records[0].delete records[1] if records.size == 2
39
+ end
40
+
41
+ def complete_relation(class_name, relation)
42
+ list = complete[:relations][class_name] ||= []
43
+ list << relation
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Commit < Action
4
+
5
+ def call
6
+ raise MethodNotAllowed unless verb == :post
7
+ commit_records input['records'] if input['records']
8
+ commit_relations input['relations'] if input['relations']
9
+ {
10
+ complete: complete,
11
+ filesToSend: files_to_send
12
+ }
13
+ end
14
+
15
+ private
16
+
17
+ def files_to_send
18
+ @files_to_send ||= {}
19
+ end
20
+
21
+ def complete
22
+ @complete ||= {
23
+ records: {},
24
+ relations: {}
25
+ }
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+
32
+ require_relative 'commit/records'
33
+ require_relative 'commit/relations'
@@ -0,0 +1,21 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Files < Action
4
+ def call
5
+ raise MethodNotAllowed unless post?
6
+ klass = class_for_record_type(path[0])
7
+ raise BadRequest, "You can't upload files for that data type" unless klass < Netfira::WebConnect::Model::Record::FileRecord
8
+
9
+ file_name = path[1]
10
+ raise BadRequest, 'You must specify a file name' unless file_name.length > 0
11
+
12
+ file = klass.find_or_initialize_by_origin_id(shop, file_name)
13
+ file << input
14
+ file.file_name = file_name
15
+
16
+ file.save or raise InternalServerError, file.errors
17
+ {}
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Info < Action
4
+ def call
5
+ {
6
+ info: {
7
+ apiVersion: Action.latest_version,
8
+ libVersion: Netfira::WebConnect::VERSION,
9
+ schema: Netfira::WebConnect.schema,
10
+ customFields: false,
11
+ acceptableRequestTypes: env['WC_ACCEPTABLE_REQUEST_TYPES'] || ['unpacked'],
12
+ locale: shop.locale
13
+ }
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Settings < Action
4
+ def call
5
+ key = path && path.first
6
+ if key
7
+ handle_single key
8
+ else
9
+ handle_multiple
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def handle_single(key)
16
+ case verb
17
+ when :put, :post then update_single key
18
+ when :get then get_single key
19
+ when :delete then delete_single key
20
+ else raise MethodNotAllowed
21
+ end
22
+ end
23
+
24
+ def update_single(key)
25
+ if input.respond_to? :read
26
+ setting = shop.setting_models.find_or_initialize_by(key: key)
27
+ setting.value = input.read
28
+ setting.content_type = env['CONTENT_TYPE'] || 'application/x-octet-stream'
29
+ setting.save
30
+ else
31
+ shop.settings[key] = input
32
+ end
33
+ {}
34
+ end
35
+
36
+ def get_single(key)
37
+ setting = shop.setting_models.find_by(key: key)
38
+ raise NotFound unless setting
39
+ # if setting.content_type == 'application/json'
40
+ # JSON.parse(setting.value, quirks_mode: true)
41
+ # else
42
+ header 'Content-Type', setting.content_type
43
+ setting.value
44
+ # end
45
+ end
46
+
47
+ def delete_single(key)
48
+ shop.setting_models.where(key: key).destroy_all
49
+ {}
50
+ end
51
+
52
+ def handle_multiple
53
+ case verb
54
+ when :put, :post then update_multiple
55
+ when :get then get_all
56
+ when :delete then delete_all
57
+ else raise MethodNotAllowed
58
+ end
59
+ end
60
+
61
+ def update_multiple
62
+ input.each do |key, value|
63
+ shop.settings[key] = value.unpack('m').first
64
+ end
65
+ {}
66
+ end
67
+
68
+ def get_all
69
+ {settings: shop.setting_models.map{ |model| [model.key, [model.value.b].pack('m0')] }.to_h}
70
+ end
71
+
72
+ def delete_all
73
+ shop.setting_models.destroy_all
74
+ {}
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,44 @@
1
+ require 'rack/utils'
2
+ require 'forwardable'
3
+
4
+ module Netfira::WebConnect::RackApp::Exceptions
5
+
6
+ class HttpException < Base
7
+ extend Forwardable
8
+
9
+ class << self
10
+ attr_accessor :status, :code, :category
11
+ attr_reader :headers
12
+ end
13
+
14
+ def_delegators :'self.class', :status, :code, :category
15
+
16
+ def initialize(*args, &block)
17
+ super *args, &block
18
+ @headers = self.class.headers.dup
19
+ end
20
+
21
+ attr_reader :headers
22
+
23
+ end
24
+
25
+ module HttpExceptions
26
+
27
+ Rack::Utils::HTTP_STATUS_CODES.each do |code, status|
28
+ next if status == 'Unassigned'
29
+ const_set status.gsub(/[ -](\w)/){$1.upcase}.sub(/ .*/, ''), (Class.new(HttpException) do
30
+
31
+ @status = status
32
+ @code = code
33
+ @category = code / 100
34
+
35
+ @headers = {}
36
+
37
+ end)
38
+ end
39
+
40
+ Unauthorized.headers['WWW-Authenticate'] = 'Basic realm="Authentication required"'
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,6 @@
1
+ module Netfira::WebConnect::RackApp::Exceptions
2
+ class Base < Exception
3
+ end
4
+ end
5
+
6
+ require_relative 'exceptions/http_exception'