supplejack_api 1

Sign up to get free protection for your applications and to get access to all the features.
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