├── log └── .keep ├── app ├── mailers │ ├── .keep │ └── application_mailer.rb ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── user_tip.rb │ ├── unit.rb │ ├── bill.rb │ ├── tip.rb │ ├── upload.rb │ └── team.rb ├── assets │ ├── images │ │ ├── .keep │ │ ├── hero.jpg │ │ ├── no_team.jpg │ │ ├── enersaveBulb.png │ │ ├── sample-graph.png │ │ ├── enersaveMoney.png │ │ ├── enersavePeople.jpg │ │ └── icon-arrow-down.svg │ ├── javascripts │ │ ├── bills.js │ │ ├── navbar.js │ │ ├── teams.coffee │ │ ├── units.coffee │ │ ├── user_buildings.coffee │ │ ├── analytics.js │ │ ├── users.js │ │ ├── application.js │ │ ├── address_autocomplete.js │ │ └── graph.js │ └── stylesheets │ │ ├── tables.scss │ │ ├── scaffolds.scss │ │ ├── application.css │ │ ├── foundation_and_overrides.scss │ │ └── main.scss ├── views │ ├── tips │ │ ├── share.js.erb │ │ ├── vote.html.erb │ │ ├── like.js.erb │ │ ├── show.json.jbuilder │ │ ├── new.html.erb │ │ ├── index.json.jbuilder │ │ ├── details.html.erb │ │ ├── edit.html.erb │ │ ├── next.js.erb │ │ ├── dislike.js.erb │ │ ├── show.html.erb │ │ ├── _rand.html.erb │ │ ├── _share.html.erb │ │ ├── _vote.html.erb │ │ ├── _form.html.erb │ │ └── index.html.erb │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── mailer.html.erb │ │ ├── _footer.html.erb │ │ └── application.html.erb │ ├── shared │ │ └── _header.html.erb │ ├── teams │ │ ├── new.html.erb │ │ ├── show.json.jbuilder │ │ ├── edit.html.erb │ │ ├── index.json.jbuilder │ │ ├── _remove.html.erb │ │ ├── _invitation.html.erb │ │ ├── index.html.erb │ │ ├── _form.html.erb │ │ ├── show.html.erb │ │ └── leaderboard.html.erb │ ├── units │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── show.json.jbuilder │ │ ├── index.json.jbuilder │ │ ├── show.html.erb │ │ ├── index.html.erb │ │ └── _form.html.erb │ ├── user_buildings │ │ ├── new.html.erb │ │ ├── show.json.jbuilder │ │ ├── edit.html.erb │ │ ├── index.json.jbuilder │ │ ├── show.html.erb │ │ ├── _form.html.erb │ │ └── index.html.erb │ ├── bills │ │ ├── edit.html.erb │ │ ├── show.json.jbuilder │ │ ├── index.json.jbuilder │ │ ├── comparison.json.jbuilder │ │ ├── show.html.erb │ │ ├── comparison.html.erb │ │ ├── index.html.erb │ │ ├── _form.html.erb │ │ └── new.html.erb │ ├── users │ │ ├── edit.html.erb │ │ ├── show.json.jbuilder │ │ ├── invitations │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── mailer │ │ │ ├── invitation_instructions.text.erb │ │ │ └── invitation_instructions.html.erb │ │ ├── _form.html.erb │ │ └── show.html.erb │ ├── uploads │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── user_tips │ │ ├── create.js.erb │ │ └── _feedback.html.erb │ ├── devise │ │ ├── mailer │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ ├── reset_password_instructions.html.erb │ │ │ ├── invitation_instructions.text.erb │ │ │ └── invitation_instructions.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── passwords │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── sessions │ │ │ └── new.html.erb │ │ ├── registrations │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ └── shared │ │ │ └── _links.html.erb │ └── graph │ │ └── index.html.erb ├── controllers │ ├── concerns │ │ └── .keep │ ├── sessions_controller.rb │ ├── graph_controller.rb │ ├── registrations_controller.rb │ ├── api │ │ └── v1 │ │ │ └── addresses_controller.rb │ ├── users │ │ ├── invitations_controller.rb │ │ └── omniauth_callbacks_controller.rb │ ├── users_controller.rb │ ├── uploads_controller.rb │ ├── user_tips_controller.rb │ ├── application_controller.rb │ ├── user_buildings_controller.rb │ ├── tips_controller.rb │ ├── bills_controller.rb │ └── teams_controller.rb ├── helpers │ ├── bills_helper.rb │ ├── teams_helper.rb │ ├── units_helper.rb │ ├── user_buildings_helper.rb │ ├── graphs_helper.rb │ ├── application_helper.rb │ └── users_helper.rb ├── policies │ ├── user_policy.rb │ ├── bill_policy.rb │ ├── upload_policy.rb │ ├── unit_policy.rb │ ├── user_building_policy.rb │ ├── user_tip_policy.rb │ ├── tip_policy.rb │ ├── team_policy.rb │ └── application_policy.rb └── uploaders │ └── team_photo_uploader.rb ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ └── sample_data.rake ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html ├── 404.html └── agreement.html ├── .ruby-version ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── .rspec ├── spec ├── support │ ├── tux.jpg │ ├── json_controller_tests.rb │ └── devise.rb ├── models │ ├── unit_spec.rb │ ├── bill_spec.rb │ ├── team_spec.rb │ ├── user_spec.rb │ └── user_building_spec.rb ├── controllers │ ├── api │ │ └── v1 │ │ │ └── addresses_controller_spec.rb │ ├── graphs_controller_spec.rb │ ├── users_controller_spec.rb │ ├── user_tips_controller_spec.rb │ ├── bills_controller_spec.rb │ ├── units_controller_spec.rb │ └── user_buildings_controller_spec.rb ├── features │ ├── user_can_sign_in_spec.rb │ ├── invite_existing_user_spec.rb │ ├── team_adds_photo_spec.rb │ └── user_can_create_new_bill_spec.rb ├── factories │ └── factories.rb └── rails_helper.rb ├── bin ├── bundle ├── rake ├── rails ├── spring └── setup ├── config ├── boot.rb ├── initializers │ ├── social_share_button.rb │ ├── cookies_serializer.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── backtrace_silencers.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ └── inflections.rb ├── environment.rb ├── locales │ ├── social_share_button.en.yml │ ├── en.yml │ └── devise_invitable.en.yml ├── secrets.yml ├── application.rb ├── deploy.rb ├── newrelic.yml ├── routes.rb ├── environments │ ├── test.rb │ ├── docker.rb │ ├── development.rb │ └── production.rb ├── deploy │ ├── production.rb │ └── staging.rb └── database.yml ├── db └── migrate │ ├── 20160521190343_drop_invitations.rb │ ├── 20160427002420_add_image_to_users.rb │ ├── 20160523201241_add_tip_num_to_user.rb │ ├── 20160727005320_add_details_to_tips.rb │ ├── 20160309013348_add_image_url_to_teams.rb │ ├── 20160411184700_add_mssg_to_invitations.rb │ ├── 20151223010957_add_unit_to_bill.rb │ ├── 20160802214548_add_feedback_to_user_tips.rb │ ├── 20160522213000_remove_street_address_from_users.rb │ ├── 20151214022746_change_integer_limit_for_phone_numbers.rb │ ├── 20160517210157_create_tips.rb │ ├── 20151209023424_create_teams.rb │ ├── 20160511005103_change_unit_number_in_units.rb │ ├── 20160803003338_add_columns_to_bills_table.rb │ ├── 20151214034305_add_omniauth_to_users.rb │ ├── 20151209021917_create_user_buildings.rb │ ├── 20160727152525_create_uploads.rb │ ├── 20160803001417_rename_cost_to_usage.rb │ ├── 20151213213550_create_bills.rb │ ├── 20160517211104_create_user_tips.rb │ ├── 20160307052834_create_invitations.rb │ ├── 20160427155259_change_phone_to_string.rb │ ├── 20151209023541_create_users.rb │ ├── 20151209023328_create_units.rb │ ├── 20160521191953_add_devise_invitable_to_users.rb │ ├── 20160521191312_devise_invitable_add_to_users.rb │ ├── 20151214032953_add_devise_to_users.rb │ └── 20160702173010_add_granular_address_columns_to_user_buildings.rb ├── config.ru ├── .env.example ├── .travis.yml ├── Dockerfile ├── Rakefile ├── docker-compose.yml ├── CHANGELOG.md ├── .gitignore ├── Capfile ├── LICENSE ├── README.md └── Gemfile /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.5 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/tips/share.js.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/tips/vote.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'vote' %> 2 | -------------------------------------------------------------------------------- /app/helpers/bills_helper.rb: -------------------------------------------------------------------------------- 1 | module BillsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/teams_helper.rb: -------------------------------------------------------------------------------- 1 | module TeamsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/units_helper.rb: -------------------------------------------------------------------------------- 1 | module UnitsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/user_buildings_helper.rb: -------------------------------------------------------------------------------- 1 | module UserBuildingsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/views/shared/_header.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /app/views/teams/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Team

2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/units/new.html.erb: -------------------------------------------------------------------------------- 1 |

About my Home

2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/teams/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @team, :id, :name, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /app/views/tips/like.js.erb: -------------------------------------------------------------------------------- 1 | $("#tips_vote").html("<%= escape_javascript(render 'tips/vote') %>"); -------------------------------------------------------------------------------- /app/views/tips/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @tip, :id, :text, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /app/views/user_buildings/new.html.erb: -------------------------------------------------------------------------------- 1 |

New User Building

2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= yield %> 4 | 5 | 6 | -------------------------------------------------------------------------------- /spec/support/tux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/spec/support/tux.jpg -------------------------------------------------------------------------------- /app/assets/images/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/app/assets/images/hero.jpg -------------------------------------------------------------------------------- /app/views/bills/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Bill

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @bill %> 6 | -------------------------------------------------------------------------------- /app/views/teams/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Team

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @team %> 6 | -------------------------------------------------------------------------------- /app/views/tips/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Tip

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', tips_path %> 6 | -------------------------------------------------------------------------------- /app/views/units/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Unit

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @unit %> 6 | -------------------------------------------------------------------------------- /app/assets/images/no_team.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/app/assets/images/no_team.jpg -------------------------------------------------------------------------------- /app/views/bills/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @bill, :id, :bill_received, :amount, :usage, :user_id, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /app/views/user_buildings/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @user_building, :id, :address, :lat, :lon, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /app/assets/images/enersaveBulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/app/assets/images/enersaveBulb.png -------------------------------------------------------------------------------- /app/assets/images/sample-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/app/assets/images/sample-graph.png -------------------------------------------------------------------------------- /app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing User

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', current_user_path %> 6 | -------------------------------------------------------------------------------- /app/assets/images/enersaveMoney.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/app/assets/images/enersaveMoney.png -------------------------------------------------------------------------------- /app/assets/images/enersavePeople.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforboston/cambridge_energy_app/HEAD/app/assets/images/enersavePeople.jpg -------------------------------------------------------------------------------- /app/views/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @user, :id, :first_name, :last_name, :phone, :unit_id, :team_id, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /app/views/tips/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@tips) do |tip| 2 | json.extract! tip, :id, :text 3 | json.url tip_url(tip, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /app/views/user_buildings/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing User Building

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @user_building %> 6 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /app/views/teams/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@teams) do |team| 2 | json.extract! team, :id, :name 3 | json.url team_url(team, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /app/views/tips/details.html.erb: -------------------------------------------------------------------------------- 1 | <% @tip = Tip.get_tip(current_user) %> 2 |

<%= @tip.text %>

3 |

<%= @tip.details %>

4 | <%= render 'vote' %> 5 | -------------------------------------------------------------------------------- /app/views/tips/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Tip

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @tip %> | 6 | <%= link_to 'Back', tips_path %> 7 | -------------------------------------------------------------------------------- /config/initializers/social_share_button.rb: -------------------------------------------------------------------------------- 1 | SocialShareButton.configure do |config| 2 | config.allow_sites = %w(twitter facebook pinterest linkedin) 3 | end 4 | -------------------------------------------------------------------------------- /app/views/teams/_remove.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@invitation, html: { method: :delete }) do |f| %> 2 | <%= f.submit 'Remove', class: "btn btn-primary" %> 3 | <% end %> -------------------------------------------------------------------------------- /db/migrate/20160521190343_drop_invitations.rb: -------------------------------------------------------------------------------- 1 | class DropInvitations < ActiveRecord::Migration 2 | def change 3 | drop_table :invitations 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/policies/user_policy.rb: -------------------------------------------------------------------------------- 1 | class UserPolicy < ApplicationPolicy 2 | def update? 3 | true 4 | end 5 | 6 | def show? 7 | true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/uploads/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Upload

2 | <%= form_for @upload do |f| %> 3 | <%= f.file_field :xml%> 4 | <%= f.submit "Upload file" %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/bills.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("turbolinks:load", function() { 2 | $( "#datepicker" ).datepicker({ dateFormat: 'yy-mm-dd' }); 3 | } ); 4 | 5 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < Devise::SessionsController 2 | def create 3 | super 4 | current_or_guest_user 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "exohedron@gmail.com" 3 | layout 'mailer' 4 | include SendGrid 5 | end 6 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /db/migrate/20160427002420_add_image_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddImageToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :image, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /db/migrate/20160523201241_add_tip_num_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddTipNumToUser < ActiveRecord::Migration 2 | def change 3 | add_column :users, :tipnum, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160727005320_add_details_to_tips.rb: -------------------------------------------------------------------------------- 1 | class AddDetailsToTips < ActiveRecord::Migration 2 | def change 3 | add_column :tips, :details, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/tips/next.js.erb: -------------------------------------------------------------------------------- 1 | $("#tips_display").html("<%= escape_javascript(render 'tips/rand') %>"); 2 | $("#tips_vote").html("<%= escape_javascript(render 'user_tips/feedback') %>"); 3 | -------------------------------------------------------------------------------- /db/migrate/20160309013348_add_image_url_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddImageUrlToTeams < ActiveRecord::Migration 2 | def change 3 | add_column :teams, :image_url, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/graph_controller.rb: -------------------------------------------------------------------------------- 1 | class GraphController < ApplicationController 2 | before_action :skip_policy_scope 3 | 4 | def index 5 | @user = current_user 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class RegistrationsController < Devise::RegistrationsController 2 | def create 3 | super 4 | current_or_guest_user 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/user_tips/create.js.erb: -------------------------------------------------------------------------------- 1 | $("#tips_display").html("<%= escape_javascript(render 'tips/rand') %>"); 2 | $("#tips_vote").html("<%= escape_javascript(render 'user_tips/feedback') %>"); 3 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /db/migrate/20160411184700_add_mssg_to_invitations.rb: -------------------------------------------------------------------------------- 1 | class AddMssgToInvitations < ActiveRecord::Migration 2 | def change 3 | add_column :invitations, :mssg, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GOOGLE_CLIENT_ID='string.apps.googleusercontent.com' 2 | GOOGLE_CLIENT_SECRET='google_client_secret' 3 | SENDGRID_USERNAME='sendgrid_username' 4 | SENDGRID_PASSWORD='sendgrid_password' -------------------------------------------------------------------------------- /app/views/bills/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@bills) do |bill| 2 | json.extract! bill, :id, :bill_received, :amount, :usage, :user_id 3 | json.url bill_url(bill, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /db/migrate/20151223010957_add_unit_to_bill.rb: -------------------------------------------------------------------------------- 1 | class AddUnitToBill < ActiveRecord::Migration 2 | def change 3 | add_reference :bills, :unit, index: true, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160802214548_add_feedback_to_user_tips.rb: -------------------------------------------------------------------------------- 1 | class AddFeedbackToUserTips < ActiveRecord::Migration 2 | def change 3 | add_column :user_tips, :feedback, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/bills/comparison.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@bill) do |bill| 2 | json.extract! bill, :id, :bill_received, :amount, :user_id, :usage 3 | json.url bill_url(bill, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_cambridge_energy_app_session' 4 | -------------------------------------------------------------------------------- /app/views/units/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @unit, :id, :user_building_id, :unit_number, :sqfootage, :number_bedrooms, :number_bathrooms, :number_rooms, :number_occupants, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /db/migrate/20160522213000_remove_street_address_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveStreetAddressFromUsers < ActiveRecord::Migration 2 | def change 3 | remove_column :users, :street_address 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.5 4 | services: 5 | - postgresql 6 | before_script: 7 | - psql -c 'create database travis_ci_test;' -U postgres 8 | addons: 9 | postgresql: "9.4" 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/navbar.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("turbolinks:load", function() { 2 | $("#dropdown-hover").hover(function() { 3 | $("#name-dropdown-list").toggleClass("display-none"); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /config/locales/social_share_button.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | social_share_button: 3 | share_to: Share to %{name} 4 | twitter: Twitter 5 | facebook: Facebook 6 | pinterest: Pinterest 7 | linkedin: Linkedin 8 | -------------------------------------------------------------------------------- /app/views/user_buildings/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@user_buildings) do |user_building| 2 | json.extract! user_building, :id, :address, :lat, :lon 3 | json.url user_building_url(user_building, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /app/uploaders/team_photo_uploader.rb: -------------------------------------------------------------------------------- 1 | class TeamPhotoUploader < CarrierWave::Uploader::Base 2 | storage :file 3 | 4 | def store_dir 5 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20151214022746_change_integer_limit_for_phone_numbers.rb: -------------------------------------------------------------------------------- 1 | class ChangeIntegerLimitForPhoneNumbers < ActiveRecord::Migration 2 | def change 3 | change_column :users, :phone, :integer, limit: 8 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160517210157_create_tips.rb: -------------------------------------------------------------------------------- 1 | class CreateTips < ActiveRecord::Migration 2 | def change 3 | create_table :tips do |t| 4 | t.string :text 5 | 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /db/migrate/20151209023424_create_teams.rb: -------------------------------------------------------------------------------- 1 | class CreateTeams < ActiveRecord::Migration 2 | def change 3 | create_table :teams do |t| 4 | t.string :name 5 | 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/user_tip.rb: -------------------------------------------------------------------------------- 1 | class UserTip < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :tip 4 | 5 | validates :user, presence: true 6 | validates :tip, presence: true 7 | validates :result, presence: true 8 | 9 | end 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.2.5 2 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs 3 | RUN mkdir /app 4 | WORKDIR /app 5 | ADD Gemfile /app/Gemfile 6 | ADD Gemfile.lock /app/Gemfile.lock 7 | RUN bundle install 8 | ADD . /app 9 | -------------------------------------------------------------------------------- /app/assets/javascripts/teams.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/units.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/views/tips/dislike.js.erb: -------------------------------------------------------------------------------- 1 | <% current_user.tipnum += 1 + rand(Tip.not_disliked(current_user).length/2) %> 2 | $("#tips_display").html("<%= escape_javascript(render 'tips/rand') %>"); 3 | $("#tips_vote").html("<%= escape_javascript(render 'tips/vote') %>"); -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/support/json_controller_tests.rb: -------------------------------------------------------------------------------- 1 | module JsonControllerTests 2 | def json_response 3 | JSON.parse(response.body) 4 | end 5 | end 6 | 7 | RSpec.configure do |config| 8 | config.include JsonControllerTests, type: :controller 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/user_buildings.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/controllers/api/v1/addresses_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V1::AddressesController < ApplicationController 2 | before_action :skip_policy_scope 3 | 4 | def index 5 | @addresses = UserBuilding.all_addresses 6 | render json: @addresses 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/units/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@units) do |unit| 2 | json.extract! unit, :id, :user_building_id, :unit_number, :sqfootage, :number_bedrooms, :number_bathrooms, :number_rooms, :number_occupants 3 | json.url unit_url(unit, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /app/helpers/graphs_helper.rb: -------------------------------------------------------------------------------- 1 | module GraphsHelper 2 | def resource_name 3 | :user 4 | end 5 | 6 | def resource 7 | @resource ||= User.new 8 | end 9 | 10 | def devise_mapping 11 | @devise_mapping ||= Devise.mappings[:user] 12 | end 13 | end -------------------------------------------------------------------------------- /app/assets/javascripts/analytics.js: -------------------------------------------------------------------------------- 1 | // Javascript 2 | $(document).on('page:change', function() { 3 | if (window._gaq != null) { 4 | return _gaq.push(['_trackPageview']); 5 | } else if (window.pageTracker != null) { 6 | return pageTracker._trackPageview(); 7 | } 8 | }); -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

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

6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/policies/bill_policy.rb: -------------------------------------------------------------------------------- 1 | class BillPolicy < ApplicationPolicy 2 | def show? 3 | user.id == record.user_id 4 | end 5 | 6 | def update? 7 | user.id == record.user_id 8 | end 9 | 10 | def destroy? 11 | user.id == record.user_id 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/tips/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | <%= @tip.text %> 5 |

6 | Results: 7 | Worked: <%= @tip.liked_votes %>
8 | Didn't work: <%= @tip.disliked_votes %>
9 | <%= render 'share' %> 10 | -------------------------------------------------------------------------------- /db/migrate/20160511005103_change_unit_number_in_units.rb: -------------------------------------------------------------------------------- 1 | class ChangeUnitNumberInUnits < ActiveRecord::Migration 2 | def up 3 | change_column :units, :unit_number, :string 4 | end 5 | 6 | def down 7 | change_column :units, :unit_number, :integer 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160803003338_add_columns_to_bills_table.rb: -------------------------------------------------------------------------------- 1 | class AddColumnsToBillsTable < ActiveRecord::Migration 2 | def change 3 | add_column :bills, :bill_days, :integer 4 | add_column :bills, :amount, :decimal 5 | add_column :bills, :upload_id, :integer 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: postgres 3 | 4 | app: 5 | build: . 6 | command: bundle exec rails server -p 3002 -b '0.0.0.0' 7 | volumes: 8 | - .:/app 9 | ports: 10 | - "3002:3002" 11 | links: 12 | - db 13 | environment: 14 | - RAILS_ENV=docker 15 | -------------------------------------------------------------------------------- /db/migrate/20151214034305_add_omniauth_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddOmniauthToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :provider, :string 4 | add_index :users, :provider 5 | add_column :users, :uid, :string 6 | add_index :users, :uid 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/tips/_rand.html.erb: -------------------------------------------------------------------------------- 1 | <% current_user.tipset %> 2 | <% @tip = Tip.get_tip(current_user) %> 3 | <% unless @tip.nil? %> 4 |

Energy Saving Tip of the Day:

5 |

<%= @tip.text %>

6 |

<%= link_to "Next tip", next_tips_path, :remote => true, class: "button" %>

7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/tips/_share.html.erb: -------------------------------------------------------------------------------- 1 | Your feedback has been recorded. Now you can save the planet by sharing this tip with friends! 2 | <% @url = 'https://www.enersaveapp.org' %> 3 | <% @via = 'https://www.enersaveapp.org' %> 4 | <%= social_share_button_tag(@tip.text, :url => @url, :popup => true) %> 5 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | bills: 3 | add: 'Add a Bill' 4 | create: 'Create Bill' 5 | 6 | graph: 7 | title: 'How does my energy bill compare to my neighbors in Cambridge?' 8 | 9 | navigation: 10 | sign_in: 'Log In' 11 | 12 | users: 13 | sign_in: 14 | submit: 'Log in' 15 | -------------------------------------------------------------------------------- /db/migrate/20151209021917_create_user_buildings.rb: -------------------------------------------------------------------------------- 1 | class CreateUserBuildings < ActiveRecord::Migration 2 | def change 3 | create_table :user_buildings do |t| 4 | t.string :address 5 | t.float :lat 6 | t.float :lon 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160727152525_create_uploads.rb: -------------------------------------------------------------------------------- 1 | class CreateUploads < ActiveRecord::Migration 2 | def change 3 | create_table :uploads do |t| 4 | t.string :filename 5 | t.integer :user_id 6 | t.jsonb :jdoc 7 | t.text :xml 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/policies/upload_policy.rb: -------------------------------------------------------------------------------- 1 | class UploadPolicy < ApplicationPolicy 2 | 3 | def new 4 | true 5 | end 6 | 7 | def show? 8 | true 9 | end 10 | 11 | def create? 12 | true 13 | end 14 | 15 | class Scope < Scope 16 | def resolve 17 | scope.all 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/devise.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include Devise::Test::ControllerHelpers, type: :controller 3 | config.include Devise::TestHelpers, type: :view 4 | config.include Warden::Test::Helpers, type: :feature 5 | 6 | config.before :suite do 7 | Warden.test_mode! 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160803001417_rename_cost_to_usage.rb: -------------------------------------------------------------------------------- 1 | class RenameCostToUsage < ActiveRecord::Migration 2 | def up 3 | change_table :bills do |t| 4 | t.rename :amount, :usage 5 | end 6 | end 7 | def down 8 | change_table :bills do |t| 9 | t.rename :usage, :amount 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20151213213550_create_bills.rb: -------------------------------------------------------------------------------- 1 | class CreateBills < ActiveRecord::Migration 2 | def change 3 | create_table :bills do |t| 4 | t.date :bill_received 5 | t.decimal :amount 6 | t.references :user, index: true, foreign_key: true 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

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

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

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

8 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('#edit_user_form').submit(concatPhone); 3 | }); 4 | 5 | var concatPhone = function() { 6 | var $areaCode = $('#area_code').val(); 7 | var $exchange = $('#exchange').val(); 8 | var $line = $('#line').val(); 9 | $('#user_phone').val($areaCode + $exchange + $line); 10 | }; 11 | -------------------------------------------------------------------------------- /db/migrate/20160517211104_create_user_tips.rb: -------------------------------------------------------------------------------- 1 | class CreateUserTips < ActiveRecord::Migration 2 | def change 3 | create_table :user_tips do |t| 4 | t.references :user, index: true, foreign_key: true 5 | t.references :tip, index: true, foreign_key: true 6 | t.string :result 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/policies/unit_policy.rb: -------------------------------------------------------------------------------- 1 | class UnitPolicy < ApplicationPolicy 2 | def show? 3 | user.unit_id == record.id 4 | end 5 | 6 | def update? 7 | user.unit_id == record.id 8 | end 9 | 10 | def destroy? 11 | user.unit_id == record.id 12 | end 13 | 14 | def create? 15 | true 16 | end 17 | 18 | def leave? 19 | true 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/models/unit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Unit do 4 | 5 | it { should belong_to :user_building } 6 | it { should have_many :users } 7 | it { should have_many :bills } 8 | 9 | it { should have_valid(:number_occupants).when(0, 15, 20) } 10 | it { should_not have_valid(:number_occupants).when(nil, "", "String", 21, -1) } 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/views/bills/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Bill received: 5 | <%= @bill.bill_received %> 6 |

7 | 8 |

9 | Usage (kwh) 10 | <%= @bill.usage %> 11 |

12 | 13 |

14 | User: 15 | <%= @bill.user %> 16 |

17 | 18 | <%= link_to 'Edit', edit_bill_path(@bill) %> 19 | -------------------------------------------------------------------------------- /db/migrate/20160307052834_create_invitations.rb: -------------------------------------------------------------------------------- 1 | class CreateInvitations < ActiveRecord::Migration 2 | def change 3 | create_table :invitations do |t| 4 | t.string :email 5 | t.integer :inviter_id 6 | t.integer :receiver_id 7 | t.integer :sender_id 8 | t.string :token 9 | 10 | t.timestamps null: false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to this project will be documented in this file. 4 | We follow the [Semantic Versioning 2.0.0](http://semver.org/) format. 5 | 6 | 7 | ## x.y.z - YYYY-MM-DD 8 | 9 | ### Added 10 | - Lorem ipsum dolor sit amet 11 | 12 | ### Deprecated 13 | - Nothing. 14 | 15 | ### Removed 16 | - Nothing. 17 | 18 | ### Fixed 19 | - Nothing. 20 | -------------------------------------------------------------------------------- /db/migrate/20160427155259_change_phone_to_string.rb: -------------------------------------------------------------------------------- 1 | class ChangePhoneToString < ActiveRecord::Migration 2 | def up 3 | change_column :users, :phone, :string 4 | end 5 | 6 | def down 7 | # 10-digit phone numbers and empty strings cannot be converted into integers. 8 | raise ActiveRecord::IrreversibleMigration, "Can't convert phone numbers to integers" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/user_buildings/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Address: 5 | <%= @user_building.address %> 6 |

7 | 8 |

9 | Lat: 10 | <%= @user_building.lat %> 11 |

12 | 13 |

14 | Lon: 15 | <%= @user_building.lon %> 16 |

17 | 18 | <%= link_to 'Edit', edit_user_building_path(@user_building) %> 19 | -------------------------------------------------------------------------------- /app/policies/user_building_policy.rb: -------------------------------------------------------------------------------- 1 | class UserBuildingPolicy < ApplicationPolicy 2 | def update? 3 | user.unit.user_building_id == record.id 4 | end 5 | 6 | def destroy? 7 | user.unit.user_building_id == record.id 8 | end 9 | 10 | def show? 11 | true 12 | end 13 | 14 | def create? 15 | true 16 | end 17 | 18 | class Scope < Scope 19 | def resolve 20 | scope.all 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/policies/user_tip_policy.rb: -------------------------------------------------------------------------------- 1 | class UserTipPolicy < ApplicationPolicy 2 | def show? 3 | user.id == record.user_id 4 | end 5 | 6 | def create? 7 | user.id == record.user_id 8 | end 9 | 10 | def update? 11 | user.id == record.user_id 12 | end 13 | 14 | def destroy? 15 | user.id == record.user_id 16 | end 17 | 18 | class Scope < Scope 19 | def resolve 20 | scope.all 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20151209023541_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :first_name 5 | t.string :last_name 6 | t.string :street_address 7 | t.integer :phone 8 | t.references :unit, index: true, foreign_key: true 9 | t.references :team, index: true, foreign_key: true 10 | 11 | t.timestamps null: false 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/controllers/api/v1/addresses_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::V1::AddressesController do 4 | describe 'GET #index' do 5 | it 'gets all the building addresses' do 6 | buildings = create_list(:user_building, 2) 7 | 8 | get :index 9 | 10 | expect(json_response).not_to be_empty 11 | expect(json_response.map{|h| h['value']}).to eq buildings.map(&:id) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/models/bill_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bill do 4 | 5 | it { should belong_to :user } 6 | it { should belong_to :unit } 7 | 8 | it { should have_valid(:bill_received).when("2016-02-07") } 9 | it { should_not have_valid(:bill_received).when(nil, "", "String") } 10 | 11 | it { should have_valid(:usage).when(0, 4000, 9999) } 12 | it { should_not have_valid(:usage).when(-10, nil, "", "String", 10000) } 13 | 14 | end 15 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/models/unit.rb: -------------------------------------------------------------------------------- 1 | class Unit < ActiveRecord::Base 2 | belongs_to :user_building 3 | has_many :users, dependent: :destroy # Again, ruthless. Or :nullify? But that would just break everything. 4 | has_many :bills 5 | after_initialize :init 6 | 7 | def init 8 | self.unit_number ||= "1" 9 | end 10 | 11 | validates :number_occupants, presence: true, numericality: { 12 | greater_than_or_equal_to: 0, 13 | less_than_or_equal_to: 20 14 | } 15 | end 16 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

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

4 | 5 |

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

6 | 7 |

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

8 |

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

9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/tables.scss: -------------------------------------------------------------------------------- 1 | tbody tr td, 2 | table tr td { 3 | display: table-cell; 4 | line-height: 1.125rem; 5 | } 6 | 7 | table tr td { 8 | font-size: 0.875rem; 9 | text-align: left; 10 | padding: 0.5625rem 0.625rem; 11 | margin: 0 auto; 12 | } 13 | 14 | th, table tr.even, table tr.alt, table tr:nth-of-type(even) { 15 | background-color: #f3f3f3; 16 | } 17 | 18 | table { 19 | background-color: white; 20 | table-layout: auto; 21 | margin: 0 auto; 22 | } 23 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /app/views/teams/_invitation.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @user.inviter_name %> wants you to join <%= @user.inviter_team.name %>

2 | 3 | <%= form_tag({controller: "teams", action: "accept_or_decline"}, method: "post") do |f| %> 4 |
5 | <%= submit_tag "accept", name: "invite_params", class: 'button--discrete', :id => 'success' %> 6 | <%= submit_tag "decline", name: "invite_params", class: 'button--discrete', :id => 'warning' %> 7 |
8 | <% end %> 9 | -------------------------------------------------------------------------------- /db/migrate/20151209023328_create_units.rb: -------------------------------------------------------------------------------- 1 | class CreateUnits < ActiveRecord::Migration 2 | def change 3 | create_table :units do |t| 4 | t.references :user_building, index: true, foreign_key: true 5 | t.integer :unit_number 6 | t.integer :sqfootage 7 | t.integer :number_bedrooms 8 | t.integer :number_bathrooms 9 | t.integer :number_rooms 10 | t.integer :number_occupants 11 | 12 | t.timestamps null: false 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/tips/_vote.html.erb: -------------------------------------------------------------------------------- 1 | <% @tip = Tip.get_tip(current_user) %> 2 | <% unless @tip.nil? %> 3 | <% if UserTip.find_by(tip_id: @tip.id, user_id: current_user.id, result: "Liked") %> 4 | <%= render 'tips/share' %> 5 | <% else %> 6 | Did you try this tip?

7 | <% @user_tip = UserTip.new() %> 8 | <%= link_to "Yes", like_tips_path, :remote => true, :class => 'button' %> 9 | <%= link_to "No", dislike_tips_path, :remote => true, :class => 'button' %> 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/users/invitations/new.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t "devise.invitations.new.header" %>

2 | 3 | <%= form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => {:method => :post} do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 | <% resource.class.invite_key_fields.each do |field| -%> 7 |

<%= f.label field %>
8 | <%= f.text_field field %>

9 | <% end -%> 10 | 11 |

<%= f.submit t("devise.invitations.new.submit_button") %>

12 | <% end %> 13 | -------------------------------------------------------------------------------- /spec/controllers/graphs_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe GraphController do 4 | 5 | before(:each) do 6 | @user = create(:user, unit: @unit) 7 | sign_in @user 8 | end 9 | 10 | describe 'GET #index' do 11 | it 'is successful' do 12 | get(:index) 13 | expect(assigns(:user)).not_to be_nil 14 | 15 | # And make sure we're assigning the right person (current_user). 16 | expect(assigns(:user).id).to eq(@user.id) 17 | end 18 | end 19 | 20 | end -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | def get_bill(user,date) 4 | bill_at_date = user.bills.select { |bill| bill.bill_received.year == date.year && bill.bill_received.month == date.month } 5 | if bill_at_date.length == 0 6 | message = "No bills this month" 7 | elsif bill_at_date.length > 1 8 | message = "Too many bills submitted" 9 | else 10 | message = bill_at_date.first.usage 11 | end 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.submit "Send me reset password instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/policies/tip_policy.rb: -------------------------------------------------------------------------------- 1 | class TipPolicy < ApplicationPolicy 2 | def show? 3 | true 4 | end 5 | 6 | def create? 7 | true 8 | end 9 | 10 | def update? 11 | true 12 | end 13 | 14 | def destroy? 15 | true 16 | end 17 | 18 | def share? 19 | true 20 | end 21 | 22 | def next? 23 | true 24 | end 25 | 26 | def like? 27 | true 28 | end 29 | 30 | def dislike? 31 | true 32 | end 33 | 34 | class Scope < Scope 35 | def resolve 36 | scope.all 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /app/models/bill.rb: -------------------------------------------------------------------------------- 1 | class Bill < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :unit 4 | belongs_to :upload 5 | accepts_nested_attributes_for :unit 6 | 7 | validate :bill_received_is_date? 8 | validates :bill_received, presence: true 9 | validates :usage, presence: true, numericality: { 10 | greater_than_or_equal_to: 0, 11 | less_than_or_equal_to: 9999 12 | } 13 | 14 | def bill_received_is_date? 15 | unless bill_received.is_a?(Date) 16 | errors.add(:bill_received, 'must be a valid date') 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /app/policies/team_policy.rb: -------------------------------------------------------------------------------- 1 | class TeamPolicy < ApplicationPolicy 2 | def show? 3 | user.team_id == record.id 4 | end 5 | 6 | def create? 7 | true 8 | end 9 | 10 | def update? 11 | user.team_id == record.id 12 | end 13 | 14 | def destroy? 15 | user.team_id == record.id 16 | end 17 | 18 | def leave? 19 | user.team_id == record.id 20 | end 21 | 22 | def leaderboard? 23 | true 24 | end 25 | 26 | def accept_or_decline? 27 | true 28 | end 29 | 30 | class Scope < Scope 31 | def resolve 32 | scope.all 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/tips/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@tip) do |f| %> 2 | <% if @tip.errors.any? %> 3 |
4 |

<%= pluralize(@tip.errors.count, "error") %> prohibited this tip from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :text %>
16 | <%= f.text_field :text %> 17 |
18 |
19 | <%= f.submit %> 20 |
21 | <% end %> 22 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/views/devise/mailer/invitation_instructions.text.erb: -------------------------------------------------------------------------------- 1 | <%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %> 2 | 3 | <%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %> 4 | 5 | <%= accept_invitation_url(@resource, :invitation_token => @token) %> 6 | 7 | <% if @resource.invitation_due_at %> 8 | <%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %> 9 | <% end %> 10 | 11 | <%= strip_tags t("devise.mailer.invitation_instructions.ignore") %> 12 | -------------------------------------------------------------------------------- /app/views/users/mailer/invitation_instructions.text.erb: -------------------------------------------------------------------------------- 1 | <%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %> 2 | 3 | <%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %> 4 | 5 | <%= accept_invitation_url(@resource, :invitation_token => @token) %> 6 | 7 | <% if @resource.invitation_due_at %> 8 | <%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %> 9 | <% end %> 10 | 11 | <%= strip_tags t("devise.mailer.invitation_instructions.ignore") %> 12 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 9 |
10 | 11 |
12 | <%= f.submit "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /spec/features/user_can_sign_in_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.feature 'User can sign in' do 4 | scenario 'from the navbar' do 5 | user = create(:user, password: '12345678') 6 | visit root_path 7 | 8 | within '.top-bar' do 9 | click_on I18n.t('navigation.sign_in') 10 | end 11 | 12 | fill_in :user_email, with: user.email 13 | fill_in :user_password, with: '12345678' 14 | 15 | click_on I18n.t('users.sign_in.submit') 16 | 17 | within '.top-bar' do 18 | expect(page).to have_content user.first_name_or_email 19 | end 20 | expect(current_path).to eq leaderboard_teams_path 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | !/log/.keep 13 | /tmp 14 | 15 | # Ignore uploaded files 16 | /public/uploads/* 17 | 18 | # Ignore local env settings 19 | .env 20 | 21 | # Ignore application configuration 22 | /config/application.yml 23 | 24 | # Ignore ctags files 25 | tags 26 | -------------------------------------------------------------------------------- /app/views/users/invitations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t 'devise.invitations.edit.header' %>

2 | 3 | <%= form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => { :method => :put } do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :invitation_token %> 6 | 7 | <% if f.object.class.require_password_on_accepting %> 8 |

<%= f.label :password %>
9 | <%= f.password_field :password %>

10 | 11 |

<%= f.label :password_confirmation %>
12 | <%= f.password_field :password_confirmation %>

13 | <% end %> 14 | 15 |

<%= f.submit t("devise.invitations.edit.submit_button") %>

16 | <% end %> 17 | -------------------------------------------------------------------------------- /app/controllers/users/invitations_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::InvitationsController < Devise::InvitationsController 2 | # POST /resource/invitation 3 | def create 4 | user = User.find_by(email: invite_params[:email]) 5 | if user && user.email != current_user.email 6 | if user.team_id != current_user.team_id 7 | current_user.invite_existing(user) 8 | flash[:notice] = "#{user.first_name_or_email} has been invited to join your team!" 9 | else 10 | flash[:notice] = "#{user.first_name_or_email} is already on your team!" 11 | end 12 | respond_with user, :location => after_invite_path_for(user) 13 | else 14 | super 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/invite_existing_user_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | require "spec_helper" 3 | include Warden::Test::Helpers 4 | 5 | feature "user can invite existing user to join team" do 6 | scenario "user successfully invites existing user" do 7 | user = create(:user) 8 | # create another user to send invitation to 9 | create(:user, email: "existing@example.com") 10 | login_as user, :scope => :user 11 | visit "/users/invitation/new" 12 | fill_in "Email", with: "existing@example.com" 13 | #click button: send an invitation 14 | find('input[name="commit"]').click 15 | expect(page).to have_content "#{user.first_name} has been invited to join your team" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/devise/mailer/invitation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>

2 | 3 |

<%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %>

4 | 5 |

<%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %>

6 | 7 | <% if @resource.invitation_due_at %> 8 |

<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>

9 | <% end %> 10 | 11 |

<%= t("devise.mailer.invitation_instructions.ignore").html_safe %>

12 | -------------------------------------------------------------------------------- /app/views/users/mailer/invitation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>

2 | 3 |

<%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %>

4 | 5 |

<%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %>

6 | 7 | <% if @resource.invitation_due_at %> 8 |

<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>

9 | <% end %> 10 | 11 |

<%= t("devise.mailer.invitation_instructions.ignore").html_safe %>

12 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | 3 | def profile_complete_meter(user) 4 | complete_percentage = 0 5 | complete_percentage += 25 unless user.first_name.nil? || user.first_name.empty? 6 | complete_percentage += 25 unless user.last_name.nil? || user.last_name.empty? 7 | complete_percentage += 35 if user.unit.present? 8 | complete_percentage += 15 unless user.phone.nil? || user.phone.empty? 9 | 10 | complete_percentage 11 | end 12 | 13 | def devise_edit_user_path 14 | '/users/edit' 15 | end 16 | 17 | def edit_current_user_path 18 | '/users/me/edit' 19 | end 20 | 21 | def current_user_path 22 | '/users/me' 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /app/views/teams/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Listing Teams

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @teams.each do |team| %> 15 | 16 | 17 | 18 | 19 | 20 | 21 | <% end %> 22 | 23 |
Name
<%= team.name %><%= link_to 'Show', team %><%= link_to 'Edit', edit_team_path(team) %><%= link_to 'Destroy', team, method: :delete, data: { confirm: 'Are you sure?' } %>
24 | 25 |
26 | 27 | <% unless @user.team_id %> 28 | <%= link_to 'Create a Team', new_team_path %> 29 | <% end %> 30 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /app/assets/images/icon-arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /app/views/teams/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@team) do |f| %> 2 | <% if @team.errors.any? %> 3 |
4 |

<%= pluralize(@team.errors.count, "error") %> prohibited this team from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :name %>
16 | <%= f.text_field :name %> 17 |
18 |
19 | <%= f.label :image_url %>
20 | <%= f.file_field :image_url %> 21 |
22 |
23 | <%= f.submit :class => 'button'%> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /db/migrate/20160521191953_add_devise_invitable_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDeviseInvitableToUsers < ActiveRecord::Migration 2 | def change 3 | def change 4 | add_column :users, :invitation_token, :string 5 | add_column :users, :invitation_created_at, :datetime 6 | add_column :users, :invitation_sent_at, :datetime 7 | add_column :users, :invitation_accepted_at, :datetime 8 | add_column :users, :invitation_limit, :integer 9 | add_column :users, :invited_by_id, :integer 10 | add_column :users, :invited_by_type, :string 11 | add_index :users, :invitation_token, :unique => true 12 | end 13 | 14 | # Allow null encrypted_password 15 | change_column :users, :encrypted_password, :string, :null => true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Log in

2 | 3 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 4 |
5 | <%= f.label :email %>
6 | <%= f.email_field :email, autofocus: true %> 7 |
8 | 9 |
10 | <%= f.label :password %>
11 | <%= f.password_field :password, autocomplete: "off" %> 12 |
13 | 14 | <% if devise_mapping.rememberable? -%> 15 |
16 | <%= f.check_box :remember_me %> 17 | <%= f.label :remember_me %> 18 |
19 | <% end %> 20 | 21 |
22 | <%= f.submit t('users.sign_in.submit'), class: "button" %> 23 |
24 | <% end %> 25 | 26 | <%= render "devise/shared/links" %> 27 | -------------------------------------------------------------------------------- /app/views/bills/comparison.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

How You Compare to Your Neighbors

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @comparison_bills.each do |bill| %> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <% end %> 25 | 26 |
Bill receivedUsage (kwh)User
<%= bill.bill_received %><%= bill.usage %><%= bill.user %><%= link_to 'Show', bill %><%= link_to 'Edit', edit_bill_path(bill) %><%= link_to 'Destroy', bill, method: :delete, data: { confirm: 'Are you sure?' } %>
27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.scss: -------------------------------------------------------------------------------- 1 | pre { 2 | background-color: #eee; 3 | padding: 10px; 4 | font-size: 11px; 5 | } 6 | 7 | div { 8 | &.field, &.actions { 9 | margin-bottom: 10px; 10 | } 11 | } 12 | 13 | .field_with_errors { 14 | padding: 2px; 15 | background-color: red; 16 | display: table; 17 | } 18 | 19 | #error_explanation { 20 | width: 450px; 21 | border: 2px solid red; 22 | padding: 7px; 23 | padding-bottom: 0; 24 | margin-bottom: 20px; 25 | background-color: #f0f0f0; 26 | 27 | h2 { 28 | text-align: left; 29 | font-weight: bold; 30 | padding: 5px 5px 5px 15px; 31 | font-size: 12px; 32 | margin: -7px; 33 | margin-bottom: 0px; 34 | background-color: #c00; 35 | color: #fff; 36 | } 37 | 38 | ul li { 39 | font-size: 12px; 40 | list-style: square; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/views/user_buildings/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@user_building) do |f| %> 2 | <% if @user_building.errors.any? %> 3 |
4 |

<%= pluralize(@user_building.errors.count, "error") %> prohibited this user_building from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :address %>
16 | <%= f.text_field :address %> 17 |
18 |
19 | <%= f.label :lat %>
20 | <%= f.text_field :lat %> 21 |
22 |
23 | <%= f.label :lon %>
24 | <%= f.text_field :lon %> 25 |
26 |
27 | <%= f.submit :class => 'button'%> 28 |
29 | <% end %> 30 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require jquery-ui/datepicker 14 | *= require social-share-button 15 | *= require_self 16 | *= require tables 17 | *= require foundation_and_overrides 18 | *= require main 19 | */ 20 | -------------------------------------------------------------------------------- /app/views/bills/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Listing Bills

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% @bills.each do |bill| %> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | <% end %> 28 | 29 |
Bill receivedUsage (kwh)AmountUser
<%= bill.bill_received %><%= bill.usage %><%= bill.amount %><%= bill.user.first_name %><%= link_to 'Show', bill %><%= link_to 'Edit', edit_bill_path(bill) %><%= link_to 'Destroy', bill, method: :delete, data: { confirm: 'Are you sure?' } %>
30 | 31 |
32 | 33 | <%= link_to 'New Bill', new_bill_path %> 34 | -------------------------------------------------------------------------------- /app/views/units/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Address: 5 | <%= link_to @unit.user_building.address, @unit.user_building if @unit.user_building.present? %> 6 |

7 | 8 |

9 | Unit number: 10 | <%= @unit.unit_number %> 11 |

12 | 13 |

14 | Sqfootage: 15 | <%= @unit.sqfootage %> 16 |

17 | 18 |

19 | Number bedrooms: 20 | <%= @unit.number_bedrooms %> 21 |

22 | 23 |

24 | Number bathrooms: 25 | <%= @unit.number_bathrooms %> 26 |

27 | 28 |

29 | Number rooms: 30 | <%= @unit.number_rooms %> 31 |

32 | 33 |

34 | Number occupants: 35 | <%= @unit.number_occupants %> 36 |

37 | 38 | <%= link_to 'Edit', edit_unit_path(@unit) %> | 39 | <%= link_to 'Move Out', leave_unit_path(@unit), :method => :patch %> 40 | -------------------------------------------------------------------------------- /app/views/user_buildings/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Listing User Buildings

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% @user_buildings.each do |user_building| %> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <% end %> 26 | 27 |
AddressLatLon
<%= user_building.address %><%= user_building.lat %><%= user_building.lon %><%= link_to 'Show', user_building %><%= link_to 'Edit', edit_user_building_path(user_building) %><%= link_to 'Destroy', user_building, method: :delete, data: { confirm: 'Are you sure?' } %>
28 | 29 |
30 | 31 | <%= link_to 'New User building', new_user_building_path %> 32 | -------------------------------------------------------------------------------- /app/views/tips/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |
4 |
5 | <%= render 'rand' %> 6 |
7 | 8 |
9 | <%= render 'user_tips/feedback' %> 10 |
11 |
12 |

Listing Tips

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <% @tips.each do |tip| %> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | <% end %> 34 | 35 |
TextLikesDislikes
<%= tip.text %><%= tip.liked_votes %><%= tip.disliked_votes %><%= link_to 'Show', tip %><%= link_to 'Destroy', tip, method: :delete, data: { confirm: 'Are you sure?' } %>
36 | 37 |
38 | 39 | <%= link_to 'New Tip', new_tip_path %> 40 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.label :password %> 13 | <% if @minimum_password_length %> 14 | (<%= @minimum_password_length %> characters minimum) 15 | <% end %>
16 | <%= f.password_field :password, autocomplete: "off" %> 17 |
18 | 19 |
20 | <%= f.label :password_confirmation %>
21 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 22 |
23 | 24 |
25 | <%= f.submit "Sign up", class: "button" %> 26 |
27 | <% end %> 28 | 29 | <%= render "devise/shared/links" %> 30 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %> 10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %> 12 | <%= f.password_field :password, autofocus: true, autocomplete: "off" %> 13 |
14 | 15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 18 |
19 | 20 |
21 | <%= f.submit "Change my password" %> 22 |
23 | <% end %> 24 | 25 | <%= render "devise/shared/links" %> 26 | -------------------------------------------------------------------------------- /db/migrate/20160521191312_devise_invitable_add_to_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseInvitableAddToUsers < ActiveRecord::Migration 2 | def up 3 | change_table :users do |t| 4 | t.string :invitation_token 5 | t.datetime :invitation_created_at 6 | t.datetime :invitation_sent_at 7 | t.datetime :invitation_accepted_at 8 | t.integer :invitation_limit 9 | t.references :invited_by, polymorphic: true 10 | t.integer :invitations_count, default: 0 11 | t.index :invitations_count 12 | t.index :invitation_token, unique: true # for invitable 13 | t.index :invited_by_id 14 | end 15 | end 16 | 17 | def down 18 | change_table :users do |t| 19 | t.remove_references :invited_by, polymorphic: true 20 | t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery.turbolinks 15 | //= require jquery_ujs 16 | //= require jquery-ui/datepicker 17 | //= require jquery-ui/autocomplete 18 | //= require turbolinks 19 | //= require d3 20 | //= require social-share-button 21 | //= require foundation 22 | //= require_tree . 23 | $(document).foundation(); 24 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | require 'new_relic/recipes' 2 | 3 | # Load DSL and set up stages 4 | require "capistrano/setup" 5 | 6 | # Include default deployment tasks 7 | require "capistrano/deploy" 8 | 9 | # Include tasks from other gems included in your Gemfile 10 | # 11 | # For documentation on these, see for example: 12 | # 13 | # https://github.com/capistrano/rvm 14 | # https://github.com/capistrano/rbenv 15 | # https://github.com/capistrano/chruby 16 | # https://github.com/capistrano/bundler 17 | # https://github.com/capistrano/rails 18 | # https://github.com/capistrano/passenger 19 | # 20 | require 'capistrano/rvm' 21 | # require 'capistrano/rbenv' 22 | # require 'capistrano/chruby' 23 | require 'capistrano/bundler' 24 | require 'capistrano/rails/assets' 25 | require 'capistrano/rails/migrations' 26 | require 'capistrano/passenger' 27 | 28 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined 29 | Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } 30 | -------------------------------------------------------------------------------- /app/models/tip.rb: -------------------------------------------------------------------------------- 1 | class Tip < ActiveRecord::Base 2 | has_many :user_tips, dependent: :destroy 3 | has_many :users, through: :user_tips 4 | 5 | def liked_votes 6 | return UserTip.select { |usertip| usertip.tip.id == self.id && usertip.result == "Liked" }.length 7 | end 8 | 9 | def disliked_votes 10 | return UserTip.select { |usertip| usertip.tip.id == self.id && usertip.result == "Disliked" }.length 11 | end 12 | 13 | def self.not_disliked(user) 14 | @user = user 15 | return Tip.select { |tip| tip.user_tips.find_by(user_id: @user.id, result: "Disliked").nil? } 16 | end 17 | 18 | def self.get_tip(user) 19 | if Tip.not_disliked(user).length > 0 20 | return Tip.not_disliked(user)[user.tipnum] 21 | end 22 | end 23 | 24 | def self.next_tip(user) 25 | @user = user 26 | @number_of_tips = Tip.not_disliked(user).length 27 | user.tipnum = ((user.tipnum + 1 + rand(@number_of_tips/2)) % @number_of_tips) 28 | user.save() 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/users/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController 2 | def google_oauth2 3 | # You need to implement the method below in your model (e.g. app/models/user.rb) 4 | @user = User.from_omniauth(request.env["omniauth.auth"]) 5 | 6 | if @user.persisted? 7 | #sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated 8 | #set_flash_message(:notice, :success, :kind => "Google") if is_navigational_format? 9 | flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google" 10 | sign_in_and_redirect @user, :event => :authentication 11 | else 12 | session["devise.google_data"] = request.env["omniauth.auth"] 13 | redirect_to new_user_registration_url 14 | end 15 | end 16 | 17 | def self.from_omniauth(access_token) 18 | data = access_token.info 19 | user = User.where(:email => data["email"]).first 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20151214032953_add_devise_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDeviseToUsers < ActiveRecord::Migration 2 | def change 3 | ## Database authenticatable 4 | add_column :users, :email, :string, null: false 5 | add_column :users, :encrypted_password, :string, null: false, default: "" 6 | 7 | ## Recoverable 8 | add_column :users, :reset_password_token, :string 9 | add_column :users, :reset_password_sent_at, :datetime 10 | 11 | ## Rememberable 12 | add_column :users, :remember_created_at, :datetime 13 | 14 | ## Trackable 15 | add_column :users, :sign_in_count, :integer, default: 0, null: false 16 | add_column :users, :current_sign_in_at, :datetime 17 | add_column :users, :last_sign_in_at, :datetime 18 | add_column :users, :current_sign_in_ip, :inet 19 | add_column :users, :last_sign_in_ip, :inet 20 | 21 | # Indexes 22 | add_index :users, :email, unique: true 23 | add_index :users, :reset_password_token, unique: true 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/upload.rb: -------------------------------------------------------------------------------- 1 | class Upload < ActiveRecord::Base 2 | belongs_to :user, inverse_of: :uploads 3 | has_many :bills 4 | 5 | def self.jsonify(xml) 6 | Hash.from_xml(xml).to_json 7 | end 8 | 9 | def create_bills_from_xml 10 | doc = Nokogiri::XML(xml) 11 | readings = doc.xpath('//ns:IntervalReading') 12 | readings.each do |reading| 13 | start_date_in_sec = reading.at('.//ns:start').text.to_i 14 | duration_in_sec = reading.at('.//ns:duration').text.to_i 15 | cost = reading.at('.//ns:cost').text.to_f / 100000 16 | kwh = reading.at('.//ns:value').text.to_i 17 | bill_date = Time.at(start_date_in_sec + duration_in_sec) 18 | bill_days = duration_in_sec/86400 # seconds per day 19 | 20 | bill = Bill.create( 21 | bill_received: bill_date, 22 | bill_days: bill_days, 23 | usage: kwh, 24 | amount: cost, 25 | user: user, 26 | unit: user.unit, 27 | upload: self 28 | ) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/features/team_adds_photo_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | require "spec_helper" 3 | include Warden::Test::Helpers 4 | 5 | feature "user can add photo to team" do 6 | scenario "user uploads failing filetype" do 7 | user = create(:user) 8 | team = create(:team) 9 | login_as user, :scope => :user 10 | visit "/teams/new" 11 | fill_in "Name", with: "Test Team" 12 | attach_file "Image url", "#{Rails.root}/spec/controllers/bills_controller_spec.rb" 13 | click_button "Create Team" 14 | expect(page).to have_content "Image url file should be one of image/jpeg, image/png, image/jpg" 15 | end 16 | 17 | scenario "user uploads allowed filetype" do 18 | user = create(:user) 19 | team = create(:team) 20 | login_as user, :scope => :user 21 | visit "/teams/new" 22 | fill_in "Name", with: "Test Team" 23 | attach_file "Image url", "#{Rails.root}/spec/support/tux.jpg" 24 | click_button "Create Team" 25 | expect(page).to have_css("img[src*='tux.jpg']") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :set_user 3 | before_action :authorize_user 4 | 5 | def show 6 | end 7 | 8 | def edit 9 | @area = @user.area_code 10 | @exchange = @user.exchange 11 | @line = @user.line 12 | end 13 | 14 | def update 15 | respond_to do |format| 16 | if @user.update(user_params) 17 | format.html { render :show, notice: 'User was successfully updated.' } 18 | format.json { render :show, status: :ok, location: @user } 19 | else 20 | format.html { render :edit } 21 | format.json { render json: @user.errors, status: :unprocessable_entity } 22 | end 23 | end 24 | end 25 | 26 | private 27 | 28 | def set_user 29 | @user = current_or_guest_user 30 | end 31 | 32 | def authorize_user 33 | authorize @user 34 | end 35 | 36 | def user_params 37 | params.require(:user).permit(:first_name, :last_name, :phone, :image) 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /app/policies/application_policy.rb: -------------------------------------------------------------------------------- 1 | class ApplicationPolicy 2 | attr_reader :user, :record 3 | 4 | def initialize(user, record) 5 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 6 | 7 | @user = user 8 | @record = record 9 | end 10 | 11 | def index? 12 | false 13 | end 14 | 15 | def show? 16 | scope.where(:id => record.id).exists? 17 | end 18 | 19 | def create? 20 | false 21 | end 22 | 23 | def new? 24 | create? 25 | end 26 | 27 | def update? 28 | false 29 | end 30 | 31 | def edit? 32 | update? 33 | end 34 | 35 | def destroy? 36 | false 37 | end 38 | 39 | def scope 40 | Pundit.policy_scope!(user, record.class) 41 | end 42 | 43 | class Scope 44 | attr_reader :user, :scope 45 | 46 | def initialize(user, scope) 47 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 48 | 49 | @user = user 50 | @scope = scope 51 | end 52 | 53 | def resolve 54 | scope 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /db/migrate/20160702173010_add_granular_address_columns_to_user_buildings.rb: -------------------------------------------------------------------------------- 1 | class AddGranularAddressColumnsToUserBuildings < ActiveRecord::Migration 2 | 3 | # Possible hash keys from Indirizzo address-parsing gem. 4 | # I bet :prenum is unit number. But I'm adding it anyway becuase it won't tangle with Unit.unit_number becuase that's going to the Unit model. 5 | # Also, no idea what a :sufnum is. 6 | # :prenum, :number, :sufnum, :street, :city, :state, :zip, :plus4, and :country 7 | GRANULES = [:prenum, :number, :sufnum, :street, :city, :state, :zip, :plus4, :country] 8 | 9 | # Everything is a string because I don't know/not validating anything that comes through the address or Indirizzo parsing. 10 | # I'm not making and DB-side defaults or null settings because I don't want to. 11 | def up 12 | GRANULES.each do |grain| 13 | add_column :user_buildings, grain, :string 14 | end 15 | end 16 | 17 | def down 18 | GRANULES.each do |grain| 19 | remove_column :user_buildings, grain, :string 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/uploads_controller.rb: -------------------------------------------------------------------------------- 1 | class UploadsController < ApplicationController 2 | before_action :authorize_user 3 | def new 4 | @upload = Upload.new 5 | end 6 | 7 | def create 8 | upload_io = params[:upload][:xml] 9 | xml = upload_io.read 10 | data_hash = Hash.from_xml(xml) 11 | json = data_hash.to_json 12 | 13 | @upload = Upload.new( 14 | filename: upload_io.original_filename, 15 | jdoc: json, 16 | xml: xml, 17 | user: current_user 18 | ) 19 | if @upload.save 20 | @upload.create_bills_from_xml 21 | redirect_to upload_path(@upload), notice: 'Upload successful!' 22 | else 23 | render :new 24 | end 25 | end 26 | 27 | def show 28 | @upload = Upload.find(params[:id]) 29 | @user = User.find(@upload.user_id) 30 | @bills = Bill.where(upload_id: @upload.id) 31 | end 32 | 33 | def allowed_params 34 | params.require(:upload).permit(:xml) 35 | end 36 | 37 | private 38 | 39 | def authorize_user 40 | authorize Upload 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name), class: 'button' %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | 22 | <%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path, class: 'button' %> 23 | -------------------------------------------------------------------------------- /app/views/teams/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |
4 |
5 |
6 | <% if @team.image_url %> 7 | <%= image_tag @team.image_url, class: "team_image" %> 8 | <% end %> 9 |

<%= @team.name %>

10 |
11 |
12 |
13 | 14 |
15 | <% @team.users.each do |user| %> 16 |
17 | Email: 18 |
19 |

20 | <%= user.email %> 21 |

22 |
23 |
24 | Score: 25 |
26 |

27 | <%= user.score %> 28 |

29 |
30 | <% end %> 31 |
32 | 33 |
34 |
35 | <%= link_to 'Invite a Friend', new_user_invitation_path, class: "button" %> 36 | <%= link_to 'Leave team', leave_team_path(@team), class: 'button', id: 'warning' %> 37 | <%= link_to 'Edit', edit_team_path(@team), class: 'button', id: 'action' %> 38 |
39 |
40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Code for Boston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/views/units/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Listing Units

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% @units.each do |unit| %> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | <% end %> 34 | 35 |
User buildingUnit numberSqfootageNumber bedroomsNumber bathroomsNumber roomsNumber occupants
<%= unit.user_building %><%= unit.unit_number %><%= unit.sqfootage %><%= unit.number_bedrooms %><%= unit.number_bathrooms %><%= unit.number_rooms %><%= unit.number_occupants %><%= link_to 'Show', unit %><%= link_to 'Edit', edit_unit_path(unit) %><%= link_to 'Destroy', unit, method: :delete, data: { confirm: 'Are you sure?' } %>
36 | 37 |
38 | 39 | <%= link_to 'New Unit', new_unit_path %> 40 | -------------------------------------------------------------------------------- /spec/models/team_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Team do 4 | 5 | let(:team) { create :team } 6 | 7 | context "Validations" do 8 | it { should validate_presence_of :name } 9 | it { should allow_value(File.open(File.join(Rails.root, 10 | '/spec/support/tux.jpg'))).for(:image_url) } 11 | it { should_not allow_value(File.open(File.join(Rails.root, 12 | '/spec/controllers/bills_controller_spec.rb'))).for(:image_url) } 13 | 14 | end 15 | 16 | context "Associations" do 17 | it { should have_many :users } 18 | end 19 | 20 | it 'has a valid factory' do 21 | expect(team).to be_valid 22 | end 23 | 24 | describe '.score' do 25 | let(:user) { create(:user, bills: []) } 26 | let(:team_with_score) { create :team_with_members } 27 | 28 | it 'should return a score if at least one team member has sufficient bills' do 29 | # RE-RUN: if the factory randomly selects bills with similar values test fails" 30 | team_with_score.users.push(user) 31 | expect(team_with_score.score).not_to eq 0 32 | end 33 | 34 | it 'should return 0 if the team lacks sufficient bills' do 35 | expect(team.score).to eq 0 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/models/team.rb: -------------------------------------------------------------------------------- 1 | class Team < ActiveRecord::Base 2 | # == carrierwave File Uploader 3 | mount_uploader :image_url, TeamPhotoUploader 4 | # == Associations 5 | has_many :users 6 | has_many :receivers, through: :invitations 7 | 8 | # == Validations 9 | validates :name, presence: true 10 | validates :image_url, file_content_type: { allow: ['image/jpeg', 'image/png', 'image/jpg'] } 11 | 12 | # == Scope methods 13 | scope :with_users, -> { includes(:users).select{ |team| team.users.length > 0 }} 14 | scope :by_score, -> { with_users.sort_by(&:score).reverse } 15 | 16 | # == Instance methods 17 | def score 18 | bills = self.users.map { |user| user.most_recent_bills(2) } 19 | bills.reject! { |score| score.length < 2 } 20 | this_month_bills = [] 21 | last_month_bills = [] 22 | bills.each do |score| 23 | last_month_bills << score[0] 24 | this_month_bills << score[1] 25 | end 26 | this_month_total = this_month_bills.reduce(:+) 27 | last_month_total = last_month_bills.reduce(:+) 28 | 29 | if this_month_total && last_month_total 30 | ((last_month_total - this_month_total) / last_month_total) 31 | else 32 | 0 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 908b58889fddebf4882b2e2286296d84366f48f46f9611ab5b9531cb70ef2c352a5dfb71f2560dcc3d255b06ffeda490dc88f6556e8b0354dcde40bc7a8c60f6 15 | 16 | docker: 17 | secret_key_base: 4d63deaced461c272536d26006fff68cc5940df200541111692ca63b462a4318a3cf1ad7df7d8d4cae7dc608c1c05695b7c2fbcf74c7b18fd7352183aaa857d9 18 | 19 | test: 20 | secret_key_base: 423d16a990f7dc01769efb4a6bc1141e0029f87433ca06b81b1e75baad6d10b7e76262a6cb5313546c59fa8d0aecc4eaa1a675e034467dcd456a47fdbe4e045a 21 | 22 | # Do not keep production secrets in the repository, 23 | # instead read values from the environment. 24 | production: 25 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 26 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module CambridgeEnergyApp 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | 23 | # Do not swallow errors in after_commit/after_rollback callbacks. 24 | config.active_record.raise_in_transactional_callbacks = true 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UsersController do 4 | let(:user) { create(:user) } 5 | 6 | #write feature test for signed-in/signed-out 7 | describe 'GET #show' do 8 | it 'renders #show' do 9 | sign_in user 10 | get :show 11 | expect(response).to be_success 12 | end 13 | end 14 | 15 | describe 'GET #edit' do 16 | it 'renders #edit' do 17 | sign_in user 18 | get :show 19 | expect(response).to be_success 20 | end 21 | end 22 | 23 | 24 | describe 'PATCH #update' do 25 | context 'when information is valid' do 26 | it 'successfully updates user' do 27 | valid_user_attrs = { phone: '1234567890' } 28 | sign_in user 29 | 30 | patch(:update, user: valid_user_attrs) 31 | 32 | expect(response).to render_template("show") 33 | end 34 | end 35 | 36 | context 'when information is invalid' do 37 | it 'renders #edit' do 38 | invalid_user_attrs = { phone: 'not a string of numbers' } 39 | sign_in user 40 | 41 | patch(:update, user: invalid_user_attrs) 42 | 43 | expect(response).to render_template("edit") 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/views/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @user, url: current_user_path, :html => { :id => 'edit_user_form' } do |f| %> 2 | <% if @user.errors.any? %> 3 |
4 |

<%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :first_name %>
16 | <%= f.text_field :first_name %> 17 |
18 |
19 | <%= f.label :last_name %>
20 | <%= f.text_field :last_name %> 21 |
22 |
23 | <%= f.label :phone %>
24 | <%= f.hidden_field :phone %> 25 | <%= text_field_tag :area_code, @area, :size => 3, :maxlength => 3 %> - <%= text_field_tag :exchange, @exchange, :size => 3, :maxlength => 3 %> - <%= text_field_tag :line, @line, :size => 4, :maxlength => 4 %> 26 |
27 |
28 | <%= f.label :image %>
29 | <%= f.text_field :image %> 30 |
31 |
32 | <%= f.submit :class => 'button'%> 33 |
34 | <% end %> 35 | -------------------------------------------------------------------------------- /app/assets/javascripts/address_autocomplete.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | if(document.getElementById("new-unit-address-input")) { 3 | var addressInput = $("#new-unit-address-input"); 4 | 5 | $.ajax({ 6 | url: '/api/v1/addresses', 7 | method: 'GET' 8 | }) 9 | .success(function(responseData) { 10 | createAutocompleteList(addressInput, responseData); 11 | }) 12 | .error(function(xhr, status, err) { 13 | console.log("error in retrieving list of adrresses", err, status) 14 | }); 15 | } 16 | }); 17 | 18 | function createAutocompleteList(inputElement, addressData) { 19 | var hiddenField = $("#user-building-id"); 20 | inputElement.autocomplete({ 21 | source: addressData, 22 | 23 | select: function(event, ui) { 24 | event.preventDefault(); 25 | inputElement.val(ui.item.label); 26 | hiddenField.val(ui.item.value); 27 | }, 28 | 29 | focus: function(event, ui) { 30 | event.preventDefault(); 31 | inputElement.val(ui.item.label); 32 | }, 33 | 34 | change: function(event, ui) { 35 | var addresses = addressData.map(function(obj) { return obj.label; }) 36 | if(ui.item == null || addresses.indexOf(ui.item.label) < 0){ 37 | hiddenField.val("") 38 | } 39 | } 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /app/views/uploads/show.html.erb: -------------------------------------------------------------------------------- 1 | 2 |

<%= notice %>

3 | 4 |

Upload

5 | 6 |
7 |
8 |
9 | Filename 10 |
11 |
12 | <%= @upload.filename %> 13 |
14 |
15 |
16 |
17 | Uploaded by 18 |
19 |
20 | <%= @user.email %> 21 |
22 |
23 |
24 |
25 | Uploaded on 26 |
27 |
28 | <%= @upload.created_at.to_date.to_formatted_s(:long) %> 29 |
30 |
31 |
32 | 33 |

Bills created from this upload:

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | <% @bills.each do |bill| %> 44 | 45 | 46 | 47 | 48 | 49 | 50 | <% end %> 51 |
Bill DateBill DaysUsage (kwh)Amount
<%= bill.bill_received.to_formatted_s(:long) %><%= bill.bill_days%><%= bill.usage.to_i%><%= number_to_currency(bill.amount)%>
52 | 53 | -------------------------------------------------------------------------------- /spec/factories/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | first_name 'Bob' 4 | last_name 'Everyman' 5 | phone '1234567890' 6 | sequence(:email) { |n| "user#{n}@example.com" } 7 | password 'password' 8 | unit 9 | team 10 | tipnum 1 11 | 12 | factory :user_with_bills do 13 | transient do 14 | bills_count 2 15 | end 16 | 17 | after(:create) do |user, evaluator| 18 | create_list(:bill, evaluator.bills_count, user: user) 19 | end 20 | end 21 | end 22 | 23 | factory :bill do 24 | bill_received '2015-12-13' 25 | amount { rand(1..99.1).round(2) } 26 | usage { rand(200..500).round(0) } 27 | user 28 | end 29 | 30 | factory :team do 31 | name 'Team AwesomeSauce' 32 | 33 | factory :team_with_members do 34 | transient do 35 | users_count 3 36 | end 37 | 38 | after(:create) do |team, evaluator| 39 | create_list(:user_with_bills, evaluator.users_count, team: team) 40 | end 41 | end 42 | end 43 | 44 | factory :unit do 45 | number_occupants 1 46 | unit_number 123 47 | sqfootage 700 48 | number_bedrooms 2 49 | number_bathrooms 1 50 | number_rooms 2 51 | user_building 52 | end 53 | 54 | factory :user_building do 55 | address '123 Main St, Cambridge, MA 02138' 56 | lat 42.3736 57 | lon(-71.1097) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /app/views/graph/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

<%= t('graph.title') %>

4 | 5 |
6 |
7 |
8 |
9 | <% unless @user %> 10 |

Sign up to learn how to save money on your energy bill:

11 | <%= link_to "Sign up with Google", user_google_oauth2_omniauth_authorize_path, class: 'button' %> 12 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 13 | <%= devise_error_messages! %> 14 | 15 |
16 | <%= f.label :email %>
17 | <%= f.email_field :email, autofocus: true %> 18 |
19 | 20 |
21 | <%= f.label :password %> 22 | <% if @minimum_password_length %> 23 | (<%= @minimum_password_length %> characters minimum) 24 | <% end %>
25 | <%= f.password_field :password, autocomplete: "off" %> 26 |
27 | 28 |
29 | <%= f.label :password_confirmation %>
30 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 31 |
32 | 33 | 34 | 35 |
36 | <%= f.submit "Sign up and learn how", class: "button expand" %> 37 |
38 | <% end %> 39 | <% end %> 40 |
41 |
42 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 12 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %> 14 | 15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "off" %> 18 |
19 | 20 |
21 | <%= f.label :password_confirmation %>
22 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 23 |
24 | 25 |
26 | <%= f.label :current_password %> (we need your current password to confirm your changes)
27 | <%= f.password_field :current_password, autocomplete: "off" %> 28 |
29 | 30 |
31 | <%= f.submit "Update" %> 32 |
33 | <% end %> 34 | 35 |

Cancel my account

36 | 37 |

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

38 | 39 | <%= link_to "Back", :back %> 40 | -------------------------------------------------------------------------------- /app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | <%= image_tag @user.image if @user.image %> 5 |

6 | 7 | <% if profile_complete_meter(@user) < 100 %> 8 |

9 | Profile Progress:
10 | 11 | <%= profile_complete_meter(@user) %>% 12 |

13 | <% end %> 14 | 15 |

16 | First name: <%= @user.first_name %> 17 |

18 | 19 |

20 | Last name: <%= @user.last_name %> 21 |

22 | 23 |

24 | My Home: 25 | <% if @user.unit.present? %> 26 | <%= link_to @user.unit.unit_number, unit_path(@user.unit) %> 27 | <% else %> 28 | <%= link_to 'Update Information', new_unit_path %> 29 | <% end %> 30 |

31 | 32 |

33 | Street address: 34 | <% if @user.unit.present? && @user.unit.user_building.present? %> 35 | <%= link_to @user.unit.user_building.try(:address), user_building_path(@user.unit.user_building) %> 36 | <% end %> 37 |

38 | 39 |

40 | Phone: <%= @user.phone_string %> 41 |

42 | 43 |

44 | Team: 45 | <% if @user.team %> 46 | <%= link_to @user.team.name, @user.team %> 47 | <% else %> 48 | <%= link_to 'Create a team!', new_team_path %> 49 | <% end %> 50 |

51 | 52 | <%= link_to 'Edit Profile', edit_current_user_path %> | 53 | <%= link_to 'Change password', devise_edit_user_path %> 54 | -------------------------------------------------------------------------------- /spec/controllers/user_tips_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UserTipsController do 4 | let(:user) { create(:user) } 5 | let(:tip) { Tip.create } 6 | let(:result) { 'Liked' } 7 | let(:feedback) { 'This tip was good' } 8 | let(:user_tip_params) { {user_id: user, tip_id: tip, result: result, feedback: feedback} } 9 | before(:each) { sign_in user } 10 | 11 | describe "POST #create" do 12 | it 'saves the new UserTip in the database' do 13 | expect { post :create, user_tip: user_tip_params }.to change{ UserTip.count }.by(1) 14 | 15 | uploaded_user_tip = UserTip.last 16 | 17 | expect(uploaded_user_tip.feedback).to eq(feedback) 18 | expect(uploaded_user_tip.tip).to eq(tip) 19 | expect(uploaded_user_tip.result).to eq(result) 20 | expect(uploaded_user_tip.user).to eq(user) 21 | end 22 | 23 | it 'does not save invalid parameters' do 24 | bad_params = {user_id: user, something_else: 'bad data', tip_id: tip, result: 'different result', malice: 'mean users'} 25 | 26 | post :create, user_tip: bad_params 27 | 28 | uploaded_user_tip = UserTip.last 29 | 30 | expect{ uploaded_user_tip.malice }.to raise_error(NoMethodError) 31 | expect{ uploaded_user_tip.something_else }.to raise_error(NoMethodError) 32 | expect(uploaded_user_tip.result).to eq('different result') 33 | expect(uploaded_user_tip.user).to eq(user) 34 | expect(uploaded_user_tip.tip).to eq(tip) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/views/user_tips/_feedback.html.erb: -------------------------------------------------------------------------------- 1 | <% @tip = Tip.get_tip(current_user) %> 2 | <% unless @tip.nil? %> 3 | <% @utip = UserTip.find_by(:user => current_user, :tip => @tip) %> 4 | <% if @utip.nil? %> 5 | <% @user_tip = UserTip.new %> 6 | <%= form_for(@user_tip, :remote => true) do |f| %> 7 | <% if @user_tip.errors.any? %> 8 |
9 |

<%= pluralize(@user_tip.errors.count, "error") %> prohibited this user_tip from being saved:

10 | 11 | 16 |
17 | <% end %> 18 |
19 | <%= f.hidden_field :tip_id, :value => @tip.id %> 20 |
21 |
22 | <%= f.hidden_field :user_id, :value => current_user.id %> 23 |
24 |
25 | Did you try this tip? 26 | <%= f.radio_button(:result, "Liked") %> 27 | <%= f.label(:result_liked, "Yes") %> 28 | <%= f.radio_button(:result, "Disliked") %> 29 | <%= f.label(:result_disliked, "No") %> 30 |
31 |
32 | Please leave your comments on this tip!
33 | <%= f.text_area :feedback %> 34 |
35 |
36 | <%= f.submit 'Submit', :class => 'button' %> 37 |
38 | <% end %> 39 | <% elsif @utip.result == "Liked"%> 40 | <%= render 'tips/share' %> 41 | <% end %> 42 | <% end %> 43 | -------------------------------------------------------------------------------- /config/locales/devise_invitable.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | devise: 3 | failure: 4 | invited: "You have a pending invitation, accept it to finish creating your account." 5 | invitations: 6 | send_instructions: "%{email} has been invited to join your team." 7 | invitation_token_invalid: "The invitation token provided is not valid!" 8 | updated: "Your password was set successfully. You are now signed in and have joined your friend's team." 9 | updated_not_active: "Your password was set successfully." 10 | no_invitations_remaining: "No invitations remaining" 11 | invitation_removed: "Your invitation was removed." 12 | new: 13 | header: "Send invitation" 14 | submit_button: "Send an invitation" 15 | edit: 16 | header: "Welcome to EnerSave" 17 | submit_button: "Set my password" 18 | mailer: 19 | invitation_instructions: 20 | subject: "You are invited to save energy with your friends at EnerSave!" 21 | hello: "Hello %{email}" 22 | someone_invited_you: "Someone has invited you to save energy with EnerSave, you can sign-up at the link below." 23 | accept: "Accept invitation" 24 | accept_until: "This invitation will be due in %{due_date}." 25 | ignore: "If you don't want to accept the invitation, please ignore this email.
\nYour account won't be created until you access the link above and set your password." 26 | time: 27 | formats: 28 | devise: 29 | mailer: 30 | invitation_instructions: 31 | accept_until_format: "%B %d, %Y %I:%M %p" 32 | -------------------------------------------------------------------------------- /app/views/bills/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@bill) do |f| %> 2 | <% if @bill.errors.any? %> 3 |
4 |

<%= pluralize(@bill.errors.count, "error") %> prohibited this bill from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 | <% if @unit && @unit.errors.any? %> 15 |
16 |

<%= pluralize(@unit.errors.count, "error") %> prohibited this record from being saved:

17 | 18 | 23 |
24 | <% end %> 25 | 26 |
27 |
28 | <%= f.label :bill_received, "Date Bill is Due" %> 29 | <%= f.text_field :bill_received, :id=> "datepicker" %> 30 |
31 | 32 |
33 | <%= f.label :usage, "Electricity used (kwh):" %> 34 | <%= f.text_field :usage, :id => "usage" %> 35 |
36 | 37 | <% unless current_user.try(:unit).present? %> 38 | <%= f.fields_for :units do |units_form| %> 39 |
40 | <%= units_form.label :number_occupants, "Number of Occupants in Your Home or Apartment" %> 41 | <%= units_form.text_field :number_occupants, :id => "occupants" %> 42 |
43 | <% end %> 44 | <% end %> 45 |
46 | <%= f.submit "Let's Go", class: 'button action' %> 47 |
48 |
49 | 50 | <% end %> 51 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | lock '3.6.1' 3 | 4 | set :application, 'enersave' 5 | set :repo_url, 'git@github.com:codeforboston/cambridge_energy_app.git' 6 | 7 | # Default branch is :master 8 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp 9 | 10 | # Default deploy_to directory is /var/www/my_app_name 11 | # set :deploy_to, '/var/www/my_app_name' 12 | 13 | # Default value for :scm is :git 14 | # set :scm, :git 15 | 16 | # Default value for :format is :airbrussh. 17 | # set :format, :airbrussh 18 | 19 | # You can configure the Airbrussh format using :format_options. 20 | # These are the defaults. 21 | # set :format_options, command_output: true, log_file: 'log/capistrano.log', color: :auto, truncate: :auto 22 | 23 | # Default value for :pty is false 24 | # set :pty, true 25 | 26 | # Default value for :linked_files is [] 27 | # set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml') 28 | 29 | # Default value for linked_dirs is [] 30 | # set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system') 31 | 32 | # Default value for default_env is {} 33 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 34 | 35 | # Default value for keep_releases is 5 36 | # set :keep_releases, 5 37 | 38 | namespace :deploy do 39 | 40 | after :restart, :clear_cache do 41 | on roles(:web), in: :groups, limit: 3, wait: 10 do 42 | # Here we can do anything such as: 43 | # within release_path do 44 | # execute :rake, 'cache:clear' 45 | # end 46 | end 47 | end 48 | 49 | after "deploy:updated", "newrelic:notice_deployment" 50 | 51 | end 52 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /app/views/bills/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

EnerSave

5 |

Save money and the planet by competing with friends to lower your electric bill.

6 |

<%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path, class: 'button' %>

7 |
8 |
9 |
10 |
11 |
12 |
13 |

Get Started

14 | <%= render 'form' %> 15 |
16 |
17 |

Compare your usage with neighbors

18 | <%= image_tag('sample-graph.png') %> 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |

Join Enersave Today

27 |
28 | <%= image_tag('enersaveMoney.png') %> 29 |

Reduce Your Energy Costs!

30 |

Get access to energy saving tips 31 | and become more aware of your spending

32 |
33 |
34 | <%= image_tag('enersavePeople.jpg') %> 35 |

Compete with friends!

36 |

Invite your friends to join your team, 37 | and work together to earn great prizes

38 |
39 |
40 | <%= image_tag('enersaveBulb.png') %> 41 |

Get rewarded with Prizes!

42 |

Reducing your energy use really pays off 43 | with great prizes like LED lightbulbs and smart power strips.

44 |
45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/controllers/bills_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe BillsController do 4 | 5 | let(:bill) { create(:bill) } 6 | let(:user) { User.find(bill.user_id) } 7 | before(:each) { sign_in user } 8 | 9 | describe 'GET #index' do 10 | before { get :index } 11 | 12 | it { is_expected.to respond_with :ok } 13 | it { is_expected.to render_template :index } 14 | it { expect(bill).to_not be nil } 15 | end 16 | 17 | describe "GET #new" do 18 | before { get :new } 19 | 20 | it { is_expected.to respond_with :ok } 21 | it { is_expected.to render_template :new } 22 | end 23 | 24 | describe "POST #create" do 25 | 26 | it "should create bill" do 27 | user = create(:user) 28 | sign_in user 29 | bill = attributes_for(:bill).merge(user_id: user.id, units: {number_occupants: 4}) 30 | 31 | expect{ post(:create, bill: bill) }.to change{ Bill.count }.by(1) 32 | end 33 | end 34 | 35 | describe "GET #edit" do 36 | before { get :edit, id: bill } 37 | 38 | it { is_expected.to respond_with :ok } 39 | it { is_expected.to render_template :edit } 40 | end 41 | 42 | 43 | describe "GET #comparison" do 44 | before { get :comparison } 45 | 46 | it { is_expected.to render_template :comparison } 47 | end 48 | 49 | describe "PATCH #update" do 50 | 51 | it "redirects to the bill show page" do 52 | bill_attributes = { amount: 42.42, usage: 400, bill_received: '01-01-16' } 53 | patch(:update, id: bill.id, bill: bill_attributes) 54 | 55 | expect(response).to redirect_to bill_path(bill) 56 | end 57 | end 58 | 59 | describe "DELETE #destroy" do 60 | it "destroys a bill" do 61 | expect{ delete(:destroy, id: bill.id) }.to change{ Bill.count }.by(-1) 62 | 63 | is_expected.to redirect_to bills_path 64 | end 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /config/newrelic.yml: -------------------------------------------------------------------------------- 1 | # 2 | # This file configures the New Relic Agent. New Relic monitors Ruby, Java, 3 | # .NET, PHP, Python and Node applications with deep visibility and low 4 | # overhead. For more information, visit www.newrelic.com. 5 | # 6 | # Generated July 06, 2016 7 | # 8 | # This configuration file is custom generated for TowedCar.info 9 | # 10 | # For full documentation of agent configuration options, please refer to 11 | # https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration 12 | 13 | common: &default_settings 14 | # Required license key associated with your New Relic account. 15 | license_key: c41f8500464a29f3649f9b61f289f9f3d6311481 16 | 17 | # Your application name. Renaming here affects where data displays in New 18 | # Relic. For more details, see https://docs.newrelic.com/docs/apm/new-relic-apm/maintenance/renaming-applications 19 | app_name: EnerSave 20 | 21 | # To disable the agent regardless of other settings, uncomment the following: 22 | # agent_enabled: false 23 | 24 | # Logging level for log/newrelic_agent.log 25 | log_level: info 26 | 27 | 28 | # Environment-specific settings are in this section. 29 | # RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment. 30 | # If your application has other named environments, configure them here. 31 | development: 32 | <<: *default_settings 33 | app_name: EnerSave (Development) 34 | 35 | # NOTE: There is substantial overhead when running in developer mode. 36 | # Do not use for production or load testing. 37 | developer_mode: true 38 | 39 | test: 40 | <<: *default_settings 41 | # It doesn't make sense to report to New Relic from automated test runs. 42 | monitor_mode: false 43 | 44 | staging: 45 | <<: *default_settings 46 | app_name: EnerSave (Staging) 47 | 48 | production: 49 | <<: *default_settings 50 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # The priority is based upon order of creation: first created -> highest priority. 3 | # See how all your routes lay out with "rake routes". 4 | 5 | # You can have the root of your site routed with "root" 6 | resources :bills, only: [:index, :new, :create] do 7 | collection { get 'comparison' } 8 | end 9 | get 'graph/index' 10 | 11 | authenticated :user do 12 | root 'teams#leaderboard', as: :authenticated_root 13 | 14 | resources :bills, except: [:index, :new, :create] 15 | 16 | resources :user_tips 17 | resources :tips do 18 | member do 19 | get 'share' 20 | end 21 | collection do 22 | get 'next' 23 | get 'like' 24 | get 'dislike' 25 | end 26 | end 27 | 28 | resources :teams, except: :destroy do 29 | member do 30 | get 'leave' 31 | end 32 | collection do 33 | get 'leaderboard' 34 | end 35 | end 36 | post 'teams/leaderboard' => 'teams#accept_or_decline' 37 | 38 | resources :units, only: [:show, :new, :create, :edit, :update] do 39 | member do 40 | patch 'leave' 41 | end 42 | end 43 | 44 | resources :uploads, only: [:new, :create, :show] 45 | 46 | resources :user_buildings 47 | 48 | get '/users/me', to: 'users#show' 49 | get '/users/me/edit', to: 'users#edit' 50 | patch '/users/me', to: 'users#update' 51 | end 52 | 53 | # You can have the root of your site routed with "root" 54 | # public 55 | root 'bills#new' 56 | 57 | # authentication 58 | devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks", :invitations => 'users/invitations' } 59 | get '/auth/:provider/callback', to: 'sessions#create' 60 | 61 | namespace :api do 62 | namespace :v1 do 63 | resources :addresses, only: [:index] 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /public/agreement.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Agreements 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

Cambridge Energy App

62 |

Legal Agreements

63 |
64 |
65 |

Section 1

66 | These are the placeholder legal terms for section 2.

67 |
68 | 69 |
70 |

Section 2

71 | These are the placeholder legal terms for section 2.

72 |
73 | 74 | 75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | config.action_mailer.default_url_options = { :host => 'localhost' } 43 | end 44 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= content_for?(:title) ? yield(:title) : "EnerSave" %> 8 | 9 | 10 | <%= stylesheet_link_tag "application", debug: false %> 11 | <%= javascript_include_tag "application", 'data-turbolinks-track' => true %> 12 | <%= csrf_meta_tags %> 13 | 14 | 15 | 16 | 46 | <%= yield %> 47 | <%= render 'layouts/footer' %> 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/views/units/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @unit do |u| %> 2 | <% if @unit.errors.any? %> 3 |
4 |

<%= pluralize(@unit.errors.count, "error") %> prohibited this unit from being saved:

5 | 10 |
11 | <% end %> 12 | 13 | <% if @user_building.errors.any? %> 14 |
15 |

<%= pluralize(@user_building.errors.count, "error") %> prohibited this building from being saved:

16 | 21 |
22 | <% end %> 23 | 24 | 25 | <%= fields_for :user_building, @user_building do |ub| %> 26 |
27 | <%= ub.label :address %>
28 | <%= ub.text_field :address, id: "new-unit-address-input"%> 29 |
30 | <% end %> 31 | 32 | <%= u.hidden_field :user_building_id , id: 'user-building-id' %> 33 | 34 |
35 | <%= u.label :unit_number %>
36 | <%= u.text_field :unit_number %> 37 |
38 |
39 | <%= u.label 'Square Footage' %>
40 | <%= u.number_field :sqfootage %> 41 |
42 |
43 | <%= u.label :number_bedrooms %>
44 | <%= u.number_field :number_bedrooms %> 45 |
46 |
47 | <%= u.label :number_bathrooms %>
48 | <%= u.number_field :number_bathrooms %> 49 |
50 |
51 | <%= u.label :number_rooms %>
52 | <%= u.number_field :number_rooms %> 53 |
54 |
55 | <%= u.label :number_occupants %>
56 | <%= u.number_field :number_occupants %> 57 |
58 | 59 |
60 | <%= u.submit :class => 'button' %> 61 |
62 | <% end %> 63 | -------------------------------------------------------------------------------- /app/assets/stylesheets/foundation_and_overrides.scss: -------------------------------------------------------------------------------- 1 | @charset 'utf-8'; 2 | 3 | @import 'settings'; 4 | @import 'foundation'; 5 | 6 | // If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. 7 | // 8 | // @import 'motion-ui/motion-ui'; 9 | 10 | // We include everything by default. To slim your CSS, remove components you don't use. 11 | 12 | @include foundation-global-styles; 13 | @include foundation-grid; 14 | @include foundation-typography; 15 | @include foundation-button; 16 | @include foundation-forms; 17 | @include foundation-visibility-classes; 18 | @include foundation-float-classes; 19 | @include foundation-accordion; 20 | @include foundation-accordion-menu; 21 | @include foundation-badge; 22 | @include foundation-breadcrumbs; 23 | @include foundation-button-group; 24 | @include foundation-callout; 25 | @include foundation-close-button; 26 | @include foundation-drilldown-menu; 27 | @include foundation-dropdown; 28 | @include foundation-dropdown-menu; 29 | @include foundation-flex-video; 30 | @include foundation-label; 31 | @include foundation-media-object; 32 | @include foundation-menu; 33 | @include foundation-menu-icon; 34 | @include foundation-off-canvas; 35 | @include foundation-orbit; 36 | @include foundation-pagination; 37 | @include foundation-progress-bar; 38 | @include foundation-slider; 39 | @include foundation-sticky; 40 | @include foundation-reveal; 41 | @include foundation-switch; 42 | @include foundation-table; 43 | @include foundation-tabs; 44 | @include foundation-thumbnail; 45 | @include foundation-title-bar; 46 | @include foundation-tooltip; 47 | @include foundation-top-bar; 48 | @include foundation-flex-classes; 49 | @include foundation-flex-grid; 50 | 51 | // If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. 52 | // 53 | // @include motion-ui-transitions; 54 | // @include motion-ui-animations; 55 | -------------------------------------------------------------------------------- /config/environments/docker.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = true 18 | ActionMailer::Base.smtp_settings = { 19 | :user_name => ENV['sendgrid_username'], 20 | :password => ENV['sendgrid_password'], 21 | :address => "smtp.sendgrid.net", 22 | :port => 2525, 23 | :domain => "enersave.com", 24 | :authentication => :plain, 25 | :enable_starttls_auto => true 26 | } 27 | 28 | # Print deprecation notices to the Rails logger. 29 | config.active_support.deprecation = :log 30 | 31 | # Raise an error on page load if there are pending migrations. 32 | config.active_record.migration_error = :page_load 33 | 34 | # Debug mode disables concatenation and preprocessing of assets. 35 | # This option may cause significant delays in view rendering with a large 36 | # number of complex assets. 37 | config.assets.debug = true 38 | 39 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 40 | # yet still be able to expire them through the digest params. 41 | config.assets.digest = true 42 | 43 | # Adds additional error checking when serving assets at runtime. 44 | # Checks for improperly declared sprockets dependencies. 45 | # Raises helpful error messages. 46 | config.assets.raise_runtime_errors = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | end 51 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Care if mailer can't send 17 | config.action_mailer.raise_delivery_errors = true 18 | ActionMailer::Base.smtp_settings = { 19 | :user_name => ENV['SENDGRID_USERNAME'], 20 | :password => ENV['SENDGRID_PASSWORD'], 21 | :address => "smtp.sendgrid.net", 22 | :port => 587, 23 | :authentication => :plain, 24 | :enable_starttls_auto => true 25 | } 26 | 27 | config.action_mailer.default_url_options = { :host => 'localhost' } 28 | 29 | # Print deprecation notices to the Rails logger. 30 | config.active_support.deprecation = :log 31 | 32 | # Raise an error on page load if there are pending migrations. 33 | config.active_record.migration_error = :page_load 34 | 35 | # Debug mode disables concatenation and preprocessing of assets. 36 | # This option may cause significant delays in view rendering with a large 37 | # number of complex assets. 38 | config.assets.debug = true 39 | 40 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 41 | # yet still be able to expire them through the digest params. 42 | config.assets.digest = true 43 | 44 | # Adds additional error checking when serving assets at runtime. 45 | # Checks for improperly declared sprockets dependencies. 46 | # Raises helpful error messages. 47 | config.assets.raise_runtime_errors = true 48 | 49 | # Raises error for missing translations 50 | # config.action_view.raise_on_missing_translations = true 51 | end 52 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | server '162.243.244.148', user: 'rails', roles: %w{app db web} 7 | # server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value 8 | # server 'db.example.com', user: 'deploy', roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | 34 | 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server 'example.com', 53 | # user: 'user_name', 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: 'user_name', # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: 'please use keys' 61 | # } 62 | -------------------------------------------------------------------------------- /config/deploy/staging.rb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | # server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value 7 | # server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value 8 | # server 'db.example.com', user: 'deploy', roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | 34 | 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server 'example.com', 53 | # user: 'user_name', 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: 'user_name', # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: 'please use keys' 61 | # } 62 | -------------------------------------------------------------------------------- /app/controllers/user_tips_controller.rb: -------------------------------------------------------------------------------- 1 | class UserTipsController < ApplicationController 2 | before_action :set_user_tip, only: [:authorize_user, :show, :edit, :update, :destroy] 3 | before_action :authorize_user, only: [:show, :edit, :update, :destroy] 4 | before_action :skip_authorization, only: [:new, :comparison, :create] 5 | before_action :skip_policy_scope, only: [:index] 6 | 7 | # GET /user_tips 8 | # GET /user_tips.json 9 | def index 10 | @user_tips = policy_scope(UserTip) 11 | end 12 | 13 | # GET /user_tips/1 14 | # GET /user_tips/1.json 15 | def show 16 | end 17 | 18 | # GET /user_tips/new 19 | def new 20 | @user_tip = UserTip.new 21 | end 22 | 23 | # GET /user_tips/1/edit 24 | def edit 25 | end 26 | 27 | # POST /user_tips 28 | # POST /user_tips.json 29 | def create 30 | @user_tip = UserTip.new(user_tip_params) 31 | respond_to do |format| 32 | if @user_tip.save 33 | if @user_tip.result == "Disliked" 34 | Tip.next_tip(current_user) 35 | end 36 | format.html do 37 | redirect_to '/' 38 | end 39 | format.json { render json: @user_tip.to_json } 40 | else 41 | format.html { render 'new'} 42 | format.json { render json: @user_tip.errors } 43 | end 44 | end 45 | end 46 | 47 | # PATCH/PUT /user_tips/1 48 | # PATCH/PUT /user_tips/1.json 49 | def update 50 | respond_to do |format| 51 | if @user_tip.update(user_tip_params) 52 | format.html { redirect_to @user_tip, notice: 'User tip was successfully updated.' } 53 | format.json { render :show, status: :ok, location: @user_tip } 54 | else 55 | format.html { render :edit } 56 | format.json { render json: @user_tip.errors, status: :unprocessable_entity } 57 | end 58 | end 59 | end 60 | 61 | # DELETE /user_tips/1 62 | # DELETE /user_tips/1.json 63 | def destroy 64 | @user_tip.destroy 65 | respond_to do |format| 66 | format.html { redirect_to user_tips_url, notice: 'User tip was successfully destroyed.' } 67 | format.json { head :no_content } 68 | end 69 | end 70 | 71 | private 72 | # Use callbacks to share common setup or constraints between actions. 73 | def set_user_tip 74 | @user_tip = UserTip.find(params[:id]) 75 | end 76 | 77 | def authorize_user 78 | authorize @user_tip 79 | end 80 | 81 | # Never trust parameters from the scary internet, only allow the white list through. 82 | def user_tip_params 83 | params.require(:user_tip).permit(:user_id, :tip_id, :result, :feedback) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | include Pundit 7 | after_action :verify_authorized, except: :index, unless: :devise_controller? 8 | after_action :verify_policy_scoped, only: :index, unless: :devise_controller? 9 | 10 | rescue_from Pundit::NotAuthorizedError do |exception| 11 | Rails.logger.debug "Access denied on #{exception.query} #{exception.policy}" 12 | 13 | if signed_in? 14 | redirect_to users_me_path(current_user), error: "Access denied." 15 | else 16 | redirect_to new_user_session_path, error: "Must be logged in" 17 | end 18 | end 19 | 20 | # if user is logged in, return current_user, else return guest_user 21 | def current_or_guest_user 22 | if current_user 23 | if session[:guest_user_id] && session[:guest_user_id] != current_user.id 24 | logging_in 25 | # reload guest_user to prevent caching problems before destruction 26 | guest_user(with_retry: false).reload.try(:destroy) 27 | session[:guest_user_id] = nil 28 | end 29 | current_user 30 | else 31 | guest_user 32 | end 33 | end 34 | 35 | # find guest_user object associated with the current session, 36 | # creating one as needed 37 | def guest_user(with_retry: true) 38 | # Cache the value the first time it's gotten. 39 | @cached_guest_user ||= User.find(session[:guest_user_id] ||= create_guest_user.id) 40 | 41 | rescue ActiveRecord::RecordNotFound # if session[:guest_user_id] invalid 42 | session[:guest_user_id] = nil 43 | guest_user if with_retry 44 | end 45 | 46 | def after_sign_in_path_for(user) 47 | leaderboard_teams_path 48 | end 49 | 50 | private 51 | 52 | # called (once) when the user logs in, insert any code your application needs 53 | # to hand off from guest_user to current_user. 54 | def logging_in 55 | # Transfer guest unit, team, and bills to the new user account 56 | current_user.update(unit: guest_user.unit, team: guest_user.team) 57 | 58 | guest_user.bills.each do |bill| 59 | bill.update(user: current_user, unit: current_user.unit) 60 | end 61 | 62 | # Reload the cache to prevent deleting bill in the next step 63 | guest_user.bills(true) 64 | current_user.bills(true) 65 | end 66 | 67 | def create_guest_user 68 | u = User.create(:first_name => "guest", :email => "guest_#{Time.now.to_i}#{rand(100)}@example.com") 69 | u.save!(:validate => false) 70 | session[:guest_user_id] = u.id 71 | u 72 | end 73 | 74 | end 75 | -------------------------------------------------------------------------------- /app/controllers/user_buildings_controller.rb: -------------------------------------------------------------------------------- 1 | class UserBuildingsController < ApplicationController 2 | before_action :set_user_building, only: [:authorize_user, :show, :edit, :update, :destroy] 3 | before_action :authorize_user, only: [:show, :edit, :update, :destroy] 4 | 5 | # GET /user_buildings 6 | # GET /user_buildings.json 7 | def index 8 | @user_buildings = policy_scope(UserBuilding) 9 | end 10 | 11 | # GET /user_buildings/1 12 | # GET /user_buildings/1.json 13 | def show 14 | end 15 | 16 | # GET /user_buildings/new 17 | def new 18 | authorize UserBuilding 19 | @user_building = UserBuilding.new 20 | end 21 | 22 | # GET /user_buildings/1/edit 23 | def edit 24 | end 25 | 26 | # POST /user_buildings 27 | # POST /user_buildings.json 28 | def create 29 | authorize UserBuilding 30 | @user_building = UserBuilding.new(user_building_params) 31 | 32 | respond_to do |format| 33 | if @user_building.save 34 | format.html { redirect_to @user_building, notice: 'User building was successfully created.' } 35 | format.json { render :show, status: :created, location: @user_building } 36 | else 37 | format.html { render :new } 38 | format.json { render json: @user_building.errors, status: :unprocessable_entity } 39 | end 40 | end 41 | end 42 | 43 | # PATCH/PUT /user_buildings/1 44 | # PATCH/PUT /user_buildings/1.json 45 | def update 46 | respond_to do |format| 47 | if @user_building.update(user_building_params) 48 | format.html { redirect_to @user_building, notice: 'User building was successfully updated.' } 49 | format.json { render :show, status: :ok, location: @user_building } 50 | else 51 | format.html { render :edit } 52 | format.json { render json: @user_building.errors, status: :unprocessable_entity } 53 | end 54 | end 55 | end 56 | 57 | # DELETE /user_buildings/1 58 | # DELETE /user_buildings/1.json 59 | def destroy 60 | @user_building.destroy 61 | respond_to do |format| 62 | format.html { redirect_to user_buildings_url, notice: 'User building was successfully destroyed.' } 63 | format.json { head :no_content } 64 | end 65 | end 66 | 67 | private 68 | # Use callbacks to share common setup or constraints between 69 | # actions. 70 | def set_user_building 71 | @user_building = UserBuilding.find(params[:id]) 72 | end 73 | 74 | def authorize_user 75 | authorize @user_building 76 | end 77 | 78 | # Never trust parameters from the scary internet, only allow 79 | # the white list through. 80 | def user_building_params 81 | params. 82 | require(:user_building). 83 | permit(:address, :lat, :lon) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require File.expand_path('../../config/environment', __FILE__) 4 | # Prevent database truncation if the environment is production 5 | abort("The Rails environment is running in production mode!") if Rails.env.production? 6 | require 'spec_helper' 7 | require 'rspec/rails' 8 | require 'shoulda-matchers' 9 | require 'database_cleaner' 10 | require 'factory_girl_rails' 11 | require 'devise' 12 | # Add additional requires below this line. Rails is not loaded until this point! 13 | 14 | # Requires supporting ruby files with custom matchers and macros, etc, in 15 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 16 | # run as spec files by default. This means that files in spec/support that end 17 | # in _spec.rb will both be required and run as specs, causing the specs to be 18 | # run twice. It is recommended that you do not name files matching this glob to 19 | # end with _spec.rb. You can configure this pattern with the --pattern 20 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 21 | # 22 | # The following line is provided for convenience purposes. It has the downside 23 | # of increasing the boot-up time by auto-requiring all files in the support 24 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 25 | # require only the support files necessary. 26 | # 27 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 28 | 29 | # Checks for pending migration and applies them before tests are run. 30 | # If you are not using ActiveRecord, you can remove this line. 31 | ActiveRecord::Migration.maintain_test_schema! 32 | 33 | RSpec.configure do |config| 34 | config.include FactoryGirl::Syntax::Methods 35 | 36 | # RSpec Rails can automatically mix in different behaviours to your tests 37 | # based on their file location, for example enabling you to call `get` and 38 | # `post` in specs under `spec/controllers`. 39 | # 40 | # You can disable this behaviour by removing the line below, and instead 41 | # explicitly tag your specs with their type, e.g.: 42 | # 43 | # RSpec.describe UsersController, :type => :controller do 44 | # # ... 45 | # end 46 | # 47 | # The different available types are documented in the features, such as in 48 | # https://relishapp.com/rspec/rspec-rails/docs 49 | config.infer_spec_type_from_file_location! 50 | 51 | # Filter lines from Rails gems in backtraces. 52 | config.filter_rails_from_backtrace! 53 | # arbitrary gems may also be filtered via: 54 | # config.filter_gems_from_backtrace("gem name") 55 | 56 | DatabaseCleaner.strategy = :truncation 57 | config.before(:suite) { DatabaseCleaner.clean } 58 | end 59 | -------------------------------------------------------------------------------- /spec/features/user_can_create_new_bill_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | feature "user can add a new bill" do 4 | scenario "user successfully adds new bill" do 5 | visit "/" 6 | fill_in "datepicker", with: "2016-03-16" 7 | fill_in "usage", with: "400" 8 | fill_in "occupants", with: "5" 9 | click_button "Let's Go" 10 | 11 | expect(page).to have_content "Sign up to learn how to save money on your energy bill:" 12 | expect(page).to have_css "svg#graph" 13 | expect(page).to_not have_content "Date Bill is Due" 14 | end 15 | 16 | scenario "user does not enter any bill information" do 17 | visit "/" 18 | click_button "Let's Go" 19 | 20 | expect(page).to have_content "Number occupants can't be blank" 21 | expect(page).to have_content "Number occupants is not a number" 22 | expect(page).to have_content "Bill received must be a valid date" 23 | expect(page).to have_content "Bill received can't be blank" 24 | expect(page).to have_content "Usage can't be blank" 25 | expect(page).to have_content "Usage is not a number" 26 | expect(page).to_not have_css "svg#graph" 27 | end 28 | 29 | scenario "user enters invalid bill information - greater than case" do 30 | visit "/" 31 | fill_in "datepicker", with: "5" 32 | fill_in "usage", with: "10000" 33 | fill_in "occupants", with: "21" 34 | click_button "Let's Go" 35 | 36 | expect(page).to have_content "Number occupants must be less than or equal to 20" 37 | expect(page).to have_content "Bill received must be a valid date" 38 | expect(page).to have_content "Bill received can't be blank" 39 | expect(page).to have_content "Usage must be less than or equal to 9999" 40 | expect(page).to_not have_css "svg#graph" 41 | end 42 | 43 | scenario "user enters invalid bill information - less than case" do 44 | visit "/" 45 | fill_in "datepicker", with: "5" 46 | fill_in "usage", with: "-1" 47 | fill_in "occupants", with: "-10" 48 | click_button "Let's Go" 49 | 50 | expect(page).to have_content "Number occupants must be greater than or equal to 0" 51 | expect(page).to have_content "Bill received must be a valid date" 52 | expect(page).to have_content "Bill received can't be blank" 53 | expect(page).to have_content "Usage must be greater than or equal to 0" 54 | expect(page).to_not have_css "svg#graph" 55 | end 56 | 57 | scenario "from the teams leaderboard" do 58 | user = create(:user) 59 | login_as(user, scope: :user) 60 | 61 | visit authenticated_root_path 62 | click_on "Add this month's bill" 63 | 64 | fill_in "datepicker", with: "2016-03-16" 65 | fill_in "usage", with: "400" 66 | click_button "Let's Go" 67 | 68 | expect(page).to have_content I18n.t('graph.title') 69 | expect(page).to have_css "svg#graph" 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /app/assets/javascripts/graph.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('#graph').append(loadGraph); 3 | }); 4 | 5 | var loadGraph = function() { 6 | //Add vertical line to denote where the user is. 7 | $.ajax({ 8 | type: "GET", 9 | contentType: "application/json; charset=utf-8", 10 | url: '/bills/comparison.json', 11 | dataType: 'json', 12 | success: function(data) { 13 | var arr = []; 14 | data.comparison_bills.forEach(function(d) { 15 | d.usage = +d.usage; 16 | arr.push({ usage: d.usage, user: d.user_id, id: d.id }); 17 | }); 18 | draw(arr, data.current_user_id, data.most_recent_bill_id); 19 | }, 20 | error: function(result) { 21 | error(); 22 | } 23 | }); 24 | }; 25 | 26 | 27 | function draw(data, current_user_id, last_bill) { 28 | var color = d3.scale.category20b(); 29 | var margin = {top: 20, right: 30, bottom: 30, left: 40}, 30 | totalWidth = parseInt(d3.select(".small-centered").style("width")), 31 | width = totalWidth - margin.left - margin.right, 32 | totalHeight = parseInt(d3.select(".small-centered").style("height")), 33 | height = totalHeight - margin.top - margin.bottom; 34 | 35 | var barWidth = width / data.length; 36 | 37 | var x = d3.scale.ordinal() 38 | .rangeRoundBands([0, width], .1); 39 | 40 | var y = d3.scale.linear() 41 | .range([height, 0]) 42 | .domain([0, d3.max(data, function(d) { return d.usage })]); 43 | 44 | var xAxis = d3.svg.axis() 45 | .scale(x) 46 | .orient("bottom"); 47 | 48 | var yAxis = d3.svg.axis() 49 | .scale(y) 50 | .orient("left"); 51 | 52 | var chart = d3.select("#graph") 53 | .style("width", totalWidth + 'px') 54 | .style("height", totalHeight + 'px') 55 | .append("g") 56 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 57 | 58 | var bar = chart.selectAll("g") 59 | .data(data) 60 | .enter().append("g") 61 | .attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; }) 62 | .style("fill", "blue"); 63 | 64 | bar.append("rect") 65 | .attr("y", function(d) { return y(d.usage) }) 66 | .attr("width", barWidth - 1) 67 | .attr("height", function(d) { return height-y(d.usage); }) 68 | .style("fill", function (d) { if(d.id == last_bill) { return "red"; } }); 69 | 70 | chart.append("g") 71 | .attr("class", "y axis") 72 | .call(yAxis) 73 | .append("text") 74 | .attr("transform", "rotate(-90)") 75 | .attr("y", 0) 76 | .attr("dy", "-2.6em") 77 | .style("text-anchor", "end") 78 | .text("Electricity usage (Kwh)"); 79 | } 80 | 81 | function error() { 82 | console.log("error") 83 | } 84 | -------------------------------------------------------------------------------- /app/controllers/tips_controller.rb: -------------------------------------------------------------------------------- 1 | class TipsController < ApplicationController 2 | before_action :set_tip, only: [:show, :edit, :update, :destroy] 3 | before_action :authorize_user, except: [:index, :next] 4 | 5 | # GET /tips 6 | # GET /tips.json 7 | def index 8 | @tips = policy_scope(Tip) 9 | end 10 | 11 | # GET /tips/1 12 | # GET /tips/1.json 13 | def show 14 | end 15 | 16 | # GET /tips/new 17 | def new 18 | @tip = Tip.new 19 | end 20 | 21 | # GET /tips/1/edit 22 | def edit 23 | end 24 | 25 | # POST /tips 26 | # POST /tips.json 27 | def create 28 | @tip = Tip.new(tip_params) 29 | 30 | respond_to do |format| 31 | if @tip.save 32 | format.html { redirect_to @tip, notice: 'Tip was successfully created.' } 33 | format.json { render :show, status: :created, location: @tip } 34 | else 35 | format.html { render :new } 36 | format.json { render json: @tip.errors, status: :unprocessable_entity } 37 | end 38 | end 39 | end 40 | 41 | # PATCH/PUT /tips/1 42 | # PATCH/PUT /tips/1.json 43 | def update 44 | respond_to do |format| 45 | if @tip.update(tip_params) 46 | format.html { redirect_to @tip, notice: 'Tip was successfully updated.' } 47 | format.json { render :show, status: :ok, location: @tip } 48 | else 49 | format.html { render :edit } 50 | format.json { render json: @tip.errors, status: :unprocessable_entity } 51 | end 52 | end 53 | end 54 | 55 | # DELETE /tips/1 56 | # DELETE /tips/1.json 57 | def destroy 58 | @tip.destroy 59 | respond_to do |format| 60 | format.html { redirect_to tips_url, notice: 'Tip was successfully destroyed.' } 61 | format.json { head :no_content } 62 | end 63 | end 64 | 65 | def next 66 | authorize Tip 67 | Tip.next_tip(current_user) 68 | respond_to do |format| 69 | format.js {} 70 | end 71 | end 72 | 73 | def like 74 | Tip.vote(current_user, "Liked") 75 | respond_to do |format| 76 | format.js {} 77 | end 78 | end 79 | 80 | def dislike 81 | Tip.vote(current_user, "Disliked") 82 | respond_to do |format| 83 | format.js {} 84 | end 85 | end 86 | 87 | # Replace with actual sharing mechanism 88 | def share 89 | respond_to do |format| 90 | format.js {} 91 | end 92 | end 93 | 94 | private 95 | # Use callbacks to share common setup or constraints between actions. 96 | def set_tip 97 | @tip = Tip.find(params[:id]) 98 | end 99 | 100 | def authorize_user 101 | authorize @tip 102 | end 103 | 104 | # Never trust parameters from the scary internet, only allow the white list through. 105 | def tip_params 106 | params.require(:tip).permit(:text, :worked, :failed) 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cambridge Energy App [![Build Status](https://travis-ci.org/codeforboston/cambridge_energy_app.svg?branch=master)](https://travis-ci.org/codeforboston/cambridge_energy_app) 2 | 3 | ## What 4 | How might we reduce energy use and cost for the 100 biggest apartment buildings in Cambridge? 5 | 6 | 7 | ## Who 8 | We are working with the City of Cambridge. 9 | 10 | 11 | ## Status 12 | We have created a Ruby on Rails app, using Foundation for the front end, and PostgreSQL as the database so that people can report their energy usage and we can give them tips about reducing their energy usage and find ways to encourage them to follow through on these tips. 13 | 14 | 15 | ## Why 16 | No one likes being cold in the winter, and no one likes paying really high heating bills, either. Thanks to the [Building Energy Use Disclosure Ordinance](https://www.cambridgema.gov/CDD/zoninganddevelopment/sustainablebldgs/buildingenergydisclosureordinance.aspx), Cambridge now has energy use data for the 100 largest apartment buildings in the City, which account for about half of the residential energy consumption. Can you use this (anonymized) data to help us communicate to the buildings about how they’re doing now and what they might do to reduce their energy consumption and bills? Apps to help residents monitor energy use, encourage action, and track results are all useful. And if Cambridge can reduce its city-wide energy use the most by the end of 2016, it could win the $5 million [Georgetown University Energy Prize](http://www.cambridgeenergyalliance.org/winit)! 17 | 18 | 19 | ## How to Contribute 20 | If you wish to contribute to the project you should read this README along with our Wiki. It will also be helpful to catch-up on the [GitHub Issues Page](https://github.com/codeforboston/cambridge_energy_app/issues). You will then want to join the #cambridgeenergyapp channel in the [Code for Boston Slack Team](http://public.codeforboston.org/) and say hello. Slack is informal so feel free to ask questions there and other collaborators will help you out. 21 | 22 | Contributors who are changing code should develop their feature or fix in a new branch in the GitHub repository and then once it is complete you should send a pull request to have another project collaborator review your work and allow others to give you feedback. After any improvements have been incorporated your pull request will be merged into master. 23 | 24 | If you have ideas for features or find a bug you should submit it as a GitHub issue. Due to the distributed and ephemeral nature of the project and our meetings ideas that are submitted in any other form are likely to turn into vaporware. 25 | 26 | If you want to collaborate in person we normally meet on Tuesdays at 7 p.m. at the Cambridge Innovation Center. 27 | 28 | 29 | ## Getting Started 30 | 31 | 1. Follow the instructions at [install Rails](http://installrails.com) to get Rails working on your local machine. Make sure to install [RVM](https://rvm.io). 32 | 2. Clone the repo to your directory. 33 | 3. run rake db:setup and rake db:seed 34 | 4. start the app! 35 | 36 | ## License 37 | [MIT License](LICENSE) 38 | -------------------------------------------------------------------------------- /app/views/teams/leaderboard.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

<%= notice %>

5 | 6 | <%= render 'invitation' if current_user.invited? %> 7 | 8 |

My Energy Saving Score

9 |

<%= @user.score %>

10 |
11 |
12 |

You can get your Green Button energy history file from Eversource by clicking here.

13 | <% unless @user.team %> 14 | <%= link_to 'Create a Team', new_team_path, class: "button" %> 15 | <% else %> 16 | <%= link_to 'Invite a Friend', new_user_invitation_path, class: "button" %> 17 | <% end %> 18 | <%= link_to "Add this month's bill", new_bill_path, class: "button" %> 19 | <%= link_to 'Upload Green Button Data', new_upload_path, class: "button" %> 20 |
21 |
22 |
23 |
24 | <% if current_user.try(:team).try(:image_url) %> 25 | <%= image_tag current_user.team.image_url %> 26 | <% else %> 27 | <%= image_tag 'no_team.jpg' %> 28 | <% end %> 29 |
30 |
31 |

Team Leaderboard

32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | <% @teams.each do |team| %> 42 | 43 | <% if current_user.is_member?(team) %> 44 | 45 | <% else %> 46 | 47 | <% end %> 48 | 49 | 50 | <% end %> 51 | 52 |
NameScore
<%= link_to team.name, team %><%= team.name %><%= team.score.round(2) %>
53 |
54 |
55 |
56 |
57 |

You Can Win Prizes

58 |

The team with the highest score on the first of each month will get LED Lightbulbs and smart power strips starting on January 1, 2017.

59 |

How my energy savings score is calculated

60 |

Your personal energy score is calculated by comparing your energy use this month with your energy use last month and represents how well you reduced your usage.

How my team energy saving score is calculated 62 |

Your team energy score is calculated by adding each team member’s individual score together.

63 |

Improve Your Energy Score

64 |

Get a free energy audit.

65 |
66 |
67 |
68 | <%= render 'tips/rand' %> 69 |
70 |
71 | <%= render 'user_tips/feedback' %> 72 |
73 |
74 |
75 | 76 |
77 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.2.5' 3 | 4 | 5 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 6 | gem 'rails', '~> 4.2.7.1' 7 | # Use postgresql as the database for Active Record 8 | gem 'pg', '~> 0.15' 9 | # Use SCSS for stylesheets 10 | gem 'sass-rails', '~> 5.0' 11 | # Use Uglifier as compressor for JavaScript assets 12 | gem 'uglifier', '>= 1.3.0' 13 | # Use CoffeeScript for .coffee assets and views 14 | gem 'coffee-rails', '~> 4.1.0' 15 | # See https://github.com/rails/execjs#readme for more supported runtimes 16 | gem 'therubyracer', platforms: :ruby 17 | # Need tzinfo-data for reasons mahtai doesn't understand 18 | gem 'tzinfo-data' 19 | gem 'social-share-button' 20 | 21 | # Use foundation for ux frameworks 22 | gem 'foundation-rails' 23 | 24 | gem 'sendgrid' 25 | # Use figaro to read environment variables from application.yml 26 | gem 'figaro' 27 | 28 | # Use jquery as the JavaScript library 29 | gem 'jquery-rails' 30 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 31 | gem 'turbolinks' 32 | gem 'jquery-turbolinks' 33 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 34 | gem 'jbuilder', '~> 2.0' 35 | # bundle exec rake doc:rails generates the API under doc/api. 36 | gem 'sdoc', '~> 0.4.0', group: :doc 37 | gem 'addressable' 38 | #jQuery gem for using datepicker 39 | gem 'jquery-ui-rails' 40 | 41 | # Use devise and omniauth for authentication 42 | gem 'devise' 43 | gem 'devise_invitable' 44 | gem 'omniauth' 45 | gem 'omniauth-google-oauth2' 46 | 47 | #inline svg helper 48 | gem 'inline_svg' 49 | 50 | gem 'newrelic_rpm' 51 | 52 | # Parse addresses 53 | #https://github.com/daveworth/Indirizzo 54 | gem 'Indirizzo' 55 | 56 | gem 'pundit' 57 | 58 | # Use ActiveModel has_secure_password 59 | # gem 'bcrypt', '~> 3.1.7' 60 | 61 | # Use Unicorn as the app server 62 | # gem 'unicorn' 63 | 64 | # Use carrierwave to handle image file uploading 65 | gem 'carrierwave' 66 | 67 | #Use file_validators to make sure we only allow certain file types to be uploaded 68 | gem 'file_validators' 69 | 70 | gem 'faker', '~> 1.6', '>= 1.6.1' 71 | 72 | group :development, :test do 73 | gem 'rspec-rails', '~> 3.0' 74 | gem 'database_cleaner', '~> 1.5', '>= 1.5.1' 75 | gem 'factory_girl_rails', '~> 4.5' 76 | gem 'shoulda' 77 | gem 'launchy' 78 | gem 'valid_attribute' 79 | gem 'byebug' 80 | gem 'pry-rails' 81 | gem 'capybara' 82 | gem 'quiet_assets' 83 | end 84 | 85 | group :test do 86 | gem 'shoulda-matchers' 87 | gem 'rake' 88 | end 89 | 90 | group :development do 91 | # Access an IRB console on exception pages or by using <%= console %> in views 92 | gem 'web-console', '~> 2.0' 93 | 94 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 95 | gem 'spring' 96 | 97 | # Use dotenv for local configs 98 | gem 'dotenv-rails' 99 | 100 | gem 'capistrano-rails', '~> 1.1' 101 | gem 'capistrano-bundler', '~> 1.1.2' 102 | gem 'capistrano-passenger' 103 | gem 'capistrano-rvm' 104 | end 105 | 106 | group :production do 107 | #12 Factor for Heorku 108 | gem 'rails_12factor' 109 | end 110 | gem 'nokogiri', '>= 1.6.8' 111 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: 5 23 | 24 | docker: 25 | <<: *default 26 | host: db 27 | username: postgres 28 | password: postgres 29 | 30 | development: 31 | <<: *default 32 | database: cambridge_energy_app_development 33 | 34 | # The specified database role being used to connect to postgres. 35 | # To create additional roles in postgres see `$ createuser --help`. 36 | # When left blank, postgres will use the default role. This is 37 | # the same name as the operating system user that initialized the database. 38 | #username: cambridge_energy_app 39 | 40 | # The password associated with the postgres role (username). 41 | #password: 42 | 43 | # Connect on a TCP socket. Omitted by default since the client uses a 44 | # domain socket that doesn't need configuration. Windows does not have 45 | # domain sockets, so uncomment these lines. 46 | #host: localhost 47 | 48 | # The TCP port the server listens on. Defaults to 5432. 49 | # If your server runs on a different port number, change accordingly. 50 | #port: 5432 51 | 52 | # Schema search path. The server defaults to $user,public 53 | #schema_search_path: myapp,sharedapp,public 54 | 55 | # Minimum log levels, in increasing order: 56 | # debug5, debug4, debug3, debug2, debug1, 57 | # log, notice, warning, error, fatal, and panic 58 | # Defaults to warning. 59 | #min_messages: notice 60 | 61 | # Warning: The database defined as "test" will be erased and 62 | # re-generated from your development database when you run "rake". 63 | # Do not set this db to the same as development or production. 64 | test: 65 | <<: *default 66 | database: travis_ci_test 67 | 68 | # As with config/secrets.yml, you never want to store sensitive information, 69 | # like your database password, in your source code. If your source code is 70 | # ever seen by anyone, they now have access to your database. 71 | # 72 | # Instead, provide the password as a unix environment variable when you boot 73 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 74 | # for a full rundown on how to provide these environment variables in a 75 | # production deployment. 76 | # 77 | # On Heroku and other platform providers, you may have a full connection URL 78 | # available as an environment variable. For example: 79 | # 80 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 81 | # 82 | # You can use this database configuration with: 83 | # 84 | # production: 85 | # url: <%= ENV['DATABASE_URL'] %> 86 | # 87 | production: 88 | <<: *default 89 | database: cambridge_energy_app_production 90 | username: cambridge_energy_app 91 | password: <%= ENV['CAMBRIDGE_ENERGY_APP_DATABASE_PASSWORD'] %> 92 | host: localhost 93 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe User do 4 | 5 | before(:each) do 6 | @user = create(:user) 7 | end 8 | 9 | it { should belong_to :team } 10 | it { should belong_to :unit } 11 | it { should have_many :bills } 12 | it { should validate_length_of(:phone).is_equal_to(10) } 13 | it { should validate_numericality_of(:phone).only_integer } 14 | 15 | it 'has a valid factory' do 16 | expect(@user).to be_valid 17 | end 18 | 19 | describe '.area_code' do 20 | it 'should return area code' do 21 | expect(@user.area_code).to eq '123' 22 | end 23 | end 24 | 25 | describe '.exchange' do 26 | it 'should return exchange' do 27 | expect(@user.exchange).to eq '456' 28 | end 29 | end 30 | 31 | describe '.line' do 32 | it 'should return line' do 33 | expect(@user.line).to eq '7890' 34 | end 35 | end 36 | 37 | describe '.phone_string' do 38 | it 'should return phone number with dashes inserted' do 39 | expect(@user.phone_string).to eq '123-456-7890' 40 | end 41 | end 42 | 43 | describe '.first_name_or_email' do 44 | 45 | context 'first name is nil' do 46 | it 'should return .email_without_domain' do 47 | @user.update(first_name: nil, email: 'bob@everyman.com') 48 | expect(@user.first_name_or_email).to eq 'bob' 49 | end 50 | end 51 | 52 | context 'first name is empty' do 53 | it 'should return .email_without_domain' do 54 | @user.update(first_name: '', email: 'bob@everyman.com') 55 | expect(@user.first_name_or_email).to eq 'bob' 56 | end 57 | end 58 | 59 | context 'first name is neither nil or empty' do 60 | it 'should return first_name' do 61 | expect(@user.first_name).to eq 'Bob' 62 | end 63 | end 64 | end 65 | 66 | describe '.is_member?(team)' do 67 | let(:team1) { create(:team) } 68 | let(:team2) { create(:team) } 69 | let(:user) { create(:user, team: team1) } 70 | 71 | it 'should return true if user belong_to team' do 72 | expect(user.is_member?(team1)).to eq true 73 | end 74 | 75 | it 'should return false if user dont belong_to team' do 76 | expect(user.is_member?(team2)).to eq false 77 | end 78 | end 79 | 80 | describe '.score' do 81 | let(:user) { create(:user_with_bills) } 82 | 83 | it 'should return a score if it has two or more bills' do 84 | expect(user.score).to be 85 | end 86 | 87 | it 'should return 0 if it has less than two bills' do 88 | expect(@user.score).to eq 0 89 | end 90 | end 91 | 92 | describe '.most_recent_bills(number)' do 93 | let(:user) { create(:user_with_bills) } 94 | it { is_expected.to respond_to(:most_recent_bills).with(0).arguments } 95 | it { is_expected.to respond_to(:most_recent_bills).with(1).argument } 96 | it { is_expected.not_to respond_to(:most_recent_bills).with(2).arguments } 97 | 98 | it 'should return the number of requested recent bills' do 99 | expect(user.most_recent_bills(2).length).to eq 2 100 | end 101 | 102 | it 'should return one bill with zero arguments' do 103 | expect(user.most_recent_bills.length).to eq 1 104 | end 105 | 106 | it 'should return an array of requested recent bills' do 107 | expect(user.most_recent_bills(2)).to be_a(Array) 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /spec/models/user_building_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UserBuilding do 4 | it { should have_many :units } 5 | it { should validate_presence_of(:address) } 6 | 7 | before(:each) do 8 | UserBuilding.destroy_all # Clear out any newly-existing find-or-generate buildings. 9 | @user_building = create(:user_building) 10 | end 11 | 12 | describe '.all_addresses' do 13 | it 'returns as many addresses as there are user_buildings' do 14 | expect(UserBuilding.all.count).to eq UserBuilding.all_addresses.count 15 | end 16 | end 17 | 18 | describe '.find_or_generate' do 19 | 20 | it 'should return a building by matching id' do 21 | b = UserBuilding.find_or_generate(@user_building.id, nil) 22 | expect(b).to eq(@user_building) 23 | end 24 | 25 | it 'should match exact address' do 26 | b = UserBuilding.find_or_generate(nil, @user_building.address) 27 | expect(b).to eq(@user_building) 28 | end 29 | 30 | [ 31 | '123 main st, cambridge, ma', 32 | '123 main st cambridge ma', #fail 33 | '123 Main st., Cambridge MA', 34 | '123 main st., Cambridge, Massachussets 02138', #fail 35 | '123 main st., Cambrdige, Masachussetts', #fail 36 | '123 main st cambridge, massachussetts' #fail 37 | ].each do |address| 38 | 39 | it "should return match or create by fuzzily-matching address '#{address}'" do 40 | match = UserBuilding.find_or_generate(nil, address) 41 | 42 | # Use this to see which addresses pass/fail. 43 | # expect(match).to eq(@user_building) 44 | 45 | # Expect match to be included in either the first (and only) building id or 46 | # in the last building id. 47 | expect([@user_building.id, UserBuilding.last.id]).to include(match.id) 48 | 49 | # And just to be sure we're solid at our expected building count. 50 | expect([1,2]).to include(UserBuilding.count) 51 | end 52 | 53 | # => Failed examples: 54 | 55 | # rspec './spec/models/user_building_spec.rb[1:4:4]' # UserBuilding.find_or_generate should return match by address '123 main st cambridge ma' 56 | # rspec './spec/models/user_building_spec.rb[1:4:6]' # UserBuilding.find_or_generate should return match by address '123 main st., Cambridge, Massachussets 02138' 57 | # rspec './spec/models/user_building_spec.rb[1:4:7]' # UserBuilding.find_or_generate should return match by address '123 main st., Cambrdige, Masachussetts' 58 | # rspec './spec/models/user_building_spec.rb[1:4:8]' # UserBuilding.find_or_generate should return match by address '123 main st cambridge, massachussetts' 59 | end 60 | end 61 | 62 | describe '.parse_and_save_address_granules' do 63 | # Note that this tests only a well-formed address, and thus 64 | # signifies only that the components are being parsed and saved, 65 | # not their being parsed well. 66 | it 'should save parsed address components via after_save callback' do 67 | new_address = '115 Prospect St, Cambridge, MA' 68 | new_build_attrs = {address: new_address} 69 | created = UserBuilding.create(new_build_attrs) 70 | 71 | # Basic address did save. 72 | expect(created).to have_attributes(address: new_address) 73 | 74 | # Parsed address components. 75 | expect(created.number).to_not be_nil 76 | expect(created.street).to_not be_nil 77 | expect(created.city).to_not be_nil 78 | expect(created.state).to_not be_nil 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /app/controllers/bills_controller.rb: -------------------------------------------------------------------------------- 1 | class BillsController < ApplicationController 2 | before_action :set_bill, only: [:authorize_user, :show, :edit, :update, :destroy] 3 | before_action :authorize_user, only: [:show, :edit, :update, :destroy] 4 | before_action :skip_authorization, only: [:new, :comparison, :create] 5 | before_action :skip_policy_scope, only: [:index] 6 | 7 | # GET /bills 8 | # GET /bills.json 9 | def index 10 | @bills = Bill.all 11 | end 12 | 13 | # GET /bills/1 14 | # GET /bills/1.json 15 | def show 16 | end 17 | 18 | # GET /bills/new 19 | def new 20 | @bill = Bill.new 21 | end 22 | 23 | # GET /bills/1/edit 24 | def edit 25 | end 26 | 27 | #GET /bills/comparison 28 | def comparison 29 | @comparison_bills = Bill.joins(:unit).where('units.number_occupants' => current_or_guest_user.unit.number_occupants || 1).order(:usage) 30 | @most_recent_bill_id = current_or_guest_user.bills.last.id 31 | 32 | respond_to do |format| 33 | format.html 34 | format.json { render json: { current_user_id: current_or_guest_user.id, comparison_bills: @comparison_bills.as_json(), most_recent_bill_id: @most_recent_bill_id} } 35 | end 36 | end 37 | 38 | 39 | # POST /bills 40 | # POST /bills.json 41 | def create 42 | @bill = Bill.new(bill_params) 43 | @unit = current_or_guest_user.process_unit(bill_unit_params, user_is_guest) 44 | 45 | respond_to do |format| 46 | if @bill.valid? && @unit.valid? 47 | @bill.update(unit: @unit, user: current_or_guest_user) 48 | format.html { redirect_to '/graph/index' } 49 | format.json { render :show, status: :created, location: @bill } 50 | else 51 | format.html { render :new } 52 | format.json { render json: @bill.errors, status: :unprocessable_entity } 53 | end 54 | end 55 | end 56 | 57 | # PATCH/PUT /bills/1 58 | # PATCH/PUT /bills/1.json 59 | def update 60 | respond_to do |format| 61 | if @bill.update(bill_params) 62 | format.html { redirect_to @bill, notice: 'Bill was successfully updated.' } 63 | format.json { render :show, status: :ok, location: @bill } 64 | else 65 | format.html { render :edit } 66 | format.json { render json: @bill.errors, status: :unprocessable_entity } 67 | end 68 | end 69 | end 70 | 71 | # DELETE /bills/1 72 | # DELETE /bills/1.json 73 | def destroy 74 | @bill.destroy 75 | respond_to do |format| 76 | format.html { redirect_to bills_url, notice: 'Bill was successfully destroyed.' } 77 | format.json { head :no_content } 78 | end 79 | end 80 | 81 | private 82 | # Use callbacks to share common setup or constraints between actions. 83 | def user_is_guest 84 | if session[:guest_user_id] && session[:guest_user_id] == current_or_guest_user.id 85 | return true 86 | else 87 | return false 88 | end 89 | end 90 | 91 | def set_bill 92 | @bill = Bill.find(params[:id]) 93 | end 94 | 95 | def authorize_user 96 | authorize @bill 97 | end 98 | 99 | def bill_unit_params 100 | allowed_params[:units] 101 | end 102 | 103 | def bill_params 104 | allowed_params.except(:units) 105 | end 106 | 107 | # Never trust parameters from the scary internet, only allow the white list through. 108 | def allowed_params 109 | params.require(:bill).permit(:bill_received, :usage, :amount, :units => [:number_occupants]) 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /app/assets/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | .display-none { 2 | display: none; 3 | } 4 | 5 | .clearfix { 6 | overflow: auto; 7 | } 8 | 9 | .ui-datepicker-title { 10 | background: white; 11 | } 12 | 13 | #datepicker { 14 | width: 300px; 15 | } 16 | 17 | #ui-datepicker-div { 18 | background-color: white; 19 | } 20 | 21 | #usage { 22 | width: 300px; 23 | } 24 | #occupants { 25 | width: 300px; 26 | } 27 | 28 | .axis text { 29 | font: 10px sans-serif; 30 | } 31 | 32 | .axis path, 33 | .axis line { 34 | fill: none; 35 | stroke: #000; 36 | } 37 | 38 | .x.axis path { 39 | display: none; 40 | } 41 | 42 | .remove-bullets { 43 | list-style: none; 44 | } 45 | 46 | .team_image { 47 | height: 100px; 48 | width: 100px; 49 | } 50 | 51 | .team_member_attrs { 52 | margin: 0; 53 | padding: 0; 54 | width: 10%; 55 | } 56 | 57 | .top-bar { 58 | ul { 59 | list-style-type: none; 60 | overflow: hidden; 61 | padding: 0; 62 | margin: 0; 63 | a { 64 | display: inline-block; 65 | } 66 | } 67 | } 68 | 69 | .top-bar li { 70 | padding: 20px 16px; 71 | float: left; 72 | } 73 | 74 | .top-bar li.nav-right { 75 | float: right; 76 | } 77 | 78 | .user-name { 79 | overflow: hidden; 80 | } 81 | 82 | li.logo { 83 | width: 200px; 84 | padding: 8px; 85 | a { 86 | display: inline; 87 | } 88 | svg { 89 | transform: translate(1%,5%); 90 | } 91 | } 92 | 93 | .dropdown-arrow { 94 | fill: #1fafd3; 95 | height: 18px; 96 | height: 18px; 97 | transform: translate(0%, -12%); 98 | } 99 | 100 | div.dropdown { 101 | background-color: white; 102 | position: absolute; 103 | right: 0; 104 | margin-top: 20px; 105 | border: 1px solid #e1dfe2; 106 | min-width: 160px; 107 | a { 108 | padding: 12px 0 12px 8px; 109 | display: block; 110 | } 111 | } 112 | 113 | .teamCenter { 114 | text-align: center; 115 | } 116 | 117 | 118 | 119 | .key-features { 120 | width: 700px; 121 | height: 500px; 122 | 123 | p{ 124 | text-align: center; 125 | } 126 | 127 | .pad { 128 | padding-top: 70px; 129 | 130 | } 131 | 132 | #ener { 133 | margin-top: 6px; 134 | padding-top: 6px; 135 | } 136 | } 137 | 138 | 139 | .ui-autocomplete { 140 | width: 400px; 141 | border-radius: 5px; 142 | list-style-type: none; 143 | padding: 0; 144 | 145 | li { 146 | padding-left: 10px; 147 | padding-top: 5px; 148 | padding-bottom: 5px; 149 | font-size: 20px; 150 | } 151 | 152 | li.ui-state-focus { 153 | background: #e1dfe2; 154 | border-radius: 5px; 155 | border-style: none; 156 | } 157 | } 158 | 159 | .upload-summary-block { 160 | float: left; 161 | width: 33.33%; 162 | box-sizing: border-box; 163 | text-align: center; 164 | } 165 | 166 | .upload-summary-label { 167 | font-size: .8em; 168 | } 169 | 170 | .hero { 171 | background: image-url('hero.jpg') no-repeat center center; 172 | background-size: cover; 173 | height: 50vh; 174 | .intro { 175 | padding: 3rem; 176 | position: relative; 177 | top: 50%; 178 | transform: translateY(-50%); 179 | } 180 | h1 { 181 | color: #ffffff; 182 | font-size: 1.5rem; 183 | line-height: 1.5em; 184 | letter-spacing: -0.025em; 185 | font-weight: 300; 186 | text-align: right; 187 | } 188 | p { 189 | color: #ffffff; 190 | line-height: 1.75em; 191 | text-align: right; 192 | margin-bottom: 2rem; 193 | } 194 | @media only screen and (min-width: 40.063em) { 195 | h1 { 196 | padding-top: 4.5rem; 197 | text-align: right; 198 | font-size: 2.5rem; 199 | } 200 | p { 201 | text-align: right; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /lib/tasks/sample_data.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'faker' 3 | rescue LoadError => e 4 | raise e unless ENV['RAILS_ENV'] == "production" 5 | end 6 | 7 | namespace :db do 8 | desc "Fill database with sample data" 9 | task :populate => :environment do 10 | Rake::Task['db:reset'].invoke 11 | user_data = [ 12 | { first_name: 'Barbara', last_name: 'Gordon', email: 'BarbaraGordon@gmail.com', password: 'password' }, 13 | { first_name: 'Bruce', last_name: 'Wayne', email: 'BruceWayne@gmail.com', password: 'password' }, 14 | { first_name: 'Steven', last_name: 'Rogers', email: 'StevenRogers@gmail.com', password: 'password' }, 15 | { first_name: 'Billy', last_name: 'Batson', email: 'BillyBatson@gmail.com', password: 'password' }, 16 | { first_name: 'Selina', last_name: 'Kyle', email: 'SelinaKyle@gmail.com', password: 'password'}, 17 | { first_name: 'Victor', last_name: 'Von Doom', email: 'VictorVonDoom@gmail.com', password: 'password' }, 18 | { first_name: 'Emma', last_name: 'Frost', email: 'EmmaFrost@gmail.com', password: 'password' }, 19 | { first_name: 'Matthew', last_name: 'Murdock', email: 'MatthewMurdock@gmail.com', password: 'password' }, 20 | { first_name: 'Jonathan', last_name: 'Osterman', email: 'JonathanOsterman@gmail.com', password: 'password' }, 21 | { first_name: 'Walter', last_name: 'Kovacs', email: 'WalterKovacs@gmail.com', password: 'password' }, 22 | { first_name: 'Tim', last_name: 'Drake', email: 'TimDrake@gmail.com', password: 'password' }, 23 | { first_name: 'Barry', last_name: 'Allen', email: 'BarryAllen@gmail.com', password: 'password' }, 24 | { first_name: 'Harvey', last_name: 'Dent', email: 'HarveyDent@gmail.com', password: 'password' }, 25 | { first_name: 'Oswald', last_name: 'Cobblepot', email: 'OswaldCobblepot@gmail.com', password: 'password' }, 26 | { first_name: 'Peter', last_name: 'Parker', email: 'PeterParker@gmail.com', password: 'password' }, 27 | { first_name: 'Bruce', last_name: 'Banner', email: 'BruceBanner@gmail.com', password: 'password' }, 28 | { first_name: 'Scott', last_name: 'Summers', email: 'ScottSummers@gmail.com', password: 'password' }, 29 | { first_name: 'Natasha', last_name: 'Romanova', email: 'NataliaRomanova@gmail.com', password: 'password' }, 30 | { first_name: 'Cain', last_name: 'Marko', email: 'CainMarko@gmail.com', password: 'password' }, 31 | { first_name: 'Charles', last_name: 'Xavier', email: 'CharlesXavier@gmail.com', password: 'password' } 32 | ] 33 | 34 | users ||= user_data.map { |user| User.create(user.merge(phone: '5555555555')) } 35 | 36 | team_data = [ 37 | { name: 'Team Awesome', image_url: '' }, 38 | { name: 'Team FancyPants', image_url: '' }, 39 | { name: 'Team The Best Team Ever', image_url: '' }, 40 | { name: 'Team Not So Great', image_url: '' }, 41 | { name: 'Team Meets Expectations', image_url: '' } 42 | ] 43 | 44 | teams ||= team_data.map { |team| Team.create(team) } 45 | 46 | 47 | # assign teams 48 | users[0..-4].each_slice(2).with_index do |slice, index| 49 | slice.each do |user| 50 | user.team = teams[index] 51 | user.save 52 | end 53 | end 54 | 55 | # create and assign units 56 | users.each do |user| 57 | user_building = UserBuilding.create(address: Faker::Address.street_address) 58 | unit = Unit.create( 59 | user_building: user_building, 60 | unit_number: 4, 61 | sqfootage: 900, 62 | number_bedrooms: 2, 63 | number_bathrooms: 1, 64 | number_rooms: 2, 65 | number_occupants: 1 66 | ) 67 | user.unit = unit 68 | user.save 69 | end 70 | 71 | # create and assign bills 72 | users.each_with_index do |user, index| 73 | (1..5).map do 74 | Bill.create( 75 | bill_received: Faker::Time.between(DateTime.now - 1.year, DateTime.now), 76 | amount: rand(75..175), 77 | usage: rand(200..400), 78 | user: user, 79 | unit: user.unit 80 | ) 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | config.force_ssl = false 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | 80 | ActionMailer::Base.smtp_settings = { 81 | :user_name => ENV['SENDGRID_USERNAME'], 82 | :password => ENV['SENDGRID_PASSWORD'], 83 | :address => "smtp.sendgrid.net", 84 | :port => 587, 85 | :domain => 'yourapp.heroku.com', 86 | :authentication => :plain, 87 | :enable_starttls_auto => true, 88 | } 89 | 90 | config.action_mailer.default_url_options = { :host => 'enersaveapp.org' } 91 | end 92 | -------------------------------------------------------------------------------- /spec/controllers/units_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UnitsController do 4 | before(:each) do 5 | @unit = create(:unit) 6 | @user = create(:user, unit: @unit) 7 | sign_in @user 8 | end 9 | 10 | describe 'GET #show' do 11 | it 'is successful' do 12 | get(:show, id: @unit.id) 13 | 14 | expect(response).to render_template :show 15 | end 16 | end 17 | 18 | describe 'GET #new' do 19 | it 'is successful' do 20 | get(:new) 21 | 22 | expect(response).to render_template :new 23 | end 24 | end 25 | 26 | describe 'GET #edit' do 27 | it 'is successful' do 28 | get(:edit, id: @unit.id) 29 | 30 | expect(response).to render_template :edit 31 | end 32 | end 33 | 34 | describe 'POST #create' do 35 | context 'new unit is created with existing address' do 36 | let(:user_building) { create(:user_building) } 37 | 38 | it 'is successful' do 39 | unit = attributes_for(:unit).merge(user_building_id: user_building.id) 40 | post = lambda do 41 | post( 42 | :create, 43 | unit: unit, 44 | user_building: { address: '' } 45 | ) 46 | end 47 | 48 | expect(&post).to change{ Unit.count }.by(1) 49 | expect(response).to redirect_to Unit.last 50 | end 51 | 52 | it 'is not successful due to failing validations' do 53 | unit = attributes_for(:unit, number_occupants: 21) 54 | .merge(user_building_id: user_building.id) 55 | post = -> { post(:create, unit: unit, user_building: { address: '' }) } 56 | 57 | expect(&post).to_not change{ Unit.count } 58 | expect(response).to render_template :new 59 | end 60 | end 61 | 62 | context 'new unit is created with new address' do 63 | it 'is successful' do 64 | unit = attributes_for(:unit).merge(user_building_id: '') #.except(:user_building_id) 65 | post = lambda do 66 | post( 67 | :create, 68 | unit: unit, 69 | user_building: { address: '1 Broadway' } 70 | ) 71 | end 72 | 73 | expect(&post).to change{ Unit.count }.by(1) 74 | .and change{ UserBuilding.count }.by(1) 75 | expect(response).to redirect_to Unit.last 76 | end 77 | end 78 | 79 | it 'is not successful due to failing validations' do 80 | unit = attributes_for(:unit).except(:user_building_id) 81 | post = lambda do 82 | post( 83 | :create, 84 | unit: unit, 85 | user_building: { address: '' } 86 | ) 87 | end 88 | 89 | expectation = expect(&post) 90 | expectation.to_not change{ UserBuilding.count } 91 | expect(response).to render_template :new 92 | end 93 | end 94 | 95 | describe 'PATCH #update' do 96 | it 'is successful' do 97 | patch = lambda do 98 | patch( 99 | :update, 100 | id: @unit.id, 101 | unit: { number_occupants: 5 } 102 | ) 103 | end 104 | 105 | expect(&patch).to_not change{ Unit.count } 106 | expect(response).to redirect_to @unit 107 | end 108 | 109 | it 'is not successful due to failing validations' do 110 | patch = lambda do 111 | patch( 112 | :update, 113 | id: @unit.id, 114 | unit: { number_occupants: 21 } 115 | ) 116 | end 117 | 118 | expect(&patch).to_not change{ Unit.count } 119 | expect(response).to render_template :edit 120 | end 121 | end 122 | 123 | describe 'PATCH #leave' do 124 | it 'is successful' do 125 | user = create(:user) 126 | sign_in user 127 | 128 | # The id is 1 because it doesn't matter. This route should be collection, 129 | # not member, since each user only has one unit 130 | patch(:leave, id: 1) 131 | 132 | expect(response).to redirect_to users_me_path(user) 133 | expect(user.reload.unit_id).not_to be 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /app/controllers/teams_controller.rb: -------------------------------------------------------------------------------- 1 | class TeamsController < ApplicationController 2 | before_action :set_team, only: [:authorize_user, :show, :edit, :update, :destroy, :invite, :inviting, :leave] 3 | before_action :authorize_user, only: [:show, :edit, :update, :destroy, :leave] 4 | 5 | # GET /teams 6 | # GET /teams.json 7 | def index 8 | @teams = policy_scope(Team) 9 | @user = current_user 10 | end 11 | 12 | # GET /teams/1 13 | # GET /teams/1.json 14 | def show 15 | end 16 | 17 | # GET /teams/new 18 | def new 19 | @team = Team.new 20 | authorize @team 21 | end 22 | 23 | # GET /teams/1/edit 24 | def edit 25 | end 26 | 27 | # POST /teams 28 | # POST /teams.json 29 | def create 30 | @team = Team.new(team_params) 31 | authorize @team 32 | current_user.update team: @team 33 | 34 | respond_to do |format| 35 | if @team.persisted? 36 | format.html { redirect_to @team, notice: 'Team was successfully created.' } 37 | format.json { render :show, status: :created, location: @team } 38 | else 39 | format.html { render :new } 40 | format.json { render json: @team.errors, status: :unprocessable_entity } 41 | end 42 | end 43 | end 44 | 45 | # PATCH/PUT /teams/1 46 | # PATCH/PUT /teams/1.json 47 | def update 48 | respond_to do |format| 49 | if @team.update(team_params) 50 | format.html { redirect_to @team, notice: 'Team was successfully updated.' } 51 | format.json { render :show, status: :ok, location: @team } 52 | else 53 | format.html { render :edit } 54 | format.json { render json: @team.errors, status: :unprocessable_entity } 55 | end 56 | end 57 | end 58 | 59 | # DELETE /teams/1 60 | # DELETE /teams/1.json 61 | def destroy 62 | @team.destroy 63 | 64 | respond_to do |format| 65 | format.html { redirect_to teams_url, notice: 'Team was successfully destroyed.' } 66 | format.json { head :no_content } 67 | end 68 | end 69 | 70 | # Leave team 71 | def leave 72 | current_user.update team: nil 73 | @team.destroy if @team.users.empty? 74 | 75 | respond_to do |format| 76 | format.html { redirect_to '/users/me', notice: 'You have left the team.' } 77 | format.json { render :show, status: :ok, location: current_user } 78 | end 79 | end 80 | 81 | def leaderboard 82 | authorize Team, :leaderboard? 83 | @user = current_or_guest_user 84 | @teams = policy_scope(Team).by_score 85 | end 86 | 87 | def accept_or_decline 88 | authorize Team, :accept_or_decline? 89 | if params[:invite_params] == 'accept' 90 | accept_invite 91 | elsif params[:invite_params] == 'decline' 92 | decline_invite 93 | else 94 | raise ActionController::RoutingError.new('Not Found') 95 | end 96 | end 97 | 98 | private 99 | # Use callbacks to share common setup or constraints between actions. 100 | def set_team 101 | @team = Team.find(params[:id]) 102 | end 103 | 104 | def authorize_user 105 | authorize @team 106 | end 107 | 108 | # Never trust parameters from the scary internet, only allow the white list through. 109 | def team_params 110 | params.require(:team).permit(:name, :image_url, :invite_parms) 111 | end 112 | 113 | def invite_params 114 | params.permit(:decline, :accept) 115 | end 116 | 117 | def accept_invite 118 | if current_user.invited_by_id 119 | current_user.accept_invite 120 | @team = current_user.team 121 | flash[:notice] = "You have joined #{current_user.team.name}!" 122 | redirect_to team_path(@team) 123 | elsif current_user.team 124 | redirect_to team_path(current_user.team) 125 | else 126 | redirect_to :root 127 | end 128 | end 129 | 130 | def decline_invite 131 | if current_user.invited_by_id 132 | flash[:notice] = "#{current_user.inviter_team.name} will be sorry to hear it!" 133 | current_user.decline_invite 134 | end 135 | redirect_to :root 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /spec/controllers/user_buildings_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UserBuildingsController do 4 | 5 | def valid_building_params 6 | { 7 | address: '2 Harvard St., Cambridge, MA 02138', 8 | lat: 42.3, 9 | lon: -71.2 10 | } 11 | end 12 | 13 | before(:each) do 14 | @user_building = create(:user_building) 15 | @unit = create(:unit, user_building: @user_building) 16 | @user = create(:user, unit: @unit) 17 | sign_in @user 18 | end 19 | 20 | describe 'GET #show' do 21 | it 'is successful' do 22 | get(:show, id: @user_building.id) 23 | 24 | expect(response).to render_template :show 25 | end 26 | end 27 | 28 | describe 'GET #new' do 29 | it 'is successful' do 30 | get(:new) 31 | 32 | expect(response).to render_template :new 33 | end 34 | end 35 | 36 | describe 'GET #index' do 37 | it 'is successful' do 38 | get(:index) 39 | 40 | expect(response).to render_template :index 41 | end 42 | end 43 | 44 | describe 'GET #edit' do 45 | it 'is successful' do 46 | get(:edit, id: @user_building.id) 47 | 48 | expect(response).to render_template :edit 49 | end 50 | end 51 | 52 | describe 'POST #create' do 53 | context 'creating a new user building' do 54 | it 'creates a building with valid params' do 55 | post = lambda do 56 | post( 57 | :create, 58 | user_building: valid_building_params 59 | ) 60 | end 61 | 62 | expecting = expect(&post) 63 | expecting.to change{ UserBuilding.count }.by(1) 64 | expect(response).to redirect_to UserBuilding.last 65 | end 66 | 67 | it 'refuses to save a building without address' do 68 | params_without_address = valid_building_params.merge(address: '') 69 | post = -> { post(:create, user_building: params_without_address) } 70 | 71 | expectation = expect(&post) 72 | expectation.to_not change{ UserBuilding.count } 73 | expect(response).to render_template :new 74 | end 75 | end 76 | end 77 | 78 | describe 'PATCH #update' do 79 | context 'updating a user building' do 80 | 81 | valid_addresses = ['14 Beacon St., Greenwich, England', 'amishcountry', '4'] 82 | invalid_addresses = ['', ' '] 83 | 84 | valid_addresses.each do |address| 85 | it "updates a building with valid params like '#{address}'" do 86 | patch = lambda do 87 | patch( 88 | :update, 89 | id: @user_building.id, 90 | user_building: { 91 | address: address 92 | } 93 | ) 94 | end 95 | 96 | expecting = expect(&patch) 97 | expecting.to_not change{ UserBuilding.count } 98 | expect(response).to redirect_to @user_building 99 | expect(valid_addresses).to include(@user_building.reload.address) 100 | end 101 | end 102 | 103 | invalid_addresses.each do |address| 104 | it "refuses to update a building with invalid address like '#{address}'" do 105 | patch = lambda do 106 | patch( 107 | :update, 108 | id: @user_building.id, 109 | user_building: { 110 | address: address 111 | } 112 | ) 113 | end 114 | 115 | expecting = expect(&patch) 116 | expecting.to_not change{ UserBuilding.count } 117 | expect(@user_building.address_changed?).to be(false) 118 | expect(response).to render_template :edit 119 | end 120 | end 121 | end 122 | end 123 | 124 | describe 'DELETE #destroy' do 125 | it 'demolishes building' do 126 | delete = lambda do 127 | delete( 128 | :destroy, 129 | id: @user_building.id 130 | ) 131 | 132 | expecting = expect(&delete) 133 | expecting.to change{ UserBuilding.count }.by(-1) 134 | expect(response).to redirect_to user_buildings_path 135 | end 136 | end 137 | end 138 | 139 | end 140 | --------------------------------------------------------------------------------