brick 1.0.105 → 1.0.107

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48fc69654d7cbdf5c7feb9ba7803b7a953c6c0ccd3c176614fe9a61994bb39e1
4
- data.tar.gz: f3a4837d334cf97cb491cacd658cb6e6d8e3f3a5c640f80c151a083bf6811b7a
3
+ metadata.gz: 74bd7cd6539a5f048aecc3e02be8912e1bc77851ac8b9f65402892e964a024de
4
+ data.tar.gz: c32c53d0012f2a9292463d803d0c679d4f32800c8931c3361469aa22c09a3fc5
5
5
  SHA512:
6
- metadata.gz: e51134710bae78f033b9dcf7827cd7c087cfd642ef8bc6244e2429f22158a80e9f804022bc703f55fa55f85cbc009ff25440289205ade1b415f5ba0af3d44c77
7
- data.tar.gz: d6069676ad70e8401f7597b92b641f0f95e3c28fffa54747f630932500c778d7b4ac967c5bbd70059647d11d398598362d9c0bb5c82ef0500b1025371104a578
6
+ metadata.gz: f84ee2aae03622a4ca67a6921daf7e975f85601e66fc608c2357613da976414d2ceeec87276887232a832413116ec1495638c883ecc712db3706f51c34efbd44
7
+ data.tar.gz: e97fbe41eca4d839795f7567bfd3e8a3d6494d4887032fa5f0f87d88ba3c9fdc810bde8a259c3cbf0b8f944c8b345c33290aa5d1e8f7aa67737e7fe7be248a6d
data/lib/brick/config.rb CHANGED
@@ -95,20 +95,19 @@ module Brick
95
95
  end
96
96
 
97
97
  def api_roots
98
- ver = api_version
99
- @mutex.synchronize { @api_roots || ["/api/#{ver}/"] }
98
+ @mutex.synchronize { @api_roots || ["/api/v1/"] }
100
99
  end
101
100
 
102
101
  def api_roots=(path)
103
102
  @mutex.synchronize { @api_roots = path }
104
103
  end
105
104
 
106
- def api_version
107
- @mutex.synchronize { @api_version || 'v1' }
105
+ def api_filter
106
+ @mutex.synchronize { @api_filter }
108
107
  end
109
108
 
110
- def api_version=(ver)
111
- @mutex.synchronize { @api_version = ver }
109
+ def api_filter=(proc)
110
+ @mutex.synchronize { @api_filter = proc }
112
111
  end
113
112
 
114
113
  # Additional table associations to use (Think of these as virtual foreign keys perhaps)
@@ -1526,9 +1526,10 @@ class Object
1526
1526
  self.protect_from_forgery unless: -> { self.request.format.js? }
1527
1527
  unless is_avo
1528
1528
  self.define_method :index do
1529
+ request_ver = request.path.split('/')[-2]
1529
1530
  current_api_root = ::Brick.config.api_roots.find do |ar|
1530
1531
  request.path.start_with?(ar) || # Exact match?
1531
- request.path.split('/')[-2] == ar.split('/').last # Version at least matches?
1532
+ request_ver == ar.split('/').last # Version at least matches?
1532
1533
  end
1533
1534
  if (current_api_root || is_openapi) &&
1534
1535
  !params&.key?('_brick_schema') &&
@@ -1542,51 +1543,72 @@ class Object
1542
1543
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
1543
1544
 
1544
1545
  if is_openapi
1546
+ api_name = Rswag::Ui.config.config_object[:urls].find do |api_url|
1547
+ api_url[:url] == request.path
1548
+ end&.fetch(:name, 'API documentation')
1545
1549
  current_api_ver = current_api_root.split('/').last&.[](1..-1).to_i
1546
- json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
1550
+ json = { 'openapi': '3.0.1', 'info': { 'title': api_name, 'version': request_ver },
1547
1551
  'servers': [
1548
- { 'url': '{scheme}://{defaultHost}', 'variables': {
1549
- 'scheme': { 'default': request.env['rack.url_scheme'] },
1550
- 'defaultHost': { 'default': request.env['HTTP_HOST'] }
1551
- } }
1552
+ { 'url': '{scheme}://{defaultHost}',
1553
+ 'variables': {
1554
+ 'scheme': { 'default': request.env['rack.url_scheme'] },
1555
+ 'defaultHost': { 'default': request.env['HTTP_HOST'] }
1556
+ }
1557
+ }
1552
1558
  ]
1553
1559
  }
1554
- json['paths'] = relations.each_with_object({}) do |relation, s|
1555
- unless ::Brick.config.enable_api == false
1560
+ unless ::Brick.config.enable_api == false
1561
+ json['paths'] = relations.each_with_object({}) do |relation, s|
1556
1562
  next if (api_vers = relation.last.fetch(:api, nil)) &&
1557
- !(api_ver_path = api_vers[current_api_ver])
1558
-
1559
- relation_name = api_ver_path || relation.first.tr('.', '/')
1560
- table_description = relation.last[:description]
1561
- s["#{current_api_root}#{relation_name}"] = {
1562
- 'get': {
1563
- 'summary': "list #{relation.first}",
1564
- 'description': table_description,
1565
- 'parameters': relation.last[:cols].map do |k, v|
1566
- param = { in: 'query', 'name' => k, 'schema': { 'type': v.first } }
1567
- if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1568
- param['description'] = col_descrip
1569
- end
1570
- param
1571
- end,
1572
- 'responses': { '200': { 'description': 'successful' } }
1573
- }
1574
- }
1575
-
1576
- s["#{current_api_root}#{relation_name}/{id}"] = {
1577
- 'patch': {
1578
- 'summary': "update a #{relation.first.singularize}",
1579
- 'description': table_description,
1580
- 'parameters': relation.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
1581
- param = { 'name' => k, 'schema': { 'type': v.first } }
1582
- if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1583
- param['description'] = col_descrip
1563
+ !(api_ver_paths = api_vers[current_api_ver] || api_vers[nil])
1564
+
1565
+ schema_tag = {}
1566
+ if (schema_name = relation.last&.fetch(:schema, nil))
1567
+ schema_tag['tags'] = [schema_name]
1568
+ end
1569
+ all_actions = relation.last.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS
1570
+ (api_ver_paths || { relation.first => all_actions }).each do |api_ver_path, actions|
1571
+ relation_name = (api_ver_path || relation.first).tr('.', '/')
1572
+ table_description = relation.last[:description]
1573
+ { :index => [:get, 'list'], :create => [:post, 'create a'] }.each do |k, v|
1574
+ unless actions&.exclude?(k)
1575
+ this_resource = (s["#{current_api_root}#{relation_name}"] ||= {})
1576
+ this_resource[v.first] = {
1577
+ 'summary': "#{v[1]} #{relation.first}",
1578
+ 'description': table_description,
1579
+ 'parameters': relation.last[:cols].map do |k2, v2|
1580
+ param = { in: 'query', 'name': k2, 'schema': { 'type': v2.first } }
1581
+ if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k2, nil))
1582
+ param['description'] = col_descrip
1583
+ end
1584
+ param
1585
+ end,
1586
+ 'responses': { '200': { 'description': 'successful' } }
1587
+ }.merge(schema_tag)
1588
+ end
1589
+ end
1590
+
1591
+ # We have not yet implemented the #show action
1592
+ if (id_col = relation.last[:pkey]&.values&.first&.first) # ... ID-dependent stuff
1593
+ { :update => [:patch, 'update'], :destroy => [:delete, 'delete'] }.each do |k, v|
1594
+ unless actions&.exclude?(k)
1595
+ this_resource = (s["#{current_api_root}#{relation_name}/{#{id_col}}"] ||= {})
1596
+ this_resource[v.first] = {
1597
+ 'summary': "#{v[1]} a #{relation.first.singularize}",
1598
+ 'description': table_description,
1599
+ 'parameters': relation.last[:cols].reject { |k1, _v1| Brick.config.metadata_columns.include?(k1) }.map do |k2, v2|
1600
+ param = { 'name': k2, 'schema': { 'type': v2.first } }
1601
+ if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k2, nil))
1602
+ param['description'] = col_descrip
1603
+ end
1604
+ param
1605
+ end,
1606
+ 'responses': { '200': { 'description': 'successful' } }
1607
+ }.merge(schema_tag)
1584
1608
  end
1585
- param
1586
- end,
1587
- 'responses': { '200': { 'description': 'successful' } }
1588
- }
1589
- } unless relation.last.fetch(:isView, nil)
1609
+ end
1610
+ end
1611
+ end # Do multiple api_ver_paths
1590
1612
  end
1591
1613
  end
1592
1614
  render inline: json.to_json, content_type: request.format
@@ -1600,11 +1622,11 @@ class Object
1600
1622
  end
1601
1623
  render inline: exported_csv, content_type: request.format
1602
1624
  return
1603
- elsif request.format == :js || current_api_root # Asking for JSON?
1604
- # %%% Add: where, order, page, page_size, offset, limit
1605
- data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
1606
- render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1607
- return
1625
+ # elsif request.format == :js || current_api_root # Asking for JSON?
1626
+ # # %%% Add: where, order, page, page_size, offset, limit
1627
+ # data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
1628
+ # render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1629
+ # return
1608
1630
  end
1609
1631
 
1610
1632
  # Normal (not swagger or CSV) request
@@ -1616,8 +1638,15 @@ class Object
1616
1638
 
1617
1639
  ar_relation = ActiveRecord.version < Gem::Version.new('4') ? model.preload : model.all
1618
1640
  @_brick_params = ar_relation.brick_select(params, (selects ||= []), order_by,
1619
- translations = {},
1620
- join_array = ::Brick::JoinArray.new)
1641
+ translations = {},
1642
+ join_array = ::Brick::JoinArray.new)
1643
+
1644
+ if request.format == :js || current_api_root # Asking for JSON?
1645
+ data = ar_relation.respond_to?(:_select!) ? ar_relation.dup._select!(*selects) : ar_relation.select(selects)
1646
+ render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1647
+ return
1648
+ end
1649
+
1621
1650
  # %%% Add custom HM count columns
1622
1651
  # %%% What happens when the PK is composite?
1623
1652
  counts = model._br_hm_counts.each_with_object([]) do |v, s|
@@ -2127,7 +2156,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2127
2156
  # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
2128
2157
  fk_references = ActiveRecord::Base.execute_sql(sql)
2129
2158
  when 'SQLite'
2130
- sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
2159
+ sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
2131
2160
  FROM sqlite_master m
2132
2161
  INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
2133
2162
  ORDER BY m.name, fkl.seq"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 105
8
+ TINY = 107
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -124,6 +124,8 @@ if Gem::Specification.all_names.any? { |g| g.start_with?('rails-') }
124
124
  require 'brick/frameworks/rails'
125
125
  end
126
126
  module Brick
127
+ ALL_API_ACTIONS = [:index, :show, :create, :update, :destroy]
128
+
127
129
  class << self
128
130
  def sti_models
129
131
  @sti_models ||= {}
@@ -347,6 +349,16 @@ module Brick
347
349
  Brick.config.api_roots
348
350
  end
349
351
 
352
+ # @api public
353
+ def api_filter=(proc)
354
+ Brick.config.api_filter = proc
355
+ end
356
+
357
+ # @api public
358
+ def api_filter
359
+ Brick.config.api_filter
360
+ end
361
+
350
362
  # @api public
351
363
  def skip_database_views=(value)
352
364
  Brick.config.skip_database_views = value
@@ -666,7 +678,10 @@ In config/initializers/brick.rb appropriate entries would look something like:
666
678
  ::Brick.relations.each do |k, v|
667
679
  next if !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
668
680
 
669
- schema_name = v.fetch(:schema, nil)
681
+ object_name = k.split('.').last # Take off any first schema part
682
+ if (schema_name = v.fetch(:schema, nil))
683
+ schema_prefix = "#{schema_name}."
684
+ end
670
685
  options = {}
671
686
  options[:only] = [:index, :show] if v.key?(:isView)
672
687
  # First do the API routes if necessary
@@ -677,49 +692,111 @@ In config/initializers/brick.rb appropriate entries would look something like:
677
692
  view_relation = nil
678
693
  # If it's a view then see if there's a versioned one available by searching for resource names
679
694
  # versioned with the closest number (equal to or less than) compared with our API version number.
680
- if v.key?(:isView) && (ver = k.match(/^v([\d_]*)/).captures.first)[-1] == '_'
681
- next if api_done_views.key?(unversioned = k[ver.length + 1..-1])
682
-
683
- # # if ().length.positive? # Does it have a version number?
684
- # try_num = (ver_num = (ver = ver[1..-1].gsub('_', '.')).to_d)
695
+ if v.key?(:isView) && (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
696
+ core_object_name = object_name[ver.length + 1..-1]
697
+ next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
685
698
 
686
699
  # Expect that the last item in the path generally holds versioning information
687
700
  api_ver = api_root.split('/')[-1]&.gsub('_', '.')
688
701
  vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
689
702
  # Was: .to_d
690
- api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
703
+ test_ver_num = api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
691
704
  # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
692
705
 
693
706
  next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
694
707
 
695
- api_ver_num -= 1 until api_ver_num.zero? ||
696
- (view_relation = ::Brick.relations.fetch(
697
- found = "v#{api_ver_num}_#{k[ver.length + 1..-1]}", nil
698
- ))
708
+ test_ver_num -= 1 until test_ver_num.zero? ||
709
+ (view_relation = ::Brick.relations.fetch(
710
+ found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
711
+ ))
699
712
  api_done_views[unversioned] = nil # Mark that for this API version this view is done
700
713
 
701
714
  # puts "Found #{found}" if view_relation
702
715
  # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
703
716
  # fall back to simply looking for "v_view_name", and then finally "view_name".
704
- unversioned = "v_#{unversioned}"
705
- view_relation ||= ::Brick.relations.fetch(found = unversioned,
706
- ::Brick.relations.fetch(found = unversioned, nil)
707
- )
708
- if found && view_relation && k != (found = unversioned)
709
- view_relation[:api][api_ver_num] = found
710
- end
717
+ no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
718
+ else
719
+ unversioned = k
711
720
  end
712
721
 
713
- # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
714
- # first_part[1..-1].gsub('_', '.').to_i
715
- # end
716
- controller_name = view_relation.fetch(:resource, nil)&.pluralize if view_relation
717
- if schema_name
718
- full_resource = "#{schema_name}/#{found || v[:resource]}"
719
- send(:get, "#{api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" })
720
- else
721
- # Normally goes to something like: /api/v1/employees
722
- send(:get, "#{api_root}#{found || v[:resource]}", { to: "#{controller_prefix}#{controller_name}#index" })
722
+ view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
723
+ (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil))
724
+ if view_relation
725
+ actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
726
+ # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
727
+ # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
728
+ # these 3 things controls and changes the nature of the endpoint that gets built:
729
+ # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
730
+ proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
731
+ begin
732
+ num_args = filter.arity.negative? ? 6 : filter.arity
733
+ filter.call(*[unversioned, k, actions, api_ver_num, found, test_ver_num][0...num_args])
734
+ rescue StandardError => e
735
+ puts "::Brick.api_filter Proc error: #{e.message}"
736
+ end
737
+ end
738
+ # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
739
+
740
+ case proc_result
741
+ when NilClass
742
+ # Do nothing differently than what normal behaviour would be
743
+ when FalseClass # Skip implementing this endpoint
744
+ view_relation[:api][api_ver_num] = nil
745
+ next
746
+ when Array # Did they give back an array of actions?
747
+ unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
748
+ proc_result = [unversioned, to_relation, proc_result]
749
+ end
750
+ # Otherwise don't change this array because it's probably legit
751
+ when String
752
+ proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
753
+ else
754
+ puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
755
+ proc_result = nil # Couldn't understand what in the world was returned
756
+ end
757
+
758
+ if proc_result&.present?
759
+ if proc_result[1] # to_other_relation
760
+ if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
761
+ k = proc_result[1] # Route this call over to this different relation
762
+ view_relation = new_view_relation
763
+ else
764
+ puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
765
+ end
766
+ end
767
+ if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
768
+ found = proc_result.first
769
+ end
770
+ actions &= proc_result[2] if proc_result[2] # allowed_actions
771
+ end
772
+ (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
773
+
774
+ # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
775
+ # first_part[1..-1].gsub('_', '.').to_i
776
+ # end
777
+
778
+ controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
779
+ "#{schema_prefix}#{last}"
780
+ else
781
+ found
782
+ end.tr('.', '/')
783
+
784
+ { :index => 'get', :create => 'post' }.each do |action, method|
785
+ if actions.include?(action)
786
+ # Normally goes to something like: /api/v1/employees
787
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
788
+ end
789
+ end
790
+ # %%% We do not yet surface the #show action
791
+ if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
792
+ { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
793
+ if actions.include?(action)
794
+ methods.each do |method|
795
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
796
+ end
797
+ end
798
+ end
799
+ end
723
800
  end
724
801
  end
725
802
 
@@ -731,7 +808,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
731
808
  else
732
809
  table_class_length = class_name.length if class_name.length > table_class_length
733
810
  tables
734
- end << [class_name, full_resource || v[:resource]]
811
+ end << [class_name, "#{schema_prefix&.tr('.', '/')}#{v[:resource]}"]
735
812
  end
736
813
 
737
814
  # Now the normal routes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.105
4
+ version: 1.0.107
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-17 00:00:00.000000000 Z
11
+ date: 2023-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: 1.42.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: rswag-ui
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: mysql2
169
183
  requirement: !ruby/object:Gem::Requirement