From 62fdc61cb87549e6cc41fe174a631d8e3bca4273 Mon Sep 17 00:00:00 2001 From: Alex Nizamov Date: Sun, 21 Sep 2025 22:05:55 +0500 Subject: [PATCH 1/2] (#896) Add filter by @type to CTID lookup API --- app/api/v1/resources.rb | 17 ++++++++++++----- lib/swagger_docs/sections/resources.rb | 5 +++++ spec/api/v1/resources_spec.rb | 26 ++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/app/api/v1/resources.rb b/app/api/v1/resources.rb index 7c23dff8..ba2f5931 100644 --- a/app/api/v1/resources.rb +++ b/app/api/v1/resources.rb @@ -29,15 +29,22 @@ class Resources < MountableAPI desc 'Returns CTIDs of existing resources' params do requires :ctids, type: [String], desc: 'CTIDs' + optional :@type, type: String, desc: 'Resource type' end post 'check_existence' do status(:ok) - @envelope_community - .envelope_resources - .not_deleted - .where('resource_id IN (?)', params[:ctids]) - .pluck(:resource_id) + resource_type = @envelope_community + .config + .dig('resource_type', 'values_map', params[:@type]) + + envelope_resources = @envelope_community + .envelope_resources + .not_deleted + .where('resource_id IN (?)', params[:ctids]) + + envelope_resources.where!(resource_type:) if resource_type + envelope_resources.pluck(:resource_id) end desc 'Returns resources with the given CTIDs or bnodes IDs' diff --git a/lib/swagger_docs/sections/resources.rb b/lib/swagger_docs/sections/resources.rb index be4298c3..ed4070d0 100644 --- a/lib/swagger_docs/sections/resources.rb +++ b/lib/swagger_docs/sections/resources.rb @@ -35,6 +35,11 @@ module Resources # rubocop:todo Metrics/ModuleLength, Style/Documentation key :type, :string end end + + property :@type do + key :type, :string + key :description, 'CTDL type' + end end end diff --git a/spec/api/v1/resources_spec.rb b/spec/api/v1/resources_spec.rb index 49886df0..e58933ee 100644 --- a/spec/api/v1/resources_spec.rb +++ b/spec/api/v1/resources_spec.rb @@ -368,6 +368,7 @@ let(:ctid1) { Faker::Lorem.characters(number: 32) } # rubocop:todo RSpec/IndexedLet let(:ctid2) { Faker::Lorem.characters(number: 32) } # rubocop:todo RSpec/IndexedLet let(:ctid3) { Faker::Lorem.characters(number: 32) } # rubocop:todo RSpec/IndexedLet + let(:ctid4) { Faker::Lorem.characters(number: 32) } # rubocop:todo RSpec/IndexedLet before do resource1 = attributes_for(:cer_competency_framework, ctid: ctid1) @@ -382,6 +383,10 @@ .except(:id) .stringify_keys + resource4 = attributes_for(:cer_org, ctid: ctid4) + .except(:id) + .stringify_keys + create( :envelope, :from_cer, @@ -402,12 +407,29 @@ processed_resource: attributes_for(:cer_graph_competency_framework) .merge(:@graph => [resource3]) ) + + create( + :envelope, + :from_cer, + processed_resource: attributes_for(:cer_org) + .merge(:@graph => [resource4]) + ) end - it 'returns existing CTIDs' do - post '/resources/check_existence', { ctids: [ctid1, ctid2, ctid3] } + it 'returns existing CTIDs' do # rubocop:todo RSpec/ExampleLength + post '/resources/check_existence', { ctids: [ctid1, ctid2, ctid3, ctid4] } + expect_status(:ok) + expect(JSON(response.body)).to contain_exactly(ctid1, ctid4) + + post '/resources/check_existence', + { ctids: [ctid1, ctid2, ctid3, ctid4], '@type': 'ceasn:CompetencyFramework' } expect_status(:ok) expect(JSON(response.body)).to contain_exactly(ctid1) + + post '/resources/check_existence', + { ctids: [ctid1, ctid2, ctid3, ctid4], '@type': 'ceterms:CredentialOrganization' } + expect_status(:ok) + expect(JSON(response.body)).to contain_exactly(ctid4) end end # rubocop:enable RSpec/MultipleMemoizedHelpers From 968cfda1ee79e78d24b939a256af9affa8e5afd8 Mon Sep 17 00:00:00 2001 From: Alex Nizamov Date: Tue, 30 Sep 2025 07:23:06 +0500 Subject: [PATCH 2/2] (#896) Include subclasses into @type filter in CTID existence check --- app/api/v1/resources.rb | 25 ++++++++++++++++++++---- app/services/ctdl_subclasses_resolver.rb | 13 +++++++++--- spec/api/v1/resources_spec.rb | 12 ++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/api/v1/resources.rb b/app/api/v1/resources.rb index ba2f5931..65e9f10d 100644 --- a/app/api/v1/resources.rb +++ b/app/api/v1/resources.rb @@ -34,16 +34,33 @@ class Resources < MountableAPI post 'check_existence' do status(:ok) - resource_type = @envelope_community - .config - .dig('resource_type', 'values_map', params[:@type]) + resource_types = + if (type = params[:@type]).present? + resolver = CtdlSubclassesResolver.new(envelope_community: @envelope_community) + + unless resolver.all_classes.include?(type) + error!( + { errors: ['@type does not have a valid value'] }, + :unprocessable_entity + ) + end + + resolver.root_class = type + + resolver.subclasses.filter_map do |subclass| + @envelope_community + .config + .dig('resource_type', 'values_map', subclass) + end + end envelope_resources = @envelope_community .envelope_resources .not_deleted .where('resource_id IN (?)', params[:ctids]) - envelope_resources.where!(resource_type:) if resource_type + envelope_resources.where!(resource_type: resource_types.uniq) unless resource_types.nil? + envelope_resources.pluck(:resource_id) end diff --git a/app/services/ctdl_subclasses_resolver.rb b/app/services/ctdl_subclasses_resolver.rb index 6175a46f..23c32a8d 100644 --- a/app/services/ctdl_subclasses_resolver.rb +++ b/app/services/ctdl_subclasses_resolver.rb @@ -1,17 +1,24 @@ class CtdlSubclassesResolver # rubocop:todo Style/Documentation SUBCLASSES_MAP_FILE = MR.root_path.join('fixtures', 'subclasses_map.json') - attr_reader :envelope_community_config, :include_root, :root_class + attr_accessor :root_class + attr_reader :envelope_community_config, :include_root - def initialize(envelope_community:, root_class:, include_root: true) + def initialize(envelope_community:, root_class: nil, include_root: true) @envelope_community_config = envelope_community.config @include_root = include_root @root_class = root_class end + def all_classes(map = ctdl_subclasses_map) + map.flat_map do |type, submap| + [type, *all_classes(submap)] + end.uniq + end + def subclasses @subclasses ||= collect_subclasses(initial_map_item) + - (include_root ? [root_class] : []) + (include_root && root_class ? [root_class] : []) end def initial_map_item diff --git a/spec/api/v1/resources_spec.rb b/spec/api/v1/resources_spec.rb index e58933ee..fc1307f5 100644 --- a/spec/api/v1/resources_spec.rb +++ b/spec/api/v1/resources_spec.rb @@ -416,11 +416,18 @@ ) end + # rubocop:todo RSpec/MultipleExpectations it 'returns existing CTIDs' do # rubocop:todo RSpec/ExampleLength + # rubocop:enable RSpec/MultipleExpectations post '/resources/check_existence', { ctids: [ctid1, ctid2, ctid3, ctid4] } expect_status(:ok) expect(JSON(response.body)).to contain_exactly(ctid1, ctid4) + post '/resources/check_existence', + { ctids: [ctid1, ctid2, ctid3, ctid4], '@type': 'foobar' } + expect_status(:unprocessable_entity) + expect_json(errors: ['@type does not have a valid value']) + post '/resources/check_existence', { ctids: [ctid1, ctid2, ctid3, ctid4], '@type': 'ceasn:CompetencyFramework' } expect_status(:ok) @@ -430,6 +437,11 @@ { ctids: [ctid1, ctid2, ctid3, ctid4], '@type': 'ceterms:CredentialOrganization' } expect_status(:ok) expect(JSON(response.body)).to contain_exactly(ctid4) + + post '/resources/check_existence', + { ctids: [ctid1, ctid2, ctid3, ctid4], '@type': 'ceterms:Organization' } + expect_status(:ok) + expect(JSON(response.body)).to contain_exactly(ctid4) end end # rubocop:enable RSpec/MultipleMemoizedHelpers