calagator 0.0.1.pre1

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 (384) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +75 -0
  4. data/Rakefile +12 -0
  5. data/app/assets/images/confused-alligator-sm.png +0 -0
  6. data/app/assets/images/disk.png +0 -0
  7. data/app/assets/images/edit.png +0 -0
  8. data/app/assets/images/external_sites/epdx.png +0 -0
  9. data/app/assets/images/external_sites/external.gif +0 -0
  10. data/app/assets/images/external_sites/facebook.png +0 -0
  11. data/app/assets/images/external_sites/foursquare.png +0 -0
  12. data/app/assets/images/external_sites/gowalla.png +0 -0
  13. data/app/assets/images/external_sites/lanyrd.png +0 -0
  14. data/app/assets/images/external_sites/meetup.png +0 -0
  15. data/app/assets/images/external_sites/plancast.png +0 -0
  16. data/app/assets/images/external_sites/shizzow.png +0 -0
  17. data/app/assets/images/external_sites/upcoming.png +0 -0
  18. data/app/assets/images/external_sites/yelp.png +0 -0
  19. data/app/assets/images/feed.png +0 -0
  20. data/app/assets/images/heart.png +0 -0
  21. data/app/assets/images/icon_ical.gif +0 -0
  22. data/app/assets/images/information.png +0 -0
  23. data/app/assets/images/nav_marker.gif +0 -0
  24. data/app/assets/images/nav_marker.png +0 -0
  25. data/app/assets/images/plus.png +0 -0
  26. data/app/assets/images/redx.png +0 -0
  27. data/app/assets/images/site-icon.png +0 -0
  28. data/app/assets/images/spinner.gif +0 -0
  29. data/app/assets/images/star.png +0 -0
  30. data/app/assets/images/subnav_marker.gif +0 -0
  31. data/app/assets/images/subnav_marker.png +0 -0
  32. data/app/assets/images/tag_blue.png +0 -0
  33. data/app/assets/images/tag_icons/angular.png +0 -0
  34. data/app/assets/images/tag_icons/beer.png +0 -0
  35. data/app/assets/images/tag_icons/bitcoin.png +0 -0
  36. data/app/assets/images/tag_icons/free.png +0 -0
  37. data/app/assets/images/tag_icons/golang.png +0 -0
  38. data/app/assets/images/tag_icons/html.png +0 -0
  39. data/app/assets/images/tag_icons/javascript.png +0 -0
  40. data/app/assets/images/tag_icons/linux.png +0 -0
  41. data/app/assets/images/tag_icons/php.png +0 -0
  42. data/app/assets/images/tag_icons/pizza.png +0 -0
  43. data/app/assets/images/tag_icons/python.png +0 -0
  44. data/app/assets/images/tag_icons/rails.png +0 -0
  45. data/app/assets/images/tag_icons/ruby.png +0 -0
  46. data/app/assets/images/tag_icons/sass.png +0 -0
  47. data/app/assets/images/tag_icons/swift.png +0 -0
  48. data/app/assets/images/transmit_blue.png +0 -0
  49. data/app/assets/images/weekday_background.gif +0 -0
  50. data/app/assets/javascripts/calagator.js +18 -0
  51. data/app/assets/javascripts/calagator/forms.js +60 -0
  52. data/app/assets/stylesheets/calagator.scss +8 -0
  53. data/app/assets/stylesheets/calagator/common.scss +14 -0
  54. data/app/assets/stylesheets/calagator/datepicker.scss +177 -0
  55. data/app/assets/stylesheets/calagator/errors.css +110 -0
  56. data/app/assets/stylesheets/calagator/forms.scss +4 -0
  57. data/app/assets/stylesheets/calagator/reset.css +40 -0
  58. data/app/assets/stylesheets/calagator/theme.css +0 -0
  59. data/app/controllers/calagator/admin_controller.rb +27 -0
  60. data/app/controllers/calagator/application_controller.rb +82 -0
  61. data/app/controllers/calagator/events_controller.rb +171 -0
  62. data/app/controllers/calagator/site_controller.rb +35 -0
  63. data/app/controllers/calagator/sources_controller.rb +94 -0
  64. data/app/controllers/calagator/venues_controller.rb +133 -0
  65. data/app/controllers/calagator/versions_controller.rb +20 -0
  66. data/app/helpers/calagator/application_helper.rb +128 -0
  67. data/app/helpers/calagator/events_helper.rb +184 -0
  68. data/app/helpers/calagator/google_event_export_helper.rb +67 -0
  69. data/app/helpers/calagator/mapping_helper.rb +103 -0
  70. data/app/helpers/calagator/sources_helper.rb +10 -0
  71. data/app/helpers/calagator/tags_helper.rb +42 -0
  72. data/app/helpers/calagator/time_range_helper.rb +166 -0
  73. data/app/models/calagator/event.rb +246 -0
  74. data/app/models/calagator/event/cloner.rb +39 -0
  75. data/app/models/calagator/event/ical_renderer.rb +155 -0
  76. data/app/models/calagator/event/overview.rb +45 -0
  77. data/app/models/calagator/event/saver.rb +58 -0
  78. data/app/models/calagator/event/search.rb +72 -0
  79. data/app/models/calagator/event/search_engine.rb +28 -0
  80. data/app/models/calagator/event/search_engine/apache_sunspot.rb +106 -0
  81. data/app/models/calagator/event/search_engine/sql.rb +107 -0
  82. data/app/models/calagator/source.rb +115 -0
  83. data/app/models/calagator/source/importer.rb +46 -0
  84. data/app/models/calagator/source/parser.rb +128 -0
  85. data/app/models/calagator/source/parser/facebook.rb +67 -0
  86. data/app/models/calagator/source/parser/hcal.rb +108 -0
  87. data/app/models/calagator/source/parser/http_authentication_required_error.rb +6 -0
  88. data/app/models/calagator/source/parser/ical.rb +186 -0
  89. data/app/models/calagator/source/parser/meetup.rb +72 -0
  90. data/app/models/calagator/source/parser/not_found.rb +9 -0
  91. data/app/models/calagator/source/parser/plancast.rb +59 -0
  92. data/app/models/calagator/venue.rb +161 -0
  93. data/app/models/calagator/venue/geocoder.rb +51 -0
  94. data/app/models/calagator/venue/search.rb +63 -0
  95. data/app/models/calagator/venue/search_engine.rb +24 -0
  96. data/app/models/calagator/venue/search_engine/apache_sunspot.rb +85 -0
  97. data/app/models/calagator/venue/search_engine/sql.rb +68 -0
  98. data/app/models/event/search_engine/base.rb +0 -0
  99. data/app/observers/calagator/cache_observer.rb +37 -0
  100. data/app/views/calagator/admin/events.html.erb +28 -0
  101. data/app/views/calagator/admin/index.html.erb +8 -0
  102. data/app/views/calagator/events/_feed_item.html.erb +62 -0
  103. data/app/views/calagator/events/_form.html.erb +63 -0
  104. data/app/views/calagator/events/_gcal_reminder.html.erb +1 -0
  105. data/app/views/calagator/events/_hcal.html.erb +41 -0
  106. data/app/views/calagator/events/_item.html.erb +120 -0
  107. data/app/views/calagator/events/_list.html.erb +30 -0
  108. data/app/views/calagator/events/_list_item.html.erb +37 -0
  109. data/app/views/calagator/events/_search_section.html.erb +15 -0
  110. data/app/views/calagator/events/_subnav.html.erb +13 -0
  111. data/app/views/calagator/events/_table.html.erb +74 -0
  112. data/app/views/calagator/events/duplicates.html.erb +58 -0
  113. data/app/views/calagator/events/edit.html.erb +3 -0
  114. data/app/views/calagator/events/index.atom.builder +32 -0
  115. data/app/views/calagator/events/index.html.erb +51 -0
  116. data/app/views/calagator/events/index.kml.erb +20 -0
  117. data/app/views/calagator/events/new.html.erb +7 -0
  118. data/app/views/calagator/events/search.html.erb +25 -0
  119. data/app/views/calagator/events/show.html.erb +81 -0
  120. data/app/views/calagator/site/_appropriateness_message.html.erb +15 -0
  121. data/app/views/calagator/site/_description.html.erb +3 -0
  122. data/app/views/calagator/site/about.html.erb +7 -0
  123. data/app/views/calagator/site/defunct.html.erb +15 -0
  124. data/app/views/calagator/site/export.html.erb +4 -0
  125. data/app/views/calagator/site/index.html.erb +40 -0
  126. data/app/views/calagator/site/opensearch.xml.builder +8 -0
  127. data/app/views/calagator/sources/_subnav.html.erb +0 -0
  128. data/app/views/calagator/sources/edit.html.erb +12 -0
  129. data/app/views/calagator/sources/import.html.erb +10 -0
  130. data/app/views/calagator/sources/index.html.erb +22 -0
  131. data/app/views/calagator/sources/new.html.erb +32 -0
  132. data/app/views/calagator/sources/show.html.erb +16 -0
  133. data/app/views/calagator/venues/_form.html.erb +57 -0
  134. data/app/views/calagator/venues/_subnav.html.erb +10 -0
  135. data/app/views/calagator/venues/duplicates.html.erb +65 -0
  136. data/app/views/calagator/venues/edit.html.erb +3 -0
  137. data/app/views/calagator/venues/index.html.erb +93 -0
  138. data/app/views/calagator/venues/index.kml.erb +15 -0
  139. data/app/views/calagator/venues/map.html.erb +4 -0
  140. data/app/views/calagator/venues/new.html.erb +5 -0
  141. data/app/views/calagator/venues/show.html.erb +113 -0
  142. data/app/views/calagator/versions/_chooser.html.erb +28 -0
  143. data/app/views/calagator/versions/_edit_with_chooser.html.erb +16 -0
  144. data/app/views/layouts/calagator/application.html.erb +110 -0
  145. data/config/deploy/local.rb +37 -0
  146. data/config/deploy/lucca.rb +33 -0
  147. data/config/initializers/dates.rb +7 -0
  148. data/config/initializers/formtastic.rb +82 -0
  149. data/config/initializers/geokit.rb +74 -0
  150. data/config/initializers/ics_renderer.rb +6 -0
  151. data/config/initializers/load_tag_model_extensions.rb +3 -0
  152. data/config/initializers/mime_types.rb +9 -0
  153. data/config/initializers/observers.rb +1 -0
  154. data/config/initializers/search_engine.rb +4 -0
  155. data/config/initializers/set_default_url_host.rb +5 -0
  156. data/config/initializers/time_get_zone.rb +8 -0
  157. data/config/locales/en.yml +5 -0
  158. data/config/routes.rb +55 -0
  159. data/config/secrets.yml.blag +75 -0
  160. data/config/sunspot.yml +23 -0
  161. data/db/development.sqlite3 +0 -0
  162. data/db/development.sqlite3.bak +0 -0
  163. data/db/development.sqlite3.old +0 -0
  164. data/db/development~20111112@110950.sqlite3 +0 -0
  165. data/db/migrate/001_create_events.rb +17 -0
  166. data/db/migrate/002_create_venues.rb +17 -0
  167. data/db/migrate/003_create_sources.rb +16 -0
  168. data/db/migrate/004_add_detailed_fields_to_venue.rb +19 -0
  169. data/db/migrate/005_add_end_time_to_events.rb +9 -0
  170. data/db/migrate/006_add_source_id_to_events.rb +9 -0
  171. data/db/migrate/008_add_source_id_to_venues.rb +10 -0
  172. data/db/migrate/009_add_duplicate_of_column_to_venues.rb +9 -0
  173. data/db/migrate/010_add_duplicate_of_column_to_events.rb +9 -0
  174. data/db/migrate/011_change_lat_long_type.rb +12 -0
  175. data/db/migrate/012_add_source_reimport.rb +9 -0
  176. data/db/migrate/013_change_end_time_to_duration.rb +11 -0
  177. data/db/migrate/014_remove_format_type_from_source.rb +9 -0
  178. data/db/migrate/015_create_updates.rb +15 -0
  179. data/db/migrate/016_remove_next_update_from_source.rb +9 -0
  180. data/db/migrate/20080705163959_change_duration_to_end_time.rb +11 -0
  181. data/db/migrate/20080705164959_create_tags_and_taggings.rb +28 -0
  182. data/db/migrate/20081011181519_create_versioned_events.rb +25 -0
  183. data/db/migrate/20081011193124_create_versioned_venues.rb +32 -0
  184. data/db/migrate/20081115190515_add_rrule_to_events.rb +15 -0
  185. data/db/migrate/20090912082129_create_versions.rb +18 -0
  186. data/db/migrate/20110219205156_add_closed_flag_to_venues.rb +9 -0
  187. data/db/migrate/20110220001008_add_wifi_flag_to_venues.rb +9 -0
  188. data/db/migrate/20110220011427_add_access_notes_to_venues.rb +9 -0
  189. data/db/migrate/20110220031117_add_events_count_to_venues.rb +8 -0
  190. data/db/migrate/20110604174521_add_venue_details_to_events.rb +9 -0
  191. data/db/migrate/20110717231316_acts_as_taggable_on_migration.rb +50 -0
  192. data/db/migrate/20120709092821_cleanup.rb +14 -0
  193. data/db/migrate/20120831234448_specify_venues_latitude_and_longitude_precision.rb +11 -0
  194. data/db/migrate/20150206085809_remove_updates.rb +13 -0
  195. data/db/migrate/20150207231355_add_locked_status_to_events.rb +5 -0
  196. data/db/production.sqlite3 +0 -0
  197. data/db/schema.rb +102 -0
  198. data/db/seeds.rb +16 -0
  199. data/db/test.sqlite3 +0 -0
  200. data/db/test2.sqlite3 +0 -0
  201. data/lib/calagator.rb +30 -0
  202. data/lib/calagator/blacklist_validator.rb +69 -0
  203. data/lib/calagator/decode_html_entities_hack.rb +33 -0
  204. data/lib/calagator/duplicate_checking.rb +133 -0
  205. data/lib/calagator/duplicate_checking/controller_actions.rb +38 -0
  206. data/lib/calagator/duplicate_checking/duplicate_finder.rb +83 -0
  207. data/lib/calagator/duplicate_checking/duplicate_squasher.rb +74 -0
  208. data/lib/calagator/engine.rb +30 -0
  209. data/lib/calagator/strip_whitespace.rb +19 -0
  210. data/lib/calagator/tag_model_extensions.rb +104 -0
  211. data/lib/calagator/url_prefixer.rb +9 -0
  212. data/lib/calagator/version.rb +3 -0
  213. data/lib/generators/calagator/install_generator.rb +39 -0
  214. data/lib/generators/calagator/templates/config/calagator.rb +26 -0
  215. data/lib/generators/calagator/templates/config/secrets.yml.sample +83 -0
  216. data/lib/secrets_reader.rb +76 -0
  217. data/lib/tasks/spec_db.rake +53 -0
  218. data/lib/tasks/sunspot_reindex_calagator.rake +6 -0
  219. data/lib/tasks/sunspot_solr_restart_enhancements.rake +19 -0
  220. data/lib/tasks/update_counter_caches.rake +13 -0
  221. data/lib/templates/erb/scaffold/_form.html.erb +11 -0
  222. data/lib/theme_reader.rb +17 -0
  223. data/lib/wait_for_solr.rb +25 -0
  224. data/spec/controllers/calagator/application_controller_spec.rb +47 -0
  225. data/spec/controllers/calagator/events_controller_spec.rb +794 -0
  226. data/spec/controllers/calagator/site_controller_spec.rb +59 -0
  227. data/spec/controllers/calagator/sources_controller_spec.rb +439 -0
  228. data/spec/controllers/calagator/venues_controller_spec.rb +319 -0
  229. data/spec/controllers/calagator/versions_controller_spec.rb +82 -0
  230. data/spec/controllers/squash_many_duplicates_examples.rb +49 -0
  231. data/spec/dummy/Gemfile +39 -0
  232. data/spec/dummy/Gemfile.lock +195 -0
  233. data/spec/dummy/README.rdoc +261 -0
  234. data/spec/dummy/Rakefile +7 -0
  235. data/spec/dummy/app/assets/images/rails.png +0 -0
  236. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  237. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  238. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  239. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  240. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  241. data/spec/dummy/config.ru +4 -0
  242. data/spec/dummy/config/application.rb +62 -0
  243. data/spec/dummy/config/boot.rb +6 -0
  244. data/spec/dummy/config/database.yml +25 -0
  245. data/spec/dummy/config/environment.rb +5 -0
  246. data/spec/dummy/config/environments/development.rb +37 -0
  247. data/spec/dummy/config/environments/production.rb +67 -0
  248. data/spec/dummy/config/environments/test.rb +37 -0
  249. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  250. data/spec/dummy/config/initializers/calagator.rb +26 -0
  251. data/spec/dummy/config/initializers/inflections.rb +15 -0
  252. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  253. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  254. data/spec/dummy/config/initializers/session_store.rb +8 -0
  255. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  256. data/spec/dummy/config/locales/en.yml +5 -0
  257. data/spec/dummy/config/routes.rb +59 -0
  258. data/spec/dummy/config/secrets.yml +83 -0
  259. data/spec/dummy/db/development.sqlite3 +0 -0
  260. data/spec/dummy/db/migrate/20150309023304_create_events.calagator.rb +18 -0
  261. data/spec/dummy/db/migrate/20150309023305_create_venues.calagator.rb +18 -0
  262. data/spec/dummy/db/migrate/20150309023306_create_sources.calagator.rb +17 -0
  263. data/spec/dummy/db/migrate/20150309023307_add_detailed_fields_to_venue.calagator.rb +20 -0
  264. data/spec/dummy/db/migrate/20150309023308_add_end_time_to_events.calagator.rb +10 -0
  265. data/spec/dummy/db/migrate/20150309023309_add_source_id_to_events.calagator.rb +10 -0
  266. data/spec/dummy/db/migrate/20150309023310_add_source_id_to_venues.calagator.rb +11 -0
  267. data/spec/dummy/db/migrate/20150309023311_add_duplicate_of_column_to_venues.calagator.rb +10 -0
  268. data/spec/dummy/db/migrate/20150309023312_add_duplicate_of_column_to_events.calagator.rb +10 -0
  269. data/spec/dummy/db/migrate/20150309023313_change_lat_long_type.calagator.rb +13 -0
  270. data/spec/dummy/db/migrate/20150309023314_add_source_reimport.calagator.rb +10 -0
  271. data/spec/dummy/db/migrate/20150309023315_change_end_time_to_duration.calagator.rb +12 -0
  272. data/spec/dummy/db/migrate/20150309023316_remove_format_type_from_source.calagator.rb +10 -0
  273. data/spec/dummy/db/migrate/20150309023317_create_updates.calagator.rb +16 -0
  274. data/spec/dummy/db/migrate/20150309023318_remove_next_update_from_source.calagator.rb +10 -0
  275. data/spec/dummy/db/migrate/20150309023319_change_duration_to_end_time.calagator.rb +12 -0
  276. data/spec/dummy/db/migrate/20150309023320_create_tags_and_taggings.calagator.rb +29 -0
  277. data/spec/dummy/db/migrate/20150309023321_create_versioned_events.calagator.rb +26 -0
  278. data/spec/dummy/db/migrate/20150309023322_create_versioned_venues.calagator.rb +33 -0
  279. data/spec/dummy/db/migrate/20150309023323_add_rrule_to_events.calagator.rb +16 -0
  280. data/spec/dummy/db/migrate/20150309023324_create_versions.calagator.rb +19 -0
  281. data/spec/dummy/db/migrate/20150309023325_add_closed_flag_to_venues.calagator.rb +10 -0
  282. data/spec/dummy/db/migrate/20150309023326_add_wifi_flag_to_venues.calagator.rb +10 -0
  283. data/spec/dummy/db/migrate/20150309023327_add_access_notes_to_venues.calagator.rb +10 -0
  284. data/spec/dummy/db/migrate/20150309023328_add_events_count_to_venues.calagator.rb +9 -0
  285. data/spec/dummy/db/migrate/20150309023329_add_venue_details_to_events.calagator.rb +10 -0
  286. data/spec/dummy/db/migrate/20150309023330_acts_as_taggable_on_migration.calagator.rb +51 -0
  287. data/spec/dummy/db/migrate/20150309023331_cleanup.calagator.rb +15 -0
  288. data/spec/dummy/db/migrate/20150309023332_specify_venues_latitude_and_longitude_precision.calagator.rb +12 -0
  289. data/spec/dummy/db/migrate/20150309023333_remove_updates.calagator.rb +14 -0
  290. data/spec/dummy/db/migrate/20150309023334_add_locked_status_to_events.calagator.rb +6 -0
  291. data/spec/dummy/db/schema.rb +95 -0
  292. data/spec/dummy/db/seeds.rb +7 -0
  293. data/spec/dummy/db/test.sqlite3 +0 -0
  294. data/spec/dummy/doc/README_FOR_APP +2 -0
  295. data/spec/dummy/log/development.log +273 -0
  296. data/spec/dummy/public/404.html +26 -0
  297. data/spec/dummy/public/422.html +26 -0
  298. data/spec/dummy/public/500.html +25 -0
  299. data/spec/dummy/public/favicon.ico +0 -0
  300. data/spec/dummy/public/robots.txt +5 -0
  301. data/spec/dummy/script/rails +6 -0
  302. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  303. data/spec/dummy/test/test_helper.rb +13 -0
  304. data/spec/factories.rb +93 -0
  305. data/spec/features/add_event_spec.rb +99 -0
  306. data/spec/features/add_venue_spec.rb +34 -0
  307. data/spec/features/admin_auth_spec.rb +22 -0
  308. data/spec/features/admin_lock_event_spec.rb +41 -0
  309. data/spec/features/import_events_from_feed_spec.rb +43 -0
  310. data/spec/features/managing_event_spec.rb +111 -0
  311. data/spec/features/managing_venue_spec.rb +71 -0
  312. data/spec/features/search_event_spec.rb +27 -0
  313. data/spec/helpers/calagator/application_helper_spec.rb +82 -0
  314. data/spec/helpers/calagator/events_helper_spec.rb +172 -0
  315. data/spec/helpers/calagator/google_event_export_helper_spec.rb +70 -0
  316. data/spec/helpers/calagator/sources_helper_spec.rb +12 -0
  317. data/spec/helpers/calagator/tags_helper_spec.rb +85 -0
  318. data/spec/helpers/calagator/time_range_helper_spec.rb +59 -0
  319. data/spec/lib/calagator/blacklist_validator_spec.rb +65 -0
  320. data/spec/lib/calagator/decode_html_entities_hack_spec.rb +54 -0
  321. data/spec/lib/calagator/settings_spec.rb +20 -0
  322. data/spec/lib/calagator/url_prefixer_spec.rb +33 -0
  323. data/spec/lib/secrets_reader_spec.rb +65 -0
  324. data/spec/models/calagator/event/cloner_spec.rb +43 -0
  325. data/spec/models/calagator/event/overview_spec.rb +79 -0
  326. data/spec/models/calagator/event/search_spec.rb +103 -0
  327. data/spec/models/calagator/event_search_spec.rb +149 -0
  328. data/spec/models/calagator/event_spec.rb +859 -0
  329. data/spec/models/calagator/source/parser_facebook_spec.rb +73 -0
  330. data/spec/models/calagator/source/parser_hcal_spec.rb +69 -0
  331. data/spec/models/calagator/source/parser_ical_non_standard_spec.rb +91 -0
  332. data/spec/models/calagator/source/parser_ical_spec.rb +322 -0
  333. data/spec/models/calagator/source/parser_meetup_spec.rb +69 -0
  334. data/spec/models/calagator/source/parser_plancast_spec.rb +53 -0
  335. data/spec/models/calagator/source/parser_spec.rb +238 -0
  336. data/spec/models/calagator/source_spec.rb +135 -0
  337. data/spec/models/calagator/venue/search_spec.rb +92 -0
  338. data/spec/models/calagator/venue_search_spec.rb +124 -0
  339. data/spec/models/calagator/venue_spec.rb +346 -0
  340. data/spec/models/tag_spec.rb +35 -0
  341. data/spec/rails_helper.rb +39 -0
  342. data/spec/spec_helper.rb +140 -0
  343. data/spec/support/disable_geocoding.rb +1 -0
  344. data/spec/support/http_samples.rb +5 -0
  345. data/spec/support/samples/facebook.json +23 -0
  346. data/spec/support/samples/hcal_basic.xml +6 -0
  347. data/spec/support/samples/hcal_dup_event_dup_venue.xml +13 -0
  348. data/spec/support/samples/hcal_event_duplicates_fixture.xml +13 -0
  349. data/spec/support/samples/hcal_event_wo_lat_and_long.xml +14 -0
  350. data/spec/support/samples/hcal_multiple.xml +16 -0
  351. data/spec/support/samples/hcal_same_event_twice_with_different_venues.xml +12 -0
  352. data/spec/support/samples/hcal_single.xml +8 -0
  353. data/spec/support/samples/hcal_two_identical_events.xml +14 -0
  354. data/spec/support/samples/hcal_upcoming_v1.html +412 -0
  355. data/spec/support/samples/hcal_upcoming_v2.html +749 -0
  356. data/spec/support/samples/hcal_upcoming_v3.html +685 -0
  357. data/spec/support/samples/hcal_upcoming_v4.html +761 -0
  358. data/spec/support/samples/ical_apple.ics +22 -0
  359. data/spec/support/samples/ical_apple_v3.ics +37 -0
  360. data/spec/support/samples/ical_basic.ics +15 -0
  361. data/spec/support/samples/ical_basic_with_duration.ics +16 -0
  362. data/spec/support/samples/ical_event_with_squashed_venue.ics +12 -0
  363. data/spec/support/samples/ical_eventful_many.ics +504 -0
  364. data/spec/support/samples/ical_gmt.ics +12 -0
  365. data/spec/support/samples/ical_google.ics +786 -0
  366. data/spec/support/samples/ical_multiple_calendars.ics +111 -0
  367. data/spec/support/samples/ical_upcoming.ics +36 -0
  368. data/spec/support/samples/ical_upcoming_many.ics +682 -0
  369. data/spec/support/samples/ical_upcoming_v2.ics +43 -0
  370. data/spec/support/samples/ical_z.ics +10 -0
  371. data/spec/support/samples/meetup.ics +16 -0
  372. data/spec/support/samples/meetup.json +31 -0
  373. data/spec/support/samples/plancast.ics +59 -0
  374. data/spec/support/samples/plancast.json +51 -0
  375. data/spec/support/samples/plancast_with_missing_venue.json +39 -0
  376. data/spec/support/samples/upcoming_v1.xml +8 -0
  377. data/spec/support/samples/upcoming_v2_with_invalid_utc_dates.xml +8 -0
  378. data/spec/support/time_convenience_methods.rb +8 -0
  379. data/spec/support/time_zones.rb +12 -0
  380. data/spec/support/url_helpers.rb +5 -0
  381. data/spec/support/wait_for_ajax.rb +15 -0
  382. data/spec/support/webmock.rb +8 -0
  383. data/spec/travis_spec.rb +7 -0
  384. metadata +1194 -0
@@ -0,0 +1,39 @@
1
+ # Return a new record with fields selectively copied from the original, and
2
+ # the start_time and end_time adjusted so that their date is set to today and
3
+ # their time-of-day is set to the original record's time-of-day.
4
+ module Calagator
5
+
6
+ class Event < ActiveRecord::Base
7
+ class Cloner < Struct.new(:event)
8
+ def self.clone(event)
9
+ new(event).clone
10
+ end
11
+
12
+ ATTRIBUTES = [:title, :description, :venue_id, :url, :tag_list, :venue_details]
13
+
14
+ def clone
15
+ clone = Event.new
16
+ ATTRIBUTES.each do |attribute|
17
+ clone.send "#{attribute}=", event.send(attribute)
18
+ end
19
+ if event.start_time
20
+ clone.start_time = clone_time_for_today(event.start_time)
21
+ end
22
+ if event.end_time
23
+ clone.end_time = clone_time_for_today(event.end_time)
24
+ end
25
+ clone
26
+ end
27
+
28
+ private
29
+
30
+ # Return a time that's today but has the time-of-day component from the
31
+ # +source+ time argument.
32
+ def clone_time_for_today(source)
33
+ today = Date.today
34
+ Time.zone.local(today.year, today.mon, today.day, source.hour, source.min, source.sec, source.usec)
35
+ end
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,155 @@
1
+ # Return an iCalendar string representing an Array of Event instances.
2
+ #
3
+ # Arguments:
4
+ # * :events - Event instance or array of them.
5
+ #
6
+ # Options:
7
+ # * :url_helper - Lambda that accepts an Event instance and generates a URL
8
+ # for it if it doesn't have a URL already.
9
+ #
10
+ # Example:
11
+ # ics1 = Event::IcalRenderer.render(myevent)
12
+ # ics2 = Event::IcalRenderer.render(myevents, url_helper: -> (event) { event_url(event) })
13
+ module Calagator
14
+
15
+ class Event < ActiveRecord::Base
16
+ class IcalRenderer
17
+ def self.render(events, opts={})
18
+ output = render_icalendar(events, opts)
19
+ output = add_name(output)
20
+ output = normalize_line_endings(output)
21
+ end
22
+
23
+ def self.render_icalendar(events, opts)
24
+ RiCal.Calendar do |calendar|
25
+ calendar.prodid = "-//Calagator//EN"
26
+
27
+ Array(events).each do |event|
28
+ calendar.event do |entry|
29
+ new(event, opts).add_event_to(entry)
30
+ end
31
+ end
32
+ end.export
33
+ end
34
+
35
+ def self.add_name(output)
36
+ output.sub(/(CALSCALE:\w+)/i, "\\1\nX-WR-CALNAME:#{Calagator.title}\nMETHOD:PUBLISH")
37
+ end
38
+
39
+ def self.normalize_line_endings(output)
40
+ output.gsub(/\r?\n/,"\r\n")
41
+ end
42
+
43
+ attr_reader :event, :imported_from
44
+
45
+ def initialize(event, opts)
46
+ @event = event
47
+ @imported_from = opts[:url_helper].call(event).to_s if opts[:url_helper]
48
+ end
49
+
50
+ def add_event_to(entry)
51
+ fields.each do |field|
52
+ value = send(field) # value = summary
53
+ entry.send field, value if value # entry.summary summary if summary
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def fields
60
+ %w(summary description url location dtstart dtend created last_modified sequence dtstamp uid)
61
+ end
62
+
63
+ def summary
64
+ event.title || 'Untitled Event'
65
+ end
66
+
67
+ def description
68
+ parts = [
69
+ description_range,
70
+ description_description,
71
+ description_tags,
72
+ description_imported_from,
73
+ ].compact
74
+
75
+ parts.join if parts.any?
76
+ end
77
+
78
+ def description_range
79
+ return unless multiday?
80
+ time_range = TimeRangeHelper.normalize_time(event, format: :text)
81
+ "This event runs from #{time_range}.\n\nDescription:\n"
82
+ end
83
+
84
+ def description_description
85
+ Loofah::Helpers.strip_tags(event.description) if event.description.present?
86
+ end
87
+
88
+ def description_tags
89
+ "\n\nTags: #{event.tag_list}" if event.tag_list.present?
90
+ end
91
+
92
+ def description_imported_from
93
+ "\n\nImported from: #{imported_from}" if imported_from
94
+ end
95
+
96
+ def url
97
+ event.url if event.url.present?
98
+ end
99
+
100
+ def location
101
+ [event.venue_title, event.venue.full_address].compact.join(": ") if event.venue
102
+ end
103
+
104
+ def dtstart
105
+ if multiday?
106
+ event.dates.first
107
+ else
108
+ event.start_time
109
+ end
110
+ end
111
+
112
+ def dtend
113
+ if multiday?
114
+ event.dates.last + 1.day
115
+ else
116
+ event.end_time || event.start_time + 1.hour
117
+ end
118
+ end
119
+
120
+ def created
121
+ event.created_at if event.created_at
122
+ end
123
+
124
+ def last_modified
125
+ event.updated_at if event.updated_at
126
+ end
127
+
128
+ def sequence
129
+ # Set the iCalendar SEQUENCE, which should be increased each time an
130
+ # event is updated. If an admin needs to forcefully increment the
131
+ # SEQUENCE for all events, they can edit the "config/secrets.yml"
132
+ # file and set the "icalendar_sequence_offset" value to something
133
+ # greater than 0.
134
+ (SECRETS.icalendar_sequence_offset || 0) + event.versions.count
135
+ end
136
+
137
+ def dtstamp
138
+ # dtstamp and uid added because of a bug in Outlook;
139
+ # Outlook 2003 will not import an .ics file unless it has DTSTAMP, UID, and METHOD
140
+ # use created_at for DTSTAMP; if there's no created_at, use event.start_time;
141
+ event.created_at || event.start_time
142
+ end
143
+
144
+ def uid
145
+ imported_from
146
+ end
147
+
148
+ # Treat any event with a duration of at least 20 hours as a multiday event.
149
+ def multiday?
150
+ event.dates.size > 1 && event.duration.seconds > 20.hours
151
+ end
152
+ end
153
+ end
154
+
155
+ end
@@ -0,0 +1,45 @@
1
+ module Calagator
2
+
3
+ class Event < ActiveRecord::Base
4
+ class Overview
5
+ def today
6
+ Event.non_duplicates.within_dates(today_date, tomorrow_date)
7
+ end
8
+
9
+ def tomorrow
10
+ Event.non_duplicates.within_dates(tomorrow_date, after_tomorrow_date)
11
+ end
12
+
13
+ def later
14
+ Event.non_duplicates.within_dates(after_tomorrow_date, future_cutoff_date)
15
+ end
16
+
17
+ def more
18
+ Event.after_date(future_cutoff_date).first
19
+ end
20
+
21
+ def tags
22
+ @tags ||= Event.tag_counts_on(:tags, limit: 100, conditions: "tags_count >= 10").sort_by(&:name)
23
+ end
24
+
25
+ private
26
+
27
+ def today_date
28
+ @today_date ||= Time.zone.now.beginning_of_day
29
+ end
30
+
31
+ def tomorrow_date
32
+ today_date + 1.day
33
+ end
34
+
35
+ def after_tomorrow_date
36
+ tomorrow_date + 1.day
37
+ end
38
+
39
+ def future_cutoff_date
40
+ today_date + 2.weeks
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,58 @@
1
+ module Calagator
2
+
3
+ class Event < ActiveRecord::Base
4
+ class Saver < Struct.new(:event, :params, :failure)
5
+ def save
6
+ event.attributes = params[:event]
7
+ event.venue = find_or_initialize_venue
8
+ event.start_time = [ params[:start_date], params[:start_time] ]
9
+ event.end_time = [ params[:end_date], params[:end_time] ]
10
+ event.tags.reload # Reload the #tags association because its members may have been modified when #tag_list was set above.
11
+
12
+ attempt_save?
13
+ end
14
+
15
+ def has_new_venue?
16
+ return unless event.venue
17
+ event.venue.previous_changes["id"] == [nil, event.venue.id] && params[:venue_name].present?
18
+ end
19
+
20
+ private
21
+
22
+ def find_or_initialize_venue
23
+ if params[:event] && params[:event][:venue_id].present?
24
+ Venue.find(params[:event][:venue_id]).progenitor
25
+ else
26
+ Venue.find_or_initialize_by_title(params[:venue_name]).progenitor
27
+ end
28
+ end
29
+
30
+ def attempt_save?
31
+ !spam? && !preview? && event.save
32
+ end
33
+
34
+ def spam?
35
+ evil_robot? || too_many_links?
36
+ end
37
+
38
+ def evil_robot?
39
+ if params[:trap_field].present?
40
+ self.failure = "<h3>Evil Robot</h3> We didn't save this event because we think you're an evil robot. If you're really not an evil robot, look at the form instructions more carefully. If this doesn't work please file a bug report and let us know."
41
+ end
42
+ end
43
+
44
+ def too_many_links?
45
+ if event.description.present? && event.description.scan(/https?:\/\//i).size > 3
46
+ self.failure = "We allow a maximum of 3 links in a description. You have too many links."
47
+ end
48
+ end
49
+
50
+ def preview?
51
+ if params[:preview]
52
+ event.valid?
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,72 @@
1
+ module Calagator
2
+
3
+ class Event < ActiveRecord::Base
4
+ class Search < Struct.new(:query, :tag, :order, :current)
5
+ def initialize attributes = {}
6
+ members.each do |key|
7
+ send "#{key}=", attributes[key]
8
+ end
9
+ self.order ||= "date"
10
+ validate!
11
+ end
12
+
13
+ # Return +events+ grouped by currentness using a data structure like:
14
+ #
15
+ # {
16
+ # :current => [ my_current_event, my_other_current_event ],
17
+ # :past => [ my_past_event ],
18
+ # }
19
+ def grouped_events
20
+ grouped = events.group_by(&:current?)
21
+ grouped = { current: grouped[true] || [], past: grouped[false] || [] }
22
+ grouped[:current].reverse! if order.to_s == "date"
23
+ grouped[:past] = [] if current
24
+ grouped
25
+ end
26
+
27
+ def current
28
+ ["1", "true"].include?(super)
29
+ end
30
+
31
+ def events
32
+ @events ||= if tag
33
+ Event.search_tag(tag, order: order, current: current)
34
+ else
35
+ Event.search(query, order: order, skip_old: current)
36
+ end
37
+ end
38
+
39
+ def failure_message
40
+ @failure_message
41
+ end
42
+
43
+ def hard_failure?
44
+ @hard_failure
45
+ end
46
+
47
+ private
48
+
49
+ def validate!
50
+ unless %w(date name title venue score).include?(order) || order.blank?
51
+ @failure_message = "Unknown ordering option #{order.inspect}, sorting by date instead."
52
+ end
53
+
54
+ if tag.present? && order == "score"
55
+ @failure_message = "You cannot sort tags by score"
56
+ end
57
+
58
+ if [query, tag].all?(&:blank?)
59
+ @failure_message = "You must enter a search query"
60
+ @hard_failure = true
61
+ end
62
+
63
+ if [query, tag].all?(&:present?)
64
+ # TODO make it possible to search by tag and query simultaneously
65
+ @failure_message = "You can't search by tag and query at the same time"
66
+ @hard_failure = true
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,28 @@
1
+ module Calagator
2
+
3
+ class Event < ActiveRecord::Base
4
+ class SearchEngine
5
+ cattr_accessor(:kind) { :sql }
6
+
7
+ def self.search(*args)
8
+ search_engine.search(*args)
9
+ end
10
+
11
+ def self.use(kind)
12
+ self.kind = kind
13
+ search_engine.configure if search_engine.respond_to?(:configure)
14
+ end
15
+
16
+ def self.score?
17
+ search_engine.score?
18
+ end
19
+
20
+ private_class_method
21
+
22
+ def self.search_engine
23
+ kind == :sunspot ? ApacheSunspot : Sql
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,106 @@
1
+ module Calagator
2
+
3
+ class Event < ActiveRecord::Base
4
+ class SearchEngine
5
+ class ApacheSunspot < Struct.new(:query, :opts)
6
+ # Return an Array of non-duplicate Event instances matching the search +query+..
7
+ #
8
+ # Options:
9
+ # * :order => How to order the entries? Defaults to :score. Permitted values:
10
+ # * :score => Sort with most relevant matches first
11
+ # * :date => Sort by date
12
+ # * :name => Sort by event title
13
+ # * :title => same as :name
14
+ # * :venue => Sort by venue title
15
+ # * :limit => Maximum number of entries to return. Defaults to 50.
16
+ # * :skip_old => Return old entries? Defaults to false.
17
+ def self.search(*args)
18
+ new(*args).all
19
+ end
20
+
21
+ def self.score?
22
+ true
23
+ end
24
+
25
+ def self.configure
26
+ Event.searchable do
27
+ text :title, :default_boost => 3
28
+ string :title
29
+
30
+ text :description
31
+
32
+ text :tag_list, :default_boost => 3
33
+
34
+ text :url
35
+
36
+ time :start_time
37
+ time :end_time
38
+
39
+ text :venue_title
40
+ string :venue_title
41
+
42
+ boolean(:duplicate) { |event| event.duplicate? }
43
+ end
44
+ end
45
+
46
+ def self.configured?
47
+ Event.respond_to?(:solr_search)
48
+ end
49
+
50
+ def initialize(*args)
51
+ super
52
+ self.class.configure unless self.class.configured?
53
+ end
54
+
55
+ def all
56
+ current_events + past_events
57
+ end
58
+
59
+ private
60
+
61
+ def current_events
62
+ search(true)
63
+ end
64
+
65
+ def past_events
66
+ skip_old ? [] : search(false)
67
+ end
68
+
69
+ def search(current)
70
+ Event.solr_search do
71
+ keywords query
72
+ order_by *order
73
+ order_by :start_time, :desc
74
+ with :duplicate, false
75
+ data_accessor_for(Event).include = [:venue]
76
+
77
+ method = current ? :greater_than_or_equal_to : :less_than
78
+ with(:start_time).send(method, Date.yesterday.to_time)
79
+ end.results.take(limit)
80
+ end
81
+
82
+ def order
83
+ case opts[:order].try(:to_sym)
84
+ when :date
85
+ [:start_time, :desc]
86
+ when :venue, :location
87
+ [:venue_title, :asc]
88
+ when :name, :title
89
+ [:title, :asc]
90
+ else
91
+ [:score, :desc]
92
+ end
93
+ end
94
+
95
+ def skip_old
96
+ opts[:skip_old] == true
97
+ end
98
+
99
+ def limit
100
+ opts[:limit] || 50
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ end