From 351a5ba1d2b7423e8cd52b4214f7f68f29161a72 Mon Sep 17 00:00:00 2001 From: Adamantish Date: Sat, 11 Feb 2017 14:27:40 +0100 Subject: [PATCH] Begin with clone of bucketlist app --- .gitignore | 20 + .rspec | 2 + Gemfile | 37 + Gemfile.lock | 272 + Procfile | 1 + README.md | 9 + Rakefile | 6 + app/assets/images/.keep | 0 app/assets/images/edit-icon.png | Bin 0 -> 491 bytes app/assets/images/no_images.png | Bin 0 -> 3806 bytes app/assets/images/sprite.png | Bin 0 -> 52898 bytes app/assets/javascripts/application.js | 16 + app/assets/javascripts/cable.coffee | 2 + .../javascripts/channels/to_do_sync.coffee | 9 + app/assets/javascripts/helpers.js | 4 + app/assets/javascripts/main.js | 79 + app/assets/javascripts/maps.js | 93 + app/assets/javascripts/modal.js | 27 + app/assets/javascripts/search.js | 50 + app/assets/javascripts/to_dos.js | 143 + app/assets/stylesheets/application.css | 15 + app/assets/stylesheets/main.scss | 49 + app/assets/stylesheets/maps.scss | 29 + app/assets/stylesheets/search.scss | 34 + app/assets/stylesheets/to_dos.scss | 275 + app/channels/application_cable/connection.rb | 9 + app/channels/to_do_sync_channel.rb | 10 + app/controllers/application_controller.rb | 5 + app/controllers/concerns/.keep | 0 app/controllers/home_controller.rb | 29 + app/controllers/to_dos_controller.rb | 114 + .../traveller/sessions_controller.rb | 26 + app/helpers/application_helper.rb | 11 + app/helpers/to_dos_helper.rb | 4 + app/mailers/.keep | 0 app/models/.keep | 0 app/models/concerns/.keep | 0 app/models/destination.rb | 4 + app/models/like.rb | 9 + app/models/to_do.rb | 21 + app/models/traveller.rb | 8 + app/services/flickr_service.rb | 65 + app/views/devise/confirmations/new.html.erb | 16 + .../mailer/confirmation_instructions.html.erb | 5 + .../reset_password_instructions.html.erb | 8 + .../mailer/unlock_instructions.html.erb | 7 + app/views/devise/passwords/edit.html.erb | 25 + app/views/devise/passwords/new.html.erb | 16 + app/views/devise/registrations/edit.html.erb | 39 + app/views/devise/registrations/new.html.erb | 34 + app/views/devise/sessions/new.html.erb | 26 + app/views/devise/shared/_links.html.erb | 25 + app/views/devise/unlocks/new.html.erb | 16 + app/views/home/index.html.haml | 45 + app/views/layouts/application.html.erb | 28 + app/views/to_dos/_edit.html.haml | 18 + app/views/to_dos/_edited.js.erb | 13 + app/views/to_dos/_liked.js.erb | 2 + app/views/to_dos/_photos.html.haml | 3 + app/views/to_dos/_to_do.html.haml | 18 + app/views/to_dos/_to_dos.html.haml | 2 + app/views/to_dos/create.js.erb | 16 + app/views/to_dos/edit.js.erb | 10 + app/views/to_dos/photos.js.erb | 3 + bin/bundle | 3 + bin/rails | 4 + bin/rake | 4 + bin/setup | 34 + bin/update | 29 + config.ru | 4 + config/application.rb | 15 + config/boot.rb | 3 + config/cable.yml | 10 + config/cucumber.yml | 8 + config/database.yml | 27 + config/environment.rb | 5 + config/environments/development.rb | 56 + config/environments/production.rb | 83 + config/environments/test.rb | 44 + ...e_record_belongs_to_required_by_default.rb | 6 + .../application_controller_renderer.rb | 6 + config/initializers/assets.rb | 11 + config/initializers/backtrace_silencers.rb | 7 + config/initializers/callback_terminator.rb | 6 + config/initializers/cookies_serializer.rb | 5 + config/initializers/cors.rb | 16 + config/initializers/devise.rb | 262 + .../initializers/filter_parameter_logging.rb | 4 + config/initializers/geocoder.rb | 21 + config/initializers/inflections.rb | 16 + config/initializers/mime_types.rb | 4 + config/initializers/per_form_csrf_tokens.rb | 4 + .../request_forgery_protection.rb | 4 + config/initializers/session_store.rb | 3 + config/initializers/wrap_parameters.rb | 14 + config/locales/devise.en.yml | 60 + config/locales/en.yml | 23 + config/puma.rb | 44 + config/routes.rb | 43 + config/secrets.yml | 24 + db/migrate/20151123114549_create_to_dos.rb | 12 + .../20151123115340_create_destinations.rb | 9 + .../20151123120854_add_lat_lng_to_to_dos.rb | 6 + ...20151126185647_devise_create_travellers.rb | 42 + ...20151126185934_adding_name_to_traveller.rb | 5 + db/migrate/20151126201047_create_likes.rb | 10 + ...0160119202250_remove_string_from_to_dos.rb | 5 + ...119202633_create_traveller_id_in_to_dos.rb | 5 + .../20160209114909_add_indexes_to_to_dos.rb | 6 + db/schema.rb | 65 + db/seeds.rb | 27 + features/destination.feature | 12 + features/likes.feature | 35 + features/search.feature | 35 + features/step_definitions/.gitkeep | 0 features/step_definitions/general_steps.rb | 19 + features/step_definitions/likes_steps.rb | 44 + features/step_definitions/search_steps.rb | 53 + .../step_definitions/to_do_photos_step.rb | 8 + features/step_definitions/to_dos_step.rb | 88 + features/step_definitions/traveller_steps.rb | 50 + features/support/env.rb | 58 + features/support/geocoder.rb | 23 + features/support/poltergeist.rb | 2 + features/to_do_photos.feature | 8 + features/todos.feature | 55 + features/traveller.feature | 26 + features/travellers.feature | 14 + lib/assets/.keep | 0 lib/tasks/.keep | 0 lib/tasks/cucumber.rake | 65 + log/.keep | 0 public/.keep | 0 public/404.html | 67 + public/422.html | 67 + public/500.html | 66 + ...fest-2f86cee30812646184df809f7ff9ccd2.json | 1 + ...4917f533d0ac5941dfa334dd0faf257d7c3529.css | 1 + ...ddb41ec90b006ef5f75427a778769fc80a2cbda.js | 27 + ...4836661717741a409734d61764f4bde7f3aca42.js | 12803 ++++++++++++++++ ...ecff82421df0691a3c726236c46d5b297a9012.css | 1 + ...922a928a2eaa7387dc9464268eee95b64c54c2.css | 1 + ...3d04db962c4dda140163db3223471b3a166152.png | Bin 0 -> 491 bytes ...644dd28fcb10fcee4d1e1616596ce13f6f4bd7.png | Bin 0 -> 3806 bytes ...7999250e32774d23e602a9f940b656da522c59.png | Bin 0 -> 52898 bytes public/favicon.ico | 0 public/images/.keep | 0 public/images/no_images.png | Bin 0 -> 3999 bytes public/robots.txt | 5 + script/cucumber | 10 + spec/controllers/to_dos_controller_spec.rb | 16 + spec/fixtures/Elephant_ride.json | 1 + spec/helpers/to_dos_helper_spec.rb | 15 + spec/models/destination_spec.rb | 20 + spec/models/like_spec.rb | 8 + spec/models/to_do_spec.rb | 81 + spec/models/traveller_spec.rb | 5 + spec/rails_helper.rb | 52 + spec/services/flickr_service_spec.rb | 35 + spec/spec_helper.rb | 92 + vendor/assets/javascripts/.keep | 0 vendor/assets/stylesheets/.keep | 0 162 files changed, 16935 insertions(+) create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Procfile create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app/assets/images/.keep create mode 100644 app/assets/images/edit-icon.png create mode 100644 app/assets/images/no_images.png create mode 100644 app/assets/images/sprite.png create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/cable.coffee create mode 100644 app/assets/javascripts/channels/to_do_sync.coffee create mode 100644 app/assets/javascripts/helpers.js create mode 100644 app/assets/javascripts/main.js create mode 100644 app/assets/javascripts/maps.js create mode 100644 app/assets/javascripts/modal.js create mode 100644 app/assets/javascripts/search.js create mode 100644 app/assets/javascripts/to_dos.js create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/assets/stylesheets/main.scss create mode 100644 app/assets/stylesheets/maps.scss create mode 100644 app/assets/stylesheets/search.scss create mode 100644 app/assets/stylesheets/to_dos.scss create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/channels/to_do_sync_channel.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/controllers/home_controller.rb create mode 100644 app/controllers/to_dos_controller.rb create mode 100644 app/controllers/traveller/sessions_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/to_dos_helper.rb create mode 100644 app/mailers/.keep create mode 100644 app/models/.keep create mode 100644 app/models/concerns/.keep create mode 100644 app/models/destination.rb create mode 100644 app/models/like.rb create mode 100644 app/models/to_do.rb create mode 100644 app/models/traveller.rb create mode 100644 app/services/flickr_service.rb create mode 100644 app/views/devise/confirmations/new.html.erb create mode 100644 app/views/devise/mailer/confirmation_instructions.html.erb create mode 100644 app/views/devise/mailer/reset_password_instructions.html.erb create mode 100644 app/views/devise/mailer/unlock_instructions.html.erb create mode 100644 app/views/devise/passwords/edit.html.erb create mode 100644 app/views/devise/passwords/new.html.erb create mode 100644 app/views/devise/registrations/edit.html.erb create mode 100644 app/views/devise/registrations/new.html.erb create mode 100644 app/views/devise/sessions/new.html.erb create mode 100644 app/views/devise/shared/_links.html.erb create mode 100644 app/views/devise/unlocks/new.html.erb create mode 100644 app/views/home/index.html.haml create mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/to_dos/_edit.html.haml create mode 100644 app/views/to_dos/_edited.js.erb create mode 100644 app/views/to_dos/_liked.js.erb create mode 100644 app/views/to_dos/_photos.html.haml create mode 100644 app/views/to_dos/_to_do.html.haml create mode 100644 app/views/to_dos/_to_dos.html.haml create mode 100644 app/views/to_dos/create.js.erb create mode 100644 app/views/to_dos/edit.js.erb create mode 100644 app/views/to_dos/photos.js.erb create mode 100755 bin/bundle create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/setup create mode 100755 bin/update create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/cable.yml create mode 100644 config/cucumber.yml create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/initializers/active_record_belongs_to_required_by_default.rb create mode 100644 config/initializers/application_controller_renderer.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/callback_terminator.rb create mode 100644 config/initializers/cookies_serializer.rb create mode 100644 config/initializers/cors.rb create mode 100644 config/initializers/devise.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/geocoder.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/per_form_csrf_tokens.rb create mode 100644 config/initializers/request_forgery_protection.rb create mode 100644 config/initializers/session_store.rb create mode 100644 config/initializers/wrap_parameters.rb create mode 100644 config/locales/devise.en.yml create mode 100644 config/locales/en.yml create mode 100644 config/puma.rb create mode 100644 config/routes.rb create mode 100644 config/secrets.yml create mode 100644 db/migrate/20151123114549_create_to_dos.rb create mode 100644 db/migrate/20151123115340_create_destinations.rb create mode 100644 db/migrate/20151123120854_add_lat_lng_to_to_dos.rb create mode 100644 db/migrate/20151126185647_devise_create_travellers.rb create mode 100644 db/migrate/20151126185934_adding_name_to_traveller.rb create mode 100644 db/migrate/20151126201047_create_likes.rb create mode 100644 db/migrate/20160119202250_remove_string_from_to_dos.rb create mode 100644 db/migrate/20160119202633_create_traveller_id_in_to_dos.rb create mode 100644 db/migrate/20160209114909_add_indexes_to_to_dos.rb create mode 100644 db/schema.rb create mode 100644 db/seeds.rb create mode 100644 features/destination.feature create mode 100644 features/likes.feature create mode 100644 features/search.feature create mode 100644 features/step_definitions/.gitkeep create mode 100644 features/step_definitions/general_steps.rb create mode 100644 features/step_definitions/likes_steps.rb create mode 100644 features/step_definitions/search_steps.rb create mode 100644 features/step_definitions/to_do_photos_step.rb create mode 100644 features/step_definitions/to_dos_step.rb create mode 100644 features/step_definitions/traveller_steps.rb create mode 100644 features/support/env.rb create mode 100644 features/support/geocoder.rb create mode 100644 features/support/poltergeist.rb create mode 100644 features/to_do_photos.feature create mode 100644 features/todos.feature create mode 100644 features/traveller.feature create mode 100644 features/travellers.feature create mode 100644 lib/assets/.keep create mode 100644 lib/tasks/.keep create mode 100644 lib/tasks/cucumber.rake create mode 100644 log/.keep create mode 100644 public/.keep create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/assets/.sprockets-manifest-2f86cee30812646184df809f7ff9ccd2.json create mode 100644 public/assets/application-19febd0dade963d92ae36f32e74917f533d0ac5941dfa334dd0faf257d7c3529.css create mode 100644 public/assets/application-43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda.js create mode 100644 public/assets/application-56cdee3e9e8a05ee19f5e55d64836661717741a409734d61764f4bde7f3aca42.js create mode 100644 public/assets/application-abbc89feee2d8330614b2b7f80ecff82421df0691a3c726236c46d5b297a9012.css create mode 100644 public/assets/application-d032d0f02c65304d41e10e2007922a928a2eaa7387dc9464268eee95b64c54c2.css create mode 100644 public/assets/edit-icon-ea81e372bebfef7d976544f8bc3d04db962c4dda140163db3223471b3a166152.png create mode 100644 public/assets/no_images-a34c4e783250d4e13c97a78b6c644dd28fcb10fcee4d1e1616596ce13f6f4bd7.png create mode 100644 public/assets/sprite-ce6a244ccf859f15d251e698a97999250e32774d23e602a9f940b656da522c59.png create mode 100644 public/favicon.ico create mode 100644 public/images/.keep create mode 100644 public/images/no_images.png create mode 100644 public/robots.txt create mode 100755 script/cucumber create mode 100644 spec/controllers/to_dos_controller_spec.rb create mode 100644 spec/fixtures/Elephant_ride.json create mode 100644 spec/helpers/to_dos_helper_spec.rb create mode 100644 spec/models/destination_spec.rb create mode 100644 spec/models/like_spec.rb create mode 100644 spec/models/to_do_spec.rb create mode 100644 spec/models/traveller_spec.rb create mode 100644 spec/rails_helper.rb create mode 100644 spec/services/flickr_service_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 vendor/assets/javascripts/.keep create mode 100644 vendor/assets/stylesheets/.keep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6890126 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +!/log/.keep +/tmp + +# Ignore source of countries sample webpage +/public/countries.html diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..8877b35 --- /dev/null +++ b/Gemfile @@ -0,0 +1,37 @@ +ruby '2.3.0' +source 'https://rubygems.org' + +gem 'pg' +gem 'rails_12factor', group: :production +gem 'rails', github: "rails/rails" + +gem 'sass-rails', '~> 5.0' +gem 'uglifier', '>= 1.3.0' +gem 'jquery-rails' +gem 'geocoder' +gem 'haml-rails' +gem 'underscore-rails' +gem 'httparty' +gem 'devise', github: 'plataformatec/devise' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug' + gem 'rspec-rails' + gem 'cucumber-rails', :require => false + gem 'pry-byebug' +end + +group :development do + # Access an IRB console on exception pages or by using <%= console %> in views + gem 'web-console', '~> 2.0' + # gem 'spring' +end + +group :test do + gem 'database_cleaner' + gem 'launchy' + gem 'poltergeist' + gem 'capybara' + # gem 'selenium' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..1a17553 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,272 @@ +GIT + remote: git://github.com/plataformatec/devise.git + revision: 7c75ebe672f11fd26cdd241263ba78ad1276abba + specs: + devise (4.0.0.rc1) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0, < 5.1) + responders + warden (~> 1.2.3) + +GIT + remote: git://github.com/rails/rails.git + revision: 9b5ae716db601a8a34a4227c9c4ef2ec9cf90f64 + specs: + actioncable (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + nio4r (~> 1.2) + websocket-driver (~> 0.6.1) + actionmailer (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + actionview (= 5.0.0.beta2) + activejob (= 5.0.0.beta2) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (5.0.0.beta2) + actionview (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) + rack (~> 2.x) + rack-test (~> 0.6.3) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.0.beta2) + activesupport (= 5.0.0.beta2) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (5.0.0.beta2) + activesupport (= 5.0.0.beta2) + globalid (>= 0.3.6) + activemodel (5.0.0.beta2) + activesupport (= 5.0.0.beta2) + activerecord (5.0.0.beta2) + activemodel (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) + arel (~> 7.0) + activesupport (5.0.0.beta2) + concurrent-ruby (~> 1.0) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + rails (5.0.0.beta2) + actioncable (= 5.0.0.beta2) + actionmailer (= 5.0.0.beta2) + actionpack (= 5.0.0.beta2) + actionview (= 5.0.0.beta2) + activejob (= 5.0.0.beta2) + activemodel (= 5.0.0.beta2) + activerecord (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) + bundler (>= 1.3.0, < 2.0) + railties (= 5.0.0.beta2) + sprockets-rails (>= 2.0.0) + railties (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + +GEM + remote: https://rubygems.org/ + specs: + addressable (2.4.0) + arel (7.0.0) + bcrypt (3.1.10) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.2) + byebug (8.2.2) + capybara (2.6.2) + addressable + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + cliver (0.3.2) + coderay (1.1.0) + concurrent-ruby (1.0.0) + cucumber (2.3.2) + builder (>= 2.1.2) + cucumber-core (~> 1.4.0) + cucumber-wire (~> 0.0.1) + diff-lcs (>= 1.1.3) + gherkin (~> 3.2.0) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.1.2) + cucumber-core (1.4.0) + gherkin (~> 3.2.0) + cucumber-rails (1.4.3) + capybara (>= 1.1.2, < 3) + cucumber (>= 1.3.8, < 3) + mime-types (>= 1.16, < 4) + nokogiri (~> 1.5) + railties (>= 3, < 5) + cucumber-wire (0.0.1) + database_cleaner (1.5.1) + debug_inspector (0.0.2) + diff-lcs (1.2.5) + erubis (2.7.0) + execjs (2.6.0) + geocoder (1.3.0) + gherkin (3.2.0) + globalid (0.3.6) + activesupport (>= 4.1.0) + haml (4.0.7) + tilt + haml-rails (0.9.0) + actionpack (>= 4.0.1) + activesupport (>= 4.0.1) + haml (>= 4.0.6, < 5.0) + html2haml (>= 1.0.1) + railties (>= 4.0.1) + html2haml (2.0.0) + erubis (~> 2.7.0) + haml (~> 4.0.0) + nokogiri (~> 1.6.0) + ruby_parser (~> 3.5) + httparty (0.13.7) + json (~> 1.8) + multi_xml (>= 0.5.2) + i18n (0.7.0) + jquery-rails (4.1.0) + rails-dom-testing (~> 1.0) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (1.8.3) + launchy (2.4.3) + addressable (~> 2.3) + loofah (2.0.3) + nokogiri (>= 1.5.9) + mail (2.6.3) + mime-types (>= 1.16, < 3) + method_source (0.8.2) + mime-types (2.99) + mini_portile2 (2.0.0) + minitest (5.8.4) + multi_json (1.11.2) + multi_test (0.1.2) + multi_xml (0.5.5) + nio4r (1.2.1) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) + orm_adapter (0.5.0) + pg (0.18.4) + poltergeist (1.9.0) + capybara (~> 2.1) + cliver (~> 0.3.1) + multi_json (~> 1.0) + websocket-driver (>= 0.2.0) + pry (0.10.3) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.3.0) + byebug (~> 8.0) + pry (~> 0.10) + rack (2.0.0.alpha) + json + rack-test (0.6.3) + rack (>= 1.0) + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + rails_12factor (0.0.3) + rails_serve_static_assets + rails_stdout_logging + rails_serve_static_assets (0.0.5) + rails_stdout_logging (0.0.4) + rake (10.5.0) + responders (2.1.1) + railties (>= 4.2.0, < 5.1) + rspec-core (3.1.7) + rspec-support (~> 3.1.0) + rspec-expectations (3.1.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.1.0) + rspec-mocks (3.1.3) + rspec-support (~> 3.1.0) + rspec-rails (3.1.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.1.0) + rspec-expectations (~> 3.1.0) + rspec-mocks (~> 3.1.0) + rspec-support (~> 3.1.0) + rspec-support (3.1.2) + ruby_parser (3.7.3) + sexp_processor (~> 4.1) + sass (3.4.21) + sass-rails (5.0.4) + railties (>= 4.0.0, < 5.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sexp_processor (4.6.1) + slop (3.6.0) + sprockets (3.5.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.0.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.2) + tzinfo (1.2.2) + thread_safe (~> 0.1) + uglifier (2.7.2) + execjs (>= 0.3.0) + json (>= 1.8.0) + underscore-rails (1.8.3) + warden (1.2.6) + rack (>= 1.0) + web-console (2.3.0) + activemodel (>= 4.0) + binding_of_caller (>= 0.7.2) + railties (>= 4.0) + sprockets-rails (>= 2.0, < 4.0) + websocket-driver (0.6.3) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + xpath (2.0.0) + nokogiri (~> 1.3) + +PLATFORMS + ruby + +DEPENDENCIES + byebug + capybara + cucumber-rails + database_cleaner + devise! + geocoder + haml-rails + httparty + jquery-rails + launchy + pg + poltergeist + pry-byebug + rails! + rails_12factor + rspec-rails + sass-rails (~> 5.0) + uglifier (>= 1.3.0) + underscore-rails + web-console (~> 2.0) + +BUNDLED WITH + 1.11.2 diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..3946eaa --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: RAILS_ENV=production bundle exec rails server -p $PORT \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bed3082 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +##Bucket List Travel Map + +### [Try it](https://bucketlist77.herokuapp.com/) +(may take about 10 seconds for initial load while the server dyno spins up) + +A single page Rails and AJAX app making use of Flickr, google maps and geocoding APIs +Makes use of OO javascript to re-invent part of a frontend framework, RESTful JSON, SCSS, HAML and sprites. + +Was mostly made during the We Got Coders training. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..ba6b733 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +Rails.application.load_tasks diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/images/edit-icon.png b/app/assets/images/edit-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..86088926c76b933dc83a8b8d1deaff05d0584025 GIT binary patch literal 491 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 + # Called when the subscription is ready for use on the server + + disconnected: -> + # Called when the subscription has been terminated by the server + + received: (data) -> + # Called when there's incoming data on the websocket for this channel diff --git a/app/assets/javascripts/helpers.js b/app/assets/javascripts/helpers.js new file mode 100644 index 0000000..7721f2e --- /dev/null +++ b/app/assets/javascripts/helpers.js @@ -0,0 +1,4 @@ +var Helpers = function(){}; + +Helpers.prototype.transitionContent + diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js new file mode 100644 index 0000000..78f215c --- /dev/null +++ b/app/assets/javascripts/main.js @@ -0,0 +1,79 @@ +$(document).ready( function() { + + $('#btn--search').addClass('undisplayed'); + $('#container--search-results').addClass('undisplayed'); + + $('body').css("opacity", "1") +}); + +function initMap() { + + window.POLL_INTERVAL = 2000 + window.markers = []; + window.map = new google.maps.Map($('#map-canvas')[0], {} ); + window.map.toDos = $('#map-canvas').data('toDos'); + window.map.latlngbounds = new google.maps.LatLngBounds(); + + if ( window.map.toDos.length > 0 ) { resetMarkers() } + + initProcesses(); + initEventListeners(); + +}; + +function initEventListeners() { + + map.addListener("idle", showToDosInBounds); + + window.$body = $('body') + $body.click( hideGreetingAndUnblur ) + + var $searchInput = $('#search') + + $searchInput.blur( function() { + $('#container--search-results').addClass('undisplayed'); + }); + + $searchInput.keyup( function(e){ + if( e.keyCode == 13 ) { + $('#search').blur(); + } else { + return makeSearch(e); + }; + } + ); + + $('#destination__search-opts').on("change", function() { + var dest_id = $("#destination__search-opts").val(); + showOnlyMarkersFor("destination_id", dest_id); + }); + + $('#select--sort-to_dos').on("change", function(e){ + sortElements("#to_dos", $(e.target).val()); + }); + + window.onfocus = function() { runSyncPolling( true ) }; + window.onblur = function() { runSyncPolling( false ) }; + +}; + +function initProcesses(){ + + getLatestToDoTimestamps(); + + runSyncPolling( true ); +}; + +function hideGreetingAndUnblur(){ + + $('#greetings').hide(); + $blurContainer = $('#blur-container') + // Setting this listener here instead of the initialising function to avoid a second jquery selection. + $blurContainer.on('transitionend webkitTransitionEnd oTransitionEnd', function() { + $bucket = $('#bucket') + $bucket.css("top","2px").unwrap(); + }) + $blurContainer.css("-webkit-filter","blur(0px) brightness(1)"); + + +}; \ No newline at end of file diff --git a/app/assets/javascripts/maps.js b/app/assets/javascripts/maps.js new file mode 100644 index 0000000..132157d --- /dev/null +++ b/app/assets/javascripts/maps.js @@ -0,0 +1,93 @@ +"use strict"; + +function resetMarkers(){ + + clearMarkers(); + addMarkers(window.map.toDos); + +}; + +function addMarker(lat, lng, title, id) { + + var myLatLng = new google.maps.LatLng(lat, lng); + var marker = new google.maps.Marker({ + + position: myLatLng, + title: title, + id: id, + map: window.map + + }); + + markers.push(marker); + window.map.latlngbounds.extend(myLatLng); + // marker.addListener("click", function() { showModal(toDo)}); + fitMapToMarkers(); + +}; + +function addMarkers(newToDos) { + + _(newToDos).each(function(toDo) { + addMarker(toDo.lat, toDo.lng, toDo.description, toDo.id); + }); + +} + +function fitMapToMarkers() { + + map.fitBounds(map.latlngbounds); + + if ( map.zoom > 12 ){ map.setZoom(12); } + + map.setCenter(map.latlngbounds.getCenter()) ; + +}; + +function clearMarkers() { + + for (var i = 0; i < markers.length; i++) { + markers[i].setMap(null); + } + map.latlngbounds = new google.maps.LatLngBounds() + markers = []; + +}; + +function showOnlyMarkersFor(criteriaType, criteria){ + + clearMarkers(); + + var filteredToDos = map.toDos.filter(function(toDo) { + if ( criteriaType == "destination_id" ) { + return toDo.destination_id == criteria; + } else if ( criteriaType == "toDo_ids") { + return criteria[toDo.id] + }; + }); + + addMarkers(filteredToDos); + showToDosInBounds(); + +}; + +function showToDosInBounds() { + + map.toDos.forEach(function(toDo) { + var $foundItem = $("div[data_id=" + toDo.id + "]") + var marker = markers.filter( function (marker) { + return marker.id == toDo.id; + })[0]; + + $foundItem.addClass("undisplayed"); + + if ( !marker ) { return } + + var inBounds = map.getBounds().contains(marker.getPosition()) + + if ( inBounds ){ + $foundItem.removeClass("undisplayed"); + } + + }); +}; diff --git a/app/assets/javascripts/modal.js b/app/assets/javascripts/modal.js new file mode 100644 index 0000000..3693f47 --- /dev/null +++ b/app/assets/javascripts/modal.js @@ -0,0 +1,27 @@ +// function showModal( template, data ) { + +// var $modalbk = $('
').attr("id", "modal-background") +// var $modal = $('
').attr("id", "modal") + +// debugger; + +// $modal.html(template(data)) + +// $modalbk.click(function(){ +// this.remove() +// }) + +// $modalbk.append($modal) +// // $('body').append($modalbk.append($modal)) + +// $('body').append($modalbk) +// } + + +// function renderModal(data) { + +// var template = _.template("

<%= description %>

Location: <%= address %>

") + +// return + +// }; \ No newline at end of file diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js new file mode 100644 index 0000000..739a17d --- /dev/null +++ b/app/assets/javascripts/search.js @@ -0,0 +1,50 @@ + + +function makeSearch(e){ + + var searchTerm = $(e.currentTarget).val(); + + if ( searchTerm.length < 1){ + $('#container--search-results').addClass('undisplayed') + resetMarkers(); + } else { + + // Going to the server is really unnecessary seeing as we've already fetched the whole search set but let's pretend we paged it instead. + + $.ajax({ + url: "/to_dos/search", + data: { + search: searchTerm + }, + // And when it the data comes back we'll take advantage of the fact that actually we have everything needed. + // If this were to become an infinite scroll kind of partial load I'd change this to identify any toDos not already in the DOM and append them + success: function(results) { renderSearchResults( results ) } + }); + }; + +}; + +renderSearchResults = function(results) { + + var searchResultListTempl = _.template("

<%= resultsNum == 0 ? 'No' : resultsNum %> Results

    "); + var searchResultTempl = _.template("
  • <%= description %> in <%= destination %>
  • "); + var $resultList = $('#search-results'); + + $resultList.html(searchResultListTempl({ resultsNum: results.length })); + var $ulResults = $resultList.find('.results'); + + var ids = {}; + + _(results).each(function(result){ + ids[result.id] = "Nowt" + var liResult = searchResultTempl({description: result.description, destination: result.destination_name}) + $ulResults.append(liResult) + }); + + // Filter displayed map markers + showOnlyMarkersFor("toDo_ids", ids); + + $('#container--search-results').removeClass('undisplayed') + +}; + diff --git a/app/assets/javascripts/to_dos.js b/app/assets/javascripts/to_dos.js new file mode 100644 index 0000000..8ce863c --- /dev/null +++ b/app/assets/javascripts/to_dos.js @@ -0,0 +1,143 @@ + + +window.toDos = {}; + +var ToDo = function( id, selector ){ + + this.id = id; + this.$el = $(selector); + this.displayHeight = this.$el.height(); + this.editHeight = "20.7rem"; + this.deleteHeight = "5.2rem"; + this.html = $(selector).html(); + window.toDos[id] = this; + +}; + +ToDo.prototype.transitionContent = function( newHTMLContent , setHeight ) { + + _this = this; + if ( !setHeight ) { setHeight = this.displayHeight }; + this.displayHeight = this.$el.height(); + this.$el.animate( { + height: setHeight + ,queue: false + } + , function() { _this.$el.css( { height: "auto" }) } + ) + this.$el.children().fadeOut( + { + complete: function(){ + _this.$el.html( newHTMLContent ); + } + } + ); + +}; + + +ToDo.prototype.cancelEdit = function() { + + this.transitionContent( this.html ); + +}; + +ToDo.prototype.insertUpdatedToDo = function( newHTML ) { + + this.transitionContent( newHTML ); + +}; + +ToDo.prototype.deleteMe = function() { + + var thisToDo = this + $.ajax({ + url: "/to_dos/" + this.id , + type: "DELETE" + , + success: function() { + thisToDo.$el.remove(); + } + }); + +}; + +// ---------------------------------------------------- + +var deleteToDoConfirm = function(id) { + + var storedToDo = new ToDo(id, "div[data_id=" + id + "]"); + deleteConfirmHTML = "

    Are you sure you want to delete?

    "; + storedToDo.transitionContent( deleteConfirmHTML, storedToDo.deleteHeight); + +}; + +$(document).ready(function() { + + toggleNewToDo(); + +}); + +function sortElements(selector, sort_by) { + + var el = $(selector) + var $kids = $(selector).children() + + $kids.sort( function(a,b) { + var aComparator = a.getAttribute(sort_by); + var bComparator = b.getAttribute(sort_by); + + if (aComparator < bComparator) { + return -1 + } else { + return 1 + } + }); + + $kids.detach().appendTo(el); + +}; + +function cancelToDoEdit(toDoID) { + + if(toDoID){ + return toDos[toDoID].cancelEdit(); + } else { + toggleNewToDo(); + } + +}; + +function toggleNewToDo() { + + $('#add__to_do').children().toggleClass("undisplayed"); + +}; + +function getLatestToDoTimestamps(){ + + $.ajax( { + url: '/to_dos/latest_timestamps', + success: function(result) { window.toDoLatestTimestamps = result.timestamps } + }); + +}; + +function getUnsyncedToDos() { + + $.ajax( { + url: '/to_dos/unsynced_changes', + data: window.toDoLatestTimestamps + }); + +}; + +function runSyncPolling( bool ) { + + if ( bool ) { + window.checkSyncInterval = setInterval( getUnsyncedToDos , POLL_INTERVAL ) + } else { + clearInterval( window.checkSyncInterval ) + }; + +} \ No newline at end of file diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000..f9cd5b3 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss new file mode 100644 index 0000000..1967081 --- /dev/null +++ b/app/assets/stylesheets/main.scss @@ -0,0 +1,49 @@ +body { + position: relative; + background-color: whitesmoke; + font-size: 1.2rem; + font-family: helvetica; + top: 2.5em; + opacity: 0; + -webkit-transition: opacity 5s; + transition: opacity 3s; +} + + +#greetings > * { + margin: 0.2em; + margin-bottom: 0.3em; +} + +.center-align { + width: 100%; + position: fixed; + text-align: center; +} + +#greetings { + + border: solid white 1px; + width: 19em; + margin-left: auto; + margin-right:auto; + color: white; + padding: 2em; + font-family: Trebuchet MS; + font-size: 1.1em; + margin-top: 3em; + +} + + +#blur-container { + width: 100%; + height: 800px; + top: 0; + position: fixed; + -webkit-filter: blur(30px) brightness(0.8) ; + filter: blur(30px) brightness(0.80) ; + + transition: -webkit-filter 0.6s; + +} \ No newline at end of file diff --git a/app/assets/stylesheets/maps.scss b/app/assets/stylesheets/maps.scss new file mode 100644 index 0000000..1d1f2a5 --- /dev/null +++ b/app/assets/stylesheets/maps.scss @@ -0,0 +1,29 @@ + + +#map-container { + position: fixed; + min-width: 100%; + min-height: 100%; + height: 900px ; + z-index: -2; + top: 2.5em; +} + +#map-canvas { + display: inline-block; + width: 100%; + height: 100%; + background-color: #B3D1FF + +} + +#map-overlay { + background-color: #B3D1FF; + width: 100%; + height: 100%; + position: relative; + opacity: 1; + transition: opacity 0.5s; +} + + diff --git a/app/assets/stylesheets/search.scss b/app/assets/stylesheets/search.scss new file mode 100644 index 0000000..131e086 --- /dev/null +++ b/app/assets/stylesheets/search.scss @@ -0,0 +1,34 @@ +#destination__search-opts { + width: 17rem; + font-size: 1.3rem; + font-style: italic; +} + +#fieldset__search { + background-color: transparent; + width: 20em; + position: fixed; + top: 0.2rem; + left: 13rem; + z-index: 1; +} + +#search { + width: 16.1rem; + font-size: 1.2rem; + padding: 0.2rem; +} + +#btn--search { + position: relative; + left: 16em; + top: -1.58em; + font-size: 1em; +} + +#container--search-results{ + z-index: 1; + width:26em; + position: fixed; + top: 2.8em; +} \ No newline at end of file diff --git a/app/assets/stylesheets/to_dos.scss b/app/assets/stylesheets/to_dos.scss new file mode 100644 index 0000000..663c901 --- /dev/null +++ b/app/assets/stylesheets/to_dos.scss @@ -0,0 +1,275 @@ +// Place all the styles related to the to_dos controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +$tiny_icon_size: 18px; +$left_nav_width: 31.5rem; + + +.sprite { + + background: url(/assets/sprite.png); + + &--tiny { + @extend .sprite ; + width: $tiny_icon_size; + height: $tiny_icon_size; + + &__close { + @extend .sprite--tiny; + background-position: calc(23 * -#{$tiny_icon_size}) -108px; + } + + &__trash { + @extend .sprite--tiny ; + background-position: calc(25 * -#{$tiny_icon_size}) -108px; + } + + &__like { + @extend .sprite--tiny ; + background-position: calc(9 * -#{$tiny_icon_size}) -108px; + } + } +} + +.icon { + + top: 0; + position: relative; + cursor: pointer; + display: inline-block; + + &__close-edit { + @extend .icon; + left: 22.8em; + } + + &__trash { + @extend .icon; + + } + +} + +header.devise-header > p { + + margin: 0 0 0.1em 0; + position: fixed; + z-index: 1; + top: 3.3em; + padding: 0.5em; + border-radius: 4px; + margin-left: 72%; + margin-right: 1em; + color: white; + +} + +.alert { + background-color:rgba(224, 158, 188, 0.84); +} + +.notice { + background-color: rgba(119, 207, 183, 0.84); +} + +.to_do { + + transition: min-height; +} + +.heading { + + z-index: 1; + font-size: 1.3em; + font-weight: bold; + padding-bottom: 0.5em; + padding-left: 1em; + position: fixed; + width: 100%; + color: #f2f2f2; + background-color: rgb(174, 85, 118); + padding-top: 0.5em; + top: 0em; +} + +.h3 { + + display: block; + font-size: 1.17em; + padding-bottom: 0.3em; + font-weight: bold; + +} + +fieldset { + background: white; + border:none; +} + +fieldset > * { + font-size: 1.5rem; +} + +fieldset > label { + width: 2em; + display: block; + font-size: small; + color: #2E2E2E; + margin-bottom: 0.5em; +} + +fieldset > input , fieldset > select, #destination__search-opts, #select--sort-to_dos, #btn__new_to_do { + margin-left: 1em; + margin-right: auto; + border: solid #f2f2f2 1px; + border-radius: 3px; + padding-left: 0.3em; +} + + +/*.box-shadow--light { + box-shadow: 0.2em 0.2em 1.5em grey; +}*/ + +#to_dos { + position: relative; + z-index: -1; +} + +#select--sort-to_dos { + font-size: 1.3rem; + margin-right: 1rem; + font-style: italic; + + /*left: 51rem;*/ +} +/* +#new_to_do { + box-shadow: 0.2em 0.2em 1.5em grey; + background-color: white; + +}*/ + +.shadow-box { + + background-color: white; + box-shadow: 0.2em 0.5em 1.2em grey; + padding: 1.3em; + padding-bottom: 1.8rem; + margin-bottom: 2em; +} + +#bucket { + position: relative; + display: inline; + float:left; + top: 3em; + z-index: 0; + width: $left_nav_width; + display: inline; + +} + +#bucket fieldset select { + width: 25rem; +} + +.nav__left { + +} + +#nav-buttons { + + z-index: 1; + left: $left_nav_width; + position: fixed; + top: 0.7rem; + + +} + +#nav-buttons > a { + color: white; + border-left: solid 1px #444444; + padding: 0 1rem; + text-decoration: none; +} + +#add__to_do { + + position: relative; + top: -3px; + +} + +#btn__new_to_do { + + position: relative; + left: 7.5rem; + font-size: 1.3rem; + color: white; + background-color: #76CFBE; + width: 11.5rem; + height: 2.5rem; + padding: 0rem 0rem 0.1rem 0; + + &--label { + position: relative; + left: 9rem; + } +} + +#btn__add-to_do { + + padding: 0.5em; + font-size: 1em; + margin: 1em 0.5em; + cursor: pointer; +} + + +.click--photos { + display: block; + font-size: 1rem; + margin-top: 0.5rem; +} + +.container--like { + display: inline-block; +} + +.num_of_likes { + display: inline; + font-size: 1rem; + padding-top: 0px; + position: relative; + top: -3px; +} + +.thumbnail { + margin-right: -6px; + margin-bottom: -4px; +} + +.undisplayed { + display: none; +} + +.perm-undisplayed { + display: none; +} + +.edit-link { + position: relative; + display: inline-block; + /*cursor: pointer;*/ + +} + +.container__to_do_actions { + position: absolute; + text-align: right; + width: 28.5rem; +} + diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..2ea1783 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,9 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = current_traveller + end + end +end \ No newline at end of file diff --git a/app/channels/to_do_sync_channel.rb b/app/channels/to_do_sync_channel.rb new file mode 100644 index 0000000..9493eff --- /dev/null +++ b/app/channels/to_do_sync_channel.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. +class ToDoSyncChannel < ApplicationCable::Channel + def subscribed + # stream_from "some_channel" + end + + def unsubscribed + # Any cleanup needed when channel is unsubscribed + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..d83690e --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,5 @@ +class ApplicationController < ActionController::Base + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + protect_from_forgery with: :exception +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 0000000..cc4703f --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,29 @@ +class HomeController < ApplicationController + + TEST_EMAIL = "test@test.com" + + include ApplicationHelper + + def index + + login_as_test unless current_traveller || session["stay_logged_out"] == "Y" + + traveller_id = current_traveller.id if current_traveller + traveller_id ||= 0 + + @edit_or_new_to_do = ToDo.new() + @destinations = Destination.all + @destination_options = get_select_options(Destination.all) + @to_dos = ToDo.includes(:likes, :destination).where("traveller_id = ?", traveller_id) + @to_dos_json = to_dos_json( @to_dos ) + + end + +private + + def login_as_test + user = Traveller.where("email = ?", TEST_EMAIL).first + sign_in user + end + +end \ No newline at end of file diff --git a/app/controllers/to_dos_controller.rb b/app/controllers/to_dos_controller.rb new file mode 100644 index 0000000..a983e67 --- /dev/null +++ b/app/controllers/to_dos_controller.rb @@ -0,0 +1,114 @@ +class ToDosController < ApplicationController + + include ApplicationHelper + + + before_filter :put_current_traveller_in_params, only: [:create] + + def unsynced_changes + # If this were a heavily concurrent app I'd test for all types of change and concatenate rendered JS + # for all affected types. In this low traffic setting it's overkill. + + # This implementation will only action one change that's been made since last check. While the page has focus + # and checks are every 2 seconds this should be fine in low traffic. + + # To make this work more robustly I'd refactor to return JSON rather than JS. + + @new_to_do = ToDo.where("id > ?", params["latest_id"].to_i ).first if params["latest_id"] + @edited_to_do = ToDo.where("updated_at > ?",Time.at(params["latest_update"].to_i + 1 )).first if params["latest_update"] + + if @new_to_do + render "create.js.erb" + elsif @edited_to_do + render partial: 'edited' + else + render text: "" + end + + end + + def latest_timestamps + output = { latest_update: ToDo.maximum(:updated_at).to_i , + latest_id: ToDo.maximum(:id) } + render json: { timestamps: output } + end + + def create + @new_to_do = ToDo.create!(sane_params) + @new_to_do_json = [@new_to_do].to_json(except: %i(created_at, updated_at)) + @to_dos = []; @to_dos << @new_to_do + end + + def edit + @destination_options = get_select_options(Destination.all) + @edit_or_new_to_do = ToDo.find(params["id"]) + end + + def update + id = params["to_do"]["id"] + @edited_to_do = ToDo.update(id, sane_params) + render partial: 'edited' + end + + def destroy + ToDo.destroy(params["id"].to_i) + render text: "" + end + + def photos + @to_do_id = params['id'] + @photos = ToDo.find(@to_do_id).photos + end + + def create_like + @to_do = ToDo.find(params["id"]) + @to_do.travellers << current_traveller + @likes = @to_do.likes.count + render partial: 'liked' + end + + def delete_like + # TODO + end + + def search + + base_query = ToDo.includes(:destination).joins(:destination).where("traveller_id = ?", current_traveller.id) + @search_results = base_query.where("description || address || name ILIKE ?", "%#{params[:search]}%") + + @results_a = [] + @search_results.each do |obj| + h = obj.attributes + h["destination_name"] = obj.destination.name + @results_a << h + end + + @numOfSearchResults = @search_results.count == 0 ? "" : @search_results.count + + respond_to do |format| + format.json {render json: @results_a} + format.html do + + @edit_or_new_to_do = ToDo.new() + @destinations = Destination.all + @destination_options = get_select_options(@destinations) + @to_dos = ToDo.all.includes(:likes) + @to_dos_json = @to_dos.to_json(except: %i(id, created_at, updated_at)) + + render 'home/index' + + end + end + end + + private + + def sane_params + params.require(:to_do).permit(:traveller_id, :destination_id, :description, :address) + end + + def put_current_traveller_in_params + params["to_do"]["traveller_id"] = current_traveller.id + end + +end diff --git a/app/controllers/traveller/sessions_controller.rb b/app/controllers/traveller/sessions_controller.rb new file mode 100644 index 0000000..20c498b --- /dev/null +++ b/app/controllers/traveller/sessions_controller.rb @@ -0,0 +1,26 @@ +class Traveller::SessionsController < Devise::SessionsController +# before_filter :configure_sign_in_params, only: [:create] + + # GET /resource/sign_in + def new + super + end + + # POST /resource/sign_in + def create + super + end + + # DELETE /resource/sign_out + def destroy + super + session["stay_logged_out"] = "Y" + end + + # protected + + # If you have extra params to permit, append them to the sanitizer. + # def configure_sign_in_params + # devise_parameter_sanitizer.for(:sign_in) << :attribute + # end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..8c9c668 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,11 @@ +module ApplicationHelper + + def get_select_options(collection) + collection.collect{ |i| [i.name, i.id]} + end + + def to_dos_json(to_dos) + @to_dos_json = to_dos.to_json(except: %i(updated_datetime)) + end + +end diff --git a/app/helpers/to_dos_helper.rb b/app/helpers/to_dos_helper.rb new file mode 100644 index 0000000..59127fd --- /dev/null +++ b/app/helpers/to_dos_helper.rb @@ -0,0 +1,4 @@ +module ToDosHelper + + +end diff --git a/app/mailers/.keep b/app/mailers/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/.keep b/app/models/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/destination.rb b/app/models/destination.rb new file mode 100644 index 0000000..82f8e20 --- /dev/null +++ b/app/models/destination.rb @@ -0,0 +1,4 @@ +class Destination < ActiveRecord::Base + + has_many :to_dos +end diff --git a/app/models/like.rb b/app/models/like.rb new file mode 100644 index 0000000..f3ea245 --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,9 @@ +class Like < ActiveRecord::Base + + belongs_to :traveller + belongs_to :to_do + validates_presence_of :to_do, :traveller + + validates :to_do ,uniqueness: { scope: :traveller, message: "Hey you can't like it THAT much!"} + +end diff --git a/app/models/to_do.rb b/app/models/to_do.rb new file mode 100644 index 0000000..6059a11 --- /dev/null +++ b/app/models/to_do.rb @@ -0,0 +1,21 @@ +class ToDo < ActiveRecord::Base + + belongs_to :destination + belongs_to :traveller + + has_many :likes + has_many :travellers, :through => :likes + + geocoded_by :geocode_string, latitude: :lat, longitude: :lng + before_save :geocode + + validates :destination_id , numericality: { greater_than: 0 } + + def geocode_string + "#{address}, #{destination.name}" + end + + def photos + FlickrService.photos_of(search: description << "#{geocode_string.gsub(',','')}", lat: lat ,lng: lng) + end +end diff --git a/app/models/traveller.rb b/app/models/traveller.rb new file mode 100644 index 0000000..4923417 --- /dev/null +++ b/app/models/traveller.rb @@ -0,0 +1,8 @@ +class Traveller < ActiveRecord::Base + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :trackable, :validatable + + has_many :likes +end diff --git a/app/services/flickr_service.rb b/app/services/flickr_service.rb new file mode 100644 index 0000000..e1bbda3 --- /dev/null +++ b/app/services/flickr_service.rb @@ -0,0 +1,65 @@ +module FlickrService + include HTTParty + base_uri 'api.flickr.com:443' + + NO_PHOTOS_PLACEHOLDER = "no_images.png" + + PHOTO_SIZES = { + + "Square" => "_s" , + "Large Square" => "_q" , + "Thumbnail" => "_t" , + "Small" => "_m" , + "Small 320" => "_n" , + "Medium" => "" , + "Medium 640" => "_z" , + "Medium 800" => "_c" , + "Large" => "_b" , + "Original" => "_o" + } + + def self.photos_of(search: , lat: ,lng:) + response = self.get("/services/rest", {query: + {method:'flickr.photos.search', + tags: tag_list(search), + api_key: Rails.application.secrets[:flickr_api_key], + format:'json', + lat: lat, + lon: lng, + nojsoncallback: 1 + } + } + ) + response_photos = response["photos"]["photo"] + photos = [] + + + if response_photos.any? + photos = response_photos.map do |photo| + Photo.new(photo["id"], photo["title"], photo["secret"], photo["farm"], photo["server"]) + end + else + # TODO: move this logic to the view + photos << Photo.new(nil, "no images found") + end + + photos + + end + + def self.tag_list(search) + search.strip.chomp.split(" ").join(",") + end + + Photo = Struct.new(:id, :title, :secret, :farm, :server) do + def image_url(size = "Medium") + if id + "https://farm#{farm}.staticflickr.com/#{server}/#{id}_#{secret}#{PHOTO_SIZES[size]}.jpg" + else + "/assets/" << NO_PHOTOS_PLACEHOLDER + end + end + end + + +end \ No newline at end of file diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb new file mode 100644 index 0000000..826672f --- /dev/null +++ b/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

    Resend confirmation instructions

    + +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> + +
    + <%= f.label :email %>
    + <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
    + +
    + <%= f.submit "Resend confirmation instructions" %> +
    +<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 0000000..dc55f64 --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

    Welcome <%= @email %>!

    + +

    You can confirm your account email through the link below:

    + +

    <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

    diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 0000000..f667dc1 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

    Hello <%= @resource.email %>!

    + +

    Someone has requested a link to change your password. You can do this through the link below.

    + +

    <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

    + +

    If you didn't request this, please ignore this email.

    +

    Your password won't change until you access the link above and create a new one.

    diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 0000000..41e148b --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

    Hello <%= @resource.email %>!

    + +

    Your account has been locked due to an excessive number of unsuccessful sign in attempts.

    + +

    Click the link below to unlock your account:

    + +

    <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

    diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 0000000..6a796b0 --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

    Change your password

    + +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= devise_error_messages! %> + <%= f.hidden_field :reset_password_token %> + +
    + <%= f.label :password, "New password" %>
    + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
    + <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "off" %> +
    + +
    + <%= f.label :password_confirmation, "Confirm new password" %>
    + <%= f.password_field :password_confirmation, autocomplete: "off" %> +
    + +
    + <%= f.submit "Change my password" %> +
    +<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb new file mode 100644 index 0000000..3d6d11a --- /dev/null +++ b/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

    Forgot your password?

    + +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> + +
    + <%= f.label :email %>
    + <%= f.email_field :email, autofocus: true %> +
    + +
    + <%= f.submit "Send me reset password instructions" %> +
    +<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb new file mode 100644 index 0000000..3ea40f0 --- /dev/null +++ b/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,39 @@ +

    Edit <%= resource_name.to_s.humanize %>

    + +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= devise_error_messages! %> + +
    + <%= f.label :email %>
    + <%= f.email_field :email, autofocus: true %> +
    + + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
    Currently waiting confirmation for: <%= resource.unconfirmed_email %>
    + <% end %> + +
    + <%= f.label :password %> (leave blank if you don't want to change it)
    + <%= f.password_field :password, autocomplete: "off" %> +
    + +
    + <%= f.label :password_confirmation %>
    + <%= f.password_field :password_confirmation, autocomplete: "off" %> +
    + +
    + <%= f.label :current_password %> (we need your current password to confirm your changes)
    + <%= f.password_field :current_password, autocomplete: "off" %> +
    + +
    + <%= f.submit "Update" %> +
    +<% end %> + +

    Cancel my account

    + +

    Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

    + +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 0000000..589d917 --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,34 @@ +

    Sign up

    + +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= devise_error_messages! %> + +
    + <%= f.label :name %>
    + <%= f.text_field :name, autofocus: true %> +
    + +
    + <%= f.label :email %>
    + <%= f.email_field :email %> +
    + +
    + <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
    + <%= f.password_field :password, autocomplete: "off" %> +
    + +
    + <%= f.label :password_confirmation %>
    + <%= f.password_field :password_confirmation, autocomplete: "off" %> +
    + +
    + <%= f.submit "Sign up" %> +
    +<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 0000000..b261cfd --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,26 @@ +

    Log in

    + +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
    + <%= f.label :email %>
    + <%= f.email_field :email, autofocus: true %> +
    + +
    + <%= f.label :password %>
    + <%= f.password_field :password, autocomplete: "off" %> +
    + + <% if devise_mapping.rememberable? -%> +
    + <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
    + <% end -%> + +
    + <%= f.submit "Log in" %> +
    +<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb new file mode 100644 index 0000000..cd795ad --- /dev/null +++ b/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
    + <% end -%> +<% end -%> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb new file mode 100644 index 0000000..16586bc --- /dev/null +++ b/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

    Resend unlock instructions

    + +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> + +
    + <%= f.label :email %>
    + <%= f.email_field :email, autofocus: true %> +
    + +
    + <%= f.submit "Resend unlock instructions" %> +
    +<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml new file mode 100644 index 0000000..943137f --- /dev/null +++ b/app/views/home/index.html.haml @@ -0,0 +1,45 @@ +#blur-container + %nav + .heading The Bucket List + .search + = form_tag search_to_dos_path, method: "GET" , remote: true do + %fieldset#fieldset__search + = text_field_tag :search , "" , placeholder: "Search pebbles" + %input#btn--search{type: "submit"} + .shadow-box#container--search-results{class: @search_results ? "" : "undisplayed"} + %fieldset + #search-results + - if @search_results + = render partial: 'search' + #nav-buttons + = select_tag "Find Destination" , options_from_collection_for_select(@destinations, "id", "name") , { id: "destination__search-opts", prompt: "Search destination"} + + = select_tag "Sort ToDos" , "", {id: "select--sort-to_dos", prompt: "Sort by"} + - if current_traveller + = link_to "Log Out", destroy_traveller_session_path, {method: "DELETE"} + - else + = link_to "Register", new_traveller_registration_path + = link_to "Log In", new_traveller_session_path + + #bucket + + #add__to_do.shadow-box + = render partial: 'to_dos/edit', locals: { edit_to_do: @edit_or_new_to_do, destination_options: @destination_options} + %span#btn__new_to_do--label.undisplayed + + %button#btn__new_to_do.undisplayed{onclick: "toggleNewToDo()"} + Add a Pebble + + + #to_dos + = render partial: 'to_dos/to_dos' + + #map-container + #map-canvas{data: {to_dos: @to_dos_json }} + #map-overlay + +.center-align + #greetings + %h1 + B U C K E T  L I S T  + %p + Commodifying your deepest desires \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..a2b6bb4 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,28 @@ + + + + BucketList + <%= stylesheet_link_tag 'application', media: 'all' %> + <%= javascript_include_tag 'application' %> + <%= csrf_meta_tags %> + + + + +
    + <% if notice %> +

    <%= notice %>

    + <% end %> + <% if alert %> +

    <%= alert %>

    + <% end %> + +
    + + <%= yield %> + + + + diff --git a/app/views/to_dos/_edit.html.haml b/app/views/to_dos/_edit.html.haml new file mode 100644 index 0000000..0ce0b14 --- /dev/null +++ b/app/views/to_dos/_edit.html.haml @@ -0,0 +1,18 @@ += form_for edit_to_do ,{ remote: true } do |f| + = f.text_field :id , { class: "perm-undisplayed " } + + .icon__close-edit.sprite--tiny__close{ onclick: "cancelToDoEdit(#{edit_to_do.id})" } + + %fieldset + = f.label :destination + = f.select :destination_id , @destination_options, { prompt: 'Pick a destination' } + + %fieldset + = f.label "What" + = f.text_field :description + + %fieldset + = f.label "Where" + = f.text_field :address + %fieldset + %button#btn__add-to_do Bucket It diff --git a/app/views/to_dos/_edited.js.erb b/app/views/to_dos/_edited.js.erb new file mode 100644 index 0000000..b7a6bdd --- /dev/null +++ b/app/views/to_dos/_edited.js.erb @@ -0,0 +1,13 @@ + +runSyncPolling( false ); + +var toDoHTML = "<%= j(render partial: 'to_dos/to_do', locals: { t: @edited_to_do }) %>" + +var id = <%= @edited_to_do.id %> + +toDos[id].insertUpdatedToDo( $(toDoHTML).children() ); + +window.toDoLatestTimestamps.latest_update = <%= @edited_to_do.updated_at.to_i %> + +runSyncPolling( true ); + diff --git a/app/views/to_dos/_liked.js.erb b/app/views/to_dos/_liked.js.erb new file mode 100644 index 0000000..4e4a6eb --- /dev/null +++ b/app/views/to_dos/_liked.js.erb @@ -0,0 +1,2 @@ +console.log("Ruby says hi") +$('div[data_id=<%= @to_do.id %>]').find('.num_of_likes').text(<%= @likes %>) \ No newline at end of file diff --git a/app/views/to_dos/_photos.html.haml b/app/views/to_dos/_photos.html.haml new file mode 100644 index 0000000..20fb2af --- /dev/null +++ b/app/views/to_dos/_photos.html.haml @@ -0,0 +1,3 @@ + +- @photos.each do |photo| + %img.thumbnail{src: photo.image_url("Large Square")} \ No newline at end of file diff --git a/app/views/to_dos/_to_do.html.haml b/app/views/to_dos/_to_do.html.haml new file mode 100644 index 0000000..18f3820 --- /dev/null +++ b/app/views/to_dos/_to_do.html.haml @@ -0,0 +1,18 @@ +.to_do.shadow-box{data: "#{t.description}" , data_id: "#{t.id}"} + .container__to_do_actions + .icon__trash.sprite--tiny__trash{ onclick: "deleteToDoConfirm(#{t.id})"} + = link_to image_tag('edit-icon.png') , edit_to_do_path(t), { id: "for-test-edit-link" , class: "edit-link", remote: true } + + .h3.to_do__title + = t.description + %p + Location: #{t.address} + + .container--like + = link_to "".html_safe, create_like_to_do_path(t), { method: "POST", remote: true } + .num_of_likes + = t.likes.length == 0 ? "" : t.likes.length + + = link_to "show Flickr snaps", photos_to_do_path(t) , { class: "click--photos", remote: true } + + \ No newline at end of file diff --git a/app/views/to_dos/_to_dos.html.haml b/app/views/to_dos/_to_dos.html.haml new file mode 100644 index 0000000..5f66868 --- /dev/null +++ b/app/views/to_dos/_to_dos.html.haml @@ -0,0 +1,2 @@ +- @to_dos.each do |t| + = render partial: 'to_dos/to_do', locals: {t: t} \ No newline at end of file diff --git a/app/views/to_dos/create.js.erb b/app/views/to_dos/create.js.erb new file mode 100644 index 0000000..4e82aaf --- /dev/null +++ b/app/views/to_dos/create.js.erb @@ -0,0 +1,16 @@ +runSyncPolling( false ); + +addMarker(<%= "#{@new_to_do.lat}, #{@new_to_do.lng}" %>, "<%= @new_to_do.description %>", "<%= @new_to_do.id %>" ); +var itemHTML = "<%= escape_javascript(render partial: @new_to_do, locals: {t: @new_to_do}) %>"; + + +$('#to_dos').prepend(itemHTML); +var heightOffset = parseInt( $('#to_dos').children().eq(1).css("height").replace("px","") ) + 96 +heightOffset = "-" + heightOffset + "px" + +$('#to_dos').css("top", heightOffset).animate({ top: "0rem" }, { duration: 500 }); + +window.map.toDos.push( JSON.parse('<%= to_dos_json( @new_to_do ).html_safe %>') ) +window.toDoLatestTimestamps.latest_id = <%= @new_to_do.id %> + +runSyncPolling( true ); diff --git a/app/views/to_dos/edit.js.erb b/app/views/to_dos/edit.js.erb new file mode 100644 index 0000000..7162281 --- /dev/null +++ b/app/views/to_dos/edit.js.erb @@ -0,0 +1,10 @@ + +var toToID = <%= @edit_or_new_to_do.id %> +var storedToDo = new ToDo(toToID, "div[data_id=" + toToID + "]") + +var formHTML = "<%= j(render partial: 'edit', locals: {edit_to_do: @edit_or_new_to_do}) %>" + +formHTML = $( formHTML ); +storedToDo.transitionContent( formHTML, storedToDo.editHeight ); + + diff --git a/app/views/to_dos/photos.js.erb b/app/views/to_dos/photos.js.erb new file mode 100644 index 0000000..4a09095 --- /dev/null +++ b/app/views/to_dos/photos.js.erb @@ -0,0 +1,3 @@ +var photosHTML = "<%= escape_javascript(render partial: 'photos', locals: {photos: @photos}) %>" + +$("div[data_id=<%= @to_do_id %>]").append(photosHTML) \ No newline at end of file diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..66e9889 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..5191e69 --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..1724048 --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..589bee8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') or system!('bundle install') + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/update b/bin/update new file mode 100755 index 0000000..9851923 --- /dev/null +++ b/bin/update @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system 'bundle check' or system! 'bundle install' + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..bd83b25 --- /dev/null +++ b/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Rails.application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..2e0ddab --- /dev/null +++ b/config/application.rb @@ -0,0 +1,15 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module BucketList + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + end +end \ No newline at end of file diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..6b750f0 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000..aa4e832 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket. +production: + adapter: redis + url: redis://localhost:6379/1 + +development: + adapter: async + +test: + adapter: async diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000..a28eb5c --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,8 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~" +%> +default: <%= std_opts %> features +wip: --tags :3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~ diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..faa475d --- /dev/null +++ b/config/database.yml @@ -0,0 +1,27 @@ + + +default: &default + adapter: postgresql + encoding: utf8 + +development: + <<: *default + database: bucket_list_dev + host: localhost + port: 5432 + username: Adam + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: bucket_list_test + host: localhost + port: 5432 + username: Adam + +production: + <<: *default + database: bucket_list_prod + username: Adam \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..ee8d90d --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require File.expand_path('../application', __FILE__) + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..c9d06f3 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,56 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=172800' + } + else + config.action_controller.perform_caching = false + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + # config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..746f97a --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,83 @@ + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Action Cable endpoint configuration + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "bucket_list_#{Rails.env}" + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..1f90453 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,44 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=3600' + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Randomize the order test cases are executed. + config.active_support.test_order = :random + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/active_record_belongs_to_required_by_default.rb b/config/initializers/active_record_belongs_to_required_by_default.rb new file mode 100644 index 0000000..f613b40 --- /dev/null +++ b/config/initializers/active_record_belongs_to_required_by_default.rb @@ -0,0 +1,6 @@ +# Be sure to restart your server when you modify this file. + +# Require `belongs_to` associations by default. This is a new Rails 5.0 +# default, so it is introduced as a configuration option to ensure that apps +# made on earlier versions of Rails are not affected when upgrading. +Rails.application.config.active_record.belongs_to_required_by_default = true diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000..51639b6 --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,6 @@ +# Be sure to restart your server when you modify this file. + +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000..01ef3e6 --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,11 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/callback_terminator.rb b/config/initializers/callback_terminator.rb new file mode 100644 index 0000000..649e822 --- /dev/null +++ b/config/initializers/callback_terminator.rb @@ -0,0 +1,6 @@ +# Be sure to restart your server when you modify this file. + +# Do not halt callback chains when a callback returns false. This is a new +# Rails 5.0 default, so it is introduced as a configuration option to ensure +# that apps made with earlier versions of Rails are not affected when upgrading. +ActiveSupport.halt_callback_chains_on_return_false = false diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000..5a6a32d --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 0000000..3b1c1b5 --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +# Rails.application.config.middleware.insert_before 0, Rack::Cors do +# allow do +# origins 'example.com' +# +# resource '*', +# headers: :any, +# methods: [:get, :post, :put, :patch, :delete, :options, :head] +# end +# end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000..d4c5c39 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,262 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key` + # by default. You can change it below and use your own secret key. + config.secret_key = '6cacd99a3ee64d25abd2a17014c91ebda12b96889ba910248d334f10ce93743e19574890c34527b490593580ac1bd0417c4d855c7b141a1fb64499a8379d15a3' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 10. If + # using other encryptors, it sets how many times you want the password re-encrypted. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # encryptor), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 10 + + # Setup a pepper to generate the encrypted password. + # config.pepper = '83091f73f7081f4591833148c8864759723b85bfec7361daabc62d81163196435b858d87abe612a97dfc4f90ed952a183b0d21875a25b260b7900f9b61570296' + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. Default is 0.days, meaning + # the user cannot access the website without confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 8..72 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + # config.email_regexp = /\A[^@]+@[^@]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another encryption algorithm besides bcrypt (default). You can use + # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, + # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) + # and :restful_authentication_sha1 (then you should set stretches to 10, and copy + # REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' +end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..4a994e1 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/geocoder.rb b/config/initializers/geocoder.rb new file mode 100644 index 0000000..d0462a8 --- /dev/null +++ b/config/initializers/geocoder.rb @@ -0,0 +1,21 @@ +# Geocoder.configure( +# api_key: "AIzaSyCzDUPKjTkoTO3tXhN9rKBkCzvMfSKjo5E", # API key for geocoding service +# use_https: true, # use HTTPS for lookup requests? (if supported) +# lookup: :google # name of geocoding service (symbol) +# # Geocoding options +# # timeout: 3, # geocoding service timeout (secs) +# # language: :en, # ISO-639 language code +# # http_proxy: nil, # HTTP proxy server (user:pass@host:port) +# # https_proxy: nil, # HTTPS proxy server (user:pass@host:port) +# # cache: nil, # cache object (must respond to #[], #[]=, and #keys) +# # cache_prefix: 'geocoder:', # prefix (string) to use for all cache keys + +# # Exceptions that should not be rescued by default +# # (if you want to implement custom error handling); +# # supports SocketError and TimeoutError +# # always_raise: [], + +# # Calculation options +# # units: :mi, # :km for kilometers or :mi for miles +# # distances: :linear # :spherical or :linear +# ) diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..ac033bf --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000..dc18996 --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/per_form_csrf_tokens.rb b/config/initializers/per_form_csrf_tokens.rb new file mode 100644 index 0000000..1f569de --- /dev/null +++ b/config/initializers/per_form_csrf_tokens.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Enable per-form CSRF tokens. +Rails.application.config.action_controller.per_form_csrf_tokens = true diff --git a/config/initializers/request_forgery_protection.rb b/config/initializers/request_forgery_protection.rb new file mode 100644 index 0000000..3eab78a --- /dev/null +++ b/config/initializers/request_forgery_protection.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Enable origin-checking CSRF mitigation. +Rails.application.config.action_controller.forgery_protection_origin_check = true diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..7e8f927 --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.session_store :cookie_store, key: '_bucket_list_session' diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000..bbfc396 --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 0000000..ef82676 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,60 @@ +# Additional translations at https://github.com/plataformatec/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "You are a bucketter!" + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + updated: "Your account has been updated successfully." + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..0653957 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,23 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000..1bf274b --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,44 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum, this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted this block will be run, if you are using `preload_app!` +# option you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..9e68d8f --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,43 @@ +Rails.application.routes.draw do + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + + # Serve websocket cable requests in-process + # binding.pry + + + # The priority is based upon order of creation: first created -> highest priority. + # See how all your routes lay out with "rake routes". + + # You can have the root of your site routed with "root" + root 'home#index' + + devise_for :travellers, controllers: { + sessions: 'traveller/sessions' + } + + # Example of regular route: + # get 'products/:id' => 'catalog#view' + + # Example of named route that can be invoked with purchase_url(id: product.id) + # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase + + # Example resource route (maps HTTP verbs to controller actions automatically): + resources :home + resources :to_dos do + member do + get 'photos' + post 'create_like' + delete 'delete_like' + end + + collection do + get 'unsynced_changes' + get 'latest_timestamps' + get 'search' + end + + end + + # binding.pry + mount ActionCable.server , :at => '/cable' +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000..5981520 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,24 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: an_insecure_key_to_share_publically + flickr_api_key: 320c93f4c42149458089021f8ea5fa5c +test: + secret_key_base: an_insecure_key_to_share_publically + flickr_api_key: 320c93f4c42149458089021f8ea5fa5c +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] || "another_insecure_key" %> + # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + flickr_api_key: 320c93f4c42149458089021f8ea5fa5c \ No newline at end of file diff --git a/db/migrate/20151123114549_create_to_dos.rb b/db/migrate/20151123114549_create_to_dos.rb new file mode 100644 index 0000000..df655c2 --- /dev/null +++ b/db/migrate/20151123114549_create_to_dos.rb @@ -0,0 +1,12 @@ +class CreateToDos < ActiveRecord::Migration + def change + create_table :to_dos do |t| + t.string :description + t.string :address + t.string :string + t.integer :destination_id + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151123115340_create_destinations.rb b/db/migrate/20151123115340_create_destinations.rb new file mode 100644 index 0000000..06c0a0d --- /dev/null +++ b/db/migrate/20151123115340_create_destinations.rb @@ -0,0 +1,9 @@ +class CreateDestinations < ActiveRecord::Migration + def change + create_table :destinations do |t| + t.string :name + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151123120854_add_lat_lng_to_to_dos.rb b/db/migrate/20151123120854_add_lat_lng_to_to_dos.rb new file mode 100644 index 0000000..c0c4263 --- /dev/null +++ b/db/migrate/20151123120854_add_lat_lng_to_to_dos.rb @@ -0,0 +1,6 @@ +class AddLatLngToToDos < ActiveRecord::Migration + def change + add_column :to_dos, :lat, :decimal + add_column :to_dos, :lng, :decimal + end +end diff --git a/db/migrate/20151126185647_devise_create_travellers.rb b/db/migrate/20151126185647_devise_create_travellers.rb new file mode 100644 index 0000000..21a27e3 --- /dev/null +++ b/db/migrate/20151126185647_devise_create_travellers.rb @@ -0,0 +1,42 @@ +class DeviseCreateTravellers < ActiveRecord::Migration + def change + create_table(:travellers) do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + ## Rememberable + t.datetime :remember_created_at + + ## Trackable + t.integer :sign_in_count, default: 0, null: false + t.datetime :current_sign_in_at + t.datetime :last_sign_in_at + t.string :current_sign_in_ip + t.string :last_sign_in_ip + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + + t.timestamps null: false + end + + add_index :travellers, :email, unique: true + add_index :travellers, :reset_password_token, unique: true + # add_index :travellers, :confirmation_token, unique: true + # add_index :travellers, :unlock_token, unique: true + end +end diff --git a/db/migrate/20151126185934_adding_name_to_traveller.rb b/db/migrate/20151126185934_adding_name_to_traveller.rb new file mode 100644 index 0000000..f7bca04 --- /dev/null +++ b/db/migrate/20151126185934_adding_name_to_traveller.rb @@ -0,0 +1,5 @@ +class AddingNameToTraveller < ActiveRecord::Migration + def change + add_column :travellers, :name, :string + end +end diff --git a/db/migrate/20151126201047_create_likes.rb b/db/migrate/20151126201047_create_likes.rb new file mode 100644 index 0000000..448afba --- /dev/null +++ b/db/migrate/20151126201047_create_likes.rb @@ -0,0 +1,10 @@ +class CreateLikes < ActiveRecord::Migration + def change + create_table :likes do |t| + t.integer :traveller_id + t.integer :to_do_id + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160119202250_remove_string_from_to_dos.rb b/db/migrate/20160119202250_remove_string_from_to_dos.rb new file mode 100644 index 0000000..9e36764 --- /dev/null +++ b/db/migrate/20160119202250_remove_string_from_to_dos.rb @@ -0,0 +1,5 @@ +class RemoveStringFromToDos < ActiveRecord::Migration + def change + remove_column :to_dos, :string + end +end diff --git a/db/migrate/20160119202633_create_traveller_id_in_to_dos.rb b/db/migrate/20160119202633_create_traveller_id_in_to_dos.rb new file mode 100644 index 0000000..378098a --- /dev/null +++ b/db/migrate/20160119202633_create_traveller_id_in_to_dos.rb @@ -0,0 +1,5 @@ +class CreateTravellerIdInToDos < ActiveRecord::Migration + def change + add_column :to_dos, :traveller_id, :integer + end +end diff --git a/db/migrate/20160209114909_add_indexes_to_to_dos.rb b/db/migrate/20160209114909_add_indexes_to_to_dos.rb new file mode 100644 index 0000000..e1ada6e --- /dev/null +++ b/db/migrate/20160209114909_add_indexes_to_to_dos.rb @@ -0,0 +1,6 @@ +class AddIndexesToToDos < ActiveRecord::Migration[5.0] + def change + add_index :to_dos, :updated_at, using: :btree + add_index :to_dos, :traveller_id, using: :btree + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..0701360 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,65 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20160209114909) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "destinations", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "likes", force: :cascade do |t| + t.integer "traveller_id" + t.integer "to_do_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "to_dos", force: :cascade do |t| + t.string "description" + t.string "address" + t.integer "destination_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.decimal "lat" + t.decimal "lng" + t.integer "traveller_id" + end + + add_index "to_dos", ["traveller_id"], name: "index_to_dos_on_traveller_id", using: :btree + add_index "to_dos", ["updated_at"], name: "index_to_dos_on_updated_at", using: :btree + + create_table "travellers", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + end + + add_index "travellers", ["email"], name: "index_travellers_on_email", unique: true, using: :btree + add_index "travellers", ["reset_password_token"], name: "index_travellers_on_reset_password_token", unique: true, using: :btree + +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..1d7f55c --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,27 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# + + +# In future I'd use HTTP fetch from something like http://country.io/names.json +# In this case I thought I'd see how easily I could scrape the wikipedia list of countries with jQuery. +# Answer not easily enough to repeat the approach, particularly as the table was partitioned into sections by the repetition of column headings. +# + +some_countries = "Afghanistan,Albania,Algeria,American Samoa,Andorra,Angola,Anguilla,Antarctica,Antigua and Barbuda,Argentina,Armenia,Aruba,Australia,Austria,Azerbaijan,Bahamas,Bahrain,Bangladesh,Barbados,Belarus,Belgium,Belize,Benin,Bermuda,Bhutan,Bolivia,Bosnia and Herzegovina,Botswana,Brazil,Brunei Darussalam,Bulgaria,Burkina Faso,Burundi,Cambodia,Cameroon,Canada,Cape Verde,Cayman Islands,Central African Republic,Chad,Chile,China,Christmas Island,Cocos (Keeling) Islands,Colombia,Comoros,Democratic Republic of the Congo (Kinshasa),Congo, Republic of (Brazzaville),Cook Islands,Costa Rica,Ivory Coast,Croatia,Cuba,Cyprus,Czech Republic," +some_countries << "Denmark,Djibouti,Dominica,Dominican Republic,East Timor (Timor-Leste),Ecuador,Egypt,El Salvador,Equatorial Guinea,Eritrea,Estonia,Ethiopia,Falkland Islands,Faroe Islands,Fiji,Finland,France,French Guiana,French Polynesia,French Southern Territories,Gabon,Gambia,Georgia,Germany,Ghana,Gibraltar,Great Britain,Greece,Greenland,Grenada,Guadeloupe,Guam,Guatemala,Guinea,Guinea-Bissau,Guyana," +some_countries << "Haiti,Holy See,Honduras,Hong Kong,Hungary,Iceland,India,Indonesia,Iran (Islamic Republic of),Iraq,Ireland,Israel,Italy,Jamaica,Japan,Jordan,Kazakhstan,Kenya,Kiribati,Korea, Democratic People's Rep. (North Korea),Korea, Republic of (South Korea),Kosovo,Kuwait,Kyrgyzstan,Lao,People's Democratic Republic,Latvia,Lebanon,Lesotho,Liberia,Libya,Liechtenstein,Lithuania,Luxembourg,Macau,Macedonia,Madagascar,Malawi,Malaysia,Maldives,Mali,Malta,Marshall Islands,Martinique,Mauritania,Mauritius,Mayotte,Mexico,Micronesia,Federal States of Moldova,Republic of Monaco,Mongolia,Montenegro,Montserrat,Morocco,Mozambique,Myanmar,Burma," +some_countries << "Namibia,Nauru,Nepal,Netherlands,Netherlands Antilles,New Caledonia,New Zealand,Nicaragua,Niger,Nigeria,Niue,Northern Mariana Islands,Norway,Oman,Pakistan,Palau,Palestinian territories,Panama,Papua New Guinea,Paraguay,Peru,Philippines,Pitcairn Island,Poland,Portugal,Puerto Rico,Qatar,Reunion Island,Romania,Russian Federation,Rwanda,Saint Kitts and Nevis,Saint Lucia,Saint Vincent and the Grenadines,Samoa,San Marino,Sao Tome and Principe,Saudi Arabia,Senegal,Serbia,Seychelles,Sierra Leone,Singapore,Slovakia (Slovak Republic),Slovenia,Solomon Islands,Somalia,South Africa,South Sudan,Spain,Sri Lanka,Sudan,Suriname,Swaziland,Sweden,Switzerland,Syria,Syrian Arab Republic," +some_countries << "Taiwan (Republic of China),Tajikistan,Tanzania,Thailand,Tibet,Timor-Leste (East Timor),Togo,Tokelau,Tonga,Trinidad and Tobago,Tunisia,Turkey,Turkmenistan,Turks and Caicos Islands,Tuvalu,Uganda,Ukraine,United Arab Emirates,United Kingdom,United States,Uruguay,Uzbekistan,Vanuatu,Vatican City State (Holy See),Venezuela,Vietnam,Virgin Islands (British),Virgin Islands (U.S.),Wallis and Futuna Islands,Western Sahara,Yemen,Zambia,Zimbabwe," + +some_countries = some_countries.split(",") + +# If I ever had to do an insert this big somewhere it affected load time for users I'd put a lot of validation rules into the DB and try to get away with a direct INSERT. + +ActiveRecord::Base.transaction do + Destination.destroy_all + + some_countries.each_with_index do | c, i| + Destination.create!(id: i + 1, name: c) + end +end \ No newline at end of file diff --git a/features/destination.feature b/features/destination.feature new file mode 100644 index 0000000..44715d1 --- /dev/null +++ b/features/destination.feature @@ -0,0 +1,12 @@ +Feature: Destinations + +# Scenario: A traveller chooses a destination they have previously bucketted +# Given A traveller is on the homepage +# And A traveller has already set some ToDos +# When A traveller selects a destination they have ToDos on. +# Then Only markers for selected destination are shown + +# Scenario: A traveller chooses a destination they have not previously bucketted +# Given A traveller is on the homepage +# When A traveller selects a destination they have no ToDos on. +# Then Map zooms to country \ No newline at end of file diff --git a/features/likes.feature b/features/likes.feature new file mode 100644 index 0000000..8167aa3 --- /dev/null +++ b/features/likes.feature @@ -0,0 +1,35 @@ + +Feature: Likings + + Background: + Given Kurt and Previn sadly exist as travellers + + Scenario: A visitor cannot click like button + Given a traveller has a to do in their bucket list + Then There are no Like buttons visible + + @javascript + Scenario: A traveller likes a ToDo which isn't already liked + Given Previn has logged in + And a traveller has a to do in their bucket list + And A traveller is on the homepage + And The ToDo has no likes + Then There is "" likes + + When The ToDo is liked by current traveller + Then There is "1" likes + And the like option is replaced with an unlike option + When the page is reloaded + Then There is "1" likes + + @javascript + Scenario: A traveller likes a ToDo which already has been liked + Given Previn has logged in + And a traveller has a to do in their bucket list + And A traveller is on the homepage + Given The ToDo is already liked by "kurt" + When The ToDo is liked by current traveller + Then There is "2" likes + And the like option is replaced with an unlike option + When the page is reloaded + Then There is "2" likes diff --git a/features/search.feature b/features/search.feature new file mode 100644 index 0000000..63fcece --- /dev/null +++ b/features/search.feature @@ -0,0 +1,35 @@ +Feature: Search Bucketlist + + Background: + Given a traveller has some to dos in their bucket list + And a traveller is on the homepage + + @javascript + Scenario: A traveller searches for an existing ToDo item + Given they start searching for a todo item + Then I should see all partially matching searches + When I finish typing out my search + Then I should see all matching searches + + Scenario: A traveller searches for an existing ToDo Item via HTML + Given they start searching for a todo item + When I submit the search + Then I should see all matching searches + + @javascript + Scenario: A traveller searches for something that doesn't exist + Given they search for something ridiculous + Then I should see a caveat saying "No Results" + + + @javascript + @wip + Scenario: A traveller makes search less than 3 characters + Given they make a 2 character search + Then The search results container should not be displayed + + @javascript + Scenario: A traveller deletes their search term + Given they start searching for a todo item + And they delete the search + Then The search results container should not be displayed diff --git a/features/step_definitions/.gitkeep b/features/step_definitions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/features/step_definitions/general_steps.rb b/features/step_definitions/general_steps.rb new file mode 100644 index 0000000..2250a50 --- /dev/null +++ b/features/step_definitions/general_steps.rb @@ -0,0 +1,19 @@ +Given(/^a traveller is on the homepage$/) do + visit root_path +end + +When(/^the page is reloaded$/) do + visit current_path +end + +Given(/^A traveller is logged in$/) do + pending # express the regexp above with the code you wish you had +end + +When(/^A traveller logs out$/) do + pending # express the regexp above with the code you wish you had +end + +Given(/^Nothing$/) do + pending # express the regexp above with the code you wish you had +end diff --git a/features/step_definitions/likes_steps.rb b/features/step_definitions/likes_steps.rb new file mode 100644 index 0000000..dc9a4f3 --- /dev/null +++ b/features/step_definitions/likes_steps.rb @@ -0,0 +1,44 @@ +Given(/^Kurt and Previn sadly exist as travellers$/) do + @kurt = Traveller.create!(name: "Kurt", email: "new@sage.trav.com", password: "LikeTotally") + @previn = Traveller.create!(name: "Previn", email: "Sweety@sage.trav.com", password: "LikeYahally") +end + +Given(/^Previn has logged in$/) do + visit root_path + click_on "Log In" + fill_in :Email, with: "Sweety@sage.trav.com" + fill_in :Password, with: "LikeYahally" + click_on "Log in" +end + +Given(/^The ToDo is already liked by "(.*?)"$/) do |traveller| + case traveller + when "kurt" + trav = @kurt + when "previn" + trav = @previn + end + + Like.create!(traveller: trav, to_do: @to_do) +end + +Given(/^The ToDo has no likes$/) do + +end + +Then(/^There is "(.*?)" likes$/) do |likes_digits| + expect(find('.num_of_likes')).to have_content likes_digits +end + + +When(/^The ToDo is liked by current traveller$/) do + click_link "Like" + end + +Then(/^the like option is replaced with an unlike option$/) do + pending # express the regexp above with the code you wish you had +end + +Then(/^There are no Like buttons visible$/) do + pending # express the regexp above with the code you wish you had +end \ No newline at end of file diff --git a/features/step_definitions/search_steps.rb b/features/step_definitions/search_steps.rb new file mode 100644 index 0000000..4a5f03c --- /dev/null +++ b/features/step_definitions/search_steps.rb @@ -0,0 +1,53 @@ +Given(/^they start searching for a todo item$/) do + fill_in "search", :with => "Ride" +end + +Then(/^I should see all partially matching searches$/) do + within('#search-results') do + expect(page).to have_content('Ride Elephant') + expect(page).to have_content('Ride in an aircraft') + end +end + +Then(/^I should see all matching searches$/) do + within('#search-results') do + expect(page).to have_content('Ride Elephant') + end +end + +When(/^I finish typing out my search$/) do + fill_in "search", :with => "Ride Elephant" +end + +Given(/^they search for something ridiculous$/) do + fill_in "search", :with => "Ooobly blah blah" +end + +Then(/^I should see a caveat saying "(.*?)"$/) do |message| + within('#search-results') do + expect(page).to have_content(message) + end +end + +When(/^I submit the search$/) do + find('#btn--search').click +end + +Then(/^there are no search results$/) do + pending # express the regexp above with the code you wish you had +end + +Given(/^they delete the search$/) do + fill_in "search", :with => "" +end + +Then(/^The search results container should not be displayed$/) do + # save_and_open_page + sleep 0.5 + expect(find("#container--search-results")).to have_css(".undisplayed") +end + +Given(/^they make a (\d+) character search$/) do |chars| + string = '' + chars.to_i.times {string << "a"} +end diff --git a/features/step_definitions/to_do_photos_step.rb b/features/step_definitions/to_do_photos_step.rb new file mode 100644 index 0000000..ab4f154 --- /dev/null +++ b/features/step_definitions/to_do_photos_step.rb @@ -0,0 +1,8 @@ + +Then(/^they should see photos of their to do item$/) do + pending # express the regexp above with the code you wish you had +end + +When(/^they select the photos link$/) do + pending # express the regexp above with the code you wish you had +end diff --git a/features/step_definitions/to_dos_step.rb b/features/step_definitions/to_dos_step.rb new file mode 100644 index 0000000..5a3fb44 --- /dev/null +++ b/features/step_definitions/to_dos_step.rb @@ -0,0 +1,88 @@ + + +Given(/^a traveller has a to do in their bucket list$/) do + @destination = Destination.create!(name: "India") + ToDo.create!(description: "Ride Elephant", address: "Delhi", destination: @destination) +end + + +Given(/^a traveller has some to dos in their bucket list$/) do + @destination = Destination.create!(name: "India") + ToDo.create!(description: "Ride Elephant", address: "Delhi", destination: @destination) + + @uk = Destination.create!(name: "UK") + ToDo.create!(description: "Ride in an aircraft", address: "Torquay", destination: @uk) + +end + +Given(/^There are at least (\d+) destinations$/) do |arg1| + Destination.create!(name: "India") + Destination.create!(name: "Finland") + +end + +Given(/^A traveller is on the homepage$/) do + visit root_path +end + +Given(/^A traveller chooses to add a ToDo$/) do + click 'btn__new_to_do' +end + +When(/^A traveller selects a destination$/) do + select "India", from: "to_do[destination_id]" +end + +When(/^Enters valid ToDo details$/) do + fill_in "to_do[description]", with: "Get Spiritual" + fill_in "to_do[address]", with: "Delhi" +end + +When(/^Submits the ToDo$/) do + click_on "btn__add-to_do" + save_and_open_page +end + +Then(/^The ToDo is added to the list$/) do + sleep 1 + expect(page).to have_content "Get Spiritual" + expect(page).to have_content "Delhi" +end + +When(/^a traveller chooses to edit$/) do + click_link "for-test-edit-link" +end + +Then(/^a traveller sees edit form$/) do + within('#to_dos') do + expect(page).to have_content("Bucket It") + end +end + +When(/^a traveller cancels edit$/) do + pending # express the regexp above with the code you wish you had +end + +Then(/^the unedited ToDo is restored$/) do + pending # express the regexp above with the code you wish you had +end + +When(/^a traveller fills edit form with valid details$/) do + pending # express the regexp above with the code you wish you had +end + +When(/^a traveller chooses to delete$/) do + pending # express the regexp above with the code you wish you had +end + +Then(/^they are asked to confirm they'd like to delete$/) do + pending # express the regexp above with the code you wish you had +end + +When(/^they confirm they'd like to delete$/) do + pending # express the regexp above with the code you wish you had +end + +Then(/^the ToDo item is deleted$/) do + pending # express the regexp above with the code you wish you had +end diff --git a/features/step_definitions/traveller_steps.rb b/features/step_definitions/traveller_steps.rb new file mode 100644 index 0000000..51ab7d9 --- /dev/null +++ b/features/step_definitions/traveller_steps.rb @@ -0,0 +1,50 @@ + +Given(/^A visitor is on the homepage$/) do + visit root_path +end + +When(/^A visitor selects Register$/) do + click_on "Register" +end + +Then(/^A visitor is shown a sign up form$/) do + expect(page).to have_content "Sign up" +end + +When(/^A visitor fills sign up with valid details$/) do + fill_in :Name, with: "Adam" + fill_in :Email, with: "blah@blah.com" + fill_in :Password, with: "password" + fill_in "Password confirmation", with: "password" + +end + +When(/^A visitor chooses to sign up$/) do + click_on "Sign up" +end + + +Given(/^A traveller exists$/) do + Traveller.create!(name: "Adam", email: "blah@blah.com", password: "password") +end + +When(/^A visitor Logs In as that traveller$/) do + click_on "Log In" +end + +Then(/^They will be shown the Log In Form$/) do + expect(page).to have_content "Log in" +end + +When(/^A visitor fills Log In with valid details$/) do + fill_in :Email, with: "blah@blah.com" + fill_in :Password, with: "password" +end + +When(/^A visitor chooses to Log In$/) do + click_on "Log in" +end + +Then(/^They see a flash "(.*?)" "(.*?)"$/) do |flash_type, message| + expect(page).to have_content message +end \ No newline at end of file diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..64ddf61 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,58 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' + +# Capybara defaults to CSS3 selectors rather than XPath. +# If you'd prefer to use XPath, just uncomment this line and adjust any +# selectors in your step definitions to use the XPath syntax. +# Capybara.default_selector = :xpath + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false + +# Remove/comment out the lines below if your app doesn't have a database. +# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. +begin + DatabaseCleaner.strategy = :transaction +rescue NameError + raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +end + +# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. +# See the DatabaseCleaner documentation for details. Example: +# +# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do +# # { :except => [:widgets] } may not do what you expect here +# # as Cucumber::Rails::Database.javascript_strategy overrides +# # this setting. +# DatabaseCleaner.strategy = :truncation +# end +# +# Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# + +# Possible values are :truncation and :transaction +# The :transaction strategy is faster, but might give you threading problems. +# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature +Cucumber::Rails::Database.javascript_strategy = :truncation + diff --git a/features/support/geocoder.rb b/features/support/geocoder.rb new file mode 100644 index 0000000..e0184da --- /dev/null +++ b/features/support/geocoder.rb @@ -0,0 +1,23 @@ +Geocoder.configure( lookup: :test) + +Geocoder::Lookup::Test.add_stub( + "Delhi, India", [ + { + 'latitude' => 28.6139391, + 'longitude' => 77.2090212, + 'address' => 'Delhi, India', + 'country' => 'India' + } + ] +) + +Geocoder::Lookup::Test.add_stub( + "Torquay, UK", [ + { + 'latitude' => 50.4619209, + 'longitude' => -3.525315, + 'address' => 'Torquay, Torbay, UK', + 'country' => 'UK' + } + ] +) diff --git a/features/support/poltergeist.rb b/features/support/poltergeist.rb new file mode 100644 index 0000000..87be24c --- /dev/null +++ b/features/support/poltergeist.rb @@ -0,0 +1,2 @@ +require 'capybara/poltergeist' +Capybara.javascript_driver = :poltergeist \ No newline at end of file diff --git a/features/to_do_photos.feature b/features/to_do_photos.feature new file mode 100644 index 0000000..d06d12c --- /dev/null +++ b/features/to_do_photos.feature @@ -0,0 +1,8 @@ +Feature: To Do Photos + + @javascript + Scenario: + Given a traveller has a to do in their bucket list + And A traveller is on the homepage + When they select the photos link + Then they should see photos of their to do item diff --git a/features/todos.feature b/features/todos.feature new file mode 100644 index 0000000..cd51e43 --- /dev/null +++ b/features/todos.feature @@ -0,0 +1,55 @@ +Feature: ToDo Editing + +Scenario: A traveller opens Bucket List for the first time + Given Nothing + +Scenario: A traveller adds a ToDo item without javascript + Given There are at least 2 destinations + And A traveller is on the homepage + When A traveller selects a destination + When A traveller chooses to add a ToDo + And Enters valid ToDo details + And Submits the ToDo + Then The ToDo is added to the list + + +@javascript +Scenario: A traveller adds a ToDo item + Given There are at least 2 destinations + And A traveller is on the homepage + When A traveller selects a destination + When A traveller chooses to add a ToDo + And Enters valid ToDo details + And Submits the ToDo + Then The ToDo is added to the list + +@javascript +Scenario: A traveller cancels their edit of a ToDo item + Given a traveller has a to do in their bucket list + And a traveller is on the homepage + When a traveller chooses to edit + Then a traveller sees edit form + When a traveller cancels edit + Then the unedited ToDo is restored + + +@javascript +Scenario: A traveller cancels their edit of a ToDo item + Given a traveller has a to do in their bucket list + And a traveller is on the homepage + When a traveller chooses to edit + Then a traveller sees edit form + When a traveller fills edit form with valid details + +@javascript +Scenario: A traveller deletes a todo item + Given a traveller has a to do in their bucket list + And a traveller is on the homepage + When a traveller chooses to delete + Then they are asked to confirm they'd like to delete + When they confirm they'd like to delete + Then the ToDo item is deleted + + + + diff --git a/features/traveller.feature b/features/traveller.feature new file mode 100644 index 0000000..6e3d3c9 --- /dev/null +++ b/features/traveller.feature @@ -0,0 +1,26 @@ +Feature: A traveller gets stuff done + +Background: + Given A visitor is on the homepage + +Scenario: A visitor registers as a traveller + When A visitor selects Register + Then A visitor is shown a sign up form + When A visitor fills sign up with valid details + And A visitor chooses to sign up + Then They see a flash "notice" "You are a bucketter!" + +Scenario: A visitor signs in as a traveller + Given A traveller exists + When A visitor Logs In as that traveller + Then They will be shown the Log In Form + When A visitor fills Log In with valid details + And A visitor chooses to Log In + Then They see a flash "notice" "Signed in successfully." + +Scenario: A traveller cannot see sign up or log in options if they are signed in + +Scenario: A traveller logs out + Given A traveller is logged in + When A traveller logs out + diff --git a/features/travellers.feature b/features/travellers.feature new file mode 100644 index 0000000..662e5b7 --- /dev/null +++ b/features/travellers.feature @@ -0,0 +1,14 @@ +Feature: Travellers list + +Background: + Given: A visitor is signed in + +@wip +Scenario: A traveller clicks to see a list of other travellers + When A traveller selects the traveller list + Then The list of travellers is displayed + When They click on the first traveller + Then The ToDos for the selected traveller are displayed + And The Add Pebble Box is not visible + And The Edit buttons are not visible + And The Delete buttons are not visible \ No newline at end of file diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000..9f53ce4 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,65 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon. + task 'test:prepare' do + end + + task :stats => 'cucumber:statsetup' +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000..e69de29 diff --git a/public/.keep b/public/.keep new file mode 100644 index 0000000..e69de29 diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..b612547 --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
    +
    +

    The page you were looking for doesn't exist.

    +

    You may have mistyped the address or the page may have moved.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..a21f82b --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
    +
    +

    The change you wanted was rejected.

    +

    Maybe you tried to change something you didn't have access to.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..061abc5 --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
    +
    +

    We're sorry, but something went wrong.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/assets/.sprockets-manifest-2f86cee30812646184df809f7ff9ccd2.json b/public/assets/.sprockets-manifest-2f86cee30812646184df809f7ff9ccd2.json new file mode 100644 index 0000000..74bca34 --- /dev/null +++ b/public/assets/.sprockets-manifest-2f86cee30812646184df809f7ff9ccd2.json @@ -0,0 +1 @@ +{"files":{"edit-icon-ea81e372bebfef7d976544f8bc3d04db962c4dda140163db3223471b3a166152.png":{"logical_path":"edit-icon.png","mtime":"2016-01-12T12:01:05+00:00","size":491,"digest":"ea81e372bebfef7d976544f8bc3d04db962c4dda140163db3223471b3a166152","integrity":"sha256-6oHjcr6/732XZUT4vD0E25YsTdoUAWPbMiNHGzoWYVI="},"no_images-a34c4e783250d4e13c97a78b6c644dd28fcb10fcee4d1e1616596ce13f6f4bd7.png":{"logical_path":"no_images.png","mtime":"2016-01-12T12:01:05+00:00","size":3806,"digest":"a34c4e783250d4e13c97a78b6c644dd28fcb10fcee4d1e1616596ce13f6f4bd7","integrity":"sha256-o0xOeDJQ1OE8l6eLbGRN0o/LEPzuTR4WFlls4T9vS9c="},"sprite-ce6a244ccf859f15d251e698a97999250e32774d23e602a9f940b656da522c59.png":{"logical_path":"sprite.png","mtime":"2016-01-12T12:01:05+00:00","size":52898,"digest":"ce6a244ccf859f15d251e698a97999250e32774d23e602a9f940b656da522c59","integrity":"sha256-zmokTM+FnxXSUeaYqXmZJQ4yd00j5gKp+UC2VtpSLFk="},"application-56cdee3e9e8a05ee19f5e55d64836661717741a409734d61764f4bde7f3aca42.js":{"logical_path":"application.js","mtime":"2016-02-02T13:25:12+00:00","size":365607,"digest":"56cdee3e9e8a05ee19f5e55d64836661717741a409734d61764f4bde7f3aca42","integrity":"sha256-Vs3uPp6KBe4Z9eVdZINmYXF3QaQJc01hdk9L3n86ykI="},"application-19febd0dade963d92ae36f32e74917f533d0ac5941dfa334dd0faf257d7c3529.css":{"logical_path":"application.css","mtime":"2016-02-01T11:41:55+00:00","size":3480,"digest":"19febd0dade963d92ae36f32e74917f533d0ac5941dfa334dd0faf257d7c3529","integrity":"sha256-Gf69Da3pY9kq428y50kX9TPQrFlB36M03Q+vJX18NSk="},"application-43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda.js":{"logical_path":"application.js","mtime":"2016-02-02T20:31:10+00:00","size":126147,"digest":"43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda","integrity":"sha256-QyZPBld7mL12xsovvdtB7JCwBu9fdUJ6d4dp/ICiy9o="},"application-abbc89feee2d8330614b2b7f80ecff82421df0691a3c726236c46d5b297a9012.css":{"logical_path":"application.css","mtime":"2016-02-02T20:24:59+00:00","size":3480,"digest":"abbc89feee2d8330614b2b7f80ecff82421df0691a3c726236c46d5b297a9012","integrity":"sha256-q7yJ/u4tgzBhSyt/gOz/gkId8GkaPHJiNsRtWyl6kBI="},"application-d032d0f02c65304d41e10e2007922a928a2eaa7387dc9464268eee95b64c54c2.css":{"logical_path":"application.css","mtime":"2016-02-03T08:31:55+00:00","size":3489,"digest":"d032d0f02c65304d41e10e2007922a928a2eaa7387dc9464268eee95b64c54c2","integrity":"sha256-0DLQ8CxlME1B4Q4gB5IqkoouqnOH3JRkJo7ulbZMVMI="}},"assets":{"edit-icon.png":"edit-icon-ea81e372bebfef7d976544f8bc3d04db962c4dda140163db3223471b3a166152.png","no_images.png":"no_images-a34c4e783250d4e13c97a78b6c644dd28fcb10fcee4d1e1616596ce13f6f4bd7.png","sprite.png":"sprite-ce6a244ccf859f15d251e698a97999250e32774d23e602a9f940b656da522c59.png","application.js":"application-43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda.js","application.css":"application-d032d0f02c65304d41e10e2007922a928a2eaa7387dc9464268eee95b64c54c2.css"}} \ No newline at end of file diff --git a/public/assets/application-19febd0dade963d92ae36f32e74917f533d0ac5941dfa334dd0faf257d7c3529.css b/public/assets/application-19febd0dade963d92ae36f32e74917f533d0ac5941dfa334dd0faf257d7c3529.css new file mode 100644 index 0000000..3a50853 --- /dev/null +++ b/public/assets/application-19febd0dade963d92ae36f32e74917f533d0ac5941dfa334dd0faf257d7c3529.css @@ -0,0 +1 @@ +body{position:relative;background-color:whitesmoke;font-size:1.2rem;font-family:helvetica, Futura;top:2.5em}#map-container{position:fixed;min-width:100%;height:800px;z-index:-2;top:2.5em}#map-canvas{display:inline-block;width:100%;height:100%;position:fixed}#modal{position:fixed;width:20em;font-size:0.5em;padding:1em;box-shadow:1em 1em 1em;background-color:white}#modal-background{width:100%;height:100%;top:0;position:fixed;background-color:transparent}#destination__search-opts{width:17rem;font-size:1.3rem;font-style:italic}#fieldset__search{background-color:transparent;width:20em;position:fixed;top:0.2rem;left:13rem;z-index:1}#search{width:16.1rem;font-size:1.2rem;padding:0.2rem}#btn--search{position:relative;left:16em;top:-1.58em;font-size:1em}#container--search-results{width:26em;position:fixed;top:2.8em}header.devise-header>p{margin:0 0 0.1em 0;position:fixed;z-index:1;top:3.3em;padding:0.5em;border-radius:4px;margin-left:72%;margin-right:1em;color:white}.sprite,.sprite--tiny,.sprite--tiny__close,.sprite--tiny__trash,.sprite--tiny__like{background:url(/assets/sprite.png)}.sprite--tiny,.sprite--tiny__close,.sprite--tiny__trash,.sprite--tiny__like{width:18px;height:18px}.sprite--tiny__close{background-position:calc(23 * -18px) -108px}.sprite--tiny__trash{background-position:calc(25 * -18px) -108px}.sprite--tiny__like{background-position:calc(9 * -18px) -108px}.alert{background-color:rgba(224,158,188,0.84)}.notice{background-color:rgba(119,207,183,0.84)}.to_do{transition:min-height}.heading{font-size:1.3em;font-weight:bold;padding-bottom:0.5em;padding-left:1em;position:fixed;width:100%;color:#f2f2f2;background-color:#ae5576;padding-top:0.5em;top:0em}.h3{display:block;font-size:1.17em;padding-bottom:0.3em;font-weight:bold}fieldset{background:white;border:none}fieldset>*{font-size:1.5rem}fieldset>label{width:2em;display:block;font-size:small;color:#2E2E2E;margin-bottom:0.5em}fieldset>input,fieldset>select,#destination__search-opts,#select--sort-to_dos,#btn__new_to_do{margin-left:1em;margin-right:auto;border:solid #f2f2f2 1px;border-radius:3px;padding-left:0.3em}#to_dos{position:relative;z-index:-1}#select--sort-to_dos{font-size:1.3rem;margin-right:1rem;font-style:italic}.shadow-box{background-color:white;box-shadow:0.2em 0.5em 1.2em grey;padding:1.3em;padding-bottom:1.8rem;margin-bottom:2em}#bucket{position:relative;display:inline;float:left;z-index:-1}#bucket fieldset select{width:25rem}.nav__left{width:31.5rem;display:inline}#nav-buttons{left:31.5rem;position:fixed;top:0.7rem}#nav-buttons>a{color:white;border-left:solid 1px #444444;padding:0 1rem;text-decoration:none}#add__to_do{position:relative;top:-3px}#btn__new_to_do{position:relative;left:7.5rem;font-size:1.3rem;color:white;background-color:#76CFBE;width:11.5rem;height:2.5rem;padding:0rem 0rem 0.1rem 0}#btn__new_to_do--label{position:relative;left:9rem}#btn__add-to_do{padding:0.5em;font-size:1em;margin:1em 0.5em;cursor:pointer}.click--photos{display:block;font-size:1rem;margin-top:0.5rem}.container--like{display:inline-block}.num_of_likes{display:inline;font-size:1rem;padding-top:0px;position:relative;top:-3px}.thumbnail{margin-right:-6px;margin-bottom:-4px}.undisplayed{display:none}.perm-undisplayed{display:none}.edit-link{position:relative;display:inline-block}.container__to_do_actions{position:absolute;text-align:right;width:28.5rem}.icon,.icon__close-edit,.icon__trash{top:0;position:relative;cursor:pointer;display:inline-block}.icon__close-edit{left:22.8em} diff --git a/public/assets/application-43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda.js b/public/assets/application-43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda.js new file mode 100644 index 0000000..3ef4865 --- /dev/null +++ b/public/assets/application-43264f06577b98bd76c6ca2fbddb41ec90b006ef5f75427a778769fc80a2cbda.js @@ -0,0 +1,27 @@ +/*! + * jQuery JavaScript Library v1.11.3 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2015-04-28T16:19Z + */ +function initMap(){window.POLL_INTERVAL=2e3,window.markers=[],window.map=new google.maps.Map($("#map-canvas")[0],{}),window.map.toDos=$("#map-canvas").data("toDos"),window.map.latlngbounds=new google.maps.LatLngBounds,window.map.toDos.length>0&&resetMarkers(),initProcesses(),initEventListeners()}function initEventListeners(){map.addListener("idle",showToDosInBounds);var e=$("#search");e.blur(function(){$("#container--search-results").addClass("undisplayed")}),e.keyup(function(e){return 13!=e.keyCode?makeSearch(e):void $("#search").blur()}),$("#destination__search-opts").on("change",function(){var e=$("#destination__search-opts").val();showOnlyMarkersFor("destination_id",e)}),$("#select--sort-to_dos").on("change",function(e){sortElements("#to_dos",$(e.target).val())}),window.onfocus=function(){runSyncPolling(!0)},window.onblur=function(){runSyncPolling(!1)}}function initProcesses(){getLatestToDoTimestamps(),runSyncPolling(!0)}function resetMarkers(){clearMarkers(),addMarkers(window.map.toDos)}function addMarker(e,t,n,r){var i=new google.maps.LatLng(e,t),o=new google.maps.Marker({position:i,title:n,id:r,map:window.map});markers.push(o),window.map.latlngbounds.extend(i),o.addListener("click",function(){showModal(toDo)}),fitMapToMarkers()}function addMarkers(e){_(e).each(function(e){addMarker(e.lat,e.lng,e.description,e.id)})}function fitMapToMarkers(){map.fitBounds(map.latlngbounds),map.zoom>12&&map.setZoom(12),map.setCenter(map.latlngbounds.getCenter())}function clearMarkers(){for(var e=0;er?-1:1}),r.detach().appendTo(n)}function cancelToDoEdit(e){return e?toDos[e].cancelEdit():void toggleNewToDo()}function toggleNewToDo(){$("#add__to_do").children().toggleClass("undisplayed")}function getLatestToDoTimestamps(){$.ajax({url:"/to_dos/latest_timestamps",success:function(e){window.toDoLatestTimestamps=e.timestamps}})}function getUnsyncedToDos(){$.ajax({url:"/to_dos/unsynced_changes",data:window.toDoLatestTimestamps})}function runSyncPolling(e){e?window.checkSyncInterval=setInterval(getUnsyncedToDos,POLL_INTERVAL):clearInterval(window.checkSyncInterval)}!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t="length"in e&&e.length,n=ie.type(e);return"function"===n||ie.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function r(e,t,n){if(ie.isFunction(t))return ie.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return ie.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(de.test(t))return ie.filter(t,e,n);t=ie.filter(t,e)}return ie.grep(e,function(e){return ie.inArray(e,t)>=0!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t=xe[e]={};return ie.each(e.match(be)||[],function(e,n){t[n]=!0}),t}function a(){he.addEventListener?(he.removeEventListener("DOMContentLoaded",s,!1),e.removeEventListener("load",s,!1)):(he.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(he.addEventListener||"load"===event.type||"complete"===he.readyState)&&(a(),ie.ready())}function u(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(Ce,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Ee.test(n)?ie.parseJSON(n):n}catch(i){}ie.data(e,t,n)}else n=void 0}return n}function l(e){var t;for(t in e)if(("data"!==t||!ie.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(ie.acceptData(e)){var i,o,a=ie.expando,s=e.nodeType,u=s?ie.cache:e,l=s?e[a]:e[a]&&a;if(l&&u[l]&&(r||u[l].data)||void 0!==n||"string"!=typeof t)return l||(l=s?e[a]=K.pop()||ie.guid++:a),u[l]||(u[l]=s?{}:{toJSON:ie.noop}),("object"==typeof t||"function"==typeof t)&&(r?u[l]=ie.extend(u[l],t):u[l].data=ie.extend(u[l].data,t)),o=u[l],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[ie.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[ie.camelCase(t)])):i=o,i}}function f(e,t,n){if(ie.acceptData(e)){var r,i,o=e.nodeType,a=o?ie.cache:e,s=o?e[ie.expando]:ie.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){ie.isArray(t)?t=t.concat(ie.map(t,ie.camelCase)):t in r?t=[t]:(t=ie.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!l(r):!ie.isEmptyObject(r))return}(n||(delete a[s].data,l(a[s])))&&(o?ie.cleanData([e],!0):ne.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}function d(){return!0}function p(){return!1}function h(){try{return he.activeElement}catch(e){}}function m(e){var t=qe.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function g(e,t){var n,r,i=0,o=typeof e.getElementsByTagName!==ke?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll!==ke?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||ie.nodeName(r,t)?o.push(r):ie.merge(o,g(r,t));return void 0===t||t&&ie.nodeName(e,t)?ie.merge([e],o):o}function v(e){Ae.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t){return ie.nodeName(e,"table")&&ie.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function b(e){return e.type=(null!==ie.find.attr(e,"type"))+"/"+e.type,e}function x(e){var t=Ve.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function w(e,t){for(var n,r=0;null!=(n=e[r]);r++)ie._data(n,"globalEval",!t||ie._data(t[r],"globalEval"))}function T(e,t){if(1===t.nodeType&&ie.hasData(e)){var n,r,i,o=ie._data(e),a=ie._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)ie.event.add(t,n,s[n][r])}a.data&&(a.data=ie.extend({},a.data))}}function k(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!ne.noCloneEvent&&t[ie.expando]){i=ie._data(t);for(r in i.events)ie.removeEvent(t,r,i.handle);t.removeAttribute(ie.expando)}"script"===n&&t.text!==e.text?(b(t).text=e.text,x(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),ne.html5Clone&&e.innerHTML&&!ie.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ae.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function E(t,n){var r,i=ie(n.createElement(t)).appendTo(n.body),o=e.getDefaultComputedStyle&&(r=e.getDefaultComputedStyle(i[0]))?r.display:ie.css(i[0],"display");return i.detach(),o}function C(e){var t=he,n=Ze[e];return n||(n=E(e,t),"none"!==n&&n||(Qe=(Qe||ie("