Merge pull request #196 from Growstuff/dev

Production push: search nearby by distance, connect to twitter and flickr, etc
This commit is contained in:
Skud
2013-05-07 03:16:16 -07:00
63 changed files with 891 additions and 128 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
.*.sw*
*~
*.DS_Store
credentials.sh

View File

@@ -1,16 +1,25 @@
---
language: ruby
rvm:
- 1.9.3
script:
- bundle exec rake db:migrate --trace
- bundle exec rspec spec/
- bundle exec rake db:migrate --trace
- bundle exec rspec spec/
notifications:
recipients:
- discuss@lists.growstuff.org
email:
on_success: change
on_failure: always
irc:
channels:
- "irc.parrot.org#growstuff"
on_success: change
on_failure: change
# after_success:
# - if [[ "$TRAVIS_BRANCH" == "dev" ]]; then git remote add heroku git@heroku.com:growstuff-dev.git
# - wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh
# - echo "Host heroku.com" >> ~/.ssh/config
# - echo " StrictHostKeyChecking no" >> ~/.ssh/config
# - echo " CheckHostIP no" >> ~/.ssh/config
# - echo " UserKnownHostsFile=/dev/null" >> ~/.ssh/config
# - heroku keys:clear
# - yes | heroku keys:add
# - yes | git push heroku dev:master
# - heroku run rake db:migrate
# - heroku restart
# - fi
env:
global:
secure: "QFQbCdNGyjeatp/H0j0y0oGiue45fpG2w6eA2QAbq2RmvhabgXbd5WIobN90\ndrae3S7TRxPDpMpus90icykX6EzOTLXCEvaC4rh9pCcRktj3SZqq5b9rVTvs\n1MvlS6HhtsVqsrKjQUb0WmPpnganIzTs0RtGaQspo2joPJO18A4="

34
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,34 @@
Thanks for contributing to Growstuff! We have different contribution
guidelines depending on whether your change is a small one (a one-line
bugfix or similar) or a larger one.
## Small changes (no more than a couple of lines)
Send us a pull request! We will get one of our pairs of coders to
review it.
If you are interested in becoming a more regular contributor or working
on larger features, please see the [Growstuff
wiki](http://wiki.growstuff.org/) for more information.
## Larger changes
Growstuff does pair programming (two coders working together) for all
significant changes. This means that if you submit a pull request and
weren't working in a pair, we can't merge it into the project as-is.
If you would like to work on any larger change, we would appreciate it
if you would get in touch with us, preferably via our [mailing
list](http://lists.growstuff.org/mailman/listinfo/discuss), and talk to
us about it first. We'll try and hook you up with a partner so you can
work as a pair, either in person or remotely depending on where you are.
The [Growstuff wiki](http://wiki.growstuff.org/) has lots more
information on our dev process, to get you started if you would like to
join us.
If you submit a significant change without working in a pair, we will
treat your work as an experimental "spike" and get one of our pairs of
programmers to look over it and maybe use what you've done as the basis
for re-implementing it using our processes. **We'd much rather work with
you, so please talk to us first!**

View File

@@ -75,6 +75,12 @@ gem 'geocoder'
# For easy calendar selection
gem 'bootstrap-datepicker-rails'
# For connecting to other services (eg Twitter)
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-flickr'
gem 'authbuttons-rails'
# for phusion passenger (i.e. mod_rails) on the server
gem 'passenger'
gem 'rake', '>= 10.0.0'

View File

@@ -29,6 +29,7 @@ GEM
i18n (= 0.6.1)
multi_json (~> 1.0)
arel (3.0.2)
authbuttons-rails (0.1.2)
bcrypt-ruby (3.0.1)
bluecloth (2.2.0)
bootstrap-datepicker-rails (1.0.0)
@@ -95,6 +96,7 @@ GEM
activesupport (>= 3.1, < 4.1)
haml (>= 3.1, < 4.1)
railties (>= 3.1, < 4.1)
hashie (1.2.0)
highline (1.6.16)
hike (1.2.1)
i18n (0.6.1)
@@ -124,8 +126,19 @@ GEM
net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5)
newrelic_rpm (3.5.8.72)
nokogiri (1.5.7)
nokogiri (1.5.8)
oauth (0.4.7)
omniauth (1.1.3)
hashie (~> 1.2)
rack
omniauth-flickr (0.0.11)
omniauth-oauth (~> 1.0)
omniauth-oauth (1.0.1)
oauth
omniauth (~> 1.0)
omniauth-twitter (0.0.16)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
orm_adapter (0.4.0)
passenger (3.0.19)
daemon_controller (>= 1.0.0)
@@ -220,6 +233,7 @@ PLATFORMS
ruby
DEPENDENCIES
authbuttons-rails
bluecloth
bootstrap-datepicker-rails
bundler (>= 1.1.5)
@@ -242,6 +256,9 @@ DEPENDENCIES
json (~> 1.7.7)
less-rails
newrelic_rpm
omniauth
omniauth-flickr
omniauth-twitter
passenger
pg
rack (~> 1.4.5)

View File

@@ -0,0 +1,2 @@
class AboutController < ApplicationController
end

View File

@@ -0,0 +1,54 @@
class AuthenticationsController < ApplicationController
# GET /authentications
# GET /authentications.json
def index
@authentications = current_member.authentications if member_signed_in?
respond_to do |format|
format.html # index.html.erb
format.json { render json: @authentications }
end
end
# POST /authentications
# POST /authentications.json
def create
auth = request.env['omniauth.auth']
@authentication = nil
if auth
name = ''
case auth['provider']
when 'twitter'
name = auth['info']['nickname']
when 'flickr'
name = auth['info']['name']
else
name = auth['info']['name']
end
@authentication = current_member.authentications.find_or_create_by_provider_and_uid(
:provider => auth['provider'],
:uid => auth['uid'],
:name => name,
:token => auth['credentials']['token'],
:secret => auth['credentials']['secret'])
flash[:notice] = "Authentication successful."
else
flash[:notice] = "Authentication failed."
end
redirect_to edit_member_registration_path
end
# DELETE /authentications/1
# DELETE /authentications/1.json
def destroy
@authentication = Authentication.find(params[:id])
@authentication.destroy
respond_to do |format|
format.html { redirect_to edit_member_registration_path }
format.json { head :no_content }
end
end
end

View File

@@ -29,6 +29,7 @@ class CommentsController < ApplicationController
@post = Post.find_by_id(params[:post_id])
if @post
@comments = @post.comments
respond_to do |format|
format.html # new.html.erb
format.json { render json: @comment }
@@ -42,6 +43,7 @@ class CommentsController < ApplicationController
# GET /comments/1/edit
def edit
@comment = Comment.find(params[:id])
@comments = @comment.post.comments
end
# POST /comments

View File

@@ -11,8 +11,10 @@ class MembersController < ApplicationController
end
def show
@member = Member.confirmed.find(params[:id])
@posts = @member.posts
@member = Member.confirmed.find(params[:id])
@twitter_auth = @member.authentications.find_by_provider('twitter')
@flickr_auth = @member.authentications.find_by_provider('flickr')
@posts = @member.posts
# The garden form partial is called from the "New Garden" tab;
# it requires a garden to be passed in @garden.
# The new garden is not persisted unless Garden#save is called.
@@ -35,8 +37,20 @@ class MembersController < ApplicationController
else
@location = nil
end
@distance = 100
@nearby_members = @location ? Member.near(@location, @distance) : []
if !params[:distance].blank?
@distance = params[:distance]
else
@distance = 100
end
if params[:units] == "mi"
@units = :mi
else
@units = :km
end
@nearby_members = @location ? Member.near(@location, @distance, :units => @units) : []
respond_to do |format|
format.html # nearby.html.haml
format.json { render json: @nearby_members }

View File

@@ -1,8 +1,17 @@
# we need this subclass so that Devise doesn't force people to change their
# password every time they want to edit their settings. Code copied from
# https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
class RegistrationsController < Devise::RegistrationsController
def edit
@twitter_auth = current_member.authentications.find_by_provider('twitter')
@flickr_auth = current_member.authentications.find_by_provider('flickr')
render "edit"
end
# we need this subclassed method so that Devise doesn't force people to
# change their password every time they want to edit their settings.
# Code copied from
# https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
def update
# required for settings form to submit when password is left blank
if params[:member][:password].blank?
@@ -12,11 +21,12 @@ class RegistrationsController < Devise::RegistrationsController
end
@member = Member.find(current_member.id)
if @member.update_attributes(params[:member])
set_flash_message :notice, :updated
# Sign in the member bypassing validation in case his password changed
sign_in @member, :bypass => true
redirect_to after_update_path_for(@member)
redirect_to edit_member_registration_path
else
render "edit"
end

View File

@@ -8,6 +8,7 @@ class Ability
can :read, :all
cannot :read, Notification
cannot :create, Notification
cannot :read, Authentication
# nobody should be able to view this except admins
cannot :read, Role
@@ -43,6 +44,10 @@ class Ability
can :manage, ScientificName
end
# can create & destroy their own authentications against other sites.
can :create, Authentication
can :destroy, Authentication, :member_id => member.id
# anyone can create a post, or comment on a post,
# but only the author can edit/destroy it.
can :create, Post

View File

@@ -0,0 +1,4 @@
class Authentication < ActiveRecord::Base
belongs_to :member
attr_accessible :provider, :uid, :token, :secret, :name
end

View File

@@ -10,6 +10,7 @@ class Member < ActiveRecord::Base
has_and_belongs_to_many :roles
has_many :notifications, :foreign_key => 'recipient_id'
has_many :sent_notifications, :foreign_key => 'sender_id'
has_many :authentications
default_scope order("lower(login_name) asc")
scope :confirmed, where('confirmed_at IS NOT NULL')

View File

@@ -0,0 +1,17 @@
-content_for :title, 'Contact'
%dl
%dt General contact email
%dd= link_to 'info@growstuff.org', 'mailto:info@growstuff.org'
%dl
%dt
Support and accounts enquiries (not covered by the
=succeed ")" do
=link_to 'FAQ', url_for(:controller => '/support')
%dd= link_to 'support@growstuff.org', 'mailto:support@growstuff.org'
%dl
%dt Media/Press enquiries
%dd= link_to 'media@growstuff.org', 'mailto:media@growstuff.org'
%dl
%dt Twitter
%dd= link_to '@Growstuff', 'http:/twitter.com/Growstuff'

View File

@@ -0,0 +1,22 @@
%h1 Linked accounts on other sites
- if @authentications
- unless @authentications.empty?
.authentications
- @authentications.each do |authentication|
%a{ :href => "http://twitter.com/#{authentication.name}" }
.authentication
= image_tag "#{authentication.provider}_64.png", :size => "64x64"
.provider
= authentication.provider.titleize
.name
= authentication.name
= link_to "X", authentication, :confirm => "Are you sure you want to remove this authentication?", :method => :delete, :class => "remove"
.clear
%p
%strong Link another external account:
- else
%p
%strong Link to an external account:
= link_to image_tag("twitter_64.png", :size => "64x64"), "/auth/twitter", :class => "auth_provider"

View File

@@ -1,5 +1,7 @@
= render :partial => "posts/single", :locals => { :post => @post || @comment.post, :subject => true }
= render :partial => "posts/comments"
%h2 Your comment
= form_for @comment do |f|
- if @comment.errors.any?

View File

@@ -8,6 +8,10 @@
%i
%small
= crop.scientific_names.first.scientific_name
%br/
%small
Planted
= pluralize(crop.plantings_count, "time")
- else
= image_tag('http://placehold.it/150x150', :alt => '', :class => 'img-rounded')
Sample crop

View File

@@ -2,11 +2,19 @@
.row
.span9
%p
=link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-primary'
%h2 Who's growing this?
- @crop.plantings.each do |p|
= render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' }
- if @crop.plantings_count > 0
%p
Planted
= pluralize(@crop.plantings_count, "time")
by #{Growstuff::Application.config.site_name} members
%p= link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-primary'
- @crop.plantings.each do |p|
= render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' }
- else
%p Nobody is growing this yet. You could be the first!
%p= link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-primary'
.span3
%h4 Scientific names:

View File

@@ -5,8 +5,7 @@
Your account on #{site_name} has been created. You just need to confirm
your email address through the link below:
%p
= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token)
%p= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token)
%p
Once you're confirmed, you can sign in with your login name
@@ -17,22 +16,23 @@
%p
We're excited to have you as a member, and hope you'll enjoy
what #{site_name} has to offer. Take a look around the site,
=link_to 'plant some things', url_for(:controller => '/crops', :only_path => false)
, and feel free to drop in on the
=link_to 'forums', url_for(:controller => '/forums', :only_path => false)
= succeed "," do
= link_to('plant some things', url_for(:controller => '/crops', :only_path => false))
and feel free to drop in on the
= link_to 'forums', url_for(:controller => '/forums', :only_path => false)
if you have any questions or feedback.
%p
We'd also appreciate it if you'd read our
=link_to 'Community Guidelines', url_for(:controller => '/policy', :action => 'community', :only_path => false)
, and make sure you follow them. We want #{site_name} to be a
= succeed "," do
= link_to 'Community Guidelines', url_for(:controller => '/policy', :action => 'community', :only_path => false)
and make sure you follow them. We want #{site_name} to be a
friendly, welcoming environment for everyone, and we hope you'll
help us keep it that way.
%p
Looking forward to seeing you!
%p Looking forward to seeing you!
%p
The #{site_name} team.
%br/
=link_to root_url, root_url
= link_to root_url, root_url

View File

@@ -1,19 +1,21 @@
- site_name = Growstuff::Application.config.site_name
%p Hello #{@resource.login_name},
%p Someone has requested a link to reset your password on #{site_name}.
We presume this was you, in which case you can do so through this link:
%p
Someone has requested a link to reset your password on #{site_name}.
We presume this was you, in which case you can do so through this link:
%p= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token)
%p
= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token)
%p If it wasn't you, then someone's made a typo or has been messing
around. In this case, you can safely ignore this email, and your
password will not be changed.
If it wasn't you, then someone's made a typo or has been messing
around. In this case, you can safely ignore this email, and your
password will not be changed.
%p See you soon,
%p The #{site_name} team.
%br/
=link_to root_url, root_url
%p
The #{site_name} team.
%br/
=link_to root_url, root_url

View File

@@ -10,11 +10,14 @@
%p= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token)
%p If you have actually forgotten your password, you can
= link_to 'reset your password', new_password_url(@resource)
after you've unlocked your account.
%p
If you have actually forgotten your password, you can
= link_to 'reset your password', new_password_url(@resource)
after you've unlocked your account.
%p See you soon,
%p The #{site_name} team.
%br/
=link_to root_url, root_url
%p
The #{site_name} team.
%br/
=link_to root_url, root_url

View File

@@ -25,7 +25,8 @@
%p
%br/
To change your profile picture, visit
= link_to 'gravatar.com', "http://gravatar.com/"
= succeed "." do
= link_to 'gravatar.com', "http://gravatar.com/"
.control-group
=f.label :location, 'Your location', :class => 'control-label'
@@ -38,6 +39,28 @@
= f.check_box :show_email
Show email publicly on your profile page
%h2 Linked accounts
.control-group
.controls
%p
= image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo'
- if @twitter_auth
You are connected to Twitter as
= succeed "." do
=link_to @twitter_auth.name, "http://twitter.com/#{@twitter_auth.name}"
= link_to "Disconnect", @twitter_auth, :confirm => "Are you sure you want to remove this connection?", :method => :delete, :class => "remove"
- else
=link_to 'Connect to Twitter', '/auth/twitter'
%p
- if @flickr_auth
You are connected to Flickr as
= succeed "." do
=link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}"
= link_to "Disconnect", @flickr_auth, :confirm => "Are you sure you want to remove this connection?", :method => :delete, :class => "remove"
- else
=link_to 'Connect to Flickr', '/auth/flickr'
%h2 Change password
%p
%span.help-block Leave blank if you don't want to change your password.

View File

@@ -4,6 +4,7 @@
.container
%ul.nav
%li= link_to "About", "http://wiki.growstuff.org"
%li= link_to "Contact", url_for(:controller => '/about', :action => 'contact')
%li= link_to "Terms of Service", url_for(:controller => '/policy', :action => 'tos')
%li= link_to "Privacy Policy", url_for(:controller => %'/policy', :action => 'privacy')
%li= link_to "Community Guidelines", url_for(:controller => '/policy', :action => 'community')

View File

@@ -10,7 +10,6 @@
%title
= content_for?(:title) ? yield(:title) + " - #{ Growstuff::Application.config.site_name} " : Growstuff::Application.config.site_name
= stylesheet_link_tag "application", :media => "all"
= csrf_meta_tags
/ Le HTML5 shim, for IE6-8 support of HTML elements
/[if lt IE 9]

View File

@@ -4,7 +4,10 @@
%p
= form_tag(nearby_members_path, :method => :get, :class => 'form-inline') do
= label_tag :location, "Find members near", :class => 'control-label'
= label_tag :distance, "Find members within", :class => 'control-label'
= text_field_tag :distance, @distance, :class => 'input-mini'
= select_tag :units, options_for_select({"miles" => :mi, "km" => :km}, @units), :class => 'input-small'
= label_tag :location, "miles of", :class => 'control-label'
= text_field_tag :location, @location
= submit_tag "Search", :class => 'btn btn-primary'

View File

@@ -12,21 +12,37 @@
=link_to 'Send Message', new_notification_path(:sender_id => current_member.id, :recipient_id => @member.id), :class => 'btn btn-primary'
%p
%strong Member since:
%strong Member since:
= @member.created_at.to_s(:date)
- if @twitter_auth || @flickr_auth || @member.show_email
%h4 Contact
- if @twitter_auth
%p
= image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo'
=link_to @twitter_auth.name, "http://twitter.com/#{@twitter_auth.name}"
- if @flickr_auth
%p
Flickr:
=link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}"
- if @member.show_email
%p
Email:
= mail_to @member.email
- if @member.location.to_s != ''
%h4 Location
%p
%strong Location:
= image_tag("http://maps.google.com/maps/api/staticmap?size=200x200&maptype=roadmap&sensor=false&markers=color:green|label:A|#{@member.latitude},#{@member.longitude}&zoom=12", :alt => "Map showing #{@member.location}", :width => 200, :height => 200 )
%br/
Location:
= @member.location
%br/
= link_to 'Find members near here', nearby_members_path(:location => @member.location)
%br/
= image_tag("http://maps.google.com/maps/api/staticmap?size=200x200&maptype=roadmap&sensor=false&markers=color:green|label:A|#{@member.latitude},#{@member.longitude}&zoom=12", :alt => "Map showing #{@member.location}", :width => 200, :height => 200 )
%p
- if @member.show_email
%p
Email:
= mail_to @member.email
- if current_member == @member
%p
= link_to 'Edit Settings', "edit", :class => 'btn btn-mini'
@@ -64,6 +80,9 @@
%h3 Create a new garden
= render 'gardens/form'
%h3 Updates
- @member.posts.each do |post|
= render :partial => "posts/single", :locals => { :post => post, :subject => true }
%h3 Posts
- if @member.posts.count > 0
- @member.posts.each do |post|
= render :partial => "posts/single", :locals => { :post => post, :subject => true }
- else
%p Nothing posted yet.

View File

@@ -0,0 +1,11 @@
%a{:name => "comments"}
- if @comments
%h2
=pluralize(@comments.length, "comment")
- @comments.each do |c|
= render :partial => "comments/single", :locals => { :comment => c }
- else
%h2 There are no comments yet

View File

@@ -10,16 +10,7 @@
= link_to 'Delete Post', @post, method: :delete, |
data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini'
%a{:name => "comments"}
- if @comments
%h2
=pluralize(@comments.length, "comment")
- @comments.each do |c|
= render :partial => "comments/single", :locals => { :comment => c }
- else
%h2 There are no comments yet
= render :partial => "comments", :locals => { :post => @post }
- if can? :create, Comment
.post-actions

View File

@@ -5,8 +5,8 @@
### Who runs Growstuff?
Growstuff is run by Alex Bayley ([Skud](/members/skud)) and Courtney
Webster ([phazel](/members/phazel)), two software developers from Melbourne, Australia.
Growstuff is run by Alex Bayley ([Skud](/members/skud)) an open source
software developer and keen veggie gardener from Melbourne, Australia.
### How did Growstuff get started?

View File

@@ -66,3 +66,12 @@ Geocoder::Lookup::Test.add_stub(
}
]
)
Geocoder::Lookup::Test.add_stub(
"Edinburgh", [
{
'latitude' => 55.953252,
'longitude' => -3.188267,
}
]
)

View File

@@ -0,0 +1 @@
Geocoder.configure(:units => :km)

View File

@@ -0,0 +1,4 @@
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
provider :flickr, ENV['FLICKR_KEY'], ENV['FLICKR_SECRET']
end

View File

@@ -4,4 +4,4 @@
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
Growstuff::Application.config.secret_token = '6b61c7a7c5f5684945c445d80ea3df725ee2bb3101a3a57a4bec349657e092a562aa714cb4706c038d7fb1bdbd497c4b103aa1247f3bdf79044e03363ea8ee50'
Growstuff::Application.config.secret_token = ENV['RAILS_SECRET_TOKEN'] || "this is not a real secret token but it's here to make life easier for developers"

View File

@@ -2,6 +2,7 @@ Growstuff::Application.routes.draw do
devise_for :members, :controllers => { :registrations => "registrations" }
resources :authentications
resources :plantings
resources :gardens
resources :posts
@@ -17,6 +18,8 @@ Growstuff::Application.routes.draw do
match 'search/members/nearby' => 'members#nearby', :as => :nearby_members
match 'auth/:provider/callback' => 'authentications#create'
# The priority is based upon order of creation:
# first created -> highest priority.
@@ -77,5 +80,7 @@ Growstuff::Application.routes.draw do
match '/policy/:action' => 'policy#:action'
match '/support' => 'support#index'
match '/support/:action' => 'support#:action'
match '/about' => 'about#index'
match '/about/:action' => 'about#:action'
end

33
credentials.example Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# This file is an empty sample file which you (i.e. Growstuff
# developers) can copy and use to store your API credentials for
# external APIs used by the Growstuff application.
# To use this file, simply copy it to credentials.sh (which is
# .gitignore'd) and then fill in whatever credentials you need. Then in
# the window where you run "rails s", first run:
#
# source credentials.sh
#
# If you then run "rails s", it will have all the environment variables
# available to it.
### CREDENTIALS ###
# Mandrill is used to send transactional email (eg. signup
# confirmations). If using Heroku connect to Mandrill via Heroku addons
# list then go to tools menu (upper right) and choose "SMTP and API
# Credentials"
export MANDRILL_USERNAME=""
export MANDRILL_APIKEY=""
# Used for connecting member accounts to Twitter
# Get Twitter key from https://dev.twitter.com/apps
export TWITTER_KEY=""
export TWITTER_SECRET=""
# Used for connecting member accounts to Flickr
# Get Flickr key from http://www.flickr.com/services/apps/create/apply/
export FLICKR_KEY=""
export FLICKR_SECRET=""

View File

@@ -0,0 +1,13 @@
class CreateAuthentications < ActiveRecord::Migration
def change
create_table :authentications do |t|
t.integer :member_id, :null => false
t.string :provider, :null => false
t.string :uid
t.string :token
t.string :secret
t.timestamps
end
add_index :authentications, :member_id
end
end

View File

@@ -0,0 +1,5 @@
class AddNameToAuthentications < ActiveRecord::Migration
def change
add_column :authentications, :name, :string
end
end

View File

@@ -11,7 +11,20 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130329045744) do
ActiveRecord::Schema.define(:version => 20130418102558) do
create_table "authentications", :force => true do |t|
t.integer "member_id", :null => false
t.string "provider", :null => false
t.string "uid"
t.string "token"
t.string "secret"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "name"
end
add_index "authentications", ["member_id"], :name => "index_authentications_on_member_id"
create_table "comments", :force => true do |t|
t.integer "post_id", :null => false
@@ -107,6 +120,14 @@ ActiveRecord::Schema.define(:version => 20130329045744) do
t.datetime "updated_at", :null => false
end
create_table "payments", :force => true do |t|
t.integer "payer_id"
t.string "payment_type"
t.decimal "amount"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "plantings", :force => true do |t|
t.integer "garden_id", :null => false
t.integer "crop_id", :null => false

View File

@@ -18,8 +18,8 @@ CSV.foreach(Rails.root.join('db', 'seeds', 'crops.csv')) do |row|
end
puts "Finished loading crops"
puts "Loading test users..."
if Rails.env.development? or Rails.env.test?
puts "Loading test users..."
(1..3).each do |i|
@user = Member.create(
:login_name => "test#{i}",
@@ -30,5 +30,34 @@ if Rails.env.development? or Rails.env.test?
@user.confirm!
@user.save!
end
puts "Finished loading test users"
puts "Creating admin role..."
@admin = Role.create(:name => 'Admin')
puts "Creating crop wrangler role..."
@wrangler = Role.create(:name => 'Crop Wrangler')
puts "Adding admin and crop wrangler members..."
@admin_user = Member.create(
:login_name => "admin1",
:email => "admin1@example.com",
:password => "password1",
:tos_agreement => true
)
@admin_user.confirm!
@admin_user.roles << @admin
@admin_user.save!
@wrangler_user = Member.create(
:login_name => "wrangler1",
:email => "wrangler1@example.com",
:password => "password1",
:tos_agreement => true
)
@wrangler_user.confirm!
@wrangler_user.roles << @wrangler
@wrangler_user.save!
puts "Done!"
end
puts "Finished loading test users"

View File

@@ -0,0 +1,77 @@
require 'spec_helper'
describe AuthenticationsController do
before(:each) do
@member = FactoryGirl.create(:member)
sign_in @member
controller.stub(:current_member) { @member }
@auth = FactoryGirl.create(:authentication, :member => @member)
request.env['omniauth.auth'] = {
'provider' => 'foo',
'uid' => 'bar',
'info' => { 'nickname' => 'blah' },
'credentials' => { 'token' => 'blah', 'secret' => 'blah' }
}
end
describe "GET index" do
it "assigns all authentications as @authentications" do
get :index, {}
assigns(:authentications).should eq([@auth])
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new Authentication" do
expect {
post :create, {:authentication => @auth}
}.to change(Authentication, :count).by(1)
end
it "assigns a newly created authentication as @authentication" do
post :create, {:authentication => @auth}
assigns(:authentication).should be_a(Authentication)
assigns(:authentication).should be_persisted
end
it "redirects to the settings page" do
post :create, {:authentication => @auth }
response.should redirect_to(edit_member_registration_path)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved authentication as @authentication" do
# Trigger the behavior that occurs when invalid params are submitted
Authentication.any_instance.stub(:save).and_return(false)
post :create, {:authentication => { "member_id" => "invalid value" }}
assigns(:authentication).should be_a_new(Authentication)
end
it "redirects to settings page" do
# Trigger the behavior that occurs when invalid params are submitted
Authentication.any_instance.stub(:save).and_return(false)
post :create, {:authentication => { "member_id" => "invalid value" }}
response.should redirect_to(edit_member_registration_path)
end
end
end
describe "DELETE destroy" do
it "destroys the requested authentication" do
authentication = @auth
expect {
delete :destroy, {:id => authentication.to_param}
}.to change(Authentication, :count).by(-1)
end
it "redirects to the settings page" do
authentication = @auth
delete :destroy, {:id => authentication.to_param}
response.should redirect_to(edit_member_registration_path)
end
end
end

View File

@@ -38,6 +38,13 @@ describe CommentsController do
assigns(:post).should eq(post)
end
it "assigns the old comments as @comments" do
post = FactoryGirl.create(:post)
old_comment = FactoryGirl.create(:comment, :post => post)
get :new, {:post_id => post.id}
assigns(:comments).should eq [old_comment]
end
it "dies if no post specified" do
get :new
response.should redirect_to(root_url)
@@ -50,6 +57,15 @@ describe CommentsController do
get :edit, {:id => comment.to_param}
assigns(:comment).should eq(comment)
end
it "assigns previous comments as @comments" do
post = FactoryGirl.create(:post)
old_comment = FactoryGirl.create(:comment, :post => post)
comment = FactoryGirl.create(:comment, :post => post)
get :edit, {:id => comment.to_param}
assigns(:comments).should eq([old_comment, comment])
end
end
describe "POST create" do

View File

@@ -21,7 +21,7 @@ describe HomeController do
end
it 'assigns interesting members' do
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
get :index, {}
assigns(:interesting_members).should eq [@member]
end

View File

@@ -5,6 +5,8 @@ describe MembersController do
before :each do
@member = FactoryGirl.create(:member)
@posts = [ FactoryGirl.create(:post, :author => @member) ]
@twitter_auth = FactoryGirl.create(:authentication, :member => @member)
@flickr_auth = FactoryGirl.create(:flickr_authentication, :member => @member)
end
describe "GET index" do
@@ -37,6 +39,16 @@ describe MembersController do
assigns(:posts).should eq(@posts)
end
it "assigns @twitter_auth" do
get :show, {:id => @member.id}
assigns(:twitter_auth).should eq(@twitter_auth)
end
it "assigns @flickr_auth" do
get :show, {:id => @member.id}
assigns(:flickr_auth).should eq(@flickr_auth)
end
it "doesn't show completely nonsense members" do
lambda { get :show, {:id => 9999} }.should raise_error
end
@@ -59,13 +71,13 @@ describe MembersController do
describe "GET nearby members" do
before(:each) do
@member_near = FactoryGirl.create(:geolocated_member)
@member_far = FactoryGirl.create(:lonely_geolocated_member)
@member_london = FactoryGirl.create(:london_member)
@member_south_pole = FactoryGirl.create(:south_pole_member)
end
context "when the user is logged in and has set their location" do
before(:each) do
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
controller.stub(:current_member) { @member }
end
@@ -76,23 +88,40 @@ describe MembersController do
it "assigns nearby members as nearby" do
get :nearby
assigns(:nearby_members).should include @member_near
assigns(:nearby_members).should include @member_london
end
it "doesn't assign far-off members as nearby" do
get :nearby
assigns(:nearby_members).should_not include @member_far
assigns(:nearby_members).should_not include @member_south_pole
end
it "gets members near the specified location if one is set" do
get :nearby, { :location => @member_far.location }
assigns(:nearby_members).should include @member_far
get :nearby, { :location => @member_south_pole.location }
assigns(:nearby_members).should include @member_south_pole
end
it "does not assign members near current_member if a location is set" do
get :nearby, { :location => @member_far.location }
assigns(:nearby_members).should_not include @member_near
get :nearby, { :location => @member_south_pole.location }
assigns(:nearby_members).should_not include @member_london
end
it "finds faraway members if you increase the distance" do
get :nearby, { :distance => "50000" }
assigns(:nearby_members).should include @member_south_pole
end
# Edinburgh and London are approximately 330mi/530km apart
it "finds London members within 350 miles of Edinburgh" do
get :nearby, { :distance => "350", :units => :mi, :location => "Edinburgh" }
assigns(:nearby_members).should include @member_london
end
it "doesn't find London members within 350 km of Edinburgh" do
get :nearby, { :distance => "350", :units => :km, :location => "Edinburgh" }
assigns(:nearby_members).should_not include @member_london
end
end
context "when the user is logged in but hasn't set their location" do
@@ -107,13 +136,13 @@ describe MembersController do
end
it "assigns nearby members if a location is set" do
get :nearby, { :location => @member_near.location }
assigns(:nearby_members).should include @member_near
get :nearby, { :location => @member_london.location }
assigns(:nearby_members).should include @member_london
end
it "does not assign far members if a location is set" do
get :nearby, { :location => @member_near.location }
assigns(:nearby_members).should_not include @member_far
get :nearby, { :location => @member_london.location }
assigns(:nearby_members).should_not include @member_south_pole
end
end
@@ -130,13 +159,13 @@ describe MembersController do
end
it "assigns nearby members if a location is set" do
get :nearby, { :location => @member_near.location }
assigns(:nearby_members).should include @member_near
get :nearby, { :location => @member_london.location }
assigns(:nearby_members).should include @member_london
end
it "does not assign far members if a location is set" do
get :nearby, { :location => @member_near.location }
assigns(:nearby_members).should_not include @member_far
get :nearby, { :location => @member_london.location }
assigns(:nearby_members).should_not include @member_south_pole
end
end
end

View File

@@ -0,0 +1,31 @@
require 'spec_helper'
describe RegistrationsController do
before :each do
@member = FactoryGirl.create(:member)
sign_in @member
controller.stub(:current_user) { @member }
controller.stub(:devise_mapping).and_return(Devise.mappings[:member])
end
describe "GET edit" do
it "assigns the requested member as @member" do
get :edit
assigns(:member).should eq(@member)
end
it "picks up the twitter auth" do
@auth = FactoryGirl.create(:authentication, :member => @member)
get :edit
assigns(:twitter_auth).should eq @auth
end
it "picks up the flickr auth" do
@auth = FactoryGirl.create(:flickr_authentication, :member => @member)
get :edit
assigns(:flickr_auth).should eq @auth
end
end
end

View File

@@ -0,0 +1,16 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :authentication do
member
provider 'twitter'
uid 'foo'
secret 'bar'
name 'baz'
factory :flickr_authentication do
provider 'flickr'
uid 'blah@blah'
end
end
end

View File

@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :comment do
post
author
body "OMG LOL"
sequence(:body) { |n| "OMG LOL #{n}" } # because our commenters are more
# polite than YouTube's
end
end

View File

@@ -24,7 +24,7 @@ FactoryGirl.define do
show_email true
end
factory :geolocated_member do
factory :london_member do
sequence(:login_name) { |n| "JohnH#{n}" } # for the astronomer who figured out longitude
location 'Greenwich, UK'
# including lat/long explicitly because geocoder doesn't work with FG
@@ -32,7 +32,7 @@ FactoryGirl.define do
longitude 0.004
end
factory :lonely_geolocated_member do
factory :south_pole_member do
sequence(:login_name) { |n| "ScottRF#{n}" }
location 'Amundsen-Scott Base, Antarctica'
latitude -90

View File

@@ -0,0 +1,10 @@
require 'spec_helper'
describe Authentication do
it 'creates an authentication' do
@auth = FactoryGirl.create(:authentication)
@auth.should be_an_instance_of Authentication
@auth.member.should be_an_instance_of Member
end
end

View File

@@ -221,9 +221,9 @@ describe 'member' do
# 1) confirmed
# 2) ordered by the most recent sign in
it 'finds interesting members' do
@member1 = FactoryGirl.create(:geolocated_member)
@member2 = FactoryGirl.create(:geolocated_member)
@member3 = FactoryGirl.create(:geolocated_member)
@member1 = FactoryGirl.create(:london_member)
@member2 = FactoryGirl.create(:london_member)
@member3 = FactoryGirl.create(:london_member)
@member4 = FactoryGirl.create(:unconfirmed_member)
@member1.updated_at = 3.days.ago

View File

@@ -0,0 +1,11 @@
require 'spec_helper'
describe "Authentications" do
describe "GET /authentications" do
it "works! (now write some real specs)" do
# Run the generator again with the --webrat flag if you want to use webrat methods/matchers
get authentications_path
response.status.should be(200)
end
end
end

View File

@@ -0,0 +1,35 @@
require "spec_helper"
describe AuthenticationsController do
describe "routing" do
it "routes to #index" do
get("/authentications").should route_to("authentications#index")
end
it "routes to #new" do
get("/authentications/new").should route_to("authentications#new")
end
it "routes to #show" do
get("/authentications/1").should route_to("authentications#show", :id => "1")
end
it "routes to #edit" do
get("/authentications/1/edit").should route_to("authentications#edit", :id => "1")
end
it "routes to #create" do
post("/authentications").should route_to("authentications#create")
end
it "routes to #update" do
put("/authentications/1").should route_to("authentications#update", :id => "1")
end
it "routes to #destroy" do
delete("/authentications/1").should route_to("authentications#destroy", :id => "1")
end
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe 'about/contact.html.haml', :type => "view" do
before(:each) do
render
end
it 'should show support faq' do
render
rendered.should contain 'General contact email'
end
end

View File

@@ -0,0 +1,28 @@
require 'spec_helper'
describe "authentications/index" do
before(:each) do
assign(:authentications, [
stub_model(Authentication,
:member_id => 1,
:provider => "Provider",
:uid => "Uid",
:name => "Name"
),
stub_model(Authentication,
:member_id => 1,
:provider => "Provider",
:uid => "Uid",
:name => "Name"
)
])
end
it "renders a list of authentications" do
render
# Run the generator again with the --webrat flag if you want to use webrat matchers
assert_select ".authentication", :count => 2
assert_select ".provider", :text => "Provider".to_s, :count => 2
assert_select ".name", :text => "Name".to_s, :count => 2
end
end

View File

@@ -3,20 +3,32 @@ require 'spec_helper'
describe "comments/new" do
before(:each) do
controller.stub(:current_user) { nil }
assign(:comment, FactoryGirl.create(:comment))
@post = FactoryGirl.create(:post)
@previous_comment = FactoryGirl.create(:comment, :post => @post)
assign(:comment, FactoryGirl.create(:comment, :post => @post))
assign(:comments, [@previous_comment])
render
end
it "shows the text of the post under discussion" do
rendered.should contain @post.body
end
it "shows previous comments" do
rendered.should contain @previous_comment.body
end
it "shows the correct comment count" do
rendered.should contain "1 comment"
end
it "renders new comment form" do
render
# Run the generator again with the --webrat flag if you want to use webrat matchers
assert_select "form", :action => comments_path, :method => "post" do
assert_select "textarea#comment_body", :name => "comment[body]"
end
end
it 'shows markdown help' do
render
rendered.should contain 'Markdown'
end

View File

@@ -11,14 +11,18 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do
@view.stub(:resource_name).and_return("member")
@view.stub(:resource_class).and_return(Member)
@view.stub(:devise_mapping).and_return(Devise.mappings[:member])
render
end
it 'should have some fields' do
rendered.should contain 'Email'
render
rendered.should contain 'Email'
end
context 'email section' do
before(:each) do
render
end
it 'has a heading' do
assert_select "h2", "Email settings"
end
@@ -29,6 +33,10 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do
end
context 'profile section' do
before(:each) do
render
end
it 'has a heading' do
assert_select "h2", "Profile details"
end
@@ -46,7 +54,56 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do
end
end
context 'other sites section' do
it 'has a heading' do
render
assert_select "h2", "Linked accounts"
end
context 'not connected to twitter' do
it 'has a link to connect' do
render
assert_select "a", "Connect to Twitter"
end
end
context 'connected to twitter' do
before(:each) do
@twitter_auth = FactoryGirl.create(:authentication, :member => @member)
render
end
it 'has a link to twitter profile' do
assert_select "a", :href => "http://twitter.com/#{@twitter_auth.name}"
end
it 'has a link to disconnect' do
render
assert_select "a", :href => @twitter_auth, :text => "Disconnect"
end
end
context 'not connected to flickr' do
it 'has a link to connect' do
render
assert_select "a", "Connect to Flickr"
end
end
context 'connected to flickr' do
before(:each) do
@flickr_auth = FactoryGirl.create(:flickr_authentication, :member => @member)
render
end
it 'has a link to flickr photostream' do
assert_select "a", :href => "http://flickr.com/photos/#{@flickr_auth.uid}"
end
it 'has a link to disconnect' do
render
assert_select "a", :href => @flickr_auth, :text => "Disconnect"
end
end
end
it 'should have a password section' do
render
assert_select "h2", "Change password"
end

View File

@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'home/index.html.haml', :type => "view" do
context 'logged out' do
before(:each) do
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
@member.updated_at = 2.days.ago
@post = FactoryGirl.create(:post, :author => @member)
@planting = FactoryGirl.create(:planting, :garden => @member.gardens.first)
@@ -35,7 +35,7 @@ describe 'home/index.html.haml', :type => "view" do
context 'logged in' do
before(:each) do
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
controller.stub(:current_user) { @member }
sign_in @member
assign(:member, @member)

View File

@@ -8,6 +8,7 @@ describe 'layouts/_footer.html.haml', :type => "view" do
it 'should have links in footer' do
rendered.should contain 'About'
rendered.should contain 'Contact'
assert_select("a[href=/policy/tos]", 'Terms of Service')
assert_select("a[href=/policy/privacy]", 'Privacy Policy')
assert_select("a[href=/policy/community]", 'Community Guidelines')

View File

@@ -6,7 +6,7 @@ describe "members/index" do
page = 1
per_page = 2
total_entries = 2
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
members = WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
pager.replace([ @member, @member ])
end

View File

@@ -2,7 +2,7 @@ require 'spec_helper'
describe "members/nearby" do
before(:each) do
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
@nearby_members = [FactoryGirl.create(:member)]
end
@@ -18,6 +18,16 @@ describe "members/nearby" do
assert_select "#location", :value => @location
end
it "shows the default distance in the textbox" do
assert_select "#distance", :value => "100"
end
it "shows a dropdown with miles and km" do
assert_select "select#units"
assert_select "select#units option[value=km]"
assert_select "select#units option[value=mi]"
end
it "shows the names of nearby members" do
@nearby_members.each do |m|
rendered.should contain m.login_name

View File

@@ -23,6 +23,38 @@ describe "members/show" do
end
end
context 'twitter' do
context "no twitter" do
it "doesn't show twitter link" do
render
assert_select "a[href^=http://twitter.com/]", :count => 0
end
end
context 'has twitter' do
it "shows twitter link" do
@twitter_auth = FactoryGirl.create(:authentication, :member => @member)
render
assert_select "a", :href => "http://twitter.com/#{@twitter_auth.name}"
end
end
end
context 'flickr' do
context "no flickr" do
it "doesn't show flickr link" do
render
assert_select "a[href^=http://flickr.com/]", :count => 0
end
end
context 'has flickr' do
it "shows flickr link" do
@flickr_auth = FactoryGirl.create(:flickr_authentication, :member => @member)
render
assert_select "a", :href => "http://flickr.com/photos/#{@flickr_auth.uid}"
end
end
end
context "gardens and plantings" do
before(:each) do
@planting = FactoryGirl.create(:planting, :garden => @garden)
@@ -59,7 +91,7 @@ describe "members/show" do
it "does not contain a 'New Garden' tab" do
assert_select "#garden_new", false
end
end
end
context "signed in member" do
@@ -93,11 +125,11 @@ describe "members/show" do
it "does not contain a 'New Garden' link" do
assert_select "a[href=#garden_new]", false
end
it "does not contain a 'New Garden' tab" do
assert_select "#garden_new", false
end
end
it "contains no edit settings button" do
rendered.should_not contain "Edit Settings"
end
@@ -124,7 +156,7 @@ describe "members/show" do
context "geolocations" do
before(:each) do
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
render
end
it "shows the location" do

View File

@@ -57,7 +57,7 @@ describe "plantings/show" do
context "location set" do
before(:each) do
controller.stub(:current_user) { nil }
@member = FactoryGirl.create(:geolocated_member)
@member = FactoryGirl.create(:london_member)
create_planting_for(@member)
render
end

View File

@@ -107,7 +107,4 @@ describe "posts/show" do
end
end

View File

@@ -6,7 +6,11 @@ describe 'support/index.html.haml', :type => "view" do
end
it 'should show support faq' do
render
rendered.should contain 'About Growstuff'
end
it 'should not mention Courtney any more' do
rendered.should_not contain 'Courtney'
rendered.should_not contain 'phazel'
end
end