mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-04-13 11:30:54 -04:00
36
Gemfile
36
Gemfile
@@ -106,7 +106,7 @@ gem 'rake', '>= 10.0.0'
|
||||
gem "responders"
|
||||
|
||||
# allows soft delete. Used for members.
|
||||
gem "paranoia", "~> 2.2"
|
||||
gem 'discard', '~> 1.0'
|
||||
|
||||
gem 'xmlrpc' # fixes rake error - can be removed if not needed later
|
||||
|
||||
@@ -115,6 +115,9 @@ gem 'puma'
|
||||
gem 'loofah', '>= 2.2.1'
|
||||
gem 'rack-protection', '>= 2.0.1'
|
||||
|
||||
# Member to member messaging system
|
||||
gem 'mailboxer'
|
||||
|
||||
group :production do
|
||||
gem 'bonsai-elasticsearch-rails' # Integration with Bonsa-Elasticsearch on heroku
|
||||
gem 'dalli'
|
||||
@@ -132,25 +135,28 @@ group :development do
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
gem 'bullet' # performance tuning by finding unnecesary queries
|
||||
gem 'byebug' # debugging
|
||||
gem 'capybara' # integration tests
|
||||
gem 'capybara-email' # integration tests for email
|
||||
gem 'capybara-screenshot' # for test debugging
|
||||
gem 'coveralls', require: false # coverage analysis
|
||||
gem 'bullet' # performance tuning by finding unnecesary queries
|
||||
gem 'byebug' # debugging
|
||||
gem 'capybara' # integration tests
|
||||
gem 'capybara-email' # integration tests for email
|
||||
gem 'capybara-screenshot' # for test debugging
|
||||
gem 'database_cleaner'
|
||||
gem 'factory_bot_rails' # for creating test data
|
||||
gem 'factory_bot_rails' # for creating test data
|
||||
gem 'faker'
|
||||
gem 'haml-i18n-extractor'
|
||||
gem 'haml-rails' # HTML templating language
|
||||
gem 'haml_lint', '>= 0.25.1' # Checks haml files for goodness
|
||||
gem 'i18n-tasks' # adds tests for finding missing and unused translations
|
||||
gem 'haml-rails' # HTML templating language
|
||||
gem 'rspec-activemodel-mocks'
|
||||
gem 'rspec-rails' # unit testing framework
|
||||
gem 'rubocop', '~> 0.71'
|
||||
gem 'rspec-rails' # unit testing framework
|
||||
gem 'rubocop-rails'
|
||||
gem 'rubocop-rspec'
|
||||
gem 'webrat' # provides HTML matchers for view tests
|
||||
gem 'webrat' # provides HTML matchers for view tests
|
||||
|
||||
# cli utils
|
||||
gem 'coveralls', require: false # coverage analysis
|
||||
gem 'haml-i18n-extractor', require: false
|
||||
gem 'haml_lint', '>= 0.25.1', require: false # Checks haml files for goodness
|
||||
gem 'i18n-tasks', require: false # adds tests for finding missing and unused translations
|
||||
gem 'rspectre', require: false # finds unused code in specs
|
||||
gem 'rubocop', '~> 0.71', require: false
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
||||
65
Gemfile.lock
65
Gemfile.lock
@@ -2,6 +2,7 @@ GEM
|
||||
remote: https://rubygems.org/
|
||||
remote: https://rails-assets.org/
|
||||
specs:
|
||||
abstract_type (0.0.7)
|
||||
actioncable (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
nio4r (~> 2.0)
|
||||
@@ -53,8 +54,15 @@ GEM
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
adamantium (0.2.0)
|
||||
ice_nine (~> 0.11.0)
|
||||
memoizable (~> 0.4.0)
|
||||
addressable (2.6.0)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
anima (0.3.1)
|
||||
abstract_type (~> 0.0.7)
|
||||
adamantium (~> 0.2)
|
||||
equalizer (~> 0.0.11)
|
||||
arel (9.0.0)
|
||||
ast (2.4.0)
|
||||
autoprefixer-rails (9.6.0)
|
||||
@@ -100,6 +108,10 @@ GEM
|
||||
capybara-screenshot (1.0.23)
|
||||
capybara (>= 1.0, < 4)
|
||||
launchy
|
||||
carrierwave (1.3.1)
|
||||
activemodel (>= 4.0.0)
|
||||
activesupport (>= 4.0.0)
|
||||
mime-types (>= 1.16)
|
||||
chartkick (3.2.1)
|
||||
childprocess (1.0.1)
|
||||
rake (< 13.0)
|
||||
@@ -126,6 +138,9 @@ GEM
|
||||
sassc-rails (>= 2.0.0)
|
||||
comfy_bootstrap_form (4.0.6)
|
||||
rails (>= 5.0.0)
|
||||
concord (0.1.5)
|
||||
adamantium (~> 0.2.0)
|
||||
equalizer (~> 0.0.9)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
coveralls (0.8.19)
|
||||
@@ -139,13 +154,15 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
dalli (2.7.10)
|
||||
database_cleaner (1.7.0)
|
||||
devise (4.6.2)
|
||||
devise (4.7.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 6.0)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.3)
|
||||
discard (1.1.0)
|
||||
activerecord (>= 4.2, < 7)
|
||||
docile (1.1.5)
|
||||
elasticsearch (6.8.0)
|
||||
elasticsearch-api (= 6.8.0)
|
||||
@@ -160,6 +177,7 @@ GEM
|
||||
elasticsearch-transport (6.8.0)
|
||||
faraday
|
||||
multi_json
|
||||
equalizer (0.0.11)
|
||||
erubi (1.8.0)
|
||||
erubis (2.7.0)
|
||||
excon (0.64.0)
|
||||
@@ -205,7 +223,7 @@ GEM
|
||||
haml (>= 4.0.6, < 6.0)
|
||||
html2haml (>= 1.0.1)
|
||||
railties (>= 5.1)
|
||||
haml_lint (0.32.0)
|
||||
haml_lint (0.33.0)
|
||||
haml (>= 4.0, < 5.2)
|
||||
rainbow
|
||||
rake (>= 10, < 13)
|
||||
@@ -238,6 +256,7 @@ GEM
|
||||
rails-i18n
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
terminal-table (>= 1.5.1)
|
||||
ice_nine (0.11.2)
|
||||
jaro_winkler (1.5.3)
|
||||
jquery-rails (4.3.5)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
@@ -283,6 +302,9 @@ GEM
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
mailboxer (0.15.1)
|
||||
carrierwave (>= 0.5.8)
|
||||
rails (>= 5.0.0)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
material-sass (4.1.1)
|
||||
@@ -291,6 +313,8 @@ GEM
|
||||
material_icons (2.2.1)
|
||||
railties (>= 3.2)
|
||||
memcachier (0.0.2)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.9.2)
|
||||
mime-types (3.2.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
@@ -315,7 +339,7 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.8.1)
|
||||
oj (3.9.0)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
@@ -335,8 +359,6 @@ GEM
|
||||
rack
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.17.0)
|
||||
paranoia (2.4.2)
|
||||
activerecord (>= 4.0, < 6.1)
|
||||
parser (2.6.3.0)
|
||||
ast (~> 2.4.0)
|
||||
percy-capybara (4.0.2)
|
||||
@@ -345,6 +367,7 @@ GEM
|
||||
heroics (~> 0.0.25)
|
||||
moneta (~> 1.0.0)
|
||||
popper_js (1.14.5)
|
||||
procto (0.0.3)
|
||||
public_suffix (3.1.1)
|
||||
puma (4.1.0)
|
||||
nio4r (~> 2.0)
|
||||
@@ -376,7 +399,7 @@ GEM
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.1.0)
|
||||
rails-html-sanitizer (1.2.0)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-i18n (5.1.3)
|
||||
i18n (>= 0.7, < 2)
|
||||
@@ -403,6 +426,10 @@ GEM
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rspec (3.8.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-activemodel-mocks (1.1.0)
|
||||
activemodel (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
@@ -424,6 +451,12 @@ GEM
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-support (3.8.2)
|
||||
rspectre (0.0.1)
|
||||
anima (~> 0.3)
|
||||
concord (~> 0.1)
|
||||
parser (~> 2.3)
|
||||
rspec (~> 3.0)
|
||||
unparser (~> 0.2)
|
||||
rubocop (0.74.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
@@ -447,8 +480,8 @@ GEM
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sass-rails (5.0.7)
|
||||
railties (>= 4.0.0, < 6)
|
||||
sass-rails (5.1.0)
|
||||
railties (>= 5.2.0)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
@@ -509,6 +542,14 @@ GEM
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.12.1)
|
||||
unparser (0.4.5)
|
||||
abstract_type (~> 0.0.7)
|
||||
adamantium (~> 0.2.0)
|
||||
concord (~> 0.1.5)
|
||||
diff-lcs (~> 1.3)
|
||||
equalizer (~> 0.0.9)
|
||||
parser (~> 2.6.3)
|
||||
procto (~> 0.0.2)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
webdrivers (4.1.2)
|
||||
@@ -522,7 +563,7 @@ GEM
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
will_paginate (3.1.7)
|
||||
will_paginate (3.1.8)
|
||||
will_paginate-bootstrap4 (0.2.2)
|
||||
will_paginate (~> 3.0, >= 3.0.0)
|
||||
xmlrpc (0.3.0)
|
||||
@@ -559,6 +600,7 @@ DEPENDENCIES
|
||||
dalli
|
||||
database_cleaner
|
||||
devise
|
||||
discard (~> 1.0)
|
||||
elasticsearch (< 7.0.0)
|
||||
factory_bot_rails
|
||||
faker
|
||||
@@ -584,6 +626,7 @@ DEPENDENCIES
|
||||
letter_opener
|
||||
listen
|
||||
loofah (>= 2.2.1)
|
||||
mailboxer
|
||||
material-sass (= 4.1.1)
|
||||
material_icons
|
||||
memcachier
|
||||
@@ -593,7 +636,6 @@ DEPENDENCIES
|
||||
omniauth-facebook
|
||||
omniauth-flickr (>= 0.0.15)
|
||||
omniauth-twitter
|
||||
paranoia (~> 2.2)
|
||||
percy-capybara (~> 4.0.0)
|
||||
pg (< 1.0.0)
|
||||
platform-api
|
||||
@@ -607,6 +649,7 @@ DEPENDENCIES
|
||||
responders
|
||||
rspec-activemodel-mocks
|
||||
rspec-rails
|
||||
rspectre
|
||||
rubocop (~> 0.71)
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
|
||||
@@ -15,3 +15,26 @@
|
||||
padding-left: 1em;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
|
||||
.member-chip {
|
||||
background-color: lighten($green, 30%);
|
||||
border-radius: 25px;
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 25px;
|
||||
|
||||
a {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
float: left;
|
||||
height: 30px;
|
||||
margin: 0 10px 0 -25px;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,23 @@ module Admin
|
||||
before_action :auth!
|
||||
|
||||
def index
|
||||
@members = Member.order(:login_name).paginate(page: params[:page])
|
||||
@members = Member.all
|
||||
@members = @members.where("login_name ILIKE ?", "%#{search_term}%") unless search_term.nil?
|
||||
@members = @members.order(:login_name).paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def destroy
|
||||
@member = Member.find_by!(slug: params[:slug])
|
||||
@member.destroy
|
||||
@member.discard
|
||||
redirect_to admin_members_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_term
|
||||
params[:q]
|
||||
end
|
||||
|
||||
def auth!
|
||||
authorize! :manage, :all
|
||||
end
|
||||
|
||||
62
app/controllers/conversations_controller.rb
Normal file
62
app/controllers/conversations_controller.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
class ConversationsController < ApplicationController
|
||||
respond_to :html
|
||||
before_action :authenticate_member!
|
||||
before_action :set_box
|
||||
before_action :check_current_subject_in_conversation, only: %i(show update destroy)
|
||||
|
||||
def index
|
||||
@conversations = if @box.eql? "inbox"
|
||||
mailbox.inbox
|
||||
elsif @box.eql? "sent"
|
||||
mailbox.sentbox
|
||||
else
|
||||
mailbox.trash
|
||||
end.paginate(page: params[:page])
|
||||
|
||||
respond_with @conversations
|
||||
end
|
||||
|
||||
def show
|
||||
@receipts = mailbox.receipts_for(@conversation)
|
||||
@receipts.mark_as_read
|
||||
@participants = @conversation.participants
|
||||
end
|
||||
|
||||
def update
|
||||
@conversation.untrash(current_member)
|
||||
redirect_to conversations_path(box: params[:box])
|
||||
end
|
||||
|
||||
def destroy
|
||||
@conversation = Mailboxer::Conversation.find(params[:id])
|
||||
@conversation.move_to_trash(current_member)
|
||||
redirect_to conversations_path(box: params[:box])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mailbox
|
||||
current_member.mailbox
|
||||
end
|
||||
|
||||
def set_box
|
||||
@boxes = {
|
||||
'inbox' => { 'total' => mailbox.inbox.size, 'unread' => current_member.receipts.where(is_read: false).count },
|
||||
'sent' => { 'total' => mailbox.sentbox.size, 'unread' => 0 },
|
||||
'trash' => { 'total' => mailbox.trash.size, 'unread' => 0 }
|
||||
}
|
||||
@box = if params[:box].blank? || !@boxes.keys.include?(params[:box])
|
||||
'inbox'
|
||||
else
|
||||
params[:box]
|
||||
end
|
||||
end
|
||||
|
||||
def check_current_subject_in_conversation
|
||||
@conversation = Mailboxer::Conversation.find_by(id: params[:id])
|
||||
return unless @conversation.nil? || !@conversation.is_participant?(current_member)
|
||||
|
||||
redirect_to conversations_path(box: box)
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -139,7 +139,7 @@ class CropsController < ApplicationController
|
||||
def notify_wranglers
|
||||
return if current_member.role? :crop_wrangler
|
||||
|
||||
Role.crop_wranglers.each do |w|
|
||||
Role.crop_wranglers&.each do |w|
|
||||
Notifier.new_crop_request(w, @crop).deliver_now!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,6 @@ class GardensController < ApplicationController
|
||||
@gardens = @gardens.active unless @show_all
|
||||
@gardens = @gardens.where(owner: @owner) if @owner.present?
|
||||
@gardens = @gardens.joins(:owner).order(:name).paginate(page: params[:page])
|
||||
@gardens = @gardens.includes(:owner, plantings: [:owner, crop: :parent])
|
||||
respond_with(@gardens)
|
||||
end
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class MembersController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@member = Member.confirmed.find_by!(slug: params[:slug])
|
||||
@member = Member.confirmed.kept.find_by!(slug: params[:slug])
|
||||
@twitter_auth = @member.auth('twitter')
|
||||
@flickr_auth = @member.auth('flickr')
|
||||
@facebook_auth = @member.auth('facebook')
|
||||
@@ -89,6 +89,6 @@ class MembersController < ApplicationController
|
||||
Member.recently_joined
|
||||
else
|
||||
Member.order(:login_name)
|
||||
end.confirmed.paginate(page: params[:page])
|
||||
end.kept.confirmed.paginate(page: params[:page])
|
||||
end
|
||||
end
|
||||
|
||||
38
app/controllers/messages_controller.rb
Normal file
38
app/controllers/messages_controller.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
class MessagesController < ApplicationController
|
||||
respond_to :html, :json
|
||||
before_action :authenticate_member!
|
||||
def index
|
||||
redirect_to conversations_path(box: @box)
|
||||
end
|
||||
|
||||
def show
|
||||
if (@message = Message.find_by(id: params[:id])) && (@conversation = @message.conversation)
|
||||
if @conversation.is_participant?(current_member)
|
||||
redirect_to conversation_path(@conversation, box: @box, anchor: "message_" + @message.id.to_s)
|
||||
return
|
||||
end
|
||||
end
|
||||
redirect_to conversations_path(box: @box)
|
||||
end
|
||||
|
||||
def new
|
||||
return if params[:recipient_id].blank?
|
||||
|
||||
@recipient = Member.find_by(id: params[:recipient_id])
|
||||
return if @recipient.nil?
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:conversation_id].present?
|
||||
@conversation = Mailboxer::Conversation.find(params[:conversation_id])
|
||||
current_member.reply_to_conversation(@conversation, params[:body])
|
||||
redirect_to conversation_path(@conversation)
|
||||
else
|
||||
recipient = Member.find(params[:recipient_id])
|
||||
body = params[:body]
|
||||
subject = params[:subject]
|
||||
@conversation = current_member.send_message(recipient, body, subject)
|
||||
redirect_to conversations_path(box: 'sentbox')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,65 +0,0 @@
|
||||
class NotificationsController < ApplicationController
|
||||
include NotificationsHelper
|
||||
before_action :authenticate_member!
|
||||
load_and_authorize_resource
|
||||
respond_to :html
|
||||
|
||||
# GET /notifications
|
||||
def index
|
||||
@notifications = Notification.by_recipient(current_member).order(created_at: :desc).paginate(page: params[:page], per_page: 30)
|
||||
end
|
||||
|
||||
# GET /notifications/1
|
||||
def show
|
||||
@notification.read = true
|
||||
@notification.save
|
||||
@reply_link = reply_link(@notification)
|
||||
end
|
||||
|
||||
# GET /notifications/new
|
||||
|
||||
def new
|
||||
@notification = Notification.new
|
||||
@recipient = Member.find_by(id: params[:recipient_id])
|
||||
@subject = params[:subject] || ""
|
||||
end
|
||||
|
||||
# GET /notifications/1/reply
|
||||
def reply
|
||||
@notification = Notification.new
|
||||
@sender_notification = Notification.find_by!(id: params[:notification_id], recipient: current_member)
|
||||
@sender_notification.read = true
|
||||
@sender_notification.save
|
||||
@recipient = @sender_notification.sender
|
||||
@subject = if @sender_notification.subject.start_with? 'Re: '
|
||||
@sender_notification.subject
|
||||
else
|
||||
"Re: #{@sender_notification.subject}"
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /notifications/1
|
||||
def destroy
|
||||
@notification.destroy
|
||||
redirect_to notifications_url
|
||||
end
|
||||
|
||||
# POST /notifications
|
||||
def create
|
||||
params[:notification][:sender_id] = current_member.id
|
||||
@notification = Notification.new(notification_params)
|
||||
@recipient = Member.find_by(id: params[:notification][:recipient_id])
|
||||
|
||||
if @notification.save
|
||||
redirect_to notifications_path, notice: 'Message was successfully sent.'
|
||||
else
|
||||
render action: "new"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notification_params
|
||||
params.require(:notification).permit(:sender_id, :recipient_id, :subject, :body, :post_id, :read)
|
||||
end
|
||||
end
|
||||
@@ -31,12 +31,7 @@ class PlantingsController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@planting = Planting.includes(:owner, :crop, :garden, :photos)
|
||||
.friendly
|
||||
.find(params[:id])
|
||||
@photos = @planting.photos
|
||||
.includes(:owner)
|
||||
.order(date_taken: :desc)
|
||||
@photos = @planting.photos.includes(:owner).order(date_taken: :desc)
|
||||
respond_with @planting
|
||||
end
|
||||
|
||||
|
||||
@@ -37,9 +37,11 @@ class RegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @member.destroy_with_password(params.require(:member)[:current_password])
|
||||
if @member.valid_password?(params.require(:member)[:current_password])
|
||||
@member.discard
|
||||
redirect_to root_path
|
||||
else
|
||||
@member.errors.add(:current_password, 'Incorrect password')
|
||||
render "edit"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module NotificationsHelper
|
||||
def reply_link(notification)
|
||||
if notification.post
|
||||
# comment on the post in question
|
||||
new_comment_url(post_id: notification.post.id)
|
||||
else
|
||||
# by default, reply link sends a PM in return
|
||||
notification_reply_url(notification)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class Notifier < ApplicationMailer
|
||||
include NotificationsHelper
|
||||
# include NotificationsHelper
|
||||
default from: "Growstuff <#{ENV['GROWSTUFF_EMAIL']}>"
|
||||
|
||||
def verifier
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Comment < ApplicationRecord
|
||||
belongs_to :author, -> { with_deleted }, class_name: 'Member', inverse_of: :comments
|
||||
belongs_to :author, class_name: 'Member', inverse_of: :comments
|
||||
belongs_to :post
|
||||
|
||||
scope :post_order, -> { reorder("created_at ASC") } # for display on post page
|
||||
|
||||
51
app/models/concerns/member_flickr.rb
Normal file
51
app/models/concerns/member_flickr.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
module MemberFlickr
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do # rubocop:disable Metrics/BlockLength
|
||||
# Authenticates against Flickr and returns an object we can use for subsequent api calls
|
||||
def flickr
|
||||
if @flickr.nil?
|
||||
flickr_auth = auth('flickr')
|
||||
if flickr_auth
|
||||
FlickRaw.api_key = ENV['GROWSTUFF_FLICKR_KEY']
|
||||
FlickRaw.shared_secret = ENV['GROWSTUFF_FLICKR_SECRET']
|
||||
@flickr = FlickRaw::Flickr.new
|
||||
@flickr.access_token = flickr_auth.token
|
||||
@flickr.access_secret = flickr_auth.secret
|
||||
end
|
||||
end
|
||||
@flickr
|
||||
end
|
||||
|
||||
# Fetches a collection of photos from Flickr
|
||||
# Returns a [[page of photos], total] pair.
|
||||
# Total is needed for pagination.
|
||||
def flickr_photos(page_num = 1, set = nil)
|
||||
result = if set
|
||||
flickr.photosets.getPhotos(
|
||||
photoset_id: set,
|
||||
page: page_num,
|
||||
per_page: 30
|
||||
)
|
||||
else
|
||||
flickr.people.getPhotos(
|
||||
user_id: 'me',
|
||||
page: page_num,
|
||||
per_page: 30
|
||||
)
|
||||
end
|
||||
return [result.photo, result.total] if result
|
||||
|
||||
[[], 0]
|
||||
end
|
||||
|
||||
# Returns a hash of Flickr photosets' ids and titles
|
||||
def flickr_sets
|
||||
sets = {}
|
||||
flickr.photosets.getList.each do |p|
|
||||
sets[p.title] = p.id
|
||||
end
|
||||
sets
|
||||
end
|
||||
end
|
||||
end
|
||||
46
app/models/concerns/member_newsletter.rb
Normal file
46
app/models/concerns/member_newsletter.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
module MemberNewsletter
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do # rubocop:disable Metrics/BlockLength
|
||||
after_save :update_newsletter_subscription
|
||||
before_destroy :newsletter_unsubscribe
|
||||
|
||||
scope :wants_newsletter, -> { where(newsletter: true) }
|
||||
|
||||
def update_newsletter_subscription
|
||||
return unless will_save_change_to_attribute?(:confirmed) || will_save_change_to_attribute?(:newsletter)
|
||||
|
||||
if newsletter
|
||||
newsletter_subscribe if confirmed_just_now? || requested_newsletter_just_now?
|
||||
elsif confirmed_at
|
||||
newsletter_unsubscribe
|
||||
end
|
||||
end
|
||||
|
||||
def confirmed_just_now?
|
||||
will_save_change_to_attribute?(:confirmed_at)
|
||||
end
|
||||
|
||||
def requested_newsletter_just_now?
|
||||
confirmed_at && will_save_change_to_attribute?(:newsletter)
|
||||
end
|
||||
|
||||
def newsletter_subscribe(gibbon = Gibbon::API.new, testing = false)
|
||||
return true if Rails.env.test? && !testing
|
||||
|
||||
gibbon.lists.subscribe(
|
||||
id: Rails.application.config.newsletter_list_id,
|
||||
email: { email: email },
|
||||
merge_vars: { login_name: login_name },
|
||||
double_optin: false # they already confirmed their email with us
|
||||
)
|
||||
end
|
||||
|
||||
def newsletter_unsubscribe(gibbon = Gibbon::API.new, testing = false)
|
||||
return true if Rails.env.test? && !testing
|
||||
|
||||
gibbon.lists.unsubscribe(id: Rails.application.config.newsletter_list_id,
|
||||
email: { email: email })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,5 +4,7 @@ module Ownable
|
||||
included do
|
||||
belongs_to :owner, class_name: 'Member', # rubocop:disable Rails/InverseOf
|
||||
foreign_key: 'owner_id', counter_cache: true
|
||||
|
||||
default_scope { joins(:owner).merge(Member.kept) } # Ensures the owner still exists
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ class Crop < ApplicationRecord
|
||||
has_many :harvests, dependent: :destroy
|
||||
has_many :photo_associations, dependent: :destroy
|
||||
has_many :photos, through: :photo_associations
|
||||
has_many :plant_parts, -> { distinct.order("plant_parts.name") }, through: :harvests
|
||||
has_many :plant_parts, -> { joins_members.distinct.order("plant_parts.name") }, through: :harvests
|
||||
belongs_to :creator, class_name: 'Member', optional: true, inverse_of: :created_crops
|
||||
belongs_to :requester, class_name: 'Member', optional: true, inverse_of: :requested_crops
|
||||
belongs_to :parent, class_name: 'Crop', optional: true, inverse_of: :varieties
|
||||
@@ -34,6 +34,7 @@ class Crop < ApplicationRecord
|
||||
scope :rejected, -> { where(approval_status: "rejected") }
|
||||
scope :interesting, -> { approved.has_photos }
|
||||
scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) }
|
||||
scope :joins_members, -> { joins("INNER JOIN members ON members.id = harvests.owner_id") }
|
||||
|
||||
# Special scope to control if it's in the search index
|
||||
scope :search_import, -> { approved }
|
||||
|
||||
@@ -16,7 +16,6 @@ class Garden < ApplicationRecord
|
||||
after_validation :empty_unwanted_geocodes
|
||||
after_save :mark_inactive_garden_plantings_as_finished
|
||||
|
||||
default_scope { joins(:owner) } # Ensures owner exists
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :inactive, -> { where(active: false) }
|
||||
scope :order_by_name, -> { order(Arel.sql("lower(name) asc")) }
|
||||
@@ -59,22 +58,6 @@ class Garden < ApplicationRecord
|
||||
"#{owner.login_name}-#{name}".downcase.tr(' ', '-')
|
||||
end
|
||||
|
||||
# featured plantings returns the most recent 4 plantings for a garden,
|
||||
# choosing them so that no crop is repeated.
|
||||
def featured_plantings
|
||||
unique_plantings = []
|
||||
seen_crops = []
|
||||
|
||||
plantings.includes(:garden, :crop, :owner, :harvests).order(created_at: :desc).each do |p|
|
||||
unless seen_crops.include?(p.crop)
|
||||
unique_plantings.push(p)
|
||||
seen_crops.push(p.crop)
|
||||
end
|
||||
end
|
||||
|
||||
unique_plantings[0..3]
|
||||
end
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
@@ -39,7 +39,6 @@ class Harvest < ApplicationRecord
|
||||
|
||||
##
|
||||
## Scopes
|
||||
default_scope { joins(:owner) } # Ensures owner exists
|
||||
scope :interesting, -> { has_photos.one_per_owner }
|
||||
scope :recent, -> { order(created_at: :desc) }
|
||||
scope :one_per_owner, lambda {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
class Member < ApplicationRecord
|
||||
acts_as_paranoid # implements soft deletion
|
||||
before_destroy :newsletter_unsubscribe
|
||||
include Discard::Model
|
||||
acts_as_messageable # messages can be sent to this model
|
||||
include Geocodable
|
||||
extend FriendlyId
|
||||
include MemberFlickr
|
||||
include MemberNewsletter
|
||||
|
||||
extend FriendlyId
|
||||
friendly_id :login_name, use: %i(slugged finders)
|
||||
|
||||
#
|
||||
@@ -46,7 +48,6 @@ class Member < ApplicationRecord
|
||||
scope :located, -> { where.not(location: '').where.not(latitude: nil).where.not(longitude: nil) }
|
||||
scope :recently_signed_in, -> { reorder(updated_at: :desc) }
|
||||
scope :recently_joined, -> { reorder(confirmed_at: :desc) }
|
||||
scope :wants_newsletter, -> { where(newsletter: true) }
|
||||
scope :interesting, -> { confirmed.located.recently_signed_in.has_plantings }
|
||||
scope :has_plantings, -> { joins(:plantings).group("members.id") }
|
||||
|
||||
@@ -57,6 +58,11 @@ class Member < ApplicationRecord
|
||||
:recoverable, :rememberable, :trackable, :validatable,
|
||||
:confirmable, :lockable, :timeoutable, :omniauthable
|
||||
|
||||
# discarded (deleted) member cannot log in
|
||||
def active_for_authentication?
|
||||
super && !discarded?
|
||||
end
|
||||
|
||||
# set up geocoding
|
||||
geocoded_by :location
|
||||
|
||||
@@ -86,7 +92,6 @@ class Member < ApplicationRecord
|
||||
# Triggers
|
||||
after_validation :geocode
|
||||
after_validation :empty_unwanted_geocodes
|
||||
after_save :update_newsletter_subscription
|
||||
|
||||
# Give each new member a default garden
|
||||
# we use find_or_create to avoid accidentally creating a second one,
|
||||
@@ -103,13 +108,17 @@ class Member < ApplicationRecord
|
||||
end
|
||||
|
||||
def to_s
|
||||
login_name
|
||||
discarded? ? 'deleted' : login_name
|
||||
end
|
||||
|
||||
def to_param
|
||||
slug
|
||||
end
|
||||
|
||||
def mailboxer_email(_messageable)
|
||||
send_notification_email ? email : false
|
||||
end
|
||||
|
||||
def role?(role_sym)
|
||||
roles.any? { |r| r.name.gsub(/\s+/, "_").underscore.to_sym == role_sym }
|
||||
end
|
||||
@@ -118,50 +127,8 @@ class Member < ApplicationRecord
|
||||
authentications.find_by(provider: provider)
|
||||
end
|
||||
|
||||
# Authenticates against Flickr and returns an object we can use for subsequent api calls
|
||||
def flickr
|
||||
if @flickr.nil?
|
||||
flickr_auth = auth('flickr')
|
||||
if flickr_auth
|
||||
FlickRaw.api_key = ENV['GROWSTUFF_FLICKR_KEY']
|
||||
FlickRaw.shared_secret = ENV['GROWSTUFF_FLICKR_SECRET']
|
||||
@flickr = FlickRaw::Flickr.new
|
||||
@flickr.access_token = flickr_auth.token
|
||||
@flickr.access_secret = flickr_auth.secret
|
||||
end
|
||||
end
|
||||
@flickr
|
||||
end
|
||||
|
||||
# Fetches a collection of photos from Flickr
|
||||
# Returns a [[page of photos], total] pair.
|
||||
# Total is needed for pagination.
|
||||
def flickr_photos(page_num = 1, set = nil)
|
||||
result = if set
|
||||
flickr.photosets.getPhotos(
|
||||
photoset_id: set,
|
||||
page: page_num,
|
||||
per_page: 30
|
||||
)
|
||||
else
|
||||
flickr.people.getPhotos(
|
||||
user_id: 'me',
|
||||
page: page_num,
|
||||
per_page: 30
|
||||
)
|
||||
end
|
||||
return [result.photo, result.total] if result
|
||||
|
||||
[[], 0]
|
||||
end
|
||||
|
||||
# Returns a hash of Flickr photosets' ids and titles
|
||||
def flickr_sets
|
||||
sets = {}
|
||||
flickr.photosets.getList.each do |p|
|
||||
sets[p.title] = p.id
|
||||
end
|
||||
sets
|
||||
def unread_count
|
||||
receipts.where(is_read: false).count
|
||||
end
|
||||
|
||||
def self.login_name_or_email(login)
|
||||
@@ -181,42 +148,6 @@ class Member < ApplicationRecord
|
||||
nearby_members
|
||||
end
|
||||
|
||||
def update_newsletter_subscription
|
||||
return unless will_save_change_to_attribute?(:confirmed) || will_save_change_to_attribute?(:newsletter)
|
||||
|
||||
if newsletter
|
||||
newsletter_subscribe if confirmed_just_now? || requested_newsletter_just_now?
|
||||
elsif confirmed_at
|
||||
newsletter_unsubscribe
|
||||
end
|
||||
end
|
||||
|
||||
def confirmed_just_now?
|
||||
will_save_change_to_attribute?(:confirmed_at)
|
||||
end
|
||||
|
||||
def requested_newsletter_just_now?
|
||||
confirmed_at && will_save_change_to_attribute?(:newsletter)
|
||||
end
|
||||
|
||||
def newsletter_subscribe(gibbon = Gibbon::API.new, testing = false)
|
||||
return true if Rails.env.test? && !testing
|
||||
|
||||
gibbon.lists.subscribe(
|
||||
id: Rails.application.config.newsletter_list_id,
|
||||
email: { email: email },
|
||||
merge_vars: { login_name: login_name },
|
||||
double_optin: false # they already confirmed their email with us
|
||||
)
|
||||
end
|
||||
|
||||
def newsletter_unsubscribe(gibbon = Gibbon::API.new, testing = false)
|
||||
return true if Rails.env.test? && !testing
|
||||
|
||||
gibbon.lists.unsubscribe(id: Rails.application.config.newsletter_list_id,
|
||||
email: { email: email })
|
||||
end
|
||||
|
||||
def already_following?(member)
|
||||
follows.exists?(followed_id: member.id)
|
||||
end
|
||||
|
||||
@@ -9,17 +9,15 @@ class Notification < ApplicationRecord
|
||||
scope :by_recipient, ->(recipient) { where(recipient_id: recipient) }
|
||||
|
||||
before_create :replace_blank_subject
|
||||
after_create :send_email
|
||||
after_create :send_message
|
||||
|
||||
def self.unread_count
|
||||
unread.size
|
||||
def send_message
|
||||
sender.send_message(recipient, body, subject) if recipient.send_notification_email
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def replace_blank_subject
|
||||
self.subject = "(no subject)" if subject.nil? || subject =~ /^\s*$/
|
||||
end
|
||||
|
||||
def send_email
|
||||
Notifier.notify(self).deliver_now! if recipient.send_notification_email
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,6 @@ class Photo < ApplicationRecord
|
||||
source_type: type
|
||||
end
|
||||
|
||||
default_scope { joins(:owner) } # Ensures the owner still exists
|
||||
scope :by_crop, ->(crop) { joins(:photo_associations).where(photo_associations: { crop: crop }) }
|
||||
scope :by_model, lambda { |model_name|
|
||||
joins(:photo_associations).where(photo_associations: { photographable_type: model_name.to_s })
|
||||
|
||||
@@ -3,10 +3,12 @@ class PlantPart < ApplicationRecord
|
||||
friendly_id :name, use: %i(slugged finders)
|
||||
|
||||
has_many :harvests, dependent: :destroy
|
||||
has_many :crops, -> { distinct }, through: :harvests
|
||||
has_many :crops, -> { joins_members.distinct }, through: :harvests
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
|
||||
scope :joins_members, -> { joins("INNER JOIN members ON members.id = harvests.owner_id") }
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
@@ -32,7 +32,6 @@ class Planting < ApplicationRecord
|
||||
|
||||
##
|
||||
## Scopes
|
||||
default_scope { joins(:owner) } # Ensures the owner still exists
|
||||
scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) }
|
||||
scope :interesting, -> { has_photos.one_per_owner.order(planted_at: :desc) }
|
||||
scope :recent, -> { order(created_at: :desc) }
|
||||
@@ -47,6 +46,7 @@ class Planting < ApplicationRecord
|
||||
delegate :name, :en_wikipedia_url, :default_scientific_name, :plantings_count,
|
||||
to: :crop, prefix: true
|
||||
|
||||
delegate :annual?, to: :crop
|
||||
##
|
||||
## Validations
|
||||
validates :garden, presence: true
|
||||
|
||||
@@ -19,7 +19,7 @@ class Post < ApplicationRecord
|
||||
after_save :update_crops_posts_association
|
||||
after_create :send_notification
|
||||
|
||||
default_scope { joins(:author) } # Ensures the owner still exists
|
||||
default_scope { joins(:author).merge(Member.kept) } # Ensures the owner still exists
|
||||
|
||||
#
|
||||
# Validations
|
||||
|
||||
@@ -48,7 +48,7 @@ class Seed < ApplicationRecord
|
||||
|
||||
#
|
||||
# Scopes
|
||||
default_scope { joins(:owner) } # Ensure owner exists
|
||||
default_scope { joins(:owner).merge(Member.kept) } # Ensure owner exists
|
||||
scope :tradable, -> { where.not(tradable_to: 'nowhere') }
|
||||
scope :interesting, -> { tradable.has_location }
|
||||
scope :has_location, -> { joins(:owner).where.not("members.location": nil) }
|
||||
|
||||
@@ -3,25 +3,41 @@
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item.active= link_to 'Admin', admin_path
|
||||
|
||||
%h2 Manage
|
||||
%h1 Manage #{ENV['GROWSTUFF_SITE_NAME']}
|
||||
|
||||
.row
|
||||
.col-md-4
|
||||
%h2 Site admin
|
||||
|
||||
%ul#site_admin
|
||||
%li= link_to "Roles", roles_path
|
||||
%li= link_to "Forums", forums_path
|
||||
%li= link_to "CMS", comfy_admin_cms_path
|
||||
%li= link_to t('.garden_types'), garden_types_path
|
||||
.card
|
||||
.card-header
|
||||
%h2
|
||||
= icon 'fas', 'tablet-alt'
|
||||
Site admin
|
||||
.card-body
|
||||
%nav.nav#site_admin
|
||||
%li= link_to "Roles", roles_path, class: 'nav-link'
|
||||
%li= link_to "Forums", forums_path, class: 'nav-link'
|
||||
%li= link_to "CMS", comfy_admin_cms_path, class: 'nav-link'
|
||||
%li= link_to t('.garden_types'), garden_types_path, class: 'nav-link'
|
||||
|
||||
.col-md-4
|
||||
%h2 Crop data admin
|
||||
%ul
|
||||
%li= link_to "Alternate names", alternate_names_path
|
||||
%li= link_to "Scientific names", scientific_names_path
|
||||
.card
|
||||
.card-header
|
||||
%h2
|
||||
= icon 'fas', 'seedling'
|
||||
Crop data admin
|
||||
.card-body
|
||||
%nav.nav#crop_admin
|
||||
%li= link_to "Crop Wrangling", wrangle_crops_path, class: 'nav-link'
|
||||
%li= link_to "Alternate names", alternate_names_path, class: 'nav-link'
|
||||
%li= link_to "Scientific names", scientific_names_path, class: 'nav-link'
|
||||
|
||||
.col-md-4
|
||||
%h2 Member admin
|
||||
%ul
|
||||
%li= link_to "Newsletter subscribers", admin_newsletter_path
|
||||
%li= link_to "Members", admin_members_path
|
||||
.card
|
||||
.card-header
|
||||
%h2
|
||||
= icon 'fas', 'user'
|
||||
Member admin
|
||||
.card-body
|
||||
%nav.nav#member_admin
|
||||
%li= link_to "Newsletter subscribers", admin_newsletter_path, class: 'nav-link'
|
||||
%li= link_to "Members", admin_members_path, class: 'nav-link'
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
.pagination
|
||||
= page_entries_info @members
|
||||
= will_paginate @members
|
||||
.row
|
||||
.col-md-6
|
||||
= bootstrap_form_tag url: admin_members_path, method: :get, layout: :horizontal do |f|
|
||||
.input-group.md-form.form-sm
|
||||
.input-group-prepend
|
||||
%span.input-group-text= icon 'fas', 'user'
|
||||
= f.text_field(:q, 'aria-label': 'search members', placeholder: 'search members', hide_label: true)
|
||||
= f.submit 'search', class: 'btn btn-secondary'
|
||||
|
||||
|
||||
%table.table.table-striped
|
||||
%thead
|
||||
@@ -11,10 +17,11 @@
|
||||
- @members.each do |member|
|
||||
%tr
|
||||
%td= render 'members/tiny', member: member
|
||||
%td
|
||||
= link_to member, member
|
||||
%td= member.login_name
|
||||
%td= member.email
|
||||
%td
|
||||
= link_to admin_member_path(member), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-light text-danger' do
|
||||
= icon 'fas', 'ban'
|
||||
Ban member
|
||||
= page_entries_info @members
|
||||
= will_paginate @members
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
.card.comment
|
||||
.card-body
|
||||
.row
|
||||
.col-md-1= render "members/tiny", member: comment.author
|
||||
.col-md-11.border-left
|
||||
.col
|
||||
= render "members/tiny", member: comment.author
|
||||
- if can?(:edit, comment) || can?(:destroy, comment)
|
||||
.dropdown.float-right
|
||||
%hr/
|
||||
.dropdown
|
||||
%button#comment-edit-button.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button"} Actions
|
||||
.dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "comment-edit-button"}
|
||||
- if can? :edit, comment
|
||||
@@ -16,9 +17,10 @@
|
||||
data: { confirm: 'Are you sure?' }, class: 'dropdown-item text-danger' do
|
||||
= delete_icon
|
||||
Delete
|
||||
.col-md-9.border-left
|
||||
.comment-meta.text-muted
|
||||
Posted by
|
||||
- if comment.author.deleted?
|
||||
- if comment.author.discarded?
|
||||
Member Deleted
|
||||
- else
|
||||
= link_to comment.author.login_name, member_path(comment.author)
|
||||
|
||||
57
app/views/conversations/index.haml
Normal file
57
app/views/conversations/index.haml
Normal file
@@ -0,0 +1,57 @@
|
||||
- content_for :title, "Inbox"
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Conversations', conversations_path
|
||||
%li.breadcrumb-item.active= link_to @box, conversations_path(box: @box)
|
||||
|
||||
.row
|
||||
.col-md-2
|
||||
.col-md-10
|
||||
%h1= @box
|
||||
- unless @conversations.empty?
|
||||
= will_paginate @conversations
|
||||
.row
|
||||
.col-md-2.list-group
|
||||
- @boxes.each do |box_name, counts|
|
||||
= link_to conversations_path(box: box_name), class: "nav-link list-group-item d-flex justify-content-between #{box_name == @box ? 'active' : ''} #{box_name}" do
|
||||
- if counts['unread'].positive?
|
||||
%span.badge.badge-info=counts['unread']
|
||||
- else
|
||||
%span
|
||||
%span
|
||||
= icon 'fas', box_name
|
||||
= box_name
|
||||
|
||||
.col-md-10
|
||||
.list-group
|
||||
- @conversations.each do |conversation|
|
||||
.list-group-item
|
||||
.row
|
||||
.col-md-1
|
||||
- if conversation.receipts_for(current_member).last.is_unread?
|
||||
%h1= icon 'far', 'envelope'
|
||||
- else
|
||||
%h1.text-muted= icon 'far', 'envelope-open'
|
||||
.col-md-10
|
||||
.text-right.float-right
|
||||
- conversation.recipients.each do |member|
|
||||
- if member != current_member
|
||||
= render 'members/tiny', member: member
|
||||
= link_to conversation_path(conversation) do
|
||||
.conversation
|
||||
%h5.mb-2.h5
|
||||
- if conversation.receipts_for(current_member).last.is_unread?
|
||||
%strong= conversation.subject
|
||||
- else
|
||||
= conversation.subject
|
||||
%small
|
||||
#{time_ago_in_words conversation.messages.last.created_at} ago
|
||||
%span.text-muted= conversation.messages.last.created_at
|
||||
= truncate(strip_tags(conversation.messages.last.body), length: 150, separator: ' ', omission: '... ')
|
||||
.col-md-1
|
||||
- if @box == 'trash'
|
||||
= link_to conversation_path(conversation, box: @box), method: :put, class: 'restore' do
|
||||
= icon 'fas', 'trash-restore'
|
||||
- else
|
||||
= link_to delete_icon, conversation_path(conversation, box: @box), method: :delete, class: 'delete text-danger'
|
||||
- unless @conversations.empty?
|
||||
= will_paginate @conversations
|
||||
33
app/views/conversations/show.html.haml
Normal file
33
app/views/conversations/show.html.haml
Normal file
@@ -0,0 +1,33 @@
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Conversations', conversations_path
|
||||
%li.breadcrumb-item.active= link_to @conversation.subject, conversation_path(@conversation)
|
||||
|
||||
.row
|
||||
.col-md-2
|
||||
.col-md-10
|
||||
%h1= @conversation.subject
|
||||
.row
|
||||
.col-md-2
|
||||
.card
|
||||
.card-header
|
||||
%h6 Participants
|
||||
%ul.list-group.list-group-flush
|
||||
- @participants.each do |member|
|
||||
%li.list-group-item= render 'members/tiny', member: member
|
||||
.col-md-10
|
||||
.card
|
||||
%ul.list-group.list-group-flush
|
||||
- @conversation.messages.order(:created_at).each do |message|
|
||||
%li.list-group-item
|
||||
.col-md-8.text-muted
|
||||
= comment_icon
|
||||
on #{message.created_at}
|
||||
.col-md-4.text-right.float-right
|
||||
= render 'members/tiny', member: message.sender
|
||||
.col-12
|
||||
%p.text-justify
|
||||
:growstuff_markdown
|
||||
#{ strip_tags(message.body) }
|
||||
%li.list-group-item
|
||||
= icon 'fas', 'reply'
|
||||
= render 'messages/form', conversation: @conversation
|
||||
@@ -1,68 +1,60 @@
|
||||
- content_for :title, "Crop Wrangling"
|
||||
|
||||
%h1 Crop Wrangling
|
||||
|
||||
%ul
|
||||
%li= link_to "Full crop hierarchy", hierarchy_crops_path
|
||||
%li= link_to "Add Crop", new_crop_path
|
||||
%nav.nav
|
||||
= link_to "Full crop hierarchy", hierarchy_crops_path, class: 'nav-link'
|
||||
= link_to "Add Crop", new_crop_path, class: 'btn'
|
||||
|
||||
.crop_wranglers
|
||||
%h2 Crop Wranglers:
|
||||
%ul
|
||||
- @crop_wranglers.each do |crop_wrangler|
|
||||
%li.crop_wrangler
|
||||
= link_to crop_wrangler.login_name, crop_wrangler
|
||||
%section.crop_wranglers
|
||||
%h2 Crop Wranglers
|
||||
- @crop_wranglers.each do |crop_wrangler|
|
||||
= render 'members/tiny', member: crop_wrangler
|
||||
|
||||
.tabbable
|
||||
%ul.nav.nav-tabs
|
||||
%li{ class: @approval_status.blank? ? 'active' : '' }
|
||||
= link_to "Recently added", wrangle_crops_path
|
||||
%li{ class: @approval_status == "pending" ? 'active' : '' }
|
||||
= link_to "Pending approval", wrangle_crops_path(approval_status: "pending")
|
||||
%li{ class: @approval_status == "rejected" ? 'active' : '' }
|
||||
= link_to "Rejected", wrangle_crops_path(approval_status: "rejected")
|
||||
%hr/
|
||||
|
||||
%h2
|
||||
- if @approval_status == "pending"
|
||||
Requested Crops
|
||||
- elsif @approval_status == "rejected"
|
||||
Rejected Crops
|
||||
- else
|
||||
Recently added crops
|
||||
%section
|
||||
%h2 Crops
|
||||
|
||||
%ul#myTab.nav.nav-tabs{role: "tablist"}
|
||||
%li.nav-item
|
||||
%a#home-tab.nav-link{ href: wrangle_crops_path, role: "tab", class: @approval_status.blank? ? 'active' : ''}
|
||||
Recently added
|
||||
%li.nav-item
|
||||
%a#profile-tab.nav-link{ href: wrangle_crops_path(approval_status: "pending"), role: "tab", class: @approval_status == "pending" ? 'active' : ''}
|
||||
Pending approval
|
||||
%li.nav-item
|
||||
%a#contact-tab.nav-link{ href: wrangle_crops_path(approval_status: "rejected"), role: "tab", class: @approval_status == "rejected" ? 'active' : ''} Rejected
|
||||
|
||||
.pagination
|
||||
= page_entries_info @crops
|
||||
= will_paginate @crops
|
||||
|
||||
%table{ class: "table table-striped",
|
||||
id: @approval_status.blank? ? 'recently-added-crops' : "#{@approval_status}-crops" }
|
||||
%tr
|
||||
%th System name
|
||||
%th English Wikipedia URL
|
||||
%th Scientific names
|
||||
%th Requested by
|
||||
- if @approval_status == "rejected"
|
||||
%th Rejected by
|
||||
- if @approval_status != "rejected" && @approval_status != "pending"
|
||||
%th Added by
|
||||
%th When
|
||||
- @crops.each do |c|
|
||||
%table.table.table-striped.table-bordered.table-sm{id: @approval_status.blank? ? 'recently-added-crops' : "#{@approval_status}-crops" }
|
||||
%tr
|
||||
%td= link_to c.name, edit_crop_path(c)
|
||||
%td= link_to c.en_wikipedia_url, c.en_wikipedia_url
|
||||
%td
|
||||
- c.scientific_names.each do |s|
|
||||
= link_to s.name, s
|
||||
%br/
|
||||
%td= c.requester.present? ? (link_to c.requester, c.requester) : "N/A"
|
||||
- unless @approval_status == "pending"
|
||||
%td= c.creator.present? ? (link_to c.creator, c.creator) : "N/A"
|
||||
%td
|
||||
= distance_of_time_in_words(c.created_at, Time.zone.now)
|
||||
ago.
|
||||
%th System name
|
||||
%th English Wikipedia URL
|
||||
%th Scientific names
|
||||
%th Requested by
|
||||
- if @approval_status == "rejected"
|
||||
%th Rejected by
|
||||
- if @approval_status != "rejected" && @approval_status != "pending"
|
||||
%th Added by
|
||||
%th When
|
||||
- @crops.each do |c|
|
||||
%tr
|
||||
%td
|
||||
= link_to edit_crop_path(c) do
|
||||
= icon 'fas', 'seedling'
|
||||
= c.name
|
||||
%td= link_to c.en_wikipedia_url, c.en_wikipedia_url
|
||||
%td
|
||||
- c.scientific_names.each do |s|
|
||||
= link_to s.name, s
|
||||
%br/
|
||||
%td= c.requester.present? ? (link_to c.requester, c.requester) : "N/A"
|
||||
- unless @approval_status == "pending"
|
||||
%td= c.creator.present? ? (link_to c.creator, c.creator) : "N/A"
|
||||
%td
|
||||
= distance_of_time_in_words(c.created_at, Time.zone.now)
|
||||
ago.
|
||||
|
||||
.pagination
|
||||
= page_entries_info @crops
|
||||
= will_paginate @crops
|
||||
|
||||
|
||||
|
||||
@@ -13,18 +13,20 @@
|
||||
- else
|
||||
%li.breadcrumb-item.active= link_to 'Gardens', gardens_path
|
||||
|
||||
= link_to gardens_active_tickbox_path(@owner, @show_all) do
|
||||
= check_box_tag 'active', 'all', @show_all
|
||||
include in-active
|
||||
|
||||
.pagination
|
||||
= page_entries_info @gardens
|
||||
= will_paginate @gardens
|
||||
|
||||
%section.border-top
|
||||
= link_to gardens_active_tickbox_path(@owner, @show_all), class: 'btn' do
|
||||
= check_box_tag 'active', 'all', @show_all
|
||||
include in-active
|
||||
|
||||
- if @gardens.empty?
|
||||
%p There are no gardens to display.
|
||||
- if can?(:create, Garden) && @owner == current_member
|
||||
= link_to 'Add a garden', new_garden_path, class: 'btn btn-primary'
|
||||
|
||||
- else
|
||||
.row
|
||||
.col-12= page_entries_info @gardens
|
||||
.col-12= will_paginate @gardens
|
||||
- @gardens.each do |garden|
|
||||
%section.border-top
|
||||
.row
|
||||
@@ -43,6 +45,6 @@
|
||||
.col-6.col-md-4.col-lg-3= render 'plantings/card', planting: planting
|
||||
|
||||
|
||||
.pagination
|
||||
= page_entries_info @gardens
|
||||
= will_paginate @gardens
|
||||
.row
|
||||
.col-12= page_entries_info @gardens
|
||||
.col-12= will_paginate @gardens
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
%span= display_quantity(@harvest)
|
||||
|
||||
|
||||
- if @harvest.description.present?
|
||||
%h2 Notes
|
||||
:growstuff_markdown
|
||||
#{ @harvest.description != "" ? strip_tags(@harvest.description) : "No description given." }
|
||||
.col-md-4
|
||||
.card
|
||||
.card-body= render 'harvests/owner', harvest: @harvest
|
||||
|
||||
= render @harvest.crop
|
||||
|
||||
%hr/
|
||||
- if @harvest.photos.size.positive?
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
- if member_signed_in?
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"}
|
||||
- if current_member.notifications.unread_count.positive?
|
||||
- if current_member.unread_count.positive?
|
||||
= t('.your_stuff')
|
||||
%span.badge.badge-info= current_member.notifications.unread_count
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
- else
|
||||
= t('.current_memberlogin_name', current_memberlogin_name: current_member.login_name)
|
||||
= t('.current_memberlogin_name', current_memberlogin_name: current_member)
|
||||
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
|
||||
= link_to member_path(current_member), class: 'dropdown-item' do
|
||||
= t('.profile')
|
||||
@@ -46,14 +46,14 @@
|
||||
= t('.seeds')
|
||||
= link_to t('.posts'), member_posts_path(current_member), class: 'dropdown-item'
|
||||
|
||||
- if current_member.notifications.unread_count.positive?
|
||||
- if current_member.unread_count.positive?
|
||||
.dropdown-divider
|
||||
%strong
|
||||
= link_to(notifications_path, class: 'dropdown-item') do
|
||||
= link_to(conversations_path, class: 'dropdown-item') do
|
||||
= t('.inbox')
|
||||
%span.badge.badge-info= current_member.notifications.unread_count
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
- else
|
||||
= link_to t('.inbox'), notifications_path, class: 'dropdown-item'
|
||||
= link_to t('.inbox'), conversations_path, class: 'dropdown-item'
|
||||
|
||||
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
|
||||
%li.nav-item.dropdown
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>You have a new message: <%= @subject %></h1>
|
||||
<p>
|
||||
You have received a new message:
|
||||
</p>
|
||||
<blockquote>
|
||||
<p>
|
||||
<%= raw @message.body %>
|
||||
</p>
|
||||
</blockquote>
|
||||
<p>
|
||||
Visit <%= link_to root_url, root_url %> and go to your inbox for more info.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
You have a new message: <%= @subject %>
|
||||
===============================================
|
||||
|
||||
You have received a new message:
|
||||
|
||||
-----------------------------------------------
|
||||
<%= @message.body.html_safe? ? @message.body : strip_tags(@message.body) %>
|
||||
-----------------------------------------------
|
||||
|
||||
Visit <%= root_url %> and go to your inbox for more info.
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>You have a new reply: <%= @subject %></h1>
|
||||
<p>
|
||||
You have received a new reply:
|
||||
</p>
|
||||
<blockquote>
|
||||
<p>
|
||||
<%= raw @message.body %>
|
||||
</p>
|
||||
</blockquote>
|
||||
<p>
|
||||
Visit <%= link_to root_url, root_url %> and go to your inbox for more info.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
You have a new reply: <%= @subject %>
|
||||
===============================================
|
||||
|
||||
You have received a new reply:
|
||||
|
||||
-----------------------------------------------
|
||||
<%= @message.body.html_safe? ? @message.body : strip_tags(@message.body) %>
|
||||
-----------------------------------------------
|
||||
|
||||
Visit <%= root_url %> and go to your inbox for more info.
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>You have a new notification: <%= @notification.subject.html_safe? ? @notification.subject : strip_tags(@notification.subject) %></h1>
|
||||
<p>
|
||||
You have received a new notification:
|
||||
</p>
|
||||
<blockquote>
|
||||
<p>
|
||||
<%= raw @notification.body %>
|
||||
</p>
|
||||
</blockquote>
|
||||
<p>
|
||||
Visit <%= link_to root_url,root_url %> and go to your notifications for more info.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
You have a new notification: <%= @notification.subject.html_safe? ? @notification.subject : strip_tags(@notification.subject) %>
|
||||
===============================================
|
||||
|
||||
You have received a new notification:
|
||||
|
||||
-----------------------------------------------
|
||||
<%= @notification.body.html_safe? ? @notification.body : strip_tags(@notification.body) %>
|
||||
-----------------------------------------------
|
||||
|
||||
Visit <%= root_url %> and go to your notifications for more info.
|
||||
@@ -1,15 +0,0 @@
|
||||
-# %p.btn-group
|
||||
-# - if can? :update, @member
|
||||
-# = link_to edit_member_registration_path, class: 'btn btn-default text-right' do
|
||||
-# = edit_icon
|
||||
-# = t('members.edit_profile')
|
||||
|
||||
-# - if can?(:create, Notification) && current_member != @member
|
||||
-# = link_to 'Send message', new_notification_path(recipient_id: @member.id), class: 'btn btn-default'
|
||||
|
||||
-# - if current_member && current_member != @member # must be logged in, can't follow yourself
|
||||
-# - follow = current_member.get_follow(@member)
|
||||
-# - if !follow && can?(:create, Follow) # not already following
|
||||
-# = link_to 'Follow', follows_path(followed: @member), method: :post, class: 'btn btn-default text-right'
|
||||
-# - if follow && can?(:destroy, follow) # already following
|
||||
-# = link_to 'Unfollow', follow_path(follow), method: :delete, class: 'btn btn-default text-right'
|
||||
@@ -19,10 +19,10 @@
|
||||
%ul.nav.nav-justified.small
|
||||
%li.nav-item.border-right
|
||||
= link_to member_plantings_path(member) do
|
||||
= localize_plural(member.plantings.active, Planting)
|
||||
= localize_plural(member.plantings, Planting)
|
||||
%li.nav-item.border-right
|
||||
= link_to member_harvests_path(member) do
|
||||
= localize_plural(member.harvests, Harvest)
|
||||
%li.nav-item
|
||||
= link_to member_seeds_path(member) do
|
||||
= localize_plural(member.seeds.active, Seed)
|
||||
= localize_plural(member.seeds, Seed)
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
= image_tag(avatar_uri(member, 50), alt: '', class: 'img img-fluid avatar', height: 50)
|
||||
.member-chip
|
||||
- if member.discarded?
|
||||
= icon 'fas', 'user-times'
|
||||
= member
|
||||
- else
|
||||
= link_to member do
|
||||
= image_tag(avatar_uri(member, 100), alt: '', height: 50, width: 50)
|
||||
= member
|
||||
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
|
||||
= render 'members/actions', member: @member
|
||||
|
||||
- content_for :member_rss_login_name, @member.login_name
|
||||
- content_for :member_rss_slug, @member.slug
|
||||
|
||||
@@ -39,7 +36,7 @@
|
||||
= t('members.edit_profile')
|
||||
|
||||
- if can?(:create, Notification) && current_member != @member
|
||||
= link_to new_notification_path(recipient_id: @member.id), class: 'btn btn-block' do
|
||||
= link_to new_message_path(recipient_id: @member.id), class: 'btn btn-block' do
|
||||
= icon('fas', 'envelope')
|
||||
Send message
|
||||
|
||||
|
||||
17
app/views/messages/_form.haml
Normal file
17
app/views/messages/_form.haml
Normal file
@@ -0,0 +1,17 @@
|
||||
= bootstrap_form_tag url: '/messages' do |f|
|
||||
- if @conversation.present?
|
||||
= f.hidden_field :conversation_id, value: @conversation.id
|
||||
- elsif @recipient.present?
|
||||
%p
|
||||
To
|
||||
= link_to @recipient, @recipient
|
||||
= render 'members/tiny', member: @recipient
|
||||
= f.hidden_field :recipient_id, value: @recipient.id
|
||||
= f.text_field :subject, value: @subject, class: 'form-control', maxlength: 255, required: true
|
||||
|
||||
= f.text_area :body, rows: 12, label: "Type your message here", required: true
|
||||
%span.help-block= render partial: "shared/markdown_help"
|
||||
|
||||
.card-footer
|
||||
= link_to 'cancel', conversations_path, class: 'btn'
|
||||
.float-right= f.submit "Send", class: 'btn btn-primary'
|
||||
@@ -1,6 +1,6 @@
|
||||
= content_for :title, "Send a message to #{@recipient}"
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Messages', notifications_path
|
||||
%li.breadcrumb-item= link_to 'Messages', conversations_path
|
||||
|
||||
= render 'form'
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
= bootstrap_form_for @notification do |f|
|
||||
.card
|
||||
.card-body
|
||||
- if @notification.errors.any?
|
||||
#error_explanation
|
||||
%h2
|
||||
= pluralize(@post.errors.size, "error")
|
||||
prohibited this message from being sent:
|
||||
%ul
|
||||
- @notification.errors.full_messages.each do |msg|
|
||||
%li= msg
|
||||
|
||||
.field
|
||||
= f.hidden_field :recipient_id, value: @recipient.id
|
||||
|
||||
%p
|
||||
To
|
||||
= link_to @recipient, @recipient
|
||||
= render 'members/tiny', member: @recipient
|
||||
= f.text_field :subject, value: @subject, class: 'form-control', maxlength: 255
|
||||
= f.text_area :body, rows: 12, label: "Type your message here"
|
||||
%span.help-block= render partial: "shared/markdown_help"
|
||||
.card-footer
|
||||
|
||||
= link_to 'cancel', notifications_path, class: 'btn'
|
||||
.float-right= f.submit "Send", class: 'btn btn-primary'
|
||||
@@ -1,13 +0,0 @@
|
||||
%p.text-muted
|
||||
From
|
||||
= link_to notification.sender, notification.sender
|
||||
on
|
||||
= notification.created_at
|
||||
|
||||
- if notification.post_id
|
||||
in response to:
|
||||
= link_to notification.post.subject, notification.post
|
||||
|
||||
.well
|
||||
:growstuff_markdown
|
||||
#{ strip_tags(notification.body) }
|
||||
@@ -1,38 +0,0 @@
|
||||
- content_for :title, "Inbox"
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item.active= link_to 'Messages', notifications_path
|
||||
|
||||
- if @notifications.empty?
|
||||
.alert.alert-success{role: "alert"} You have no messages.
|
||||
- else
|
||||
= paginate @notifications
|
||||
|
||||
- @notifications.each do |n|
|
||||
.card.message
|
||||
.card-body
|
||||
.row
|
||||
.col-6.col-md-9
|
||||
%h3= link_to n.subject, notification_path(n)
|
||||
%p
|
||||
- if n.read
|
||||
= icon 'far', 'envelope-open'
|
||||
- else
|
||||
= icon 'far', 'envelope'
|
||||
%strong unread
|
||||
= n.created_at
|
||||
.col-6.col-md-3.text-right
|
||||
- if n.sender.present?
|
||||
= link_to n.sender do
|
||||
%h3
|
||||
= render 'members/tiny', member: n.sender
|
||||
= n.sender
|
||||
= render 'members/follow_buttons', member: n.sender
|
||||
- else
|
||||
Member deleted
|
||||
.card-footer
|
||||
= link_to 'Read', n, class: 'btn btn-primary'
|
||||
= link_to 'Reply', reply_link(n), class: 'btn btn-secondary'
|
||||
= link_to n, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-danger btn-xs' do
|
||||
= delete_icon
|
||||
= t('buttons.delete')
|
||||
= paginate @notifications
|
||||
@@ -1,8 +0,0 @@
|
||||
= content_for :title, "Send a message to #{@recipient}"
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Messages', notifications_path
|
||||
|
||||
.card
|
||||
.card-body= render @sender_notification
|
||||
= render 'form'
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
= content_for :title, @notification.subject
|
||||
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Messages', notifications_path
|
||||
|
||||
.card
|
||||
.card-header
|
||||
.card-title= @notification.subject
|
||||
.float-right
|
||||
- if @notification.sender.present?
|
||||
= link_to @notification.sender do
|
||||
= @notification.sender
|
||||
= render 'members/tiny', member: @notification.sender
|
||||
- else
|
||||
Member deleted
|
||||
|
||||
.card-body
|
||||
= render @notification
|
||||
|
||||
.card-footer
|
||||
= link_to 'Reply', @reply_link, class: 'btn btn-primary'
|
||||
= link_to @notification, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-danger' do
|
||||
= delete_icon
|
||||
= t('buttons.delete')
|
||||
@@ -21,5 +21,4 @@
|
||||
.card-body.text-center
|
||||
%h4.card-title= link_to planting.crop, planting
|
||||
.text-center= render 'plantings/badges', planting: planting
|
||||
.card-footer
|
||||
= render 'plantings/progress', planting: planting
|
||||
@@ -1,10 +1,8 @@
|
||||
- if planting.planted_at.present?
|
||||
planted #{planting.planted_at}
|
||||
- if planting.percentage_grown.present?
|
||||
- if planting.annual? && planting.percentage_grown.present?
|
||||
.progress
|
||||
.progress-bar.bg-success{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, :role => "progressbar", :style => "width: #{planting.percentage_grown}%; height: 25px"}
|
||||
|
||||
.float-left #{sprintf '%.0f', planting.percentage_grown}%
|
||||
|
||||
- unless planting.finish_predicted_at.blank?
|
||||
.float-right= planting.finish_predicted_at
|
||||
.float-right= planting.finish_predicted_at.strftime(Date::DATE_FORMATS[:ymd])
|
||||
|
||||
@@ -13,25 +13,28 @@
|
||||
- else
|
||||
%li.breadcrumb-item.active= link_to 'Plantings', plantings_path
|
||||
|
||||
= link_to plantings_active_tickbox_path(@owner, @show_all) do
|
||||
= check_box_tag 'active', 'all', @show_all
|
||||
include in-active
|
||||
%section.border-top
|
||||
.btn-group
|
||||
= link_to plantings_active_tickbox_path(@owner, @show_all), class: 'btn' do
|
||||
= check_box_tag 'active', 'all', @show_all
|
||||
include in-active
|
||||
|
||||
- if @owner
|
||||
= link_to t('.view_owners_profile', owner: @owner), member_path(@owner)
|
||||
- if @owner
|
||||
= link_to t('.view_owners_profile', owner: @owner), member_path(@owner), class: 'btn'
|
||||
|
||||
.pagination
|
||||
= page_entries_info @plantings
|
||||
= render 'layouts/pagination', collection: @plantings
|
||||
.row
|
||||
.col-12= page_entries_info @plantings
|
||||
.col-12= render 'layouts/pagination', collection: @plantings
|
||||
|
||||
.index-cards= render @plantings, full: true
|
||||
|
||||
.pagination
|
||||
= page_entries_info @plantings
|
||||
= render 'layouts/pagination', collection: @plantings
|
||||
.row
|
||||
.col-12= page_entries_info @plantings
|
||||
.col-12= render 'layouts/pagination', collection: @plantings
|
||||
|
||||
%ul.list-inline
|
||||
%li= t('.the_data_on_this_page_is_available_in_the_following_formats')
|
||||
- ['csv', 'json', 'rss'].each do |format|
|
||||
%li= link_to format.upcase,
|
||||
(@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format))
|
||||
%p= t('.the_data_on_this_page_is_available_in_the_following_formats')
|
||||
%ul.list-group.list-group-horizontal
|
||||
- ['csv', 'json', 'rss'].each do |format|
|
||||
%li.list-group-item
|
||||
= icon 'fas', format
|
||||
= link_to format.upcase, (@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format))
|
||||
|
||||
@@ -32,25 +32,24 @@
|
||||
|
||||
.row
|
||||
.col-md-8.col-12
|
||||
%section.blog-post
|
||||
.card.post{ id: "post-#{@post.id}" }
|
||||
.card-header
|
||||
%h2.display-3.float-left
|
||||
= @post.subject
|
||||
= render 'posts/actions', post: @post
|
||||
.card-body
|
||||
.post-content= render 'posts/single', post: @post
|
||||
.index-cards
|
||||
- @post.photos.each do |photo|
|
||||
= render 'photos/thumbnail', photo: photo
|
||||
.card.post{ id: "post-#{@post.id}" }
|
||||
.card-header
|
||||
%h2.display-3.float-left
|
||||
= @post.subject
|
||||
= render 'posts/actions', post: @post
|
||||
.card-body
|
||||
.post-content= render 'posts/single', post: @post
|
||||
.index-cards
|
||||
- @post.photos.each do |photo|
|
||||
= render 'photos/thumbnail', photo: photo
|
||||
|
||||
.card-footer
|
||||
= render 'posts/likes', post: @post
|
||||
.float-right
|
||||
- if can? :create, Comment
|
||||
= link_to new_comment_path(post_id: @post.id), class: 'btn' do
|
||||
= icon 'fas', 'comment'
|
||||
Comment
|
||||
.card-footer
|
||||
= render 'posts/likes', post: @post
|
||||
.float-right
|
||||
- if can? :create, Comment
|
||||
= link_to new_comment_path(post_id: @post.id), class: 'btn' do
|
||||
= icon 'fas', 'comment'
|
||||
Comment
|
||||
|
||||
%secion.comments= render "comments", post: @post
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
= link_to seed do
|
||||
= image_tag(seed_image_path(seed, full_size: true), alt: seed, class: 'img-card')
|
||||
.card-body
|
||||
%h5= link_to seed, seed
|
||||
.card-title= link_to seed.crop, seed
|
||||
.card-footer
|
||||
= render 'members/tiny', member: seed.owner
|
||||
- if seed.tradable?
|
||||
%span.badge.badge-pill.badge-secondary Will trade #{seed.tradable_to}
|
||||
= link_to seed.owner do
|
||||
.card-footer
|
||||
%span.badge.badge-pill.badge-secondary
|
||||
= truncate(seed.owner.location, length: 20, separator: ' ', omission: '... ')
|
||||
= render 'members/tiny', member: seed.owner
|
||||
%span.badge.badge-pill.badge-info
|
||||
= icon 'fas', 'map'
|
||||
Will trade #{seed.tradable_to}
|
||||
%span.badge.badge-pill.badge-secondary
|
||||
= truncate(seed.owner.location, length: 30, separator: ' ', omission: '... ')
|
||||
@@ -68,7 +68,7 @@
|
||||
- if current_member
|
||||
- if @seed.tradable? && current_member != @seed.owner
|
||||
%p= link_to "Request seeds",
|
||||
new_notification_path(recipient_id: @seed.owner.id,
|
||||
new_message_path(recipient_id: @seed.owner.id,
|
||||
subject: "Interested in your #{@seed.crop} seeds"),
|
||||
class: 'btn btn-primary'
|
||||
- else
|
||||
|
||||
21
config/initializers/mailboxer.rb
Normal file
21
config/initializers/mailboxer.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
Mailboxer.setup do |config|
|
||||
# Configures if your application uses or not email sending for Notifications and Messages
|
||||
config.uses_emails = true
|
||||
|
||||
# Configures the default from for emails sent for Messages and Notifications
|
||||
config.default_from = "no-reply@growstuff.org"
|
||||
|
||||
# Configures the methods needed by mailboxer
|
||||
# config.email_method = :email
|
||||
config.name_method = :login_name
|
||||
config.notify_method = :notify
|
||||
|
||||
# Configures if you use or not a search engine and which one you are using
|
||||
# Supported engines: [:solr,:sphinx,:pg_search]
|
||||
# config.search_enabled = false
|
||||
# config.search_engine = :solr
|
||||
|
||||
# Configures maximum length of the message subject and body
|
||||
config.subject_max_length = 255
|
||||
config.body_max_length = 32_000
|
||||
end
|
||||
@@ -97,9 +97,8 @@ Rails.application.routes.draw do
|
||||
get 'followers' => 'follows#followers'
|
||||
end
|
||||
|
||||
resources :notifications do
|
||||
get 'reply'
|
||||
end
|
||||
resources :messages
|
||||
resources :conversations
|
||||
|
||||
resources :places, only: %i(index show), param: :place do
|
||||
get 'search', on: :collection
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class SetPredictionData < ActiveRecord::Migration[4.2]
|
||||
def up
|
||||
say "Updating all plantings time to first harvest"
|
||||
Planting.all.each(&:update_harvest_days!)
|
||||
Planting.unscoped.all.each(&:update_harvest_days!)
|
||||
say "Updating crop median time to first harvest, and lifespan"
|
||||
Crop.all.each do |crop|
|
||||
crop.update_lifespan_medians
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class FinishedBoolean < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
Planting.where('finished_at < now()').update_all(finished: true)
|
||||
Planting.where(finished: nil).update_all(finished: false)
|
||||
Planting.unscoped.where('finished_at < now()').update_all(finished: true)
|
||||
Planting.unscoped.where(finished: nil).update_all(finished: false)
|
||||
change_column_null :plantings, :finished, false
|
||||
change_column_default :plantings, :finished, from: nil, to: false
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ class AddHarvestsCount < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :plantings, :harvests_count, :integer, default: 0
|
||||
Planting.reset_column_information
|
||||
Planting.pluck(:id).each do |planting_id|
|
||||
Planting.unscoped.pluck(:id).each do |planting_id|
|
||||
Planting.reset_counters(planting_id, :harvests)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# This migration comes from mailboxer_engine (originally 20110511145103)
|
||||
class CreateMailboxer < ActiveRecord::Migration[4.2]
|
||||
def self.up
|
||||
# Tables
|
||||
# Conversations
|
||||
create_table :mailboxer_conversations do |t|
|
||||
t.column :subject, :string, default: ""
|
||||
t.timestamps null: false
|
||||
end
|
||||
# Receipts
|
||||
create_table :mailboxer_receipts do |t|
|
||||
t.references :receiver, polymorphic: true
|
||||
t.column :notification_id, :integer, null: false
|
||||
t.column :is_read, :boolean, default: false
|
||||
t.column :trashed, :boolean, default: false
|
||||
t.column :deleted, :boolean, default: false
|
||||
t.column :mailbox_type, :string, limit: 25
|
||||
t.timestamps null: false
|
||||
end
|
||||
# Notifications and Messages
|
||||
create_table :mailboxer_notifications do |t|
|
||||
t.column :type, :string
|
||||
t.column :body, :text
|
||||
t.column :subject, :string, default: ""
|
||||
t.references :sender, polymorphic: true
|
||||
t.column :conversation_id, :integer
|
||||
t.column :draft, :boolean, default: false
|
||||
t.string :notification_code, default: nil
|
||||
t.references :notified_object, polymorphic: true, index: { name: 'mailboxer_notifications_notified_object' }
|
||||
t.column :attachment, :string
|
||||
t.timestamps null: false
|
||||
t.boolean :global, default: false
|
||||
t.datetime :expires
|
||||
end
|
||||
|
||||
# Indexes
|
||||
# Conversations
|
||||
# Receipts
|
||||
add_index "mailboxer_receipts", "notification_id"
|
||||
|
||||
# Messages
|
||||
add_index "mailboxer_notifications", "conversation_id"
|
||||
|
||||
# Foreign keys
|
||||
# Conversations
|
||||
# Receipts
|
||||
add_foreign_key "mailboxer_receipts", "mailboxer_notifications",
|
||||
name: "receipts_on_notification_id", column: "notification_id"
|
||||
# Messages
|
||||
add_foreign_key "mailboxer_notifications", "mailboxer_conversations",
|
||||
name: "notifications_on_conversation_id", column: "conversation_id"
|
||||
end
|
||||
|
||||
def self.down
|
||||
# Tables
|
||||
remove_foreign_key "mailboxer_receipts", name: "receipts_on_notification_id"
|
||||
remove_foreign_key "mailboxer_notifications", name: "notifications_on_conversation_id"
|
||||
|
||||
# Indexes
|
||||
drop_table :mailboxer_receipts
|
||||
drop_table :mailboxer_conversations
|
||||
drop_table :mailboxer_notifications
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# This migration comes from mailboxer_engine (originally 20131206080416)
|
||||
class AddConversationOptout < ActiveRecord::Migration[4.2]
|
||||
def self.up
|
||||
create_table :mailboxer_conversation_opt_outs do |t|
|
||||
t.references :unsubscriber, polymorphic: true
|
||||
t.references :conversation
|
||||
t.timestamps null: false
|
||||
end
|
||||
add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations",
|
||||
name: "mb_opt_outs_on_conversations_id", column: "conversation_id"
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_foreign_key "mailboxer_conversation_opt_outs", name: "mb_opt_outs_on_conversations_id"
|
||||
drop_table :mailboxer_conversation_opt_outs
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# This migration comes from mailboxer_engine (originally 20131206080417)
|
||||
class AddMissingIndices < ActiveRecord::Migration[4.2]
|
||||
def change
|
||||
# We'll explicitly specify its name, as the auto-generated name is too long and exceeds 63
|
||||
# characters limitation.
|
||||
add_index :mailboxer_conversation_opt_outs, %i(unsubscriber_id unsubscriber_type),
|
||||
name: 'index_mailboxer_conversation_opt_outs_on_unsubscriber_id_type'
|
||||
add_index :mailboxer_conversation_opt_outs, :conversation_id
|
||||
|
||||
add_index :mailboxer_notifications, :type
|
||||
add_index :mailboxer_notifications, %i(sender_id sender_type)
|
||||
|
||||
# We'll explicitly specify its name, as the auto-generated name is too long and exceeds 63
|
||||
# characters limitation.
|
||||
add_index :mailboxer_notifications, %i(notified_object_id notified_object_type),
|
||||
name: 'index_mailboxer_notifications_on_notified_object_id_and_type'
|
||||
|
||||
add_index :mailboxer_receipts, %i(receiver_id receiver_type)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
# This migration comes from mailboxer_engine (originally 20151103080417)
|
||||
class AddDeliveryTrackingInfoToMailboxerReceipts < ActiveRecord::Migration[4.2]
|
||||
def change
|
||||
add_column :mailboxer_receipts, :is_delivered, :boolean, default: false
|
||||
add_column :mailboxer_receipts, :delivery_method, :string
|
||||
add_column :mailboxer_receipts, :message_id, :string
|
||||
end
|
||||
end
|
||||
20
db/migrate/20190720000625_notifications_to_mailboxer.rb
Normal file
20
db/migrate/20190720000625_notifications_to_mailboxer.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class NotificationsToMailboxer < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
Mailboxer.setup do |config|
|
||||
# turn off emails
|
||||
config.uses_emails = false
|
||||
end
|
||||
Notification.find_in_batches.each do |group|
|
||||
group.each do |n|
|
||||
n.body = 'message has no body' if n.body.blank?
|
||||
receipt = n.sender.send_message(n.recipient, n.body, n.subject)
|
||||
# Copy over which messages are read
|
||||
receipt.conversation.receipts.each(&:mark_as_read) if n.read
|
||||
# copy over timestamps
|
||||
receipt.conversation.messages.each do |msg|
|
||||
msg.update!(created_at: n.created_at, updated_at: n.updated_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
5
db/migrate/20190721042146_paranoia_to_discard.rb
Normal file
5
db/migrate/20190721042146_paranoia_to_discard.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class ParanoiaToDiscard < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
rename_column :members, :deleted_at, :discarded_at
|
||||
end
|
||||
end
|
||||
65
db/schema.rb
65
db/schema.rb
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_07_12_234859) do
|
||||
ActiveRecord::Schema.define(version: 2019_07_21_042146) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -268,6 +268,62 @@ ActiveRecord::Schema.define(version: 2019_07_12_234859) do
|
||||
t.index ["member_id"], name: "index_likes_on_member_id"
|
||||
end
|
||||
|
||||
create_table "mailboxer_conversation_opt_outs", id: :serial, force: :cascade do |t|
|
||||
t.string "unsubscriber_type"
|
||||
t.integer "unsubscriber_id"
|
||||
t.integer "conversation_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["conversation_id"], name: "index_mailboxer_conversation_opt_outs_on_conversation_id"
|
||||
t.index ["unsubscriber_id", "unsubscriber_type"], name: "index_mailboxer_conversation_opt_outs_on_unsubscriber_id_type"
|
||||
end
|
||||
|
||||
create_table "mailboxer_conversations", id: :serial, force: :cascade do |t|
|
||||
t.string "subject", default: ""
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "mailboxer_notifications", id: :serial, force: :cascade do |t|
|
||||
t.string "type"
|
||||
t.text "body"
|
||||
t.string "subject", default: ""
|
||||
t.string "sender_type"
|
||||
t.integer "sender_id"
|
||||
t.integer "conversation_id"
|
||||
t.boolean "draft", default: false
|
||||
t.string "notification_code"
|
||||
t.string "notified_object_type"
|
||||
t.integer "notified_object_id"
|
||||
t.string "attachment"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "global", default: false
|
||||
t.datetime "expires"
|
||||
t.index ["conversation_id"], name: "index_mailboxer_notifications_on_conversation_id"
|
||||
t.index ["notified_object_id", "notified_object_type"], name: "index_mailboxer_notifications_on_notified_object_id_and_type"
|
||||
t.index ["notified_object_type", "notified_object_id"], name: "mailboxer_notifications_notified_object"
|
||||
t.index ["sender_id", "sender_type"], name: "index_mailboxer_notifications_on_sender_id_and_sender_type"
|
||||
t.index ["type"], name: "index_mailboxer_notifications_on_type"
|
||||
end
|
||||
|
||||
create_table "mailboxer_receipts", id: :serial, force: :cascade do |t|
|
||||
t.string "receiver_type"
|
||||
t.integer "receiver_id"
|
||||
t.integer "notification_id", null: false
|
||||
t.boolean "is_read", default: false
|
||||
t.boolean "trashed", default: false
|
||||
t.boolean "deleted", default: false
|
||||
t.string "mailbox_type", limit: 25
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "is_delivered", default: false
|
||||
t.string "delivery_method"
|
||||
t.string "message_id"
|
||||
t.index ["notification_id"], name: "index_mailboxer_receipts_on_notification_id"
|
||||
t.index ["receiver_id", "receiver_type"], name: "index_mailboxer_receipts_on_receiver_id_and_receiver_type"
|
||||
end
|
||||
|
||||
create_table "median_functions", id: :serial, force: :cascade do |t|
|
||||
end
|
||||
|
||||
@@ -307,11 +363,11 @@ ActiveRecord::Schema.define(version: 2019_07_12_234859) do
|
||||
t.integer "gardens_count"
|
||||
t.integer "harvests_count"
|
||||
t.integer "seeds_count"
|
||||
t.datetime "deleted_at"
|
||||
t.datetime "discarded_at"
|
||||
t.integer "photos_count"
|
||||
t.integer "forums_count"
|
||||
t.index ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true
|
||||
t.index ["deleted_at"], name: "index_members_on_deleted_at"
|
||||
t.index ["discarded_at"], name: "index_members_on_discarded_at"
|
||||
t.index ["email"], name: "index_members_on_email", unique: true
|
||||
t.index ["reset_password_token"], name: "index_members_on_reset_password_token", unique: true
|
||||
t.index ["slug"], name: "index_members_on_slug", unique: true
|
||||
@@ -457,6 +513,9 @@ ActiveRecord::Schema.define(version: 2019_07_12_234859) do
|
||||
end
|
||||
|
||||
add_foreign_key "harvests", "plantings"
|
||||
add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations", column: "conversation_id", name: "mb_opt_outs_on_conversations_id"
|
||||
add_foreign_key "mailboxer_notifications", "mailboxer_conversations", column: "conversation_id", name: "notifications_on_conversation_id"
|
||||
add_foreign_key "mailboxer_receipts", "mailboxer_notifications", column: "notification_id", name: "receipts_on_notification_id"
|
||||
add_foreign_key "photo_associations", "crops"
|
||||
add_foreign_key "photo_associations", "photos"
|
||||
add_foreign_key "plantings", "seeds", column: "parent_seed_id", name: "parent_seed", on_delete: :nullify
|
||||
|
||||
@@ -5,12 +5,6 @@ require 'rails_helper'
|
||||
RSpec.describe Api::V1::PlantingsController, type: :controller do
|
||||
subject { JSON.parse response.body }
|
||||
|
||||
let(:headers) do
|
||||
{
|
||||
'Accept' => 'application/vnd.api+json',
|
||||
'Content-Type' => 'application/vnd.api+json'
|
||||
}
|
||||
end
|
||||
let!(:member) { FactoryBot.create :member }
|
||||
|
||||
describe '#index' do
|
||||
|
||||
@@ -2,7 +2,6 @@ require 'rails_helper'
|
||||
|
||||
describe Charts::GardensController do
|
||||
include Devise::Test::ControllerHelpers
|
||||
let(:valid_params) { { name: 'My second Garden' } }
|
||||
|
||||
let(:garden) { FactoryBot.create :garden }
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@ RSpec.describe GardenTypesController, type: :controller do
|
||||
let(:valid_params) { { name: 'My second GardenType' } }
|
||||
let(:garden_type) { FactoryBot.create :garden_type }
|
||||
|
||||
let(:member) { FactoryBot.create(:member) }
|
||||
let(:admin_member) { FactoryBot.create(:admin) }
|
||||
|
||||
context "when not signed in" do
|
||||
describe 'GET new' do
|
||||
before { get :new, params: { id: garden_type.to_param } }
|
||||
|
||||
@@ -4,7 +4,6 @@ describe LikesController do
|
||||
let(:like) { FactoryBot.create :like, member: member }
|
||||
let(:member) { FactoryBot.create(:member) }
|
||||
let(:blogpost) { FactoryBot.create(:post) }
|
||||
let(:mypost) { FactoryBot.create(:post, author: member) }
|
||||
|
||||
before { sign_in member }
|
||||
|
||||
@@ -22,7 +21,6 @@ describe LikesController do
|
||||
end
|
||||
|
||||
describe "Liking your own post" do
|
||||
let(:blogpost) { FactoryBot.create(:post, author: member) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe NotificationsController do
|
||||
login_member
|
||||
|
||||
def valid_attributes
|
||||
{
|
||||
"recipient_id" => subject.current_member.id,
|
||||
"sender_id" => FactoryBot.create(:member).id,
|
||||
"subject" => 'test'
|
||||
}
|
||||
end
|
||||
|
||||
describe "GET index" do
|
||||
it "assigns all notifications as @notifications" do
|
||||
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
|
||||
get :index, params: {}
|
||||
assigns(:notifications).should eq([notification])
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET show" do
|
||||
it "assigns the requested notification as @notification" do
|
||||
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
|
||||
get :show, params: { id: notification.to_param }
|
||||
assigns(:notification).should eq(notification)
|
||||
end
|
||||
|
||||
it "assigns the reply link for a post comment" do
|
||||
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
|
||||
|
||||
get :show, params: { id: notification.to_param }
|
||||
assigns(:reply_link).should_not be_nil
|
||||
assigns(:reply_link).should eq new_comment_url(
|
||||
post_id: notification.post.id
|
||||
)
|
||||
end
|
||||
|
||||
it "marks notifications as read" do
|
||||
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
|
||||
get :show, params: { id: notification.to_param }
|
||||
# we need to fetch it from the db again, can't test against the old one
|
||||
n = Notification.find(notification.id)
|
||||
n.read.should eq true
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET reply" do
|
||||
it "marks notifications as read" do
|
||||
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
|
||||
get :reply, params: { notification_id: notification.to_param }
|
||||
# we need to fetch it from the db again, can't test against the old one
|
||||
n = Notification.find(notification.id)
|
||||
n.read.should eq true
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET new" do
|
||||
it "assigns a recipient" do
|
||||
@recipient = FactoryBot.create(:member)
|
||||
get :new, params: { recipient_id: @recipient.id }
|
||||
expect(assigns(:recipient)).to be_an_instance_of(Member)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST create" do
|
||||
describe "with valid params" do
|
||||
it "redirects to the recipient's profile" do
|
||||
@recipient = FactoryBot.create(:member)
|
||||
post :create, params: { notification: { recipient_id: @recipient.id, subject: 'foo' } }
|
||||
response.should redirect_to(notifications_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +1,8 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "forums", js: true do
|
||||
context "as an admin user" do
|
||||
let(:member) { create :admin_member }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
end
|
||||
|
||||
context 'signed in admin' do
|
||||
include_context 'signed in admin'
|
||||
it "navigating to forum admin with js" do
|
||||
visit admin_path
|
||||
Percy.snapshot(page, name: 'Admin page')
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "forums", js: true do
|
||||
context "as an admin user" do
|
||||
let(:member) { create :admin_member }
|
||||
let(:forum) { create :forum }
|
||||
include_context 'signed in admin'
|
||||
let(:forum) { create :forum }
|
||||
|
||||
describe "navigating to forum admin with js" do
|
||||
before do
|
||||
login_as member
|
||||
end
|
||||
|
||||
it "navigating to forum admin with js" do
|
||||
visit admin_path
|
||||
within 'ul#site_admin' do
|
||||
within 'nav#site_admin' do
|
||||
click_link "Forums"
|
||||
end
|
||||
expect(current_path).to eq forums_path
|
||||
expect(page).to have_link "New forum"
|
||||
end
|
||||
it { expect(current_path).to eq forums_path }
|
||||
it { expect(page).to have_link "New forum" }
|
||||
end
|
||||
|
||||
it "adding a forum" do
|
||||
describe "adding a forum" do
|
||||
before do
|
||||
visit forums_path
|
||||
click_link "New forum"
|
||||
expect(current_path).to eq new_forum_path
|
||||
fill_in 'Name', with: 'Discussion'
|
||||
fill_in 'Description', with: "this is a new forum"
|
||||
click_button 'Save'
|
||||
expect(current_path).to eq forum_path(Forum.last)
|
||||
expect(page).to have_content 'Forum was successfully created'
|
||||
end
|
||||
it { expect(current_path).to eq forum_path(Forum.last) }
|
||||
it { expect(page).to have_content 'Forum was successfully created' }
|
||||
end
|
||||
|
||||
it 'editing forum' do
|
||||
describe 'editing forum' do
|
||||
before do
|
||||
visit forum_path forum
|
||||
click_link 'Edit'
|
||||
fill_in 'Name', with: 'Something else'
|
||||
click_button 'Save'
|
||||
forum.reload
|
||||
expect(current_path).to eq forum_path(forum)
|
||||
expect(page).to have_content 'Forum was successfully updated'
|
||||
expect(page).to have_content 'Something else'
|
||||
end
|
||||
it { expect(current_path).to eq forum_path(forum) }
|
||||
it { expect(page).to have_content 'Forum was successfully updated' }
|
||||
it { expect(page).to have_content 'Something else' }
|
||||
end
|
||||
|
||||
it 'deleting forum' do
|
||||
describe 'deleting forum' do
|
||||
before do
|
||||
visit forum_path forum
|
||||
accept_confirm do
|
||||
click_link 'Delete'
|
||||
end
|
||||
expect(current_path).to eq forums_path
|
||||
expect(page).to have_content 'Forum was successfully deleted'
|
||||
end
|
||||
it { expect(current_path).to eq forums_path }
|
||||
it { expect(page).to have_content 'Forum was successfully deleted' }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "cms admin" do
|
||||
let(:member) { create :member }
|
||||
let(:admin_member) { create :admin_member }
|
||||
|
||||
it "can't view CMS admin if not signed in" do
|
||||
visit comfy_admin_cms_path
|
||||
expect(current_path).to eq root_path
|
||||
expect(page).to have_content "Please sign in as an admin user"
|
||||
end
|
||||
|
||||
it "can't view CMS admin if not an admin member" do
|
||||
# sign in as an ordinary member
|
||||
login_as member
|
||||
visit comfy_admin_cms_path
|
||||
expect(current_path).to eq root_path
|
||||
expect(page).to have_content "Please sign in as an admin user"
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
it "can't view CMS admin if not an admin member" do
|
||||
visit comfy_admin_cms_path
|
||||
expect(current_path).to eq root_path
|
||||
expect(page).to have_content "Please sign in as an admin user"
|
||||
end
|
||||
end
|
||||
|
||||
it "admin members can view CMS admin area" do
|
||||
login_as admin_member
|
||||
visit comfy_admin_cms_path
|
||||
expect(current_path).to match(/#{comfy_admin_cms_path}/) # match any CMS admin page
|
||||
context 'admin' do
|
||||
include_context 'signed in admin'
|
||||
it "admin members can view CMS admin area" do
|
||||
visit comfy_admin_cms_path
|
||||
expect(current_path).to match(/#{comfy_admin_cms_path}/) # match any CMS admin page
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'Commenting on a post' do
|
||||
let(:member) { create :member }
|
||||
let(:post) { create :post, author: member }
|
||||
include_context 'signed in member'
|
||||
let(:member) { create :member }
|
||||
let(:post) { create :post, author: member }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
visit new_comment_path post_id: post.id
|
||||
end
|
||||
before { visit new_comment_path post_id: post.id }
|
||||
|
||||
it "creating a comment" do
|
||||
fill_in "comment_body", with: "This is a sample test for comment"
|
||||
|
||||
42
spec/features/conversations/index_spec.rb
Normal file
42
spec/features/conversations/index_spec.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Conversations", :js do
|
||||
let(:sender) { create :member }
|
||||
let(:recipient) { create :member, login_name: 'beyonce' }
|
||||
|
||||
before do
|
||||
sender.send_message(recipient, "this is the body", "something i want to say")
|
||||
login_as recipient
|
||||
end
|
||||
|
||||
describe "Read conversations list" do
|
||||
before do
|
||||
visit root_path
|
||||
click_link 'Your Stuff'
|
||||
click_link 'Inbox'
|
||||
end
|
||||
it { expect(page).to have_content 'something i want to say' }
|
||||
it { Percy.snapshot(page, name: 'conversations#index') }
|
||||
|
||||
describe 'deleting' do
|
||||
before do
|
||||
# delete button
|
||||
click_link class: 'delete'
|
||||
end
|
||||
|
||||
describe 'view trash' do
|
||||
before { click_link 'trash' }
|
||||
it { expect(page).to have_content 'something i want to say' }
|
||||
describe 'restore conversation' do
|
||||
before { click_link class: 'restore' }
|
||||
it { expect(page).not_to have_content 'something i want to say' }
|
||||
|
||||
describe 'conversation was restored' do
|
||||
before { click_link 'inbox' }
|
||||
it { expect(page).to have_content 'something i want to say' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
spec/features/conversations/show_spec.rb
Normal file
32
spec/features/conversations/show_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Conversations", :js do
|
||||
let(:sender) { create :member }
|
||||
let(:recipient) { create :member, login_name: 'beyonce' }
|
||||
|
||||
before do
|
||||
sender.send_message(recipient, "this is the body", "something i want to say")
|
||||
login_as recipient
|
||||
end
|
||||
|
||||
describe 'view conversation thread' do
|
||||
before do
|
||||
visit root_path
|
||||
click_link 'Your Stuff'
|
||||
click_link 'Inbox'
|
||||
click_link 'something i want to say'
|
||||
end
|
||||
|
||||
it { expect(page).to have_content 'this is the body' }
|
||||
it { expect(page).to have_link sender.login_name }
|
||||
it { Percy.snapshot(page, name: 'conversations#show') }
|
||||
|
||||
describe 'Replying to the conversation' do
|
||||
before do
|
||||
fill_in :body, with: 'i like this too'
|
||||
click_button 'Send'
|
||||
end
|
||||
it { expect(page).to have_content "i like this too" }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,26 +4,23 @@ describe "Alternate names", js: true do
|
||||
let!(:alternate_eggplant) { create :alternate_eggplant }
|
||||
let(:crop) { alternate_eggplant.crop }
|
||||
|
||||
it "Display alternate names on crop page" do
|
||||
visit crop_path(alternate_eggplant.crop)
|
||||
# expect(page.status_code).to equal 200
|
||||
expect(page).to have_content alternate_eggplant.name
|
||||
end
|
||||
|
||||
it "Index page for alternate names" do
|
||||
visit alternate_names_path
|
||||
expect(page).to have_content alternate_eggplant.name
|
||||
end
|
||||
|
||||
context "User is a crop wrangler" do
|
||||
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
|
||||
let(:member) { crop_wranglers.first }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
shared_examples 'show alt names' do
|
||||
it "can see alternate names on crop page" do
|
||||
visit crop_path(alternate_eggplant.crop)
|
||||
# expect(page.status_code).to equal 200
|
||||
expect(page).to have_content alternate_eggplant.name
|
||||
end
|
||||
|
||||
it "Crop wranglers can edit alternate names" do
|
||||
it "can see page for alternate names" do
|
||||
visit alternate_names_path
|
||||
expect(page).to have_content alternate_eggplant.name
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'edit alt names' do
|
||||
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
|
||||
|
||||
it "can edit alternate names" do
|
||||
visit crop_path(crop)
|
||||
# expect(page.status_code).to equal 200
|
||||
expect(page).to have_content "CROP WRANGLER"
|
||||
@@ -41,7 +38,7 @@ describe "Alternate names", js: true do
|
||||
expect(page).to have_content 'Alternate name was successfully updated'
|
||||
end
|
||||
|
||||
it "Crop wranglers can delete alternate names" do
|
||||
it "can delete alternate names" do
|
||||
visit crop_path(alternate_eggplant.crop)
|
||||
click_link('aubergine', href: '#')
|
||||
accept_confirm do
|
||||
@@ -51,7 +48,7 @@ describe "Alternate names", js: true do
|
||||
expect(page).to have_content 'Alternate name was successfully deleted'
|
||||
end
|
||||
|
||||
it "Crop wranglers can add alternate names" do
|
||||
it "can add alternate names" do
|
||||
visit crop_path(crop)
|
||||
expect(page).to have_link "Add",
|
||||
href: new_alternate_name_path(crop_id: crop.id)
|
||||
@@ -65,7 +62,7 @@ describe "Alternate names", js: true do
|
||||
expect(page).to have_content 'Alternate name was successfully created'
|
||||
end
|
||||
|
||||
it "The show-alternate-name page works" do
|
||||
it "shows alternate-name page" do
|
||||
visit alternate_name_path(alternate_eggplant)
|
||||
# expect(page.status_code).to equal 200
|
||||
expect(page).to have_content alternate_eggplant.crop.name
|
||||
@@ -82,4 +79,25 @@ describe "Alternate names", js: true do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'Anonymous' do
|
||||
include_examples 'show alt names'
|
||||
end
|
||||
|
||||
context 'Signed in member' do
|
||||
include_context 'signed in member'
|
||||
include_examples 'show alt names'
|
||||
end
|
||||
|
||||
context 'Crop wrangler' do
|
||||
include_context 'signed in crop wrangler'
|
||||
include_examples 'show alt names'
|
||||
include_examples 'edit alt names'
|
||||
end
|
||||
|
||||
context 'Admin' do
|
||||
include_context 'signed in admin'
|
||||
include_examples 'show alt names'
|
||||
include_examples 'edit alt names'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,35 +6,51 @@ describe "browse crops" do
|
||||
let!(:pending_crop) { FactoryBot.create :crop_request }
|
||||
let!(:rejected_crop) { FactoryBot.create :rejected_crop }
|
||||
|
||||
it "has a form for sorting by" do
|
||||
visit crops_path
|
||||
expect(page).to have_css "select#sort"
|
||||
end
|
||||
|
||||
it "shows a list of crops" do
|
||||
crop1 = tomato
|
||||
visit crops_path
|
||||
expect(page).to have_content crop1.name
|
||||
end
|
||||
|
||||
it "pending crops are not listed" do
|
||||
visit crops_path
|
||||
expect(page).not_to have_content pending_crop.name
|
||||
end
|
||||
|
||||
it "rejected crops are not listed" do
|
||||
visit crops_path
|
||||
expect(page).not_to have_content rejected_crop.name
|
||||
end
|
||||
|
||||
context "logged in and crop wrangler" do
|
||||
before do
|
||||
login_as FactoryBot.create(:crop_wrangling_member)
|
||||
visit crops_path
|
||||
shared_examples 'shows crops' do
|
||||
before { visit crops_path }
|
||||
it "has a form for sorting by" do
|
||||
expect(page).to have_css "select#sort"
|
||||
end
|
||||
|
||||
it "shows a list of crops" do
|
||||
expect(page).to have_content tomato.name
|
||||
end
|
||||
|
||||
it "pending crops are not listed" do
|
||||
expect(page).not_to have_content pending_crop.name
|
||||
end
|
||||
|
||||
it "rejected crops are not listed" do
|
||||
expect(page).not_to have_content rejected_crop.name
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'add new crop' do
|
||||
it "shows a new crop link" do
|
||||
expect(page).to have_link "Add New Crop"
|
||||
end
|
||||
end
|
||||
|
||||
context 'anon' do
|
||||
include_examples 'shows crops'
|
||||
it { expect(page).not_to have_link "Add New Crop" }
|
||||
end
|
||||
|
||||
context 'member' do
|
||||
include_context 'signed in member'
|
||||
include_examples 'shows crops'
|
||||
include_examples 'add new crop'
|
||||
end
|
||||
|
||||
context 'wrangler' do
|
||||
include_context 'signed in crop wrangler'
|
||||
include_examples 'shows crops'
|
||||
include_examples 'add new crop'
|
||||
end
|
||||
|
||||
context 'admin' do
|
||||
include_context 'signed in admin'
|
||||
include_examples 'shows crops'
|
||||
include_examples 'add new crop'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,35 +1,66 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Crop - " do
|
||||
let!(:crop_wrangler) { FactoryBot.create :crop_wrangling_member }
|
||||
let!(:member) { FactoryBot.create :member }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
visit new_crop_path
|
||||
describe "Crop", js: true do
|
||||
shared_examples 'fill in form' do
|
||||
before do
|
||||
visit new_crop_path
|
||||
within "form#new_crop" do
|
||||
fill_in "crop_name", with: "Philippine flower"
|
||||
fill_in "en_wikipedia_url", with: "https://en.wikipedia.org/wiki/Jasminum_sambac"
|
||||
click_button "add-sci_name-row"
|
||||
fill_in "sci_name[1]", with: "Jasminum sambac 1"
|
||||
fill_in "sci_name[2]", with: "Jasminum sambac 2"
|
||||
fill_in "alt_name[1]", with: "Sampaguita"
|
||||
click_button "add-alt_name-row"
|
||||
click_button "add-alt_name-row"
|
||||
fill_in "alt_name[2]", with: "Manol"
|
||||
click_button "add-alt_name-row"
|
||||
fill_in "alt_name[3]", with: "Jazmin"
|
||||
fill_in "alt_name[4]", with: "Matsurika"
|
||||
end
|
||||
end
|
||||
end
|
||||
shared_examples 'request crop' do
|
||||
describe "requesting a crop with multiple scientific and alternate name" do
|
||||
include_examples 'fill in form'
|
||||
before do
|
||||
within "form#new_crop" do
|
||||
fill_in "request_notes", with: "This is the Philippine national flower."
|
||||
click_button "Save"
|
||||
end
|
||||
end
|
||||
it { expect(page).to have_content 'crop was successfully created.' }
|
||||
it { expect(page).to have_content "This crop is currently pending approval." }
|
||||
it { expect(page).to have_content "Jasminum sambac 2" }
|
||||
it { expect(page).to have_content "Matsurika" }
|
||||
end
|
||||
end
|
||||
shared_examples 'create crop' do
|
||||
describe "creating a crop with multiple scientific and alternate name" do
|
||||
include_examples 'fill in form'
|
||||
before do
|
||||
click_button "Save"
|
||||
end
|
||||
it { expect(page).to have_content 'crop was successfully created.' }
|
||||
it { expect(page).to have_content "Jasminum sambac 2" }
|
||||
it { expect(page).to have_content "Matsurika" }
|
||||
end
|
||||
end
|
||||
|
||||
it "creating a crop with multiple scientific and alternate name", :js do
|
||||
within "form#new_crop" do
|
||||
fill_in "crop_name", with: "Philippine flower"
|
||||
fill_in "en_wikipedia_url", with: "https://en.wikipedia.org/wiki/Jasminum_sambac"
|
||||
click_button "add-sci_name-row"
|
||||
fill_in "sci_name[1]", with: "Jasminum sambac 1"
|
||||
fill_in "sci_name[2]", with: "Jasminum sambac 2"
|
||||
fill_in "alt_name[1]", with: "Sampaguita"
|
||||
click_button "add-alt_name-row"
|
||||
click_button "add-alt_name-row"
|
||||
fill_in "alt_name[2]", with: "Manol"
|
||||
click_button "add-alt_name-row"
|
||||
fill_in "alt_name[3]", with: "Jazmin"
|
||||
fill_in "alt_name[4]", with: "Matsurika"
|
||||
fill_in "request_notes", with: "This is the Philippine national flower."
|
||||
click_button "Save"
|
||||
end
|
||||
|
||||
expect(page).to have_content 'crop was successfully created.'
|
||||
expect(page).to have_content "This crop is currently pending approval."
|
||||
expect(page).to have_content "Jasminum sambac 2"
|
||||
expect(page).to have_content "Matsurika"
|
||||
context 'anon' do
|
||||
before { visit new_crop_path }
|
||||
it { expect(page).to have_content 'You need to sign in' }
|
||||
end
|
||||
context 'member' do
|
||||
include_context 'signed in member'
|
||||
include_examples 'request crop'
|
||||
end
|
||||
context 'crop wrangler' do
|
||||
include_context 'signed in crop wrangler'
|
||||
include_examples 'create crop'
|
||||
end
|
||||
context 'admin' do
|
||||
include_context 'signed in admin'
|
||||
include_examples 'create crop'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,13 +20,8 @@ describe "crop detail page", js: true do
|
||||
end
|
||||
end
|
||||
|
||||
context "signed in member" do
|
||||
let(:member) { create :member }
|
||||
|
||||
before do
|
||||
login_as(member)
|
||||
end
|
||||
|
||||
context 'signed in' do
|
||||
include_context "signed in member"
|
||||
context "action buttons" do
|
||||
before { subject }
|
||||
|
||||
@@ -78,25 +73,25 @@ describe "crop detail page", js: true do
|
||||
end
|
||||
|
||||
context "seed quantity for a crop" do
|
||||
let(:member) { create :member }
|
||||
let(:seed) { create :seed, crop: crop, quantity: 20, owner: member }
|
||||
let(:seed) { create :seed, crop: crop, quantity: 20 }
|
||||
|
||||
it "User not signed in" do
|
||||
visit crop_path(seed.crop)
|
||||
expect(page).not_to have_content "You have 20 seeds"
|
||||
end
|
||||
|
||||
it "User signed in" do
|
||||
login_as(member)
|
||||
visit crop_path(seed.crop)
|
||||
expect(page).to have_link "You have 20 seeds of this crop."
|
||||
end
|
||||
|
||||
it "click link to your owned seeds" do
|
||||
login_as(member)
|
||||
visit crop_path(seed.crop)
|
||||
click_link "You have 20 seeds of this crop."
|
||||
expect(current_path).to eq member_seeds_path(member_slug: member.slug)
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
before { seed.update! owner: member }
|
||||
it "User signed in" do
|
||||
visit crop_path(seed.crop)
|
||||
expect(page).to have_link "You have 20 seeds of this crop."
|
||||
end
|
||||
it "click link to your owned seeds" do
|
||||
visit crop_path(seed.crop)
|
||||
click_link "You have 20 seeds of this crop."
|
||||
expect(current_path).to eq member_seeds_path(member_slug: member.slug)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3,20 +3,20 @@ require 'rails_helper'
|
||||
describe "crop detail page", js: true do
|
||||
subject { page }
|
||||
|
||||
let!(:member) { FactoryBot.create :member }
|
||||
let!(:owner_member) { FactoryBot.create :member }
|
||||
|
||||
let!(:crop) { FactoryBot.create :crop }
|
||||
|
||||
let!(:planting) { FactoryBot.create :planting, crop: crop, owner: member }
|
||||
let!(:harvest) { FactoryBot.create :harvest, crop: crop, owner: member }
|
||||
let!(:seed) { FactoryBot.create :seed, crop: crop, owner: member }
|
||||
let!(:planting) { FactoryBot.create :planting, crop: crop, owner: owner_member }
|
||||
let!(:harvest) { FactoryBot.create :harvest, crop: crop, owner: owner_member }
|
||||
let!(:seed) { FactoryBot.create :seed, crop: crop, owner: owner_member }
|
||||
|
||||
let!(:photo1) { FactoryBot.create(:photo, owner: member) }
|
||||
let!(:photo2) { FactoryBot.create(:photo, owner: member) }
|
||||
let!(:photo3) { FactoryBot.create(:photo, owner: member) }
|
||||
let!(:photo4) { FactoryBot.create(:photo, owner: member) }
|
||||
let!(:photo5) { FactoryBot.create(:photo, owner: member) }
|
||||
let!(:photo6) { FactoryBot.create(:photo, owner: member) }
|
||||
let!(:photo1) { FactoryBot.create(:photo, owner: owner_member) }
|
||||
let!(:photo2) { FactoryBot.create(:photo, owner: owner_member) }
|
||||
let!(:photo3) { FactoryBot.create(:photo, owner: owner_member) }
|
||||
let!(:photo4) { FactoryBot.create(:photo, owner: owner_member) }
|
||||
let!(:photo5) { FactoryBot.create(:photo, owner: owner_member) }
|
||||
let!(:photo6) { FactoryBot.create(:photo, owner: owner_member) }
|
||||
|
||||
before do
|
||||
planting.photos << photo1
|
||||
@@ -50,14 +50,13 @@ describe "crop detail page", js: true do
|
||||
end
|
||||
|
||||
context "when signed in" do
|
||||
before { login_as(FactoryBot.create(:member)) }
|
||||
|
||||
include_context 'signed in member'
|
||||
include_examples "shows photos"
|
||||
end
|
||||
|
||||
context "when signed in as photos owner" do
|
||||
before { login_as(member) }
|
||||
|
||||
include_context 'signed in member'
|
||||
let(:member) { owner_member }
|
||||
include_examples "shows photos"
|
||||
end
|
||||
|
||||
|
||||
@@ -2,21 +2,19 @@ require 'rails_helper'
|
||||
|
||||
describe "crop wranglers", js: true do
|
||||
context "signed in wrangler" do
|
||||
include_context 'signed in crop wrangler'
|
||||
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
|
||||
let(:wrangler) { crop_wranglers.first }
|
||||
let!(:crops) { create_list :crop, 2 }
|
||||
let!(:requested_crop) { create :crop_request }
|
||||
let!(:rejected_crop) { create :rejected_crop }
|
||||
|
||||
before { login_as wrangler }
|
||||
|
||||
it "sees crop wranglers listed on the crop wrangler page" do
|
||||
visit root_path
|
||||
click_link 'Admin'
|
||||
click_link 'Crop Wrangling'
|
||||
|
||||
within '.crop_wranglers' do
|
||||
expect(page).to have_content 'Crop Wranglers:'
|
||||
expect(page).to have_content 'Crop Wranglers'
|
||||
crop_wranglers.each do |crop_wrangler|
|
||||
expect(page).to have_link crop_wrangler.login_name, href: member_path(crop_wrangler)
|
||||
end
|
||||
@@ -67,22 +65,20 @@ describe "crop wranglers", js: true do
|
||||
expect(page).to have_content "This crop was rejected for the following reason: Totally fake"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "signed in non-wrangler" do
|
||||
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
|
||||
let(:member) { create :member }
|
||||
context "signed in non-wrangler" do
|
||||
include_context 'signed in member'
|
||||
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
|
||||
|
||||
before { login_as member }
|
||||
it "can't see wrangling page without js", js: false do
|
||||
visit root_path
|
||||
expect(page).not_to have_link "Crop Wrangling"
|
||||
end
|
||||
|
||||
it "can't see wrangling page without js", js: false do
|
||||
visit root_path
|
||||
expect(page).not_to have_link "Crop Wrangling"
|
||||
end
|
||||
|
||||
it "can't see wrangling page with js" do
|
||||
visit root_path
|
||||
click_link member.login_name
|
||||
expect(page).not_to have_link "Crop Wrangling"
|
||||
end
|
||||
it "can't see wrangling page with js" do
|
||||
visit root_path
|
||||
click_link member.login_name
|
||||
expect(page).not_to have_link "Crop Wrangling"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "crop wrangling button" do
|
||||
let(:crop_wrangler) { create :crop_wrangling_member }
|
||||
let(:member) { create :member }
|
||||
|
||||
context "crop wrangling button" do
|
||||
before do
|
||||
login_as crop_wrangler
|
||||
visit crops_path
|
||||
end
|
||||
|
||||
it "has a link to crop wrangling page" do
|
||||
expect(page).to have_link "Wrangle Crops", href: wrangle_crops_path
|
||||
end
|
||||
context 'not signed in' do
|
||||
before { visit crops_path }
|
||||
it { expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path }
|
||||
end
|
||||
|
||||
context "crop wrangling button" do
|
||||
before do
|
||||
login_as member
|
||||
visit crops_path
|
||||
end
|
||||
context "signed in, but not a crop wrangler" do
|
||||
include_context 'signed in member'
|
||||
before { visit crops_path }
|
||||
it { expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path }
|
||||
end
|
||||
|
||||
it "has no link to crop wrangling page" do
|
||||
expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path
|
||||
end
|
||||
context "signed in crop wrangler" do
|
||||
include_context 'signed in crop wrangler'
|
||||
before { visit crops_path }
|
||||
it { expect(page).to have_link "Wrangle Crops", href: wrangle_crops_path }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Delete crop spec" do
|
||||
context "As a crop wrangler" do
|
||||
let(:wrangler) { FactoryBot.create :crop_wrangling_member }
|
||||
shared_examples 'delete crop' do
|
||||
let!(:pending_crop) { FactoryBot.create :crop_request }
|
||||
let!(:approved_crop) { FactoryBot.create :crop }
|
||||
|
||||
before { login_as wrangler }
|
||||
|
||||
it "Delete approved crop" do
|
||||
it "deletes approved crop" do
|
||||
visit crop_path(approved_crop)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
@@ -17,7 +13,7 @@ describe "Delete crop spec" do
|
||||
expect(page).to have_content "crop was successfully destroyed"
|
||||
end
|
||||
|
||||
it "Delete pending crop" do
|
||||
it "deletes pending crop" do
|
||||
visit crop_path(pending_crop)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
@@ -26,4 +22,14 @@ describe "Delete crop spec" do
|
||||
expect(page).to have_content "crop was successfully destroyed"
|
||||
end
|
||||
end
|
||||
|
||||
context "As a crop wrangler" do
|
||||
include_context 'signed in crop wrangler'
|
||||
include_examples 'delete crop'
|
||||
end
|
||||
|
||||
context 'admin' do
|
||||
include_context 'signed in admin'
|
||||
include_examples 'delete crop'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,13 +2,7 @@ require 'rails_helper'
|
||||
|
||||
describe "Requesting a new crop" do
|
||||
context "As a regular member" do
|
||||
let(:member) { create :member }
|
||||
let!(:wrangler) { create :crop_wrangling_member }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
end
|
||||
|
||||
include_context 'signed in member'
|
||||
it "Submit request" do
|
||||
visit new_crop_path
|
||||
fill_in "Name", with: "Couch potato"
|
||||
@@ -20,12 +14,10 @@ describe "Requesting a new crop" do
|
||||
end
|
||||
|
||||
context "As a crop wrangler" do
|
||||
let(:wrangler) { create :crop_wrangling_member }
|
||||
include_context 'signed in crop wrangler'
|
||||
let!(:crop) { create :crop_request }
|
||||
let!(:already_approved) { create :crop }
|
||||
|
||||
before { login_as wrangler }
|
||||
|
||||
it "Approve a request" do
|
||||
visit edit_crop_path(crop)
|
||||
select "approved", from: "Approval status"
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Crop - " do
|
||||
let(:member) { create :member }
|
||||
describe "Requesting Crops" do
|
||||
let!(:requested_crop) { create :crop, requester: member, approval_status: 'pending', name: 'puha for dinner' }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
visit requested_crops_path
|
||||
end
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
before { visit requested_crops_path }
|
||||
|
||||
it "creating a crop with multiple scientific and alternate name", :js do
|
||||
expect(page).to have_content "puha for dinner"
|
||||
it "creating a crop with multiple scientific and alternate name", :js do
|
||||
expect(page).to have_content "puha for dinner"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,12 +18,8 @@ describe "Scientific names", js: true do
|
||||
|
||||
context "User is a crop wrangler" do
|
||||
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
|
||||
let(:member) { crop_wranglers.first }
|
||||
|
||||
before do
|
||||
login_as(member)
|
||||
end
|
||||
|
||||
include_context 'signed in crop wrangler'
|
||||
it "Crop wranglers can edit scientific names" do
|
||||
visit crop_path(crop)
|
||||
# expect(page.status_code).to equal 200
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "browse crops" do
|
||||
let(:tomato) { create :tomato }
|
||||
let(:maize) { create :maize }
|
||||
let(:pending_crop) { create :crop_request }
|
||||
let(:rejected_crop) { create :rejected_crop }
|
||||
let(:tomato) { create :tomato }
|
||||
|
||||
it "Show crop info" do
|
||||
visit crop_path(tomato)
|
||||
|
||||
@@ -5,9 +5,7 @@ describe "Gardens" do
|
||||
context 'logged in' do
|
||||
subject { page }
|
||||
|
||||
let(:member) { FactoryBot.create :member }
|
||||
before { login_as member }
|
||||
|
||||
include_context 'signed in member'
|
||||
let(:garden) { member.gardens.first }
|
||||
let(:other_member_garden) { FactoryBot.create :garden }
|
||||
|
||||
|
||||
@@ -2,37 +2,35 @@ require 'rails_helper'
|
||||
require 'custom_matchers'
|
||||
|
||||
describe "Gardens", :js do
|
||||
let(:member) { FactoryBot.create :member }
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
before { visit new_garden_path }
|
||||
|
||||
before do
|
||||
login_as member
|
||||
visit new_garden_path
|
||||
end
|
||||
it "has the required fields help text" do
|
||||
expect(page).to have_content "* denotes a required field"
|
||||
end
|
||||
|
||||
it "has the required fields help text" do
|
||||
expect(page).to have_content "* denotes a required field"
|
||||
end
|
||||
it "displays required and optional fields properly" do
|
||||
expect(page).to have_selector ".required", text: "Name"
|
||||
expect(page).to have_selector 'textarea#garden_description'
|
||||
expect(page).to have_selector 'input#garden_location'
|
||||
expect(page).to have_selector 'input#garden_area'
|
||||
end
|
||||
|
||||
it "displays required and optional fields properly" do
|
||||
expect(page).to have_selector ".required", text: "Name"
|
||||
expect(page).to have_selector 'textarea#garden_description'
|
||||
expect(page).to have_selector 'input#garden_location'
|
||||
expect(page).to have_selector 'input#garden_area'
|
||||
end
|
||||
it "Create new garden" do
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
expect(page).to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "New garden"
|
||||
end
|
||||
|
||||
it "Create new garden" do
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
expect(page).to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "New garden"
|
||||
end
|
||||
|
||||
it "Refuse to create new garden with negative area" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "Negative Garden"
|
||||
fill_in "Area", with: -5
|
||||
click_button "Save"
|
||||
expect(page).not_to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "Area must be greater than or equal to 0"
|
||||
it "Refuse to create new garden with negative area" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "Negative Garden"
|
||||
fill_in "Area", with: -5
|
||||
click_button "Save"
|
||||
expect(page).not_to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "Area must be greater than or equal to 0"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,121 +1,120 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe "Planting a crop", js: true do
|
||||
# name is aaa to ensure it is ordered first
|
||||
let!(:garden) { create :garden, name: 'aaa' }
|
||||
let!(:planting) { create :planting, garden: garden, owner: garden.owner, planted_at: Date.parse("2013-3-10") }
|
||||
let!(:tomato) { create :tomato }
|
||||
let!(:finished_planting) { create :finished_planting, owner: garden.owner, garden: garden, crop: tomato }
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
# name is aaa to ensure it is ordered first
|
||||
let!(:garden) { create :garden, name: 'aaa', owner: member }
|
||||
let!(:planting) { create :planting, garden: garden, owner: garden.owner, planted_at: Date.parse("2013-3-10") }
|
||||
let!(:tomato) { create :tomato }
|
||||
let!(:finished_planting) { create :finished_planting, owner: garden.owner, garden: garden, crop: tomato }
|
||||
|
||||
before do
|
||||
login_as garden.owner
|
||||
end
|
||||
|
||||
it "View gardens" do
|
||||
visit gardens_path
|
||||
expect(page).to have_content "Everyone's gardens"
|
||||
within '.layout-actions' do
|
||||
click_link "My gardens"
|
||||
end
|
||||
expect(page).to have_content "#{garden.owner.login_name}'s gardens"
|
||||
within '.layout-actions' do
|
||||
click_link "Everyone's gardens"
|
||||
end
|
||||
expect(page).to have_content "Everyone's gardens"
|
||||
end
|
||||
|
||||
it "Marking a garden as inactive" do
|
||||
visit garden_path(garden)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
click_link "Mark as inactive"
|
||||
end
|
||||
expect(page).to have_content "Garden was successfully updated"
|
||||
expect(page).to have_content "This garden is inactive"
|
||||
|
||||
click_link 'Actions'
|
||||
expect(page).to have_content "Mark as active"
|
||||
expect(page).not_to have_content "Mark as inactive"
|
||||
end
|
||||
|
||||
it "List only active gardens" do
|
||||
visit garden_path(garden)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
click_link "Mark as inactive"
|
||||
end
|
||||
visit gardens_path
|
||||
expect(page).not_to have_link garden_path(garden)
|
||||
end
|
||||
|
||||
it "Create new garden" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
expect(page).to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "New garden"
|
||||
end
|
||||
|
||||
it "Refuse to create new garden with negative area" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "Negative Garden"
|
||||
fill_in "Area", with: -5
|
||||
click_button "Save"
|
||||
expect(page).not_to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "Area must be greater than or equal to 0"
|
||||
end
|
||||
|
||||
context "Clicking edit from the index page" do
|
||||
before do
|
||||
it "View gardens" do
|
||||
visit gardens_path
|
||||
expect(page).to have_content "Everyone's gardens"
|
||||
within '.layout-actions' do
|
||||
click_link "My gardens"
|
||||
end
|
||||
expect(page).to have_content "#{garden.owner.login_name}'s gardens"
|
||||
within '.layout-actions' do
|
||||
click_link "Everyone's gardens"
|
||||
end
|
||||
expect(page).to have_content "Everyone's gardens"
|
||||
end
|
||||
|
||||
it "button on index to edit garden" do
|
||||
first('a#garden-actions-button').click
|
||||
click_link href: edit_garden_path(garden)
|
||||
expect(page).to have_content 'Edit garden'
|
||||
end
|
||||
end
|
||||
|
||||
it "Edit garden" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
click_link 'Actions'
|
||||
within '.garden-actions' do
|
||||
click_link 'Edit'
|
||||
end
|
||||
fill_in "Name", with: "Different name"
|
||||
click_button "Save"
|
||||
expect(page).to have_content "Garden was successfully updated"
|
||||
expect(page).to have_content "Different name"
|
||||
end
|
||||
|
||||
it "Delete garden" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
visit garden_path(Garden.last)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
click_link 'Delete'
|
||||
end
|
||||
expect(page).to have_content "Garden was successfully deleted"
|
||||
expect(page).to have_content "#{garden.owner}'s gardens"
|
||||
end
|
||||
|
||||
describe "Making a planting inactive from garden show" do
|
||||
it do
|
||||
it "Marking a garden as inactive" do
|
||||
visit garden_path(garden)
|
||||
click_link(class: 'planting-menu')
|
||||
click_link "Mark as finished"
|
||||
find(".datepicker-days td.day", text: "21").click
|
||||
expect(page).to have_content 'Finished'
|
||||
end
|
||||
end
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
click_link "Mark as inactive"
|
||||
end
|
||||
expect(page).to have_content "Garden was successfully updated"
|
||||
expect(page).to have_content "This garden is inactive"
|
||||
|
||||
it "List only active plantings on a garden" do
|
||||
visit gardens_path
|
||||
expect(page).not_to have_content finished_planting.crop_name
|
||||
click_link 'Actions'
|
||||
expect(page).to have_content "Mark as active"
|
||||
expect(page).not_to have_content "Mark as inactive"
|
||||
end
|
||||
|
||||
it "List only active gardens" do
|
||||
visit garden_path(garden)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
click_link "Mark as inactive"
|
||||
end
|
||||
visit gardens_path
|
||||
expect(page).not_to have_link garden_path(garden)
|
||||
end
|
||||
|
||||
it "Create new garden" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
expect(page).to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "New garden"
|
||||
end
|
||||
|
||||
it "Refuse to create new garden with negative area" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "Negative Garden"
|
||||
fill_in "Area", with: -5
|
||||
click_button "Save"
|
||||
expect(page).not_to have_content "Garden was successfully created"
|
||||
expect(page).to have_content "Area must be greater than or equal to 0"
|
||||
end
|
||||
|
||||
context "Clicking edit from the index page" do
|
||||
before do
|
||||
visit gardens_path
|
||||
end
|
||||
|
||||
it "button on index to edit garden" do
|
||||
first('a#garden-actions-button').click
|
||||
click_link href: edit_garden_path(garden)
|
||||
expect(page).to have_content 'Edit garden'
|
||||
end
|
||||
end
|
||||
|
||||
it "Edit garden" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
click_link 'Actions'
|
||||
within '.garden-actions' do
|
||||
click_link 'Edit'
|
||||
end
|
||||
fill_in "Name", with: "Different name"
|
||||
click_button "Save"
|
||||
expect(page).to have_content "Garden was successfully updated"
|
||||
expect(page).to have_content "Different name"
|
||||
end
|
||||
|
||||
it "Delete garden" do
|
||||
visit new_garden_path
|
||||
fill_in "Name", with: "New garden"
|
||||
click_button "Save"
|
||||
visit garden_path(Garden.last)
|
||||
click_link 'Actions'
|
||||
accept_confirm do
|
||||
click_link 'Delete'
|
||||
end
|
||||
expect(page).to have_content "Garden was successfully deleted"
|
||||
expect(page).to have_content "#{garden.owner}'s gardens"
|
||||
end
|
||||
|
||||
describe "Making a planting inactive from garden show" do
|
||||
it do
|
||||
visit garden_path(garden)
|
||||
click_link(class: 'planting-menu')
|
||||
click_link "Mark as finished"
|
||||
find(".datepicker-days td.day", text: "21").click
|
||||
expect(page).to have_content 'Finished'
|
||||
end
|
||||
end
|
||||
|
||||
it "List only active plantings on a garden" do
|
||||
visit gardens_path
|
||||
expect(page).not_to have_content finished_planting.crop_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,10 +3,9 @@ require 'custom_matchers'
|
||||
|
||||
describe "Gardens#index", :js do
|
||||
context "Logged in as member" do
|
||||
include_context 'signed in member'
|
||||
let(:member) { FactoryBot.create :member, login_name: 'shadow' }
|
||||
|
||||
before { login_as member }
|
||||
|
||||
context "with 10 gardens" do
|
||||
before do
|
||||
FactoryBot.create_list :garden, 10, owner: member
|
||||
|
||||
@@ -3,30 +3,30 @@ require 'rails_helper'
|
||||
describe "browse harvests" do
|
||||
subject { page }
|
||||
|
||||
let!(:member) { create :member }
|
||||
let!(:harvest) { create :harvest, owner: member }
|
||||
|
||||
before { login_as member }
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
describe 'blank optional fields' do
|
||||
let!(:harvest) { create :harvest, :no_description }
|
||||
|
||||
describe 'blank optional fields' do
|
||||
let!(:harvest) { create :harvest, :no_description }
|
||||
before { visit harvests_path }
|
||||
|
||||
before { visit harvests_path }
|
||||
|
||||
it 'read more' do
|
||||
expect(subject).not_to have_link "Read more"
|
||||
end
|
||||
end
|
||||
|
||||
describe "filled in optional fields" do
|
||||
let!(:harvest) { create :harvest, :long_description }
|
||||
|
||||
before do
|
||||
visit harvests_path
|
||||
it 'read more' do
|
||||
expect(subject).not_to have_link "Read more"
|
||||
end
|
||||
end
|
||||
|
||||
it 'links to #show' do
|
||||
expect(subject).to have_link harvest.crop.name, href: harvest_path(harvest)
|
||||
describe "filled in optional fields" do
|
||||
let!(:harvest) { create :harvest, :long_description }
|
||||
|
||||
before do
|
||||
visit harvests_path
|
||||
end
|
||||
|
||||
it 'links to #show' do
|
||||
expect(subject).to have_link harvest.crop.name, href: harvest_path(harvest)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user