landable 1.7.0

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 (310) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +15 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +56 -0
  7. data/Rakefile +22 -0
  8. data/app/controllers/concerns/landable/variables_concern.rb +38 -0
  9. data/app/controllers/landable/api/access_tokens_controller.rb +48 -0
  10. data/app/controllers/landable/api/assets_controller.rb +55 -0
  11. data/app/controllers/landable/api/categories_controller.rb +13 -0
  12. data/app/controllers/landable/api/directories_controller.rb +19 -0
  13. data/app/controllers/landable/api/page_revisions_controller.rb +38 -0
  14. data/app/controllers/landable/api/pages_controller.rb +96 -0
  15. data/app/controllers/landable/api/templates_controller.rb +33 -0
  16. data/app/controllers/landable/api/themes_controller.rb +57 -0
  17. data/app/controllers/landable/api_controller.rb +75 -0
  18. data/app/controllers/landable/application_controller.rb +4 -0
  19. data/app/controllers/landable/public/pages_controller.rb +25 -0
  20. data/app/controllers/landable/public/preview/page_revisions_controller.rb +16 -0
  21. data/app/controllers/landable/public/preview/pages_controller.rb +16 -0
  22. data/app/controllers/landable/public/sitemap_controller.rb +18 -0
  23. data/app/decorators/landable/null_page_decorator.rb +26 -0
  24. data/app/decorators/landable/page_decorator.rb +40 -0
  25. data/app/helpers/landable/application_helper.rb +13 -0
  26. data/app/helpers/landable/pages_helper.rb +20 -0
  27. data/app/models/concerns/landable/has_assets.rb +74 -0
  28. data/app/models/concerns/landable/table_name.rb +12 -0
  29. data/app/models/concerns/landable/traffic/table_name.rb +14 -0
  30. data/app/models/landable/access_token.rb +20 -0
  31. data/app/models/landable/asset.rb +63 -0
  32. data/app/models/landable/asset_search_engine.rb +20 -0
  33. data/app/models/landable/author.rb +12 -0
  34. data/app/models/landable/category.rb +27 -0
  35. data/app/models/landable/directory.rb +23 -0
  36. data/app/models/landable/page.rb +259 -0
  37. data/app/models/landable/page_revision.rb +74 -0
  38. data/app/models/landable/page_search_engine.rb +20 -0
  39. data/app/models/landable/search_engine.rb +39 -0
  40. data/app/models/landable/template.rb +24 -0
  41. data/app/models/landable/theme.rb +20 -0
  42. data/app/models/landable/traffic/access.rb +11 -0
  43. data/app/models/landable/traffic/ad_group.rb +11 -0
  44. data/app/models/landable/traffic/ad_type.rb +11 -0
  45. data/app/models/landable/traffic/attribution.rb +38 -0
  46. data/app/models/landable/traffic/bid_match_type.rb +11 -0
  47. data/app/models/landable/traffic/browser.rb +11 -0
  48. data/app/models/landable/traffic/campaign.rb +11 -0
  49. data/app/models/landable/traffic/city.rb +11 -0
  50. data/app/models/landable/traffic/content.rb +11 -0
  51. data/app/models/landable/traffic/cookie.rb +16 -0
  52. data/app/models/landable/traffic/country.rb +11 -0
  53. data/app/models/landable/traffic/creative.rb +11 -0
  54. data/app/models/landable/traffic/device.rb +11 -0
  55. data/app/models/landable/traffic/device_type.rb +11 -0
  56. data/app/models/landable/traffic/domain.rb +12 -0
  57. data/app/models/landable/traffic/event.rb +12 -0
  58. data/app/models/landable/traffic/event_type.rb +11 -0
  59. data/app/models/landable/traffic/experiment.rb +11 -0
  60. data/app/models/landable/traffic/http_method.rb +11 -0
  61. data/app/models/landable/traffic/ip_address.rb +11 -0
  62. data/app/models/landable/traffic/ip_lookup.rb +12 -0
  63. data/app/models/landable/traffic/keyword.rb +11 -0
  64. data/app/models/landable/traffic/location.rb +11 -0
  65. data/app/models/landable/traffic/match_type.rb +11 -0
  66. data/app/models/landable/traffic/medium.rb +11 -0
  67. data/app/models/landable/traffic/mime_type.rb +11 -0
  68. data/app/models/landable/traffic/network.rb +11 -0
  69. data/app/models/landable/traffic/owner.rb +10 -0
  70. data/app/models/landable/traffic/ownership.rb +10 -0
  71. data/app/models/landable/traffic/page_view.rb +24 -0
  72. data/app/models/landable/traffic/path.rb +13 -0
  73. data/app/models/landable/traffic/placement.rb +11 -0
  74. data/app/models/landable/traffic/platform.rb +11 -0
  75. data/app/models/landable/traffic/position.rb +11 -0
  76. data/app/models/landable/traffic/query_string.rb +12 -0
  77. data/app/models/landable/traffic/referer.rb +11 -0
  78. data/app/models/landable/traffic/region.rb +11 -0
  79. data/app/models/landable/traffic/search_term.rb +11 -0
  80. data/app/models/landable/traffic/source.rb +11 -0
  81. data/app/models/landable/traffic/target.rb +11 -0
  82. data/app/models/landable/traffic/user_agent.rb +28 -0
  83. data/app/models/landable/traffic/user_agent_type.rb +11 -0
  84. data/app/models/landable/traffic/visit.rb +15 -0
  85. data/app/models/landable/traffic/visitor.rb +13 -0
  86. data/app/responders/landable/api_responder.rb +76 -0
  87. data/app/responders/landable/page_render_responder.rb +15 -0
  88. data/app/serializers/landable/access_token_serializer.rb +6 -0
  89. data/app/serializers/landable/asset_serializer.rb +14 -0
  90. data/app/serializers/landable/author_serializer.rb +5 -0
  91. data/app/serializers/landable/category_serializer.rb +5 -0
  92. data/app/serializers/landable/directory_serializer.rb +8 -0
  93. data/app/serializers/landable/page_revision_serializer.rb +15 -0
  94. data/app/serializers/landable/page_serializer.rb +31 -0
  95. data/app/serializers/landable/template_serializer.rb +5 -0
  96. data/app/serializers/landable/theme_serializer.rb +7 -0
  97. data/app/services/landable/authentication_service.rb +44 -0
  98. data/app/services/landable/registration_service.rb +13 -0
  99. data/app/services/landable/render_service.rb +86 -0
  100. data/app/services/landable/tidy_service.rb +155 -0
  101. data/app/uploaders/landable/asset_uploader.rb +9 -0
  102. data/app/validators/path_validator.rb +12 -0
  103. data/app/validators/url_validator.rb +13 -0
  104. data/app/views/templates/preview.liquid +122 -0
  105. data/bin/rails +8 -0
  106. data/bin/redb +7 -0
  107. data/config.ru +7 -0
  108. data/config/cucumber.yml +5 -0
  109. data/config/routes.rb +62 -0
  110. data/db/migrate/20130510221424_create_landable_schema.rb +338 -0
  111. data/db/migrate/20130909182713_landable_pages__add_updated_by.rb +11 -0
  112. data/db/migrate/20130909182715_landable_page_revisions__break_out_snapshot.rb +72 -0
  113. data/db/migrate/20130909191153_landable_pages__add_lock_version.rb +5 -0
  114. data/db/migrate/20131002220041_file_based_themes.rb +12 -0
  115. data/db/migrate/20131008164204_create_head_tag_on_page.rb +19 -0
  116. data/db/migrate/20131008193544_drop_status_codes_model.rb +44 -0
  117. data/db/migrate/20131028145652_add_traffic_schema.rb +276 -0
  118. data/db/migrate/20131101213623_add_dnt_column_to_visits.rb +7 -0
  119. data/db/migrate/20131104224120_add_meta_on_events.rb +7 -0
  120. data/db/migrate/20131106185946_add_index_on_page_revisions_path.rb +6 -0
  121. data/db/migrate/20131106193021_page_revisisons__path_status_code_index.rb +9 -0
  122. data/db/migrate/20131108212501_traffic_owner_ids_are_serials.rb +20 -0
  123. data/db/migrate/20131121150902_add_attribution_id_to_unique_index.rb +10 -0
  124. data/db/migrate/20131216214027_drop_browser_screenshot_tables.rb +6 -0
  125. data/db/migrate/20140128170659_file_backed_templates.rb +8 -0
  126. data/db/migrate/20140205193757_fix_status_codes.rb +24 -0
  127. data/db/migrate/20140206211322_add_response_time_to_traffic_page_views.rb +7 -0
  128. data/db/migrate/20140220170324_add_slug_to_categories.rb +14 -0
  129. data/db/migrate/20140220174630_add_abstract_and_hero_asset_to_pages_and_page_revisions.rb +8 -0
  130. data/db/migrate/20140224205516_rename_traffic_schema.rb +40 -0
  131. data/db/pgtap/pgtap.sql +9034 -0
  132. data/db/test/landable.access_tokens.sql +13 -0
  133. data/db/test/landable.assets.sql +16 -0
  134. data/db/test/landable.authors.sql +22 -0
  135. data/db/test/landable.categories.sql +9 -0
  136. data/db/test/landable.page_revisions.sql +41 -0
  137. data/db/test/landable.pages.sql +19 -0
  138. data/db/test/landable.templates.sql +15 -0
  139. data/db/test/landable.themes.sql +25 -0
  140. data/doc/schema/access_token.json +22 -0
  141. data/doc/schema/asset.json +65 -0
  142. data/doc/schema/author.json +30 -0
  143. data/doc/schema/directory.json +24 -0
  144. data/doc/schema/page.json +95 -0
  145. data/doc/schema/page_revision.json +70 -0
  146. data/doc/schema/theme.json +37 -0
  147. data/doc/schema/uuid.json +6 -0
  148. data/features/api/access_tokens.feature +84 -0
  149. data/features/api/assets.feature +46 -0
  150. data/features/api/cors.feature +25 -0
  151. data/features/api/pages.feature +42 -0
  152. data/features/api/preview.feature +16 -0
  153. data/features/api/templates.feature +33 -0
  154. data/features/api/themes.feature +33 -0
  155. data/features/liquid/body.feature +35 -0
  156. data/features/liquid/drops/categories.feature +54 -0
  157. data/features/liquid/tags.feature +168 -0
  158. data/features/public/content_types.feature +17 -0
  159. data/features/public/publishing.feature +45 -0
  160. data/features/public/status_codes.feature +25 -0
  161. data/features/public/views.feature +17 -0
  162. data/features/step_definitions/asset_steps.rb +60 -0
  163. data/features/step_definitions/core_api_steps.rb +139 -0
  164. data/features/step_definitions/debug_steps.rb +3 -0
  165. data/features/step_definitions/factory_steps.rb +124 -0
  166. data/features/step_definitions/html_steps.rb +9 -0
  167. data/features/step_definitions/liquid_steps.rb +79 -0
  168. data/features/step_definitions/revision_steps.rb +5 -0
  169. data/features/step_definitions/theme_steps.rb +43 -0
  170. data/features/support/env.rb +66 -0
  171. data/features/support/usefulness.rb +13 -0
  172. data/landable.gemspec +54 -0
  173. data/lib/generators/landable/collection/collection_generator.rb +0 -0
  174. data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%.less +0 -0
  175. data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%/mixins.less +0 -0
  176. data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%/variables.less +0 -0
  177. data/lib/generators/landable/component/component_generator.rb +0 -0
  178. data/lib/generators/landable/component/templates/javascripts/landable/%file_name%.less +0 -0
  179. data/lib/generators/landable/component/templates/stylesheets/landable/%file_name%.less +0 -0
  180. data/lib/generators/landable/install_generator.rb +19 -0
  181. data/lib/generators/landable/landable_generator.rb +22 -0
  182. data/lib/generators/templates/landable.rb +34 -0
  183. data/lib/landable.rb +30 -0
  184. data/lib/landable/configuration.rb +115 -0
  185. data/lib/landable/core_ext/ipaddr.rb +18 -0
  186. data/lib/landable/engine.rb +69 -0
  187. data/lib/landable/error.rb +16 -0
  188. data/lib/landable/inflections.rb +4 -0
  189. data/lib/landable/layout.rb +60 -0
  190. data/lib/landable/liquid.rb +27 -0
  191. data/lib/landable/liquid/asset_tags.rb +76 -0
  192. data/lib/landable/liquid/drops.rb +46 -0
  193. data/lib/landable/liquid/filters.rb +11 -0
  194. data/lib/landable/liquid/tags.rb +91 -0
  195. data/lib/landable/migration.rb +40 -0
  196. data/lib/landable/mime_types.rb +15 -0
  197. data/lib/landable/partial.rb +46 -0
  198. data/lib/landable/seeds.rb +36 -0
  199. data/lib/landable/traffic.rb +34 -0
  200. data/lib/landable/traffic/crawl_tracker.rb +9 -0
  201. data/lib/landable/traffic/noop_tracker.rb +8 -0
  202. data/lib/landable/traffic/ping_tracker.rb +9 -0
  203. data/lib/landable/traffic/scan_tracker.rb +9 -0
  204. data/lib/landable/traffic/scrape_tracker.rb +9 -0
  205. data/lib/landable/traffic/tracker.rb +283 -0
  206. data/lib/landable/traffic/user_tracker.rb +65 -0
  207. data/lib/landable/version.rb +10 -0
  208. data/lib/tasks/landable/cucumber.rake +67 -0
  209. data/lib/tasks/landable/data.rake +166 -0
  210. data/lib/tasks/landable/pgtap.rake +26 -0
  211. data/lib/tasks/landable/rdoc.rake +11 -0
  212. data/lib/tasks/landable/seed.rake +16 -0
  213. data/lib/tasks/landable/spec.rake +15 -0
  214. data/script/cucumber +10 -0
  215. data/spec/concerns/landable/has_assets_spec.rb +75 -0
  216. data/spec/concerns/landable/table_name_spec.rb +15 -0
  217. data/spec/concerns/landable/traffic/table_name_spec.rb +16 -0
  218. data/spec/controllers/concerns/landable/variables_concern_spec.rb +66 -0
  219. data/spec/controllers/landable/api/assets_controller_spec.rb +24 -0
  220. data/spec/controllers/landable/api/categories_controller_spec.rb +45 -0
  221. data/spec/controllers/landable/api/directories_controller_spec.rb +56 -0
  222. data/spec/controllers/landable/api/page_revisions_controller_spec.rb +29 -0
  223. data/spec/controllers/landable/api/pages_controller_spec.rb +271 -0
  224. data/spec/controllers/landable/api_controller_spec.rb +189 -0
  225. data/spec/controllers/public/preview/page_revisions_controller_spec.rb +41 -0
  226. data/spec/controllers/public/preview/pages_controller_spec.rb +36 -0
  227. data/spec/controllers/public/sitemap_controller_spec.rb +25 -0
  228. data/spec/decorators/page_decorator_spec.rb +90 -0
  229. data/spec/dummy/README.rdoc +28 -0
  230. data/spec/dummy/Rakefile +6 -0
  231. data/spec/dummy/app/assets/images/foo.jpg +0 -0
  232. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  233. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  234. data/spec/dummy/app/controllers/application_controller.rb +10 -0
  235. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  236. data/spec/dummy/app/controllers/priority_controller.rb +7 -0
  237. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  238. data/spec/dummy/app/mailers/.keep +0 -0
  239. data/spec/dummy/app/models/.keep +0 -0
  240. data/spec/dummy/app/models/concerns/.keep +0 -0
  241. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  242. data/spec/dummy/app/views/layouts/priority.html.erb +18 -0
  243. data/spec/dummy/app/views/partials/_foobazz.html +1 -0
  244. data/spec/dummy/app/views/priority/show.html.erb +11 -0
  245. data/spec/dummy/bin/bundle +3 -0
  246. data/spec/dummy/bin/rails +4 -0
  247. data/spec/dummy/bin/rake +4 -0
  248. data/spec/dummy/config.ru +4 -0
  249. data/spec/dummy/config/application.rb +21 -0
  250. data/spec/dummy/config/boot.rb +5 -0
  251. data/spec/dummy/config/database.yml +60 -0
  252. data/spec/dummy/config/environment.rb +5 -0
  253. data/spec/dummy/config/environments/development.rb +29 -0
  254. data/spec/dummy/config/environments/production.rb +80 -0
  255. data/spec/dummy/config/environments/test.rb +42 -0
  256. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  257. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  258. data/spec/dummy/config/initializers/inflections.rb +16 -0
  259. data/spec/dummy/config/initializers/landable.rb +22 -0
  260. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  261. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  262. data/spec/dummy/config/initializers/session_store.rb +3 -0
  263. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  264. data/spec/dummy/config/locales/en.yml +23 -0
  265. data/spec/dummy/config/routes.rb +4 -0
  266. data/spec/dummy/db/structure.sql +3736 -0
  267. data/spec/dummy/lib/assets/.keep +0 -0
  268. data/spec/dummy/log/.keep +0 -0
  269. data/spec/dummy/public/404.html +58 -0
  270. data/spec/dummy/public/422.html +58 -0
  271. data/spec/dummy/public/500.html +57 -0
  272. data/spec/dummy/public/favicon.ico +0 -0
  273. data/spec/factories/asset.rb +29 -0
  274. data/spec/factories/authors.rb +12 -0
  275. data/spec/factories/category.rb +6 -0
  276. data/spec/factories/page_revision.rb +6 -0
  277. data/spec/factories/pages.rb +33 -0
  278. data/spec/factories/template.rb +13 -0
  279. data/spec/factories/theme.rb +14 -0
  280. data/spec/fixtures/assets/cthulhu.jpg +0 -0
  281. data/spec/fixtures/assets/panda.png +0 -0
  282. data/spec/fixtures/assets/sloth.png +0 -0
  283. data/spec/fixtures/assets/small.pdf +0 -0
  284. data/spec/helpers/pages_helper_spec.rb +35 -0
  285. data/spec/lib/landable/configuration_spec.rb +20 -0
  286. data/spec/lib/landable/layout_spec.rb +25 -0
  287. data/spec/lib/landable/liquid_spec.rb +24 -0
  288. data/spec/lib/landable/migration_spec.rb +51 -0
  289. data/spec/lib/landable/partial_spec.rb +84 -0
  290. data/spec/lib/landable/tracking_spec.rb +62 -0
  291. data/spec/lib/landable/traffic_spec.rb +45 -0
  292. data/spec/models/landable/access_token_spec.rb +13 -0
  293. data/spec/models/landable/asset_spec.rb +48 -0
  294. data/spec/models/landable/directory_spec.rb +36 -0
  295. data/spec/models/landable/page/errors_spec.rb +30 -0
  296. data/spec/models/landable/page_revision_spec.rb +75 -0
  297. data/spec/models/landable/page_spec.rb +377 -0
  298. data/spec/models/landable/template_spec.rb +47 -0
  299. data/spec/models/landable/theme_spec.rb +8 -0
  300. data/spec/responders/page_render_responder_spec.rb +43 -0
  301. data/spec/routing/public_page_route_spec.rb +36 -0
  302. data/spec/services/landable/authentication_service_spec.rb +61 -0
  303. data/spec/services/landable/render_service_spec.rb +103 -0
  304. data/spec/services/landable/tidy_service_spec.rb +157 -0
  305. data/spec/spec_helper.rb +38 -0
  306. data/spec/support/behaviors.rb +107 -0
  307. data/spec/support/carrier_wave.rb +17 -0
  308. data/spec/support/categories.yml +2 -0
  309. data/spec/support/helpers.rb +22 -0
  310. metadata +795 -0
@@ -0,0 +1,15 @@
1
+ # Re-register application/json, adding our own vendored mime types as aliases
2
+
3
+ # TODO Be more generous about accepting version constraints. Currently,
4
+ # requesting v1.5 when we're running v1.5.2 will return a 406.
5
+
6
+ api_mime_types = %W(
7
+ application/vnd.landable.v#{Landable::VERSION::MAJOR}+json
8
+ application/vnd.landable.v#{Landable::VERSION::STRING}+json
9
+ application/vnd.landable+json
10
+ text/x-json
11
+ application/jsonrequest
12
+ )
13
+
14
+ Mime::Type.unregister :json
15
+ Mime::Type.register 'application/json', :json, api_mime_types
@@ -0,0 +1,46 @@
1
+ module Landable
2
+ class Partial
3
+ def initialize(file)
4
+ @file = file
5
+ end
6
+
7
+ def process
8
+ @name = @file.gsub('/',' ').titlecase
9
+ @description = "Defined in Source Code at #{@file}"
10
+ @slug = @file.gsub(/[^\w]/, '_')
11
+
12
+ @processed = true
13
+ end
14
+
15
+ def to_template
16
+ process unless @processed
17
+
18
+ template = Template.where(file: @file).first_or_initialize
19
+ template.body = ''
20
+ template.name = @name
21
+ template.slug = @slug
22
+ template.description = @description
23
+ template.editable = false
24
+ template.is_layout = false
25
+ template.thumbnail_url ||= "http://placehold.it/300x200"
26
+
27
+ template.save!
28
+
29
+ template
30
+ end
31
+
32
+ class << self
33
+ def all
34
+ files.map { |file| new(file) }
35
+ end
36
+
37
+ def files
38
+ files = []
39
+
40
+ Landable.configuration.partials_to_templates.each do |path|
41
+ files << path
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ # Stop-gap solution to allow us to keep mission-critical seed data somewhere
2
+ # accessible. Not too thrilled about it; refactor away.
3
+ module Landable
4
+ module Seeds
5
+
6
+ def self.seed key
7
+ method_key = "seed_#{key}".to_sym
8
+ if respond_to? method_key
9
+ send method_key if ActiveRecord::Base.connection.schema_exists? "#{Landable.configuration.database_schema_prefix}landable"
10
+ else
11
+ raise NotImplementedError, "No seeds for key '#{key}'"
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ # Required data.
18
+ def self.seed_required
19
+ # categories ('Uncategorized' is mandatory)
20
+ Landable::Category.where(name: 'Uncategorized').first_or_create!
21
+ Landable.configuration.categories.each do |category_name, category_description|
22
+ Landable::Category.where(name: category_name).first_or_create!(description: category_description)
23
+ end
24
+ end
25
+
26
+ # Less required data.
27
+ def self.seed_extras
28
+ # themes
29
+ Landable::Theme.where(name: 'Blank').first_or_create!(
30
+ body: '',
31
+ description: 'A completely blank theme; only the page body will be rendered.',
32
+ thumbnail_url: 'http://placehold.it/300x200',
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ require 'landable/traffic/tracker'
2
+ require 'landable/traffic/crawl_tracker'
3
+ require 'landable/traffic/ping_tracker'
4
+ require 'landable/traffic/scan_tracker'
5
+ require 'landable/traffic/scrape_tracker'
6
+ require 'landable/traffic/user_tracker'
7
+ require 'landable/traffic/noop_tracker'
8
+
9
+ module Landable
10
+ module Traffic
11
+ def track_with_landable!
12
+ begin
13
+ @tracker = Tracker.for self
14
+ @tracker.track
15
+ rescue => e
16
+ Rails.logger.error e
17
+ if respond_to? :newrelic_notice_error
18
+ newrelic_notice_error e
19
+ end
20
+ end
21
+
22
+ yield
23
+
24
+ begin
25
+ @tracker.save
26
+ rescue => e
27
+ Rails.logger.error e
28
+ if respond_to? :newrelic_notice_error
29
+ newrelic_notice_error e
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module Landable
2
+ module Traffic
3
+ class CrawlTracker < Tracker
4
+ def save
5
+ record_access
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Landable
2
+ module Traffic
3
+ class NoopTracker < Tracker
4
+ def save
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Landable
2
+ module Traffic
3
+ class PingTracker < Tracker
4
+ def save
5
+ record_access
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Landable
2
+ module Traffic
3
+ class ScanTracker < Tracker
4
+ def save
5
+ record_access
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Landable
2
+ module Traffic
3
+ class ScrapeTracker < Tracker
4
+ def save
5
+ record_access
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,283 @@
1
+ require 'digest/sha2'
2
+ require 'uri'
3
+
4
+ module Landable
5
+ module Traffic
6
+ class Tracker
7
+ TRACKING_PARAMS = {
8
+ "ad_type" => %w[ad_type adtype],
9
+ "ad_group" => %w[ad_group adgroup ovadgrpid ysmadgrpid],
10
+ "bid_match_type" => %w[bidmatchtype bid_match_type bmt],
11
+ "campaign" => %w[campaign utm_campaign ovcampgid ysmcampgid],
12
+ "content" => %w[content utm_content],
13
+ "creative" => %w[creative adid ovadid],
14
+ "device_type" => %w[device_type devicetype device],
15
+ "click_id" => %w[gclid click_id clickid],
16
+ "experiment" => %w[experiment aceid],
17
+ "keyword" => %w[keyword kw utm_term ovkey ysmkey],
18
+ "match_type" => %w[match_type matchtype match ovmtc ysmmtc],
19
+ "medium" => %w[medium utm_medium],
20
+ "network" => %w[network],
21
+ "placement" => %w[placement],
22
+ "position" => %w[position adposition ad_position],
23
+ "search_term" => %w[search_term searchterm q querystring ovraw ysmraw],
24
+ "source" => %w[source utm_source],
25
+ "target" => %w[target],
26
+ }.freeze
27
+
28
+ TRACKING_KEYS = TRACKING_PARAMS.values.flatten.freeze
29
+ ATTRIBUTION_KEYS = TRACKING_PARAMS.except("click_id").keys
30
+
31
+ TRACKING_PARAMS_TRANSFORM = {
32
+ "ad_type" => { 'pe' => 'product_extensions',
33
+ 'pla' => 'product_listing' },
34
+
35
+ "bid_match_type" => { 'bb' => 'bidded broad',
36
+ 'bc' => 'bidded content',
37
+ 'be' => 'bidded exact',
38
+ 'bp' => 'bidded phrase' },
39
+
40
+ "device_type" => { 'c' => 'computer',
41
+ 'm' => 'mobile',
42
+ 't' => 'tablet' },
43
+
44
+ "match_type" => { 'b' => 'broad',
45
+ 'c' => 'content',
46
+ 'e' => 'exact',
47
+ 'p' => 'phrase',
48
+ 'std' => 'standard',
49
+ 'adv' => 'advanced',
50
+ 'cnt' => 'content' },
51
+
52
+ "network" => { 'g' => 'google_search',
53
+ 's' => 'search_partner',
54
+ 'd' => 'display_network' },
55
+ }.freeze
56
+
57
+ UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\Z/
58
+ UUID_REGEX_V4 = /\A\h{8}-\h{4}-4\h{3}-[89aAbB]\h{3}-\h{12}\Z/
59
+
60
+ # Save space in the session by shortening names
61
+ KEYS = {
62
+ visit_id: 'vid',
63
+ visitor_id: 'vsid',
64
+ visit_time: 'vt',
65
+ visitor_hash: 'vh',
66
+ attribution_hash: 'ah',
67
+ referer_hash: 'rh'
68
+ }.freeze
69
+
70
+ attr_reader :controller
71
+
72
+ delegate :request, :response, :session, to: :controller
73
+ delegate :headers, :path, :query_parameters, :referer, :remote_ip, to: :request
74
+
75
+ class << self
76
+ def for(controller)
77
+ type = controller.request.user_agent.presence && Landable::Traffic::UserAgent[controller.request.user_agent].user_agent_type
78
+ type = 'noop' if Landable.configuration.traffic_enabled == :html and not controller.request.format.html?
79
+ type = 'user'if type.nil?
80
+ type = 'user'if controller.request.query_parameters.slice(*TRACKING_KEYS).any?
81
+
82
+ "Landable::Traffic::#{type.classify}Tracker".constantize.new(controller)
83
+ end
84
+ end
85
+
86
+ def initialize(controller)
87
+ # Allow subclasses to super from initialize
88
+ raise NotImplementedError, "You must subclass Tracker" if self.class == Tracker
89
+ @controller = controller
90
+ @start_time = Time.now
91
+ end
92
+
93
+ def track
94
+ raise NotImplementedError, "You must subclass Tracker" if self.class == Tracker
95
+ end
96
+
97
+ def visitor_id
98
+ @visitor_id = visitor.id if visitor_changed?
99
+ @visitor_id
100
+ end
101
+
102
+ def create_event(type, meta = {})
103
+ return unless @visit_id
104
+
105
+ Event.create(visit_id: @visit_id, event_type: type, meta: meta)
106
+ end
107
+
108
+ def landing_path
109
+ @visit_id and PageView.where(visit_id: @visit_id).order(:page_view_id).first.try(:path)
110
+ end
111
+
112
+ protected
113
+ def cookies
114
+ request.cookie_jar
115
+ end
116
+
117
+ def cookie
118
+ validate_cookie
119
+
120
+ @cookie_id ||= Cookie.create.id
121
+
122
+ set_cookie
123
+ end
124
+
125
+ def validate_cookie
126
+ return unless @cookie_id
127
+ return if @cookie_id =~ UUID_REGEX_V4 && Cookie[@cookie_id]
128
+
129
+ # add_ip_to_graylist
130
+ @cookie_id = nil
131
+ end
132
+
133
+ def set_cookie
134
+ cookies.permanent[:landable] = cookie_defaults.merge(value: @cookie_id)
135
+ end
136
+
137
+ def cookie_defaults
138
+ { domain: :all, secure: false, httponly: true }
139
+ end
140
+
141
+ def do_not_track
142
+ return unless headers["DNT"]
143
+
144
+ headers["DNT"] == "1"
145
+ end
146
+
147
+ def user_agent
148
+ @user_agent ||= UserAgent[request.user_agent]
149
+ end
150
+
151
+ def referer
152
+ return @referer if @referer
153
+ return unless referer_uri
154
+
155
+ params = Rack::Utils.parse_query referer_uri.query
156
+ attribution = Attribution.lookup params.slice(*ATTRIBUTION_KEYS)
157
+ query = params.except(*ATTRIBUTION_KEYS)
158
+
159
+ @referer = Referer.where(domain_id: Domain[referer_uri.host],
160
+ path_id: Path[referer_uri.path],
161
+ query_string_id: QueryString[query.to_query],
162
+ attribution_id: attribution.id).first_or_create
163
+ end
164
+
165
+ def ip_address
166
+ @ip_address ||= IpAddress[remote_ip]
167
+ end
168
+
169
+ def attribution_hash
170
+ Attribution.digest attribution_parameters
171
+ end
172
+
173
+ def visitor_hash
174
+ Digest::SHA2.base64digest [remote_ip, request.user_agent].join
175
+ end
176
+
177
+ def referer_hash
178
+ Digest::SHA2.base64digest request.referer
179
+ end
180
+
181
+ def tracking?
182
+ tracking_parameters.any?
183
+ end
184
+
185
+ def attribution?
186
+ attribution_parameters.any?
187
+ end
188
+
189
+ def record_visit
190
+ create_visit if new_visit?
191
+ end
192
+
193
+ def record_access
194
+ access = Access.where(visitor_id: visitor_id, path_id: Path[request.path]).first_or_initialize
195
+ access.last_accessed_at = Time.current
196
+ access.save!
197
+ end
198
+
199
+ def create_visit
200
+ visit = Visit.new
201
+ visit.attribution = attribution
202
+ visit.cookie_id = @cookie_id
203
+ visit.referer_id = referer.try(:id)
204
+ visit.visitor_id = visitor_id
205
+ visit.do_not_track = do_not_track
206
+ visit.save!
207
+
208
+ @visit_id = visit.id
209
+ end
210
+
211
+ def new_visit?
212
+ @visit_id.nil? || referer_changed? || attribution_changed? || visitor_changed? || visit_stale?
213
+ end
214
+
215
+ def referer_changed?
216
+ external_referer? && referer_hash != @referer_hash
217
+ end
218
+
219
+ def referer_uri
220
+ @referer_uri ||= URI(URI.encode(request.referer)) if request.referer
221
+ end
222
+
223
+ def external_referer?
224
+ referer_uri && referer_uri.host != request.host
225
+ end
226
+
227
+ def visitor_changed?
228
+ visitor_hash != @visitor_hash
229
+ end
230
+
231
+ def attribution_changed?
232
+ attribution? && attribution_hash != @attribution_hash
233
+ end
234
+
235
+ def visit_stale?
236
+ return false unless @last_visit_time
237
+
238
+ Time.current - @last_visit_time > 30.minutes
239
+ end
240
+
241
+ def extract_tracking(params)
242
+ hash = {}
243
+
244
+ TRACKING_PARAMS.each do |key, names|
245
+ next unless param = names.find { |name| params.key?(name) }
246
+ hash[key] = params[param]
247
+ end
248
+
249
+ TRACKING_PARAMS_TRANSFORM.each do |key, transform|
250
+ next unless hash.key? key
251
+
252
+ hash[key] = transform[hash[key]] if transform.key? hash[key]
253
+ end
254
+
255
+ hash
256
+ end
257
+
258
+ def tracking_parameters
259
+ @tracking_parameters ||= extract_tracking(query_parameters)
260
+ end
261
+
262
+ def untracked_parameters
263
+ query_parameters.except(*TRACKING_PARAMS.values.flatten)
264
+ end
265
+
266
+ def attribution_parameters
267
+ @attribution_parameters ||= tracking_parameters.slice(*ATTRIBUTION_KEYS)
268
+ end
269
+
270
+ def attribution
271
+ @attribution ||= Attribution.lookup attribution_parameters
272
+ end
273
+
274
+ def visitor
275
+ @visitor ||= Visitor.with_ip_address(ip_address).with_user_agent(user_agent).first_or_create
276
+ end
277
+
278
+ def visit
279
+ @visit ||= @visit_id && Visit.find(@visit_id)
280
+ end
281
+ end
282
+ end
283
+ end