supplejack_api 1

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.
Files changed (291) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +80 -0
  3. data/Rakefile +42 -0
  4. data/app/assets/javascripts/supplejack_api/application.js +26 -0
  5. data/app/assets/stylesheets/supplejack_api/application.css +30 -0
  6. data/app/controllers/supplejack_api/admin/base_controller.rb +24 -0
  7. data/app/controllers/supplejack_api/admin/sessions_controller.rb +25 -0
  8. data/app/controllers/supplejack_api/admin/site_activities_controller.rb +21 -0
  9. data/app/controllers/supplejack_api/admin/users_controller.rb +55 -0
  10. data/app/controllers/supplejack_api/application_controller.rb +70 -0
  11. data/app/controllers/supplejack_api/concepts_controller.rb +52 -0
  12. data/app/controllers/supplejack_api/concerns/ignore_metrics.rb +14 -0
  13. data/app/controllers/supplejack_api/concerns/records_controller_metrics.rb +26 -0
  14. data/app/controllers/supplejack_api/concerns/set_items_controller_metrics.rb +20 -0
  15. data/app/controllers/supplejack_api/concerns/user_sets_controller_metrics.rb +26 -0
  16. data/app/controllers/supplejack_api/harvester/concepts_controller.rb +41 -0
  17. data/app/controllers/supplejack_api/harvester/fragments_controller.rb +30 -0
  18. data/app/controllers/supplejack_api/harvester/records_controller.rb +68 -0
  19. data/app/controllers/supplejack_api/metrics_api_controller.rb +44 -0
  20. data/app/controllers/supplejack_api/partners_controller.rb +39 -0
  21. data/app/controllers/supplejack_api/records_controller.rb +71 -0
  22. data/app/controllers/supplejack_api/schema_controller.rb +19 -0
  23. data/app/controllers/supplejack_api/set_items_controller.rb +38 -0
  24. data/app/controllers/supplejack_api/sources_controller.rb +67 -0
  25. data/app/controllers/supplejack_api/status_controller.rb +67 -0
  26. data/app/controllers/supplejack_api/user_sets_controller.rb +97 -0
  27. data/app/controllers/supplejack_api/users_controller.rb +49 -0
  28. data/app/helpers/supplejack_api/application_helper.rb +39 -0
  29. data/app/helpers/supplejack_api/facets_helper.rb +30 -0
  30. data/app/mailers/supplejack_api/request_limit_mailer.rb +43 -0
  31. data/app/models/supplejack_api/api_concept/concept_fragment.rb +31 -0
  32. data/app/models/supplejack_api/api_record/record_fragment.rb +15 -0
  33. data/app/models/supplejack_api/concept.rb +46 -0
  34. data/app/models/supplejack_api/concept_search.rb +86 -0
  35. data/app/models/supplejack_api/concerns/queryable_by_date.rb +31 -0
  36. data/app/models/supplejack_api/concerns/record.rb +108 -0
  37. data/app/models/supplejack_api/concerns/record_fragmentable.rb +24 -0
  38. data/app/models/supplejack_api/concerns/searchable.rb +430 -0
  39. data/app/models/supplejack_api/concerns/user_set.rb +219 -0
  40. data/app/models/supplejack_api/daily_metrics.rb +20 -0
  41. data/app/models/supplejack_api/faceted_metrics.rb +55 -0
  42. data/app/models/supplejack_api/fragment.rb +82 -0
  43. data/app/models/supplejack_api/interaction_models/record.rb +60 -0
  44. data/app/models/supplejack_api/interaction_models/set.rb +13 -0
  45. data/app/models/supplejack_api/interaction_updaters/all_usage_metric.rb +41 -0
  46. data/app/models/supplejack_api/interaction_updaters/set_metrics.rb +32 -0
  47. data/app/models/supplejack_api/interaction_updaters/usage_metrics.rb +73 -0
  48. data/app/models/supplejack_api/partner.rb +24 -0
  49. data/app/models/supplejack_api/preview_record.rb +30 -0
  50. data/app/models/supplejack_api/record.rb +18 -0
  51. data/app/models/supplejack_api/record_search.rb +12 -0
  52. data/app/models/supplejack_api/schema_definition.rb +147 -0
  53. data/app/models/supplejack_api/search.rb +14 -0
  54. data/app/models/supplejack_api/set_item.rb +57 -0
  55. data/app/models/supplejack_api/site_activity.rb +59 -0
  56. data/app/models/supplejack_api/source.rb +28 -0
  57. data/app/models/supplejack_api/source_activity.rb +36 -0
  58. data/app/models/supplejack_api/source_authority.rb +42 -0
  59. data/app/models/supplejack_api/supplejack_schema.rb +37 -0
  60. data/app/models/supplejack_api/support/concept/searchable.rb +79 -0
  61. data/app/models/supplejack_api/support/concept/storable.rb +59 -0
  62. data/app/models/supplejack_api/support/fragment_helpers.rb +96 -0
  63. data/app/models/supplejack_api/support/harvestable.rb +105 -0
  64. data/app/models/supplejack_api/support/searchable.rb +115 -0
  65. data/app/models/supplejack_api/support/status_logger.rb +23 -0
  66. data/app/models/supplejack_api/support/storable.rb +49 -0
  67. data/app/models/supplejack_api/support/validation_logger.rb +23 -0
  68. data/app/models/supplejack_api/usage_metrics.rb +25 -0
  69. data/app/models/supplejack_api/user.rb +211 -0
  70. data/app/models/supplejack_api/user_activity.rb +65 -0
  71. data/app/models/supplejack_api/user_set.rb +17 -0
  72. data/app/serializers/supplejack_api/application_serializer.rb +12 -0
  73. data/app/serializers/supplejack_api/concept_record_serializer.rb +23 -0
  74. data/app/serializers/supplejack_api/concept_search_serializer.rb +12 -0
  75. data/app/serializers/supplejack_api/concept_serializer.rb +100 -0
  76. data/app/serializers/supplejack_api/concerns/record_serializable.rb +119 -0
  77. data/app/serializers/supplejack_api/concerns/search_serializable.rb +24 -0
  78. data/app/serializers/supplejack_api/record_search_serializer.rb +64 -0
  79. data/app/serializers/supplejack_api/record_serializer.rb +13 -0
  80. data/app/serializers/supplejack_api/search_serializer.rb +13 -0
  81. data/app/serializers/supplejack_api/source_authority_serializer.rb +23 -0
  82. data/app/serializers/supplejack_api/user_serializer.rb +13 -0
  83. data/app/serializers/supplejack_api/user_set_record_serializer.rb +14 -0
  84. data/app/serializers/supplejack_api/user_set_serializer.rb +77 -0
  85. data/app/services/metrics_api/v3/api.rb +30 -0
  86. data/app/services/metrics_api/v3/endpoints/facets.rb +23 -0
  87. data/app/services/metrics_api/v3/endpoints/global.rb +26 -0
  88. data/app/services/metrics_api/v3/endpoints/helpers.rb +14 -0
  89. data/app/services/metrics_api/v3/endpoints/root.rb +97 -0
  90. data/app/services/metrics_api/v3/presenters/daily_metric.rb +23 -0
  91. data/app/services/metrics_api/v3/presenters/extended_metadata.rb +41 -0
  92. data/app/services/metrics_api/v3/presenters/record.rb +26 -0
  93. data/app/services/metrics_api/v3/presenters/view.rb +28 -0
  94. data/app/views/layouts/supplejack_api/_head.html.erb +17 -0
  95. data/app/views/layouts/supplejack_api/_top_nav.html.erb +33 -0
  96. data/app/views/layouts/supplejack_api/application.html.erb +33 -0
  97. data/app/views/supplejack_api/admin/sessions/new.html.erb +25 -0
  98. data/app/views/supplejack_api/admin/shared/_links.erb +34 -0
  99. data/app/views/supplejack_api/admin/site_activities/index.csv.erb +22 -0
  100. data/app/views/supplejack_api/admin/site_activities/index.html.erb +54 -0
  101. data/app/views/supplejack_api/admin/users/edit.html.erb +16 -0
  102. data/app/views/supplejack_api/admin/users/index.csv.erb +22 -0
  103. data/app/views/supplejack_api/admin/users/index.html.erb +57 -0
  104. data/app/views/supplejack_api/admin/users/show.html.erb +33 -0
  105. data/app/views/supplejack_api/request_limit_mailer/at100percent.text.erb +13 -0
  106. data/app/views/supplejack_api/request_limit_mailer/at100percent_admin.text.erb +1 -0
  107. data/app/views/supplejack_api/request_limit_mailer/at90percent.text.erb +13 -0
  108. data/app/views/supplejack_api/request_limit_mailer/at90percent_admin.text.erb +1 -0
  109. data/app/workers/supplejack_api/daily_metrics_worker.rb +112 -0
  110. data/app/workers/supplejack_api/interaction_metrics_worker.rb +45 -0
  111. data/config/cucumber.yml +16 -0
  112. data/config/initializers/force_eagerload.rb +11 -0
  113. data/config/initializers/interaction_updaters.rb +7 -0
  114. data/config/routes.rb +81 -0
  115. data/db/binding_records.development.json +7 -0
  116. data/db/binding_records.staging.json +7 -0
  117. data/db/concepts.json +33 -0
  118. data/db/concepts_data.json +569745 -0
  119. data/db/source_authorities.json +119 -0
  120. data/lib/generators/supplejack_api/install_generator.rb +124 -0
  121. data/lib/mongoid/paperclip.rb +53 -0
  122. data/lib/mongoid/string.rb +20 -0
  123. data/lib/sunspot/mongoid.rb +51 -0
  124. data/lib/sunspot/resque_session_proxy.rb +72 -0
  125. data/lib/sunspot/sunspot_spellcheck.rb +98 -0
  126. data/lib/supplejack_api.rb +13 -0
  127. data/lib/supplejack_api/admin/sortable.rb +40 -0
  128. data/lib/supplejack_api/engine.rb +46 -0
  129. data/lib/supplejack_api/harvester_constraint.rb +28 -0
  130. data/lib/supplejack_api/stylesheets.rb +36 -0
  131. data/lib/supplejack_api/utils.rb +75 -0
  132. data/lib/supplejack_api/version.rb +11 -0
  133. data/lib/tasks/resque.rake +22 -0
  134. data/lib/url_validator.rb +38 -0
  135. data/spec/controllers/supplejack_api/admin/base_controller_spec.rb +55 -0
  136. data/spec/controllers/supplejack_api/admin/site_activities_controller_spec.rb +43 -0
  137. data/spec/controllers/supplejack_api/admin/users_controller_spec.rb +79 -0
  138. data/spec/controllers/supplejack_api/application_controller_spec.rb +133 -0
  139. data/spec/controllers/supplejack_api/concepts_controller_spec.rb +83 -0
  140. data/spec/controllers/supplejack_api/concerns/records_controller_metrics_spec.rb +60 -0
  141. data/spec/controllers/supplejack_api/harvester/concepts_controller_spec.rb +57 -0
  142. data/spec/controllers/supplejack_api/harvester/records_controller_spec.rb +138 -0
  143. data/spec/controllers/supplejack_api/metrics_api_controller_spec.rb +71 -0
  144. data/spec/controllers/supplejack_api/partners_controller_spec.rb +77 -0
  145. data/spec/controllers/supplejack_api/records_controller_spec.rb +143 -0
  146. data/spec/controllers/supplejack_api/set_items_controller_spec.rb +78 -0
  147. data/spec/controllers/supplejack_api/sources_controller_spec.rb +161 -0
  148. data/spec/controllers/supplejack_api/status_controller_spec.rb +120 -0
  149. data/spec/controllers/supplejack_api/user_sets_controller_spec.rb +233 -0
  150. data/spec/controllers/supplejack_api/users_controller_spec.rb +61 -0
  151. data/spec/dummy/README.rdoc +268 -0
  152. data/spec/dummy/Rakefile +7 -0
  153. data/spec/dummy/app/assets/javascripts/application.js +23 -0
  154. data/spec/dummy/app/assets/stylesheets/application.css +22 -0
  155. data/spec/dummy/app/controllers/application_controller.rb +10 -0
  156. data/spec/dummy/app/helpers/application_helper.rb +9 -0
  157. data/spec/dummy/app/supplejack_api/concept_schema.rb +83 -0
  158. data/spec/dummy/app/supplejack_api/record_schema.rb +64 -0
  159. data/spec/dummy/app/supplejack_api/record_schema.txt +66 -0
  160. data/spec/dummy/app/views/layouts/application.html.erb +23 -0
  161. data/spec/dummy/app/workers/supplejack_api/clear_index_buffer.rb +32 -0
  162. data/spec/dummy/app/workers/supplejack_api/flush_old_records_worker.rb +17 -0
  163. data/spec/dummy/app/workers/supplejack_api/index_buffer.rb +49 -0
  164. data/spec/dummy/app/workers/supplejack_api/index_source_worker.rb +39 -0
  165. data/spec/dummy/app/workers/supplejack_api/index_worker.rb +82 -0
  166. data/spec/dummy/app/workers/supplejack_api/store_user_activity_worker.rb +24 -0
  167. data/spec/dummy/config.ru +11 -0
  168. data/spec/dummy/config/application.rb +76 -0
  169. data/spec/dummy/config/application.yml +31 -0
  170. data/spec/dummy/config/application.yml.example +36 -0
  171. data/spec/dummy/config/boot.rb +17 -0
  172. data/spec/dummy/config/environment.rb +12 -0
  173. data/spec/dummy/config/environments/development.rb +42 -0
  174. data/spec/dummy/config/environments/production.rb +76 -0
  175. data/spec/dummy/config/environments/test.rb +46 -0
  176. data/spec/dummy/config/initializers/backtrace_silencers.rb +14 -0
  177. data/spec/dummy/config/initializers/devise.rb +214 -0
  178. data/spec/dummy/config/initializers/inflections.rb +22 -0
  179. data/spec/dummy/config/initializers/kaminari_config.rb +17 -0
  180. data/spec/dummy/config/initializers/mime_types.rb +12 -0
  181. data/spec/dummy/config/initializers/mongoid.rb +9 -0
  182. data/spec/dummy/config/initializers/quiet_logger.rb +23 -0
  183. data/spec/dummy/config/initializers/resque.rb +18 -0
  184. data/spec/dummy/config/initializers/secret_token.rb +14 -0
  185. data/spec/dummy/config/initializers/session_store.rb +15 -0
  186. data/spec/dummy/config/initializers/simple_form.rb +149 -0
  187. data/spec/dummy/config/initializers/simple_form_foundation.rb +33 -0
  188. data/spec/dummy/config/initializers/state_machine.rb +8 -0
  189. data/spec/dummy/config/initializers/sunspot.rb +32 -0
  190. data/spec/dummy/config/initializers/wrap_parameters.rb +17 -0
  191. data/spec/dummy/config/locales/devise.en.yml +63 -0
  192. data/spec/dummy/config/locales/en.yml +66 -0
  193. data/spec/dummy/config/locales/simple_form.en.yml +33 -0
  194. data/spec/dummy/config/mongoid.travis.yml +34 -0
  195. data/spec/dummy/config/mongoid.yml +36 -0
  196. data/spec/dummy/config/resque-pool.yml +18 -0
  197. data/spec/dummy/config/resque_schedule.yml +12 -0
  198. data/spec/dummy/config/routes.rb +11 -0
  199. data/spec/dummy/config/secrets.yml +8 -0
  200. data/spec/dummy/config/sunspot.yml +38 -0
  201. data/spec/dummy/db/concepts.json +37 -0
  202. data/spec/dummy/db/source_authorities.json +116 -0
  203. data/spec/dummy/public/404.html +35 -0
  204. data/spec/dummy/public/422.html +35 -0
  205. data/spec/dummy/public/500.html +34 -0
  206. data/spec/dummy/public/favicon.ico +8 -0
  207. data/spec/dummy/script/rails +6 -0
  208. data/spec/dummy/solr/collection1/conf/elevate.xml +36 -0
  209. data/spec/dummy/solr/collection1/conf/mapping-FoldToASCII.txt +3813 -0
  210. data/spec/dummy/solr/collection1/conf/schema.xml +292 -0
  211. data/spec/dummy/solr/collection1/conf/solrconfig.xml +1780 -0
  212. data/spec/dummy/solr/collection1/conf/spellings.txt +2 -0
  213. data/spec/dummy/solr/collection1/conf/stopwords.txt +58 -0
  214. data/spec/dummy/solr/collection1/conf/synonyms.txt +31 -0
  215. data/spec/factories/concepts.rb +16 -0
  216. data/spec/factories/daily_metrics.rb +10 -0
  217. data/spec/factories/faceted_metrics.rb +25 -0
  218. data/spec/factories/fragment.rb +14 -0
  219. data/spec/factories/partners.rb +14 -0
  220. data/spec/factories/record_interaction.rb +17 -0
  221. data/spec/factories/records.rb +47 -0
  222. data/spec/factories/set_interaction.rb +15 -0
  223. data/spec/factories/source_authorities.rb +19 -0
  224. data/spec/factories/sources.rb +16 -0
  225. data/spec/factories/usage_metrics.rb +20 -0
  226. data/spec/factories/user_activities.rb +14 -0
  227. data/spec/factories/user_sets.rb +27 -0
  228. data/spec/factories/users.rb +23 -0
  229. data/spec/helpers/application_helper_spec.rb +35 -0
  230. data/spec/models/concept_spec.rb +163 -0
  231. data/spec/models/fragment_spec.rb +15 -0
  232. data/spec/models/site_activity_spec.rb +103 -0
  233. data/spec/models/source_activity_spec.rb +56 -0
  234. data/spec/models/source_authority_spec.rb +42 -0
  235. data/spec/models/supplejack_api/api_concept/concept_fragment_spec.rb +189 -0
  236. data/spec/models/supplejack_api/api_record/record_fragment_spec.rb +189 -0
  237. data/spec/models/supplejack_api/concept_spec.rb +0 -0
  238. data/spec/models/supplejack_api/interaction_updaters/all_usage_metric_spec.rb +41 -0
  239. data/spec/models/supplejack_api/interaction_updaters/set_metrics_spec.rb +32 -0
  240. data/spec/models/supplejack_api/interaction_updaters/usage_metrics_spec.rb +44 -0
  241. data/spec/models/supplejack_api/partner_spec.rb +20 -0
  242. data/spec/models/supplejack_api/record_search_spec.rb +429 -0
  243. data/spec/models/supplejack_api/record_spec.rb +280 -0
  244. data/spec/models/supplejack_api/schema_definition_spec.rb +272 -0
  245. data/spec/models/supplejack_api/search_spec.rb +243 -0
  246. data/spec/models/supplejack_api/set_item_spec.rb +123 -0
  247. data/spec/models/supplejack_api/source_spec.rb +22 -0
  248. data/spec/models/supplejack_api/support/fragment_helpers_spec.rb +216 -0
  249. data/spec/models/supplejack_api/support/harvestable_spec.rb +177 -0
  250. data/spec/models/supplejack_api/support/searchable_spec.rb +85 -0
  251. data/spec/models/supplejack_api/support/storable_spec.rb +18 -0
  252. data/spec/models/supplejack_api/user_activity_spec.rb +88 -0
  253. data/spec/models/supplejack_api/user_set_spec.rb +559 -0
  254. data/spec/models/supplejack_api/user_spec.rb +348 -0
  255. data/spec/routing/concepts_routing_spec.rb +22 -0
  256. data/spec/routing/harvester_constraint_spec.rb +66 -0
  257. data/spec/routing/harvester_routing_spec.rb +51 -0
  258. data/spec/routing/partner_routing_spec.rb +34 -0
  259. data/spec/routing/records_routing_spec.rb +26 -0
  260. data/spec/routing/sets_routing_spec.rb +44 -0
  261. data/spec/routing/source_routing_spec.rb +38 -0
  262. data/spec/routing/users_routing_spec.rb +18 -0
  263. data/spec/serializers/concept_record_serializer_spec.rb +30 -0
  264. data/spec/serializers/supplejack_api/application_serializer_spec.rb +14 -0
  265. data/spec/serializers/supplejack_api/concept_search_serializer_spec.rb +34 -0
  266. data/spec/serializers/supplejack_api/concept_serializer_spec.rb +92 -0
  267. data/spec/serializers/supplejack_api/record_search_serializer_spec.rb +34 -0
  268. data/spec/serializers/supplejack_api/record_serializer_spec.rb +271 -0
  269. data/spec/serializers/supplejack_api/search_serializer_spec.rb +90 -0
  270. data/spec/serializers/supplejack_api/user_serializer_spec.rb +22 -0
  271. data/spec/serializers/supplejack_api/user_set_serializer_spec.rb +96 -0
  272. data/spec/services/metrics_api/v3/api_spec.rb +23 -0
  273. data/spec/services/metrics_api/v3/endpoints/facets_spec.rb +25 -0
  274. data/spec/services/metrics_api/v3/endpoints/global_spec.rb +22 -0
  275. data/spec/services/metrics_api/v3/endpoints/root_spec.rb +49 -0
  276. data/spec/services/metrics_api/v3/presenters/extended_metadata_spec.rb +49 -0
  277. data/spec/spec_helper.rb +70 -0
  278. data/spec/support/api/schemas/metrics/daily_metrics_metadata.json +24 -0
  279. data/spec/support/api/schemas/metrics/extended_metrics.json +26 -0
  280. data/spec/support/api/schemas/metrics/extended_response.json +6 -0
  281. data/spec/support/api/schemas/metrics/facets_response.json +6 -0
  282. data/spec/support/api/schemas/metrics/record_metadata.json +33 -0
  283. data/spec/support/api/schemas/metrics/top_level_response.json +6 -0
  284. data/spec/support/api/schemas/metrics/view_metadata.json +33 -0
  285. data/spec/support/api_schema_matcher.rb +14 -0
  286. data/spec/support/devise.rb +10 -0
  287. data/spec/workers/supplejack_api/daily_metrics_worker_spec.rb +140 -0
  288. data/spec/workers/supplejack_api/flush_old_records_worker_spec.rb +20 -0
  289. data/spec/workers/supplejack_api/index_worker_spec.rb +52 -0
  290. data/spec/workers/supplejack_api/source_index_worker_spec.rb +61 -0
  291. metadata +1231 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ # The majority of the Supplejack API code is Crown copyright (C) 2014, New Zealand Government,
3
+ # and is licensed under the GNU General Public License, version 3.
4
+ # One component is a third party component. See https://github.com/DigitalNZ/supplejack_api for details.
5
+ #
6
+ # Supplejack was created by DigitalNZ at the National Library of NZ and
7
+ # the Department of Internal Affairs. http://digitalnz.org/supplejack
8
+
9
+ module SupplejackApi
10
+ class Concept
11
+ include Support::Concept::Storable
12
+ include Support::Concept::Searchable
13
+ include ActiveModel::SerializerSupport
14
+
15
+ def self.build_context(fields)
16
+ context = {}
17
+ namespaces = []
18
+
19
+ fields.each do |field|
20
+ namespaces << ConceptSchema.model_fields[field].try(:namespace)
21
+ end
22
+
23
+ namespaces.compact.uniq.each do |namespace|
24
+ context[namespace] = ConceptSchema.namespaces[namespace].url
25
+ namespaced_fields = ConceptSchema.fields.select { |_key, field| field.namespace == namespace }
26
+ namespaced_fields.each do |name, field|
27
+ if fields.include?(name) && name != field.namespace
28
+ context[name] = "#{field.namespace}:#{field.namespace_field}"
29
+ end
30
+ end
31
+ end
32
+
33
+ # Manually build context for concept_id
34
+ context[:dcterms] = ConceptSchema.namespaces[:dcterms].url
35
+ context[:concept_id] = {}
36
+ context[:concept_id]['@id'] = 'dcterms:identifier'
37
+
38
+ fields.each do |field|
39
+ context[field] = {}
40
+ namespace = ConceptSchema.model_fields[field].try(:namespace)
41
+ context[field]['@id'] = "#{namespace}:#{field}"
42
+ end
43
+ context
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+ # The majority of the Supplejack API code is Crown copyright (C) 2014, New Zealand Government,
3
+ # and is licensed under the GNU General Public License, version 3.
4
+ # One component is a third party component. See https://github.com/DigitalNZ/supplejack_api for details.
5
+ #
6
+ # Supplejack was created by DigitalNZ at the National Library of NZ and
7
+ # the Department of Internal Affairs. http://digitalnz.org/supplejack
8
+
9
+ module SupplejackApi
10
+ class ConceptSearch < Search
11
+ def initialize(options = {})
12
+ @options = options.dup
13
+ @options.reverse_merge!(
14
+ and: {},
15
+ or: {},
16
+ without: {},
17
+ page: 1,
18
+ per_page: 20,
19
+ sort: nil,
20
+ direction: 'desc',
21
+ fields: ConceptSchema.model_fields.keys.join(','),
22
+ debug: nil
23
+ )
24
+ end
25
+
26
+ # FIXME: make me smaller!
27
+ def search_builder
28
+ @search_builder ||= Sunspot.new_search(SupplejackApi::Concept) do
29
+ spellcheck collate: true, only_more_popular: true if options[:suggest]
30
+
31
+ options[:without].each do |name, values|
32
+ values = values.split(',')
33
+ values.each do |value|
34
+ without(name, to_proper_value(name, value))
35
+ end
36
+ end
37
+
38
+ if options[:geo_bbox]
39
+ coords = options[:geo_bbox].split(',').map(&:to_f)
40
+ with(:lat_lng).in_bounding_box([coords[2], coords[1]], [coords[0], coords[3]])
41
+ end
42
+
43
+ adjust_solr_params do |params|
44
+ if options[:solr_query].present?
45
+ params[:q] ||= ''
46
+ params['q.alt'] = options[:solr_query]
47
+ params[:defType] = 'dismax'
48
+ end
49
+ end
50
+
51
+ order_by(sort, direction) if options[:sort].present?
52
+
53
+ paginate page: page, per_page: per_page
54
+ end
55
+
56
+ @search_builder.build(&build_conditions)
57
+ @search_builder
58
+ end
59
+ # rubocop:enable Metrics/MethodLength
60
+
61
+ def query_fields
62
+ query_fields_list = super
63
+ query_fields_list += [:name, :label] if (query_fields_list && [:name, :label]).present?
64
+ end
65
+
66
+ def field_list
67
+ return @field_list if @field_list
68
+ model_fields = ConceptSchema.model_fields.dup
69
+ valid_fields = model_fields.keep_if { |_key, field| field.try(:store).nil? }
70
+
71
+ @field_list = options[:fields].split(',').map { |f| f.strip.tr(':', '_').to_sym }
72
+ @field_list.keep_if do |f|
73
+ valid_fields.include?(f)
74
+ end
75
+
76
+ @field_list
77
+ end
78
+
79
+ def group_list
80
+ return @group_list if @group_list
81
+ @group_list = options[:fields].split(',').map { |f| f.strip.to_sym }
82
+ @group_list.keep_if { |f| ConceptSchema.groups.keys.include?(f) }
83
+ @group_list
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ # The majority of the Supplejack API code is Crown copyright (C) 2014, New Zealand Government,
3
+ # and is licensed under the GNU General Public License, version 3.
4
+ # One component is a third party component. See https://github.com/DigitalNZ/supplejack_api for details.
5
+ #
6
+ # Supplejack was created by DigitalNZ at the National Library of NZ and
7
+ # the Department of Internal Affairs. http://digitalnz.org/supplejack
8
+
9
+ module SupplejackApi
10
+ module Concerns
11
+ # Contains convenience methods for querying documents by their day field
12
+ # This concern assumes documents have the field 'day' of type +Date+
13
+ module QueryableByDate
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ index date: 1
18
+ end
19
+
20
+ module ClassMethods
21
+ def created_on(date)
22
+ where(:date.gte => date.at_beginning_of_day, :date.lte => date.at_end_of_day)
23
+ end
24
+
25
+ def created_between(start_date, end_date)
26
+ where(:date.gte => start_date, :date.lte => end_date)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+ module SupplejackApi::Concerns::Record
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_accessor :next_record, :previous_record, :next_page, :previous_page
7
+ attr_accessor :should_index_flag
8
+
9
+ # Associations
10
+ embeds_many :fragments, cascade_callbacks: true, class_name: 'SupplejackApi::ApiRecord::RecordFragment'
11
+ embeds_one :merged_fragment, class_name: 'SupplejackApi::ApiRecord::RecordFragment'
12
+ has_and_belongs_to_many :concepts, class_name: 'SupplejackApi::Concept'
13
+
14
+ # From storable
15
+ store_in collection: 'records'
16
+ index(concept_ids: 1)
17
+ index({ record_id: 1 }, unique: true)
18
+ auto_increment :record_id, session: 'strong'
19
+
20
+ # Callbacks
21
+ before_save :merge_fragments
22
+
23
+ # Scopes
24
+ scope :active, -> { where(status: 'active') }
25
+ scope :deleted, -> { where(status: 'deleted') }
26
+ scope :suppressed, -> { where(status: 'suppressed') }
27
+ scope :solr_rejected, -> { where(status: 'solr_rejected') }
28
+
29
+ build_model_fields
30
+
31
+ def self.created_on(date)
32
+ where(:created_at.gte => date.at_beginning_of_day, :created_at.lte => date.at_end_of_day)
33
+ end
34
+
35
+ def self.find_multiple(ids)
36
+ return [] unless ids.try(:any?)
37
+ string_ids = ids.find_all { |id| id.to_s.length > 10 }
38
+ integer_ids = ids.find_all { |id| id.to_s.length <= 10 }
39
+
40
+ records = []
41
+ records += active.find(string_ids) if string_ids.present?
42
+ records += active.where(:record_id.in => integer_ids)
43
+ records = records.sort_by { |r| ids.find_index(r.record_id) || 100 }
44
+ end
45
+
46
+ # rubocop:disable Metrics/MethodLength
47
+ # FIXME: make me smaller!
48
+ def find_next_and_previous_records(scope, options = {})
49
+ return unless options.try(:any?)
50
+
51
+ search = ::SupplejackApi::RecordSearch.new(options)
52
+ search.scope = scope
53
+
54
+ return nil unless search.valid?
55
+
56
+ # Find the index in the array for the current record
57
+ record_index = search.hits.find_index { |i| i.primary_key == id.to_s }
58
+ total_pages = (search.total.to_f / search.per_page).ceil
59
+
60
+ self.next_page = search.page
61
+ self.previous_page = search.page
62
+
63
+ return unless record_index
64
+
65
+ if record_index.zero?
66
+ unless search.page == 1
67
+ previous_page_search = ::SupplejackApi::RecordSearch.new(options.merge(page: search.page - 1))
68
+ previous_primary_key = previous_page_search.hits[-1].try(:primary_key)
69
+ self.previous_page = search.page - 1
70
+ end
71
+ else
72
+ previous_primary_key = search.hits[record_index - 1].try(:primary_key)
73
+ end
74
+
75
+ if previous_primary_key.present?
76
+ self.previous_record = SupplejackApi::Record.find(previous_primary_key).try(:record_id) rescue nil
77
+ end
78
+
79
+ if record_index == search.hits.size - 1
80
+ unless search.page >= total_pages
81
+ next_page_search = ::SupplejackApi::RecordSearch.new(options.merge(page: search.page + 1))
82
+ next_primary_key = next_page_search.hits[0].try(:primary_key)
83
+ self.next_page = search.page + 1
84
+ end
85
+ else
86
+ next_primary_key = search.hits[record_index + 1].try(:primary_key)
87
+ end
88
+
89
+ return unless next_primary_key.present?
90
+
91
+ self.next_record = ::SupplejackApi::Record.find(next_primary_key).try(:record_id) rescue nil
92
+ end
93
+ # rubocop:enable Metrics/MethodLength
94
+
95
+ def active?
96
+ status == 'active'
97
+ end
98
+
99
+ def should_index?
100
+ return should_index_flag unless should_index_flag.nil?
101
+ active?
102
+ end
103
+
104
+ def fragment_class
105
+ SupplejackApi::ApiRecord::RecordFragment
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ module SupplejackApi::Concerns::RecordFragmentable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ embedded_in :record
7
+
8
+ delegate :record_id, to: :record
9
+
10
+ def self.schema_class
11
+ RecordSchema
12
+ end
13
+
14
+ build_mongoid_schema
15
+
16
+ def self.mutable_fields
17
+ @@mutable_fields ||= begin
18
+ immutable_fields = %w(_id _type source_id created_at updated_at)
19
+ mutable_fields = fields.keys - immutable_fields
20
+ Hash[mutable_fields.map { |name| [name, fields[name].type] }]
21
+ end.freeze
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,430 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/ModuleLength
3
+ module SupplejackApi::Concerns::Searchable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ INTEGER_ATTRIBUTES ||= [:page, :per_page, :facets_per_page, :facets_page, :record_type].freeze
8
+
9
+ attr_accessor :options, :request_url, :scope, :solr_request_params, :errors, :warnings
10
+
11
+ class_attribute :max_values
12
+
13
+ self.max_values = {
14
+ page: 100_000,
15
+ per_page: 100,
16
+ facets_per_page: 150,
17
+ facets_page: 5000
18
+ }
19
+
20
+ def initialize(options = {})
21
+ @options = options.dup
22
+ @options.reverse_merge!(
23
+ facets: '',
24
+ and: {},
25
+ or: {},
26
+ without: {},
27
+ page: 1,
28
+ per_page: 20,
29
+ record_type: 0,
30
+ facets_per_page: 10,
31
+ facets_page: 1,
32
+ sort: nil,
33
+ direction: 'desc',
34
+ fields: 'default',
35
+ facet_query: {},
36
+ debug: nil
37
+ )
38
+ end
39
+
40
+ def self.model_class
41
+ to_s.gsub(/Search/, '').constantize
42
+ end
43
+
44
+ def self.schema_class
45
+ "#{model_class.to_s.demodulize}Schema".constantize
46
+ end
47
+
48
+ # The records that match the criteria within each role will be removed
49
+ # from the search results
50
+ #
51
+ def self.role_collection_restrictions(scope)
52
+ restrictions = []
53
+
54
+ if scope
55
+ role = scope.role.try(:to_sym)
56
+ if schema_class.roles[role].record_restrictions
57
+ restrictions = schema_class.roles[role].record_restrictions
58
+ end
59
+ end
60
+
61
+ restrictions
62
+ end
63
+
64
+ # rubocop:disable Metrics/MethodLength
65
+ # rubocop:disable Metrics/AbcSize
66
+ # FIXME: Make this method smaller, it's triple the max method length
67
+ def search_builder
68
+ search_model = self
69
+
70
+ @search_builder ||= Sunspot.new_search(search_model.class.model_class) do
71
+ with(:record_type, record_type) unless options[:record_type] == 'all'
72
+
73
+ search_model.facet_list.each do |facet_name|
74
+ facet(facet_name, limit: facets_per_page, offset: facets_offset)
75
+ end
76
+
77
+ spellcheck collate: true, only_more_popular: true if options[:suggest]
78
+
79
+ options[:without].each do |name, values|
80
+ values = values.split(',')
81
+ values.each do |value|
82
+ without(name, to_proper_value(name, value))
83
+ end
84
+ end
85
+
86
+ if options[:geo_bbox]
87
+ coords = options[:geo_bbox].split(',').map(&:to_f)
88
+ with(:lat_lng).in_bounding_box([coords[2], coords[1]], [coords[0], coords[3]])
89
+ end
90
+
91
+ adjust_solr_params do |params|
92
+ if options[:solr_query].present?
93
+ params[:q] ||= ''
94
+ params['q.alt'] = options[:solr_query]
95
+ params[:defType] = 'dismax'
96
+ end
97
+ end
98
+
99
+ # Facet Queries
100
+ #
101
+ # The facet query parameter should have the following format:
102
+ #
103
+ # facet_query: {images: {"creator" => "all"}, headings: {"record_type" => 1}}
104
+ #
105
+ # - Each key in the top level hash will be the name of each facet row returned.
106
+ # - Each value in the top level hash is a hash similar with all the restrictions
107
+ #
108
+
109
+ if options[:facet_query].any?
110
+ facet(:counts) do
111
+ options[:facet_query].each_pair do |row_name, filters_hash|
112
+ row(row_name.to_s) do
113
+ filters_hash.each_pair do |filter, value|
114
+ if value == 'all'
115
+ without(filter.to_sym, nil)
116
+ elsif filter =~ /-(.+)/
117
+ without(Regexp.last_match(1).to_sym, to_proper_value(filter, value))
118
+ else
119
+ if value.is_a?(Array)
120
+ with(filter.to_sym).all_of(value)
121
+ else
122
+ with(filter.to_sym, to_proper_value(filter, value))
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ order_by(sort, direction) if options[:sort].present?
132
+
133
+ search_model.class.role_collection_restrictions(search_model.scope).each do |field, values|
134
+ without(field.to_sym, values)
135
+ end
136
+
137
+ SupplejackApi::Source.suppressed.each do |source|
138
+ without(:source_id, source.source_id)
139
+ end
140
+
141
+ paginate page: page, per_page: per_page
142
+ end
143
+
144
+ @search_builder.build(&build_conditions)
145
+
146
+ @search_builder
147
+ end
148
+ # rubocop:enable Metrics/AbcSize
149
+ # rubocop:enable Metrics/MethodLength
150
+
151
+ # Return an array of valid facets
152
+ # It will remove any invalid facets in order to avoid Solr errors
153
+ #
154
+ def facet_list
155
+ return @facet_list if @facet_list
156
+
157
+ @facet_list = options[:facets].split(',').map { |f| f.strip.to_sym }
158
+ @facet_list.keep_if { |f| self.class.model_class.valid_facets.include?(f) }
159
+ @facet_list
160
+ end
161
+
162
+ def field_list
163
+ return @field_list if @field_list
164
+ valid_fields = self.class.schema_class.fields.keys.dup
165
+
166
+ @field_list = options[:fields].split(',').map { |f| f.strip.tr(':', '_').to_sym }
167
+ @field_list.delete_if do |f|
168
+ !valid_fields.include?(f)
169
+ end
170
+
171
+ @field_list
172
+ end
173
+
174
+ # Returns the facets part of the search results converted to a hash
175
+ #
176
+ # @return [Hash] facets in hash form, each facet is respresented as +facet_name => facet_values+
177
+ def facets_hash
178
+ facets = {}
179
+
180
+ self.facets.each do |facet|
181
+ rows = {}
182
+ facet.rows.each do |row|
183
+ rows[row.value] = row.count
184
+ end
185
+
186
+ facets.merge!(facet.name => rows)
187
+ end
188
+
189
+ facets
190
+ end
191
+
192
+ # Returns all valid groups of fields
193
+ # The groups are extracted from the "fields" parameter
194
+ #
195
+ def group_list
196
+ return @group_list if @group_list
197
+ @group_list = options[:fields].split(',').map { |f| f.strip.to_sym }
198
+ @group_list.keep_if { |f| self.class.model_class.valid_groups.include?(f) }
199
+ @group_list
200
+ end
201
+
202
+ def query_fields
203
+ query_field_list = nil
204
+
205
+ if options[:query_fields].is_a?(String)
206
+ query_field_list = options[:query_fields].split(',').map(&:strip).map(&:to_sym)
207
+ elsif options[:query_fields].is_a?(Array)
208
+ query_field_list = options[:query_fields].map(&:to_sym)
209
+ end
210
+
211
+ return nil if query_field_list.try(:empty?)
212
+ query_field_list
213
+ end
214
+
215
+ def extract_range(value)
216
+ if value =~ /^\[(\d+)\sTO\s(\d+)\]$/
217
+ Regexp.last_match(1).to_i..Regexp.last_match(2).to_i
218
+ else
219
+ value.to_i.positive? ? value.to_i : value.strip
220
+ end
221
+ end
222
+
223
+ def to_proper_value(_name, value)
224
+ return false if value == 'false'
225
+ return true if value == 'true'
226
+ return nil if %w(nil null).include?(value)
227
+
228
+ value = value.strip if value.is_a?(String)
229
+ value
230
+ end
231
+
232
+ # Downcase all queries before sending to SOLR, except queries
233
+ # which have specific lucene syntax.
234
+ #
235
+ def text
236
+ @text = options[:text]
237
+ if @text.present? && !@text.match(/:\"/)
238
+ @text.downcase!
239
+ @text.gsub!(/ and | or | not /, &:upcase)
240
+ end
241
+ @text
242
+ end
243
+
244
+ def offset
245
+ (page * per_page) - per_page
246
+ end
247
+
248
+ def facets_offset
249
+ offset = (facets_page * facets_per_page) - facets_per_page
250
+ offset.negative? ? 0 : offset
251
+ end
252
+
253
+ def valid?
254
+ self.errors ||= []
255
+ self.warnings ||= []
256
+ self.class.max_values.each do |attribute, max_value|
257
+ max_value = self.class.max_values[attribute]
258
+ if @options[attribute].to_i > max_value
259
+ self.warnings << "The #{attribute} parameter can not exceed #{max_value}"
260
+ end
261
+ end
262
+
263
+ solr_search_object
264
+ self.errors.empty?
265
+ end
266
+
267
+ def records
268
+ solr_search_object.results
269
+ end
270
+
271
+ def jsonp
272
+ @options[:jsonp].present? ? @options[:jsonp] : nil
273
+ end
274
+
275
+ # It's currently required to make the active_model_serializers gem to work with XML
276
+ # The XML Serialization is handled by the respective serializer
277
+ #
278
+ def to_xml; end
279
+
280
+ # IMPORTANT !!!!
281
+ #
282
+ # Try to make this a bit prettier
283
+ #
284
+ INTEGER_ATTRIBUTES.each do |method|
285
+ define_method(method) do
286
+ value = @options[:"#{method.to_sym}"].to_i
287
+ value = [value, self.class.max_values[method]].min if self.class.max_values[method]
288
+ value
289
+ end
290
+ end
291
+
292
+ def sort
293
+ value = @options[:sort].to_sym
294
+
295
+ begin
296
+ field = Sunspot::Setup.for(self.class.model_class).field(value)
297
+ return value
298
+ rescue Sunspot::UnrecognizedFieldError => e
299
+ return 'score'
300
+ end
301
+ end
302
+
303
+ def direction
304
+ if %w(asc desc).include?(@options[:direction])
305
+ @options[:direction].to_sym
306
+ else
307
+ :desc
308
+ end
309
+ end
310
+
311
+ def solr_search_object
312
+ return @solr_search_object if @solr_search_object
313
+ @solr_search_object = execute_solr_search
314
+
315
+ if options[:debug] == 'true' && @solr_search_object.respond_to?(:query)
316
+ self.solr_request_params = @solr_search_object.query.to_params
317
+ end
318
+
319
+ @solr_search_object
320
+ end
321
+
322
+ def solr_error_message(exception)
323
+ {
324
+ title: "#{exception.response[:status]} #{RSolr::Error::Http::STATUS_CODES[exception.response[:status].to_i]}",
325
+ body: exception.send(:parse_solr_error_response, exception.response[:body])
326
+ }
327
+ end
328
+
329
+ # rubocop:disable Style/MethodMissing
330
+ def method_missing(symbol, *args)
331
+ return nil unless solr_search_object.respond_to?(:hits)
332
+ solr_search_object.send(symbol, *args)
333
+ end
334
+ # rubocop:enable Style/MethodMissing
335
+
336
+ def execute_solr_search
337
+ search = search_builder
338
+
339
+ search.build do
340
+ keywords text, fields: query_fields
341
+ end
342
+
343
+ execute_solr_search_and_handle_errors(search)
344
+ end
345
+
346
+ def execute_solr_search_and_handle_errors(search)
347
+ self.errors ||= []
348
+ sunspot = search.execute
349
+ rescue RSolr::Error::Http => e
350
+ self.errors << solr_error_message(e)
351
+ Rails.logger.info e.message
352
+ sunspot = {}
353
+ rescue Timeout::Error, Errno::ECONNREFUSED, Errno::ECONNRESET => e
354
+ self.errors << 'Solr is temporarily unavailable please try again in a few seconds.'
355
+ Rails.logger.info e.message
356
+ sunspot = {}
357
+ ensure
358
+ return sunspot
359
+ end
360
+
361
+ private
362
+
363
+ # Generates the :and and :or conditions for a search object
364
+ #
365
+ def build_conditions
366
+ proc do
367
+ { and: options[:and], or: options[:or] }.each do |operator, value|
368
+ Utils.call_block(self, &recurse_conditions(operator, value))
369
+ end
370
+ end
371
+ end
372
+
373
+ # Detects when the key is a operator (:and, :or) and calls itself
374
+ # recursively until it finds facets defined in Sunspot.
375
+ #
376
+ def recurse_conditions(key, conditions, current_operator = :and)
377
+ proc do
378
+ case key.to_sym
379
+ when :and
380
+ all_of do
381
+ conditions.each do |filter, value|
382
+ Utils.call_block(self, &recurse_conditions(filter, value, :and))
383
+ end
384
+ end
385
+ when :or
386
+ any_of do
387
+ conditions.each do |filter, value|
388
+ Utils.call_block(self, &recurse_conditions(filter, value, :or))
389
+ end
390
+ end
391
+ else
392
+ Utils.call_block(self, &filter_values(key, conditions, current_operator))
393
+ end
394
+ end
395
+ end
396
+
397
+ # Generates a single condition. It can take a operator to
398
+ # determine how the values within the filter are going to be
399
+ # joined.
400
+ #
401
+ def filter_values(key, conditions, current_operator = :and)
402
+ proc do
403
+ if conditions.is_a? Hash
404
+ operator, values = conditions.first
405
+ else
406
+ operator = current_operator
407
+ values = conditions
408
+ end
409
+
410
+ if values.is_a?(Array)
411
+ case operator.to_sym
412
+ when :or
413
+ with(key).any_of(values)
414
+ when :and
415
+ with(key).all_of(values)
416
+ else
417
+ raise Exception, 'Expected operator (:and, :or)'
418
+ end
419
+ else
420
+ if values =~ /(.+)\*$/
421
+ with(key).starting_with(Regexp.last_match(1))
422
+ else
423
+ with(key, to_proper_value(key, values))
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end
429
+ end
430
+ # rubocop:enable Metrics/ModuleLength