brick 1.0.105 → 1.0.107
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 +4 -4
- data/lib/brick/config.rb +5 -6
- data/lib/brick/extensions.rb +77 -48
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +106 -29
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74bd7cd6539a5f048aecc3e02be8912e1bc77851ac8b9f65402892e964a024de
|
4
|
+
data.tar.gz: c32c53d0012f2a9292463d803d0c679d4f32800c8931c3361469aa22c09a3fc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
107
|
-
@mutex.synchronize { @
|
105
|
+
def api_filter
|
106
|
+
@mutex.synchronize { @api_filter }
|
108
107
|
end
|
109
108
|
|
110
|
-
def
|
111
|
-
@mutex.synchronize { @
|
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)
|
data/lib/brick/extensions.rb
CHANGED
@@ -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
|
-
|
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':
|
1550
|
+
json = { 'openapi': '3.0.1', 'info': { 'title': api_name, 'version': request_ver },
|
1547
1551
|
'servers': [
|
1548
|
-
{ 'url': '{scheme}://{defaultHost}',
|
1549
|
-
'
|
1550
|
-
|
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
|
-
|
1555
|
-
|
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
|
-
!(
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
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
|
-
|
1586
|
-
|
1587
|
-
|
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
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
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
|
-
|
1620
|
-
|
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"
|
data/lib/brick/version_number.rb
CHANGED
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
|
-
|
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 =
|
681
|
-
|
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
|
-
|
696
|
-
|
697
|
-
|
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
|
-
|
705
|
-
|
706
|
-
|
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
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
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,
|
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.
|
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-
|
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
|