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,94 @@
1
+ module Calagator
2
+
3
+ class SourcesController < ApplicationController
4
+ # POST /import
5
+ # POST /import.xml
6
+ def import
7
+ @importer = Source::Importer.new(params[:source])
8
+ respond_to do |format|
9
+ if @importer.import
10
+ format.html { redirect_to events_path, flash: { success: render_to_string(layout: false) } }
11
+ format.xml { render xml: @importer.source, events: @importer.events }
12
+ else
13
+ format.html { redirect_to new_source_path(url: @importer.source.url), flash: { failure: @importer.failure_message } }
14
+ format.xml { render xml: @importer.source.errors, status: :unprocessable_entity }
15
+ end
16
+ end
17
+ end
18
+
19
+ # GET /sources
20
+ # GET /sources.xml
21
+ def index
22
+ @sources = Source.listing
23
+
24
+ respond_to do |format|
25
+ format.html { @sources = @sources.paginate(page: params[:page], per_page: params[:per_page]) }
26
+ format.xml { render xml: @sources }
27
+ end
28
+ end
29
+
30
+ # GET /sources/1
31
+ # GET /sources/1.xml
32
+ def show
33
+ @source = Source.find(params[:id], include: [:events, :venues])
34
+ respond_to do |format|
35
+ format.html # show.html.erb
36
+ format.xml { render xml: @source }
37
+ end
38
+ rescue ActiveRecord::RecordNotFound => error
39
+ flash[:failure] = error.to_s if params[:id] != "import"
40
+ redirect_to new_source_path
41
+ end
42
+
43
+ # GET /sources/new
44
+ def new
45
+ @source = Source.new
46
+ @source.url = params[:url] if params[:url].present?
47
+ end
48
+
49
+ # GET /sources/1/edit
50
+ def edit
51
+ @source = Source.find(params[:id])
52
+ end
53
+
54
+ # POST /sources
55
+ # POST /sources.xml
56
+ def create
57
+ @source = Source.new
58
+ create_or_update
59
+ end
60
+
61
+ # PUT /sources/1
62
+ # PUT /sources/1.xml
63
+ def update
64
+ @source = Source.find(params[:id])
65
+ create_or_update
66
+ end
67
+
68
+ def create_or_update
69
+ respond_to do |format|
70
+ if @source.update_attributes(params[:source])
71
+ format.html { redirect_to @source, notice: 'Source was successfully saved.' }
72
+ format.xml { render xml: @source, status: :created, location: @source }
73
+ else
74
+ format.html { render action: @source.new_record? ? "new" : "edit" }
75
+ format.xml { render xml: @source.errors, status: :unprocessable_entity }
76
+ end
77
+ end
78
+ end
79
+ private :create_or_update
80
+
81
+ # DELETE /sources/1
82
+ # DELETE /sources/1.xml
83
+ def destroy
84
+ @source = Source.find(params[:id])
85
+ @source.destroy
86
+
87
+ respond_to do |format|
88
+ format.html { redirect_to sources_url }
89
+ format.xml { head :ok }
90
+ end
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,133 @@
1
+ require "calagator/duplicate_checking/controller_actions"
2
+
3
+ module Calagator
4
+
5
+ class VenuesController < ApplicationController
6
+ # Provides #duplicates and #squash_many_duplicates
7
+ include DuplicateChecking::ControllerActions
8
+ require_admin only: [:duplicates, :squash_many_duplicates]
9
+
10
+ # GET /venues
11
+ # GET /venues.xml
12
+ def index
13
+ @search = Venue::Search.new(params)
14
+ @venues = @search.venues
15
+
16
+ respond_to do |format|
17
+ format.html # index.html.erb
18
+ format.kml # index.kml.erb
19
+ format.xml { render :xml => @venues }
20
+ format.json { render :json => @venues, :callback => params[:callback] }
21
+ format.js { render :json => @venues, :callback => params[:callback] }
22
+ end
23
+ end
24
+
25
+ def autocomplete
26
+ @venues = Venue
27
+ .non_duplicates
28
+ .in_business
29
+ .where(["LOWER(title) LIKE ?", "%#{params[:term]}%".downcase])
30
+ .order('LOWER(title)')
31
+
32
+ respond_to do |format|
33
+ format.json { render :json => @venues, :callback => params[:callback] }
34
+ end
35
+ end
36
+
37
+ # GET /venues/map
38
+ def map
39
+ @venues = Venue.non_duplicates.in_business
40
+ end
41
+
42
+ # GET /venues/1
43
+ # GET /venues/1.xml
44
+ def show
45
+ @venue = Venue.find(params[:id], include: :source)
46
+
47
+ return redirect_to @venue.duplicate_of if @venue.duplicate?
48
+
49
+ respond_to do |format|
50
+ format.html
51
+ format.xml { render xml: @venue }
52
+ format.json { render json: @venue, callback: params[:callback] }
53
+ format.ics { render ics: @venue.events.order("start_time ASC").non_duplicates }
54
+ end
55
+
56
+ rescue ActiveRecord::RecordNotFound => e
57
+ flash[:failure] = e.to_s
58
+ redirect_to venues_path
59
+ end
60
+
61
+ # GET /venues/new
62
+ # GET /venues/new.xml
63
+ def new
64
+ @venue = Venue.new
65
+ render layout: params[:layout] != "false"
66
+ end
67
+
68
+ # GET /venues/1/edit
69
+ def edit
70
+ @venue = Venue.find(params[:id])
71
+ end
72
+
73
+ # POST /venues
74
+ # POST /venues.xml
75
+ def create
76
+ @venue = Venue.new
77
+ create_or_update
78
+ end
79
+
80
+ # PUT /venues/1
81
+ # PUT /venues/1.xml
82
+ def update
83
+ @venue = Venue.find(params[:id])
84
+ create_or_update
85
+ end
86
+
87
+ def create_or_update
88
+ @venue.attributes = params[:venue]
89
+ respond_to do |format|
90
+ if !evil_robot? && @venue.save
91
+ format.html { redirect_to from_event || @venue, flash: { success: "Venue was successfully saved." } }
92
+ format.xml { render xml: @venue, status: :created, location: @venue }
93
+ else
94
+ format.html { render action: @venue.new_record? ? "new" : "edit" }
95
+ format.xml { render xml: @venue.errors, status: :unprocessable_entity }
96
+ end
97
+ end
98
+ end
99
+
100
+ # DELETE /venues/1
101
+ # DELETE /venues/1.xml
102
+ def destroy
103
+ @venue = Venue.find(params[:id])
104
+
105
+ if @venue.events.any?
106
+ message = "Cannot destroy venue that has associated events, you must reassociate all its events first."
107
+ respond_to do |format|
108
+ format.html { redirect_to @venue, flash: { failure: message } }
109
+ format.xml { render xml: message, status: :unprocessable_entity }
110
+ end
111
+ else
112
+ @venue.destroy
113
+ respond_to do |format|
114
+ format.html { redirect_to venues_path, flash: { success: %("#{@venue.title}" has been deleted) } }
115
+ format.xml { head :ok }
116
+ end
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def evil_robot?
123
+ if params[:trap_field].present?
124
+ flash[:failure] = "<h3>Evil Robot</h3> We didn't save this venue 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."
125
+ end
126
+ end
127
+
128
+ def from_event
129
+ Event.find_by_id(params[:from_event])
130
+ end
131
+ end
132
+
133
+ end
@@ -0,0 +1,20 @@
1
+ module Calagator
2
+
3
+ class VersionsController < ApplicationController
4
+ def edit
5
+ @version = Version.find(params[:id])
6
+ @record = @version.next.try(:reify) || @version.item || @version.reify
7
+
8
+ singular = @record.class.name.singularize.underscore.split("/").last
9
+ plural = @record.class.name.pluralize.underscore.split("/").last
10
+ self.instance_variable_set("@#{singular}", @record)
11
+
12
+ if request.xhr?
13
+ render :partial => "calagator/#{plural}/form", :locals => { singular.to_sym => @record }
14
+ else
15
+ render "calagator/#{plural}/edit", :locals => { singular.to_sym => @record }
16
+ end
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,128 @@
1
+ # Methods added to this helper will be available to all templates in the application.
2
+ module Calagator
3
+
4
+ module ApplicationHelper
5
+ include TimeRangeHelper
6
+
7
+ # Returns HTML string of an event or venue description for display in a view.
8
+ def format_description(string)
9
+ sanitize(auto_link(upgrade_br(markdown(string))))
10
+ end
11
+
12
+ def markdown(text)
13
+ BlueCloth.new(text, :relaxed => true).to_html
14
+ end
15
+
16
+ # Return a HTML string with the BR tags converted to XHTML compliant markup.
17
+ def upgrade_br(content)
18
+ content.gsub('<br>','<br />')
19
+ end
20
+
21
+ FLASH_TYPES = [:success, :failure]
22
+
23
+ def render_flash
24
+ FLASH_TYPES.map{|type|
25
+ next unless flash[type].present?
26
+ content_tag(:div, :class => "flash #{type} flash_#{type}") do
27
+ "#{type == :failure ? 'ERROR: ' : ''}#{flash[type]}".html_safe
28
+ end
29
+ }.compact.join.html_safe
30
+ end
31
+
32
+ def datetime_format(time,format)
33
+ format = format.gsub(/(%[dHImU])/,'*\1')
34
+ time.strftime(format).gsub(/\*0*/,'').html_safe
35
+ end
36
+
37
+ def self.source_code_version_raw
38
+ # Return a string describing the source code version being used
39
+ return "" unless system("git status 2>&1 >/dev/null")
40
+ " - Git timestamp: #{`git log -1 --format=format:"%ad" 2>&1`}"
41
+ rescue Errno::ENOENT
42
+ # Fail quietly if that didn't work; we don't want to get in the way.
43
+ ""
44
+ end
45
+
46
+ Calagator::ApplicationController::SOURCE_CODE_VERSION = self.source_code_version_raw
47
+
48
+ def source_code_version
49
+ Calagator::ApplicationController::SOURCE_CODE_VERSION
50
+ end
51
+
52
+ # returns html markup with source (if any), imported/created time, and - if modified - modified time
53
+ def datestamp(item)
54
+ stamp = "This item was "
55
+ if item.source.nil?
56
+ stamp << "added directly to #{Calagator.title}"
57
+ else
58
+ stamp << "imported from " << link_to(truncate(item.source.name, :length => 40), url_for(item.source))
59
+ end
60
+ stamp << " <br />" << content_tag(:strong, normalize_time(item.created_at, :format => :html) )
61
+ if item.updated_at > item.created_at
62
+ stamp << " and last updated <br />" << content_tag(:strong, normalize_time(item.updated_at, :format => :html) )
63
+ end
64
+ stamp << "."
65
+ stamp.html_safe
66
+ end
67
+
68
+ # Caches +block+ in view only if the +condition+ is true.
69
+ # http://skionrails.wordpress.com/2008/05/22/conditional-fragment-caching/
70
+ def cache_if(condition, name={}, &block)
71
+ if condition
72
+ cache(name, &block)
73
+ else
74
+ block.call
75
+ end
76
+ end
77
+
78
+ # Insert a chunk of +javascript+ into the page, and execute it when the document is ready.
79
+ def insert_javascript(javascript)
80
+ content_for(:javascript_insert) do
81
+ (<<-HERE).html_safe
82
+ <script>
83
+ $(document).ready(function() {
84
+ #{javascript}
85
+ });
86
+ </script>
87
+ HERE
88
+ end
89
+ end
90
+
91
+ # Focus cursor on DOM element specified by +xpath_query+ using JavaScript, e.g.:
92
+ #
93
+ # <% focus_on '#search_field' %>
94
+ def focus_on(xpath_query)
95
+ insert_javascript "$('#{xpath_query}').focus();"
96
+ end
97
+
98
+ # Set the first tabindex to DOM element specified by +xpath_query+.
99
+ def tabindex_on(xpath_query)
100
+ #insert_javascript "$('#{xpath_query}')[0].tabindex = 1;"
101
+ #insert_javascript "$('#{xpath_query}')[0].attributes['tabindex'] = 1;"
102
+ # TODO Figure out how to set tabindex, because neither of these work right.
103
+ end
104
+
105
+ # Returns a string with safely encoded entities thanks to #h, while preserving any existing HTML entities.
106
+ def cleanse(string)
107
+ return escape_once(string)
108
+ end
109
+
110
+ def subnav_class_for(controller_name, action_name)
111
+ return [
112
+ "#{controller.controller_name}_#{controller.action_name}_subnav",
113
+ controller.controller_name == controller_name && controller.action_name == action_name ?
114
+ "active" :
115
+ nil
116
+ ].compact.join(" ")
117
+ end
118
+
119
+ # String name of the mobile preference cookie's name, e.g. "calagator_mobile".
120
+ MOBILE_COOKIE_NAME = "#{SECRETS.session_name}_mobile"
121
+
122
+ # CGI escape a string-like object. The issue is that CGI::escape fails if used on a RailsXss SafeBuffer: https://github.com/rails/rails_xss/issues/8
123
+ def cgi_escape(data)
124
+ return CGI::escape(data.to_str)
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,184 @@
1
+ module Calagator
2
+
3
+ module EventsHelper
4
+ include TimeRangeHelper # provides normalize_time
5
+
6
+ def today_tomorrow_or_weekday(record)
7
+ if record.ongoing?
8
+ "Started #{record.start_time.strftime('%A')}"
9
+ else
10
+ record.start_time.strftime('%A')
11
+ end
12
+ end
13
+
14
+ # calculate rowspans for an array of events
15
+ # argument: array of events
16
+ # returns: rowspans, an array in which each entry corresponds to an event in events;
17
+ # each entry is number of rows spanned by today_tomorrow_weekday entry, if any, to left of event
18
+ # entry will be > 0 for first event of day, 0 for other events
19
+ def calculate_rowspans(events)
20
+ previous_start_time = nil
21
+ rowspans = Array.new(events.size, 0)
22
+ first_event_of_day = 0
23
+
24
+ events.each_with_index do |event, index|
25
+ new_day = previous_start_time.nil? || (previous_start_time.to_date != event.start_time.to_date)
26
+ if new_day
27
+ first_event_of_day = index
28
+ end
29
+ rowspans[first_event_of_day] += 1
30
+ previous_start_time = event.start_time
31
+ end
32
+
33
+ return rowspans
34
+ end
35
+
36
+ def google_maps_url(address)
37
+ return "http://maps.google.com/maps?q=#{cgi_escape(address)}"
38
+ end
39
+
40
+ #---[ Event sorting ]----------------------------------------------------
41
+
42
+ # Return a link for sorting by +key+ (e.g., "name").
43
+ def events_sort_link(key)
44
+ if key.present?
45
+ link_to(sorting_label_for(key, @tag.present?), url_for(params.merge(:order => key)))
46
+ else
47
+ link_to('Default', url_for(params.tap { |o| o.delete :order }))
48
+ end
49
+ end
50
+
51
+ # Return a human-readable label describing what the sorting +key+ is.
52
+ def events_sort_label(key)
53
+ if key.present? or @tag.present?
54
+ sanitize " by <strong>#{sorting_label_for(key, @tag.present?)}.</strong>"
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ #---[ Feed links ]------------------------------------------------------
61
+
62
+ # Returns a URL for an events feed.
63
+ #
64
+ # @param [Hash] filter Options for filtering. If values are defined, returns
65
+ # a link to all events. If a :query is defined, returns a link to search
66
+ # events' text by that query. If a :tag is defined, returns a link to search
67
+ # events with that tag.
68
+ # @param [Hash] common Options for the URL helper, such as :protocol, :format
69
+ # and such.
70
+ #
71
+ # @raise [ArgumentError] Raised if given invalid filter options.
72
+ #
73
+ # @return [String] URL
74
+ def _events_feed_linker(filter={}, common={})
75
+ # Delete blank filter options because this method is typically called with
76
+ # both a :tag and :query filter, but only one will actually be set.
77
+ filter.delete_if { |key, value| value.blank? }
78
+
79
+ if (unknown = filter.keys - [:query, :tag]).present?
80
+ raise ArgumentError, "Unknown option(s): #{unknown.inspect}"
81
+ end
82
+
83
+ return filter.present? ?
84
+ search_events_url(common.merge(filter)) :
85
+ events_url(common)
86
+ end
87
+
88
+ GOOGLE_EVENT_SUBSCRIBE_BASE = "http://www.google.com/calendar/render?cid="
89
+
90
+ # Returns a Google Calendar subscription URL.
91
+ #
92
+ # @see #_events_feed_linker for details on parameters and exceptions.
93
+ def google_events_subscription_link(filter={})
94
+ link = _events_feed_linker(filter, :format => "ics")
95
+ return "#{GOOGLE_EVENT_SUBSCRIBE_BASE}#{CGI::escape(link)}"
96
+ end
97
+
98
+ # Returns an iCalendar subscription URL.
99
+ #
100
+ # @see #_events_feed_linker for details on parameters and exceptions.
101
+ def icalendar_feed_link(filter={})
102
+ return _events_feed_linker(filter, :protocol => "webcal", :format => "ics")
103
+ end
104
+
105
+ # Returns an iCalendar export URL.
106
+ #
107
+ # @see #_events_feed_linker for details on parameters and exceptions.
108
+ def icalendar_export_link(filter={})
109
+ return _events_feed_linker(filter, :format => "ics")
110
+ end
111
+
112
+ # Returns an ATOM subscription URL.
113
+ #
114
+ # @see #_events_feed_linker for details on parameters and exceptions.
115
+ def atom_feed_link(filter={})
116
+ return _events_feed_linker(filter, :format => "atom")
117
+ end
118
+
119
+ #--[ Sharing buttons ]-----------------------------------------
120
+
121
+ # Tweet button text
122
+
123
+ def tweet_text(event)
124
+ lengths = tweet_text_sizer(event)
125
+
126
+ result = []
127
+ result << "#{truncate(event.title, :length => lengths[:title])} -"
128
+ result << event.start_time.strftime("%I:%M%p %m.%d.%Y") # "04:00PM 08.01.2012"
129
+ result << "@ #{truncate(event.venue.title, :length => lengths[:venue])}" if event.venue
130
+
131
+ return result.join(" ")
132
+ end
133
+
134
+ # Will increase the maximum length of either the event title or venue
135
+ # if one of the two is shorter than the maximum: 46
136
+
137
+ def tweet_text_sizer(event)
138
+ title_length = event.title.length
139
+ venue_length = (event.venue.try(:title) || "").length
140
+ title_length = tweet_length(title_length, venue_length)
141
+ venue_length = tweet_length(venue_length, title_length)
142
+ { title: title_length, venue: title_length }
143
+ end
144
+ private :tweet_text_sizer
145
+
146
+ def tweet_length length_a, length_b
147
+ if length_a > 46
148
+ length_a = 46
149
+ length_a += (46-length_b) if length_b < 46
150
+ end
151
+ length_a
152
+ end
153
+ private :tweet_length
154
+
155
+ def shareable_event_url(event)
156
+ event_url(event) if event.persisted?
157
+ end
158
+
159
+ #---[ Sort labels ]-------------------------------------------
160
+
161
+ # Return the label for the +sorting_key+ (e.g. 'score'). Optionally set the
162
+ # +is_searching_by_tag+, to constrain options available for tag searches.
163
+ def sorting_label_for(sorting_key=nil, is_searching_by_tag=false)
164
+ sorting_key = sorting_key.to_s
165
+ if sorting_key.present? and SORTING_LABELS.has_key?(sorting_key)
166
+ SORTING_LABELS[sorting_key]
167
+ elsif is_searching_by_tag
168
+ SORTING_LABELS['date']
169
+ else
170
+ SORTING_LABELS['score']
171
+ end
172
+ end
173
+
174
+ # Labels displayed for sorting options:
175
+ SORTING_LABELS = {
176
+ 'name' => 'Event Name',
177
+ 'venue' => 'Location',
178
+ 'score' => 'Relevance',
179
+ 'date' => 'Date',
180
+ }
181
+ private_constant :SORTING_LABELS
182
+ end
183
+
184
+ end