├── 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 |
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 |
7 | <% @tip.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
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 | Name
9 |
10 |
11 |
12 |
13 |
14 | <% @teams.each do |team| %>
15 |
16 | <%= team.name %>
17 | <%= link_to 'Show', team %>
18 | <%= link_to 'Edit', edit_team_path(team) %>
19 | <%= link_to 'Destroy', team, method: :delete, data: { confirm: 'Are you sure?' } %>
20 |
21 | <% end %>
22 |
23 |
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 |
7 | <% @team.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
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 | Bill received
8 | Usage (kwh)
9 | User
10 |
11 |
12 |
13 |
14 |
15 | <% @comparison_bills.each do |bill| %>
16 |
17 | <%= bill.bill_received %>
18 | <%= bill.usage %>
19 | <%= bill.user %>
20 | <%= link_to 'Show', bill %>
21 | <%= link_to 'Edit', edit_bill_path(bill) %>
22 | <%= link_to 'Destroy', bill, method: :delete, data: { confirm: 'Are you sure?' } %>
23 |
24 | <% end %>
25 |
26 |
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 |
7 | <% @user_building.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
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 | Bill received
9 | Usage (kwh)
10 | Amount
11 | User
12 |
13 |
14 |
15 |
16 |
17 | <% @bills.each do |bill| %>
18 |
19 | <%= bill.bill_received %>
20 | <%= bill.usage %>
21 | <%= bill.amount %>
22 | <%= bill.user.first_name %>
23 | <%= link_to 'Show', bill %>
24 | <%= link_to 'Edit', edit_bill_path(bill) %>
25 | <%= link_to 'Destroy', bill, method: :delete, data: { confirm: 'Are you sure?' } %>
26 |
27 | <% end %>
28 |
29 |
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 | Address
9 | Lat
10 | Lon
11 |
12 |
13 |
14 |
15 |
16 | <% @user_buildings.each do |user_building| %>
17 |
18 | <%= user_building.address %>
19 | <%= user_building.lat %>
20 | <%= user_building.lon %>
21 | <%= link_to 'Show', user_building %>
22 | <%= link_to 'Edit', edit_user_building_path(user_building) %>
23 | <%= link_to 'Destroy', user_building, method: :delete, data: { confirm: 'Are you sure?' } %>
24 |
25 | <% end %>
26 |
27 |
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 | Text
18 | Likes
19 | Dislikes
20 |
21 |
22 |
23 |
24 |
25 | <% @tips.each do |tip| %>
26 |
27 | <%= tip.text %>
28 | <%= tip.liked_votes %>
29 | <%= tip.disliked_votes %>
30 | <%= link_to 'Show', tip %>
31 | <%= link_to 'Destroy', tip, method: :delete, data: { confirm: 'Are you sure?' } %>
32 |
33 | <% end %>
34 |
35 |
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 | User building
9 | Unit number
10 | Sqfootage
11 | Number bedrooms
12 | Number bathrooms
13 | Number rooms
14 | Number occupants
15 |
16 |
17 |
18 |
19 |
20 | <% @units.each do |unit| %>
21 |
22 | <%= unit.user_building %>
23 | <%= unit.unit_number %>
24 | <%= unit.sqfootage %>
25 | <%= unit.number_bedrooms %>
26 | <%= unit.number_bathrooms %>
27 | <%= unit.number_rooms %>
28 | <%= unit.number_occupants %>
29 | <%= link_to 'Show', unit %>
30 | <%= link_to 'Edit', edit_unit_path(unit) %>
31 | <%= link_to 'Destroy', unit, method: :delete, data: { confirm: 'Are you sure?' } %>
32 |
33 | <% end %>
34 |
35 |
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 |
7 | <% @user.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
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 | Bill Date
38 | Bill Days
39 | Usage (kwh)
40 | Amount
41 |
42 |
43 | <% @bills.each do |bill| %>
44 |
45 | <%= bill.bill_received.to_formatted_s(:long) %>
46 | <%= bill.bill_days%>
47 | <%= bill.usage.to_i%>
48 | <%= number_to_currency(bill.amount)%>
49 |
50 | <% end %>
51 |
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 |
12 | <% @user_tip.errors.full_messages.each do |message| %>
13 | <%= message %>
14 | <% end %>
15 |
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 |
7 | <% @bill.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
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 |
19 | <% @unit.errors.full_messages.each do |message| %>
20 | <%= message %>
21 | <% end %>
22 |
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 |
17 |
18 |
19 | <%= inline_svg("logo-h.svg") %>
20 |
21 | <% if user_signed_in? %>
22 |
23 | <%= link_to current_user.first_name_or_email, users_me_path, class: 'user-name' %>
24 | <%= inline_svg 'icon-arrow-down.svg', class: 'dropdown-arrow' %>
25 |
26 | <%= link_to 'Sign Out', destroy_user_session_path, method: :delete %>
27 |
28 |
29 | <% else %>
30 |
31 | <%= link_to 'Sign Up', new_user_registration_path %>
32 |
33 |
34 | <%= link_to t('navigation.sign_in'), new_user_session_path %>
35 |
36 | <% end %>
37 |
38 | Feedback
39 |
40 |
41 | Back
42 |
43 |
44 |
45 |
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 |
6 | <% @unit.errors.full_messages.each do |message| %>
7 | <%= message %>
8 | <% end %>
9 |
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 |
17 | <% @user_building.errors.full_messages.each do |message| %>
18 | <%= message %>
19 | <% end %>
20 |
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 [](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 | Name
36 | Score
37 |
38 |
39 |
40 |
41 | <% @teams.each do |team| %>
42 |
43 | <% if current_user.is_member?(team) %>
44 | <%= link_to team.name, team %>
45 | <% else %>
46 | <%= team.name %>
47 | <% end %>
48 | <%= team.score.round(2) %>
49 |
50 | <% end %>
51 |
52 |
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 |
--------------------------------------------------------------------------------